mirror of
https://github.com/LMMS/lmms.git
synced 2026-03-17 21:48:23 -04:00
Since addition of automation, controllers etc. the freeze functionality has been rather broken and thus adds no value anymore. In order to not confuse users with broken functionality, remove it at all. Closes #345.
1275 lines
26 KiB
C++
1275 lines
26 KiB
C++
/*
|
|
* song.cpp - root of the model tree
|
|
*
|
|
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
*
|
|
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
|
*
|
|
* 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 <QtCore/QCoreApplication>
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtGui/QMessageBox>
|
|
#include <QApplication>
|
|
|
|
#include <math.h>
|
|
|
|
#include "song.h"
|
|
#include "AutomationTrack.h"
|
|
#include "AutomationEditor.h"
|
|
#include "bb_editor.h"
|
|
#include "bb_track.h"
|
|
#include "bb_track_container.h"
|
|
#include "config_mgr.h"
|
|
#include "ControllerRackView.h"
|
|
#include "ControllerConnection.h"
|
|
#include "embed.h"
|
|
#include "EnvelopeAndLfoParameters.h"
|
|
#include "export_project_dialog.h"
|
|
#include "FxMixer.h"
|
|
#include "FxMixerView.h"
|
|
#include "ImportFilter.h"
|
|
#include "InstrumentTrack.h"
|
|
#include "MainWindow.h"
|
|
#include "FileDialog.h"
|
|
#include "MidiClient.h"
|
|
#include "DataFile.h"
|
|
#include "NotePlayHandle.h"
|
|
#include "pattern.h"
|
|
#include "piano_roll.h"
|
|
#include "ProjectJournal.h"
|
|
#include "project_notes.h"
|
|
#include "ProjectRenderer.h"
|
|
#include "rename_dialog.h"
|
|
#include "song_editor.h"
|
|
#include "templates.h"
|
|
#include "text_float.h"
|
|
#include "timeline.h"
|
|
#include "PeakController.h"
|
|
|
|
|
|
tick_t MidiTime::s_ticksPerTact = DefaultTicksPerTact;
|
|
|
|
|
|
|
|
song::song() :
|
|
TrackContainer(),
|
|
m_globalAutomationTrack( dynamic_cast<AutomationTrack *>(
|
|
track::create( track::HiddenAutomationTrack,
|
|
this ) ) ),
|
|
m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ),
|
|
m_timeSigModel( this ),
|
|
m_oldTicksPerTact( DefaultTicksPerTact ),
|
|
m_masterVolumeModel( 100, 0, 200, this, tr( "Master volume" ) ),
|
|
m_masterPitchModel( 0, -12, 12, this, tr( "Master pitch" ) ),
|
|
m_fileName(),
|
|
m_oldFileName(),
|
|
m_modified( false ),
|
|
m_recording( false ),
|
|
m_exporting( false ),
|
|
m_exportLoop( false ),
|
|
m_playing( false ),
|
|
m_paused( false ),
|
|
m_loadingProject( false ),
|
|
m_playMode( Mode_None ),
|
|
m_length( 0 ),
|
|
m_trackToPlay( NULL ),
|
|
m_patternToPlay( NULL ),
|
|
m_loopPattern( false ),
|
|
m_elapsedMilliSeconds( 0 ),
|
|
m_elapsedTicks( 0 ),
|
|
m_elapsedTacts( 0 )
|
|
{
|
|
connect( &m_tempoModel, SIGNAL( dataChanged() ),
|
|
this, SLOT( setTempo() ) );
|
|
connect( &m_tempoModel, SIGNAL( dataUnchanged() ),
|
|
this, SLOT( setTempo() ) );
|
|
connect( &m_timeSigModel, SIGNAL( dataChanged() ),
|
|
this, SLOT( setTimeSignature() ) );
|
|
|
|
|
|
connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this,
|
|
SLOT( updateFramesPerTick() ) );
|
|
|
|
connect( &m_masterVolumeModel, SIGNAL( dataChanged() ),
|
|
this, SLOT( masterVolumeChanged() ) );
|
|
/* connect( &m_masterPitchModel, SIGNAL( dataChanged() ),
|
|
this, SLOT( masterPitchChanged() ) );*/
|
|
|
|
qRegisterMetaType<note>( "note" );
|
|
}
|
|
|
|
|
|
|
|
|
|
song::~song()
|
|
{
|
|
m_playing = false;
|
|
delete m_globalAutomationTrack;
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::masterVolumeChanged()
|
|
{
|
|
engine::mixer()->setMasterGain( m_masterVolumeModel.value() /
|
|
100.0f );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::setTempo()
|
|
{
|
|
const bpm_t tempo = (bpm_t) m_tempoModel.value();
|
|
engine::mixer()->lock();
|
|
PlayHandleList & playHandles = engine::mixer()->playHandles();
|
|
for( PlayHandleList::Iterator it = playHandles.begin();
|
|
it != playHandles.end(); ++it )
|
|
{
|
|
NotePlayHandle * nph = dynamic_cast<NotePlayHandle *>( *it );
|
|
if( nph && !nph->isReleased() )
|
|
{
|
|
nph->resize( tempo );
|
|
}
|
|
}
|
|
engine::mixer()->unlock();
|
|
|
|
engine::updateFramesPerTick();
|
|
|
|
m_vstSyncController.setTempo( tempo );
|
|
|
|
emit tempoChanged( tempo );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::setTimeSignature()
|
|
{
|
|
MidiTime::setTicksPerTact( ticksPerTact() );
|
|
emit timeSignatureChanged( m_oldTicksPerTact, ticksPerTact() );
|
|
emit dataChanged();
|
|
m_oldTicksPerTact = ticksPerTact();
|
|
|
|
m_vstSyncController.setTimeSignature( getTimeSigModel().getNumerator(), getTimeSigModel().getDenominator() );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::savePos()
|
|
{
|
|
timeLine * tl = m_playPos[m_playMode].m_timeLine;
|
|
|
|
if( tl != NULL )
|
|
{
|
|
tl->savePos( m_playPos[m_playMode] );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::processNextBuffer()
|
|
{
|
|
if( m_playing == false )
|
|
{
|
|
return;
|
|
}
|
|
|
|
TrackList track_list;
|
|
int tco_num = -1;
|
|
|
|
switch( m_playMode )
|
|
{
|
|
case Mode_PlaySong:
|
|
track_list = tracks();
|
|
// at song-start we have to reset the LFOs
|
|
if( m_playPos[Mode_PlaySong] == 0 )
|
|
{
|
|
EnvelopeAndLfoParameters::instances()->reset();
|
|
}
|
|
break;
|
|
|
|
case Mode_PlayTrack:
|
|
track_list.push_back( m_trackToPlay );
|
|
break;
|
|
|
|
case Mode_PlayBB:
|
|
if( engine::getBBTrackContainer()->numOfBBs() > 0 )
|
|
{
|
|
tco_num = engine::getBBTrackContainer()->
|
|
currentBB();
|
|
track_list.push_back( bbTrack::findBBTrack(
|
|
tco_num ) );
|
|
}
|
|
break;
|
|
|
|
case Mode_PlayPattern:
|
|
if( m_patternToPlay != NULL )
|
|
{
|
|
tco_num = m_patternToPlay->getTrack()->
|
|
getTCONum( m_patternToPlay );
|
|
track_list.push_back(
|
|
m_patternToPlay->getTrack() );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
|
|
}
|
|
|
|
if( track_list.empty() == true )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// check for looping-mode and act if necessary
|
|
timeLine * tl = m_playPos[m_playMode].m_timeLine;
|
|
bool check_loop = tl != NULL && m_exporting == false &&
|
|
tl->loopPointsEnabled();
|
|
if( check_loop )
|
|
{
|
|
if( m_playPos[m_playMode] < tl->loopBegin() ||
|
|
m_playPos[m_playMode] >= tl->loopEnd() )
|
|
{
|
|
m_elapsedMilliSeconds = (tl->loopBegin().getTicks()*60*1000/48)/getTempo();
|
|
m_playPos[m_playMode].setTicks(
|
|
tl->loopBegin().getTicks() );
|
|
}
|
|
}
|
|
|
|
f_cnt_t total_frames_played = 0;
|
|
const float frames_per_tick = engine::framesPerTick();
|
|
|
|
while( total_frames_played
|
|
< engine::mixer()->framesPerPeriod() )
|
|
{
|
|
m_vstSyncController.update();
|
|
|
|
f_cnt_t played_frames = engine::mixer()->framesPerPeriod() - total_frames_played;
|
|
|
|
float current_frame = m_playPos[m_playMode].currentFrame();
|
|
// did we play a tick?
|
|
if( current_frame >= frames_per_tick )
|
|
{
|
|
int ticks = m_playPos[m_playMode].getTicks() + (int)( current_frame / frames_per_tick );
|
|
|
|
m_vstSyncController.setAbsolutePosition( ticks );
|
|
|
|
// did we play a whole tact?
|
|
if( ticks >= MidiTime::ticksPerTact() )
|
|
{
|
|
// per default we just continue playing even if
|
|
// there's no more stuff to play
|
|
// (song-play-mode)
|
|
int max_tact = m_playPos[m_playMode].getTact()
|
|
+ 2;
|
|
|
|
// then decide whether to go over to next tact
|
|
// or to loop back to first tact
|
|
if( m_playMode == Mode_PlayBB )
|
|
{
|
|
max_tact = engine::getBBTrackContainer()
|
|
->lengthOfCurrentBB();
|
|
}
|
|
else if( m_playMode == Mode_PlayPattern &&
|
|
m_loopPattern == true &&
|
|
tl != NULL &&
|
|
tl->loopPointsEnabled() == false )
|
|
{
|
|
max_tact = m_patternToPlay->length()
|
|
.getTact();
|
|
}
|
|
|
|
// end of played object reached?
|
|
if( m_playPos[m_playMode].getTact() + 1
|
|
>= max_tact )
|
|
{
|
|
// then start from beginning and keep
|
|
// offset
|
|
ticks = ticks % ( max_tact * MidiTime::ticksPerTact() );
|
|
|
|
m_vstSyncController.setAbsolutePosition( ticks );
|
|
}
|
|
}
|
|
m_playPos[m_playMode].setTicks( ticks );
|
|
|
|
if( check_loop )
|
|
{
|
|
m_vstSyncController.startCycle( tl->loopBegin().getTicks(), tl->loopEnd().getTicks() );
|
|
|
|
if( m_playPos[m_playMode] >= tl->loopEnd() )
|
|
{
|
|
m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() );
|
|
m_elapsedMilliSeconds = ((tl->loopBegin().getTicks())*60*1000/48)/getTempo();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_vstSyncController.stopCycle();
|
|
}
|
|
|
|
current_frame = fmodf( current_frame, frames_per_tick );
|
|
m_playPos[m_playMode].setCurrentFrame( current_frame );
|
|
}
|
|
|
|
f_cnt_t last_frames = (f_cnt_t)frames_per_tick -
|
|
(f_cnt_t) current_frame;
|
|
// skip last frame fraction
|
|
if( last_frames == 0 )
|
|
{
|
|
++total_frames_played;
|
|
m_playPos[m_playMode].setCurrentFrame( current_frame
|
|
+ 1.0f );
|
|
continue;
|
|
}
|
|
// do we have some samples left in this tick but these are
|
|
// less then samples we have to play?
|
|
if( last_frames < played_frames )
|
|
{
|
|
// then set played_samples to remaining samples, the
|
|
// rest will be played in next loop
|
|
played_frames = last_frames;
|
|
}
|
|
|
|
if( (f_cnt_t) current_frame == 0 )
|
|
{
|
|
if( m_playMode == Mode_PlaySong )
|
|
{
|
|
m_globalAutomationTrack->play(
|
|
m_playPos[m_playMode],
|
|
played_frames,
|
|
total_frames_played, tco_num );
|
|
}
|
|
|
|
// loop through all tracks and play them
|
|
for( int i = 0; i < track_list.size(); ++i )
|
|
{
|
|
track_list[i]->play( m_playPos[m_playMode],
|
|
played_frames,
|
|
total_frames_played, tco_num );
|
|
}
|
|
}
|
|
|
|
// update frame-counters
|
|
total_frames_played += played_frames;
|
|
m_playPos[m_playMode].setCurrentFrame( played_frames +
|
|
current_frame );
|
|
m_elapsedMilliSeconds += (((played_frames/frames_per_tick)*60*1000/48)/getTempo());
|
|
m_elapsedTacts = m_playPos[Mode_PlaySong].getTact();
|
|
m_elapsedTicks = (m_playPos[Mode_PlaySong].getTicks()%ticksPerTact())/48;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::playSong()
|
|
{
|
|
m_recording = false;
|
|
|
|
if( isStopped() == false )
|
|
{
|
|
stop();
|
|
}
|
|
|
|
m_playMode = Mode_PlaySong;
|
|
m_playing = true;
|
|
m_paused = false;
|
|
|
|
m_vstSyncController.setPlaybackState( true );
|
|
|
|
savePos();
|
|
|
|
if( QApplication::type() != QApplication::Tty )
|
|
{
|
|
engine::updatePlayPauseIcons();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::record()
|
|
{
|
|
m_recording = true;
|
|
// TODO: Implement
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::playAndRecord()
|
|
{
|
|
playSong();
|
|
m_recording = true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::playTrack( track * _trackToPlay )
|
|
{
|
|
if( isStopped() == false )
|
|
{
|
|
stop();
|
|
}
|
|
m_trackToPlay = _trackToPlay;
|
|
|
|
m_playMode = Mode_PlayTrack;
|
|
m_playing = true;
|
|
m_paused = false;
|
|
|
|
m_vstSyncController.setPlaybackState( true );
|
|
|
|
savePos();
|
|
|
|
engine::updatePlayPauseIcons();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::playBB()
|
|
{
|
|
if( isStopped() == false )
|
|
{
|
|
stop();
|
|
}
|
|
|
|
m_playMode = Mode_PlayBB;
|
|
m_playing = true;
|
|
m_paused = false;
|
|
|
|
m_vstSyncController.setPlaybackState( true );
|
|
|
|
savePos();
|
|
|
|
engine::updatePlayPauseIcons();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::playPattern( pattern * _patternToPlay, bool _loop )
|
|
{
|
|
if( isStopped() == false )
|
|
{
|
|
stop();
|
|
}
|
|
|
|
m_patternToPlay = _patternToPlay;
|
|
m_loopPattern = _loop;
|
|
|
|
if( m_patternToPlay != NULL )
|
|
{
|
|
m_playMode = Mode_PlayPattern;
|
|
m_playing = true;
|
|
m_paused = false;
|
|
}
|
|
|
|
savePos();
|
|
|
|
engine::updatePlayPauseIcons();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::updateLength()
|
|
{
|
|
m_length = 0;
|
|
m_tracksMutex.lockForRead();
|
|
for( TrackList::const_iterator it = tracks().begin();
|
|
it != tracks().end(); ++it )
|
|
{
|
|
const tact_t cur = ( *it )->length();
|
|
if( cur > m_length )
|
|
{
|
|
m_length = cur;
|
|
}
|
|
}
|
|
m_tracksMutex.unlock();
|
|
|
|
emit lengthChanged( m_length );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::setPlayPos( tick_t _ticks, PlayModes _play_mode )
|
|
{
|
|
m_elapsedTicks += m_playPos[_play_mode].getTicks() - _ticks;
|
|
m_elapsedMilliSeconds += (((( _ticks - m_playPos[_play_mode].getTicks()))*60*1000/48)/getTempo());
|
|
m_playPos[_play_mode].setTicks( _ticks );
|
|
m_playPos[_play_mode].setCurrentFrame( 0.0f );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::togglePause()
|
|
{
|
|
if( m_paused == true )
|
|
{
|
|
m_playing = true;
|
|
m_paused = false;
|
|
}
|
|
else
|
|
{
|
|
m_playing = false;
|
|
m_paused = true;
|
|
}
|
|
|
|
m_vstSyncController.setPlaybackState( m_playing );
|
|
|
|
engine::updatePlayPauseIcons();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::stop()
|
|
{
|
|
timeLine * tl = m_playPos[m_playMode].m_timeLine;
|
|
m_playing = false;
|
|
m_paused = false;
|
|
m_recording = true;
|
|
|
|
if( tl != NULL )
|
|
{
|
|
|
|
switch( tl->behaviourAtStop() )
|
|
{
|
|
case timeLine::BackToZero:
|
|
m_playPos[m_playMode].setTicks( 0 );
|
|
m_elapsedMilliSeconds = 0;
|
|
break;
|
|
|
|
case timeLine::BackToStart:
|
|
if( tl->savedPos() >= 0 )
|
|
{
|
|
m_playPos[m_playMode].setTicks( tl->savedPos().getTicks() );
|
|
m_elapsedMilliSeconds = (((tl->savedPos().getTicks())*60*1000/48)/getTempo());
|
|
tl->savePos( -1 );
|
|
}
|
|
break;
|
|
|
|
case timeLine::KeepStopPosition:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_playPos[m_playMode].setTicks( 0 );
|
|
m_elapsedMilliSeconds = 0;
|
|
}
|
|
|
|
m_playPos[m_playMode].setCurrentFrame( 0 );
|
|
|
|
m_vstSyncController.setPlaybackState( m_exporting );
|
|
m_vstSyncController.setAbsolutePosition( m_playPos[m_playMode].getTicks() );
|
|
|
|
// remove all note-play-handles that are active
|
|
engine::mixer()->clear();
|
|
|
|
m_playMode = Mode_None;
|
|
|
|
if(QApplication::type() != QApplication::Tty) {
|
|
engine::updatePlayPauseIcons();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::startExport()
|
|
{
|
|
stop();
|
|
|
|
playSong();
|
|
|
|
m_exporting = true;
|
|
|
|
m_vstSyncController.setPlaybackState( true );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::stopExport()
|
|
{
|
|
stop();
|
|
m_exporting = false;
|
|
m_exportLoop = false;
|
|
|
|
m_vstSyncController.setPlaybackState( m_playing );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::insertBar()
|
|
{
|
|
m_tracksMutex.lockForRead();
|
|
for( TrackList::const_iterator it = tracks().begin();
|
|
it != tracks().end(); ++it )
|
|
{
|
|
( *it )->insertTact( m_playPos[Mode_PlaySong] );
|
|
}
|
|
m_tracksMutex.unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::removeBar()
|
|
{
|
|
m_tracksMutex.lockForRead();
|
|
for( TrackList::const_iterator it = tracks().begin();
|
|
it != tracks().end(); ++it )
|
|
{
|
|
( *it )->removeTact( m_playPos[Mode_PlaySong] );
|
|
}
|
|
m_tracksMutex.unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::addBBTrack()
|
|
{
|
|
engine::mixer()->lock();
|
|
track * t = track::create( track::BBTrack, this );
|
|
engine::getBBTrackContainer()->setCurrentBB(
|
|
bbTrack::numOfBBTrack( t ) );
|
|
engine::mixer()->unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::addSampleTrack()
|
|
{
|
|
engine::mixer()->lock();
|
|
(void) track::create( track::SampleTrack, this );
|
|
engine::mixer()->unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::addAutomationTrack()
|
|
{
|
|
engine::mixer()->lock();
|
|
(void) track::create( track::AutomationTrack, this );
|
|
engine::mixer()->unlock();
|
|
}
|
|
|
|
|
|
|
|
|
|
bpm_t song::getTempo()
|
|
{
|
|
return (bpm_t) m_tempoModel.value();
|
|
}
|
|
|
|
|
|
|
|
|
|
AutomationPattern * song::tempoAutomationPattern()
|
|
{
|
|
return AutomationPattern::globalAutomationPattern( &m_tempoModel );
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::clearProject()
|
|
{
|
|
engine::projectJournal()->setJournalling( false );
|
|
|
|
if( m_playing )
|
|
{
|
|
stop();
|
|
}
|
|
|
|
for( int i = 0; i < Mode_Count; i++ )
|
|
{
|
|
setPlayPos( 0, ( PlayModes )i );
|
|
}
|
|
|
|
|
|
engine::mixer()->lock();
|
|
if( engine::getBBEditor() )
|
|
{
|
|
engine::getBBEditor()->clearAllTracks();
|
|
}
|
|
if( engine::getSongEditor() )
|
|
{
|
|
engine::getSongEditor()->clearAllTracks();
|
|
}
|
|
if( engine::fxMixerView() )
|
|
{
|
|
engine::fxMixerView()->clear();
|
|
}
|
|
QCoreApplication::sendPostedEvents();
|
|
engine::getBBTrackContainer()->clearAllTracks();
|
|
clearAllTracks();
|
|
|
|
engine::fxMixer()->clear();
|
|
|
|
if( engine::automationEditor() )
|
|
{
|
|
engine::automationEditor()->setCurrentPattern( NULL );
|
|
}
|
|
|
|
m_tempoModel.reset();
|
|
m_masterVolumeModel.reset();
|
|
m_masterPitchModel.reset();
|
|
m_timeSigModel.reset();
|
|
|
|
AutomationPattern::globalAutomationPattern( &m_tempoModel )->clear();
|
|
AutomationPattern::globalAutomationPattern( &m_masterVolumeModel )->
|
|
clear();
|
|
AutomationPattern::globalAutomationPattern( &m_masterPitchModel )->
|
|
clear();
|
|
|
|
engine::mixer()->unlock();
|
|
|
|
if( engine::getProjectNotes() )
|
|
{
|
|
engine::getProjectNotes()->clear();
|
|
}
|
|
|
|
// Move to function
|
|
while( !m_controllers.empty() )
|
|
{
|
|
delete m_controllers.last();
|
|
}
|
|
|
|
emit dataChanged();
|
|
|
|
engine::projectJournal()->clearJournal();
|
|
|
|
engine::projectJournal()->setJournalling( true );
|
|
|
|
InstrumentTrackView::cleanupWindowCache();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// create new file
|
|
void song::createNewProject()
|
|
{
|
|
QString default_template = configManager::inst()->userProjectsDir()
|
|
+ "templates/default.mpt";
|
|
|
|
if( QFile::exists( default_template ) )
|
|
{
|
|
createNewProjectFromTemplate( default_template );
|
|
return;
|
|
}
|
|
|
|
default_template = configManager::inst()->factoryProjectsDir()
|
|
+ "templates/default.mpt";
|
|
if( QFile::exists( default_template ) )
|
|
{
|
|
createNewProjectFromTemplate( default_template );
|
|
return;
|
|
}
|
|
|
|
m_loadingProject = true;
|
|
|
|
clearProject();
|
|
|
|
engine::projectJournal()->setJournalling( false );
|
|
|
|
m_fileName = m_oldFileName = "";
|
|
|
|
track * t;
|
|
t = track::create( track::InstrumentTrack, this );
|
|
dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
|
|
"tripleoscillator" );
|
|
t = track::create( track::InstrumentTrack,
|
|
engine::getBBTrackContainer() );
|
|
dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
|
|
"kicker" );
|
|
track::create( track::SampleTrack, this );
|
|
track::create( track::BBTrack, this );
|
|
track::create( track::AutomationTrack, this );
|
|
|
|
m_tempoModel.setInitValue( DefaultTempo );
|
|
m_timeSigModel.reset();
|
|
m_masterVolumeModel.setInitValue( 100 );
|
|
m_masterPitchModel.setInitValue( 0 );
|
|
|
|
QCoreApplication::instance()->processEvents();
|
|
|
|
m_loadingProject = false;
|
|
|
|
engine::getBBTrackContainer()->updateAfterTrackAdd();
|
|
|
|
engine::projectJournal()->setJournalling( true );
|
|
|
|
QCoreApplication::sendPostedEvents();
|
|
|
|
m_modified = false;
|
|
|
|
if( engine::mainWindow() )
|
|
{
|
|
engine::mainWindow()->resetWindowTitle();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::createNewProjectFromTemplate( const QString & _template )
|
|
{
|
|
loadProject( _template );
|
|
// clear file-name so that user doesn't overwrite template when
|
|
// saving...
|
|
m_fileName = m_oldFileName = "";
|
|
// update window title
|
|
if( engine::mainWindow() )
|
|
{
|
|
engine::mainWindow()->resetWindowTitle();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// load given song
|
|
void song::loadProject( const QString & _file_name )
|
|
{
|
|
m_loadingProject = true;
|
|
|
|
clearProject();
|
|
|
|
engine::projectJournal()->setJournalling( false );
|
|
|
|
m_fileName = _file_name;
|
|
m_oldFileName = _file_name;
|
|
|
|
DataFile dataFile( m_fileName );
|
|
// if file could not be opened, head-node is null and we create
|
|
// new project
|
|
if( dataFile.head().isNull() )
|
|
{
|
|
createNewProject();
|
|
return;
|
|
}
|
|
|
|
engine::mixer()->lock();
|
|
|
|
// get the header information from the DOM
|
|
m_tempoModel.loadSettings( dataFile.head(), "bpm" );
|
|
m_timeSigModel.loadSettings( dataFile.head(), "timesig" );
|
|
m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" );
|
|
m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" );
|
|
|
|
if( m_playPos[Mode_PlaySong].m_timeLine )
|
|
{
|
|
// reset loop-point-state
|
|
m_playPos[Mode_PlaySong].m_timeLine->toggleLoopPoints( 0 );
|
|
}
|
|
|
|
if( !dataFile.content().firstChildElement( "track" ).isNull() )
|
|
{
|
|
m_globalAutomationTrack->restoreState( dataFile.content().
|
|
firstChildElement( "track" ) );
|
|
}
|
|
|
|
//Backward compatibility for LMMS <= 0.4.15
|
|
PeakController::initGetControllerBySetting();
|
|
|
|
QDomNode node = dataFile.content().firstChild();
|
|
while( !node.isNull() )
|
|
{
|
|
if( node.isElement() )
|
|
{
|
|
if( node.nodeName() == "trackcontainer" )
|
|
{
|
|
( (JournallingObject *)( this ) )->
|
|
restoreState( node.toElement() );
|
|
}
|
|
else if( node.nodeName() == "controllers" )
|
|
{
|
|
restoreControllerStates( node.toElement() );
|
|
}
|
|
else if( node.nodeName() == engine::fxMixer()->nodeName() )
|
|
{
|
|
engine::fxMixer()->restoreState( node.toElement() );
|
|
}
|
|
else if( engine::hasGUI() )
|
|
{
|
|
if( node.nodeName() ==
|
|
engine::getControllerRackView()->nodeName() )
|
|
{
|
|
engine::getControllerRackView()->
|
|
restoreState( node.toElement() );
|
|
}
|
|
else if( node.nodeName() ==
|
|
engine::getPianoRoll()->nodeName() )
|
|
{
|
|
engine::getPianoRoll()->restoreState(
|
|
node.toElement() );
|
|
}
|
|
else if( node.nodeName() ==
|
|
engine::automationEditor()->
|
|
nodeName() )
|
|
{
|
|
engine::automationEditor()->
|
|
restoreState( node.toElement() );
|
|
}
|
|
else if( node.nodeName() ==
|
|
engine::getProjectNotes()->
|
|
nodeName() )
|
|
{
|
|
engine::getProjectNotes()->
|
|
SerializingObject::restoreState( node.toElement() );
|
|
}
|
|
else if( node.nodeName() ==
|
|
m_playPos[Mode_PlaySong].
|
|
m_timeLine->nodeName() )
|
|
{
|
|
m_playPos[Mode_PlaySong].
|
|
m_timeLine->restoreState(
|
|
node.toElement() );
|
|
}
|
|
}
|
|
}
|
|
node = node.nextSibling();
|
|
}
|
|
|
|
// quirk for fixing projects with broken positions of TCOs inside
|
|
// BB-tracks
|
|
engine::getBBTrackContainer()->fixIncorrectPositions();
|
|
|
|
// Connect controller links to their controllers
|
|
// now that everything is loaded
|
|
ControllerConnection::finalizeConnections();
|
|
|
|
// resolve all IDs so that autoModels are automated
|
|
AutomationPattern::resolveAllIDs();
|
|
|
|
|
|
engine::mixer()->unlock();
|
|
|
|
configManager::inst()->addRecentlyOpenedProject( _file_name );
|
|
|
|
engine::projectJournal()->setJournalling( true );
|
|
|
|
emit projectLoaded();
|
|
|
|
m_loadingProject = false;
|
|
m_modified = false;
|
|
|
|
if( engine::mainWindow() )
|
|
{
|
|
engine::mainWindow()->resetWindowTitle();
|
|
}
|
|
}
|
|
|
|
|
|
// only save current song as _filename and do nothing else
|
|
bool song::saveProjectFile( const QString & _filename )
|
|
{
|
|
DataFile dataFile( DataFile::SongProject );
|
|
|
|
m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" );
|
|
m_timeSigModel.saveSettings( dataFile, dataFile.head(), "timesig" );
|
|
m_masterVolumeModel.saveSettings( dataFile, dataFile.head(), "mastervol" );
|
|
m_masterPitchModel.saveSettings( dataFile, dataFile.head(), "masterpitch" );
|
|
|
|
saveState( dataFile, dataFile.content() );
|
|
|
|
m_globalAutomationTrack->saveState( dataFile, dataFile.content() );
|
|
engine::fxMixer()->saveState( dataFile, dataFile.content() );
|
|
if( engine::hasGUI() )
|
|
{
|
|
engine::getControllerRackView()->saveState( dataFile, dataFile.content() );
|
|
engine::getPianoRoll()->saveState( dataFile, dataFile.content() );
|
|
engine::automationEditor()->saveState( dataFile, dataFile.content() );
|
|
engine::getProjectNotes()->
|
|
SerializingObject::saveState( dataFile, dataFile.content() );
|
|
m_playPos[Mode_PlaySong].m_timeLine->saveState(
|
|
dataFile, dataFile.content() );
|
|
}
|
|
|
|
saveControllerStates( dataFile, dataFile.content() );
|
|
|
|
return dataFile.writeFile( _filename );
|
|
}
|
|
|
|
|
|
|
|
// save current song and update the gui
|
|
bool song::guiSaveProject()
|
|
{
|
|
DataFile dataFile( DataFile::SongProject );
|
|
m_fileName = dataFile.nameWithExtension( m_fileName );
|
|
if( saveProjectFile( m_fileName ) && engine::hasGUI() )
|
|
{
|
|
textFloat::displayMessage( tr( "Project saved" ),
|
|
tr( "The project %1 is now saved."
|
|
).arg( m_fileName ),
|
|
embed::getIconPixmap( "project_save", 24, 24 ),
|
|
2000 );
|
|
configManager::inst()->addRecentlyOpenedProject( m_fileName );
|
|
m_modified = false;
|
|
engine::mainWindow()->resetWindowTitle();
|
|
}
|
|
else if( engine::hasGUI() )
|
|
{
|
|
textFloat::displayMessage( tr( "Project NOT saved." ),
|
|
tr( "The project %1 was not saved!" ).arg(
|
|
m_fileName ),
|
|
embed::getIconPixmap( "error" ), 4000 );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
// save current song in given filename
|
|
bool song::guiSaveProjectAs( const QString & _file_name )
|
|
{
|
|
QString o = m_oldFileName;
|
|
m_oldFileName = m_fileName;
|
|
m_fileName = _file_name;
|
|
if( guiSaveProject() == false )
|
|
{
|
|
m_fileName = m_oldFileName;
|
|
m_oldFileName = o;
|
|
return false;
|
|
}
|
|
m_oldFileName = m_fileName;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::importProject()
|
|
{
|
|
FileDialog ofd( NULL, tr( "Import file" ),
|
|
configManager::inst()->userProjectsDir(),
|
|
tr("MIDI sequences") +
|
|
" (*.mid *.midi *.rmi);;" +
|
|
tr("FL Studio projects") +
|
|
" (*.flp);;" +
|
|
tr("Hydrogen projects") +
|
|
" (*.h2song);;" +
|
|
tr("All file types") +
|
|
" (*.*)");
|
|
|
|
ofd.setFileMode( FileDialog::ExistingFiles );
|
|
if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
|
|
{
|
|
ImportFilter::import( ofd.selectedFiles()[0], this );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::saveControllerStates( QDomDocument & _doc, QDomElement & _this )
|
|
{
|
|
// save settings of controllers
|
|
QDomElement controllersNode =_doc.createElement( "controllers" );
|
|
_this.appendChild( controllersNode );
|
|
for( int i = 0; i < m_controllers.size(); ++i )
|
|
{
|
|
m_controllers[i]->saveState( _doc, controllersNode );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::restoreControllerStates( const QDomElement & _this )
|
|
{
|
|
QDomNode node = _this.firstChild();
|
|
while( !node.isNull() )
|
|
{
|
|
Controller * c = Controller::create( node.toElement(), this );
|
|
Q_ASSERT( c != NULL );
|
|
|
|
/* For PeakController, addController() was called in
|
|
* PeakControllerEffect::PeakControllerEffect().
|
|
* This line removes the previously added controller for PeakController
|
|
* without affecting the order of controllers in Controller Rack
|
|
*/
|
|
engine::getSong()->removeController( c );
|
|
addController( c );
|
|
|
|
node = node.nextSibling();
|
|
}
|
|
}
|
|
|
|
|
|
void song::exportProjectTracks()
|
|
{
|
|
exportProject(true);
|
|
}
|
|
|
|
void song::exportProject(bool multiExport)
|
|
{
|
|
if( isEmpty() )
|
|
{
|
|
QMessageBox::information( engine::mainWindow(),
|
|
tr( "Empty project" ),
|
|
tr( "This project is empty so exporting makes "
|
|
"no sense. Please put some items into "
|
|
"Song Editor first!" ) );
|
|
return;
|
|
}
|
|
|
|
FileDialog efd( engine::mainWindow() );
|
|
if (multiExport)
|
|
{
|
|
efd.setFileMode( FileDialog::Directory);
|
|
efd.setWindowTitle( tr( "Select directory for writing exported tracks..." ) );
|
|
if( !m_fileName.isEmpty() )
|
|
{
|
|
efd.setDirectory( QFileInfo( m_fileName ).absolutePath() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
efd.setFileMode( FileDialog::AnyFile );
|
|
int idx = 0;
|
|
QStringList types;
|
|
while( __fileEncodeDevices[idx].m_fileFormat !=
|
|
ProjectRenderer::NumFileFormats )
|
|
{
|
|
types << tr( __fileEncodeDevices[idx].m_description );
|
|
++idx;
|
|
}
|
|
efd.setFilters( types );
|
|
QString base_filename;
|
|
if( !m_fileName.isEmpty() )
|
|
{
|
|
efd.setDirectory( QFileInfo( m_fileName ).absolutePath() );
|
|
base_filename = QFileInfo( m_fileName ).completeBaseName();
|
|
}
|
|
else
|
|
{
|
|
efd.setDirectory( configManager::inst()->userProjectsDir() );
|
|
base_filename = tr( "untitled" );
|
|
}
|
|
efd.selectFile( base_filename + __fileEncodeDevices[0].m_extension );
|
|
efd.setWindowTitle( tr( "Select file for project-export..." ) );
|
|
}
|
|
|
|
efd.setAcceptMode( FileDialog::AcceptSave );
|
|
|
|
|
|
if( efd.exec() == QDialog::Accepted &&
|
|
!efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() )
|
|
{
|
|
const QString export_file_name = efd.selectedFiles()[0];
|
|
exportProjectDialog epd( export_file_name,
|
|
engine::mainWindow(), multiExport );
|
|
epd.exec();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::updateFramesPerTick()
|
|
{
|
|
engine::updateFramesPerTick();
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::setModified()
|
|
{
|
|
if( !m_loadingProject )
|
|
{
|
|
m_modified = true;
|
|
if( engine::mainWindow() &&
|
|
QThread::currentThread() ==
|
|
engine::mainWindow()->thread() )
|
|
{
|
|
engine::mainWindow()->resetWindowTitle();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::addController( Controller * _c )
|
|
{
|
|
if( _c != NULL && !m_controllers.contains( _c ) )
|
|
{
|
|
m_controllers.append( _c );
|
|
emit dataChanged();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void song::removeController( Controller * _controller )
|
|
{
|
|
int index = m_controllers.indexOf( _controller );
|
|
if( index != -1 )
|
|
{
|
|
m_controllers.remove( index );
|
|
|
|
if( engine::getSong() )
|
|
{
|
|
engine::getSong()->setModified();
|
|
}
|
|
emit dataChanged();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool song::isLoadingProject()
|
|
{
|
|
return m_loadingProject;
|
|
}
|
|
|
|
|
|
#include "moc_song.cxx"
|
|
|
|
|