1 /*!******************************************************************** 2 3 Audacity: A Digital Audio Editor 4 5 @file MIDIPlay.h 6 @brief Inject added MIDI playback capability into Audacity's audio engine 7 8 Paul Licameli split from AudIOBase.h 9 10 **********************************************************************/ 11 12 #ifndef __AUDACITY_MIDI_PLAY__ 13 #define __AUDACITY_MIDI_PLAY__ 14 15 #include "AudioIOExt.h" 16 #include "NoteTrack.h" 17 #include <optional> 18 #include "../lib-src/header-substitutes/allegro.h" 19 20 typedef void PmStream; 21 typedef int32_t PmTimestamp; 22 class Alg_event; 23 class Alg_iterator; 24 using NoteTrackConstArray = std::vector < std::shared_ptr< const NoteTrack > >; 25 26 class AudioThread; 27 28 // This workaround makes pause and stop work when output is to GarageBand, 29 // which seems not to implement the notes-off message correctly. 30 #define AUDIO_IO_GB_MIDI_WORKAROUND 31 32 #include "PlaybackSchedule.h" 33 34 namespace { 35 36 struct MIDIPlay : AudioIOExt 37 { 38 explicit MIDIPlay(const PlaybackSchedule &schedule); 39 ~MIDIPlay() override; 40 AudioTimeMIDIPlay41 double AudioTime(double rate) const 42 { return mPlaybackSchedule.mT0 + mNumFrames / rate; } 43 44 const PlaybackSchedule &mPlaybackSchedule; 45 NoteTrackConstArray mMidiPlaybackTracks; 46 47 /// True when output reaches mT1 48 static bool mMidiOutputComplete; 49 50 /// mMidiStreamActive tells when mMidiStream is open for output 51 static bool mMidiStreamActive; 52 53 PmStream *mMidiStream = nullptr; 54 int mLastPmError = 0; 55 56 /// Latency of MIDI synthesizer 57 long mSynthLatency = MIDISynthLatency_ms.GetDefault(); 58 59 // These fields are used to synchronize MIDI with audio: 60 61 /// Number of frames output, including pauses 62 long mNumFrames = 0; 63 /// total of backward jumps 64 int mMidiLoopPasses = 0; 65 // MidiLoopOffsetMIDIPlay66 inline double MidiLoopOffset() { 67 return mMidiLoopPasses * (mPlaybackSchedule.mT1 - mPlaybackSchedule.mT0); 68 } 69 70 long mAudioFramesPerBuffer = 0; 71 /// Used by Midi process to record that pause has begun, 72 /// so that AllNotesOff() is only delivered once 73 bool mMidiPaused = false; 74 /// The largest timestamp written so far, used to delay 75 /// stream closing until last message has been delivered 76 PmTimestamp mMaxMidiTimestamp = 0; 77 78 /// Offset from ideal sample computation time to system time, 79 /// where "ideal" means when we would get the callback if there 80 /// were no scheduling delays or computation time 81 double mSystemMinusAudioTime = 0.0; 82 /// audio output latency reported by PortAudio 83 /// (initially; for Alsa, we adjust it to the largest "observed" value) 84 double mAudioOutLatency = 0.0; 85 86 // Next two are used to adjust the previous two, if 87 // PortAudio does not provide the info (using ALSA): 88 89 /// time of first callback 90 /// used to find "observed" latency 91 double mStartTime = 0.0; 92 /// number of callbacks since stream start 93 long mCallbackCount = 0; 94 95 double mSystemMinusAudioTimePlusLatency = 0.0; 96 97 std::optional<Alg_iterator> mIterator; 98 /// The next event to play (or null) 99 Alg_event *mNextEvent = nullptr; 100 101 #ifdef AUDIO_IO_GB_MIDI_WORKAROUND 102 std::vector< std::pair< int, int > > mPendingNotesOff; 103 #endif 104 105 /// Real time at which the next event should be output, measured in seconds. 106 /// Note that this could be a note's time+duration for note offs. 107 double mNextEventTime = 0.0; 108 /// Track of next event 109 NoteTrack *mNextEventTrack = nullptr; 110 /// Is the next event a note-on? 111 bool mNextIsNoteOn = false; 112 /// when true, mSendMidiState means send only updates, not note-on's, 113 /// used to send state changes that precede the selected notes 114 bool mSendMidiState = false; 115 116 void PrepareMidiIterator(bool send, double offset); 117 bool StartPortMidiStream(double rate); 118 119 // Compute nondecreasing real time stamps, accounting for pauses, but not the 120 // synth latency. 121 double UncorrectedMidiEventTime(double pauseTime); 122 123 void OutputEvent(double pauseTime); 124 void GetNextEvent(); 125 double PauseTime(double rate, unsigned long pauseFrames); 126 void AllNotesOff(bool looping = false); 127 128 /** \brief Compute the current PortMidi timestamp time. 129 * 130 * This is used by PortMidi to synchronize midi time to audio samples 131 */ 132 PmTimestamp MidiTime(); 133 134 // Note: audio code solves the problem of soloing/muting tracks by scanning 135 // all playback tracks on every call to the audio buffer fill routine. 136 // We do the same for Midi, but it seems wasteful for at least two 137 // threads to be frequently polling to update status. This could be 138 // eliminated (also with a reduction in code I think) by updating mHasSolo 139 // each time a solo button is activated or deactivated. For now, I'm 140 // going to do this polling in the FillMidiBuffer routine to localize 141 // changes for midi to the midi code, but I'm declaring the variable 142 // here so possibly in the future, Audio code can use it too. -RBD 143 private: 144 bool mHasSolo = false; // is any playback solo button pressed? 145 public: 146 bool SetHasSolo(bool hasSolo); GetHasSoloMIDIPlay147 bool GetHasSolo() { return mHasSolo; } 148 149 bool mUsingAlsa = false; 150 151 static bool IsActive(); 152 bool IsOtherStreamActive() const override; 153 154 void ComputeOtherTimings(double rate, 155 const PaStreamCallbackTimeInfo *timeInfo, 156 unsigned long framesPerBuffer) override; 157 void SignalOtherCompletion() override; 158 unsigned CountOtherSoloTracks() const override; 159 160 bool StartOtherStream(const TransportTracks &tracks, 161 const PaStreamInfo* info, double startTime, double rate) override; 162 void AbortOtherStream() override; 163 void FillOtherBuffers(double rate, unsigned long pauseFrames, 164 bool paused, bool hasSolo) override; 165 void StopOtherStream() override; 166 167 AudioIODiagnostics Dump() const override; 168 }; 169 170 } 171 172 #endif 173