1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-31 07:59:27 +02:00

Roger Dannenberg's fix for MIDI notes-off, applied to Linux only...

... but then, always:  It's a problem in portmidi which uses ALSA always, no
matter what the chosen portaudio host is.
This commit is contained in:
Paul Licameli 2017-09-23 16:53:18 -04:00
parent a971dd5bb4
commit 857a7ca737
2 changed files with 57 additions and 10 deletions

View File

@ -2217,6 +2217,7 @@ bool AudioIO::StartPortMidiStream()
mMidiPaused = false;
mMidiLoopPasses = 0;
mMidiOutputComplete = false;
mMaxMidiTimestamp = 0;
PrepareMidiIterator();
// It is ok to call this now, but do not send timestamped midi
@ -2405,9 +2406,10 @@ void AudioIO::StopStream()
// respond to these messages. This is probably a bug in PortMidi
// if the All Off messages do not get out, but for security,
// delay a bit so that messages can be delivered before closing
// the stream. It should take about 16ms to send All Off messages,
// so this will add 24ms latency.
wxMilliSleep(40); // deliver the all-off messages
// the stream. Add 2ms of "padding" to avoid any rounding errors.
while (mMaxMidiTimestamp + 2 > MidiTime()) {
wxMilliSleep(1); // deliver the all-off messages
}
Pm_Close(mMidiStream);
mMidiStream = NULL;
mIterator->end();
@ -3873,11 +3875,12 @@ void AudioIO::OutputEvent()
if (time < 0 || mSendMidiState) time = 0;
PmTimestamp timestamp = (PmTimestamp) (time * 1000); /* s to ms */
// The special event gAllNotesOffEvent means "end of playback, send
// The special event gAllNotesOff means "end of playback, send
// all notes off on all channels"
if (mNextEvent == &gAllNotesOff) {
AllNotesOff();
if (mPlayMode == gAudioIO->PLAY_LOOPED) {
bool looping = (mPlayMode == gAudioIO->PLAY_LOOPED);
AllNotesOff(looping);
if (looping) {
// jump back to beginning of loop
++mMidiLoopPasses;
PrepareMidiIterator(false, MidiLoopOffset());
@ -3984,6 +3987,10 @@ void AudioIO::OutputEvent()
}
}
if (command != -1) {
// keep track of greatest timestamp used
if (timestamp > mMaxMidiTimestamp) {
mMaxMidiTimestamp = timestamp;
}
Pm_WriteShort(mMidiStream, timestamp,
Pm_Message((int) (command + channel),
(long) data1, (long) data2));
@ -4143,13 +4150,46 @@ PmTimestamp AudioIO::MidiTime()
)));
}
void AudioIO::AllNotesOff()
void AudioIO::AllNotesOff(bool looping)
{
#ifdef __WXGTK__
bool doDelay = !looping;
#else
bool doDelay = false;
WXUNUSED(looping);
#endif
// to keep track of when MIDI should all be delivered,
// update mMaxMidiTimestamp to now:
PmTimestamp now = MidiTime();
if (mMaxMidiTimestamp < now) {
mMaxMidiTimestamp = now;
}
#ifdef AUDIO_IO_GB_MIDI_WORKAROUND
// PRL:
// Send individual note-off messages for each note-on not yet paired.
// RBD:
// Even this did not work as planned. My guess is ALSA does not use
// a "stable sort" for timed messages, so that when a note-off is
// added later at the same time as a future note-on, the order is
// not respected, and the note-off can go first, leaving a stuck note.
// The workaround here is to use mMaxMidiTimestamp to ensure that
// note-offs come at least 1ms later than any previous message
// PRL:
// I think we should do that only when stopping or pausing, not when looping
// Note that on Linux, MIDI always uses ALSA, no matter whether portaudio
// uses some other host api.
mMaxMidiTimestamp += 1;
for (const auto &pair : mPendingNotesOff) {
Pm_WriteShort(mMidiStream, 0, Pm_Message(
Pm_WriteShort(mMidiStream,
(doDelay ? mMaxMidiTimestamp : 0),
Pm_Message(
0x90 + pair.first, pair.second, 0));
mMaxMidiTimestamp++; // allow 1ms per note-off
}
mPendingNotesOff.clear();
@ -4157,7 +4197,10 @@ void AudioIO::AllNotesOff()
#endif
for (int chan = 0; chan < 16; chan++) {
Pm_WriteShort(mMidiStream, 0, Pm_Message(0xB0 + chan, 0x7B, 0));
Pm_WriteShort(mMidiStream,
(doDelay ? mMaxMidiTimestamp : 0),
Pm_Message(0xB0 + chan, 0x7B, 0));
mMaxMidiTimestamp++; // allow 1ms per all-notes-off
}
}

View File

@ -460,7 +460,7 @@ private:
void GetNextEvent();
double AudioTime() { return mT0 + mNumFrames / mRate; }
double PauseTime();
void AllNotesOff();
void AllNotesOff(bool looping = false);
#endif
/** \brief Get the number of audio samples free in all of the playback
@ -573,6 +573,10 @@ private:
/// Used by Midi process to record that pause has begun,
/// so that AllNotesOff() is only delivered once
volatile bool mMidiPaused;
/// The largest timestamp written so far, used to delay
/// stream closing until last message has been delivered
PmTimestamp mMaxMidiTimestamp;
Alg_seq_ptr mSeq;
std::unique_ptr<Alg_iterator> mIterator;