1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-06 14:52:34 +02:00

Group some of the fields of AudioIO into PlaybackSchedule

This commit is contained in:
Paul Licameli 2018-06-12 11:43:27 -04:00
parent adb33f466e
commit 46d7804cdf
2 changed files with 285 additions and 241 deletions

View File

@ -1206,7 +1206,7 @@ AudioIO::AudioIO()
mLastRecordingOffset = 0.0;
mNumCaptureChannels = 0;
mPaused = false;
mPlayMode = PLAY_STRAIGHT;
mPlaybackSchedule.ResetMode();
mListener = NULL;
mUpdateMeters = false;
@ -1923,16 +1923,16 @@ int AudioIO::StartStream(const TransportTracks &tracks,
// the existing audio. (Unless we figured out the inverse warp of the
// captured samples in real time.)
// So just quietly ignore the time track.
mTimeTrack = nullptr;
mPlaybackSchedule.mTimeTrack = nullptr;
}
else {
mTimeTrack = options.timeTrack;
mPlaybackSchedule.mTimeTrack = options.timeTrack;
}
// Clamp pre-roll so we don't play before time 0
const auto preRoll = std::max(0.0, std::min(t0, options.preRoll));
mT0 = t0 - preRoll;
mT1 = t1;
mPlaybackSchedule.mT0 = t0 - preRoll;
mPlaybackSchedule.mT1 = t1;
mRecordingSchedule = {};
mRecordingSchedule.mPreRoll = preRoll;
mRecordingSchedule.mLatencyCorrection =
@ -1943,13 +1943,13 @@ int AudioIO::StartStream(const TransportTracks &tracks,
if (tracks.captureTracks.size() > 0)
// adjust mT1 so that we don't give paComplete too soon to fill up the
// desired length of recording
mT1 -= mRecordingSchedule.mLatencyCorrection;
mPlaybackSchedule.mT1 -= mRecordingSchedule.mLatencyCorrection;
if (options.pCrossfadeData)
mRecordingSchedule.mCrossfadeData.swap( *options.pCrossfadeData );
mListener = options.listener;
mRate = sampleRate;
mTime = mT0;
mPlaybackSchedule.mTime = mPlaybackSchedule.mT0;
mSeek = 0;
mLastRecordingOffset = 0;
mCaptureTracks = tracks.captureTracks;
@ -1970,9 +1970,11 @@ int AudioIO::StartStream(const TransportTracks &tracks,
}
});
mPlayMode = options.playLooped ? PLAY_LOOPED : PLAY_STRAIGHT;
mCutPreviewGapStart = options.cutPreviewGapStart;
mCutPreviewGapLen = options.cutPreviewGapLen;
mPlaybackSchedule.mPlayMode = options.playLooped
? PlaybackSchedule::PLAY_LOOPED
: PlaybackSchedule::PLAY_STRAIGHT;
mPlaybackSchedule.mCutPreviewGapStart = options.cutPreviewGapStart;
mPlaybackSchedule.mCutPreviewGapLen = options.cutPreviewGapLen;
mPlaybackBuffers.reset();
mPlaybackMixers.reset();
@ -1995,41 +1997,27 @@ int AudioIO::StartStream(const TransportTracks &tracks,
const auto &scrubOptions = *options.pScrubbingOptions;
if (mCaptureTracks.size() > 0 ||
mPlayMode == PLAY_LOOPED ||
mTimeTrack != NULL ||
mPlaybackSchedule.Looping() ||
mPlaybackSchedule.mTimeTrack != NULL ||
scrubOptions.maxSpeed < ScrubbingOptions::MinAllowedScrubSpeed()) {
wxASSERT(false);
scrubbing = false;
}
else {
playbackTime = lrint(scrubOptions.delay * sampleRate) / sampleRate;
mPlayMode = (scrubOptions.isPlayingAtSpeed) ? PLAY_AT_SPEED : PLAY_SCRUB;
mPlaybackSchedule.mPlayMode = (scrubOptions.isPlayingAtSpeed) ? PlaybackSchedule::PLAY_AT_SPEED : PlaybackSchedule::PLAY_SCRUB;
}
}
#endif
// mWarpedTime and mWarpedLength are irrelevant when scrubbing,
// else they are used in updating mTime,
// and when not scrubbing or playing looped, mTime is also used
// in the test for termination of playback.
// with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy
// (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug
mWarpedTime = 0.0;
mPlaybackSchedule.mWarpedTime = 0.0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (scrubbing)
mWarpedLength = 0.0;
if (mPlaybackSchedule.Scrubbing())
mPlaybackSchedule.mWarpedLength = 0.0f;
else
#endif
{
if (mTimeTrack)
// Following gives negative when mT0 > mT1
mWarpedLength = mTimeTrack->ComputeWarpedLength(mT0, mT1);
else
mWarpedLength = mT1 - mT0;
// PRL allow backwards play
mWarpedLength = fabs(mWarpedLength);
}
mPlaybackSchedule.mWarpedLength =
mPlaybackSchedule.RealDuration(mPlaybackSchedule.mT1);
//
// The RingBuffer sizes, and the max amount of the buffer to
@ -2112,7 +2100,7 @@ int AudioIO::StartStream(const TransportTracks &tracks,
mStreamToken = 0;
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
mPlaybackSchedule.ResetMode();
return 0;
}
@ -2148,7 +2136,7 @@ int AudioIO::StartStream(const TransportTracks &tracks,
ScrubbingOptions::MaxAllowedScrubSpeed())
:
#endif
Mixer::WarpOptions(mTimeTrack);
Mixer::WarpOptions(mPlaybackSchedule.mTimeTrack);
for (unsigned int i = 0; i < mPlaybackTracks.size(); i++)
{
@ -2173,7 +2161,7 @@ int AudioIO::StartStream(const TransportTracks &tracks,
// Don't throw for read errors, just play silence:
false,
warpOptions,
mT0,
mPlaybackSchedule.mT0,
endTime,
1,
playbackMixBufferSize, false,
@ -2267,15 +2255,14 @@ int AudioIO::StartStream(const TransportTracks &tracks,
if (options.pStartTime)
{
// Calculate the NEW time position
mTime = std::max(mT0, std::min(mT1, *options.pStartTime));
mPlaybackSchedule.mTime =
std::max(mPlaybackSchedule.mT0,
std::min(mPlaybackSchedule.mT1, *options.pStartTime));
// Reset mixer positions for all playback tracks
unsigned numMixers = mPlaybackTracks.size();
for (unsigned ii = 0; ii < numMixers; ++ii)
mPlaybackMixers[ii]->Reposition(mTime);
if(mTimeTrack)
mWarpedTime = mTimeTrack->ComputeWarpedLength(mT0, mTime);
else
mWarpedTime = mTime - mT0;
mPlaybackMixers[ii]->Reposition(mPlaybackSchedule.mTime);
mPlaybackSchedule.RealTimeInit();
}
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
@ -2283,7 +2270,9 @@ int AudioIO::StartStream(const TransportTracks &tracks,
{
const auto &scrubOptions = *options.pScrubbingOptions;
mScrubQueue =
std::make_unique<ScrubQueue>(mT0, mT1, scrubOptions.startClockTimeMillis,
std::make_unique<ScrubQueue>(
mPlaybackSchedule.mT0, mPlaybackSchedule.mT1,
scrubOptions.startClockTimeMillis,
sampleRate, 2 * scrubOptions.minStutter,
scrubOptions);
mScrubDuration = 0;
@ -2410,7 +2399,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
mPlaybackSchedule.ResetMode();
}
#ifdef EXPERIMENTAL_MIDI_OUT
@ -2448,7 +2437,7 @@ void AudioIO::PrepareMidiIterator(bool send, double offset)
// Start MIDI from current cursor position
mSendMidiState = true;
while (mNextEvent &&
mNextEventTime < mT0 + offset) {
mNextEventTime < mPlaybackSchedule.mT0 + offset) {
if (send) OutputEvent();
GetNextEvent();
}
@ -2834,7 +2823,7 @@ void AudioIO::StopStream()
}
// Don't cause a busy wait in the audio thread after stopping scrubbing
mPlayMode = PLAY_STRAIGHT;
mPlaybackSchedule.ResetMode();
}
void AudioIO::SetPaused(bool state)
@ -2916,16 +2905,16 @@ bool AudioIO::IsMonitoring()
return ( mPortStreamV19 && mStreamToken==0 );
}
double AudioIO::LimitStreamTime(double absoluteTime) const
double AudioIO::PlaybackSchedule::LimitStreamTime() const
{
// Allows for forward or backward play
if (ReversedTime())
return std::max(mT1, std::min(mT0, absoluteTime));
return std::max(mT1, std::min(mT0, mTime));
else
return std::max(mT0, std::min(mT1, absoluteTime));
return std::max(mT0, std::min(mT1, mTime));
}
double AudioIO::NormalizeStreamTime(double absoluteTime) const
double AudioIO::PlaybackSchedule::NormalizeStreamTime() const
{
// dmazzoni: This function is needed for two reasons:
// One is for looped-play mode - this function makes sure that the
@ -2939,12 +2928,14 @@ double AudioIO::NormalizeStreamTime(double absoluteTime) const
// mode. In this case, we should jump over a defined "gap" in the
// audio.
auto absoluteTime = mTime;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// Limit the time between t0 and t1 if not scrubbing.
// Should the limiting be necessary in any play mode if there are no bugs?
if( (mPlayMode != PLAY_SCRUB) && (mPlayMode != PLAY_AT_SPEED))
if (!Interactive())
#endif
absoluteTime = LimitStreamTime(absoluteTime);
absoluteTime = LimitStreamTime();
if (mCutPreviewGapLen > 0)
{
@ -2962,7 +2953,7 @@ double AudioIO::GetStreamTime()
if( !IsStreamActive() )
return BAD_STREAM_TIME;
return NormalizeStreamTime(mTime);
return mPlaybackSchedule.NormalizeStreamTime();
}
@ -3257,12 +3248,7 @@ AudioThread::ExitCode AudioThread::Entry()
}
gAudioIO->mAudioThreadFillBuffersLoopActive = false;
if (gAudioIO->mPlayMode == AudioIO::PLAY_SCRUB) {
// Rely on the Wait() in ScrubQueue::Transformer()
// This allows the scrubbing update interval to be made very short without
// playback becoming intermittent.
}
else if (gAudioIO->mPlayMode == AudioIO::PLAY_AT_SPEED) {
if (gAudioIO->mPlaybackSchedule.Interactive()) {
// Rely on the Wait() in ScrubQueue::Transformer()
// This allows the scrubbing update interval to be made very short without
// playback becoming intermittent.
@ -3833,10 +3819,11 @@ void AudioIO::FillBuffers()
// The exception is if we're at the end of the selected
// region - then we should just fill the buffer.
//
const auto realTimeRemaining = mPlaybackSchedule.RealTimeRemaining();
if (nAvailable >= (int)mPlaybackSamplesToCopy ||
(mPlayMode == PLAY_STRAIGHT &&
(mPlaybackSchedule.PlayingStraight() &&
nAvailable > 0 &&
mWarpedTime+(nAvailable/mRate) >= mWarpedLength))
nAvailable / mRate >= realTimeRemaining))
{
// Limit maximum buffer size (increases performance)
auto available =
@ -3855,25 +3842,25 @@ void AudioIO::FillBuffers()
auto frames = available;
bool progress = true;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if ((mPlayMode == PLAY_SCRUB) || (mPlayMode == PLAY_AT_SPEED))
if (mPlaybackSchedule.Interactive())
// scrubbing does not use warped time and length
frames = limitSampleBufferSize(frames, mScrubDuration);
else
#endif
{
double deltat = frames / mRate;
if (mWarpedTime + deltat > mWarpedLength)
if (deltat > realTimeRemaining)
{
frames = (mWarpedLength - mWarpedTime) * mRate;
frames = realTimeRemaining * mRate;
// Don't fall into an infinite loop, if loop-playing a selection
// that is so short, it has no samples: detect that case
progress =
!(mPlayMode == PLAY_LOOPED &&
mWarpedTime == 0.0 && frames == 0);
mWarpedTime = mWarpedLength;
!(mPlaybackSchedule.Looping() &&
mPlaybackSchedule.mWarpedTime == 0.0 && frames == 0);
mPlaybackSchedule.RealTimeAdvance( realTimeRemaining );
}
else
mWarpedTime += deltat;
mPlaybackSchedule.RealTimeAdvance( deltat );
}
if (!progress)
@ -3892,8 +3879,8 @@ void AudioIO::FillBuffers()
// don't generate either if scrubbing at zero speed.
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
const bool silent = ((mPlayMode == PLAY_SCRUB)||
(mPlayMode == PLAY_AT_SPEED)) && mSilentScrub;
const bool silent =
mPlaybackSchedule.Interactive() && mSilentScrub;
#else
const bool silent = false;
#endif
@ -3917,7 +3904,7 @@ void AudioIO::FillBuffers()
// If scrubbing, we may be producing some silence. Otherwise this should not happen,
// but makes sure anyway that we produce equal
// numbers of samples for all channels for this pass of the do-loop.
if(processed < frames && mPlayMode != PLAY_STRAIGHT)
if(processed < frames && !mPlaybackSchedule.PlayingStraight())
{
mSilentBuf.Resize(frames, floatSample);
ClearSamples(mSilentBuf.ptr(), floatSample, 0, frames);
@ -3932,11 +3919,11 @@ void AudioIO::FillBuffers()
available -= frames;
wxASSERT(available >= 0);
switch (mPlayMode)
switch (mPlaybackSchedule.mPlayMode)
{
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
case PLAY_SCRUB:
case PLAY_AT_SPEED:
case PlaybackSchedule::PLAY_SCRUB:
case PlaybackSchedule::PLAY_AT_SPEED:
{
mScrubDuration -= frames;
wxASSERT(mScrubDuration >= 0);
@ -3970,16 +3957,16 @@ void AudioIO::FillBuffers()
}
break;
#endif
case PLAY_LOOPED:
case PlaybackSchedule::PLAY_LOOPED:
{
done = !progress || (available == 0);
// msmeyer: If playing looped, check if we are at the end of the buffer
// and if yes, restart from the beginning.
if (mWarpedTime >= mWarpedLength)
if (mPlaybackSchedule.RealTimeRemaining() <= 0)
{
for (i = 0; i < mPlaybackTracks.size(); i++)
mPlaybackMixers[i]->Restart();
mWarpedTime = 0.0;
mPlaybackSchedule.RealTimeRestart();
}
}
break;
@ -4187,10 +4174,11 @@ static Alg_update gAllNotesOff; // special event for loop ending
double AudioIO::UncorrectedMidiEventTime()
{
double time;
if (mTimeTrack)
if (mPlaybackSchedule.mTimeTrack)
time =
mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime - MidiLoopOffset())
+ mT0 + (mMidiLoopPasses * mWarpedLength);
mPlaybackSchedule.RealDuration(mNextEventTime - MidiLoopOffset())
+ mPlaybackSchedule.mT0 + (mMidiLoopPasses *
mPlaybackSchedule.mWarpedLength);
else
time = mNextEventTime;
@ -4220,7 +4208,7 @@ void AudioIO::OutputEvent()
// The special event gAllNotesOff means "end of playback, send
// all notes off on all channels"
if (mNextEvent == &gAllNotesOff) {
bool looping = (mPlayMode == PLAY_LOOPED);
bool looping = mPlaybackSchedule.Looping();
AllNotesOff(looping);
if (looping) {
// jump back to beginning of loop
@ -4355,17 +4343,17 @@ void AudioIO::GetNextEvent()
}
auto midiLoopOffset = MidiLoopOffset();
mNextEvent = mIterator->next(&mNextIsNoteOn,
(void **) &mNextEventTrack,
&nextOffset, mT1 + midiLoopOffset);
(void **) &mNextEventTrack,
&nextOffset, mPlaybackSchedule.mT1 + midiLoopOffset);
mNextEventTime = mT1 + midiLoopOffset + 1;
mNextEventTime = mPlaybackSchedule.mT1 + midiLoopOffset + 1;
if (mNextEvent) {
mNextEventTime = (mNextIsNoteOn ? mNextEvent->time :
mNextEvent->get_end_time()) + nextOffset;;
}
if (mNextEventTime > (mT1 + midiLoopOffset)){ // terminate playback at mT1
if (mNextEventTime > (mPlaybackSchedule.mT1 + midiLoopOffset)){ // terminate playback at mT1
mNextEvent = &gAllNotesOff;
mNextEventTime = mT1 + midiLoopOffset - ALG_EPS;
mNextEventTime = mPlaybackSchedule.mT1 + midiLoopOffset - ALG_EPS;
mNextIsNoteOn = true; // do not look at duration
mIterator->end();
mIterator.reset(); // debugging aid
@ -4446,15 +4434,11 @@ void AudioIO::FillMidiBuffers()
// position at mT1 before shutting down the stream.
const double loopDelay = 0.220;
double timeAtSpeed;
if (mTimeTrack)
timeAtSpeed = mTimeTrack->SolveWarpedLength(mT0, realTime);
else
timeAtSpeed = realTime;
auto timeAtSpeed = mPlaybackSchedule.TrackDuration(realTime);
mMidiOutputComplete =
(mPlayMode == PLAY_STRAIGHT && // PRL: what if scrubbing?
timeAtSpeed >= mT1 + loopDelay);
(mPlaybackSchedule.PlayingStraight() && // PRL: what if scrubbing?
timeAtSpeed >= mPlaybackSchedule.mT1 + loopDelay);
// !mNextEvent);
}
@ -4765,7 +4749,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
#ifdef EXPERIMENTAL_MIDI_OUT
if (mCallbackCount++ == 0) {
// This is effectively mSystemMinusAudioTime when the buffer is empty:
mStartTime = SystemTime(mUsingAlsa) - mT0;
mStartTime = SystemTime(mUsingAlsa) - mPlaybackSchedule.mT0;
// later, mStartTime - mSystemMinusAudioTime will tell us latency
}
@ -4952,9 +4936,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// While scrubbing, ignore seek requests
if (mSeek && mPlayMode == AudioIO::PLAY_SCRUB)
mSeek = 0.0;
else if (mSeek && mPlayMode == AudioIO::PLAY_AT_SPEED)
if (mSeek && mPlaybackSchedule.Interactive())
mSeek = 0.0;
else
#endif
@ -4974,24 +4956,17 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
}
// Calculate the NEW time position
mTime += mSeek;
mTime = LimitStreamTime(mTime);
mPlaybackSchedule.mTime += mSeek;
mPlaybackSchedule.mTime = mPlaybackSchedule.LimitStreamTime();
mSeek = 0.0;
// Reset mixer positions and flush buffers for all tracks
if(mTimeTrack)
// Following gives negative when mT0 > mTime
mWarpedTime =
mTimeTrack->ComputeWarpedLength
(mT0, mTime);
else
mWarpedTime = mTime - mT0;
mWarpedTime = std::abs(mWarpedTime);
mPlaybackSchedule.RealTimeInit();
// Reset mixer positions and flush buffers for all tracks
for (i = 0; i < numPlaybackTracks; i++)
{
mPlaybackMixers[i]->Reposition(mTime);
mPlaybackMixers[i]->Reposition( mPlaybackSchedule.mTime );
const auto toDiscard =
mPlaybackBuffers[i]->AvailForGet();
const auto discarded =
@ -5135,37 +5110,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
group++;
bool bDone = true;
// If the time indicator is past
// the end, then we may have finished playing the entire
// selection.
if (bDone)
bDone = bDone && (ReversedTime()
? mTime <= mT1
: mTime >= mT1);
// We never finish if we are playing looped or or scrubbing.
if (bDone) {
// playing straight we must have no more audio.
if (mPlayMode == AudioIO::PLAY_STRAIGHT)
bDone = (len == 0);
// playing at speed, it is OK to have some audio left over.
else if (mPlayMode == AudioIO::PLAY_AT_SPEED)
bDone = true;
else
bDone = false;
}
if (bDone){
// PRL: singalling MIDI output complete is necessary if
// not USE_MIDI_THREAD, otherwise it's harmlessly redundant
#ifdef EXPERIMENTAL_MIDI_OUT
mMidiOutputComplete = true,
#endif
callbackReturn = paComplete;
}
CallbackCheckCompletion(callbackReturn, len);
if (cut) // no samples to process, they've been discarded
continue;
@ -5217,30 +5162,15 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
// Poke: If there are no playback tracks, then the earlier check
// about the time indicator being passed the end won't happen;
// do it here instead (but not if looping or scrubbing)
if (numPlaybackTracks == 0 &&
mPlayMode == AudioIO::PLAY_STRAIGHT)
{
if ((ReversedTime()
? mTime <= mT1
: mTime >= mT1)) {
// PRL: singalling MIDI output complete is necessary if
// not USE_MIDI_THREAD, otherwise it's harmlessly redundant
#ifdef EXPERIMENTAL_MIDI_OUT
mMidiOutputComplete = true,
#endif
callbackReturn = paComplete;
}
}
if (numPlaybackTracks == 0)
CallbackCheckCompletion(callbackReturn, 0);
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// Update the current time position, for scrubbing
// "Consume" only as much as the ring buffers produced, which may
// be less than framesPerBuffer (during "stutter")
if (mPlayMode == AudioIO::PLAY_SCRUB)
mTime = mScrubQueue->Consumer(maxLen);
else if (mPlayMode == AudioIO::PLAY_AT_SPEED)
mTime = mScrubQueue->Consumer(maxLen);
if (mPlaybackSchedule.Interactive())
mPlaybackSchedule.mTime = mScrubQueue->Consumer( maxLen );
#endif
em.RealtimeProcessEnd();
@ -5281,7 +5211,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
{
// If there are no playback tracks, and we are recording, then the
// earlier checks for being passed the end won't happen, so do it here.
if (mTime >= mT1) {
if (mPlaybackSchedule.PassIsComplete()) {
callbackReturn = paComplete;
}
@ -5314,8 +5244,8 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
len < framesPerBuffer) ) {
// Assume that any good partial buffer should be written leftmost
// and zeroes will be padded after; label the zeroes.
auto start = mTime + len / mRate +
mRecordingSchedule.mLatencyCorrection;
auto start = mPlaybackSchedule.mTime +
len / mRate + mRecordingSchedule.mLatencyCorrection;
auto duration = (framesPerBuffer - len) / mRate;
auto interval = std::make_pair( start, duration );
mLostCaptureIntervals.push_back( interval );
@ -5376,35 +5306,8 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
// Update the current time position if not scrubbing
// (Already did it above, for scrubbing)
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if( (mPlayMode != AudioIO::PLAY_SCRUB) &&
(mPlayMode != AudioIO::PLAY_AT_SPEED) )
#endif
{
double delta = framesPerBuffer / mRate;
if (ReversedTime())
delta *= -1.0;
if (mTimeTrack)
// MB: this is why SolveWarpedLength is needed :)
mTime =
mTimeTrack->SolveWarpedLength(mTime, delta);
else
mTime += delta;
}
// Wrap to start if looping
if (mPlayMode == AudioIO::PLAY_LOOPED)
{
while (ReversedTime()
? mTime <= mT1
: mTime >= mT1)
{
// LL: This is not exactly right, but I'm at my wits end trying to
// figure it out. Feel free to fix it. :-)
// MB: it's much easier than you think, mTime isn't warped at all!
mTime -= mT1 - mT0;
}
}
mPlaybackSchedule.TrackTimeUpdate( framesPerBuffer / mRate );
// Record the reported latency from PortAudio.
// TODO: Don't recalculate this with every callback?
@ -5497,6 +5400,96 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
return callbackReturn;
}
void AudioIO::CallbackCheckCompletion(
int &callbackReturn, unsigned long len)
{
bool done = mPlaybackSchedule.PassIsComplete();
if (done)
done = (
mPlaybackSchedule.PlayingAtSpeed()
// some leftover length allowed in this case
|| (mPlaybackSchedule.PlayingStraight() && len == 0)
);
if(done) {
// PRL: singalling MIDI output complete is necessary if
// not USE_MIDI_THREAD, otherwise it's harmlessly redundant
#ifdef EXPERIMENTAL_MIDI_OUT
mMidiOutputComplete = true,
#endif
callbackReturn = paComplete;
}
}
bool AudioIO::PlaybackSchedule::PassIsComplete() const
{
if (Scrubbing())
return false; // but may be true if playing at speed
return (ReversedTime() ? mTime <= mT1 : mTime >= mT1);
}
void AudioIO::PlaybackSchedule::TrackTimeUpdate(double realElapsed)
{
if (!Interactive()) {
if (ReversedTime())
realElapsed *= -1.0;
if (mTimeTrack)
mTime = mTimeTrack->SolveWarpedLength(mTime, realElapsed);
else
mTime += realElapsed;
// Wrap to start if looping
if (Looping()) {
while (PassIsComplete()) {
// LL: This is not exactly right, but I'm at my wits end trying to
// figure it out. Feel free to fix it. :-)
// MB: it's much easier than you think, mTime isn't warped at all!
mTime -= mT1 - mT0;
}
}
}
}
double AudioIO::PlaybackSchedule::TrackDuration(double realElapsed) const
{
if (mTimeTrack)
return mTimeTrack->SolveWarpedLength(mT0, realElapsed);
else
return realElapsed;
}
double AudioIO::PlaybackSchedule::RealDuration(double trackTime1) const
{
double duration;
if (mTimeTrack)
duration = mTimeTrack->ComputeWarpedLength(mT0, trackTime1);
else
duration = trackTime1 - mT0;
return fabs(duration);
}
double AudioIO::PlaybackSchedule::RealTimeRemaining() const
{
return mWarpedLength - mWarpedTime;
}
void AudioIO::PlaybackSchedule::RealTimeAdvance( double increment )
{
mWarpedLength += increment;
}
void AudioIO::PlaybackSchedule::RealTimeInit()
{
if (Scrubbing())
mWarpedTime = 0.0;
else
mWarpedTime = RealDuration(mTime);
}
void AudioIO::PlaybackSchedule::RealTimeRestart()
{
mWarpedTime = 0;
}
double AudioIO::RecordingSchedule::ToConsume() const
{
return mDuration - Consumed();
@ -5515,5 +5508,6 @@ double AudioIO::RecordingSchedule::ToDiscard() const
bool AudioIO::IsCapturing() const
{
return GetNumCaptureChannels() > 0 &&
mTime >= mT0 + mRecordingSchedule.mPreRoll;
mPlaybackSchedule.mTime >=
mPlaybackSchedule.mT0 + mRecordingSchedule.mPreRoll;
}

View File

@ -210,6 +210,9 @@ class AUDACITY_DLL_API AudioIO final {
const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData);
void CallbackCheckCompletion(
int &callbackReturn, unsigned long len);
AudioIOListener* GetListener() { return mListener; }
void SetListener(AudioIOListener* listener);
@ -519,7 +522,7 @@ private:
void OutputEvent();
void FillMidiBuffers();
void GetNextEvent();
double AudioTime() { return mT0 + mNumFrames / mRate; }
double AudioTime() { return mPlaybackSchedule.mT0 + mNumFrames / mRate; }
double PauseTime();
void AllNotesOff(bool looping = false);
#endif
@ -572,27 +575,6 @@ private:
/** \brief How many sample rates to try */
static const int NumRatesToTry;
/** \brief True if the end time is before the start time */
bool ReversedTime() const
{
return mT1 < mT0;
}
/** \brief Clamps the given time to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
* @param absoluteTime A time in seconds, usually mTime
*/
double LimitStreamTime(double absoluteTime) const;
/** \brief Normalizes the given time, clamping it and handling gaps from cut preview.
*
* Clamps the time (unless scrubbing), and skips over the cut section.
* Returns a time in seconds.
* @param absoluteTime A time in seconds, usually mTime
*/
double NormalizeStreamTime(double absoluteTime) const;
/** \brief Clean up after StartStream if it fails.
*
* If bOnlyBuffers is specified, it only cleans up the buffers. */
@ -619,7 +601,9 @@ private:
volatile long mNumPauseFrames;
/// total of backward jumps
volatile int mMidiLoopPasses;
inline double MidiLoopOffset() { return mMidiLoopPasses * (mT1 - mT0); }
inline double MidiLoopOffset() {
return mMidiLoopPasses * (mPlaybackSchedule.mT1 - mPlaybackSchedule.mT0);
}
volatile long mAudioFramesPerBuffer;
/// Used by Midi process to record that pause has begun,
@ -711,22 +695,6 @@ private:
double mFactor;
/// Audio playback rate in samples per second
double mRate;
/// Playback starts at offset of mT0, which is measured in seconds.
double mT0;
/// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing.
double mT1;
/// Current time position during playback, in seconds. Between mT0 and mT1.
double 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;
/// 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;
@ -780,17 +748,6 @@ private:
bool mInputMixerWorks;
float mMixerOutputVol;
volatile enum {
PLAY_STRAIGHT,
PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
PLAY_SCRUB,
PLAY_AT_SPEED, // a version of PLAY_SCRUB.
#endif
} mPlayMode;
double mCutPreviewGapStart;
double mCutPreviewGapLen;
GrowableSampleBuffer mSilentBuf;
AudioIOListener* mListener;
@ -802,8 +759,6 @@ private:
friend void InitAudioIO();
const TimeTrack *mTimeTrack;
bool mUsingAlsa { false };
// For cacheing supported sample rates
@ -850,6 +805,101 @@ public:
// detect more dropouts
bool mDetectUpstreamDropouts{ true };
struct PlaybackSchedule {
/// Playback starts at offset of mT0, which is measured in seconds.
double mT0;
/// Playback ends at offset of mT1, which is measured in seconds. Note that mT1 may be less than mT0 during scrubbing.
double mT1;
/// Current time position during playback, in seconds. Between mT0 and mT1.
double 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;
/// 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;
// mWarpedTime and mWarpedLength are irrelevant when scrubbing,
// else they are used in updating mTime,
// and when not scrubbing or playing looped, mTime is also used
// in the test for termination of playback.
// with ComputeWarpedLength, it is now possible the calculate the warped length with 100% accuracy
// (ignoring accumulated rounding errors during playback) which fixes the 'missing sound at the end' bug
const TimeTrack *mTimeTrack;
volatile enum {
PLAY_STRAIGHT,
PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
PLAY_SCRUB,
PLAY_AT_SPEED, // a version of PLAY_SCRUB.
#endif
} mPlayMode;
double mCutPreviewGapStart;
double mCutPreviewGapLen;
/** \brief True if the end time is before the start time */
bool ReversedTime() const
{
return mT1 < mT0;
}
/** \brief Clamps mTime to be between mT0 and mT1
*
* Returns the bound if the value is out of bounds; does not wrap.
* Returns a time in seconds.
*/
double LimitStreamTime() const;
/** \brief Normalizes mTime, clamping it and handling gaps from cut preview.
*
* Clamps the time (unless scrubbing), and skips over the cut section.
* Returns a time in seconds.
*/
double NormalizeStreamTime() const;
void ResetMode() { mPlayMode = PLAY_STRAIGHT; }
bool PlayingStraight() const { return mPlayMode == PLAY_STRAIGHT; }
bool Looping() const { return mPlayMode == PLAY_LOOPED; }
bool Scrubbing() const { return mPlayMode == PLAY_SCRUB; }
bool PlayingAtSpeed() const { return mPlayMode == PLAY_AT_SPEED; }
bool Interactive() const { return Scrubbing() || PlayingAtSpeed(); }
// Returns true if a loop pass, or the sole pass of straight play,
// is completed at the current value of mTime
bool PassIsComplete() const;
void TrackTimeUpdate(double realElapsed);
// Convert a nonnegative real duration to an increment of track time
// relative to mT0.
double TrackDuration(double realElapsed) const;
// Convert time between mT0 and argument to real duration, according to
// time track if one is given; result is always nonnegative
double RealDuration(double trackTime1) const;
// How much real ("warped") time left?
double RealTimeRemaining() const;
// Advance the real time position
void RealTimeAdvance( double increment );
// Determine starting duration within the first pass -- sometimes not
// zero
void RealTimeInit();
void RealTimeRestart();
} mPlaybackSchedule;
private:
struct RecordingSchedule {
double mPreRoll{};