1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-16 08:34:10 +02:00

Fix MIDI timestamp calculations when you loop AND have time track...

... At least it fixes the gross problem; but there seems to be a small
accumulation of error still each time around the loop, that I don't understand
yet.
This commit is contained in:
Paul Licameli 2017-08-24 19:44:35 -04:00
parent 85b2f80545
commit 4a0a7efd84
2 changed files with 45 additions and 23 deletions

View File

@ -214,7 +214,8 @@
special "event" of sending all notes off. After that, we destroy
the iterator and use PrepareMidiIterator() to set up a NEW one.
At each iteration, time must advance by (mT1 - mT0), so the
accumulated time is held in mMidiLoopOffset.
accumulated complete loop time (in "unwarped," track time) is computed
by MidiLoopOffset().
\todo run through all functions called from audio and portaudio threads
to verify they are thread-safe. Note that synchronization of the style:
@ -2144,7 +2145,7 @@ bool AudioIO::StartPortMidiStream()
if (mLastPmError == pmNoError) {
mMidiStreamActive = true;
mMidiPaused = false;
mMidiLoopOffset = 0;
mMidiLoopPasses = 0;
mMidiOutputComplete = false;
PrepareMidiIterator();
@ -3804,6 +3805,19 @@ void AudioIO::SetListener(AudioIOListener* listener)
static Alg_update gAllNotesOff; // special event for loop ending
// the fields of this event are never used, only the address is important
double AudioIO::UncorrectedMidiEventTime()
{
double time;
if (mTimeTrack)
time =
mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime - MidiLoopOffset())
+ mT0 + (mMidiLoopPasses * mWarpedLength);
else
time = mNextEventTime;
return time + PauseTime();
}
void AudioIO::OutputEvent()
{
int channel = (mNextEvent->chan) & 0xF; // must be in [0..15]
@ -3811,13 +3825,10 @@ void AudioIO::OutputEvent()
int data1 = -1;
int data2 = -1;
double eventTime;
if (mTimeTrack)
eventTime = mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime) + mT0;
else
eventTime = mNextEventTime;
double eventTime = UncorrectedMidiEventTime();
// 0.0005 is for rounding
double time = eventTime + PauseTime() + 0.0005 -
double time = eventTime + 0.0005 -
((mMidiLatency + mSynthLatency) * 0.001);
time += 1; // MidiTime() has a 1s offset
@ -3833,8 +3844,8 @@ void AudioIO::OutputEvent()
AllNotesOff();
if (mPlayMode == gAudioIO->PLAY_LOOPED) {
// jump back to beginning of loop
mMidiLoopOffset += (mT1 - mT0);
PrepareMidiIterator(false, mMidiLoopOffset);
++mMidiLoopPasses;
PrepareMidiIterator(false, MidiLoopOffset());
} else {
mNextEvent = NULL;
}
@ -3958,18 +3969,19 @@ void AudioIO::GetNextEvent()
mNextEvent = NULL;
return;
}
auto midiLoopOffset = MidiLoopOffset();
mNextEvent = mIterator->next(&mNextIsNoteOn,
(void **) &mNextEventTrack,
&nextOffset, mT1 + mMidiLoopOffset);
&nextOffset, mT1 + midiLoopOffset);
mNextEventTime = mT1 + mMidiLoopOffset + 1;
mNextEventTime = mT1 + midiLoopOffset + 1;
if (mNextEvent) {
mNextEventTime = (mNextIsNoteOn ? mNextEvent->time :
mNextEvent->get_end_time()) + nextOffset;;
}
if (mNextEventTime > (mT1 + mMidiLoopOffset)){ // terminate playback at mT1
if (mNextEventTime > (mT1 + midiLoopOffset)){ // terminate playback at mT1
mNextEvent = &gAllNotesOff;
mNextEventTime = mT1 + mMidiLoopOffset - ALG_EPS;
mNextEventTime = mT1 + midiLoopOffset - ALG_EPS;
mNextIsNoteOn = true; // do not look at duration
mIterator->end();
mIterator.reset(); // debugging aid
@ -4000,12 +4012,10 @@ void AudioIO::FillMidiBuffers()
break;
}
SetHasSolo(hasSolo);
// Compute the current track time differently depending upon
// whether audio playback is in effect:
double time = AudioTime() - PauseTime();
double time = AudioTime();
while (mNextEvent &&
(mTimeTrack ? (mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime) + mT0) : mNextEventTime)
< time + ((MIDI_SLEEP + mSynthLatency) * 0.001)) {
UncorrectedMidiEventTime() <
time + ((MIDI_SLEEP + mSynthLatency) * 0.001)) {
OutputEvent();
GetNextEvent();
}

View File

@ -437,6 +437,11 @@ private:
#ifdef EXPERIMENTAL_MIDI_OUT
void PrepareMidiIterator(bool send = true, double offset = 0);
bool StartPortMidiStream();
// Compute nondecreasing time stamps, accounting for pauses, but not the
// synth latency.
double UncorrectedMidiEventTime();
void OutputEvent();
void FillMidiBuffers();
void GetNextEvent();
@ -542,7 +547,9 @@ private:
/// How many frames of zeros were output due to pauses?
volatile long mNumPauseFrames;
/// total of backward jumps
volatile double mMidiLoopOffset;
volatile int mMidiLoopPasses;
inline double MidiLoopOffset() { return mMidiLoopPasses * (mT1 - mT0); }
volatile long mAudioFramesPerBuffer;
/// Used by Midi process to record that pause has begun,
/// so that AllNotesOff() is only delivered once
@ -613,12 +620,17 @@ private:
double mT1;
/// Current time position during playback, in seconds. Between mT0 and mT1.
double mTime;
/// Current time after warping, starting at zero (unlike mTime).
/// Length in real seconds between mT0 and mTime.
/// Accumulated real time (not track position), starting at zero (unlike
/// mTime), and wrapping back to zero each time around looping play.
/// Thus, it is the length in real seconds between mT0 and mTime.
double mWarpedTime;
/// Total length after warping via a time track.
/// Real length to be played (if looping, for each pass) after warping via a
/// time track, computed just once when starting the stream.
/// Length in real seconds between mT0 and mT1. Always positive.
double mWarpedLength;
double mSeek;
double mPlaybackRingBufferSecs;
double mCaptureRingBufferSecs;