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:
parent
85b2f80545
commit
4a0a7efd84
@ -214,7 +214,8 @@
|
|||||||
special "event" of sending all notes off. After that, we destroy
|
special "event" of sending all notes off. After that, we destroy
|
||||||
the iterator and use PrepareMidiIterator() to set up a NEW one.
|
the iterator and use PrepareMidiIterator() to set up a NEW one.
|
||||||
At each iteration, time must advance by (mT1 - mT0), so the
|
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
|
\todo run through all functions called from audio and portaudio threads
|
||||||
to verify they are thread-safe. Note that synchronization of the style:
|
to verify they are thread-safe. Note that synchronization of the style:
|
||||||
@ -2144,7 +2145,7 @@ bool AudioIO::StartPortMidiStream()
|
|||||||
if (mLastPmError == pmNoError) {
|
if (mLastPmError == pmNoError) {
|
||||||
mMidiStreamActive = true;
|
mMidiStreamActive = true;
|
||||||
mMidiPaused = false;
|
mMidiPaused = false;
|
||||||
mMidiLoopOffset = 0;
|
mMidiLoopPasses = 0;
|
||||||
mMidiOutputComplete = false;
|
mMidiOutputComplete = false;
|
||||||
PrepareMidiIterator();
|
PrepareMidiIterator();
|
||||||
|
|
||||||
@ -3804,6 +3805,19 @@ void AudioIO::SetListener(AudioIOListener* listener)
|
|||||||
static Alg_update gAllNotesOff; // special event for loop ending
|
static Alg_update gAllNotesOff; // special event for loop ending
|
||||||
// the fields of this event are never used, only the address is important
|
// 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()
|
void AudioIO::OutputEvent()
|
||||||
{
|
{
|
||||||
int channel = (mNextEvent->chan) & 0xF; // must be in [0..15]
|
int channel = (mNextEvent->chan) & 0xF; // must be in [0..15]
|
||||||
@ -3811,13 +3825,10 @@ void AudioIO::OutputEvent()
|
|||||||
int data1 = -1;
|
int data1 = -1;
|
||||||
int data2 = -1;
|
int data2 = -1;
|
||||||
|
|
||||||
double eventTime;
|
double eventTime = UncorrectedMidiEventTime();
|
||||||
if (mTimeTrack)
|
|
||||||
eventTime = mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime) + mT0;
|
|
||||||
else
|
|
||||||
eventTime = mNextEventTime;
|
|
||||||
// 0.0005 is for rounding
|
// 0.0005 is for rounding
|
||||||
double time = eventTime + PauseTime() + 0.0005 -
|
double time = eventTime + 0.0005 -
|
||||||
((mMidiLatency + mSynthLatency) * 0.001);
|
((mMidiLatency + mSynthLatency) * 0.001);
|
||||||
|
|
||||||
time += 1; // MidiTime() has a 1s offset
|
time += 1; // MidiTime() has a 1s offset
|
||||||
@ -3833,8 +3844,8 @@ void AudioIO::OutputEvent()
|
|||||||
AllNotesOff();
|
AllNotesOff();
|
||||||
if (mPlayMode == gAudioIO->PLAY_LOOPED) {
|
if (mPlayMode == gAudioIO->PLAY_LOOPED) {
|
||||||
// jump back to beginning of loop
|
// jump back to beginning of loop
|
||||||
mMidiLoopOffset += (mT1 - mT0);
|
++mMidiLoopPasses;
|
||||||
PrepareMidiIterator(false, mMidiLoopOffset);
|
PrepareMidiIterator(false, MidiLoopOffset());
|
||||||
} else {
|
} else {
|
||||||
mNextEvent = NULL;
|
mNextEvent = NULL;
|
||||||
}
|
}
|
||||||
@ -3958,18 +3969,19 @@ void AudioIO::GetNextEvent()
|
|||||||
mNextEvent = NULL;
|
mNextEvent = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto midiLoopOffset = MidiLoopOffset();
|
||||||
mNextEvent = mIterator->next(&mNextIsNoteOn,
|
mNextEvent = mIterator->next(&mNextIsNoteOn,
|
||||||
(void **) &mNextEventTrack,
|
(void **) &mNextEventTrack,
|
||||||
&nextOffset, mT1 + mMidiLoopOffset);
|
&nextOffset, mT1 + midiLoopOffset);
|
||||||
|
|
||||||
mNextEventTime = mT1 + mMidiLoopOffset + 1;
|
mNextEventTime = mT1 + midiLoopOffset + 1;
|
||||||
if (mNextEvent) {
|
if (mNextEvent) {
|
||||||
mNextEventTime = (mNextIsNoteOn ? mNextEvent->time :
|
mNextEventTime = (mNextIsNoteOn ? mNextEvent->time :
|
||||||
mNextEvent->get_end_time()) + nextOffset;;
|
mNextEvent->get_end_time()) + nextOffset;;
|
||||||
}
|
}
|
||||||
if (mNextEventTime > (mT1 + mMidiLoopOffset)){ // terminate playback at mT1
|
if (mNextEventTime > (mT1 + midiLoopOffset)){ // terminate playback at mT1
|
||||||
mNextEvent = &gAllNotesOff;
|
mNextEvent = &gAllNotesOff;
|
||||||
mNextEventTime = mT1 + mMidiLoopOffset - ALG_EPS;
|
mNextEventTime = mT1 + midiLoopOffset - ALG_EPS;
|
||||||
mNextIsNoteOn = true; // do not look at duration
|
mNextIsNoteOn = true; // do not look at duration
|
||||||
mIterator->end();
|
mIterator->end();
|
||||||
mIterator.reset(); // debugging aid
|
mIterator.reset(); // debugging aid
|
||||||
@ -4000,12 +4012,10 @@ void AudioIO::FillMidiBuffers()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SetHasSolo(hasSolo);
|
SetHasSolo(hasSolo);
|
||||||
// Compute the current track time differently depending upon
|
double time = AudioTime();
|
||||||
// whether audio playback is in effect:
|
|
||||||
double time = AudioTime() - PauseTime();
|
|
||||||
while (mNextEvent &&
|
while (mNextEvent &&
|
||||||
(mTimeTrack ? (mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime) + mT0) : mNextEventTime)
|
UncorrectedMidiEventTime() <
|
||||||
< time + ((MIDI_SLEEP + mSynthLatency) * 0.001)) {
|
time + ((MIDI_SLEEP + mSynthLatency) * 0.001)) {
|
||||||
OutputEvent();
|
OutputEvent();
|
||||||
GetNextEvent();
|
GetNextEvent();
|
||||||
}
|
}
|
||||||
|
@ -437,6 +437,11 @@ private:
|
|||||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
void PrepareMidiIterator(bool send = true, double offset = 0);
|
void PrepareMidiIterator(bool send = true, double offset = 0);
|
||||||
bool StartPortMidiStream();
|
bool StartPortMidiStream();
|
||||||
|
|
||||||
|
// Compute nondecreasing time stamps, accounting for pauses, but not the
|
||||||
|
// synth latency.
|
||||||
|
double UncorrectedMidiEventTime();
|
||||||
|
|
||||||
void OutputEvent();
|
void OutputEvent();
|
||||||
void FillMidiBuffers();
|
void FillMidiBuffers();
|
||||||
void GetNextEvent();
|
void GetNextEvent();
|
||||||
@ -542,7 +547,9 @@ private:
|
|||||||
/// How many frames of zeros were output due to pauses?
|
/// How many frames of zeros were output due to pauses?
|
||||||
volatile long mNumPauseFrames;
|
volatile long mNumPauseFrames;
|
||||||
/// total of backward jumps
|
/// total of backward jumps
|
||||||
volatile double mMidiLoopOffset;
|
volatile int mMidiLoopPasses;
|
||||||
|
inline double MidiLoopOffset() { return mMidiLoopPasses * (mT1 - mT0); }
|
||||||
|
|
||||||
volatile long mAudioFramesPerBuffer;
|
volatile long mAudioFramesPerBuffer;
|
||||||
/// 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
|
||||||
@ -613,12 +620,17 @@ private:
|
|||||||
double mT1;
|
double mT1;
|
||||||
/// Current time position during playback, in seconds. Between mT0 and mT1.
|
/// Current time position during playback, in seconds. Between mT0 and mT1.
|
||||||
double mTime;
|
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;
|
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.
|
/// Length in real seconds between mT0 and mT1. Always positive.
|
||||||
double mWarpedLength;
|
double mWarpedLength;
|
||||||
|
|
||||||
double mSeek;
|
double mSeek;
|
||||||
double mPlaybackRingBufferSecs;
|
double mPlaybackRingBufferSecs;
|
||||||
double mCaptureRingBufferSecs;
|
double mCaptureRingBufferSecs;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user