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 #ifndef RG_ROSEGARDENDOCUMENT_H 19 #define RG_ROSEGARDENDOCUMENT_H 20 21 #include "base/Composition.h" 22 #include "base/Configuration.h" 23 #include "base/Device.h" 24 #include "base/MidiProgram.h" 25 #include "base/RealTime.h" 26 #include "base/Segment.h" 27 #include "base/Studio.h" 28 #include "gui/editors/segment/compositionview/AudioPeaksThread.h" 29 #include "sound/AudioFileManager.h" 30 #include "base/Event.h" 31 32 #include <QObject> 33 #include <QString> 34 #include <QStringList> 35 #include <QProgressDialog> 36 #include <QPointer> 37 #include <QSharedPointer> 38 39 #include <map> 40 #include <vector> 41 42 class QLockFile; 43 class QWidget; 44 class QTextStream; 45 class NoteOnRecSet; 46 47 namespace Rosegarden 48 { 49 50 class SequenceManager; 51 class RosegardenMainViewWidget; 52 class MappedEventList; 53 class Event; 54 class EditViewBase; 55 class AudioPluginManager; 56 57 58 static const int MERGE_AT_END = (1 << 0); 59 static const int MERGE_IN_NEW_TRACKS = (1 << 1); 60 static const int MERGE_KEEP_OLD_TIMINGS = (1 << 2); 61 static const int MERGE_KEEP_NEW_TIMINGS = (1 << 3); 62 63 64 /// The document object for a document-view model. 65 /** 66 * The RosegardenDocument class provides a document object that can be 67 * used in conjunction with the classes RosegardenMainWindow and 68 * RosegardenMainViewWidget to create a document-view model 69 * based on QApplication and QMainWindow. Thereby, the 70 * document object is created by the RosegardenMainWindow instance and 71 * contains the document structure with related methods for 72 * manipulation of the document data by RosegardenMainViewWidget 73 * objects. Also, RosegardenDocument contains the methods for 74 * serialization of the document data from and to files. 75 * 76 * An instance of RosegardenDocument is owned by RosegardenMainWindow. 77 * The easiest way to get here without passing pointers around: 78 * 79 * RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument(); 80 * 81 * RosegardenDocument owns the Composition in the document. 82 */ 83 class ROSEGARDENPRIVATE_EXPORT RosegardenDocument : public QObject 84 { 85 Q_OBJECT 86 87 public: 88 89 /** 90 * Constructor for the fileclass of the application 91 * 92 * Our old command history under KDE didn't get wiped out when creating a 93 * temporary document to use alongside the application's current one, but 94 * that is no longer the case since Thorn. We need to be able to avoid 95 * clearing the command history here, or certain commands wipe out the 96 * entire undo history needlessly. 97 */ 98 RosegardenDocument(QObject *parent, 99 QSharedPointer<AudioPluginManager> audioPluginManager, 100 bool skipAutoload = false, 101 bool clearCommandHistory = true, 102 bool enableSound = true); 103 104 private: 105 RosegardenDocument(const RosegardenDocument &doc); 106 RosegardenDocument& operator=(const RosegardenDocument &doc); 107 108 public: 109 static int FILE_FORMAT_VERSION_MAJOR; 110 static int FILE_FORMAT_VERSION_MINOR; 111 static int FILE_FORMAT_VERSION_POINT; 112 113 /** 114 * Destructor for the fileclass of the application 115 */ 116 ~RosegardenDocument() override; 117 118 /** 119 * adds a view to the document which represents the document 120 * contents. Usually this is your main view. 121 */ 122 void attachView(RosegardenMainViewWidget *view); 123 124 /** 125 * removes a view from the list of currently connected views 126 */ 127 void detachView(RosegardenMainViewWidget *view); 128 129 /** 130 * adds an Edit View (notation, matrix, event list) 131 */ 132 void attachEditView(EditViewBase*); 133 134 /** 135 * removes a view from the list of currently connected edit views 136 */ 137 void detachEditView(EditViewBase*); 138 139 /** 140 * delete all Edit Views 141 */ 142 void deleteEditViews(); 143 144 /// Set the modified flag but do not notify observers. 145 /** 146 * This also clears m_autoSaved. 147 * 148 * Use this rather than slotDocumentModified() when you do not 149 * want the entire UI to refresh. This can be used for very high 150 * frequency changes that might cause high CPU usage if the UI 151 * were refreshed every time. 152 * 153 * See slotDocumentModified() and emitDocumentModified(). 154 */ 155 void setModified(); 156 157 /** 158 * returns if the document is modified or not. Use this to 159 * determine if your document needs saving by the user on closing. 160 */ isModified()161 bool isModified() const { return m_modified; } 162 163 /** 164 * clears the 'modified' status of the document (sets it back to false). 165 * 166 */ 167 void clearModifiedStatus(); 168 169 /// Emit the documentModified() signal. 170 /** 171 * Use this in situations where you need a UI refresh, but the document 172 * hasn't been modified to the point of requiring a save (e.g. the Track 173 * selection has changed). 174 * 175 * See setModified() and slotDocumentModified(). 176 */ emitDocumentModified()177 void emitDocumentModified() { emit documentModified(true); } 178 179 /** 180 * get the autosave interval in seconds 181 */ 182 unsigned int getAutoSavePeriod() const; 183 184 /** 185 * Load the document by filename and format and emit the 186 * updateViews() signal. The "permanent" argument should be true 187 * if this document is intended to be loaded to the GUI for real 188 * editing work: in this case, any necessary device-synchronisation 189 * with the sequencer will be carried out. If permanent is false, 190 * the sequencer's device list will be left alone. If squelch is 191 * true, no progress dialog will be shown. 192 */ 193 bool openDocument(const QString &filename, 194 bool permanent = true, 195 bool squelchProgressDialog = false, 196 bool enableLock = true); 197 198 /** 199 * merge another document into this one 200 */ 201 void mergeDocument(RosegardenDocument *doc, int options); 202 203 /** 204 * saves the document under filename and format. 205 * 206 * errMsg will be set to a user-readable error message if save fails 207 */ 208 bool saveDocument(const QString &filename, QString& errMsg, 209 bool autosave = false); 210 211 /// Save under a new name. 212 bool saveAs(const QString &newName, QString &errMsg); 213 214 /** 215 * exports all or part of the studio to a file. If devices is 216 * empty, exports all devices. 217 */ 218 bool exportStudio(const QString &filename, 219 QString &errMsg, 220 std::vector<DeviceId> devices = 221 std::vector<DeviceId>()); 222 223 /** 224 * sets the path to the file connected with the document 225 */ 226 void setAbsFilePath(const QString &filename); 227 228 /** 229 * returns the pathname of the current document file 230 */ 231 const QString &getAbsFilePath() const; 232 233 /** 234 * removes the autosave file (e.g. after saving) 235 */ 236 void deleteAutoSaveFile(); 237 238 /** 239 * sets the filename of the document 240 */ 241 void setTitle(const QString &title); 242 243 /** 244 * returns the title of the document 245 */ 246 const QString &getTitle() const; 247 248 /** 249 * Returns true if the file is a regular Rosegarden ".rg" file, 250 * false if it's an imported file or a new file (not yet saved) 251 */ 252 bool isRegularDotRGFile() const; 253 254 void setQuickMarker(); 255 void jumpToQuickMarker(); getQuickMarkerTime()256 timeT getQuickMarkerTime() { return m_quickMarkerTime; } 257 258 /** 259 * returns the composition (the principal constituent of the document) 260 */ getComposition()261 Composition& getComposition() { return m_composition; } 262 263 /** 264 * returns the composition (the principal constituent of the document) 265 */ getComposition()266 const Composition& getComposition() const { return m_composition; } 267 268 /* 269 * return the Studio 270 */ getStudio()271 Studio& getStudio() { return m_studio;} 272 getStudio()273 const Studio& getStudio() const { return m_studio;} 274 275 /* 276 * return the AudioPeaksThread 277 */ getAudioPeaksThread()278 AudioPeaksThread& getAudioPeaksThread() 279 { return m_audioPeaksThread; } 280 getAudioPeaksThread()281 const AudioPeaksThread& getAudioPeaksThread() const 282 { return m_audioPeaksThread; } 283 284 /* 285 * return the AudioFileManager 286 */ getAudioFileManager()287 AudioFileManager& getAudioFileManager() 288 { return m_audioFileManager; } 289 getAudioFileManager()290 const AudioFileManager& getAudioFileManager() const 291 { return m_audioFileManager; } 292 293 /* 294 * return the Configuration object 295 */ getConfiguration()296 DocumentConfiguration& getConfiguration() { return m_config; } 297 getConfiguration()298 const DocumentConfiguration& getConfiguration() const 299 { return m_config; } 300 301 /** 302 * Returns whether playing sound is enabled at all 303 */ 304 bool isSoundEnabled() const; 305 306 /// Insert some recorded MIDI events into our recording Segment. 307 /** 308 * These MIDI events come from AlsaDriver::getMappedEventList() in 309 * the sequencer thread. 310 */ 311 void insertRecordedMidi(const MappedEventList &mc); 312 313 /** 314 * Update the recording value() -- called regularly from 315 * RosegardenMainWindow::slotUpdatePlaybackPosition() while recording 316 */ 317 void updateRecordingMIDISegment(); 318 319 /** 320 * Update the recording value() for audio 321 */ 322 void updateRecordingAudioSegments(); 323 324 /** 325 * Tidy up the recording SegmentItems and other post record jobs 326 */ 327 void stopRecordingMidi(); 328 void stopRecordingAudio(); 329 330 /** 331 * And any post-play jobs 332 */ 333 void stopPlaying(); 334 335 /** 336 * Register audio samples at the sequencer 337 */ 338 void prepareAudio(); 339 340 /** 341 * Cause the loopChanged signal to be emitted and any 342 * associated internal work in the document to happen 343 */ 344 void setLoop(timeT, timeT); 345 346 /** 347 * Cause the document to use the given time as the origin 348 * when inserting any subsequent recorded data 349 */ setRecordStartTime(timeT t)350 void setRecordStartTime(timeT t) { m_recordStartTime = t; } 351 352 /* 353 * Get a MappedDevice from the sequencer and add the 354 * results to our Studio 355 */ 356 /*!DEVPUSH 357 void getMappedDevice(DeviceId id); 358 */ 359 360 void addRecordMIDISegment(TrackId); 361 void addRecordAudioSegment(InstrumentId, AudioFileId); 362 363 /** 364 * Audio play and record latencies direct from the sequencer 365 */ 366 367 RealTime getAudioPlayLatency(); 368 RealTime getAudioRecordLatency(); 369 void updateAudioRecordLatency(); 370 371 /** Complete the add of an audio file when a new file has finished 372 * being recorded at the sequencer. This method will ensure that 373 * the audio file is added to the AudioFileManager, that 374 * a preview is generated and that the sequencer also knows to add 375 * the new file to its own hash table. Flow of control is a bit 376 * awkward around new audio files as timing is crucial - the gui can't 377 * access the file until lead-out information has been written by the 378 * sequencer. 379 * 380 * Note that the sequencer doesn't know the audio file id (yet), 381 * only the instrument it was recorded to. (It does know the 382 * filename, but the instrument id is enough for us.) 383 */ 384 385 void finalizeAudioFile(InstrumentId instrument); 386 387 /** Tell the document that an audio file has been orphaned. An 388 * orphaned audio file is a file that was created by recording in 389 * Rosegarden during the current session, but that has been 390 * unloaded from the audio file manager. It's therefore likely 391 * that no other application will be using it, and that that user 392 * doesn't want to keep it. We can offer to delete these files 393 * permanently when the document is saved. 394 */ 395 void addOrphanedRecordedAudioFile(QString fileName); 396 void addOrphanedDerivedAudioFile(QString fileName); 397 398 /* 399 * Consider whether to orphan the given audio file which is about 400 * to be removed from the audio file manager. 401 */ 402 void notifyAudioFileRemoval(AudioFileId id); 403 404 /* 405 void setAudioRecordLatency(const RealTime &latency) 406 { m_audioRecordLatency = latency; } 407 void setAudioPlayLatency(const RealTime &latency) 408 { m_audioPlayLatency = latency; } 409 */ 410 411 /** 412 * Return the AudioPluginManager 413 */ getPluginManager()414 QSharedPointer<AudioPluginManager> getPluginManager() 415 { return m_pluginManager; } 416 417 /** 418 * Return the instrument that plays segment 419 */ 420 Instrument * 421 getInstrument(Segment *segment); 422 423 /** 424 * Clear all plugins from sequencer and from gui 425 */ 426 void clearAllPlugins(); 427 428 /// Send channel setups (BS/PC/CCs) for each Track. 429 void sendChannelSetups(bool reset); 430 431 /** 432 * Initialise the Studio with a new document's settings 433 */ 434 void initialiseStudio(); 435 436 /* 437 * Get the sequence manager from the app 438 */ 439 SequenceManager* getSequenceManager(); 440 441 /** 442 * Set the sequence manager (called by SequenceManager itself) 443 */ 444 void setSequenceManager(SequenceManager *sm); 445 446 /** 447 * return the list of the views currently connected to the document 448 */ getViewList()449 QList<RosegardenMainViewWidget*>& getViewList() { return m_viewList; } 450 isBeingDestroyed()451 bool isBeingDestroyed() { return m_beingDestroyed; } 452 453 static const unsigned int MinNbOfTracks; // 64 454 455 /// Verify that the audio path exists and can be written to. 456 void checkAudioPath(Track *track); 457 458 bool deleteOrphanedAudioFiles(bool documentWillNotBeSaved); 459 460 void stealLockFile(RosegardenDocument *other); 461 462 public slots: 463 /** 464 * calls repaint() on all views connected to the document object 465 * and is called by the view by which the document has been 466 * changed. As this view normally repaints itself, it is excluded 467 * from the paintEvent. 468 */ 469 void slotUpdateAllViews(RosegardenMainViewWidget *sender); 470 471 /// Set the modified flag and notify observers via documentModified(). 472 /** 473 * This also clears m_autoSaved and emits documentModified(). 474 * 475 * Call this when modifications have been made to the document and 476 * an immediate update to the UI is needed. Do not call this too 477 * frequently as it causes a refresh of the entire UI which is very 478 * expensive. For high-frequency changes, use setModified() and 479 * let the UI update on a timer at a reasonable pace. 480 * 481 * See setModified() and emitDocumentModified(). 482 */ 483 void slotDocumentModified(); 484 void slotDocumentRestored(); 485 486 /** 487 * saves the document to a suitably-named backup file 488 */ 489 void slotAutoSave(); 490 491 void slotSetPointerPosition(timeT); 492 slotSetLoop(timeT s,timeT e)493 void slotSetLoop(timeT s, timeT e) {setLoop(s,e);} 494 495 void slotDocColoursChanged(); 496 497 signals: 498 /// Emitted when the document is modified. 499 /** 500 * See slotDocumentModified(). 501 * 502 * ??? These signals should be moved out of RosegardenDocument into 503 * a separate global signal class. Then the document can come and 504 * go, and the various observers of these signals can stay up and 505 * connected. RosegardenDocument should not derive from QObject. 506 */ 507 void documentModified(bool); 508 509 /** 510 * Emitted during playback, to suggest that views should track along, 511 * as well as when pointer is moved via a click on the loop ruler. 512 */ 513 void pointerPositionChanged(timeT); 514 515 /** 516 * Emitted during recording, to indicate that some new notes (it's 517 * only emitted for notes) have appeared in the recording segment 518 * and anything tracking should track. updatedFrom gives the 519 * start of the new region, which is presumed to extend up to the 520 * end of the segment. 521 */ 522 void recordMIDISegmentUpdated(Segment *recordSegment, 523 timeT updatedFrom); 524 525 /** 526 * Emitted when a new MIDI recording segment is set 527 */ 528 void newMIDIRecordingSegment(Segment*); 529 530 /** 531 * Emitted when a new audio recording segment is set 532 */ 533 void newAudioRecordingSegment(Segment*); 534 535 void makeTrackVisible(int trackPosition); 536 537 void stoppedAudioRecording(); 538 void stoppedMIDIRecording(); 539 void audioFileFinalized(Segment*); 540 541 void playPositionChanged(timeT); 542 void loopChanged(timeT, timeT); 543 /** 544 * We probably want to keep this notification as a special case. 545 * The reason being that to detect a change to the color list will 546 * require comparing a list of 420 strings. That's a bit too much. 547 * I guess we could implement some sort of trickery like a hash 548 * or a change count that clients can cache and compare with the 549 * current value to detect a change. Clever. But is it too 550 * clever? Which is easier to understand? A special notification 551 * or a change count? 552 */ 553 void docColoursChanged(); 554 void devicesResyncd(); 555 556 private: 557 /** 558 * initializes the document generally 559 */ 560 void newDocument(); 561 562 /** 563 * Autoload 564 */ 565 void performAutoload(); 566 567 /** 568 * Parse the Rosegarden file in \a file 569 * 570 * \a errMsg will contains the error messages 571 * if parsing failed. 572 * 573 * @return false if parsing failed 574 * @see RoseXmlHandler 575 */ 576 bool xmlParse(QString fileContents, QString &errMsg, 577 bool permanent, 578 bool &cancelled); 579 580 /** 581 * Set the "auto saved" status of the document 582 * Doc. modification sets it to false, autosaving 583 * sets it to true 584 */ setAutoSaved(bool s)585 void setAutoSaved(bool s) { m_autoSaved = s; } 586 587 /** 588 * Returns whether the document should be auto-saved 589 */ isAutoSaved()590 bool isAutoSaved() const { return m_autoSaved; } 591 592 /** 593 * Returns the name of the autosave file 594 */ 595 QString getAutoSaveFileName(); 596 597 /** 598 * Save document to the given file. This function does the actual 599 * save of the file to the given filename; saveDocument() wraps 600 * this, saving to a temporary file and then renaming to the 601 * required file, so as not to lose the original if a failure 602 * occurs during overwriting. 603 */ 604 bool saveDocumentActual(const QString &filename, QString& errMsg, 605 bool autosave = false); 606 607 /** 608 * Save one segment to the given text stream 609 */ 610 void saveSegment(QTextStream&, Segment*, 611 long totalNbOfEvents, long &count, 612 QString extraAttributes = QString()); 613 614 /// Identifies a specific event within a specific segment. 615 /** 616 * A struct formed by a Segment pointer and an iterator into the same 617 * Segment, used in NoteOn calculations when recording MIDI. 618 */ 619 struct NoteOnRec { 620 Segment *m_segment; 621 Segment::iterator m_segmentIterator; 622 }; 623 624 /** 625 * A vector of NoteOnRec elements, necessary in multitrack MIDI 626 * recording for NoteOn calculations 627 */ 628 typedef std::vector<NoteOnRec> NoteOnRecSet; 629 630 /** 631 * Store a single NoteOnRec element in the m_noteOnEvents map 632 */ 633 void storeNoteOnEvent( Segment *s, Segment::iterator it, 634 int device, int channel ); 635 636 /// Adjust the end time for a list of overlapping note events. 637 /** 638 * Adjusts the end time for all the note-on events for a 639 * device/channel/pitch. 640 * 641 * Replace recorded Note events in one or several segments, returning the 642 * resulting NoteOnRecSet 643 */ 644 NoteOnRecSet* adjustEndTimes(NoteOnRecSet &rec_vec, timeT endTime); 645 646 /** 647 * Insert a recorded event in one or several segments 648 */ 649 void insertRecordedEvent(Event *ev, int device, int channel, bool isNoteOn); 650 651 /** 652 * Transpose an entire segment relative to its destination track. This is 653 * used for transposing a source MIDI recording segment on a per-track 654 * basis, so that the results all come out with the same sound as the 655 * original recording. 656 */ 657 void transposeRecordedSegment(Segment *s); 658 659 // File locking functions to prevent multiple users from editing 660 // the same file. 661 662 /// Returns true if the lock was successful. 663 bool lock(); 664 void release(); 665 666 static QLockFile *createLock(const QString &absFilePath); 667 static QString lockFilename(const QString &absFilePath); 668 669 //--------------- Data members --------------------------------- 670 671 /** 672 * the list of the views currently connected to the document 673 */ 674 QList<RosegardenMainViewWidget*> m_viewList; 675 676 /** 677 * the list of the edit views currently editing a part of this document 678 */ 679 QList<EditViewBase*> m_editViewList; 680 681 /** 682 * the modified flag of the current document 683 */ 684 bool m_modified; 685 686 /** 687 * the autosaved status of the current document 688 */ 689 bool m_autoSaved; 690 691 /** 692 * the title of the current document 693 */ 694 QString m_title; 695 696 /** 697 * absolute file path of the current document 698 */ 699 QString m_absFilePath; 700 701 /** 702 * absolute file path of the current document 703 */ 704 QLockFile *m_lockFile; 705 706 /** 707 * the composition this document is wrapping 708 */ 709 Composition m_composition; 710 711 /** 712 * stores AudioFile mappings 713 */ 714 AudioFileManager m_audioFileManager; 715 716 /** 717 * calculates AudioFile previews 718 */ 719 AudioPeaksThread m_audioPeaksThread; 720 721 typedef std::map<InstrumentId, Segment *> RecordingSegmentMap; 722 723 /** 724 * Segments onto which we can record MIDI events 725 */ 726 //Segment *m_recordMIDISegment; 727 RecordingSegmentMap m_recordMIDISegments; 728 729 /** 730 * Segments for recording audio (per instrument) 731 */ 732 RecordingSegmentMap m_recordAudioSegments; 733 734 /** 735 * a map[Pitch] of NoteOnRecSet elements, for NoteOn calculations 736 */ 737 typedef std::map<int /*pitch*/, NoteOnRecSet> PitchMap; 738 739 /** 740 * a map[Channel] of PitchMap 741 */ 742 typedef std::map<int /*channel*/, PitchMap> ChanMap; 743 744 /** 745 * a map[Port] of ChanMap 746 */ 747 typedef std::map<int /*device*/, ChanMap> NoteOnMap; 748 749 /** 750 * During recording, we collect note-ons that haven't yet had a note-off 751 * in here 752 */ 753 NoteOnMap m_noteOnEvents; 754 755 /** 756 * the Studio 757 */ 758 Studio m_studio; 759 760 /** 761 * A configuration object 762 */ 763 DocumentConfiguration m_config; 764 765 SequenceManager *m_seqManager; 766 767 /** 768 * AudioPluginManager - sequencer and local plugin management 769 */ 770 QSharedPointer<AudioPluginManager> m_pluginManager; 771 772 RealTime m_audioRecordLatency; 773 774 timeT m_recordStartTime; 775 776 timeT m_quickMarkerTime; 777 778 std::vector<QString> m_orphanedRecordedAudioFiles; 779 std::vector<QString> m_orphanedDerivedAudioFiles; 780 781 /** 782 * Autosave period for this document in seconds 783 */ 784 int m_autoSavePeriod; 785 786 // Set to true when the dtor starts 787 bool m_beingDestroyed; 788 789 /** 790 * Tells this document whether it should clear the command history upon 791 * construction. Usually true. 792 */ 793 bool m_clearCommandHistory; 794 795 /// Enable/disable playing sounds 796 bool m_soundEnabled; 797 798 /// Allow file lock to be released. 799 bool m_release; 800 801 QPointer<QProgressDialog> m_progressDialog; 802 }; 803 804 805 } 806 807 #endif 808