mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-06 23:02:42 +02:00
Group some of the fields of AudioIO into PlaybackSchedule
This commit is contained in:
parent
adb33f466e
commit
46d7804cdf
370
src/AudioIO.cpp
370
src/AudioIO.cpp
@ -1206,7 +1206,7 @@ AudioIO::AudioIO()
|
|||||||
mLastRecordingOffset = 0.0;
|
mLastRecordingOffset = 0.0;
|
||||||
mNumCaptureChannels = 0;
|
mNumCaptureChannels = 0;
|
||||||
mPaused = false;
|
mPaused = false;
|
||||||
mPlayMode = PLAY_STRAIGHT;
|
mPlaybackSchedule.ResetMode();
|
||||||
|
|
||||||
mListener = NULL;
|
mListener = NULL;
|
||||||
mUpdateMeters = false;
|
mUpdateMeters = false;
|
||||||
@ -1923,16 +1923,16 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
// the existing audio. (Unless we figured out the inverse warp of the
|
// the existing audio. (Unless we figured out the inverse warp of the
|
||||||
// captured samples in real time.)
|
// captured samples in real time.)
|
||||||
// So just quietly ignore the time track.
|
// So just quietly ignore the time track.
|
||||||
mTimeTrack = nullptr;
|
mPlaybackSchedule.mTimeTrack = nullptr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mTimeTrack = options.timeTrack;
|
mPlaybackSchedule.mTimeTrack = options.timeTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clamp pre-roll so we don't play before time 0
|
// Clamp pre-roll so we don't play before time 0
|
||||||
const auto preRoll = std::max(0.0, std::min(t0, options.preRoll));
|
const auto preRoll = std::max(0.0, std::min(t0, options.preRoll));
|
||||||
mT0 = t0 - preRoll;
|
mPlaybackSchedule.mT0 = t0 - preRoll;
|
||||||
mT1 = t1;
|
mPlaybackSchedule.mT1 = t1;
|
||||||
mRecordingSchedule = {};
|
mRecordingSchedule = {};
|
||||||
mRecordingSchedule.mPreRoll = preRoll;
|
mRecordingSchedule.mPreRoll = preRoll;
|
||||||
mRecordingSchedule.mLatencyCorrection =
|
mRecordingSchedule.mLatencyCorrection =
|
||||||
@ -1943,13 +1943,13 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
if (tracks.captureTracks.size() > 0)
|
if (tracks.captureTracks.size() > 0)
|
||||||
// adjust mT1 so that we don't give paComplete too soon to fill up the
|
// adjust mT1 so that we don't give paComplete too soon to fill up the
|
||||||
// desired length of recording
|
// desired length of recording
|
||||||
mT1 -= mRecordingSchedule.mLatencyCorrection;
|
mPlaybackSchedule.mT1 -= mRecordingSchedule.mLatencyCorrection;
|
||||||
if (options.pCrossfadeData)
|
if (options.pCrossfadeData)
|
||||||
mRecordingSchedule.mCrossfadeData.swap( *options.pCrossfadeData );
|
mRecordingSchedule.mCrossfadeData.swap( *options.pCrossfadeData );
|
||||||
|
|
||||||
mListener = options.listener;
|
mListener = options.listener;
|
||||||
mRate = sampleRate;
|
mRate = sampleRate;
|
||||||
mTime = mT0;
|
mPlaybackSchedule.mTime = mPlaybackSchedule.mT0;
|
||||||
mSeek = 0;
|
mSeek = 0;
|
||||||
mLastRecordingOffset = 0;
|
mLastRecordingOffset = 0;
|
||||||
mCaptureTracks = tracks.captureTracks;
|
mCaptureTracks = tracks.captureTracks;
|
||||||
@ -1970,9 +1970,11 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mPlayMode = options.playLooped ? PLAY_LOOPED : PLAY_STRAIGHT;
|
mPlaybackSchedule.mPlayMode = options.playLooped
|
||||||
mCutPreviewGapStart = options.cutPreviewGapStart;
|
? PlaybackSchedule::PLAY_LOOPED
|
||||||
mCutPreviewGapLen = options.cutPreviewGapLen;
|
: PlaybackSchedule::PLAY_STRAIGHT;
|
||||||
|
mPlaybackSchedule.mCutPreviewGapStart = options.cutPreviewGapStart;
|
||||||
|
mPlaybackSchedule.mCutPreviewGapLen = options.cutPreviewGapLen;
|
||||||
|
|
||||||
mPlaybackBuffers.reset();
|
mPlaybackBuffers.reset();
|
||||||
mPlaybackMixers.reset();
|
mPlaybackMixers.reset();
|
||||||
@ -1995,41 +1997,27 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
const auto &scrubOptions = *options.pScrubbingOptions;
|
const auto &scrubOptions = *options.pScrubbingOptions;
|
||||||
|
|
||||||
if (mCaptureTracks.size() > 0 ||
|
if (mCaptureTracks.size() > 0 ||
|
||||||
mPlayMode == PLAY_LOOPED ||
|
mPlaybackSchedule.Looping() ||
|
||||||
mTimeTrack != NULL ||
|
mPlaybackSchedule.mTimeTrack != NULL ||
|
||||||
scrubOptions.maxSpeed < ScrubbingOptions::MinAllowedScrubSpeed()) {
|
scrubOptions.maxSpeed < ScrubbingOptions::MinAllowedScrubSpeed()) {
|
||||||
wxASSERT(false);
|
wxASSERT(false);
|
||||||
scrubbing = false;
|
scrubbing = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
playbackTime = lrint(scrubOptions.delay * sampleRate) / sampleRate;
|
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
|
#endif
|
||||||
|
|
||||||
// mWarpedTime and mWarpedLength are irrelevant when scrubbing,
|
mPlaybackSchedule.mWarpedTime = 0.0;
|
||||||
// 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;
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
if (scrubbing)
|
if (mPlaybackSchedule.Scrubbing())
|
||||||
mWarpedLength = 0.0;
|
mPlaybackSchedule.mWarpedLength = 0.0f;
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
mPlaybackSchedule.mWarpedLength =
|
||||||
if (mTimeTrack)
|
mPlaybackSchedule.RealDuration(mPlaybackSchedule.mT1);
|
||||||
// Following gives negative when mT0 > mT1
|
|
||||||
mWarpedLength = mTimeTrack->ComputeWarpedLength(mT0, mT1);
|
|
||||||
else
|
|
||||||
mWarpedLength = mT1 - mT0;
|
|
||||||
// PRL allow backwards play
|
|
||||||
mWarpedLength = fabs(mWarpedLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// The RingBuffer sizes, and the max amount of the buffer to
|
// The RingBuffer sizes, and the max amount of the buffer to
|
||||||
@ -2112,7 +2100,7 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
mStreamToken = 0;
|
mStreamToken = 0;
|
||||||
|
|
||||||
// Don't cause a busy wait in the audio thread after stopping scrubbing
|
// Don't cause a busy wait in the audio thread after stopping scrubbing
|
||||||
mPlayMode = PLAY_STRAIGHT;
|
mPlaybackSchedule.ResetMode();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2148,7 +2136,7 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
ScrubbingOptions::MaxAllowedScrubSpeed())
|
ScrubbingOptions::MaxAllowedScrubSpeed())
|
||||||
:
|
:
|
||||||
#endif
|
#endif
|
||||||
Mixer::WarpOptions(mTimeTrack);
|
Mixer::WarpOptions(mPlaybackSchedule.mTimeTrack);
|
||||||
|
|
||||||
for (unsigned int i = 0; i < mPlaybackTracks.size(); i++)
|
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:
|
// Don't throw for read errors, just play silence:
|
||||||
false,
|
false,
|
||||||
warpOptions,
|
warpOptions,
|
||||||
mT0,
|
mPlaybackSchedule.mT0,
|
||||||
endTime,
|
endTime,
|
||||||
1,
|
1,
|
||||||
playbackMixBufferSize, false,
|
playbackMixBufferSize, false,
|
||||||
@ -2267,15 +2255,14 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
if (options.pStartTime)
|
if (options.pStartTime)
|
||||||
{
|
{
|
||||||
// Calculate the NEW time position
|
// 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
|
// Reset mixer positions for all playback tracks
|
||||||
unsigned numMixers = mPlaybackTracks.size();
|
unsigned numMixers = mPlaybackTracks.size();
|
||||||
for (unsigned ii = 0; ii < numMixers; ++ii)
|
for (unsigned ii = 0; ii < numMixers; ++ii)
|
||||||
mPlaybackMixers[ii]->Reposition(mTime);
|
mPlaybackMixers[ii]->Reposition(mPlaybackSchedule.mTime);
|
||||||
if(mTimeTrack)
|
mPlaybackSchedule.RealTimeInit();
|
||||||
mWarpedTime = mTimeTrack->ComputeWarpedLength(mT0, mTime);
|
|
||||||
else
|
|
||||||
mWarpedTime = mTime - mT0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
@ -2283,7 +2270,9 @@ int AudioIO::StartStream(const TransportTracks &tracks,
|
|||||||
{
|
{
|
||||||
const auto &scrubOptions = *options.pScrubbingOptions;
|
const auto &scrubOptions = *options.pScrubbingOptions;
|
||||||
mScrubQueue =
|
mScrubQueue =
|
||||||
std::make_unique<ScrubQueue>(mT0, mT1, scrubOptions.startClockTimeMillis,
|
std::make_unique<ScrubQueue>(
|
||||||
|
mPlaybackSchedule.mT0, mPlaybackSchedule.mT1,
|
||||||
|
scrubOptions.startClockTimeMillis,
|
||||||
sampleRate, 2 * scrubOptions.minStutter,
|
sampleRate, 2 * scrubOptions.minStutter,
|
||||||
scrubOptions);
|
scrubOptions);
|
||||||
mScrubDuration = 0;
|
mScrubDuration = 0;
|
||||||
@ -2410,7 +2399,7 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
|
|||||||
|
|
||||||
|
|
||||||
// Don't cause a busy wait in the audio thread after stopping scrubbing
|
// Don't cause a busy wait in the audio thread after stopping scrubbing
|
||||||
mPlayMode = PLAY_STRAIGHT;
|
mPlaybackSchedule.ResetMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
@ -2448,7 +2437,7 @@ void AudioIO::PrepareMidiIterator(bool send, double offset)
|
|||||||
// Start MIDI from current cursor position
|
// Start MIDI from current cursor position
|
||||||
mSendMidiState = true;
|
mSendMidiState = true;
|
||||||
while (mNextEvent &&
|
while (mNextEvent &&
|
||||||
mNextEventTime < mT0 + offset) {
|
mNextEventTime < mPlaybackSchedule.mT0 + offset) {
|
||||||
if (send) OutputEvent();
|
if (send) OutputEvent();
|
||||||
GetNextEvent();
|
GetNextEvent();
|
||||||
}
|
}
|
||||||
@ -2834,7 +2823,7 @@ void AudioIO::StopStream()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't cause a busy wait in the audio thread after stopping scrubbing
|
// Don't cause a busy wait in the audio thread after stopping scrubbing
|
||||||
mPlayMode = PLAY_STRAIGHT;
|
mPlaybackSchedule.ResetMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioIO::SetPaused(bool state)
|
void AudioIO::SetPaused(bool state)
|
||||||
@ -2916,16 +2905,16 @@ bool AudioIO::IsMonitoring()
|
|||||||
return ( mPortStreamV19 && mStreamToken==0 );
|
return ( mPortStreamV19 && mStreamToken==0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
double AudioIO::LimitStreamTime(double absoluteTime) const
|
double AudioIO::PlaybackSchedule::LimitStreamTime() const
|
||||||
{
|
{
|
||||||
// Allows for forward or backward play
|
// Allows for forward or backward play
|
||||||
if (ReversedTime())
|
if (ReversedTime())
|
||||||
return std::max(mT1, std::min(mT0, absoluteTime));
|
return std::max(mT1, std::min(mT0, mTime));
|
||||||
else
|
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:
|
// dmazzoni: This function is needed for two reasons:
|
||||||
// One is for looped-play mode - this function makes sure that the
|
// 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
|
// mode. In this case, we should jump over a defined "gap" in the
|
||||||
// audio.
|
// audio.
|
||||||
|
|
||||||
|
auto absoluteTime = mTime;
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
// Limit the time between t0 and t1 if not scrubbing.
|
// Limit the time between t0 and t1 if not scrubbing.
|
||||||
// Should the limiting be necessary in any play mode if there are no bugs?
|
// 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
|
#endif
|
||||||
absoluteTime = LimitStreamTime(absoluteTime);
|
absoluteTime = LimitStreamTime();
|
||||||
|
|
||||||
if (mCutPreviewGapLen > 0)
|
if (mCutPreviewGapLen > 0)
|
||||||
{
|
{
|
||||||
@ -2962,7 +2953,7 @@ double AudioIO::GetStreamTime()
|
|||||||
if( !IsStreamActive() )
|
if( !IsStreamActive() )
|
||||||
return BAD_STREAM_TIME;
|
return BAD_STREAM_TIME;
|
||||||
|
|
||||||
return NormalizeStreamTime(mTime);
|
return mPlaybackSchedule.NormalizeStreamTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3257,12 +3248,7 @@ AudioThread::ExitCode AudioThread::Entry()
|
|||||||
}
|
}
|
||||||
gAudioIO->mAudioThreadFillBuffersLoopActive = false;
|
gAudioIO->mAudioThreadFillBuffersLoopActive = false;
|
||||||
|
|
||||||
if (gAudioIO->mPlayMode == AudioIO::PLAY_SCRUB) {
|
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.
|
|
||||||
}
|
|
||||||
else if (gAudioIO->mPlayMode == AudioIO::PLAY_AT_SPEED) {
|
|
||||||
// Rely on the Wait() in ScrubQueue::Transformer()
|
// Rely on the Wait() in ScrubQueue::Transformer()
|
||||||
// This allows the scrubbing update interval to be made very short without
|
// This allows the scrubbing update interval to be made very short without
|
||||||
// playback becoming intermittent.
|
// playback becoming intermittent.
|
||||||
@ -3833,10 +3819,11 @@ void AudioIO::FillBuffers()
|
|||||||
// The exception is if we're at the end of the selected
|
// The exception is if we're at the end of the selected
|
||||||
// region - then we should just fill the buffer.
|
// region - then we should just fill the buffer.
|
||||||
//
|
//
|
||||||
|
const auto realTimeRemaining = mPlaybackSchedule.RealTimeRemaining();
|
||||||
if (nAvailable >= (int)mPlaybackSamplesToCopy ||
|
if (nAvailable >= (int)mPlaybackSamplesToCopy ||
|
||||||
(mPlayMode == PLAY_STRAIGHT &&
|
(mPlaybackSchedule.PlayingStraight() &&
|
||||||
nAvailable > 0 &&
|
nAvailable > 0 &&
|
||||||
mWarpedTime+(nAvailable/mRate) >= mWarpedLength))
|
nAvailable / mRate >= realTimeRemaining))
|
||||||
{
|
{
|
||||||
// Limit maximum buffer size (increases performance)
|
// Limit maximum buffer size (increases performance)
|
||||||
auto available =
|
auto available =
|
||||||
@ -3855,25 +3842,25 @@ void AudioIO::FillBuffers()
|
|||||||
auto frames = available;
|
auto frames = available;
|
||||||
bool progress = true;
|
bool progress = true;
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
if ((mPlayMode == PLAY_SCRUB) || (mPlayMode == PLAY_AT_SPEED))
|
if (mPlaybackSchedule.Interactive())
|
||||||
// scrubbing does not use warped time and length
|
// scrubbing does not use warped time and length
|
||||||
frames = limitSampleBufferSize(frames, mScrubDuration);
|
frames = limitSampleBufferSize(frames, mScrubDuration);
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
double deltat = frames / mRate;
|
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
|
// Don't fall into an infinite loop, if loop-playing a selection
|
||||||
// that is so short, it has no samples: detect that case
|
// that is so short, it has no samples: detect that case
|
||||||
progress =
|
progress =
|
||||||
!(mPlayMode == PLAY_LOOPED &&
|
!(mPlaybackSchedule.Looping() &&
|
||||||
mWarpedTime == 0.0 && frames == 0);
|
mPlaybackSchedule.mWarpedTime == 0.0 && frames == 0);
|
||||||
mWarpedTime = mWarpedLength;
|
mPlaybackSchedule.RealTimeAdvance( realTimeRemaining );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
mWarpedTime += deltat;
|
mPlaybackSchedule.RealTimeAdvance( deltat );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!progress)
|
if (!progress)
|
||||||
@ -3892,8 +3879,8 @@ void AudioIO::FillBuffers()
|
|||||||
|
|
||||||
// don't generate either if scrubbing at zero speed.
|
// don't generate either if scrubbing at zero speed.
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
const bool silent = ((mPlayMode == PLAY_SCRUB)||
|
const bool silent =
|
||||||
(mPlayMode == PLAY_AT_SPEED)) && mSilentScrub;
|
mPlaybackSchedule.Interactive() && mSilentScrub;
|
||||||
#else
|
#else
|
||||||
const bool silent = false;
|
const bool silent = false;
|
||||||
#endif
|
#endif
|
||||||
@ -3917,7 +3904,7 @@ void AudioIO::FillBuffers()
|
|||||||
// If scrubbing, we may be producing some silence. Otherwise this should not happen,
|
// If scrubbing, we may be producing some silence. Otherwise this should not happen,
|
||||||
// but makes sure anyway that we produce equal
|
// but makes sure anyway that we produce equal
|
||||||
// numbers of samples for all channels for this pass of the do-loop.
|
// 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);
|
mSilentBuf.Resize(frames, floatSample);
|
||||||
ClearSamples(mSilentBuf.ptr(), floatSample, 0, frames);
|
ClearSamples(mSilentBuf.ptr(), floatSample, 0, frames);
|
||||||
@ -3932,11 +3919,11 @@ void AudioIO::FillBuffers()
|
|||||||
available -= frames;
|
available -= frames;
|
||||||
wxASSERT(available >= 0);
|
wxASSERT(available >= 0);
|
||||||
|
|
||||||
switch (mPlayMode)
|
switch (mPlaybackSchedule.mPlayMode)
|
||||||
{
|
{
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
case PLAY_SCRUB:
|
case PlaybackSchedule::PLAY_SCRUB:
|
||||||
case PLAY_AT_SPEED:
|
case PlaybackSchedule::PLAY_AT_SPEED:
|
||||||
{
|
{
|
||||||
mScrubDuration -= frames;
|
mScrubDuration -= frames;
|
||||||
wxASSERT(mScrubDuration >= 0);
|
wxASSERT(mScrubDuration >= 0);
|
||||||
@ -3970,16 +3957,16 @@ void AudioIO::FillBuffers()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case PLAY_LOOPED:
|
case PlaybackSchedule::PLAY_LOOPED:
|
||||||
{
|
{
|
||||||
done = !progress || (available == 0);
|
done = !progress || (available == 0);
|
||||||
// msmeyer: If playing looped, check if we are at the end of the buffer
|
// msmeyer: If playing looped, check if we are at the end of the buffer
|
||||||
// and if yes, restart from the beginning.
|
// and if yes, restart from the beginning.
|
||||||
if (mWarpedTime >= mWarpedLength)
|
if (mPlaybackSchedule.RealTimeRemaining() <= 0)
|
||||||
{
|
{
|
||||||
for (i = 0; i < mPlaybackTracks.size(); i++)
|
for (i = 0; i < mPlaybackTracks.size(); i++)
|
||||||
mPlaybackMixers[i]->Restart();
|
mPlaybackMixers[i]->Restart();
|
||||||
mWarpedTime = 0.0;
|
mPlaybackSchedule.RealTimeRestart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -4187,10 +4174,11 @@ static Alg_update gAllNotesOff; // special event for loop ending
|
|||||||
double AudioIO::UncorrectedMidiEventTime()
|
double AudioIO::UncorrectedMidiEventTime()
|
||||||
{
|
{
|
||||||
double time;
|
double time;
|
||||||
if (mTimeTrack)
|
if (mPlaybackSchedule.mTimeTrack)
|
||||||
time =
|
time =
|
||||||
mTimeTrack->ComputeWarpedLength(mT0, mNextEventTime - MidiLoopOffset())
|
mPlaybackSchedule.RealDuration(mNextEventTime - MidiLoopOffset())
|
||||||
+ mT0 + (mMidiLoopPasses * mWarpedLength);
|
+ mPlaybackSchedule.mT0 + (mMidiLoopPasses *
|
||||||
|
mPlaybackSchedule.mWarpedLength);
|
||||||
else
|
else
|
||||||
time = mNextEventTime;
|
time = mNextEventTime;
|
||||||
|
|
||||||
@ -4220,7 +4208,7 @@ void AudioIO::OutputEvent()
|
|||||||
// The special event gAllNotesOff 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) {
|
||||||
bool looping = (mPlayMode == PLAY_LOOPED);
|
bool looping = mPlaybackSchedule.Looping();
|
||||||
AllNotesOff(looping);
|
AllNotesOff(looping);
|
||||||
if (looping) {
|
if (looping) {
|
||||||
// jump back to beginning of loop
|
// jump back to beginning of loop
|
||||||
@ -4356,16 +4344,16 @@ void AudioIO::GetNextEvent()
|
|||||||
auto midiLoopOffset = MidiLoopOffset();
|
auto midiLoopOffset = MidiLoopOffset();
|
||||||
mNextEvent = mIterator->next(&mNextIsNoteOn,
|
mNextEvent = mIterator->next(&mNextIsNoteOn,
|
||||||
(void **) &mNextEventTrack,
|
(void **) &mNextEventTrack,
|
||||||
&nextOffset, mT1 + midiLoopOffset);
|
&nextOffset, mPlaybackSchedule.mT1 + midiLoopOffset);
|
||||||
|
|
||||||
mNextEventTime = mT1 + midiLoopOffset + 1;
|
mNextEventTime = mPlaybackSchedule.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 + midiLoopOffset)){ // terminate playback at mT1
|
if (mNextEventTime > (mPlaybackSchedule.mT1 + midiLoopOffset)){ // terminate playback at mT1
|
||||||
mNextEvent = &gAllNotesOff;
|
mNextEvent = &gAllNotesOff;
|
||||||
mNextEventTime = mT1 + midiLoopOffset - ALG_EPS;
|
mNextEventTime = mPlaybackSchedule.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
|
||||||
@ -4446,15 +4434,11 @@ void AudioIO::FillMidiBuffers()
|
|||||||
// position at mT1 before shutting down the stream.
|
// position at mT1 before shutting down the stream.
|
||||||
const double loopDelay = 0.220;
|
const double loopDelay = 0.220;
|
||||||
|
|
||||||
double timeAtSpeed;
|
auto timeAtSpeed = mPlaybackSchedule.TrackDuration(realTime);
|
||||||
if (mTimeTrack)
|
|
||||||
timeAtSpeed = mTimeTrack->SolveWarpedLength(mT0, realTime);
|
|
||||||
else
|
|
||||||
timeAtSpeed = realTime;
|
|
||||||
|
|
||||||
mMidiOutputComplete =
|
mMidiOutputComplete =
|
||||||
(mPlayMode == PLAY_STRAIGHT && // PRL: what if scrubbing?
|
(mPlaybackSchedule.PlayingStraight() && // PRL: what if scrubbing?
|
||||||
timeAtSpeed >= mT1 + loopDelay);
|
timeAtSpeed >= mPlaybackSchedule.mT1 + loopDelay);
|
||||||
// !mNextEvent);
|
// !mNextEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4765,7 +4749,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
#ifdef EXPERIMENTAL_MIDI_OUT
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
||||||
if (mCallbackCount++ == 0) {
|
if (mCallbackCount++ == 0) {
|
||||||
// This is effectively mSystemMinusAudioTime when the buffer is empty:
|
// 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
|
// later, mStartTime - mSystemMinusAudioTime will tell us latency
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4952,9 +4936,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
// While scrubbing, ignore seek requests
|
// While scrubbing, ignore seek requests
|
||||||
if (mSeek && mPlayMode == AudioIO::PLAY_SCRUB)
|
if (mSeek && mPlaybackSchedule.Interactive())
|
||||||
mSeek = 0.0;
|
|
||||||
else if (mSeek && mPlayMode == AudioIO::PLAY_AT_SPEED)
|
|
||||||
mSeek = 0.0;
|
mSeek = 0.0;
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
@ -4974,24 +4956,17 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the NEW time position
|
// Calculate the NEW time position
|
||||||
mTime += mSeek;
|
mPlaybackSchedule.mTime += mSeek;
|
||||||
mTime = LimitStreamTime(mTime);
|
mPlaybackSchedule.mTime = mPlaybackSchedule.LimitStreamTime();
|
||||||
mSeek = 0.0;
|
mSeek = 0.0;
|
||||||
|
|
||||||
// Reset mixer positions and flush buffers for all tracks
|
// Reset mixer positions and flush buffers for all tracks
|
||||||
if(mTimeTrack)
|
mPlaybackSchedule.RealTimeInit();
|
||||||
// Following gives negative when mT0 > mTime
|
|
||||||
mWarpedTime =
|
|
||||||
mTimeTrack->ComputeWarpedLength
|
|
||||||
(mT0, mTime);
|
|
||||||
else
|
|
||||||
mWarpedTime = mTime - mT0;
|
|
||||||
mWarpedTime = std::abs(mWarpedTime);
|
|
||||||
|
|
||||||
// Reset mixer positions and flush buffers for all tracks
|
// Reset mixer positions and flush buffers for all tracks
|
||||||
for (i = 0; i < numPlaybackTracks; i++)
|
for (i = 0; i < numPlaybackTracks; i++)
|
||||||
{
|
{
|
||||||
mPlaybackMixers[i]->Reposition(mTime);
|
mPlaybackMixers[i]->Reposition( mPlaybackSchedule.mTime );
|
||||||
const auto toDiscard =
|
const auto toDiscard =
|
||||||
mPlaybackBuffers[i]->AvailForGet();
|
mPlaybackBuffers[i]->AvailForGet();
|
||||||
const auto discarded =
|
const auto discarded =
|
||||||
@ -5135,37 +5110,7 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
group++;
|
group++;
|
||||||
|
|
||||||
|
|
||||||
bool bDone = true;
|
CallbackCheckCompletion(callbackReturn, len);
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cut) // no samples to process, they've been discarded
|
if (cut) // no samples to process, they've been discarded
|
||||||
continue;
|
continue;
|
||||||
@ -5217,30 +5162,15 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
// Poke: If there are no playback tracks, then the earlier check
|
// Poke: If there are no playback tracks, then the earlier check
|
||||||
// about the time indicator being passed the end won't happen;
|
// about the time indicator being passed the end won't happen;
|
||||||
// do it here instead (but not if looping or scrubbing)
|
// do it here instead (but not if looping or scrubbing)
|
||||||
if (numPlaybackTracks == 0 &&
|
if (numPlaybackTracks == 0)
|
||||||
mPlayMode == AudioIO::PLAY_STRAIGHT)
|
CallbackCheckCompletion(callbackReturn, 0);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
|
||||||
// Update the current time position, for scrubbing
|
// Update the current time position, for scrubbing
|
||||||
// "Consume" only as much as the ring buffers produced, which may
|
// "Consume" only as much as the ring buffers produced, which may
|
||||||
// be less than framesPerBuffer (during "stutter")
|
// be less than framesPerBuffer (during "stutter")
|
||||||
if (mPlayMode == AudioIO::PLAY_SCRUB)
|
if (mPlaybackSchedule.Interactive())
|
||||||
mTime = mScrubQueue->Consumer(maxLen);
|
mPlaybackSchedule.mTime = mScrubQueue->Consumer( maxLen );
|
||||||
else if (mPlayMode == AudioIO::PLAY_AT_SPEED)
|
|
||||||
mTime = mScrubQueue->Consumer(maxLen);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
em.RealtimeProcessEnd();
|
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
|
// 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.
|
// earlier checks for being passed the end won't happen, so do it here.
|
||||||
if (mTime >= mT1) {
|
if (mPlaybackSchedule.PassIsComplete()) {
|
||||||
callbackReturn = paComplete;
|
callbackReturn = paComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5314,8 +5244,8 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
len < framesPerBuffer) ) {
|
len < framesPerBuffer) ) {
|
||||||
// Assume that any good partial buffer should be written leftmost
|
// Assume that any good partial buffer should be written leftmost
|
||||||
// and zeroes will be padded after; label the zeroes.
|
// and zeroes will be padded after; label the zeroes.
|
||||||
auto start = mTime + len / mRate +
|
auto start = mPlaybackSchedule.mTime +
|
||||||
mRecordingSchedule.mLatencyCorrection;
|
len / mRate + mRecordingSchedule.mLatencyCorrection;
|
||||||
auto duration = (framesPerBuffer - len) / mRate;
|
auto duration = (framesPerBuffer - len) / mRate;
|
||||||
auto interval = std::make_pair( start, duration );
|
auto interval = std::make_pair( start, duration );
|
||||||
mLostCaptureIntervals.push_back( interval );
|
mLostCaptureIntervals.push_back( interval );
|
||||||
@ -5376,35 +5306,8 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
|
|
||||||
// Update the current time position if not scrubbing
|
// Update the current time position if not scrubbing
|
||||||
// (Already did it above, for 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
|
mPlaybackSchedule.TrackTimeUpdate( framesPerBuffer / mRate );
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record the reported latency from PortAudio.
|
// Record the reported latency from PortAudio.
|
||||||
// TODO: Don't recalculate this with every callback?
|
// TODO: Don't recalculate this with every callback?
|
||||||
@ -5497,6 +5400,96 @@ int AudioIO::AudioCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
return callbackReturn;
|
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
|
double AudioIO::RecordingSchedule::ToConsume() const
|
||||||
{
|
{
|
||||||
return mDuration - Consumed();
|
return mDuration - Consumed();
|
||||||
@ -5515,5 +5508,6 @@ double AudioIO::RecordingSchedule::ToDiscard() const
|
|||||||
bool AudioIO::IsCapturing() const
|
bool AudioIO::IsCapturing() const
|
||||||
{
|
{
|
||||||
return GetNumCaptureChannels() > 0 &&
|
return GetNumCaptureChannels() > 0 &&
|
||||||
mTime >= mT0 + mRecordingSchedule.mPreRoll;
|
mPlaybackSchedule.mTime >=
|
||||||
|
mPlaybackSchedule.mT0 + mRecordingSchedule.mPreRoll;
|
||||||
}
|
}
|
||||||
|
154
src/AudioIO.h
154
src/AudioIO.h
@ -210,6 +210,9 @@ class AUDACITY_DLL_API AudioIO final {
|
|||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
const PaStreamCallbackFlags statusFlags, void *userData);
|
const PaStreamCallbackFlags statusFlags, void *userData);
|
||||||
|
|
||||||
|
void CallbackCheckCompletion(
|
||||||
|
int &callbackReturn, unsigned long len);
|
||||||
|
|
||||||
AudioIOListener* GetListener() { return mListener; }
|
AudioIOListener* GetListener() { return mListener; }
|
||||||
void SetListener(AudioIOListener* listener);
|
void SetListener(AudioIOListener* listener);
|
||||||
|
|
||||||
@ -519,7 +522,7 @@ private:
|
|||||||
void OutputEvent();
|
void OutputEvent();
|
||||||
void FillMidiBuffers();
|
void FillMidiBuffers();
|
||||||
void GetNextEvent();
|
void GetNextEvent();
|
||||||
double AudioTime() { return mT0 + mNumFrames / mRate; }
|
double AudioTime() { return mPlaybackSchedule.mT0 + mNumFrames / mRate; }
|
||||||
double PauseTime();
|
double PauseTime();
|
||||||
void AllNotesOff(bool looping = false);
|
void AllNotesOff(bool looping = false);
|
||||||
#endif
|
#endif
|
||||||
@ -572,27 +575,6 @@ private:
|
|||||||
/** \brief How many sample rates to try */
|
/** \brief How many sample rates to try */
|
||||||
static const int NumRatesToTry;
|
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.
|
/** \brief Clean up after StartStream if it fails.
|
||||||
*
|
*
|
||||||
* If bOnlyBuffers is specified, it only cleans up the buffers. */
|
* If bOnlyBuffers is specified, it only cleans up the buffers. */
|
||||||
@ -619,7 +601,9 @@ private:
|
|||||||
volatile long mNumPauseFrames;
|
volatile long mNumPauseFrames;
|
||||||
/// total of backward jumps
|
/// total of backward jumps
|
||||||
volatile int mMidiLoopPasses;
|
volatile int mMidiLoopPasses;
|
||||||
inline double MidiLoopOffset() { return mMidiLoopPasses * (mT1 - mT0); }
|
inline double MidiLoopOffset() {
|
||||||
|
return mMidiLoopPasses * (mPlaybackSchedule.mT1 - mPlaybackSchedule.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,
|
||||||
@ -711,22 +695,6 @@ private:
|
|||||||
double mFactor;
|
double mFactor;
|
||||||
/// Audio playback rate in samples per second
|
/// Audio playback rate in samples per second
|
||||||
double mRate;
|
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 mSeek;
|
||||||
double mPlaybackRingBufferSecs;
|
double mPlaybackRingBufferSecs;
|
||||||
@ -780,17 +748,6 @@ private:
|
|||||||
bool mInputMixerWorks;
|
bool mInputMixerWorks;
|
||||||
float mMixerOutputVol;
|
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;
|
GrowableSampleBuffer mSilentBuf;
|
||||||
|
|
||||||
AudioIOListener* mListener;
|
AudioIOListener* mListener;
|
||||||
@ -802,8 +759,6 @@ private:
|
|||||||
|
|
||||||
friend void InitAudioIO();
|
friend void InitAudioIO();
|
||||||
|
|
||||||
const TimeTrack *mTimeTrack;
|
|
||||||
|
|
||||||
bool mUsingAlsa { false };
|
bool mUsingAlsa { false };
|
||||||
|
|
||||||
// For cacheing supported sample rates
|
// For cacheing supported sample rates
|
||||||
@ -850,6 +805,101 @@ public:
|
|||||||
// detect more dropouts
|
// detect more dropouts
|
||||||
bool mDetectUpstreamDropouts{ true };
|
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:
|
private:
|
||||||
struct RecordingSchedule {
|
struct RecordingSchedule {
|
||||||
double mPreRoll{};
|
double mPreRoll{};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user