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