1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 #define RG_MODULE_STRING "[RosegardenDocument]"
19 
20 #define RG_NO_DEBUG_PRINT 1
21 
22 #include "RosegardenDocument.h"
23 
24 #include "CommandHistory.h"
25 #include "RoseXmlHandler.h"
26 #include "GzipFile.h"
27 
28 #include "base/AudioDevice.h"
29 #include "base/AudioPluginInstance.h"
30 #include "base/BaseProperties.h"
31 #include "base/Composition.h"
32 #include "base/Configuration.h"
33 #include "base/Device.h"
34 #include "base/Event.h"
35 #include "base/Exception.h"
36 #include "base/Instrument.h"
37 #include "base/SegmentLinker.h"
38 #include "base/MidiDevice.h"
39 #include "base/MidiProgram.h"
40 #include "base/MidiTypes.h"
41 #include "base/NotationTypes.h"
42 #include "base/Profiler.h"
43 #include "base/RealTime.h"
44 #include "base/RecordIn.h"
45 #include "base/Segment.h"
46 #include "base/SoftSynthDevice.h"
47 #include "base/Studio.h"
48 #include "base/Track.h"
49 #include "base/XmlExportable.h"
50 #include "commands/edit/EventQuantizeCommand.h"
51 #include "commands/notation/NormalizeRestsCommand.h"
52 #include "commands/segment/AddTracksCommand.h"
53 #include "commands/segment/SegmentInsertCommand.h"
54 #include "commands/segment/SegmentRecordCommand.h"
55 #include "commands/segment/ChangeCompositionLengthCommand.h"
56 #include "gui/editors/segment/TrackEditor.h"
57 #include "gui/editors/segment/TrackButtons.h"
58 #include "gui/general/ClefIndex.h"
59 #include "gui/application/TransportStatus.h"
60 #include "gui/application/RosegardenMainWindow.h"
61 #include "gui/application/RosegardenMainViewWidget.h"
62 #include "gui/dialogs/UnusedAudioSelectionDialog.h"
63 #include "gui/editors/segment/compositionview/AudioPeaksThread.h"
64 #include "gui/editors/segment/TrackLabel.h"
65 #include "gui/general/EditViewBase.h"
66 #include "gui/general/GUIPalette.h"
67 #include "gui/general/ResourceFinder.h"
68 #include "gui/widgets/StartupLogo.h"
69 #include "gui/seqmanager/SequenceManager.h"
70 #include "gui/studio/AudioPluginManager.h"
71 #include "gui/studio/StudioControl.h"
72 #include "gui/general/AutoSaveFinder.h"
73 #include "sequencer/RosegardenSequencer.h"
74 #include "sound/AudioFile.h"
75 #include "sound/AudioFileManager.h"
76 #include "sound/ExternalController.h"
77 #include "sound/MappedCommon.h"
78 #include "sound/MappedEventList.h"
79 #include "sound/MappedDevice.h"
80 #include "sound/MappedInstrument.h"
81 #include "sound/MappedEvent.h"
82 #include "sound/MappedStudio.h"
83 #include "sound/PluginIdentifier.h"
84 #include "sound/SoundDriver.h"
85 #include "sound/Midi.h"
86 #include "misc/AppendLabel.h"
87 #include "misc/Debug.h"
88 #include "misc/Strings.h"
89 #include "document/Command.h"
90 #include "document/io/XMLReader.h"
91 #include "misc/ConfigGroups.h"
92 
93 #include "rosegarden-version.h"
94 
95 #include <QApplication>
96 #include <QSettings>
97 #include <QMessageBox>
98 #include <QProcess>
99 #include <QTemporaryFile>
100 #include <QByteArray>
101 #include <QDataStream>
102 #include <QDialog>
103 #include <QDir>
104 #include <QFile>
105 #include <QFileInfo>
106 #include <QObject>
107 #include <QString>
108 #include <QStringList>
109 #include <QTextStream>
110 #include <QWidget>
111 #include <QHostInfo>
112 #include <QLockFile>
113 
114 namespace Rosegarden
115 {
116 
117 using namespace BaseProperties;
118 
RosegardenDocument(QObject * parent,QSharedPointer<AudioPluginManager> pluginManager,bool skipAutoload,bool clearCommandHistory,bool enableSound)119 RosegardenDocument::RosegardenDocument(
120         QObject *parent,
121         QSharedPointer<AudioPluginManager> pluginManager,
122         bool skipAutoload,
123         bool clearCommandHistory,
124         bool enableSound) :
125     QObject(parent),
126     m_modified(false),
127     m_autoSaved(false),
128     m_lockFile(nullptr),
129     m_audioPeaksThread(&m_audioFileManager),
130     m_seqManager(nullptr),
131     m_pluginManager(pluginManager),
132     m_audioRecordLatency(0, 0),
133     m_quickMarkerTime(-1),
134     m_autoSavePeriod(0),
135     m_beingDestroyed(false),
136     m_clearCommandHistory(clearCommandHistory),
137     m_soundEnabled(enableSound)
138 {
139     connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()),
140             this, SLOT(slotDocumentModified()));
141 
142     connect(CommandHistory::getInstance(), &CommandHistory::documentRestored,
143             this, &RosegardenDocument::slotDocumentRestored);
144 
145     // autoload a new document
146     if (!skipAutoload)
147         performAutoload();
148 
149     // now set it up as a "new document"
150     newDocument();
151 }
152 
~RosegardenDocument()153 RosegardenDocument::~RosegardenDocument()
154 {
155     RG_DEBUG << "~RosegardenDocument()";
156     m_beingDestroyed = true;
157 
158     m_audioPeaksThread.finish();
159     m_audioPeaksThread.wait();
160 
161     deleteEditViews();
162 
163     //     ControlRulerCanvasRepository::clear();
164 
165     if (m_clearCommandHistory) CommandHistory::getInstance()->clear(); // before Composition is deleted
166 
167     release();
168 }
169 
170 unsigned int
getAutoSavePeriod() const171 RosegardenDocument::getAutoSavePeriod() const
172 {
173     QSettings settings;
174     settings.beginGroup( GeneralOptionsConfigGroup );
175 
176     unsigned int ret;
177     ret = settings.value("autosaveinterval", 60).toUInt();
178 
179     settings.endGroup();        // corresponding to: settings.beginGroup( GeneralOptionsConfigGroup );
180     return ret;
181 }
182 
attachView(RosegardenMainViewWidget * view)183 void RosegardenDocument::attachView(RosegardenMainViewWidget *view)
184 {
185     m_viewList.append(view);
186 }
187 
detachView(RosegardenMainViewWidget * view)188 void RosegardenDocument::detachView(RosegardenMainViewWidget *view)
189 {
190     m_viewList.removeOne(view);
191 }
192 
attachEditView(EditViewBase * view)193 void RosegardenDocument::attachEditView(EditViewBase *view)
194 {
195     m_editViewList.append(view);
196 }
197 
detachEditView(EditViewBase * view)198 void RosegardenDocument::detachEditView(EditViewBase *view)
199 {
200     // auto-deletion is disabled, as
201     // the editview detaches itself when being deleted
202     m_editViewList.removeOne(view);
203 }
204 
deleteEditViews()205 void RosegardenDocument::deleteEditViews()
206 {
207     QList<EditViewBase*> views = m_editViewList;
208     m_editViewList.clear();
209     qDeleteAll(views);
210 }
211 
setAbsFilePath(const QString & filename)212 void RosegardenDocument::setAbsFilePath(const QString &filename)
213 {
214     m_absFilePath = filename;
215 }
216 
setTitle(const QString & title)217 void RosegardenDocument::setTitle(const QString &title)
218 {
219     m_title = title;
220 }
221 
getAbsFilePath() const222 const QString &RosegardenDocument::getAbsFilePath() const
223 {
224     return m_absFilePath;
225 }
226 
deleteAutoSaveFile()227 void RosegardenDocument::deleteAutoSaveFile()
228 {
229     QFile::remove(getAutoSaveFileName());
230 }
231 
getTitle() const232 const QString& RosegardenDocument::getTitle() const
233 {
234     return m_title;
235 }
236 
slotUpdateAllViews(RosegardenMainViewWidget * sender)237 void RosegardenDocument::slotUpdateAllViews(RosegardenMainViewWidget *sender)
238 {
239     RG_DEBUG << "RosegardenDocument::slotUpdateAllViews";
240     for (int i = 0; i < int(m_viewList.size()); ++i ){
241         if (m_viewList.at(i) != sender) {
242             // try to fix another crash, though I don't really understand
243             // exactly what m_viewList is, etc.
244             if (m_viewList.at(i)) {
245                 m_viewList.at(i)->update();
246             }
247         }
248     }
249 }
250 
setModified()251 void RosegardenDocument::setModified()
252 {
253     // Already set?  Bail.
254     if (m_modified)
255         return;
256 
257     m_modified = true;
258     m_autoSaved = false;
259 
260     // Make sure the star (*) appears in the title bar.
261     RosegardenMainWindow::self()->slotUpdateTitle(true);
262 }
263 
clearModifiedStatus()264 void RosegardenDocument::clearModifiedStatus()
265 {
266     m_modified = false;
267     m_autoSaved = true;
268 
269     emit documentModified(false);
270 }
271 
slotDocumentModified()272 void RosegardenDocument::slotDocumentModified()
273 {
274     m_modified = true;
275     m_autoSaved = false;
276 
277     emit documentModified(true);
278 }
279 
slotDocumentRestored()280 void RosegardenDocument::slotDocumentRestored()
281 {
282     RG_DEBUG << "RosegardenDocument::slotDocumentRestored()";
283     m_modified = false;
284 
285     // if we hit the bottom of the undo stack, emit this so the modified flag
286     // will be cleared from assorted title bars
287     emit documentModified(false);
288 }
289 
290 void
setQuickMarker()291 RosegardenDocument::setQuickMarker()
292 {
293     RG_DEBUG << "RosegardenDocument::setQuickMarker";
294 
295     m_quickMarkerTime = getComposition().getPosition();
296 }
297 
298 void
jumpToQuickMarker()299 RosegardenDocument::jumpToQuickMarker()
300 {
301     RG_DEBUG << "RosegardenDocument::jumpToQuickMarker";
302 
303     if (m_quickMarkerTime >= 0)
304         slotSetPointerPosition(m_quickMarkerTime);
305 }
306 
getAutoSaveFileName()307 QString RosegardenDocument::getAutoSaveFileName()
308 {
309     QString filename = getAbsFilePath();
310     if (filename.isEmpty())
311         filename = QDir::currentPath() + "/" + getTitle();
312 
313     //!!! NB this should _not_ use the new TempDirectory class -- that
314     //!!! is for files that are more temporary than this.  Its files
315     //!!! are cleaned up after a crash, the next time RG is started,
316     //!!! so they aren't appropriate for recovery purposes.
317 
318     QString autoSaveFileName = AutoSaveFinder().getAutoSavePath(filename);
319     RG_DEBUG << "RosegardenDocument::getAutoSaveFilename(): returning "
320              << autoSaveFileName;
321 
322     return autoSaveFileName;
323 }
324 
slotAutoSave()325 void RosegardenDocument::slotAutoSave()
326 {
327     //     RG_DEBUG << "RosegardenDocument::slotAutoSave()";
328 
329     if (isAutoSaved() || !isModified())
330         return ;
331 
332     QString autoSaveFileName = getAutoSaveFileName();
333 
334     RG_DEBUG << "RosegardenDocument::slotAutoSave() - doc modified - saving '"
335     << getAbsFilePath() << "' as"
336     << autoSaveFileName;
337 
338     QString errMsg;
339 
340     saveDocument(autoSaveFileName, errMsg, true);
341 
342 }
343 
isRegularDotRGFile() const344 bool RosegardenDocument::isRegularDotRGFile() const
345 {
346     return getAbsFilePath().right(3).toLower() == ".rg";
347 }
348 
349 bool
deleteOrphanedAudioFiles(bool documentWillNotBeSaved)350 RosegardenDocument::deleteOrphanedAudioFiles(bool documentWillNotBeSaved)
351 {
352     std::vector<QString> recordedOrphans;
353     std::vector<QString> derivedOrphans;
354 
355     if (documentWillNotBeSaved) {
356 
357         // All audio files recorded or derived in this session are
358         // about to become orphans
359 
360         for (std::vector<AudioFile *>::const_iterator i =
361                     m_audioFileManager.begin();
362                 i != m_audioFileManager.end(); ++i) {
363 
364             if (m_audioFileManager.wasAudioFileRecentlyRecorded((*i)->getId())) {
365                 recordedOrphans.push_back((*i)->getFilename());
366             }
367 
368             if (m_audioFileManager.wasAudioFileRecentlyDerived((*i)->getId())) {
369                 derivedOrphans.push_back((*i)->getFilename());
370             }
371         }
372     }
373 
374     // Whether we save or not, explicitly orphaned (i.e. recorded in
375     // this session and then unloaded) recorded files are orphans.
376     // Make sure they are actually unknown to the audio file manager
377     // (i.e. they haven't been loaded more than once, or reloaded
378     // after orphaning).
379 
380     for (std::vector<QString>::iterator i = m_orphanedRecordedAudioFiles.begin();
381             i != m_orphanedRecordedAudioFiles.end(); ++i) {
382 
383         bool stillHave = false;
384 
385         for (std::vector<AudioFile *>::const_iterator j =
386                  m_audioFileManager.begin();
387                 j != m_audioFileManager.end(); ++j) {
388             if ((*j)->getFilename() == *i) {
389                 stillHave = true;
390                 break;
391             }
392         }
393 
394         if (!stillHave) recordedOrphans.push_back(*i);
395     }
396 
397     // Derived orphans get deleted whatever happens
398     //!!! Should we orphan any file derived during this session that
399     //is not currently used in a segment?  Probably: we have no way to
400     //reuse them
401 
402     for (std::vector<QString>::iterator i = m_orphanedDerivedAudioFiles.begin();
403             i != m_orphanedDerivedAudioFiles.end(); ++i) {
404 
405         bool stillHave = false;
406 
407         for (std::vector<AudioFile *>::const_iterator j =
408                  m_audioFileManager.begin();
409                 j != m_audioFileManager.end(); ++j) {
410             if ((*j)->getFilename() == *i) {
411                 stillHave = true;
412                 break;
413             }
414         }
415 
416         if (!stillHave) derivedOrphans.push_back(*i);
417     }
418 
419     for (size_t i = 0; i < derivedOrphans.size(); ++i) {
420         QFile file(derivedOrphans[i]);
421         if (!file.remove()) {
422             std::cerr << "WARNING: Failed to remove orphaned derived audio file \"" << derivedOrphans[i] << std::endl;
423         }
424         QFile peakFile(QString("%1.pk").arg(derivedOrphans[i]));
425         peakFile.remove();
426     }
427 
428     m_orphanedDerivedAudioFiles.clear();
429 
430     if (recordedOrphans.empty())
431         return true;
432 
433     if (documentWillNotBeSaved) {
434 
435         int reply = QMessageBox::warning
436             (dynamic_cast<QWidget *>(parent()), "Warning",
437              tr("Delete the %n audio file(s) recorded during the unsaved session?", "", recordedOrphans.size()),
438              QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
439              QMessageBox::Cancel);
440 
441         switch (reply) {
442 
443         case QMessageBox::Yes:
444             break;
445 
446         case QMessageBox::No:
447             return true;
448 
449         default:
450         case QMessageBox::Cancel:
451             return false;
452         }
453 
454     } else {
455 
456         UnusedAudioSelectionDialog *dialog =
457             new UnusedAudioSelectionDialog
458             (dynamic_cast<QWidget *>(parent()),
459              tr("The following audio files were recorded during this session but have been unloaded\nfrom the audio file manager, and so are no longer in use in the document you are saving.\n\nYou may want to clean up these files to save disk space.\n\nPlease select any you wish to delete permanently from the hard disk.\n"),
460              recordedOrphans);
461 
462         if (dialog->exec() != QDialog::Accepted) {
463             delete dialog;
464             return true;
465         }
466 
467         recordedOrphans = dialog->getSelectedAudioFileNames();
468         delete dialog;
469     }
470 
471     if (recordedOrphans.empty())
472         return true;
473 
474     QString question =
475         tr("<qt>About to delete %n audio file(s) permanently from the hard disk.<br>There will be no way to recover the file(s).<br>Are you sure?</qt>", "", recordedOrphans.size());
476 
477     int reply = QMessageBox::warning(dynamic_cast<QWidget *>(parent()), "Warning", question, QMessageBox::Ok | QMessageBox::Cancel);
478 
479     if (reply == QMessageBox::Ok) {
480         for (size_t i = 0; i < recordedOrphans.size(); ++i) {
481             QFile file(recordedOrphans[i]);
482             if (!file.remove()) {
483                 QMessageBox::critical(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("File %1 could not be deleted.")
484                                     .arg(recordedOrphans[i]));
485             }
486 
487             QFile peakFile(QString("%1.pk").arg(recordedOrphans[i]));
488             peakFile.remove();
489         }
490     }
491 
492     return true;
493 }
494 
newDocument()495 void RosegardenDocument::newDocument()
496 {
497     m_modified = false;
498     setAbsFilePath(QString());
499     setTitle(tr("Untitled"));
500     if (m_clearCommandHistory) CommandHistory::getInstance()->clear();
501 }
502 
performAutoload()503 void RosegardenDocument::performAutoload()
504 {
505     QString autoloadFile = ResourceFinder().getAutoloadPath();
506 
507     QFileInfo autoloadFileInfo(autoloadFile);
508 
509     if (autoloadFile == "" || !autoloadFileInfo.isReadable()) {
510         std::cerr << "WARNING: RosegardenDocument::performAutoload - "
511                   << "can't find autoload file - defaulting" << std::endl;
512         return ;
513     }
514 
515     bool permanent = true;
516     bool squelchProgressDialog = true;
517     // Don't lock the autoload.rg file.
518     bool enableLock = false;
519     openDocument(autoloadFile, permanent, squelchProgressDialog, enableLock);
520 }
521 
openDocument(const QString & filename,bool permanent,bool squelchProgressDialog,bool enableLock)522 bool RosegardenDocument::openDocument(const QString &filename,
523                                       bool permanent,
524                                       bool squelchProgressDialog,
525                                       bool enableLock)
526 {
527     RG_DEBUG << "openDocument(" << filename << ")";
528 
529     if (filename.isEmpty())
530         return false;
531 
532     newDocument();
533 
534     QFileInfo fileInfo(filename);
535     setTitle(fileInfo.fileName());
536 
537     // If the file cannot be read, or it's a directory
538     if (!fileInfo.isReadable() || fileInfo.isDir()) {
539         StartupLogo::hideIfStillThere();
540 
541         QString msg(tr("Can't open file '%1'").arg(filename));
542         QMessageBox::warning(dynamic_cast<QWidget *>(parent()),
543                              tr("Rosegarden"), msg);
544 
545         return false;
546     }
547 
548     // Progress Dialog
549     // Note: The label text and range will be set later as needed.
550     QProgressDialog progressDialog(
551             tr("Reading file..."),  // labelText
552             tr("Cancel"),  // cancelButtonText
553             0, 100,  // min, max
554             RosegardenMainWindow::self());  // parent
555     progressDialog.setWindowTitle(tr("Rosegarden"));
556     progressDialog.setWindowModality(Qt::WindowModal);
557     // Don't want to auto close since this is a multi-step
558     // process.  Any of the steps may set progress to 100.  We
559     // will close anyway when this object goes out of scope.
560     progressDialog.setAutoClose(false);
561     // We're usually a bit late to the game here as it is.  Shave off a
562     // couple of seconds to make up for it.
563     // ??? We should move the progress dialog further up the call chain
564     //     to include the additional time.
565     //progressDialog.setMinimumDuration(2000);
566 
567     m_progressDialog = &progressDialog;
568 
569     if (squelchProgressDialog) {
570         m_progressDialog = nullptr;
571     } else {
572         // Just force the progress dialog up.
573         // Both Qt4 and Qt5 have bugs related to delayed showing of progress
574         // dialogs.  In Qt4, the dialog sometimes won't show.  In Qt5, KDE
575         // based distros might lock up.  See Bug #1546.
576         progressDialog.show();
577     }
578 
579     setAbsFilePath(fileInfo.absoluteFilePath());
580 
581     // Lock.
582 
583     if (permanent  &&  enableLock) {
584         if (!lock()) {
585             // Avoid deleting the lock file by clearing out this document.
586             newDocument();
587 
588             return false;
589         }
590     }
591 
592     // Load.
593 
594     QString fileContents;
595 
596     // Unzip
597     bool okay = GzipFile::readFromFile(filename, fileContents);
598 
599     QString errMsg;
600     bool cancelled = false;
601 
602     if (!okay) {
603         errMsg = tr("Could not open Rosegarden file");
604     } else {
605         // Parse the XML
606         okay = xmlParse(fileContents,
607                         errMsg,
608                         permanent,
609                         cancelled);
610     }
611 
612     if (!okay) {
613         StartupLogo::hideIfStillThere();
614 
615         QString msg(tr("Error when parsing file '%1': \"%2\"")
616                      .arg(filename)
617                      .arg(errMsg));
618         QMessageBox::warning(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), msg);
619 
620         return false;
621     }
622 
623     if (cancelled) {
624         // Don't leave a lock file around.
625         release();
626 
627         newDocument();
628 
629         return false;
630     }
631 
632     RG_DEBUG << "openDocument() - "
633              << "m_composition : " << &m_composition
634              << " - m_composition->getNbSegments() : "
635              << m_composition.getNbSegments()
636              << " - m_composition->getDuration() : "
637              << m_composition.getDuration();
638 
639     if (m_composition.begin() != m_composition.end()) {
640         RG_DEBUG << "First segment starts at " << (*m_composition.begin())->getStartTime();
641     }
642 
643     m_audioFileManager.setProgressDialog(m_progressDialog);
644 
645     try {
646         // generate any audio previews after loading the files
647         m_audioFileManager.generatePreviews();
648     } catch (const Exception &e) {
649         StartupLogo::hideIfStillThere();
650         QMessageBox::critical(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), strtoqstr(e.getMessage()));
651     }
652 
653     RG_DEBUG << "openDocument(): Successfully opened document \"" << filename << "\"";
654 
655     return true;
656 }
657 
658 void
stealLockFile(RosegardenDocument * other)659 RosegardenDocument::stealLockFile(RosegardenDocument *other)
660 {
661     Q_ASSERT(!m_lockFile);
662     m_lockFile = other->m_lockFile;
663     other->m_lockFile = nullptr;
664 }
665 
666 void
mergeDocument(RosegardenDocument * doc,int options)667 RosegardenDocument::mergeDocument(RosegardenDocument *doc,
668                                   int options)
669 {
670     MacroCommand *command = new MacroCommand(tr("Merge"));
671 
672     timeT time0 = 0;
673     if (options & MERGE_AT_END) {
674         time0 = getComposition().getBarEndForTime(getComposition().getDuration());
675     }
676 
677     int myMaxTrack = getComposition().getNbTracks();
678     int yrMinTrack = 0;
679     int yrMaxTrack = doc->getComposition().getNbTracks();
680     int yrNrTracks = yrMaxTrack - yrMinTrack + 1;
681 
682     int firstAlteredTrack = yrMinTrack;
683 
684     if (options & MERGE_IN_NEW_TRACKS) {
685 
686         //!!! worry about instruments and other studio stuff later... if at all
687         command->addCommand(new AddTracksCommand
688                             (&getComposition(),
689                              yrNrTracks,
690                              MidiInstrumentBase,
691                              -1));
692 
693         firstAlteredTrack = myMaxTrack + 1;
694 
695     } else if (yrMaxTrack > myMaxTrack) {
696 
697         command->addCommand(new AddTracksCommand
698                             (&getComposition(),
699                              yrMaxTrack - myMaxTrack,
700                              MidiInstrumentBase,
701                              -1));
702     }
703 
704     TrackId firstNewTrackId = getComposition().getNewTrackId();
705     timeT lastSegmentEndTime = 0;
706 
707     for (Composition::iterator i = doc->getComposition().begin(), j = i;
708          i != doc->getComposition().end(); i = j) {
709 
710         ++j;
711         Segment *s = *i;
712         timeT segmentEndTime = s->getEndMarkerTime();
713 
714         int yrTrack = s->getTrack();
715         Track *t = doc->getComposition().getTrackById(yrTrack);
716         if (t) yrTrack = t->getPosition();
717 
718         int myTrack = yrTrack;
719 
720         if (options & MERGE_IN_NEW_TRACKS) {
721             myTrack = yrTrack - yrMinTrack + myMaxTrack + 1;
722         }
723 
724         doc->getComposition().detachSegment(s);
725 
726         if (options & MERGE_AT_END) {
727             s->setStartTime(s->getStartTime() + time0);
728             segmentEndTime += time0;
729         }
730         if (segmentEndTime > lastSegmentEndTime) {
731             lastSegmentEndTime = segmentEndTime;
732         }
733 
734         Track *track = getComposition().getTrackByPosition(myTrack);
735         TrackId tid = 0;
736         if (track) tid = track->getId();
737         else tid = firstNewTrackId + yrTrack - yrMinTrack;
738 
739         command->addCommand(new SegmentInsertCommand(&getComposition(), s, tid));
740     }
741 
742     if (!(options & MERGE_KEEP_OLD_TIMINGS)) {
743         for (int i = getComposition().getTimeSignatureCount() - 1; i >= 0; --i) {
744             getComposition().removeTimeSignature(i);
745         }
746         for (int i = getComposition().getTempoChangeCount() - 1; i >= 0; --i) {
747             getComposition().removeTempoChange(i);
748         }
749     }
750 
751     if (options & MERGE_KEEP_NEW_TIMINGS) {
752         for (int i = 0; i < doc->getComposition().getTimeSignatureCount(); ++i) {
753             std::pair<timeT, TimeSignature> ts =
754                 doc->getComposition().getTimeSignatureChange(i);
755             getComposition().addTimeSignature(ts.first + time0, ts.second);
756         }
757         for (int i = 0; i < doc->getComposition().getTempoChangeCount(); ++i) {
758             std::pair<timeT, tempoT> t =
759                 doc->getComposition().getTempoChange(i);
760             getComposition().addTempoAtTime(t.first + time0, t.second);
761         }
762     }
763 
764     if (lastSegmentEndTime > getComposition().getEndMarker()) {
765         command->addCommand(new ChangeCompositionLengthCommand
766                             (&getComposition(),
767                              getComposition().getStartMarker(),
768                              lastSegmentEndTime,
769                              getComposition().autoExpandEnabled()));
770     }
771 
772     CommandHistory::getInstance()->addCommand(command);
773 
774     emit makeTrackVisible(firstAlteredTrack + yrNrTracks/2 + 1);
775 }
776 
sendChannelSetups(bool reset)777 void RosegardenDocument::sendChannelSetups(bool reset)
778 {
779     std::set<DeviceId> devicesSeen;
780     std::set<InstrumentId> instrumentsSeen;
781 
782     // For each track in the composition, send the channel setup
783     for (Composition::trackcontainer::const_iterator i =
784              m_composition.getTracks().begin();
785          i != m_composition.getTracks().end();
786          ++i) {
787 
788         //const TrackId trackId = i->first;
789         const Track *track = i->second;
790 
791         const InstrumentId instrumentId = track->getInstrument();
792 
793         // If we've already seen this instrument, try the next track.
794         if (instrumentsSeen.find(instrumentId) != instrumentsSeen.end())
795             continue;
796 
797         instrumentsSeen.insert(instrumentId);
798 
799         Instrument *instrument = m_studio.getInstrumentById(instrumentId);
800 
801         // If this instrument doesn't exist, try the next track.
802         if (!instrument)
803             continue;
804 
805         // If this isn't a MIDI instrument, try the next track.
806         if (instrument->getType() != Instrument::Midi)
807             continue;
808 
809         // The reset feature is experimental and unused (all callers
810         // specify reset == false).  I suspect it might
811         // cause trouble with some synths.  It's probably also
812         // ignored by some.  Might want to make this configurable
813         // via the .conf or maybe pop up a dialog to ask whether
814         // to send resets.
815 
816         const DeviceId deviceId = instrument->getDevice()->getId();
817 
818         // If we've not seen this Device before
819         if (reset  &&  devicesSeen.find(deviceId) == devicesSeen.end())
820         {
821             // Send a Reset
822             // ??? Would it be better to send the resets, wait a few seconds,
823             //     then send the channel setups?  Some hardware might need
824             //     time to respond to a reset.
825 
826             const MappedEvent mappedEvent(
827                     instrumentId,
828                     MappedEvent::MidiSystemMessage,
829                     MIDI_SYSTEM_RESET);
830 
831             StudioControl::sendMappedEvent(mappedEvent);
832 
833             // Add Device to devices we've seen.
834             devicesSeen.insert(deviceId);
835         }
836 
837         // If this instrument isn't in fixed channel mode, try the next track.
838         if (!instrument->hasFixedChannel())
839             continue;
840 
841         // Call Instrument::sendChannelSetup() to make sure the program
842         // change for this track has been sent out.
843         // The test case (MIPP #35) for this is a bit esoteric:
844         //   1. Load a composition and play it.
845         //   2. Load a different composition, DO NOT play it.
846         //   3. Select tracks in the new composition and play the MIDI
847         //      input keyboard.
848         //   4. Verify that you hear the programs for the new composition.
849         // Without the following, you'll hear the programs for the old
850         // composition.
851         instrument->sendChannelSetup();
852     }
853 }
854 
initialiseStudio()855 void RosegardenDocument::initialiseStudio()
856 {
857     //Profiler profiler("initialiseStudio", true);
858 
859     RG_DEBUG << "initialiseStudio() begin...";
860 
861     // Destroy all the mapped objects in the studio.
862     RosegardenSequencer::getInstance()->clearStudio();
863 
864     // To reduce the number of DCOP calls at this stage, we put some
865     // of the float property values in a big list and commit in one
866     // single call at the end.  We can only do this with properties
867     // that aren't depended on by other port, connection, or non-float
868     // properties during the initialisation process.
869     MappedObjectIdList ids;
870     MappedObjectPropertyList properties;
871     MappedObjectValueList values;
872 
873     // All the softsynths, audio instruments, and busses.
874     std::vector<PluginContainer *> pluginContainers;
875 
876     BussList busses = m_studio.getBusses();
877 
878     // For each buss (first one is master)
879     for (size_t i = 0; i < busses.size(); ++i) {
880 
881         MappedObjectId mappedId =
882             StudioControl::createStudioObject(MappedObject::AudioBuss);
883 
884         StudioControl::setStudioObjectProperty(mappedId,
885                                                MappedAudioBuss::BussId,
886                                                MappedObjectValue(i));
887 
888         // Level
889         ids.push_back(mappedId);
890         properties.push_back(MappedAudioBuss::Level);
891         values.push_back(MappedObjectValue(busses[i]->getLevel()));
892 
893         // Pan
894         ids.push_back(mappedId);
895         properties.push_back(MappedAudioBuss::Pan);
896         values.push_back(MappedObjectValue(busses[i]->getPan()) - 100.0);
897 
898         busses[i]->setMappedId(mappedId);
899 
900         pluginContainers.push_back(busses[i]);
901     }
902 
903     RecordInList recordIns = m_studio.getRecordIns();
904 
905     // For each record in
906     for (size_t i = 0; i < recordIns.size(); ++i) {
907 
908         MappedObjectId mappedId =
909             StudioControl::createStudioObject(MappedObject::AudioInput);
910 
911         StudioControl::setStudioObjectProperty(mappedId,
912                                                MappedAudioInput::InputNumber,
913                                                MappedObjectValue(i));
914 
915         recordIns[i]->mappedId = mappedId;
916     }
917 
918     InstrumentList list = m_studio.getAllInstruments();
919 
920     // For each instrument
921     for (InstrumentList::iterator it = list.begin();
922          it != list.end();
923          ++it) {
924         Instrument &instrument = **it;
925 
926         if (instrument.getType() == Instrument::Audio  ||
927             instrument.getType() == Instrument::SoftSynth) {
928 
929             MappedObjectId mappedId =
930                 StudioControl::createStudioObject(MappedObject::AudioFader);
931 
932             instrument.setMappedId(mappedId);
933 
934             //RG_DEBUG << "initialiseStudio(): Setting mapped object ID = " << mappedId << " - on Instrument " << (*it)->getId();
935 
936             StudioControl::setStudioObjectProperty(
937                     mappedId,
938                     MappedObject::Instrument,
939                     MappedObjectValue(instrument.getId()));
940 
941             // Fader level
942             ids.push_back(mappedId);
943             properties.push_back(MappedAudioFader::FaderLevel);
944             values.push_back(static_cast<MappedObjectValue>(instrument.getLevel()));
945 
946             // Fader record level
947             ids.push_back(mappedId);
948             properties.push_back(MappedAudioFader::FaderRecordLevel);
949             values.push_back(static_cast<MappedObjectValue>(instrument.getRecordLevel()));
950 
951             // Channels
952             ids.push_back(mappedId);
953             properties.push_back(MappedAudioFader::Channels);
954             values.push_back(static_cast<MappedObjectValue>(instrument.getAudioChannels()));
955 
956             // Pan
957             ids.push_back(mappedId);
958             properties.push_back(MappedAudioFader::Pan);
959             values.push_back(static_cast<MappedObjectValue>(instrument.getPan()) - 100.0f);
960 
961             // Set up connections
962 
963             // Clear any existing connections (shouldn't be necessary, but)
964             StudioControl::disconnectStudioObject(mappedId);
965 
966             // Handle the output connection.
967             BussId outputBuss = instrument.getAudioOutput();
968             if (outputBuss < (unsigned int)busses.size()) {
969                 MappedObjectId bussMappedId = busses[outputBuss]->getMappedId();
970 
971                 if (bussMappedId > 0)
972                     StudioControl::connectStudioObjects(mappedId, bussMappedId);
973             }
974 
975             // Handle the input connection.
976             bool isBuss;
977             int channel;
978             int input = instrument.getAudioInput(isBuss, channel);
979 
980             MappedObjectId inputMappedId = 0;
981 
982             if (isBuss) {
983                 if (input < static_cast<int>(busses.size()))
984                     inputMappedId = busses[input]->getMappedId();
985             } else {
986                 if (input < static_cast<int>(recordIns.size()))
987                     inputMappedId = recordIns[input]->mappedId;
988             }
989 
990             ids.push_back(mappedId);
991             properties.push_back(MappedAudioFader::InputChannel);
992             values.push_back(MappedObjectValue(channel));
993 
994             if (inputMappedId > 0)
995                 StudioControl::connectStudioObjects(inputMappedId, mappedId);
996 
997             pluginContainers.push_back(&instrument);
998 
999         }
1000     }
1001 
1002     sendChannelSetups(false);  // no resets
1003 
1004     RG_DEBUG << "initialiseStudio(): Have " << pluginContainers.size() << " plugin container(s)";
1005 
1006     // For each softsynth, audio instrument, and buss
1007     for (std::vector<PluginContainer *>::iterator pluginContainerIter =
1008                  pluginContainers.begin();
1009          pluginContainerIter != pluginContainers.end();
1010          ++pluginContainerIter) {
1011 
1012         PluginContainer &pluginContainer = **pluginContainerIter;
1013 
1014         // Initialise all the plugins for this Instrument or Buss
1015 
1016         // For each plugin within this instrument or buss
1017         for (AudioPluginVector::iterator pli = pluginContainer.beginPlugins();
1018              pli != pluginContainer.endPlugins();
1019              ++pli) {
1020 
1021             AudioPluginInstance &plugin = **pli;
1022 
1023             RG_DEBUG << "initialiseStudio(): Container id " << pluginContainer.getId()
1024                      << ", plugin position " << plugin.getPosition()
1025                      << ", identifier " << plugin.getIdentifier()
1026                      << ", assigned " << plugin.isAssigned();
1027 
1028             if (!plugin.isAssigned())
1029                 continue;
1030 
1031             RG_DEBUG << "initialiseStudio(): Found an assigned plugin";
1032 
1033             // Plugin Slot
1034             MappedObjectId pluginMappedId =
1035                     StudioControl::createStudioObject(
1036                             MappedObject::PluginSlot);
1037 
1038             plugin.setMappedId(pluginMappedId);
1039 
1040             RG_DEBUG << "initialiseStudio(): Creating plugin ID = " << pluginMappedId;
1041 
1042             // Position
1043             StudioControl::setStudioObjectProperty(
1044                     pluginMappedId,
1045                     MappedObject::Position,
1046                     MappedObjectValue(plugin.getPosition()));
1047 
1048             // Instrument
1049             StudioControl::setStudioObjectProperty(
1050                     pluginMappedId,
1051                     MappedObject::Instrument,
1052                     pluginContainer.getId());
1053 
1054             // Identifier
1055             StudioControl::setStudioObjectProperty(
1056                     pluginMappedId,
1057                     MappedPluginSlot::Identifier,
1058                     plugin.getIdentifier().c_str());
1059 
1060             plugin.setConfigurationValue(
1061                     qstrtostr(PluginIdentifier::RESERVED_PROJECT_DIRECTORY_KEY),
1062                     qstrtostr(getAudioFileManager().getAudioPath()));
1063 
1064             // Set opaque string configuration data (e.g. for DSSI plugin)
1065 
1066             MappedObjectPropertyList config;
1067 
1068             for (AudioPluginInstance::ConfigMap::const_iterator i =
1069                          plugin.getConfiguration().begin();
1070                  i != plugin.getConfiguration().end();
1071                  ++i) {
1072                 config.push_back(strtoqstr(i->first));
1073                 config.push_back(strtoqstr(i->second));
1074 
1075                 RG_DEBUG << "initialiseStudio(): plugin configuration: " << i->first << " -> " << i->second;
1076             }
1077 
1078             RG_DEBUG << "initialiseStudio(): plugin configuration: " << config.size() << " values";
1079 
1080             QString error = StudioControl::setStudioObjectPropertyList(
1081                     pluginMappedId,
1082                     MappedPluginSlot::Configuration,
1083                     config);
1084 
1085             if (error != "") {
1086                 StartupLogo::hideIfStillThere();
1087 //                if (m_progressDialog)
1088 //                    m_progressDialog->hide();
1089                 QMessageBox::warning(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), error);
1090 //                if (m_progressDialog)
1091 //                    m_progressDialog->show();
1092             }
1093 
1094             // Bypassed
1095             ids.push_back(pluginMappedId);
1096             properties.push_back(MappedPluginSlot::Bypassed);
1097             values.push_back(MappedObjectValue(plugin.isBypassed()));
1098 
1099             // Port Values
1100 
1101             for (PortInstanceIterator portIt = plugin.begin();
1102                  portIt != plugin.end();
1103                  ++portIt) {
1104                 StudioControl::setStudioPluginPort(pluginMappedId,
1105                                                    (*portIt)->number,
1106                                                    (*portIt)->value);
1107             }
1108 
1109             // Program
1110             if (plugin.getProgram() != "") {
1111                 StudioControl::setStudioObjectProperty(
1112                         pluginMappedId,
1113                         MappedPluginSlot::Program,
1114                         strtoqstr(plugin.getProgram()));
1115             }
1116 
1117             // Set the post-program port values
1118             // ??? Why?
1119             for (PortInstanceIterator portIt = plugin.begin();
1120                  portIt != plugin.end();
1121                  ++portIt) {
1122                 if ((*portIt)->changedSinceProgramChange) {
1123                     StudioControl::setStudioPluginPort(pluginMappedId,
1124                                                        (*portIt)->number,
1125                                                        (*portIt)->value);
1126                 }
1127             }
1128         }
1129     }
1130 
1131     // Now commit all the remaining changes
1132     StudioControl::setStudioObjectProperties(ids, properties, values);
1133 
1134     QSettings settings;
1135     settings.beginGroup(SequencerOptionsConfigGroup);
1136 
1137     bool faderOuts = qStrToBool( settings.value("audiofaderouts", "false") ) ;
1138     bool submasterOuts = qStrToBool( settings.value("audiosubmasterouts", "false") ) ;
1139     unsigned int audioFileFormat = settings.value("audiorecordfileformat", 1).toUInt() ;
1140 
1141     settings.endGroup();
1142 
1143     // Send System Audio Ports Event
1144 
1145     MidiByte ports = 0;
1146 
1147     if (faderOuts)
1148         ports |= MappedEvent::FaderOuts;
1149 
1150     if (submasterOuts)
1151         ports |= MappedEvent::SubmasterOuts;
1152 
1153     MappedEvent mEports(
1154             MidiInstrumentBase, MappedEvent::SystemAudioPorts, ports);
1155     StudioControl::sendMappedEvent(mEports);
1156 
1157     // Send System Audio File Format Event
1158 
1159     MappedEvent mEff(MidiInstrumentBase,
1160                      MappedEvent::SystemAudioFileFormat,
1161                      audioFileFormat);
1162     StudioControl::sendMappedEvent(mEff);
1163 }
1164 
1165 SequenceManager *
getSequenceManager()1166 RosegardenDocument::getSequenceManager()
1167 {
1168     return m_seqManager;
1169 }
1170 
setSequenceManager(SequenceManager * sm)1171 void RosegardenDocument::setSequenceManager(SequenceManager *sm)
1172 {
1173     m_seqManager = sm;
1174 }
1175 
1176 
1177 // FILE FORMAT VERSION NUMBERS
1178 //
1179 // These should be updated when the file format changes.  The
1180 // intent is to warn the user that they are loading a file that
1181 // was saved with a newer version of Rosegarden, and data might
1182 // be lost as a result.  See RoseXmlHandler::startElement().
1183 //
1184 // Increment the major version number only for updates so
1185 // substantial that we shouldn't bother even trying to read a file
1186 // saved with a newer major version number than our own.  Older
1187 // versions of Rosegarden *will not* try to load files with
1188 // newer major version numbers.  Basically, this should be done
1189 // only as a last resort to lock out all older versions of
1190 // Rosegarden from reading in and completely mangling the contents
1191 // of a file.
1192 //
1193 // Increment the minor version number for updates that may break
1194 // compatibility such that we should warn when reading a file
1195 // that was saved with a newer minor version than our own.
1196 //
1197 // Increment the point version number for updates that shouldn't
1198 // break compatibility in either direction, just for informational
1199 // purposes.
1200 //
1201 // When updating major, reset minor to zero; when updating minor,
1202 // reset point to zero.
1203 //
1204 int RosegardenDocument::FILE_FORMAT_VERSION_MAJOR = 1;
1205 int RosegardenDocument::FILE_FORMAT_VERSION_MINOR = 6;
1206 int RosegardenDocument::FILE_FORMAT_VERSION_POINT = 6;
1207 
saveDocument(const QString & filename,QString & errMsg,bool autosave)1208 bool RosegardenDocument::saveDocument(const QString& filename,
1209                                     QString& errMsg,
1210                                     bool autosave)
1211 {
1212     QFileInfo fileInfo(filename);
1213 
1214     if (!fileInfo.exists()) { // safe to write directly
1215         return saveDocumentActual(filename, errMsg, autosave);
1216     }
1217 
1218     if (fileInfo.exists()  &&  !fileInfo.isWritable()) {
1219         errMsg = tr("'%1' is read-only.  Please save to a different file.").arg(filename);
1220         return false;
1221     }
1222 
1223     QTemporaryFile temp(filename + ".");
1224     //!!! was: KTempFile temp(filename + ".", "", 0644); // will be umask'd
1225 
1226     temp.setAutoRemove(false);
1227 
1228     temp.open(); // This creates the file and opens it atomically
1229 
1230     if ( temp.error() ) {
1231         errMsg = tr("Could not create temporary file in directory of '%1': %2").arg(filename).arg(temp.errorString());        //### removed .arg(strerror(status))
1232         return false;
1233     }
1234 
1235     QString tempFileName = temp.fileName(); // Must do this before temp.close()
1236 
1237     // The temporary file is now open: close it (without removing it)
1238     temp.close();
1239 
1240     if( temp.error() ){
1241         //status = temp.status();
1242         errMsg = tr("Failure in temporary file handling for file '%1': %2")
1243             .arg(tempFileName).arg(temp.errorString()); // .arg(strerror(status))
1244         return false;
1245     }
1246 
1247     bool success = saveDocumentActual(tempFileName, errMsg, autosave);
1248 
1249     if (!success) {
1250         // errMsg should be already set
1251         return false;
1252     }
1253 
1254     QDir dir(QFileInfo(tempFileName).dir());
1255     // According to  http://doc.trolltech.com/4.4/qdir.html#rename
1256     // some systems fail, if renaming over an existing file.
1257     // Therefore, delete first the existing file.
1258     if (dir.exists(filename)) dir.remove(filename);
1259     if (!dir.rename(tempFileName, filename)) {
1260         errMsg = tr("Failed to rename temporary output file '%1' to desired output file '%2'").arg(tempFileName).arg(filename);
1261         return false;
1262     }
1263 
1264     return true;
1265 }
1266 
1267 
saveDocumentActual(const QString & filename,QString & errMsg,bool autosave)1268 bool RosegardenDocument::saveDocumentActual(const QString& filename,
1269                                           QString& errMsg,
1270                                           bool autosave)
1271 {
1272     //Profiler profiler("RosegardenDocument::saveDocumentActual");
1273 
1274     RG_DEBUG << "RosegardenDocument::saveDocumentActual(" << filename << ")";
1275 
1276     QString outText;
1277     QTextStream outStream(&outText, QIODevice::WriteOnly);
1278 //    outStream.setEncoding(QTextStream::UnicodeUTF8); qt3
1279 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1280     // qt6 default codec is UTF-8
1281 #else
1282     outStream.setCodec("UTF-8");
1283 #endif
1284 
1285     // output XML header
1286     //
1287     outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1288     << "<!DOCTYPE rosegarden-data>\n"
1289     << "<rosegarden-data version=\"" << VERSION
1290     << "\" format-version-major=\"" << FILE_FORMAT_VERSION_MAJOR
1291     << "\" format-version-minor=\"" << FILE_FORMAT_VERSION_MINOR
1292     << "\" format-version-point=\"" << FILE_FORMAT_VERSION_POINT
1293     << "\">\n";
1294 
1295     // First make sure all MIDI devices know their current connections
1296     //
1297     m_studio.resyncDeviceConnections();
1298 
1299     // Send out Composition (this includes Tracks, Instruments, Tempo
1300     // and Time Signature changes and any other sub-objects)
1301     //
1302     outStream << strtoqstr(getComposition().toXmlString())
1303               << "\n\n";
1304 
1305     outStream << strtoqstr(getAudioFileManager().toXmlString())
1306               << "\n\n";
1307 
1308     outStream << strtoqstr(getConfiguration().toXmlString())
1309               << "\n\n";
1310 
1311     long totalEvents = 0;
1312     for (Composition::iterator segitr = m_composition.begin();
1313          segitr != m_composition.end(); ++segitr) {
1314         totalEvents += (long)(*segitr)->size();
1315     }
1316 
1317     for (Composition::triggersegmentcontaineriterator ci =
1318              m_composition.getTriggerSegments().begin();
1319          ci != m_composition.getTriggerSegments().end(); ++ci) {
1320         totalEvents += (long)(*ci)->getSegment()->size();
1321     }
1322 
1323     // output all elements
1324     //
1325     // Iterate on segments
1326     long eventCount = 0;
1327 
1328     // Put a break in the file
1329     //
1330     outStream << "\n\n";
1331 
1332     for (Composition::iterator segitr = m_composition.begin();
1333          segitr != m_composition.end(); ++segitr) {
1334 
1335         Segment *segment = *segitr;
1336 
1337         // Fix #1446 : Replace isLinked() with isTrulyLinked().
1338         // Maybe this fix will need to be removed some day if the
1339         // LinkTransposeParams come to be used.
1340         if (segment->isTrulyLinked()) {
1341             QString attsString = QString("linkerid=\"%1\" ");
1342             attsString += QString("linkertransposechangekey=\"%2\" ");
1343             attsString += QString("linkertransposesteps=\"%3\" ");
1344             attsString += QString("linkertransposesemitones=\"%4\" ");
1345             attsString += QString("linkertransposesegmentback=\"%5\" ");
1346             QString linkedSegAtts = QString(attsString)
1347               .arg(segment->getLinker()->getSegmentLinkerId())
1348               .arg(segment->getLinkTransposeParams().m_changeKey ? "true" :
1349                                                                    "false")
1350               .arg(segment->getLinkTransposeParams().m_steps)
1351               .arg(segment->getLinkTransposeParams().m_semitones)
1352               .arg(segment->getLinkTransposeParams().m_transposeSegmentBack
1353                                                          ? "true" : "false");
1354 
1355             saveSegment(outStream, segment, totalEvents,
1356                                             eventCount, linkedSegAtts);
1357         } else {
1358             saveSegment(outStream, segment, totalEvents, eventCount);
1359         }
1360 
1361     }
1362 
1363     // Put a break in the file
1364     //
1365     outStream << "\n\n";
1366 
1367     for (Composition::triggersegmentcontaineriterator ci =
1368                 m_composition.getTriggerSegments().begin();
1369             ci != m_composition.getTriggerSegments().end(); ++ci) {
1370 
1371         QString triggerAtts = QString
1372                               ("triggerid=\"%1\" triggerbasepitch=\"%2\" triggerbasevelocity=\"%3\" triggerretune=\"%4\" triggeradjusttimes=\"%5\" ")
1373                               .arg((*ci)->getId())
1374                               .arg((*ci)->getBasePitch())
1375                               .arg((*ci)->getBaseVelocity())
1376                               .arg((*ci)->getDefaultRetune())
1377                               .arg(strtoqstr((*ci)->getDefaultTimeAdjust()));
1378 
1379         Segment *segment = (*ci)->getSegment();
1380         saveSegment(outStream, segment, totalEvents, eventCount, triggerAtts);
1381     }
1382 
1383     // Put a break in the file
1384     //
1385     outStream << "\n\n";
1386 
1387     // Send out the studio - a self contained command
1388     //
1389     outStream << strtoqstr(m_studio.toXmlString()) << "\n\n";
1390 
1391     // Send out the appearance data
1392     outStream << "<appearance>\n";
1393     outStream << strtoqstr(getComposition().getSegmentColourMap().toXmlString("segmentmap"));
1394     outStream << strtoqstr(getComposition().getGeneralColourMap().toXmlString("generalmap"));
1395     outStream << "</appearance>\n\n\n";
1396 
1397     // close the top-level XML tag
1398     //
1399     outStream << "</rosegarden-data>\n";
1400 
1401     bool okay = GzipFile::writeToFile(filename, outText);
1402     if (!okay) {
1403         errMsg = tr("Error while writing on '%1'").arg(filename);
1404         return false;
1405     }
1406 
1407     RG_DEBUG << "RosegardenDocument::saveDocument() finished";
1408 
1409     if (!autosave) {
1410         emit documentModified(false);
1411         m_modified = false;
1412         CommandHistory::getInstance()->documentSaved();
1413     }
1414 
1415     setAutoSaved(true);
1416 
1417     return true;
1418 }
1419 
exportStudio(const QString & filename,QString & errMsg,std::vector<DeviceId> devices)1420 bool RosegardenDocument::exportStudio(const QString& filename,
1421                                       QString &errMsg,
1422                                       std::vector<DeviceId> devices)
1423 {
1424     Profiler profiler("RosegardenDocument::exportStudio");
1425     RG_DEBUG << "RosegardenDocument::exportStudio(" << filename << ")";
1426 
1427     QString outText;
1428     QTextStream outStream(&outText, QIODevice::WriteOnly);
1429 //    outStream.setEncoding(QTextStream::UnicodeUTF8); qt3
1430 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
1431     // qt6 default codec is UTF-8
1432 #else
1433     outStream.setCodec("UTF-8");
1434 #endif
1435 
1436     // output XML header
1437     //
1438     outStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1439               << "<!DOCTYPE rosegarden-data>\n"
1440               << "<rosegarden-data version=\"" << VERSION << "\">\n";
1441 
1442     // Send out the studio - a self contained command
1443     //
1444     outStream << strtoqstr(m_studio.toXmlString(devices)) << "\n\n";
1445 
1446     // close the top-level XML tag
1447     //
1448     outStream << "</rosegarden-data>\n";
1449 
1450     bool okay = GzipFile::writeToFile(filename, outText);
1451     if (!okay) {
1452         errMsg = tr("Could not open file '%1' for writing").arg(filename);
1453         return false;
1454     }
1455 
1456     RG_DEBUG << "RosegardenDocument::exportStudio() finished";
1457     return true;
1458 }
1459 
saveSegment(QTextStream & outStream,Segment * segment,long,long &,QString extraAttributes)1460 void RosegardenDocument::saveSegment(QTextStream& outStream, Segment *segment,
1461                                    long /*totalEvents*/, long &/*count*/,
1462                                    QString extraAttributes)
1463 {
1464     QString time;
1465 
1466     outStream << QString("<%1 track=\"%2\" start=\"%3\" ")
1467     .arg(segment->getXmlElementName())
1468     .arg(segment->getTrack())
1469     .arg(segment->getStartTime());
1470 
1471     if (!extraAttributes.isEmpty())
1472         outStream << extraAttributes << " ";
1473 
1474     outStream << "label=\"" <<
1475     strtoqstr(XmlExportable::encode(segment->getLabel()));
1476 
1477     if (segment->isRepeating()) {
1478         outStream << "\" repeat=\"true";
1479     }
1480 
1481     if (segment->getTranspose() != 0) {
1482         outStream << "\" transpose=\"" << segment->getTranspose();
1483     }
1484 
1485     if (segment->getDelay() != 0) {
1486         outStream << "\" delay=\"" << segment->getDelay();
1487     }
1488 
1489     if (segment->getRealTimeDelay() != RealTime::zeroTime) {
1490         outStream << "\" rtdelaysec=\"" << segment->getRealTimeDelay().sec
1491         << "\" rtdelaynsec=\"" << segment->getRealTimeDelay().nsec;
1492     }
1493 
1494     if (segment->getColourIndex() != 0) {
1495         outStream << "\" colourindex=\"" << segment->getColourIndex();
1496     }
1497 
1498     if (segment->getSnapGridSize() != -1) {
1499         outStream << "\" snapgridsize=\"" << segment->getSnapGridSize();
1500     }
1501 
1502     if (segment->getViewFeatures() != 0) {
1503         outStream << "\" viewfeatures=\"" << segment->getViewFeatures();
1504     }
1505 
1506     if (segment->getForNotation() != true) {
1507         outStream << "\" fornotation=\"" << "false";
1508     }
1509 
1510     const timeT *endMarker = segment->getRawEndMarkerTime();
1511     if (endMarker) {
1512         outStream << "\" endmarker=\"" << *endMarker;
1513     }
1514 
1515     if (segment->getType() == Segment::Audio) {
1516 
1517         outStream << "\" type=\"audio\" "
1518                   << "file=\""
1519                   << segment->getAudioFileId();
1520 
1521         if (segment->getStretchRatio() != 1.f &&
1522             segment->getStretchRatio() != 0.f) {
1523 
1524             outStream << "\" unstretched=\""
1525                       << segment->getUnstretchedFileId()
1526                       << "\" stretch=\""
1527                       << segment->getStretchRatio();
1528         }
1529 
1530         outStream << "\">\n";
1531 
1532         // convert out - should do this as XmlExportable really
1533         // once all this code is centralised
1534         //
1535 
1536         outStream << "    <begin index=\""
1537         << segment->getAudioStartTime()
1538         << "\"/>\n";
1539 
1540         outStream << "    <end index=\""
1541         << segment->getAudioEndTime()
1542         << "\"/>\n";
1543 
1544         if (segment->isAutoFading()) {
1545             outStream << "    <fadein time=\""
1546             << segment->getFadeInTime()
1547             << "\"/>\n";
1548 
1549             outStream << "    <fadeout time=\""
1550             << segment->getFadeOutTime()
1551             << "\"/>\n";
1552         }
1553 
1554     } else // Internal type
1555     {
1556         outStream << "\">\n";
1557 
1558         bool inChord = false;
1559         timeT chordStart = 0, chordDuration = 0;
1560         timeT expectedTime = segment->getStartTime();
1561 
1562         for (Segment::iterator i = segment->begin();
1563                 i != segment->end(); ++i) {
1564 
1565             timeT absTime = (*i)->getAbsoluteTime();
1566 
1567             Segment::iterator nextEl = i;
1568             ++nextEl;
1569 
1570             if (nextEl != segment->end() &&
1571                     (*nextEl)->getAbsoluteTime() == absTime &&
1572                     (*i)->getDuration() != 0 &&
1573                     !inChord) {
1574                 outStream << "<chord>\n";
1575                 inChord = true;
1576                 chordStart = absTime;
1577                 chordDuration = 0;
1578             }
1579 
1580             if (inChord && (*i)->getDuration() > 0)
1581                 if (chordDuration == 0 || (*i)->getDuration() < chordDuration)
1582                     chordDuration = (*i)->getDuration();
1583 
1584             outStream << '\t'
1585             << strtoqstr((*i)->toXmlString(expectedTime)) << "\n";
1586 
1587             if (nextEl != segment->end() &&
1588                     (*nextEl)->getAbsoluteTime() != absTime &&
1589                     inChord) {
1590                 outStream << "</chord>\n";
1591                 inChord = false;
1592                 expectedTime = chordStart + chordDuration;
1593             } else if (inChord) {
1594                 expectedTime = absTime;
1595             } else {
1596                 expectedTime = absTime + (*i)->getDuration();
1597             }
1598 
1599 //            if ((++count % 500 == 0) && progress) {
1600 //                progress->setValue(count * 100 / totalEvents);
1601 //            }
1602         }
1603 
1604         if (inChord) {
1605             outStream << "</chord>\n";
1606         }
1607 
1608         // <matrix>
1609 
1610         outStream << "  <matrix>\n";
1611 
1612         // Zoom factors
1613         outStream << "    <hzoom factor=\"" << segment->matrixHZoomFactor <<
1614                      "\" />\n";
1615         outStream << "    <vzoom factor=\"" << segment->matrixVZoomFactor <<
1616                      "\" />\n";
1617 
1618         // For each matrix ruler...
1619         for (const Segment::Ruler &ruler : *(segment->matrixRulers))
1620         {
1621             outStream << "    <ruler type=\"" << ruler.type << "\"";
1622 
1623             if (ruler.type == Controller::EventType)
1624                 outStream << " ccnumber=\"" << ruler.ccNumber << "\"";
1625 
1626             outStream << " />\n";
1627         }
1628 
1629         outStream << "  </matrix>\n";
1630 
1631         // <notation>
1632 
1633         outStream << "  <notation>\n";
1634 
1635         // For each notation ruler...
1636         for (const Segment::Ruler &ruler : *(segment->notationRulers))
1637         {
1638             outStream << "    <ruler type=\"" << ruler.type << "\"";
1639 
1640             if (ruler.type == Controller::EventType)
1641                 outStream << " ccnumber=\"" << ruler.ccNumber << "\"";
1642 
1643             outStream << " />\n";
1644         }
1645 
1646         outStream << "  </notation>\n";
1647 
1648     }
1649 
1650 
1651     outStream << QString("</%1>\n").arg(segment->getXmlElementName()); //-------------------------
1652 
1653 }
1654 
saveAs(const QString & newName,QString & errMsg)1655 bool RosegardenDocument::saveAs(const QString &newName, QString &errMsg)
1656 {
1657     QFileInfo newNameInfo(newName);
1658 
1659     // If we're saving under the same name, just do a normal save.
1660     if (newNameInfo.absoluteFilePath() == m_absFilePath)
1661         return saveDocument(newName, errMsg);
1662 
1663     QString oldTitle = m_title;
1664     QString oldAbsFilePath = m_absFilePath;
1665 
1666     m_title = newNameInfo.fileName();
1667     m_absFilePath = newNameInfo.absoluteFilePath();
1668 
1669     // Lock and lock the new name.  If the lock fails...
1670     QLockFile *newLock = createLock(m_absFilePath);
1671     if (!newLock) {
1672         // Put back the old title/name.
1673         m_title = oldTitle;
1674         m_absFilePath = oldAbsFilePath;
1675 
1676         // Fail.
1677         return false;
1678     }
1679 
1680     // Save.  If the save fails...
1681     if (!saveDocument(newName, errMsg)) {
1682         // Unlock the new name.
1683         delete newLock;
1684 
1685         // Put back the old title/name.
1686         m_title = oldTitle;
1687         m_absFilePath = oldAbsFilePath;
1688 
1689         // Fail.
1690         return false;
1691     }
1692 
1693     // Release the old lock
1694     release();
1695     m_lockFile = newLock;
1696 
1697     // Success.
1698     return true;
1699 }
1700 
isSoundEnabled() const1701 bool RosegardenDocument::isSoundEnabled() const
1702 {
1703     return m_soundEnabled;
1704 }
1705 
1706 bool
xmlParse(QString fileContents,QString & errMsg,bool permanent,bool & cancelled)1707 RosegardenDocument::xmlParse(QString fileContents, QString &errMsg,
1708                            bool permanent,
1709                            bool &cancelled)
1710 {
1711     //Profiler profiler("RosegardenDocument::xmlParse");
1712 
1713     cancelled = false;
1714 
1715     unsigned int elementCount = 0;
1716     for (int i = 0; i < fileContents.length() - 1; ++i) {
1717         if (fileContents[i] == '<' && fileContents[i+1] != '/') {
1718             ++elementCount;
1719         }
1720     }
1721 
1722     if (permanent && m_soundEnabled) RosegardenSequencer::getInstance()->removeAllDevices();
1723 
1724     RoseXmlHandler handler(this, elementCount, m_progressDialog, permanent);
1725 
1726     XMLReader reader;
1727     reader.setHandler(&handler);
1728 
1729     bool ok = reader.parse(fileContents);
1730 
1731     if (m_progressDialog  &&  m_progressDialog->wasCanceled()) {
1732         QMessageBox::information(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("File load cancelled"));
1733         cancelled = true;
1734         return true;
1735     }
1736 
1737     if (!ok) {
1738 
1739 #if 0
1740         if (m_progressDialog  &&  m_progressDialog->wasCanceled()) {
1741             RG_DEBUG << "File load cancelled";
1742             StartupLogo::hideIfStillThere();
1743             QMessageBox::information(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("File load cancelled"));
1744             cancelled = true;
1745             return true;
1746         } else {
1747 #endif
1748             errMsg = handler.errorString();
1749 #if 0
1750         }
1751 #endif
1752 
1753     } else {
1754 
1755         if (getSequenceManager() &&
1756             !(getSequenceManager()->getSoundDriverStatus() & AUDIO_OK)) {
1757 
1758             StartupLogo::hideIfStillThere();
1759 
1760             if (handler.hasActiveAudio() ||
1761                 (m_pluginManager && !handler.pluginsNotFound().empty())) {
1762 
1763 #ifdef HAVE_LIBJACK
1764                 QMessageBox::information
1765                     (dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but Rosegarden is currently running without audio because the JACK audio server was not available on startup.</p><p>Please exit Rosegarden, start the JACK audio server and re-start Rosegarden if you wish to load this complete composition.</p><p><b>WARNING:</b> If you re-save this composition, all audio and plugin data and settings in it will be lost.</p>"));
1766 #else
1767                 QMessageBox::information
1768                     (dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("<h3>Audio and plugins not available</h3><p>This composition uses audio files or plugins, but you are running a version of Rosegarden that was compiled without audio support.</p><p><b>WARNING:</b> If you re-save this composition from this version of Rosegarden, all audio and plugin data and settings in it will be lost.</p>"));
1769 #endif
1770             }
1771 
1772         } else {
1773 
1774             bool shownWarning = false;
1775 
1776             int sr = 0;
1777             if (getSequenceManager()) {
1778                 sr = getSequenceManager()->getSampleRate();
1779             }
1780 
1781             int er = m_audioFileManager.getExpectedSampleRate();
1782 
1783             std::set<int> rates = m_audioFileManager.getActualSampleRates();
1784             bool other = false;
1785             bool mixed = (rates.size() > 1);
1786             for (std::set<int>::iterator i = rates.begin();
1787                  i != rates.end(); ++i) {
1788                 if (*i != sr) {
1789                     other = true;
1790                     break;
1791                 }
1792             }
1793 
1794             if (sr != 0 &&
1795                 handler.hasActiveAudio() &&
1796                 ((er != 0 && er != sr) ||
1797                  (other && !mixed))) {
1798 
1799                 if (er == 0) er = *rates.begin();
1800 
1801                 StartupLogo::hideIfStillThere();
1802 
1803                 QMessageBox::information(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("<h3>Incorrect audio sample rate</h3><p>This composition contains audio files that were recorded or imported with the audio server running at a different sample rate (%1 Hz) from the current JACK server sample rate (%2 Hz).</p><p>Rosegarden will play this composition at the correct speed, but any audio files in it will probably sound awful.</p><p>Please consider re-starting the JACK server at the correct rate (%3 Hz) and re-loading this composition before you do any more work with it.</p>").arg(er).arg(sr).arg(er));
1804 
1805                 shownWarning = true;
1806 
1807             } else if (sr != 0 && mixed) {
1808 
1809                 StartupLogo::hideIfStillThere();
1810 
1811                 QMessageBox::information(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), tr("<h3>Inconsistent audio sample rates</h3><p>This composition contains audio files at more than one sample rate.</p><p>Rosegarden will play them at the correct speed, but any audio files that were recorded or imported at rates different from the current JACK server sample rate (%1 Hz) will probably sound awful.</p><p>Please see the audio file manager dialog for more details, and consider resampling any files that are at the wrong rate.</p>").arg(sr),
1812                                          tr("Inconsistent sample rates"),
1813                                          "file-load-inconsistent-samplerates");
1814 
1815                 shownWarning = true;
1816             }
1817 
1818             if (m_pluginManager && !handler.pluginsNotFound().empty()) {
1819 
1820                 // We only warn if a plugin manager is present, so as
1821                 // to avoid warnings when importing a studio from
1822                 // another file (which is the normal case in which we
1823                 // have no plugin manager).
1824 
1825                 QString msg(tr("<h3>Plugins not found</h3><p>The following audio plugins could not be loaded:</p><ul>"));
1826 
1827                 for (std::set<QString>::iterator i = handler.pluginsNotFound().begin();
1828                      i != handler.pluginsNotFound().end(); ++i) {
1829                     QString ident = *i;
1830                     QString type, soName, label;
1831                     PluginIdentifier::parseIdentifier(ident, type, soName, label);
1832                     QString pluginFileName = QFileInfo(soName).fileName();
1833                     msg += tr("<li>%1 (from %2)</li>").arg(label).arg(pluginFileName);
1834                 }
1835                 msg += "</ul>";
1836 
1837                 StartupLogo::hideIfStillThere();
1838                 QMessageBox::information(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), msg);
1839                 shownWarning = true;
1840 
1841             }
1842 
1843             if (handler.isDeprecated() && !shownWarning) {
1844 
1845                 QString msg(tr("This file contains one or more old element types that are now deprecated.\nSupport for these elements may disappear in future versions of Rosegarden.\nWe recommend you re-save this file from this version of Rosegarden to ensure that it can still be re-loaded in future versions."));
1846                 slotDocumentModified(); // so file can be re-saved immediately
1847 
1848                 StartupLogo::hideIfStillThere();
1849                 QMessageBox::information(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), msg);
1850             }
1851 
1852         }
1853 
1854         getComposition().resetLinkedSegmentRefreshStatuses();
1855     }
1856 
1857     return ok;
1858 }
1859 
1860 void
insertRecordedMidi(const MappedEventList & mC)1861 RosegardenDocument::insertRecordedMidi(const MappedEventList &mC)
1862 {
1863     Profiler profiler("RosegardenDocument::insertRecordedMidi()");
1864 
1865     //RG_DEBUG << "RosegardenDocument::insertRecordedMidi: " << mC.size() << " events";
1866 
1867     // Just create a new record Segment if we don't have one already.
1868     // Make sure we don't recreate the record segment if it's already
1869     // freed.
1870     //
1871 
1872     //Track *midiRecordTrack = 0;
1873 
1874     const Composition::recordtrackcontainer &recordTracks =
1875         getComposition().getRecordTracks();
1876 
1877     bool haveMIDIRecordTrack = false;
1878 
1879     // For each recording track
1880     for (Composition::recordtrackcontainer::const_iterator i =
1881             recordTracks.begin(); i != recordTracks.end(); ++i) {
1882         TrackId tid = (*i);
1883         Track *track = getComposition().getTrackById(tid);
1884         if (track) {
1885             Instrument *instrument =
1886                 m_studio.getInstrumentById(track->getInstrument());
1887             if (instrument->getType() == Instrument::Midi ||
1888                     instrument->getType() == Instrument::SoftSynth) {
1889                 haveMIDIRecordTrack = true;
1890                 if (!m_recordMIDISegments[track->getInstrument()]) {
1891                     addRecordMIDISegment(track->getId());
1892                 }
1893                 // ??? This is a tad perplexing at first glance.  What if
1894                 //     two tracks are armed for record?  Don't we need to
1895                 //     create two segments?  Won't this end the for loop
1896                 //     after creating only one?  Maybe it works because this
1897                 //     routine is called over and over very quickly before
1898                 //     any events come in.  Seems unnecessary to break here.
1899                 break;
1900             }
1901         }
1902     }
1903 
1904     if (!haveMIDIRecordTrack)
1905         return ;
1906 
1907     // If there are no events, bail.
1908     // ??? Seems like something we should do at the very top.  But beware the
1909     //     odd "break" above which may depend on this being here.  And perhaps
1910     //     we do want to make the record segments even if there is no data
1911     //     yet.
1912     if (mC.empty())
1913         return;
1914 
1915     timeT updateFrom = m_composition.getDuration();
1916     bool haveNotes = false;
1917 
1918     MappedEventList::const_iterator i;
1919 
1920     // For each incoming event
1921     for (i = mC.begin(); i != mC.end(); ++i) {
1922 
1923         // Send events from the external controller port to the views
1924         // ??? Is this how we handle external controller events during record?
1925         //     Or is this unnecessary?  Need to remove and see if it affects
1926         //     external controller functionality during record.
1927         if ((*i)->getRecordedDevice() == Device::EXTERNAL_CONTROLLER) {
1928 
1929             ExternalController::self().processEvent(*i);
1930 
1931             // No further processing is required for this event.
1932             continue;
1933         }
1934 
1935         const timeT absTime = m_composition.getElapsedTimeForRealTime((*i)->getEventTime());
1936 
1937         /* This is incorrect, unless the tempo at absTime happens to
1938            be the same as the tempo at zero and there are no tempo
1939            changes within the given duration after either zero or
1940            absTime
1941 
1942            timeT duration = m_composition.getElapsedTimeForRealTime((*i)->getDuration());
1943         */
1944         const timeT endTime = m_composition.getElapsedTimeForRealTime(
1945                 (*i)->getEventTime() + (*i)->getDuration());
1946         timeT duration = endTime - absTime;
1947 
1948         Event *rEvent = nullptr;
1949         bool isNoteOn = false;
1950         const int pitch = (*i)->getPitch();
1951         int channel = (*i)->getRecordedChannel();
1952         const int device = (*i)->getRecordedDevice();
1953 
1954         switch ((*i)->getType()) {
1955 
1956         case MappedEvent::MidiNote:
1957 
1958             // If this is a note on event.
1959             // (In AlsaDriver::getMappedEventList() we set the duration to
1960             // -1 seconds to indicate a note-on event.)
1961             if ((*i)->getDuration() < RealTime::zeroTime) {
1962 
1963                 //printf("Note On  ch %2d | ptch %3d | vel %3d\n", channel, pitch, (*i)->getVelocity());
1964                 //RG_DEBUG << "RD::iRM Note On cpv:" << channel << "/" << pitch << "/" << (*i)->getVelocity();
1965 
1966                 // give it a default duration for insertion into the segment
1967                 duration = Note(Note::Crotchet).getDuration();
1968 
1969                 // make a mental note to stick it in the note-on map for when
1970                 // we see the corresponding note-off
1971                 isNoteOn = true;
1972 
1973                 rEvent = new Event(Note::EventType,
1974                                    absTime,
1975                                    duration);
1976 
1977                 rEvent->set<Int>(PITCH, pitch);
1978                 rEvent->set<Int>(VELOCITY, (*i)->getVelocity());
1979 
1980             } else {  // it's a note-off
1981 
1982                 //printf("Note Off event on Channel %2d: %5d\n", channel, pitch);
1983                 //RG_DEBUG << "RD::iRM Note Off cp:" << channel << "/" << pitch;
1984 
1985                 PitchMap *pitchMap = &m_noteOnEvents[device][channel];
1986                 PitchMap::iterator mi = pitchMap->find(pitch);
1987 
1988                 // If we have a matching note-on for this note-off
1989                 if (mi != pitchMap->end()) {
1990 
1991                     // Get the vector of note-ons that match with this
1992                     // note-off.
1993                     NoteOnRecSet rec_vec = mi->second;
1994 
1995                     // Adjust updateFrom for quantization.
1996 
1997                     Event *oldEv = *rec_vec[0].m_segmentIterator;
1998                     timeT eventAbsTime = oldEv->getAbsoluteTime();
1999 
2000                     // Make sure we quantize starting at the beginning of this
2001                     // note at least.
2002                     if (updateFrom > eventAbsTime)
2003                         updateFrom = eventAbsTime;
2004 
2005                     // Modify the previously held note-on Event(s), instead
2006                     // of assigning to rEvent.
2007                     NoteOnRecSet *replaced = adjustEndTimes(rec_vec, endTime);
2008                     delete replaced;
2009 
2010                     // Remove the original note-on(s) from the pitch map.
2011                     pitchMap->erase(mi);
2012 
2013                     haveNotes = true;
2014 
2015                     // at this point we could quantize the bar if we were
2016                     // tracking in a notation view
2017 
2018                 } else {
2019                     RG_DEBUG << " WARNING: NOTE OFF received without corresponding NOTE ON  channel:" << channel << "  pitch:" << pitch;
2020                 }
2021             }
2022 
2023             break;
2024 
2025         case MappedEvent::MidiPitchBend:
2026             rEvent = PitchBend::makeEvent(
2027                     absTime,
2028                     (*i)->getData1(),  // msb
2029                     (*i)->getData2());  // lsb
2030 
2031             break;
2032 
2033         case MappedEvent::MidiController:
2034             rEvent = Controller::makeEvent(
2035                     absTime,
2036                     (*i)->getData1(),  // number
2037                     (*i)->getData2());  // value
2038             break;
2039 
2040         case MappedEvent::MidiProgramChange:
2041             rEvent = ProgramChange::makeEvent(
2042                     absTime,
2043                     (*i)->getData1());  // program
2044             break;
2045 
2046         case MappedEvent::MidiKeyPressure:
2047             rEvent = KeyPressure::makeEvent(
2048                     absTime,
2049                     (*i)->getData1(),  // pitch
2050                     (*i)->getData2());  // pressure
2051             break;
2052 
2053         case MappedEvent::MidiChannelPressure:
2054             rEvent = ChannelPressure::makeEvent(
2055                     absTime,
2056                     (*i)->getData1());  // pressure
2057             break;
2058 
2059         case MappedEvent::MidiSystemMessage:
2060             channel = -1;
2061             if ((*i)->getData1() == MIDI_SYSTEM_EXCLUSIVE) {
2062                 rEvent = SystemExclusive::makeEvent(
2063                         absTime,
2064                         DataBlockRepository::getDataBlockForEvent(*i));
2065             }
2066 
2067             // Ignore other SystemMessage events for the moment
2068             //
2069 
2070             break;
2071 
2072         case MappedEvent::MidiNoteOneShot:
2073             RG_DEBUG << "RosegardenDocument::insertRecordedMidi() - "
2074                      << "GOT UNEXPECTED MappedEvent::MidiNoteOneShot";
2075             break;
2076 
2077             // Audio control signals - ignore these
2078         case MappedEvent::Audio:
2079         case MappedEvent::AudioCancel:
2080         case MappedEvent::AudioLevel:
2081         case MappedEvent::AudioStopped:
2082         case MappedEvent::AudioGeneratePreview:
2083         case MappedEvent::SystemUpdateInstruments:
2084             break;
2085 
2086 
2087         // list everything in the enum to avoid the annoying compiler
2088         // warning
2089         case MappedEvent::InvalidMappedEvent:
2090         case MappedEvent::Marker:
2091         case MappedEvent::SystemJackTransport:
2092         case MappedEvent::SystemMMCTransport:
2093         case MappedEvent::SystemMIDIClock:
2094         case MappedEvent::SystemMetronomeDevice:
2095         case MappedEvent::SystemAudioPortCounts:
2096         case MappedEvent::SystemAudioPorts:
2097         case MappedEvent::SystemFailure:
2098         case MappedEvent::Text:
2099         case MappedEvent::TimeSignature:
2100         case MappedEvent::Tempo:
2101         case MappedEvent::Panic:
2102         case MappedEvent::SystemMTCTransport:
2103         case MappedEvent::SystemMIDISyncAuto:
2104         case MappedEvent::SystemAudioFileFormat:
2105         case MappedEvent::KeySignature:
2106         default:
2107             RG_DEBUG << "RosegardenDocument::insertRecordedMidi() - "
2108                      << "GOT UNSUPPORTED MAPPED EVENT";
2109             break;
2110         }
2111 
2112         // sanity check
2113         //
2114         if (rEvent == nullptr)
2115             continue;
2116 
2117         // Set the recorded input port
2118         //
2119         rEvent->set<Int>(RECORDED_PORT, device);
2120 
2121         // Set the recorded channel, if this isn't a sysex event
2122         if (channel >= 0)
2123             rEvent->set<Int>(RECORDED_CHANNEL, channel);
2124 
2125         // Set the proper start index (if we haven't before)
2126         //
2127         for (RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin();
2128              it != m_recordMIDISegments.end(); ++it) {
2129             Segment *recordMIDISegment = it->second;
2130             if (recordMIDISegment->size() == 0) {
2131                 recordMIDISegment->setStartTime (m_composition.getBarStartForTime(absTime));
2132                 recordMIDISegment->fillWithRests(absTime);
2133             }
2134         }
2135 
2136         // Now insert the new event
2137         //
2138         insertRecordedEvent(rEvent, device, channel, isNoteOn);
2139         delete rEvent;
2140     }
2141 
2142     // If we have note events, quantize the notation for the recording
2143     // segments.
2144     if (haveNotes) {
2145 
2146         QSettings settings;
2147         settings.beginGroup( GeneralOptionsConfigGroup );
2148 
2149         // This is usually 0.  I don't think there is even a way to change
2150         // this through the UI.
2151         int tracking = settings.value("recordtracking", 0).toUInt() ;
2152         settings.endGroup();
2153         if (tracking == 1) { // notation
2154             for (RecordingSegmentMap::const_iterator it = m_recordMIDISegments.begin();
2155                  it != m_recordMIDISegments.end(); ++it) {
2156 
2157                 Segment *recordMIDISegment = it->second;
2158 
2159                 EventQuantizeCommand *command = new EventQuantizeCommand
2160                     (*recordMIDISegment,
2161                      updateFrom,
2162                      recordMIDISegment->getEndTime(),
2163                      NotationOptionsConfigGroup,
2164                      EventQuantizeCommand::QUANTIZE_NOTATION_ONLY);
2165                 // don't add to history
2166                 command->execute();
2167             }
2168         }
2169 
2170         // this signal is currently unused - leaving just in case
2171         // recording segments are updated through the SegmentObserver::eventAdded() interface
2172         //         emit recordMIDISegmentUpdated(m_recordMIDISegment, updateFrom);
2173     }
2174 }
2175 
2176 void
updateRecordingMIDISegment()2177 RosegardenDocument::updateRecordingMIDISegment()
2178 {
2179     Profiler profiler("RosegardenDocument::updateRecordingMIDISegment()");
2180 
2181 //    RG_DEBUG << "RosegardenDocument::updateRecordingMIDISegment";
2182 
2183     if (m_recordMIDISegments.size() == 0) {
2184         // make this call once to create one
2185         insertRecordedMidi(MappedEventList());
2186         if (m_recordMIDISegments.size() == 0)
2187             return ; // not recording any MIDI
2188     }
2189 
2190 //    RG_DEBUG << "RosegardenDocument::updateRecordingMIDISegment: have record MIDI segment";
2191 
2192     NoteOnMap tweakedNoteOnEvents;
2193     for (NoteOnMap::iterator mi = m_noteOnEvents.begin();
2194          mi != m_noteOnEvents.end(); ++mi)
2195         for (ChanMap::iterator cm = mi->second.begin();
2196              cm != mi->second.end(); ++cm)
2197             for (PitchMap::iterator pm = cm->second.begin();
2198                  pm != cm->second.end(); ++pm) {
2199 
2200                 // anything in the note-on map should be tweaked so as to end
2201                 // at the recording pointer
2202                 NoteOnRecSet rec_vec = pm->second;
2203                 if (rec_vec.size() > 0) {
2204                     tweakedNoteOnEvents[mi->first][cm->first][pm->first] =
2205                         *adjustEndTimes(rec_vec, m_composition.getPosition());
2206                 }
2207             }
2208     m_noteOnEvents = tweakedNoteOnEvents;
2209 }
2210 
2211 void
transposeRecordedSegment(Segment * s)2212 RosegardenDocument::transposeRecordedSegment(Segment *s)
2213 {
2214         // get a selection of all the events in the segment, since we apparently
2215         // can't just iterate through a segment's events without one.  (?)
2216         EventSelection *selectedWholeSegment = new EventSelection(
2217             *s,
2218             s->getStartTime(),
2219             s->getEndMarkerTime());
2220 
2221          // Say we've got a recorded segment destined for a Bb trumpet track.
2222          // It will have transpose of -2, and we want to move the notation +2 to
2223          // compensate, so the user hears the same thing she just recorded
2224          //
2225          // (All debate over whether this is the right way to go with this whole
2226          // issue is now officially settled, and no longer tentative.)
2227          Composition *c = s->getComposition();
2228          if (c) {
2229              Track *t = c->getTrackById(s->getTrack());
2230              if (t) {
2231                  // pull transpose from the destination track
2232                  int semitones = t->getTranspose();
2233 
2234                  for (EventSelection::eventcontainer::iterator i =
2235                       selectedWholeSegment->getSegmentEvents().begin();
2236                      i != selectedWholeSegment->getSegmentEvents().end(); ++i) {
2237 
2238                      if ((*i)->isa(Note::EventType)) {
2239                          if (semitones != 0) {
2240                             if (!(*i)->has(PITCH)) {
2241                                 std::cerr << "WARNING! RosegardenDocument::transposeRecordedSegment: Note has no pitch!  Andy says \"Oh noes!!!  ZOMFG!!!\"" << std::endl;
2242                             } else {
2243                                 int pitch = (*i)->get<Int>(PITCH) - semitones;
2244                                 std::cerr << "pitch = " << pitch
2245                                           << " after transpose = "
2246                                           << semitones << " (for track "
2247                                           << s->getTrack() << ")" << std::endl;
2248                                 (*i)->set<Int>(PITCH, pitch);
2249                             }
2250                         }
2251                     }
2252                  }
2253              }
2254         }
2255 }
2256 
2257 RosegardenDocument::NoteOnRecSet *
adjustEndTimes(NoteOnRecSet & rec_vec,timeT endTime)2258 RosegardenDocument::adjustEndTimes(NoteOnRecSet& rec_vec, timeT endTime)
2259 {
2260     // Not too keen on profilers, but I'll give it a shot for fun...
2261     Profiler profiler("RosegardenDocument::adjustEndTimes()");
2262 
2263     // Create a vector to hold the new note-on events for return.
2264     NoteOnRecSet *new_vector = new NoteOnRecSet();
2265 
2266     // For each note-on event
2267     for (NoteOnRecSet::const_iterator i = rec_vec.begin(); i != rec_vec.end(); ++i) {
2268         // ??? All this removing and re-inserting of Events from the Segment
2269         //     seems like a serious waste.  Can't we just modify the Event
2270         //     in place?  Otherwise we are doing all of this:
2271         //        1. Segment::erase() notifications.
2272         //        2. Segment::insert() notifications.
2273         //        3. Event delete and new.
2274 
2275         Event *oldEvent = *(i->m_segmentIterator);
2276 
2277         timeT newDuration = endTime - oldEvent->getAbsoluteTime();
2278 
2279         // Don't allow zero duration events.
2280         if (newDuration == 0)
2281             newDuration = 1;
2282 
2283         // Make a new copy of the event in the segment and modify the
2284         // duration as needed.
2285         // ??? Can't we modify the Event in place in the Segment?
2286         //     No.  All setters are protected.  Events are read-only.
2287         Event *newEvent = new Event(
2288                 *oldEvent,  // reference Event object
2289                 oldEvent->getAbsoluteTime(),  // absoluteTime (preserved)
2290                 newDuration  // duration (adjusted)
2291                 );
2292 
2293         // Remove the old event from the segment
2294         Segment *recordMIDISegment = i->m_segment;
2295         recordMIDISegment->erase(i->m_segmentIterator);
2296 
2297         // Insert the new event into the segment
2298         NoteOnRec noteRec;
2299         noteRec.m_segment = recordMIDISegment;
2300         // ??? Performance: This causes a slew of change notifications to be
2301         //        sent out by Segment::insert().  That may be causing the
2302         //        performance issues when recording.  Try removing the
2303         //        notifications from insert() and see if things improve.
2304         //        Also take a look at Segment::erase() which is called above.
2305         noteRec.m_segmentIterator = recordMIDISegment->insert(newEvent);
2306 
2307         // don't need to transpose this event; it was copied from an
2308         // event that had been transposed already (in storeNoteOnEvent)
2309 
2310         // Collect the new NoteOnRec objects for return.
2311         new_vector->push_back(noteRec);
2312     }
2313 
2314     return new_vector;
2315 }
2316 
2317 void
storeNoteOnEvent(Segment * s,Segment::iterator it,int device,int channel)2318 RosegardenDocument::storeNoteOnEvent(Segment *s, Segment::iterator it, int device, int channel)
2319 {
2320     NoteOnRec record;
2321     record.m_segment = s;
2322     record.m_segmentIterator = it;
2323 
2324     int pitch = (*it)->get<Int>(PITCH);
2325 
2326     m_noteOnEvents[device][channel][pitch].push_back(record);
2327 }
2328 
2329 void
insertRecordedEvent(Event * ev,int device,int channel,bool isNoteOn)2330 RosegardenDocument::insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn)
2331 {
2332     Profiler profiler("RosegardenDocument::insertRecordedEvent()");
2333 
2334     Segment::iterator it;
2335     for ( RecordingSegmentMap::const_iterator i = m_recordMIDISegments.begin();
2336             i != m_recordMIDISegments.end(); ++i) {
2337         Segment *recordMIDISegment = i->second;
2338         TrackId tid = recordMIDISegment->getTrack();
2339         Track *track = getComposition().getTrackById(tid);
2340         if (track) {
2341             //Instrument *instrument =
2342             //    m_studio.getInstrumentById(track->getInstrument());
2343             int chan_filter = track->getMidiInputChannel();
2344             int dev_filter = track->getMidiInputDevice();
2345 
2346             if (((chan_filter < 0) || (chan_filter == channel)) &&
2347                 ((dev_filter == int(Device::ALL_DEVICES)) || (dev_filter == device))) {
2348 
2349                 // Insert the event into the segment.
2350                 it = recordMIDISegment->insert(new Event(*ev));
2351 
2352                 if (isNoteOn) {
2353                     // Add the event to m_noteOnEvents.
2354                     // To match up with a note-off later.
2355                     storeNoteOnEvent(recordMIDISegment, it, device, channel);
2356                 }
2357 
2358                 //RG_DEBUG << "RosegardenDocument::insertRecordedEvent() - matches filter";
2359 
2360             } else {
2361                 //RG_DEBUG << "RosegardenDocument::insertRecordedEvent() - unmatched event discarded";
2362             }
2363         }
2364     }
2365 }
2366 
2367 void
stopPlaying()2368 RosegardenDocument::stopPlaying()
2369 {
2370     emit pointerPositionChanged(m_composition.getPosition());
2371 }
2372 
2373 void
stopRecordingMidi()2374 RosegardenDocument::stopRecordingMidi()
2375 {
2376     RG_DEBUG << "RosegardenDocument::stopRecordingMidi";
2377 
2378     Composition &c = getComposition();
2379 
2380     timeT endTime = c.getBarEnd(0);
2381 
2382     bool haveMeaning = false;
2383     timeT earliestMeaning = 0;
2384 
2385     std::vector<RecordingSegmentMap::iterator> toErase;
2386 
2387     for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin();
2388          i != m_recordMIDISegments.end();
2389          ++i) {
2390 
2391         Segment *s = i->second;
2392 
2393         bool meaningless = true;
2394 
2395         for (Segment::iterator si = s->begin(); si != s->end(); ++si) {
2396 
2397             if ((*si)->isa(Clef::EventType)) continue;
2398 
2399             // no rests in the segment yet, so anything else is meaningful
2400             meaningless = false;
2401 
2402             if (!haveMeaning || (*si)->getAbsoluteTime() < earliestMeaning) {
2403                 earliestMeaning = (*si)->getAbsoluteTime();
2404             }
2405 
2406             haveMeaning = true;
2407             break;
2408         }
2409 
2410         if (meaningless) {
2411             if (!c.deleteSegment(s)) delete s;
2412             toErase.push_back(i);
2413         } else {
2414             if (endTime < s->getEndTime()) {
2415                 endTime = s->getEndTime();
2416             }
2417         }
2418     }
2419 
2420     for (size_t i = 0; i < toErase.size(); ++i) {
2421         m_recordMIDISegments.erase(toErase[i]);
2422     }
2423 
2424     if (!haveMeaning) return;
2425 
2426     RG_DEBUG << "RosegardenDocument::stopRecordingMidi: have something";
2427 
2428     // adjust the clef timings so as not to leave a clef stranded at
2429     // the start of an otherwise empty count-in
2430 
2431     timeT meaningfulBarStart = c.getBarStartForTime(earliestMeaning);
2432 
2433     for (RecordingSegmentMap::iterator i = m_recordMIDISegments.begin();
2434          i != m_recordMIDISegments.end();
2435          ++i) {
2436 
2437         Segment *s = i->second;
2438         Segment::iterator j = s->begin();
2439 
2440         if (j == s->end() || !(*j)->isa(Clef::EventType)) continue;
2441 
2442         if ((*j)->getAbsoluteTime() < meaningfulBarStart) {
2443             Event *e = new Event(**j, meaningfulBarStart);
2444             s->erase(j);
2445             s->insert(e);
2446         }
2447     }
2448 
2449     for (NoteOnMap::iterator mi = m_noteOnEvents.begin();
2450          mi != m_noteOnEvents.end(); ++mi) {
2451 
2452         for (ChanMap::iterator cm = mi->second.begin();
2453              cm != mi->second.end(); ++cm) {
2454 
2455             for (PitchMap::iterator pm = cm->second.begin();
2456                  pm != cm->second.end(); ++pm) {
2457 
2458                 // anything remaining in the note-on map should be
2459                 // made to end at the end of the segment
2460 
2461                 NoteOnRecSet rec_vec = pm->second;
2462 
2463                 if (rec_vec.size() > 0) {
2464                     // Adjust the end times of the note-on events for
2465                     // this device/channel/pitch.
2466                     NoteOnRecSet *replaced =
2467                             adjustEndTimes(rec_vec, endTime);
2468                     delete replaced;
2469                 }
2470             }
2471         }
2472     }
2473     m_noteOnEvents.clear();
2474 
2475     while (!m_recordMIDISegments.empty()) {
2476 
2477         Segment *s = m_recordMIDISegments.begin()->second;
2478         m_recordMIDISegments.erase(m_recordMIDISegments.begin());
2479 
2480         // the record segment will have already been added to the
2481         // composition if there was anything in it; otherwise we don't
2482         // need to do so
2483 
2484         if (s->getComposition() == nullptr) {
2485             delete s;
2486             continue;
2487         }
2488 
2489         // Quantize for notation only -- doesn't affect performance timings.
2490         MacroCommand *command = new MacroCommand(tr("Insert Recorded MIDI"));
2491 
2492         command->addCommand(new EventQuantizeCommand
2493                             (*s,
2494                              s->getStartTime(),
2495                              s->getEndTime(),
2496                              NotationOptionsConfigGroup,
2497                              EventQuantizeCommand::QUANTIZE_NOTATION_ONLY));
2498 
2499         command->addCommand(new NormalizeRestsCommand
2500                             (*s,
2501                              c.getBarStartForTime(s->getStartTime()),
2502                              c.getBarEndForTime(s->getEndTime())));
2503 
2504         command->addCommand(new SegmentRecordCommand(s));
2505 
2506         // Transpose the entire recorded segment as a unit, rather than
2507         // transposing its individual events one time.  This allows the same
2508         // source recording to be transposed to multiple destination tracks in
2509         // different transpositions as part of one simultaneous operation from
2510         // the user's perspective.  This wasn't done as a command, because it
2511         // will be undone if the segment itself is undone, and I wanted to avoid
2512         // writing a new command at a time when we've got something completely
2513         // different over in the new Qt4 branch; to facilitate porting.
2514         transposeRecordedSegment(s);
2515 
2516         CommandHistory::getInstance()->addCommand(command);
2517     }
2518 
2519     emit stoppedMIDIRecording();
2520 
2521     slotUpdateAllViews(nullptr);
2522 
2523     emit pointerPositionChanged(m_composition.getPosition());
2524 }
2525 
2526 void
prepareAudio()2527 RosegardenDocument::prepareAudio()
2528 {
2529     if (!m_soundEnabled) return;
2530 
2531     // Clear down the sequencer AudioFilePlayer object
2532     //
2533     RosegardenSequencer::getInstance()->clearAllAudioFiles();
2534 
2535     for (AudioFileManagerIterator it = m_audioFileManager.begin();
2536          it != m_audioFileManager.end(); it++) {
2537 
2538         bool result = RosegardenSequencer::getInstance()->
2539             addAudioFile((*it)->getFilename(),
2540                          (*it)->getId());
2541         if (!result) {
2542             RG_DEBUG << "prepareAudio() - failed to add file \""
2543                      << (*it)->getFilename() << "\"";
2544         }
2545     }
2546 }
2547 
2548 void
slotSetPointerPosition(timeT t)2549 RosegardenDocument::slotSetPointerPosition(timeT t)
2550 {
2551     m_composition.setPosition(t);
2552     emit pointerPositionChanged(t);
2553 }
2554 
2555 void
setLoop(timeT t0,timeT t1)2556 RosegardenDocument::setLoop(timeT t0, timeT t1)
2557 {
2558     m_composition.setLoopStart(t0);
2559     m_composition.setLoopEnd(t1);
2560     emit loopChanged(t0, t1);
2561 }
2562 
2563 void
addRecordMIDISegment(TrackId tid)2564 RosegardenDocument::addRecordMIDISegment(TrackId tid)
2565 {
2566     RG_DEBUG << "RosegardenDocument::addRecordMIDISegment(" << tid << ")";
2567 //    std::cerr << kdBacktrace() << std::endl;
2568 
2569     Segment *recordMIDISegment;
2570 
2571     recordMIDISegment = new Segment();
2572     recordMIDISegment->setTrack(tid);
2573     recordMIDISegment->setStartTime(m_recordStartTime);
2574 
2575     // Set an appropriate segment label
2576     //
2577     std::string label = "";
2578 
2579     Track *track = m_composition.getTrackById(tid);
2580     if (track) {
2581         if (track->getPresetLabel() != "") {
2582             label = track->getPresetLabel();
2583         } else if (track->getLabel() == "") {
2584             Instrument *instr =
2585                 m_studio.getInstrumentById(track->getInstrument());
2586             if (instr) {
2587                 label = m_studio.getSegmentName(instr->getId());
2588             }
2589         } else {
2590             label = track->getLabel();
2591         }
2592     }
2593 
2594     recordMIDISegment->setLabel(appendLabel(label,
2595             qstrtostr(tr("(recorded)"))));
2596 
2597     // set segment transpose, color, highest/lowest playable from track parameters
2598     Clef clef = clefIndexToClef(track->getClef());
2599     recordMIDISegment->insert(clef.getAsEvent
2600                             (recordMIDISegment->getStartTime()));
2601 
2602     recordMIDISegment->setTranspose(track->getTranspose());
2603     recordMIDISegment->setColourIndex(track->getColor());
2604     recordMIDISegment->setHighestPlayable(track->getHighestPlayable());
2605     recordMIDISegment->setLowestPlayable(track->getLowestPlayable());
2606 
2607     m_composition.addSegment(recordMIDISegment);
2608 
2609     m_recordMIDISegments[track->getInstrument()] = recordMIDISegment;
2610 
2611     RosegardenMainViewWidget *w;
2612     int lenx = m_viewList.count();
2613     int i = 0;
2614     //for (w = m_viewList.first(); w != 0; w = m_viewList.next()) {
2615     for( i=0; i<lenx; i++ ){
2616         w = m_viewList.value( i );
2617         w->getTrackEditor()->getTrackButtons()->slotUpdateTracks();
2618     }
2619 
2620     emit newMIDIRecordingSegment(recordMIDISegment);
2621 }
2622 
2623 void
addRecordAudioSegment(InstrumentId iid,AudioFileId auid)2624 RosegardenDocument::addRecordAudioSegment(InstrumentId iid,
2625                                         AudioFileId auid)
2626 {
2627     Segment *recordSegment = new Segment
2628                              (Segment::Audio);
2629 
2630     // Find the right track
2631 
2632     Track *recordTrack = nullptr;
2633 
2634     const Composition::recordtrackcontainer &tr =
2635         getComposition().getRecordTracks();
2636 
2637     for (Composition::recordtrackcontainer::const_iterator i =
2638                 tr.begin(); i != tr.end(); ++i) {
2639         TrackId tid = (*i);
2640         Track *track = getComposition().getTrackById(tid);
2641         if (track) {
2642             if (iid == track->getInstrument()) {
2643                 recordTrack = track;
2644                 break;
2645             }
2646         }
2647     }
2648 
2649     if (!recordTrack) {
2650         RG_DEBUG << "RosegardenDocument::addRecordAudioSegment(" << iid << ", "
2651         << auid << "): No record-armed track found for instrument!";
2652         return;
2653     }
2654 
2655     recordSegment->setTrack(recordTrack->getId());
2656     recordSegment->setStartTime(m_recordStartTime);
2657     recordSegment->setAudioStartTime(RealTime::zeroTime);
2658 
2659     // Set an appropriate segment label
2660     //
2661     std::string label = "";
2662 
2663     if (recordTrack) {
2664         if (recordTrack->getLabel() == "") {
2665 
2666             Instrument *instr =
2667                 m_studio.getInstrumentById(recordTrack->getInstrument());
2668 
2669             if (instr) {
2670                 label = instr->getName();
2671             }
2672 
2673         } else {
2674             label = recordTrack->getLabel();
2675         }
2676     }
2677 
2678     recordSegment->setLabel(appendLabel(label, qstrtostr(RosegardenDocument::tr("(recorded)"))));
2679     recordSegment->setAudioFileId(auid);
2680 
2681     // set color for audio segment to distinguish it from a MIDI segment on an
2682     // audio track drawn with the pencil (depends on having the current
2683     // autoload.rg or a file derived from it to deliever predictable results,
2684     // but the worst case here is segments drawn in the wrong color when
2685     // adding new segments to old files, which I don't forsee as being enough
2686     // of a problem to be worth cooking up a more robust implementation of
2687     // this new color for new audio segments (DMM)
2688     recordSegment->setColourIndex(GUIPalette::AudioDefaultIndex);
2689 
2690     RG_DEBUG << "RosegardenDocument::addRecordAudioSegment: adding record segment for instrument " << iid << " on track " << recordTrack->getId();
2691     m_recordAudioSegments[iid] = recordSegment;
2692 
2693     RosegardenMainViewWidget *w;
2694     int lenx = m_viewList.count();
2695     int i = 0;
2696     //for (w = m_viewList.first(); w != 0; w = m_viewList.next()) {
2697     for( i=0; i<lenx; i++ ){
2698         w = m_viewList.value( i );
2699         w->getTrackEditor()->getTrackButtons()->slotUpdateTracks();
2700     }
2701 
2702     emit newAudioRecordingSegment(recordSegment);
2703 }
2704 
2705 void
updateRecordingAudioSegments()2706 RosegardenDocument::updateRecordingAudioSegments()
2707 {
2708     const Composition::recordtrackcontainer &tr =
2709         getComposition().getRecordTracks();
2710 
2711     for (Composition::recordtrackcontainer::const_iterator i =
2712                 tr.begin(); i != tr.end(); ++i) {
2713 
2714         TrackId tid = (*i);
2715         Track *track = getComposition().getTrackById(tid);
2716 
2717         if (track) {
2718 
2719             InstrumentId iid = track->getInstrument();
2720 
2721             if (m_recordAudioSegments[iid]) {
2722 
2723                 Segment *recordSegment = m_recordAudioSegments[iid];
2724                 if (!recordSegment->getComposition()) {
2725 
2726                     // always insert straight away for audio
2727                     m_composition.addSegment(recordSegment);
2728                 }
2729 
2730                 recordSegment->setAudioEndTime(
2731                     m_composition.getRealTimeDifference(recordSegment->getStartTime(),
2732                                                         m_composition.getPosition()));
2733 
2734             } else {
2735                 //         RG_DEBUG << "RosegardenDocument::updateRecordingAudioSegments: no segment for instr "
2736                 //              << iid;
2737             }
2738         }
2739     }
2740 }
2741 
2742 void
stopRecordingAudio()2743 RosegardenDocument::stopRecordingAudio()
2744 {
2745     RG_DEBUG << "RosegardenDocument::stopRecordingAudio";
2746 
2747     for (RecordingSegmentMap::iterator ri = m_recordAudioSegments.begin();
2748             ri != m_recordAudioSegments.end(); ++ri) {
2749 
2750         Segment *recordSegment = ri->second;
2751 
2752         if (!recordSegment)
2753             continue;
2754 
2755         // set the audio end time
2756         //
2757         recordSegment->setAudioEndTime(
2758             m_composition.getRealTimeDifference(recordSegment->getStartTime(),
2759                                                 m_composition.getPosition()));
2760 
2761         // now add the Segment
2762         RG_DEBUG << "RosegardenDocument::stopRecordingAudio - "
2763         << "got recorded segment";
2764 
2765         // now move the segment back by the record latency
2766         //
2767         /*!!!
2768           No.  I don't like this.
2769 
2770           The record latency doesn't always exist -- for example, if recording
2771           from a synth plugin there is no record latency, and we have no way
2772           here to distinguish.
2773 
2774           The record latency is a total latency figure that actually includes
2775           some play latency, and we compensate for that again on playback (see
2776           bug #1378766).
2777 
2778           The timeT conversion of record latency is approximate in frames,
2779           giving potential phase error.
2780 
2781           Cutting this out won't break any existing files, as the latency
2782           compensation there is already encoded into the file.
2783 
2784             RealTime adjustedStartTime =
2785                 m_composition.getElapsedRealTime(recordSegment->getStartTime()) -
2786                 m_audioRecordLatency;
2787 
2788             timeT shiftedStartTime =
2789                 m_composition.getElapsedTimeForRealTime(adjustedStartTime);
2790 
2791             RG_DEBUG << "RosegardenDocument::stopRecordingAudio - "
2792                          << "shifted recorded audio segment by "
2793                          <<  recordSegment->getStartTime() - shiftedStartTime
2794                  << " clicks (from " << recordSegment->getStartTime()
2795                  << " to " << shiftedStartTime << ")";
2796 
2797             recordSegment->setStartTime(shiftedStartTime);
2798         */
2799     }
2800     emit stoppedAudioRecording();
2801 
2802     emit pointerPositionChanged(m_composition.getPosition());
2803 }
2804 
2805 void
finalizeAudioFile(InstrumentId iid)2806 RosegardenDocument::finalizeAudioFile(InstrumentId iid)
2807 {
2808     RG_DEBUG << "finalizeAudioFile(" << iid << ")";
2809 
2810     Segment *recordSegment = m_recordAudioSegments[iid];
2811 
2812     if (!recordSegment) {
2813         RG_WARNING << "finalizeAudioFile() WARNING: Failed to find segment";
2814         return;
2815     }
2816 
2817     AudioFile *newAudioFile = m_audioFileManager.getAudioFile(
2818             recordSegment->getAudioFileId());
2819     if (!newAudioFile) {
2820         RG_WARNING << "finalizeAudioFile() WARNING: No audio file found for instrument " << iid << " (audio file id " << recordSegment->getAudioFileId() << ")";
2821         return;
2822     }
2823 
2824     // Progress Dialog
2825     // Note: The label text and range will be set later as needed.
2826     QProgressDialog progressDialog(
2827             "...",  // labelText
2828             tr("Cancel"),  // cancelButtonText
2829             0, 100,  // min, max
2830             RosegardenMainWindow::self());  // parent
2831     progressDialog.setWindowTitle(tr("Rosegarden"));
2832     progressDialog.setWindowModality(Qt::WindowModal);
2833     // Auto-close is ok for this since there is only one step.
2834     progressDialog.setAutoClose(true);
2835     // Just force the progress dialog up.
2836     // Both Qt4 and Qt5 have bugs related to delayed showing of progress
2837     // dialogs.  In Qt4, the dialog sometimes won't show.  In Qt5, KDE
2838     // based distros might lock up.  See Bug #1546.
2839     progressDialog.show();
2840 
2841     m_audioFileManager.setProgressDialog(&progressDialog);
2842 
2843     try {
2844         m_audioFileManager.generatePreview(newAudioFile->getId());
2845     } catch (const Exception &e) {
2846         StartupLogo::hideIfStillThere();
2847         QMessageBox::critical(dynamic_cast<QWidget *>(parent()), tr("Rosegarden"), strtoqstr(e.getMessage()));
2848     }
2849 
2850     if (!recordSegment->getComposition())
2851         getComposition().addSegment(recordSegment);
2852 
2853     CommandHistory::getInstance()->addCommand(
2854             new SegmentRecordCommand(recordSegment));
2855 
2856     // update views
2857     slotUpdateAllViews(nullptr);
2858 
2859     // Add the file to the sequencer
2860     RosegardenSequencer::getInstance()->addAudioFile(
2861             newAudioFile->getFilename(),
2862             newAudioFile->getId());
2863 
2864     // clear down
2865     m_recordAudioSegments.erase(iid);
2866     emit audioFileFinalized(recordSegment);
2867 }
2868 
2869 RealTime
getAudioPlayLatency()2870 RosegardenDocument::getAudioPlayLatency()
2871 {
2872     return RosegardenSequencer::getInstance()->getAudioPlayLatency();
2873 }
2874 
2875 RealTime
getAudioRecordLatency()2876 RosegardenDocument::getAudioRecordLatency()
2877 {
2878     return RosegardenSequencer::getInstance()->getAudioRecordLatency();
2879 }
2880 
2881 void
updateAudioRecordLatency()2882 RosegardenDocument::updateAudioRecordLatency()
2883 {
2884     m_audioRecordLatency = getAudioRecordLatency();
2885 }
2886 
2887 void
clearAllPlugins()2888 RosegardenDocument::clearAllPlugins()
2889 {
2890     RG_DEBUG << "clearAllPlugins";
2891 
2892     InstrumentList list = m_studio.getAllInstruments();
2893     MappedEventList mC;
2894 
2895     InstrumentList::iterator it = list.begin();
2896     for (; it != list.end(); ++it) {
2897         if ((*it)->getType() == Instrument::Audio) {
2898             AudioPluginVector::iterator pIt = (*it)->beginPlugins();
2899 
2900             for (; pIt != (*it)->endPlugins(); pIt++) {
2901                 if ((*pIt)->getMappedId() != -1) {
2902                     if (StudioControl::
2903                         destroyStudioObject((*pIt)->getMappedId()) == false) {
2904                         RG_DEBUG << "RosegardenDocument::clearAllPlugins - "
2905                                  << "couldn't find plugin instance "
2906                                  << (*pIt)->getMappedId();
2907                     }
2908                 }
2909                 (*pIt)->clearPorts();
2910             }
2911             (*it)->emptyPlugins();
2912 
2913             /*
2914             RG_DEBUG << "RosegardenDocument::clearAllPlugins - "
2915                      << "cleared " << (*it)->getName();
2916             */
2917         }
2918     }
2919 }
2920 
slotDocColoursChanged()2921 void RosegardenDocument::slotDocColoursChanged()
2922 {
2923     RG_DEBUG << "RosegardenDocument::slotDocColoursChanged(): emitting docColoursChanged()";
2924 
2925     emit docColoursChanged();
2926 }
2927 
2928 void
addOrphanedRecordedAudioFile(QString fileName)2929 RosegardenDocument::addOrphanedRecordedAudioFile(QString fileName)
2930 {
2931     m_orphanedRecordedAudioFiles.push_back(fileName);
2932     slotDocumentModified();
2933 }
2934 
2935 void
addOrphanedDerivedAudioFile(QString fileName)2936 RosegardenDocument::addOrphanedDerivedAudioFile(QString fileName)
2937 {
2938     m_orphanedDerivedAudioFiles.push_back(fileName);
2939     slotDocumentModified();
2940 }
2941 
2942 void
notifyAudioFileRemoval(AudioFileId id)2943 RosegardenDocument::notifyAudioFileRemoval(AudioFileId id)
2944 {
2945     AudioFile *file = nullptr;
2946 
2947     if (m_audioFileManager.wasAudioFileRecentlyRecorded(id)) {
2948         file = m_audioFileManager.getAudioFile(id);
2949         if (file) addOrphanedRecordedAudioFile( file->getFilename() );
2950         return;
2951     }
2952 
2953     if (m_audioFileManager.wasAudioFileRecentlyDerived(id)) {
2954         file = m_audioFileManager.getAudioFile(id);
2955         if (file) addOrphanedDerivedAudioFile( file->getFilename() );
2956         return;
2957     }
2958 }
2959 
2960 // Get the instrument that plays the segment.
2961 // @returns a pointer to the instrument object
2962 Instrument *
2963 RosegardenDocument::
getInstrument(Segment * segment)2964 getInstrument(Segment *segment)
2965 {
2966     if (!segment || !(segment->getComposition())) {
2967         return nullptr;
2968     }
2969 
2970     Studio &studio = getStudio();
2971     Instrument *instrument =
2972         studio.getInstrumentById
2973         (segment->getComposition()->getTrackById(segment->getTrack())->
2974          getInstrument());
2975     return instrument;
2976 }
2977 
2978 void
checkAudioPath(Track * track)2979 RosegardenDocument::checkAudioPath(Track *track)
2980 {
2981     // Might consider calling this from a trackChanged() handler.  Although
2982     // it might not be a good idea to do a dialog from there, and the dialog
2983     // could keep popping up over and over without some sort of static flag.
2984 
2985     if (!track->isArmed())
2986         return;
2987 
2988     Instrument *instrument =
2989             getStudio().getInstrumentById(track->getInstrument());
2990 
2991     bool audio = (instrument  &&
2992                   instrument->getType() == Instrument::Audio);
2993 
2994     if (!audio)
2995         return;
2996 
2997     try {
2998         getAudioFileManager().testAudioPath();
2999     } catch (AudioFileManager::BadAudioPathException & /*e*/) {
3000         // ho ho, here was the real culprit: this dialog inherited style
3001         // from the track button, hence the weird background and black
3002         // foreground!
3003         if (QMessageBox::warning(nullptr,
3004                                  tr("Warning"),
3005                                  tr("The audio file path does not exist or is not writable.\nPlease set the audio file path to a valid directory in Document Properties before recording audio.\nWould you like to set it now?"),
3006                                  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel
3007                                 ) == QMessageBox::Yes) {
3008             RosegardenMainWindow::self()->slotOpenAudioPathSettings();
3009         }
3010     }
3011 }
3012 
3013 QString
lockFilename(const QString & absFilePath)3014 RosegardenDocument::lockFilename(const QString &absFilePath) // static
3015 {
3016     QFileInfo fileInfo(absFilePath);
3017     return fileInfo.absolutePath() + "/.~lock." + fileInfo.fileName() + "#";
3018 }
3019 
3020 bool
lock()3021 RosegardenDocument::lock()
3022 {
3023     // Can't lock something that isn't a file on the filesystem.
3024     if (!isRegularDotRGFile())
3025         return true;
3026 
3027     delete m_lockFile;
3028     m_lockFile = createLock(m_absFilePath);
3029     return m_lockFile != nullptr;
3030 }
3031 
3032 QLockFile *
createLock(const QString & absFilePath)3033 RosegardenDocument::createLock(const QString &absFilePath) // static
3034 {
3035     QLockFile *lockFile = new QLockFile(lockFilename(absFilePath));
3036     lockFile->setStaleLockTime(0);
3037 
3038     if (!lockFile->tryLock()) {
3039         if (lockFile->error() == QLockFile::LockFailedError) {
3040             // Read in the existing lock file.
3041             qint64 pid;
3042             QString hostname;
3043             QString appname;
3044             if (!lockFile->getLockInfo(&pid, &hostname, &appname)) {
3045                 RG_WARNING << "createLock(): Failed to read lock file information! Permission problem? Deleted meanwhile?";
3046             }
3047             QString message;
3048             QTextStream out(&message);
3049             out << tr("Lock Filename: ") << lockFilename(absFilePath) << '\n';
3050             out << tr("Process ID: ") << pid << '\n';
3051             out << tr("Host: ") << hostname << '\n';
3052             out << tr("Application: ") << appname << '\n';
3053             out.flush();
3054 
3055             // Present a dialog to the user with the info.
3056             StartupLogo::hideIfStillThere();
3057             QMessageBox::warning(
3058                     RosegardenMainWindow::self(),
3059                     tr("Rosegarden"),
3060                     tr("Could not lock file.\n\n"
3061                         "Another user or instance of Rosegarden may already be\n"
3062                         "editing this file.  If you are sure no one else is\n"
3063                         "editing this file, delete the lock file and try again.\n\n") +
3064                     message);
3065             // Maybe we can add a button which would call lockFile->removeStaleLockFile()?
3066             // On the other hand with QLockFile it's much more rare to need to do this
3067             // since it detects when the owning process went away and then we don't get here.
3068 
3069             delete lockFile;
3070             return nullptr;
3071         }
3072         // This is why we don't handle the other error codes from tryLock:
3073         // If we do not have permission, don't worry about it.  Pretend the lock
3074         // was successful.  After all, we won't be able to save.
3075     }
3076 
3077     return lockFile;
3078 }
3079 
release()3080 void RosegardenDocument::release()
3081 {
3082     // Remove the lock file
3083     delete m_lockFile;
3084     m_lockFile = nullptr;
3085 }
3086 
3087 }
3088