mirror of
https://github.com/cookiengineer/audacity
synced 2025-09-18 09:00:52 +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:
parent
a971dd5bb4
commit
857a7ca737
@ -2217,6 +2217,7 @@ bool AudioIO::StartPortMidiStream()
|
|||||||
mMidiPaused = false;
|
mMidiPaused = false;
|
||||||
mMidiLoopPasses = 0;
|
mMidiLoopPasses = 0;
|
||||||
mMidiOutputComplete = false;
|
mMidiOutputComplete = false;
|
||||||
|
mMaxMidiTimestamp = 0;
|
||||||
PrepareMidiIterator();
|
PrepareMidiIterator();
|
||||||
|
|
||||||
// It is ok to call this now, but do not send timestamped midi
|
// 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
|
// respond to these messages. This is probably a bug in PortMidi
|
||||||
// if the All Off messages do not get out, but for security,
|
// if the All Off messages do not get out, but for security,
|
||||||
// delay a bit so that messages can be delivered before closing
|
// delay a bit so that messages can be delivered before closing
|
||||||
// the stream. It should take about 16ms to send All Off messages,
|
// the stream. Add 2ms of "padding" to avoid any rounding errors.
|
||||||
// so this will add 24ms latency.
|
while (mMaxMidiTimestamp + 2 > MidiTime()) {
|
||||||
wxMilliSleep(40); // deliver the all-off messages
|
wxMilliSleep(1); // deliver the all-off messages
|
||||||
|
}
|
||||||
Pm_Close(mMidiStream);
|
Pm_Close(mMidiStream);
|
||||||
mMidiStream = NULL;
|
mMidiStream = NULL;
|
||||||
mIterator->end();
|
mIterator->end();
|
||||||
@ -3873,11 +3875,12 @@ void AudioIO::OutputEvent()
|
|||||||
if (time < 0 || mSendMidiState) time = 0;
|
if (time < 0 || mSendMidiState) time = 0;
|
||||||
PmTimestamp timestamp = (PmTimestamp) (time * 1000); /* s to ms */
|
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"
|
// all notes off on all channels"
|
||||||
if (mNextEvent == &gAllNotesOff) {
|
if (mNextEvent == &gAllNotesOff) {
|
||||||
AllNotesOff();
|
bool looping = (mPlayMode == gAudioIO->PLAY_LOOPED);
|
||||||
if (mPlayMode == gAudioIO->PLAY_LOOPED) {
|
AllNotesOff(looping);
|
||||||
|
if (looping) {
|
||||||
// jump back to beginning of loop
|
// jump back to beginning of loop
|
||||||
++mMidiLoopPasses;
|
++mMidiLoopPasses;
|
||||||
PrepareMidiIterator(false, MidiLoopOffset());
|
PrepareMidiIterator(false, MidiLoopOffset());
|
||||||
@ -3984,6 +3987,10 @@ void AudioIO::OutputEvent()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (command != -1) {
|
if (command != -1) {
|
||||||
|
// keep track of greatest timestamp used
|
||||||
|
if (timestamp > mMaxMidiTimestamp) {
|
||||||
|
mMaxMidiTimestamp = timestamp;
|
||||||
|
}
|
||||||
Pm_WriteShort(mMidiStream, timestamp,
|
Pm_WriteShort(mMidiStream, timestamp,
|
||||||
Pm_Message((int) (command + channel),
|
Pm_Message((int) (command + channel),
|
||||||
(long) data1, (long) data2));
|
(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
|
#ifdef AUDIO_IO_GB_MIDI_WORKAROUND
|
||||||
|
// PRL:
|
||||||
// Send individual note-off messages for each note-on not yet paired.
|
// 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) {
|
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));
|
0x90 + pair.first, pair.second, 0));
|
||||||
|
mMaxMidiTimestamp++; // allow 1ms per note-off
|
||||||
}
|
}
|
||||||
mPendingNotesOff.clear();
|
mPendingNotesOff.clear();
|
||||||
|
|
||||||
@ -4157,7 +4197,10 @@ void AudioIO::AllNotesOff()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (int chan = 0; chan < 16; chan++) {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +460,7 @@ private:
|
|||||||
void GetNextEvent();
|
void GetNextEvent();
|
||||||
double AudioTime() { return mT0 + mNumFrames / mRate; }
|
double AudioTime() { return mT0 + mNumFrames / mRate; }
|
||||||
double PauseTime();
|
double PauseTime();
|
||||||
void AllNotesOff();
|
void AllNotesOff(bool looping = false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** \brief Get the number of audio samples free in all of the playback
|
/** \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,
|
/// Used by Midi process to record that pause has begun,
|
||||||
/// so that AllNotesOff() is only delivered once
|
/// so that AllNotesOff() is only delivered once
|
||||||
volatile bool mMidiPaused;
|
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;
|
Alg_seq_ptr mSeq;
|
||||||
std::unique_ptr<Alg_iterator> mIterator;
|
std::unique_ptr<Alg_iterator> mIterator;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user