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