diff --git a/doc/lmms.1 b/doc/lmms.1 index 45b657fc1..1b0a1801a 100644 --- a/doc/lmms.1 +++ b/doc/lmms.1 @@ -42,8 +42,14 @@ LMMS features components such as a Song Editor, a Beat+Bassline Editor, a Piano .SH OPTIONS .IP "\fB\-r, --render\fP \fIproject-file\fP Render given file to either a wav\- or ogg\-file. See \fB\-f\fP for details -.IP "\fB\-o, --output\fP \fIfile\fP -render into \fIfile\fP +.IP "\fB\-r, --rendertracks\fP \fIproject-file\fP +Render each track into a separate wav\- or ogg\-file. See \fB\-f\fP for details +.IP "\fB\-o, --output\fP \fIpath\fP +Render into \fIpath\fP +.br +For --render, this is interpreted as a file path. +.br +For --render-tracks, this is interpreted as a path to an existing directory. .IP "\fB\-f, --format\fP \fIformat\fP Specify format of render-output where \fIformat\fP is either 'wav' or 'ogg' .IP "\fB\-s, --samplerate\fP \fIsamplerate\fP diff --git a/include/ExportProjectDialog.h b/include/ExportProjectDialog.h index 46ab7fdb2..dfab28561 100644 --- a/include/ExportProjectDialog.h +++ b/include/ExportProjectDialog.h @@ -32,7 +32,7 @@ #include "ui_export_project.h" #include "ProjectRenderer.h" - +#include "RenderManager.h" class ExportProjectDialog : public QDialog, public Ui::ExportProjectDialog { @@ -50,26 +50,17 @@ protected: private slots: void startBtnClicked( void ); void updateTitleBar( int ); - void render(ProjectRenderer* renderer); - void multiRender(); - ProjectRenderer* prepRender(); - void popRender(); void accept(); + void startExport(); private: QString m_fileName; QString m_dirName; QString m_fileExtension; - typedef QVector RenderVector; - RenderVector m_renderers; bool m_multiExport; - typedef QVector TrackVector; - TrackVector m_unmuted; - TrackVector m_unmutedBB; ProjectRenderer::ExportFileFormats m_ft; - TrackVector m_tracksToRender; - ProjectRenderer* m_activeRenderer; + RenderManager* m_renderManager; } ; #endif diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index 08f3a8ab6..9c68d578d 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -87,8 +87,9 @@ public: static ExportFileFormats getFileFormatFromExtension( const QString & _ext ); - static const FileEncodeDevice fileEncodeDevices[]; + static QString getFileExtensionFromFormat( ExportFileFormats fmt ); + static const FileEncodeDevice fileEncodeDevices[]; public slots: void startProcessing(); diff --git a/include/RenderManager.h b/include/RenderManager.h new file mode 100644 index 000000000..2bd35d3b6 --- /dev/null +++ b/include/RenderManager.h @@ -0,0 +1,76 @@ +/* + * RenderManager.h - Provides a uniform interface for rendering the project or + * individual tracks for the CLI and GUI. + * + * Copyright (c) 2015 Ryan Roden-Corrent + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef RENDER_MANAGER_H +#define RENDER_MANAGER_H + +#include + +#include "ProjectRenderer.h" + +class RenderManager : public QObject +{ + Q_OBJECT +public: + RenderManager( + const Mixer::qualitySettings & qualitySettings, + const ProjectRenderer::OutputSettings & outputSettings, + ProjectRenderer::ExportFileFormats fmt, + QString outputPath); + + virtual ~RenderManager(); + + /// Export all unmuted tracks into a single file + void renderProject(); + + /// Export all unmuted tracks into individual file + void renderTracks(); + + void abortProcessing(); + +signals: + void progressChanged( int ); + void finished(); + +private slots: + void renderNextTrack(); + void updateConsoleProgress(); + +private: + QString pathForTrack( const Track *track, int num ); + void restoreMutedState(); + + const Mixer::qualitySettings m_qualitySettings; + const ProjectRenderer::OutputSettings m_outputSettings; + ProjectRenderer::ExportFileFormats m_format; + QString m_outputPath; + + ProjectRenderer* m_activeRenderer; + + QVector m_tracksToRender; + QVector m_unmuted; +} ; + +#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index adc6ef3a9..082951d34 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -51,6 +51,7 @@ set(LMMS_SRCS core/ProjectRenderer.cpp core/ProjectVersion.cpp core/RemotePlugin.cpp + core/RenderManager.cpp core/RingBuffer.cpp core/SampleBuffer.cpp core/SamplePlayHandle.cpp diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index a05ce6c9c..6b44a1d63 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -124,6 +124,15 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( +QString ProjectRenderer::getFileExtensionFromFormat( + ExportFileFormats fmt ) +{ + return fileEncodeDevices[fmt].m_extension; +} + + + + void ProjectRenderer::startProcessing() { diff --git a/src/core/RenderManager.cpp b/src/core/RenderManager.cpp new file mode 100644 index 000000000..2808aa39d --- /dev/null +++ b/src/core/RenderManager.cpp @@ -0,0 +1,214 @@ +/* + * RenderManager - exporting logic common between the CLI and GUI. + * + * Copyright (c) 2015 Ryan Roden-Corrent + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include + +#include "RenderManager.h" +#include "Song.h" +#include "BBTrackContainer.h" +#include "BBTrack.h" +#include "debug.h" + +RenderManager::RenderManager( + const Mixer::qualitySettings & qualitySettings, + const ProjectRenderer::OutputSettings & outputSettings, + ProjectRenderer::ExportFileFormats fmt, + QString outputPath) : + m_qualitySettings(qualitySettings), + m_outputSettings(outputSettings), + m_format(fmt), + m_outputPath(outputPath), + m_activeRenderer(NULL) +{ +} + +RenderManager::~RenderManager() +{ + delete m_activeRenderer; +} + +void RenderManager::abortProcessing() +{ + if ( m_activeRenderer ) { + m_activeRenderer->abortProcessing(); + } + restoreMutedState(); +} + +// Called to render each new track when rendering tracks individually. +void RenderManager::renderNextTrack() +{ + delete m_activeRenderer; + m_activeRenderer = NULL; + + if( m_tracksToRender.isEmpty() ) + { + // nothing left to render + restoreMutedState(); + emit finished(); + } + else + { + // pop the next track from our rendering queue + Track* renderTrack = m_tracksToRender.back(); + m_tracksToRender.pop_back(); + + // mute everything but the track we are about to render + for( auto it = m_unmuted.begin(); it != m_unmuted.end(); ++it ) + { + (*it)->setMuted( (*it) != renderTrack ); + } + + // for multi-render, prefix each output file with a different number + int trackNum = m_tracksToRender.size() + 1; + + // create a renderer for this track + m_activeRenderer = new ProjectRenderer( + m_qualitySettings, + m_outputSettings, + m_format, + pathForTrack(renderTrack, trackNum)); + + if ( m_activeRenderer->isReady() ) + { + // pass progress signals through + connect( m_activeRenderer, SIGNAL( progressChanged( int ) ), + this, SIGNAL( progressChanged( int ) ) ); + + // when it is finished, render the next track + connect( m_activeRenderer, SIGNAL( finished() ), + this, SLOT( renderNextTrack() ) ); + + m_activeRenderer->startProcessing(); + } + else + { + qDebug( "Renderer failed to acquire a file device!" ); + renderNextTrack(); + } + } +} + +// Render the song into individual tracks +void RenderManager::renderTracks() +{ + const TrackContainer::TrackList & tl = Engine::getSong()->tracks(); + + // find all currently unnmuted tracks -- we want to render these. + for( auto it = tl.begin(); it != tl.end(); ++it ) + { + Track* tk = (*it); + Track::TrackTypes type = tk->type(); + + // Don't mute automation tracks + if ( tk->isMuted() == false && + ( type == Track::InstrumentTrack || type == Track::SampleTrack ) ) + { + m_unmuted.push_back(tk); + } + } + + const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks(); + for( auto it = t2.begin(); it != t2.end(); ++it ) + { + Track* tk = (*it); + if ( tk->isMuted() == false ) + { + m_unmuted.push_back(tk); + } + } + + // copy the list of unmuted tracks into our rendering queue. + // we need to remember which tracks were unmuted to restore state at the end. + m_tracksToRender = m_unmuted; + + renderNextTrack(); +} + +// Render the song into a single track +void RenderManager::renderProject() +{ + m_activeRenderer = new ProjectRenderer( + m_qualitySettings, + m_outputSettings, + m_format, + m_outputPath); + + if( m_activeRenderer->isReady() ) + { + // pass progress signals through + connect( m_activeRenderer, SIGNAL( progressChanged( int ) ), + this, SIGNAL( progressChanged( int ) ) ); + + // as we have not queued any tracks, renderNextTrack will just clean up + connect( m_activeRenderer, SIGNAL( finished() ), + this, SLOT( renderNextTrack() ) ); + + m_activeRenderer->startProcessing(); + } + else + { + qDebug( "Renderer failed to acquire a file device!" ); + emit finished(); + } +} + +// Unmute all tracks that were muted while rendering tracks +void RenderManager::restoreMutedState() +{ + while( !m_unmuted.isEmpty() ) + { + Track* restoreTrack = m_unmuted.back(); + m_unmuted.pop_back(); + restoreTrack->setMuted( false ); + } +} + +// Determine the output path for a track when rendering tracks individually +QString RenderManager::pathForTrack(const Track *track, int num) +{ + QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format ); + QString name = track->name(); + name = name.remove(QRegExp("[^a-zA-Z]")); + name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension ); + return QDir(m_outputPath).filePath(name); +} + +void RenderManager::updateConsoleProgress() +{ + if ( m_activeRenderer ) + { + m_activeRenderer->updateConsoleProgress(); + + int totalNum = m_unmuted.size(); + if ( totalNum > 0 ) + { + // we are rendering multiple tracks, append a track counter to the output + int trackNum = totalNum - m_tracksToRender.size(); + fprintf( stderr, "(%d/%d)", trackNum, totalNum ); + } + } +} diff --git a/src/core/main.cpp b/src/core/main.cpp index d656c2faf..8ff44e573 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -66,6 +66,7 @@ #include "ImportFilter.h" #include "MainWindow.h" #include "ProjectRenderer.h" +#include "RenderManager.h" #include "DataFile.h" #include "Song.h" @@ -126,7 +127,10 @@ void printHelp() " [ -h ]\n" " [ ]\n\n" "-r, --render Render given project file\n" - "-o, --output Render into \n" + " --rendertracks Render each track to a different file\n" + "-o, --output Render into \n" + " For --render, provide a file path\n" + " For --rendertracks, provide a directory path\n" "-f, --format Specify format of render-output where\n" " Format is either 'wav' or 'ogg'.\n" "-s, --samplerate Specify output samplerate in Hz\n" @@ -172,6 +176,7 @@ int main( int argc, char * * argv ) bool exitAfterImport = false; bool allowRoot = false; bool renderLoop = false; + bool renderTracks = false; QString fileToLoad, fileToImport, renderOut, profilerOutputFile; // first of two command-line parsing stages @@ -185,6 +190,11 @@ int main( int argc, char * * argv ) { coreOnly = true; } + else if( arg == "--rendertracks" ) + { + coreOnly = true; + renderTracks = true; + } else if( arg == "--allowroot" ) { allowRoot = true; @@ -288,7 +298,7 @@ int main( int argc, char * * argv ) return EXIT_SUCCESS; } - else if( arg == "--render" || arg == "-r" ) + else if( arg == "--render" || arg == "-r" || arg == "--rendertracks" ) { ++i; @@ -301,7 +311,7 @@ int main( int argc, char * * argv ) fileToLoad = QString::fromLocal8Bit( argv[i] ); - renderOut = baseName( fileToLoad ) + "."; + renderOut = fileToLoad; } else if( arg == "--loop" || arg == "-l" ) { @@ -319,7 +329,7 @@ int main( int argc, char * * argv ) } - renderOut = baseName( QString::fromLocal8Bit( argv[i] ) ) + "."; + renderOut = QString::fromLocal8Bit( argv[i] ); } else if( arg == "--format" || arg == "-f" ) { @@ -588,9 +598,16 @@ int main( int argc, char * * argv ) Engine::getSong()->setExportLoop( renderLoop ); + // when rendering multiple tracks, renderOut is a directory + // otherwise, it is a file, so we need to append the file extension + if ( !renderTracks ) + { + renderOut = baseName( renderOut ) + + ProjectRenderer::getFileExtensionFromFormat(eff); + } + // create renderer - QString extension = ( eff == ProjectRenderer::WaveFile ) ? "wav" : "ogg"; - ProjectRenderer * r = new ProjectRenderer( qs, os, eff, renderOut + extension ); + RenderManager * r = new RenderManager( qs, os, eff, renderOut ); QCoreApplication::instance()->connect( r, SIGNAL( finished() ), SLOT( quit() ) ); @@ -606,7 +623,14 @@ int main( int argc, char * * argv ) } // start now! - r->startProcessing(); + if ( renderTracks ) + { + r->renderTracks(); + } + else + { + r->renderProject(); + } } else // otherwise, start the GUI { diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index d81d6b917..23794db80 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "ExportProjectDialog.h" #include "Song.h" @@ -41,10 +42,10 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name, m_fileName( _file_name ), m_fileExtension(), m_multiExport( multi_export ), - m_activeRenderer( NULL ) + m_renderManager( NULL ) { setupUi( this ); - setWindowTitle( tr( "Export project to %1" ).arg( + setWindowTitle( tr( "Export project to %1" ).arg( QFileInfo( _file_name ).fileName() ) ); // get the extension of the chosen file @@ -80,7 +81,6 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name, connect( startButton, SIGNAL( clicked() ), this, SLOT( startBtnClicked() ) ); - } @@ -88,12 +88,7 @@ ExportProjectDialog::ExportProjectDialog( const QString & _file_name, ExportProjectDialog::~ExportProjectDialog() { - - for( RenderVector::ConstIterator it = m_renderers.begin(); - it != m_renderers.end(); ++it ) - { - delete (*it); - } + delete m_renderManager; } @@ -101,14 +96,12 @@ ExportProjectDialog::~ExportProjectDialog() void ExportProjectDialog::reject() { - for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) - { - (*it)->abortProcessing(); + if( m_renderManager ) { + m_renderManager->abortProcessing(); } - if( m_activeRenderer ) { - m_activeRenderer->abortProcessing(); - } + delete m_renderManager; + m_renderManager = NULL; QDialog::reject(); } @@ -117,24 +110,10 @@ void ExportProjectDialog::reject() void ExportProjectDialog::accept() { - // If more to render, kick off next render job - if( m_renderers.isEmpty() == false ) - { - popRender(); - } - else - { - // If done, then reset mute states - while( m_unmuted.isEmpty() == false ) - { - Track* restoreTrack = m_unmuted.back(); - m_unmuted.pop_back(); - restoreTrack->setMuted( false ); - } + delete m_renderManager; + m_renderManager = NULL; - QDialog::accept(); - - } + QDialog::accept(); } @@ -142,16 +121,8 @@ void ExportProjectDialog::accept() void ExportProjectDialog::closeEvent( QCloseEvent * _ce ) { - for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) - { - if( (*it)->isRunning() ) - { - (*it)->abortProcessing(); - } - } - - if( m_activeRenderer && m_activeRenderer->isRunning() ) { - m_activeRenderer->abortProcessing(); + if( m_renderManager ) { + m_renderManager->abortProcessing(); } QDialog::closeEvent( _ce ); @@ -159,94 +130,8 @@ void ExportProjectDialog::closeEvent( QCloseEvent * _ce ) -void ExportProjectDialog::popRender() -{ - if( m_multiExport && m_tracksToRender.isEmpty() == false ) - { - Track* renderTrack = m_tracksToRender.back(); - m_tracksToRender.pop_back(); - // Set must states for song tracks - for( TrackVector::ConstIterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it ) - { - if( (*it) == renderTrack ) - { - (*it)->setMuted( false ); - } - else - { - (*it)->setMuted( true ); - } - } - } - - - // Pop next render job and start - m_activeRenderer = m_renderers.back(); - m_renderers.pop_back(); - render( m_activeRenderer ); -} - - - -void ExportProjectDialog::multiRender() -{ - m_dirName = m_fileName; - QString path = QDir(m_fileName).filePath("text.txt"); - - int x = 1; - - const TrackContainer::TrackList & tl = Engine::getSong()->tracks(); - - // Check for all unmuted tracks. Remember list. - for( TrackContainer::TrackList::ConstIterator it = tl.begin(); - it != tl.end(); ++it ) - { - Track* tk = (*it); - Track::TrackTypes type = tk->type(); - // Don't mute automation tracks - if ( tk->isMuted() == false && - ( type == Track::InstrumentTrack || type == Track::SampleTrack ) ) - { - m_unmuted.push_back(tk); - QString nextName = tk->name(); - nextName = nextName.remove(QRegExp("[^a-zA-Z]")); - QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension ); - m_fileName = QDir(m_dirName).filePath(name); - prepRender(); - } - else if (! tk->isMuted() && type == Track::BBTrack ) - { - m_unmutedBB.push_back(tk); - } - - - } - - const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks(); - for( TrackContainer::TrackList::ConstIterator it = t2.begin(); it != t2.end(); ++it ) - { - Track* tk = (*it); - if ( tk->isMuted() == false ) - { - m_unmuted.push_back(tk); - QString nextName = tk->name(); - nextName = nextName.remove(QRegExp("[^a-zA-Z]")); - QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension ); - m_fileName = QDir(m_dirName).filePath(name); - prepRender(); - } - } - - - m_tracksToRender = m_unmuted; - - popRender(); -} - - - -ProjectRenderer* ExportProjectDialog::prepRender() +void ExportProjectDialog::startExport() { Mixer::qualitySettings qs = Mixer::qualitySettings( @@ -262,38 +147,33 @@ ProjectRenderer* ExportProjectDialog::prepRender() bitrates[ bitrateCB->currentIndex() ], static_cast( depthCB->currentIndex() ) ); + m_renderManager = new RenderManager( qs, os, m_ft, m_fileName ); + Engine::getSong()->setExportLoop( exportLoopCB->isChecked() ); Engine::getSong()->setRenderBetweenMarkers( renderMarkersCB->isChecked() ); - ProjectRenderer* renderer = new ProjectRenderer( qs, os, m_ft, m_fileName ); + connect( m_renderManager, SIGNAL( progressChanged( int ) ), + progressBar, SLOT( setValue( int ) ) ); + connect( m_renderManager, SIGNAL( progressChanged( int ) ), + this, SLOT( updateTitleBar( int ) )) ; + connect( m_renderManager, SIGNAL( finished() ), + this, SLOT( accept() ) ); + connect( m_renderManager, SIGNAL( finished() ), + gui->mainWindow(), SLOT( resetWindowTitle() ) ); - m_renderers.push_back(renderer); - - return renderer; -} - - - -void ExportProjectDialog::render( ProjectRenderer* renderer ) -{ - - if( renderer->isReady() ) + if ( m_multiExport ) { - connect( renderer, SIGNAL( progressChanged( int ) ), progressBar, SLOT( setValue( int ) ) ); - connect( renderer, SIGNAL( progressChanged( int ) ), this, SLOT( updateTitleBar( int ) )) ; - connect( renderer, SIGNAL( finished() ), this, SLOT( accept() ) ); - connect( renderer, SIGNAL( finished() ), gui->mainWindow(), SLOT( resetWindowTitle() ) ); - - renderer->startProcessing(); + m_renderManager->renderTracks(); } else { - accept(); + m_renderManager->renderProject(); } } + void ExportProjectDialog::startBtnClicked() { m_ft = ProjectRenderer::NumFileFormats; @@ -325,15 +205,7 @@ void ExportProjectDialog::startBtnClicked() updateTitleBar( 0 ); - if (m_multiExport==true) - { - multiRender(); - } - else - { - prepRender(); - popRender(); - } + startExport(); }