1 /*
2  * Song.cpp - root of the model tree
3  *
4  * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
5  *
6  * This file is part of LMMS - https://lmms.io
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this program (see COPYING); if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301 USA.
22  *
23  */
24 
25 #include "Song.h"
26 #include <QTextStream>
27 #include <QCoreApplication>
28 #include <QDebug>
29 #include <QFile>
30 #include <QFileInfo>
31 #include <QMessageBox>
32 
33 #include <functional>
34 
35 #include "AutomationTrack.h"
36 #include "AutomationEditor.h"
37 #include "BBEditor.h"
38 #include "BBTrack.h"
39 #include "BBTrackContainer.h"
40 #include "ConfigManager.h"
41 #include "ControllerRackView.h"
42 #include "ControllerConnection.h"
43 #include "embed.h"
44 #include "EnvelopeAndLfoParameters.h"
45 #include "ExportProjectDialog.h"
46 #include "FxMixer.h"
47 #include "FxMixerView.h"
48 #include "GuiApplication.h"
49 #include "ImportFilter.h"
50 #include "ExportFilter.h"
51 #include "MainWindow.h"
52 #include "FileDialog.h"
53 #include "Pattern.h"
54 #include "PianoRoll.h"
55 #include "ProjectJournal.h"
56 #include "ProjectNotes.h"
57 #include "SongEditor.h"
58 #include "TextFloat.h"
59 #include "TimeLineWidget.h"
60 #include "PeakController.h"
61 #include "VersionedSaveDialog.h"
62 
63 
64 tick_t MidiTime::s_ticksPerTact = DefaultTicksPerTact;
65 
66 
67 
Song()68 Song::Song() :
69 	TrackContainer(),
70 	m_globalAutomationTrack( dynamic_cast<AutomationTrack *>(
71 				Track::create( Track::HiddenAutomationTrack,
72 								this ) ) ),
73 	m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ),
74 	m_timeSigModel( this ),
75 	m_oldTicksPerTact( DefaultTicksPerTact ),
76 	m_masterVolumeModel( 100, 0, 200, this, tr( "Master volume" ) ),
77 	m_masterPitchModel( 0, -12, 12, this, tr( "Master pitch" ) ),
78 	m_nLoadingTrack( 0 ),
79 	m_fileName(),
80 	m_oldFileName(),
81 	m_modified( false ),
82 	m_loadOnLaunch( true ),
83 	m_recording( false ),
84 	m_exporting( false ),
85 	m_exportLoop( false ),
86 	m_renderBetweenMarkers( false ),
87 	m_playing( false ),
88 	m_paused( false ),
89 	m_loadingProject( false ),
90 	m_isCancelled( false ),
91 	m_playMode( Mode_None ),
92 	m_length( 0 ),
93 	m_patternToPlay( NULL ),
94 	m_loopPattern( false ),
95 	m_elapsedMilliSeconds( 0 ),
96 	m_elapsedTicks( 0 ),
97 	m_elapsedTacts( 0 )
98 {
99 	connect( &m_tempoModel, SIGNAL( dataChanged() ),
100 			this, SLOT( setTempo() ), Qt::DirectConnection );
101 	connect( &m_tempoModel, SIGNAL( dataUnchanged() ),
102 			this, SLOT( setTempo() ), Qt::DirectConnection );
103 	connect( &m_timeSigModel, SIGNAL( dataChanged() ),
104 			this, SLOT( setTimeSignature() ), Qt::DirectConnection );
105 
106 
107 	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this,
108 						SLOT( updateFramesPerTick() ) );
109 
110 	connect( &m_masterVolumeModel, SIGNAL( dataChanged() ),
111 			this, SLOT( masterVolumeChanged() ), Qt::DirectConnection );
112 /*	connect( &m_masterPitchModel, SIGNAL( dataChanged() ),
113 			this, SLOT( masterPitchChanged() ) );*/
114 
115 	qRegisterMetaType<Note>( "Note" );
116 	setType( SongContainer );
117 }
118 
119 
120 
121 
~Song()122 Song::~Song()
123 {
124 	m_playing = false;
125 	delete m_globalAutomationTrack;
126 }
127 
128 
129 
130 
masterVolumeChanged()131 void Song::masterVolumeChanged()
132 {
133 	Engine::mixer()->setMasterGain( m_masterVolumeModel.value() /
134 								100.0f );
135 }
136 
137 
138 
139 
setTempo()140 void Song::setTempo()
141 {
142 	Engine::mixer()->requestChangeInModel();
143 	const bpm_t tempo = ( bpm_t ) m_tempoModel.value();
144 	PlayHandleList & playHandles = Engine::mixer()->playHandles();
145 	for( PlayHandleList::Iterator it = playHandles.begin();
146 						it != playHandles.end(); ++it )
147 	{
148 		NotePlayHandle * nph = dynamic_cast<NotePlayHandle *>( *it );
149 		if( nph && !nph->isReleased() )
150 		{
151 			nph->lock();
152 			nph->resize( tempo );
153 			nph->unlock();
154 		}
155 	}
156 	Engine::mixer()->doneChangeInModel();
157 
158 	Engine::updateFramesPerTick();
159 
160 	m_vstSyncController.setTempo( tempo );
161 
162 	emit tempoChanged( tempo );
163 }
164 
165 
166 
167 
setTimeSignature()168 void Song::setTimeSignature()
169 {
170 	MidiTime::setTicksPerTact( ticksPerTact() );
171 	emit timeSignatureChanged( m_oldTicksPerTact, ticksPerTact() );
172 	emit dataChanged();
173 	m_oldTicksPerTact = ticksPerTact();
174 
175 	m_vstSyncController.setTimeSignature(
176 		getTimeSigModel().getNumerator(), getTimeSigModel().getDenominator() );
177 }
178 
179 
180 
181 
savePos()182 void Song::savePos()
183 {
184 	TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine;
185 
186 	if( tl != NULL )
187 	{
188 		tl->savePos( m_playPos[m_playMode] );
189 	}
190 }
191 
192 
193 
194 
processNextBuffer()195 void Song::processNextBuffer()
196 {
197 	m_vstSyncController.setPlaybackJumped( false );
198 
199 	// if not playing, nothing to do
200 	if( m_playing == false )
201 	{
202 		return;
203 	}
204 
205 	TrackList trackList;
206 	int tcoNum = -1; // track content object number
207 
208 	// determine the list of tracks to play and the track content object
209 	// (TCO) number
210 	switch( m_playMode )
211 	{
212 		case Mode_PlaySong:
213 			trackList = tracks();
214 			// at song-start we have to reset the LFOs
215 			if( m_playPos[Mode_PlaySong] == 0 )
216 			{
217 				EnvelopeAndLfoParameters::instances()->reset();
218 			}
219 			break;
220 
221 		case Mode_PlayBB:
222 			if( Engine::getBBTrackContainer()->numOfBBs() > 0 )
223 			{
224 				tcoNum = Engine::getBBTrackContainer()->
225 								currentBB();
226 				trackList.push_back( BBTrack::findBBTrack(
227 								tcoNum ) );
228 			}
229 			break;
230 
231 		case Mode_PlayPattern:
232 			if( m_patternToPlay != NULL )
233 			{
234 				tcoNum = m_patternToPlay->getTrack()->
235 						getTCONum( m_patternToPlay );
236 				trackList.push_back(
237 						m_patternToPlay->getTrack() );
238 			}
239 			break;
240 
241 		default:
242 			return;
243 
244 	}
245 
246 	// if we have no tracks to play, nothing to do
247 	if( trackList.empty() == true )
248 	{
249 		return;
250 	}
251 
252 	// check for looping-mode and act if necessary
253 	TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine;
254 	bool checkLoop =
255 		tl != NULL && m_exporting == false && tl->loopPointsEnabled();
256 
257 	if( checkLoop )
258 	{
259 		// if looping-mode is enabled and we are outside of the looping
260 		// range, go to the beginning of the range
261 		if( m_playPos[m_playMode] < tl->loopBegin() ||
262 					m_playPos[m_playMode] >= tl->loopEnd() )
263 		{
264 			m_elapsedMilliSeconds =
265 				( tl->loopBegin().getTicks() * 60 * 1000 / 48 ) / getTempo();
266 			m_playPos[m_playMode].setTicks(
267 						tl->loopBegin().getTicks() );
268 
269 			m_vstSyncController.setPlaybackJumped( true );
270 
271 			emit updateSampleTracks();
272 		}
273 	}
274 
275 	if( m_playPos[m_playMode].jumped() )
276 	{
277 		m_vstSyncController.setPlaybackJumped( true );
278 		m_playPos[m_playMode].setJumped( false );
279 	}
280 
281 	f_cnt_t framesPlayed = 0;
282 	const float framesPerTick = Engine::framesPerTick();
283 
284 	while( framesPlayed < Engine::mixer()->framesPerPeriod() )
285 	{
286 		m_vstSyncController.update();
287 
288 		float currentFrame = m_playPos[m_playMode].currentFrame();
289 		// did we play a tick?
290 		if( currentFrame >= framesPerTick )
291 		{
292 			int ticks = m_playPos[m_playMode].getTicks() +
293 				( int )( currentFrame / framesPerTick );
294 
295 			// did we play a whole tact?
296 			if( ticks >= MidiTime::ticksPerTact() )
297 			{
298 				// per default we just continue playing even if
299 				// there's no more stuff to play
300 				// (song-play-mode)
301 				int maxTact = m_playPos[m_playMode].getTact()
302 									+ 2;
303 
304 				// then decide whether to go over to next tact
305 				// or to loop back to first tact
306 				if( m_playMode == Mode_PlayBB )
307 				{
308 					maxTact = Engine::getBBTrackContainer()
309 							->lengthOfCurrentBB();
310 				}
311 				else if( m_playMode == Mode_PlayPattern &&
312 					m_loopPattern == true &&
313 					tl != NULL &&
314 					tl->loopPointsEnabled() == false )
315 				{
316 					maxTact = m_patternToPlay->length()
317 								.getTact();
318 				}
319 
320 				// end of played object reached?
321 				if( m_playPos[m_playMode].getTact() + 1
322 								>= maxTact )
323 				{
324 					// then start from beginning and keep
325 					// offset
326 					ticks %= ( maxTact * MidiTime::ticksPerTact() );
327 
328 					// wrap milli second counter
329 					m_elapsedMilliSeconds =
330 						( ticks * 60 * 1000 / 48 ) / getTempo();
331 
332 					m_vstSyncController.setPlaybackJumped( true );
333 				}
334 			}
335 			m_playPos[m_playMode].setTicks( ticks );
336 
337 			if( checkLoop )
338 			{
339 				m_vstSyncController.startCycle(
340 					tl->loopBegin().getTicks(), tl->loopEnd().getTicks() );
341 
342 				// if looping-mode is enabled and we have got
343 				// past the looping range, return to the
344 				// beginning of the range
345 				if( m_playPos[m_playMode] >= tl->loopEnd() )
346 				{
347 					ticks = tl->loopBegin().getTicks();
348 					m_playPos[m_playMode].setTicks( ticks );
349 
350 					m_elapsedMilliSeconds =
351 						( ticks * 60 * 1000 / 48 ) / getTempo();
352 
353 					m_vstSyncController.setPlaybackJumped( true );
354 
355 					emit updateSampleTracks();
356 				}
357 			}
358 			else
359 			{
360 				m_vstSyncController.stopCycle();
361 			}
362 
363 			currentFrame = fmodf( currentFrame, framesPerTick );
364 			m_playPos[m_playMode].setCurrentFrame( currentFrame );
365 		}
366 
367 		if( framesPlayed == 0 )
368 		{
369 			// update VST sync position after we've corrected frame/
370 			// tick count but before actually playing any frames
371 			m_vstSyncController.setAbsolutePosition(
372 				m_playPos[m_playMode].getTicks()
373 				+ m_playPos[m_playMode].currentFrame()
374 				/ (double) framesPerTick );
375 		}
376 
377 		f_cnt_t framesToPlay =
378 			Engine::mixer()->framesPerPeriod() - framesPlayed;
379 
380 		f_cnt_t framesLeft = ( f_cnt_t )framesPerTick -
381 						( f_cnt_t )currentFrame;
382 		// skip last frame fraction
383 		if( framesLeft == 0 )
384 		{
385 			++framesPlayed;
386 			m_playPos[m_playMode].setCurrentFrame( currentFrame
387 								+ 1.0f );
388 			continue;
389 		}
390 		// do we have samples left in this tick but these are less
391 		// than samples we have to play?
392 		if( framesLeft < framesToPlay )
393 		{
394 			// then set framesToPlay to remaining samples, the
395 			// rest will be played in next loop
396 			framesToPlay = framesLeft;
397 		}
398 
399 		if( ( f_cnt_t ) currentFrame == 0 )
400 		{
401 			processAutomations(trackList, m_playPos[m_playMode], framesToPlay);
402 
403 			// loop through all tracks and play them
404 			for( int i = 0; i < trackList.size(); ++i )
405 			{
406 				trackList[i]->play( m_playPos[m_playMode],
407 						framesToPlay,
408 						framesPlayed, tcoNum );
409 			}
410 		}
411 
412 		// update frame-counters
413 		framesPlayed += framesToPlay;
414 		m_playPos[m_playMode].setCurrentFrame( framesToPlay +
415 								currentFrame );
416 		m_elapsedMilliSeconds +=
417 			( ( framesToPlay / framesPerTick ) * 60 * 1000 / 48 )
418 				/ getTempo();
419 		m_elapsedTacts = m_playPos[Mode_PlaySong].getTact();
420 		m_elapsedTicks = ( m_playPos[Mode_PlaySong].getTicks() % ticksPerTact() ) / 48;
421 	}
422 }
423 
424 
processAutomations(const TrackList & tracklist,MidiTime timeStart,fpp_t)425 void Song::processAutomations(const TrackList &tracklist, MidiTime timeStart, fpp_t)
426 {
427 	AutomatedValueMap values;
428 
429 	QSet<const AutomatableModel*> recordedModels;
430 
431 	TrackContainer* container = this;
432 	int tcoNum = -1;
433 
434 	switch (m_playMode)
435 	{
436 	case Mode_PlaySong:
437 		break;
438 	case Mode_PlayBB:
439 	{
440 		Q_ASSERT(tracklist.size() == 1);
441 		Q_ASSERT(tracklist.at(0)->type() == Track::BBTrack);
442 		auto bbTrack = dynamic_cast<BBTrack*>(tracklist.at(0));
443 		auto bbContainer = Engine::getBBTrackContainer();
444 		container = bbContainer;
445 		tcoNum = bbTrack->index();
446 	}
447 		break;
448 	default:
449 		return;
450 	}
451 
452 	values = container->automatedValuesAt(timeStart, tcoNum);
453 	TrackList tracks = container->tracks();
454 
455 	Track::tcoVector tcos;
456 	for (Track* track : tracks)
457 	{
458 		if (track->type() == Track::AutomationTrack) {
459 			track->getTCOsInRange(tcos, 0, timeStart);
460 		}
461 	}
462 
463 	// Process recording
464 	for (TrackContentObject* tco : tcos)
465 	{
466 		auto p = dynamic_cast<AutomationPattern *>(tco);
467 		MidiTime relTime = timeStart - p->startPosition();
468 		if (p->isRecording() && relTime >= 0 && relTime < p->length())
469 		{
470 			const AutomatableModel* recordedModel = p->firstObject();
471 			p->recordValue(relTime, recordedModel->value<float>());
472 
473 			recordedModels << recordedModel;
474 		}
475 	}
476 
477 	// Apply values
478 	for (auto it = values.begin(); it != values.end(); it++)
479 	{
480 		if (! recordedModels.contains(it.key()))
481 		{
482 			it.key()->setAutomatedValue(it.value());
483 		}
484 	}
485 }
486 
getExportEndpoints() const487 std::pair<MidiTime, MidiTime> Song::getExportEndpoints() const
488 {
489 	if ( m_renderBetweenMarkers )
490 	{
491 		return std::pair<MidiTime, MidiTime>(
492 			m_playPos[Mode_PlaySong].m_timeLine->loopBegin(),
493 			m_playPos[Mode_PlaySong].m_timeLine->loopEnd()
494 		);
495 	}
496 	else if ( m_exportLoop )
497 	{
498 		return std::pair<MidiTime, MidiTime>( MidiTime(0, 0), MidiTime(m_length, 0) );
499 	}
500 	else
501 	{
502 		// if not exporting as a loop, we leave one bar of padding at the end of the song to accomodate reverb, etc.
503 		return std::pair<MidiTime, MidiTime>( MidiTime(0, 0), MidiTime(m_length+1, 0) );
504 	}
505 }
506 
507 
508 
509 
playSong()510 void Song::playSong()
511 {
512 	m_recording = false;
513 
514 	if( isStopped() == false )
515 	{
516 		stop();
517 	}
518 
519 	m_playMode = Mode_PlaySong;
520 	m_playing = true;
521 	m_paused = false;
522 
523 	m_vstSyncController.setPlaybackState( true );
524 
525 	savePos();
526 
527 	emit playbackStateChanged();
528 }
529 
530 
531 
532 
record()533 void Song::record()
534 {
535 	m_recording = true;
536 	// TODO: Implement
537 }
538 
539 
540 
541 
playAndRecord()542 void Song::playAndRecord()
543 {
544 	playSong();
545 	m_recording = true;
546 }
547 
548 
549 
550 
playBB()551 void Song::playBB()
552 {
553 	if( isStopped() == false )
554 	{
555 		stop();
556 	}
557 
558 	m_playMode = Mode_PlayBB;
559 	m_playing = true;
560 	m_paused = false;
561 
562 	m_vstSyncController.setPlaybackState( true );
563 
564 	savePos();
565 
566 	emit playbackStateChanged();
567 }
568 
569 
570 
571 
playPattern(const Pattern * patternToPlay,bool loop)572 void Song::playPattern( const Pattern* patternToPlay, bool loop )
573 {
574 	if( isStopped() == false )
575 	{
576 		stop();
577 	}
578 
579 	m_patternToPlay = patternToPlay;
580 	m_loopPattern = loop;
581 
582 	if( m_patternToPlay != NULL )
583 	{
584 		m_playMode = Mode_PlayPattern;
585 		m_playing = true;
586 		m_paused = false;
587 	}
588 
589 	savePos();
590 
591 	emit playbackStateChanged();
592 }
593 
594 
595 
596 
updateLength()597 void Song::updateLength()
598 {
599 	m_length = 0;
600 	m_tracksMutex.lockForRead();
601 	for( TrackList::const_iterator it = tracks().begin();
602 						it != tracks().end(); ++it )
603 	{
604 		if( Engine::getSong()->isExporting() &&
605 				( *it )->isMuted() )
606 		{
607 			continue;
608 		}
609 
610 		const tact_t cur = ( *it )->length();
611 		if( cur > m_length )
612 		{
613 			m_length = cur;
614 		}
615 	}
616 	m_tracksMutex.unlock();
617 
618 	emit lengthChanged( m_length );
619 }
620 
621 
622 
623 
setPlayPos(tick_t ticks,PlayModes playMode)624 void Song::setPlayPos( tick_t ticks, PlayModes playMode )
625 {
626 	m_elapsedTicks += m_playPos[playMode].getTicks() - ticks;
627 	m_elapsedMilliSeconds +=
628 		( ( ( ( ticks - m_playPos[playMode].getTicks() ) ) * 60 * 1000 / 48) /
629 			getTempo() );
630 	m_playPos[playMode].setTicks( ticks );
631 	m_playPos[playMode].setCurrentFrame( 0.0f );
632 	m_playPos[playMode].setJumped( true );
633 
634 // send a signal if playposition changes during playback
635 	if( isPlaying() )
636 	{
637 		emit playbackPositionChanged();
638 		emit updateSampleTracks();
639 	}
640 }
641 
642 
643 
644 
togglePause()645 void Song::togglePause()
646 {
647 	if( m_paused == true )
648 	{
649 		m_playing = true;
650 		m_paused = false;
651 	}
652 	else
653 	{
654 		m_playing = false;
655 		m_paused = true;
656 	}
657 
658 	m_vstSyncController.setPlaybackState( m_playing );
659 
660 	emit playbackStateChanged();
661 }
662 
663 
664 
665 
stop()666 void Song::stop()
667 {
668 	// do not stop/reset things again if we're stopped already
669 	if( m_playMode == Mode_None )
670 	{
671 		return;
672 	}
673 
674 	TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine;
675 	m_paused = false;
676 	m_recording = true;
677 
678 	if( tl != NULL )
679 	{
680 
681 		switch( tl->behaviourAtStop() )
682 		{
683 			case TimeLineWidget::BackToZero:
684 				m_playPos[m_playMode].setTicks( 0 );
685 				m_elapsedMilliSeconds = 0;
686 				if( gui && gui->songEditor() &&
687 						( tl->autoScroll() == TimeLineWidget::AutoScrollEnabled ) )
688 				{
689 					QMetaObject::invokeMethod(gui->songEditor()->m_editor, "updatePosition", Qt::AutoConnection, Q_ARG(MidiTime, 0));
690 				}
691 				break;
692 
693 			case TimeLineWidget::BackToStart:
694 				if( tl->savedPos() >= 0 )
695 				{
696 					m_playPos[m_playMode].setTicks( tl->savedPos().getTicks() );
697 					m_elapsedMilliSeconds =
698 						( ( ( tl->savedPos().getTicks() ) * 60 * 1000 / 48 ) /
699 							getTempo() );
700 					if( gui && gui->songEditor() &&
701 							( tl->autoScroll() == TimeLineWidget::AutoScrollEnabled ) )
702 					{
703 						QMetaObject::invokeMethod(gui->songEditor()->m_editor, "updatePosition", Qt::AutoConnection, Q_ARG(MidiTime, tl->savedPos().getTicks()));
704 					}
705 					tl->savePos( -1 );
706 				}
707 				break;
708 
709 			case TimeLineWidget::KeepStopPosition:
710 			default:
711 				break;
712 		}
713 	}
714 	else
715 	{
716 		m_playPos[m_playMode].setTicks( 0 );
717 		m_elapsedMilliSeconds = 0;
718 	}
719 	m_playing = false;
720 
721 	m_playPos[m_playMode].setCurrentFrame( 0 );
722 
723 	m_vstSyncController.setPlaybackState( m_exporting );
724 	m_vstSyncController.setAbsolutePosition(
725 		m_playPos[m_playMode].getTicks()
726 		+ m_playPos[m_playMode].currentFrame()
727 		/ (double) Engine::framesPerTick() );
728 
729 	// remove all note-play-handles that are active
730 	Engine::mixer()->clear();
731 
732 	m_playMode = Mode_None;
733 
734 	emit playbackStateChanged();
735 }
736 
737 
738 
739 
startExport()740 void Song::startExport()
741 {
742 	stop();
743 	if(m_renderBetweenMarkers)
744 	{
745 		m_playPos[Mode_PlaySong].setTicks( m_playPos[Mode_PlaySong].m_timeLine->loopBegin().getTicks() );
746 	}
747 	else
748 	{
749 		m_playPos[Mode_PlaySong].setTicks( 0 );
750 	}
751 
752 	playSong();
753 
754 	m_exporting = true;
755 
756 	m_vstSyncController.setPlaybackState( true );
757 }
758 
759 
760 
761 
stopExport()762 void Song::stopExport()
763 {
764 	stop();
765 	m_exporting = false;
766 	m_exportLoop = false;
767 
768 	m_vstSyncController.setPlaybackState( m_playing );
769 }
770 
771 
772 
773 
insertBar()774 void Song::insertBar()
775 {
776 	m_tracksMutex.lockForRead();
777 	for( TrackList::const_iterator it = tracks().begin();
778 					it != tracks().end(); ++it )
779 	{
780 		( *it )->insertTact( m_playPos[Mode_PlaySong] );
781 	}
782 	m_tracksMutex.unlock();
783 }
784 
785 
786 
787 
removeBar()788 void Song::removeBar()
789 {
790 	m_tracksMutex.lockForRead();
791 	for( TrackList::const_iterator it = tracks().begin();
792 					it != tracks().end(); ++it )
793 	{
794 		( *it )->removeTact( m_playPos[Mode_PlaySong] );
795 	}
796 	m_tracksMutex.unlock();
797 }
798 
799 
800 
801 
addBBTrack()802 void Song::addBBTrack()
803 {
804 	Track * t = Track::create( Track::BBTrack, this );
805 	Engine::getBBTrackContainer()->setCurrentBB( dynamic_cast<BBTrack *>( t )->index() );
806 }
807 
808 
809 
810 
addSampleTrack()811 void Song::addSampleTrack()
812 {
813 	( void )Track::create( Track::SampleTrack, this );
814 }
815 
816 
817 
818 
addAutomationTrack()819 void Song::addAutomationTrack()
820 {
821 	( void )Track::create( Track::AutomationTrack, this );
822 }
823 
824 
825 
826 
getTempo()827 bpm_t Song::getTempo()
828 {
829 	return ( bpm_t )m_tempoModel.value();
830 }
831 
832 
833 
834 
tempoAutomationPattern()835 AutomationPattern * Song::tempoAutomationPattern()
836 {
837 	return AutomationPattern::globalAutomationPattern( &m_tempoModel );
838 }
839 
840 
automatedValuesAt(MidiTime time,int tcoNum) const841 AutomatedValueMap Song::automatedValuesAt(MidiTime time, int tcoNum) const
842 {
843 	return TrackContainer::automatedValuesFromTracks(TrackList{m_globalAutomationTrack} << tracks(), time, tcoNum);
844 }
845 
846 
847 
848 
clearProject()849 void Song::clearProject()
850 {
851 	Engine::projectJournal()->setJournalling( false );
852 
853 	if( m_playing )
854 	{
855 		stop();
856 	}
857 
858 	for( int i = 0; i < Mode_Count; i++ )
859 	{
860 		setPlayPos( 0, ( PlayModes )i );
861 	}
862 
863 
864 	Engine::mixer()->requestChangeInModel();
865 
866 	if( gui && gui->getBBEditor() )
867 	{
868 		gui->getBBEditor()->trackContainerView()->clearAllTracks();
869 	}
870 	if( gui && gui->songEditor() )
871 	{
872 		gui->songEditor()->m_editor->clearAllTracks();
873 	}
874 	if( gui && gui->fxMixerView() )
875 	{
876 		gui->fxMixerView()->clear();
877 	}
878 	QCoreApplication::sendPostedEvents();
879 	Engine::getBBTrackContainer()->clearAllTracks();
880 	clearAllTracks();
881 
882 	Engine::fxMixer()->clear();
883 
884 	if( gui && gui->automationEditor() )
885 	{
886 		gui->automationEditor()->setCurrentPattern( NULL );
887 	}
888 
889 	if( gui && gui->pianoRoll() )
890 	{
891 		gui->pianoRoll()->reset();
892 	}
893 
894 	m_tempoModel.reset();
895 	m_masterVolumeModel.reset();
896 	m_masterPitchModel.reset();
897 	m_timeSigModel.reset();
898 
899 	AutomationPattern::globalAutomationPattern( &m_tempoModel )->clear();
900 	AutomationPattern::globalAutomationPattern( &m_masterVolumeModel )->
901 									clear();
902 	AutomationPattern::globalAutomationPattern( &m_masterPitchModel )->
903 									clear();
904 
905 	Engine::mixer()->doneChangeInModel();
906 
907 	if( gui && gui->getProjectNotes() )
908 	{
909 		gui->getProjectNotes()->clear();
910 	}
911 
912 	removeAllControllers();
913 
914 	emit dataChanged();
915 
916 	Engine::projectJournal()->clearJournal();
917 
918 	Engine::projectJournal()->setJournalling( true );
919 
920 	InstrumentTrackView::cleanupWindowCache();
921 }
922 
923 
924 
925 
926 // create new file
createNewProject()927 void Song::createNewProject()
928 {
929 
930 	QString defaultTemplate = ConfigManager::inst()->userTemplateDir()
931 						+ "default.mpt";
932 
933 
934 	if( QFile::exists( defaultTemplate ) )
935 	{
936 		createNewProjectFromTemplate( defaultTemplate );
937 		return;
938 	}
939 
940 	defaultTemplate = ConfigManager::inst()->factoryProjectsDir()
941 						+ "templates/default.mpt";
942 	if( QFile::exists( defaultTemplate ) )
943 	{
944 		createNewProjectFromTemplate( defaultTemplate );
945 		return;
946 	}
947 
948 	m_loadingProject = true;
949 
950 	clearProject();
951 
952 	Engine::projectJournal()->setJournalling( false );
953 
954 	m_fileName = m_oldFileName = "";
955 
956 	Track * t;
957 	t = Track::create( Track::InstrumentTrack, this );
958 	dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
959 					"tripleoscillator" );
960 	t = Track::create( Track::InstrumentTrack,
961 						Engine::getBBTrackContainer() );
962 	dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
963 						"kicker" );
964 	Track::create( Track::SampleTrack, this );
965 	Track::create( Track::BBTrack, this );
966 	Track::create( Track::AutomationTrack, this );
967 
968 	m_tempoModel.setInitValue( DefaultTempo );
969 	m_timeSigModel.reset();
970 	m_masterVolumeModel.setInitValue( 100 );
971 	m_masterPitchModel.setInitValue( 0 );
972 
973 	QCoreApplication::instance()->processEvents();
974 
975 	m_loadingProject = false;
976 
977 	Engine::getBBTrackContainer()->updateAfterTrackAdd();
978 
979 	Engine::projectJournal()->setJournalling( true );
980 
981 	QCoreApplication::sendPostedEvents();
982 
983 	m_modified = false;
984 	m_loadOnLaunch = false;
985 
986 	if( gui->mainWindow() )
987 	{
988 		gui->mainWindow()->resetWindowTitle();
989 	}
990 }
991 
992 
993 
994 
createNewProjectFromTemplate(const QString & templ)995 void Song::createNewProjectFromTemplate( const QString & templ )
996 {
997 	loadProject( templ );
998 	// clear file-name so that user doesn't overwrite template when
999 	// saving...
1000 	m_fileName = m_oldFileName = "";
1001 	// update window title
1002 	m_loadOnLaunch = false;
1003 	if( gui->mainWindow() )
1004 	{
1005 		gui->mainWindow()->resetWindowTitle();
1006 	}
1007 }
1008 
1009 
1010 
1011 
1012 // load given song
loadProject(const QString & fileName)1013 void Song::loadProject( const QString & fileName )
1014 {
1015 	QDomNode node;
1016 
1017 	m_loadingProject = true;
1018 
1019 	Engine::projectJournal()->setJournalling( false );
1020 
1021 	m_oldFileName = m_fileName;
1022 	m_fileName = fileName;
1023 
1024 	DataFile dataFile( m_fileName );
1025 	// if file could not be opened, head-node is null and we create
1026 	// new project
1027 	if( dataFile.head().isNull() )
1028 	{
1029 		if( m_loadOnLaunch )
1030 		{
1031 			createNewProject();
1032 		}
1033 		m_fileName = m_oldFileName;
1034 		return;
1035 	}
1036 
1037 	m_oldFileName = m_fileName;
1038 
1039 	clearProject();
1040 
1041 	clearErrors();
1042 
1043 	Engine::mixer()->requestChangeInModel();
1044 
1045 	// get the header information from the DOM
1046 	m_tempoModel.loadSettings( dataFile.head(), "bpm" );
1047 	m_timeSigModel.loadSettings( dataFile.head(), "timesig" );
1048 	m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" );
1049 	m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" );
1050 
1051 	if( m_playPos[Mode_PlaySong].m_timeLine )
1052 	{
1053 		// reset loop-point-state
1054 		m_playPos[Mode_PlaySong].m_timeLine->toggleLoopPoints( 0 );
1055 	}
1056 
1057 	if( !dataFile.content().firstChildElement( "track" ).isNull() )
1058 	{
1059 		m_globalAutomationTrack->restoreState( dataFile.content().
1060 						firstChildElement( "track" ) );
1061 	}
1062 
1063 	//Backward compatibility for LMMS <= 0.4.15
1064 	PeakController::initGetControllerBySetting();
1065 
1066 	// Load mixer first to be able to set the correct range for FX channels
1067 	node = dataFile.content().firstChildElement( Engine::fxMixer()->nodeName() );
1068 	if( !node.isNull() )
1069 	{
1070 		Engine::fxMixer()->restoreState( node.toElement() );
1071 		if( gui )
1072 		{
1073 			// refresh FxMixerView
1074 			gui->fxMixerView()->refreshDisplay();
1075 		}
1076 	}
1077 
1078 	node = dataFile.content().firstChild();
1079 
1080 	QDomNodeList tclist=dataFile.content().elementsByTagName("trackcontainer");
1081 	m_nLoadingTrack=0;
1082 	for( int i=0,n=tclist.count(); i<n; ++i )
1083 	{
1084 		QDomNode nd=tclist.at(i).firstChild();
1085 		while(!nd.isNull())
1086 		{
1087 			if( nd.isElement() && nd.nodeName() == "track" )
1088 			{
1089 				++m_nLoadingTrack;
1090 				if( nd.toElement().attribute("type").toInt() == Track::BBTrack )
1091 				{
1092 					n += nd.toElement().elementsByTagName("bbtrack").at(0)
1093 						.toElement().firstChildElement().childNodes().count();
1094 				}
1095 				nd=nd.nextSibling();
1096 			}
1097 		}
1098 	}
1099 
1100 	while( !node.isNull() && !isCancelled() )
1101 	{
1102 		if( node.isElement() )
1103 		{
1104 			if( node.nodeName() == "trackcontainer" )
1105 			{
1106 				( (JournallingObject *)( this ) )->restoreState( node.toElement() );
1107 			}
1108 			else if( node.nodeName() == "controllers" )
1109 			{
1110 				restoreControllerStates( node.toElement() );
1111 			}
1112 			else if( gui )
1113 			{
1114 				if( node.nodeName() == gui->getControllerRackView()->nodeName() )
1115 				{
1116 					gui->getControllerRackView()->restoreState( node.toElement() );
1117 				}
1118 				else if( node.nodeName() == gui->pianoRoll()->nodeName() )
1119 				{
1120 					gui->pianoRoll()->restoreState( node.toElement() );
1121 				}
1122 				else if( node.nodeName() == gui->automationEditor()->m_editor->nodeName() )
1123 				{
1124 					gui->automationEditor()->m_editor->restoreState( node.toElement() );
1125 				}
1126 				else if( node.nodeName() == gui->getProjectNotes()->nodeName() )
1127 				{
1128 					 gui->getProjectNotes()->SerializingObject::restoreState( node.toElement() );
1129 				}
1130 				else if( node.nodeName() == m_playPos[Mode_PlaySong].m_timeLine->nodeName() )
1131 				{
1132 					m_playPos[Mode_PlaySong].m_timeLine->restoreState( node.toElement() );
1133 				}
1134 			}
1135 		}
1136 		node = node.nextSibling();
1137 	}
1138 
1139 	// quirk for fixing projects with broken positions of TCOs inside
1140 	// BB-tracks
1141 	Engine::getBBTrackContainer()->fixIncorrectPositions();
1142 
1143 	// Connect controller links to their controllers
1144 	// now that everything is loaded
1145 	ControllerConnection::finalizeConnections();
1146 
1147 	// Remove dummy controllers that was added for correct connections
1148 	m_controllers.erase(std::remove_if(m_controllers.begin(), m_controllers.end(),
1149 		[](Controller* c){return c->type() == Controller::DummyController;}),
1150 		m_controllers.end());
1151 
1152 	// resolve all IDs so that autoModels are automated
1153 	AutomationPattern::resolveAllIDs();
1154 
1155 
1156 	Engine::mixer()->doneChangeInModel();
1157 
1158 	ConfigManager::inst()->addRecentlyOpenedProject( fileName );
1159 
1160 	Engine::projectJournal()->setJournalling( true );
1161 
1162 	emit projectLoaded();
1163 
1164 	if( isCancelled() )
1165 	{
1166 		m_isCancelled = false;
1167 		createNewProject();
1168 		return;
1169 	}
1170 
1171 	if ( hasErrors())
1172 	{
1173 		if ( gui )
1174 		{
1175 			QMessageBox::warning( NULL, tr("LMMS Error report"), errorSummary(),
1176 							QMessageBox::Ok );
1177 		}
1178 		else
1179 		{
1180 			QTextStream(stderr) << Engine::getSong()->errorSummary() << endl;
1181 		}
1182 	}
1183 
1184 	m_loadingProject = false;
1185 	m_modified = false;
1186 	m_loadOnLaunch = false;
1187 
1188 	if( gui && gui->mainWindow() )
1189 	{
1190 		gui->mainWindow()->resetWindowTitle();
1191 	}
1192 }
1193 
1194 
1195 // only save current song as _filename and do nothing else
saveProjectFile(const QString & filename)1196 bool Song::saveProjectFile( const QString & filename )
1197 {
1198 	DataFile dataFile( DataFile::SongProject );
1199 
1200 	m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" );
1201 	m_timeSigModel.saveSettings( dataFile, dataFile.head(), "timesig" );
1202 	m_masterVolumeModel.saveSettings( dataFile, dataFile.head(), "mastervol" );
1203 	m_masterPitchModel.saveSettings( dataFile, dataFile.head(), "masterpitch" );
1204 
1205 	saveState( dataFile, dataFile.content() );
1206 
1207 	m_globalAutomationTrack->saveState( dataFile, dataFile.content() );
1208 	Engine::fxMixer()->saveState( dataFile, dataFile.content() );
1209 	if( gui )
1210 	{
1211 		gui->getControllerRackView()->saveState( dataFile, dataFile.content() );
1212 		gui->pianoRoll()->saveState( dataFile, dataFile.content() );
1213 		gui->automationEditor()->m_editor->saveState( dataFile, dataFile.content() );
1214 		gui->getProjectNotes()->SerializingObject::saveState( dataFile, dataFile.content() );
1215 		m_playPos[Mode_PlaySong].m_timeLine->saveState( dataFile, dataFile.content() );
1216 	}
1217 
1218 	saveControllerStates( dataFile, dataFile.content() );
1219 
1220 	return dataFile.writeFile( filename );
1221 }
1222 
1223 
1224 
1225 // save current song and update the gui
guiSaveProject()1226 bool Song::guiSaveProject()
1227 {
1228 	DataFile dataFile( DataFile::SongProject );
1229 	m_fileName = dataFile.nameWithExtension( m_fileName );
1230 	if( saveProjectFile( m_fileName ) && gui != nullptr )
1231 	{
1232 		TextFloat::displayMessage( tr( "Project saved" ),
1233 					tr( "The project %1 is now saved."
1234 							).arg( m_fileName ),
1235 				embed::getIconPixmap( "project_save", 24, 24 ),
1236 									2000 );
1237 		ConfigManager::inst()->addRecentlyOpenedProject( m_fileName );
1238 		m_modified = false;
1239 		gui->mainWindow()->resetWindowTitle();
1240 	}
1241 	else if( gui != nullptr )
1242 	{
1243 		TextFloat::displayMessage( tr( "Project NOT saved." ),
1244 				tr( "The project %1 was not saved!" ).arg(
1245 							m_fileName ),
1246 				embed::getIconPixmap( "error" ), 4000 );
1247 		return false;
1248 	}
1249 
1250 	return true;
1251 }
1252 
1253 
1254 
1255 
1256 // save current song in given filename
guiSaveProjectAs(const QString & _file_name)1257 bool Song::guiSaveProjectAs( const QString & _file_name )
1258 {
1259 	QString o = m_oldFileName;
1260 	m_oldFileName = m_fileName;
1261 	m_fileName = _file_name;
1262 	if( guiSaveProject() == false )
1263 	{
1264 		m_fileName = m_oldFileName;
1265 		m_oldFileName = o;
1266 		return false;
1267 	}
1268 	m_oldFileName = m_fileName;
1269 	return true;
1270 }
1271 
1272 
1273 
1274 
importProject()1275 void Song::importProject()
1276 {
1277 	FileDialog ofd( NULL, tr( "Import file" ),
1278 			ConfigManager::inst()->userProjectsDir(),
1279 			tr("MIDI sequences") +
1280 			" (*.mid *.midi *.rmi);;" +
1281 			tr("Hydrogen projects") +
1282 			" (*.h2song);;" +
1283 			tr("All file types") +
1284 			" (*.*)");
1285 
1286 	ofd.setFileMode( FileDialog::ExistingFiles );
1287 	if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
1288 	{
1289 		ImportFilter::import( ofd.selectedFiles()[0], this );
1290 	}
1291 	m_loadOnLaunch = false;
1292 }
1293 
1294 
1295 
1296 
saveControllerStates(QDomDocument & doc,QDomElement & element)1297 void Song::saveControllerStates( QDomDocument & doc, QDomElement & element )
1298 {
1299 	// save settings of controllers
1300 	QDomElement controllersNode = doc.createElement( "controllers" );
1301 	element.appendChild( controllersNode );
1302 	for( int i = 0; i < m_controllers.size(); ++i )
1303 	{
1304 		m_controllers[i]->saveState( doc, controllersNode );
1305 	}
1306 }
1307 
1308 
1309 
1310 
restoreControllerStates(const QDomElement & element)1311 void Song::restoreControllerStates( const QDomElement & element )
1312 {
1313 	QDomNode node = element.firstChild();
1314 	while( !node.isNull() && !isCancelled() )
1315 	{
1316 		Controller * c = Controller::create( node.toElement(), this );
1317 		if (c) {addController(c);}
1318 		else
1319 		{
1320 			// Fix indices to ensure correct connections
1321 			m_controllers.append(Controller::create(
1322 				Controller::DummyController, this));
1323 		}
1324 
1325 		node = node.nextSibling();
1326 	}
1327 }
1328 
1329 
1330 
removeAllControllers()1331 void Song::removeAllControllers()
1332 {
1333 	while (m_controllers.size() != 0)
1334 	{
1335 		removeController(m_controllers.at(0));
1336 	}
1337 
1338 	m_controllers.clear();
1339 }
1340 
1341 
1342 
exportProjectTracks()1343 void Song::exportProjectTracks()
1344 {
1345 	exportProject( true );
1346 }
1347 
exportProject(bool multiExport)1348 void Song::exportProject( bool multiExport )
1349 {
1350 	if( isEmpty() )
1351 	{
1352 		QMessageBox::information( gui->mainWindow(),
1353 				tr( "Empty project" ),
1354 				tr( "This project is empty so exporting makes "
1355 					"no sense. Please put some items into "
1356 					"Song Editor first!" ) );
1357 		return;
1358 	}
1359 
1360 	FileDialog efd( gui->mainWindow() );
1361 
1362 	if ( multiExport )
1363 	{
1364 		efd.setFileMode( FileDialog::Directory);
1365 		efd.setWindowTitle( tr( "Select directory for writing exported tracks..." ) );
1366 		if( !m_fileName.isEmpty() )
1367 		{
1368 			efd.setDirectory( QFileInfo( m_fileName ).absolutePath() );
1369 		}
1370 	}
1371 	else
1372 	{
1373 		efd.setFileMode( FileDialog::AnyFile );
1374 		int idx = 0;
1375 		QStringList types;
1376 		while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::NumFileFormats)
1377 		{
1378 			if(ProjectRenderer::fileEncodeDevices[idx].isAvailable()) {
1379 				types << tr(ProjectRenderer::fileEncodeDevices[idx].m_description);
1380 			}
1381 			++idx;
1382 		}
1383 		efd.setNameFilters( types );
1384 		QString baseFilename;
1385 		if( !m_fileName.isEmpty() )
1386 		{
1387 			efd.setDirectory( QFileInfo( m_fileName ).absolutePath() );
1388 			baseFilename = QFileInfo( m_fileName ).completeBaseName();
1389 		}
1390 		else
1391 		{
1392 			efd.setDirectory( ConfigManager::inst()->userProjectsDir() );
1393 			baseFilename = tr( "untitled" );
1394 		}
1395 		efd.selectFile( baseFilename + ProjectRenderer::fileEncodeDevices[0].m_extension );
1396 		efd.setWindowTitle( tr( "Select file for project-export..." ) );
1397 	}
1398 
1399 	QString suffix = "wav";
1400 	efd.setDefaultSuffix( suffix );
1401 	efd.setAcceptMode( FileDialog::AcceptSave );
1402 
1403 	if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() &&
1404 					 !efd.selectedFiles()[0].isEmpty() )
1405 	{
1406 
1407 		QString exportFileName = efd.selectedFiles()[0];
1408 		if ( !multiExport )
1409 		{
1410 			int stx = efd.selectedNameFilter().indexOf( "(*." );
1411 			int etx = efd.selectedNameFilter().indexOf( ")" );
1412 
1413 			if ( stx > 0 && etx > stx )
1414 			{
1415 				// Get first extension from selected dropdown.
1416 				// i.e. ".wav" from "WAV-File (*.wav), Dummy-File (*.dum)"
1417 				suffix = efd.selectedNameFilter().mid( stx + 2, etx - stx - 2 ).split( " " )[0].trimmed();
1418 
1419 				Qt::CaseSensitivity cs = Qt::CaseSensitive;
1420 #if defined(LMMS_BUILD_APPLE) || defined(LMMS_BUILD_WIN32)
1421 				cs = Qt::CaseInsensitive;
1422 #endif
1423 				exportFileName.remove( "." + suffix, cs );
1424 				if ( efd.selectedFiles()[0].endsWith( suffix ) )
1425 				{
1426 					if( VersionedSaveDialog::fileExistsQuery( exportFileName + suffix,
1427 							tr( "Save project" ) ) )
1428 					{
1429 						exportFileName += suffix;
1430 					}
1431 				}
1432 			}
1433 		}
1434 
1435 		ExportProjectDialog epd( exportFileName, gui->mainWindow(), multiExport );
1436 		epd.exec();
1437 	}
1438 }
1439 
1440 
exportProjectMidi()1441 void Song::exportProjectMidi()
1442 {
1443 	if( isEmpty() )
1444 	{
1445 		QMessageBox::information( gui->mainWindow(),
1446 				tr( "Empty project" ),
1447 				tr( "This project is empty so exporting makes "
1448 					"no sense. Please put some items into "
1449 					"Song Editor first!" ) );
1450 		return;
1451 	}
1452 
1453 	FileDialog efd( gui->mainWindow() );
1454 
1455 	efd.setFileMode( FileDialog::AnyFile );
1456 
1457 	QStringList types;
1458 	types << tr("MIDI File (*.mid)");
1459 	efd.setNameFilters( types );
1460 	QString base_filename;
1461 	if( !m_fileName.isEmpty() )
1462 	{
1463 		efd.setDirectory( QFileInfo( m_fileName ).absolutePath() );
1464 		base_filename = QFileInfo( m_fileName ).completeBaseName();
1465 	}
1466 	else
1467 	{
1468 		efd.setDirectory( ConfigManager::inst()->userProjectsDir() );
1469 		base_filename = tr( "untitled" );
1470 	}
1471 	efd.selectFile( base_filename + ".mid" );
1472 	efd.setDefaultSuffix( "mid");
1473 	efd.setWindowTitle( tr( "Select file for project-export..." ) );
1474 
1475 	efd.setAcceptMode( FileDialog::AcceptSave );
1476 
1477 
1478 	if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() )
1479 	{
1480 		const QString suffix = ".mid";
1481 
1482 		QString export_filename = efd.selectedFiles()[0];
1483 		if (!export_filename.endsWith(suffix)) export_filename += suffix;
1484 
1485 		// NOTE start midi export
1486 
1487 		// instantiate midi export plugin
1488 		TrackContainer::TrackList tracks;
1489 		TrackContainer::TrackList tracks_BB;
1490 		tracks = Engine::getSong()->tracks();
1491 		tracks_BB = Engine::getBBTrackContainer()->tracks();
1492 		ExportFilter *exf = dynamic_cast<ExportFilter *> (Plugin::instantiate("midiexport", NULL, NULL));
1493 		if (exf==NULL) {
1494 			qDebug() << "failed to load midi export filter!";
1495 			return;
1496 		}
1497 		exf->tryExport(tracks, tracks_BB, getTempo(), m_masterPitchModel.value(), export_filename);
1498 	}
1499 }
1500 
1501 
1502 
updateFramesPerTick()1503 void Song::updateFramesPerTick()
1504 {
1505 	Engine::updateFramesPerTick();
1506 }
1507 
1508 
1509 
1510 
setModified()1511 void Song::setModified()
1512 {
1513 	if( !m_loadingProject )
1514 	{
1515 		m_modified = true;
1516 		if( gui != nullptr && gui->mainWindow() &&
1517 			QThread::currentThread() == gui->mainWindow()->thread() )
1518 		{
1519 			gui->mainWindow()->resetWindowTitle();
1520 		}
1521 	}
1522 }
1523 
1524 
1525 
1526 
addController(Controller * controller)1527 void Song::addController( Controller * controller )
1528 {
1529 	if( controller && !m_controllers.contains( controller ) )
1530 	{
1531 		m_controllers.append( controller );
1532 		emit controllerAdded( controller );
1533 
1534 		this->setModified();
1535 	}
1536 }
1537 
1538 
1539 
1540 
removeController(Controller * controller)1541 void Song::removeController( Controller * controller )
1542 {
1543 	int index = m_controllers.indexOf( controller );
1544 	if( index != -1 )
1545 	{
1546 		m_controllers.remove( index );
1547 
1548 		emit controllerRemoved( controller );
1549 		delete controller;
1550 
1551 		this->setModified();
1552 	}
1553 }
1554 
1555 
1556 
1557 
clearErrors()1558 void Song::clearErrors()
1559 {
1560 	m_errors.clear();
1561 }
1562 
1563 
1564 
collectError(const QString error)1565 void Song::collectError( const QString error )
1566 {
1567 	m_errors.append( error );
1568 }
1569 
1570 
1571 
hasErrors()1572 bool Song::hasErrors()
1573 {
1574 	return ( m_errors.length() > 0 );
1575 }
1576 
1577 
1578 
errorSummary()1579 QString Song::errorSummary()
1580 {
1581 	QString errors = m_errors.join("\n") + '\n';
1582 
1583 	errors.prepend( "\n\n" );
1584 	errors.prepend( tr( "The following errors occured while loading: " ) );
1585 
1586 	return errors;
1587 }
1588 
1589 
1590 
1591 
1592