1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-03 17:19:43 +02:00

Merge branch 'scrubbing' -- preliminaries only, not the complete feature

This commit is contained in:
Paul-Licameli 2015-04-17 02:53:01 -04:00
commit f49a94755d
21 changed files with 1120 additions and 436 deletions

View File

@ -79,6 +79,7 @@
the speed control. In a separate algorithm, the audio callback updates
mTime by (frames / samplerate) * factor, where factor reflects the
speed at mTime. This effectively integrates speed to get position.
Negative speeds are allowed too, for instance in scrubbing.
\par Midi Time
MIDI is not warped according to the speed control. This might be
@ -340,6 +341,296 @@ wxArrayLong AudioIO::mCachedSampleRates;
double AudioIO::mCachedBestRateIn = 0.0;
double AudioIO::mCachedBestRateOut;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
/*
This work queue class, with the aid of the playback ring
buffers, coordinates three threads during scrub play:
The UI thread which specifies scrubbing intervals to play,
The Audio thread which consumes those specifications a first time
and fills the ring buffers with samples for play,
The PortAudio thread which consumes from the ring buffers, then
also consumes a second time from this queue,
to figure out how to update mTime
-- which the UI thread, in turn, uses to redraw the play head indicator
in the right place.
Audio produces samples for PortAudio, which consumes them, both in
approximate real time. The UI thread might go idle and so the others
might catch up, emptying the queue and causing scrub to go silent.
The UI thread will not normally outrun the others -- because InitEntry()
limits the real time duration over which each enqueued interval will play.
So a small, fixed queue size should be adequate.
*/
struct AudioIO::ScrubQueue
{
ScrubQueue(double t0, double t1, wxLongLong startClockMillis,
double rate, double maxSpeed, double minStutter)
: mTrailingIdx(0)
, mMiddleIdx(1)
, mLeadingIdx(2)
, mRate(rate)
, mMinStutter(lrint(std::max(0.0, minStutter) * mRate))
, mLastScrubTimeMillis(startClockMillis)
, mUpdating()
{
bool success = InitEntry(mEntries[mMiddleIdx],
t0, t1, maxSpeed, false, NULL, false);
if (!success)
{
// StartClock equals now? Really?
--mLastScrubTimeMillis;
success = InitEntry(mEntries[mMiddleIdx],
t0, t1, maxSpeed, false, NULL, false);
}
wxASSERT(success);
// So the play indicator starts out unconfused:
{
Entry &entry = mEntries[mTrailingIdx];
entry.mS0 = entry.mS1 = mEntries[mMiddleIdx].mS0;
entry.mPlayed = entry.mDuration = 1;
}
}
~ScrubQueue() {}
bool Producer(double startTime, double end, double maxSpeed, bool bySpeed, bool maySkip)
{
// Main thread indicates a scrubbing interval
// MAY ADVANCE mLeadingIdx, BUT IT NEVER CATCHES UP TO mTrailingIdx.
wxCriticalSectionLocker locker(mUpdating);
const unsigned next = (mLeadingIdx + 1) % Size;
if (next != mTrailingIdx)
{
Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
if (startTime < 0.0)
// Use the previous end as new start.
startTime = previous.mS1 / mRate;
// Might reject the request because of zero duration,
// or a too-short "stutter"
const bool success =
(InitEntry(mEntries[mLeadingIdx], startTime, end, maxSpeed,
bySpeed, &previous, maySkip));
if (success)
mLeadingIdx = next;
return success;
}
else
{
// ??
// Queue wasn't long enough. Write side (UI thread)
// has overtaken the trailing read side (PortAudio thread), despite
// my comments above! We lose some work requests then.
// wxASSERT(false);
return false;
}
}
void Transformer(long &startSample, long &endSample, long &duration)
{
// Audio thread is ready for the next interval.
// MAY ADVANCE mMiddleIdx, WHICH MAY EQUAL mLeadingIdx, BUT DOES NOT PASS IT.
wxCriticalSectionLocker locker(mUpdating);
if (mMiddleIdx != mLeadingIdx)
{
// There is work in the queue
Entry &entry = mEntries[mMiddleIdx];
startSample = entry.mS0;
endSample = entry.mS1;
duration = entry.mDuration;
const unsigned next = (mMiddleIdx + 1) % Size;
mMiddleIdx = next;
}
else
{
// next entry is not yet ready
startSample = endSample = duration = -1L;
}
}
double Consumer(unsigned long frames)
{
// Portaudio thread consumes samples and must update
// the time for the indicator. This finds the time value.
// MAY ADVANCE mTrailingIdx, BUT IT NEVER CATCHES UP TO mMiddleIdx.
wxCriticalSectionLocker locker(mUpdating);
// Mark entries as partly or fully "consumed" for
// purposes of mTime update. It should not happen that
// frames exceed the total of samples to be consumed,
// but in that case we just use the t1 of the latest entry.
while (1)
{
Entry *pEntry = &mEntries[mTrailingIdx];
unsigned long remaining = pEntry->mDuration - pEntry->mPlayed;
if (frames >= remaining)
{
frames -= remaining;
pEntry->mPlayed = pEntry->mDuration;
}
else
{
pEntry->mPlayed += frames;
break;
}
const unsigned next = (mTrailingIdx + 1) % Size;
if (next == mMiddleIdx)
break;
mTrailingIdx = next;
}
return mEntries[mTrailingIdx].GetTime(mRate);
}
private:
struct Entry
{
Entry()
: mS0(0)
, mS1(0)
, mGoal(0)
, mDuration(0)
, mPlayed(0)
{}
bool Init(long s0, long s1, long duration, Entry *previous,
double maxSpeed, long minStutter, bool adjustStart)
{
if (duration <= 0)
return false;
double speed = double(abs(s1 - s0)) / duration;
bool maxed = false;
// May change the requested speed (or reject)
if (speed > maxSpeed)
{
// Reduce speed to the maximum selected in the user interface.
speed = maxSpeed;
maxed = true;
}
else if (!adjustStart &&
previous &&
previous->mGoal >= 0 &&
previous->mGoal == s1)
{
// In case the mouse has not moved, and playback
// is catching up to the mouse at maximum speed,
// continue at no less than maximum. (Without this
// the final catch-up can make a slow scrub interval
// that drops the pitch and sounds wrong.)
duration = lrint(speed * duration / maxSpeed);
if (duration <= 0)
{
previous->mGoal = -1;
return false;
}
speed = maxSpeed;
maxed = true;
}
if (maxed)
{
// When playback follows a fast mouse movement by "stuttering"
// at maximum playback, don't make stutters too short to be useful.
if (adjustStart && duration < minStutter)
return false;
}
else if (speed < GetMinScrubSpeed())
// Mixers were set up to go only so slowly, not slower.
// This will put a request for some silence in the work queue.
speed = 0.0;
// No more rejections.
// Before we change s1:
mGoal = maxed ? s1 : -1;
// May change s1 or s0 to match speed change:
long diff = lrint(speed * duration);
if (adjustStart)
{
if (s0 < s1)
s0 = s1 - diff;
else
s0 = s1 + diff;
}
else
{
// adjust end
if (s0 < s1)
s1 = s0 + diff;
else
s1 = s0 - diff;
}
mS0 = s0;
mS1 = s1;
mPlayed = 0;
mDuration = duration;
return true;
}
double GetTime(double rate) const
{
return (mS0 + ((mS1 - mS0) * mPlayed) / double(mDuration)) / rate;
}
// These sample counts are initialized in the UI, producer, thread:
long mS0;
long mS1;
long mGoal;
// This field is initialized in the UI thread too, and
// this work queue item corresponds to exactly this many samples of
// playback output:
long mDuration;
// The middleman Audio thread does not change these entries, but only
// changes indices in the queue structure.
// This increases from 0 to mDuration as the PortAudio, consumer,
// thread catches up. When they are equal, this entry can be discarded:
long mPlayed;
};
bool InitEntry(Entry &entry, double t0, double end, double maxSpeed,
bool bySpeed, Entry *previous, bool maySkip)
{
const wxLongLong clockTime(::wxGetLocalTimeMillis());
const long duration =
mRate * (clockTime - mLastScrubTimeMillis).ToDouble() / 1000.0;
const long s0 = t0 * mRate;
const long s1 = bySpeed
? s0 + lrint(duration * end) // end is a speed
: lrint(end * mRate); // end is a time
const bool success =
entry.Init(s0, s1, duration, previous, maxSpeed, mMinStutter, maySkip);
if (success)
mLastScrubTimeMillis = clockTime;
return success;
}
enum { Size = 10 };
Entry mEntries[Size];
unsigned mTrailingIdx;
unsigned mMiddleIdx;
unsigned mLeadingIdx;
const double mRate;
const long mMinStutter;
wxLongLong mLastScrubTimeMillis;
wxCriticalSection mUpdating;
};
#endif
const int AudioIO::StandardRates[] = {
8000,
11025,
@ -552,7 +843,7 @@ AudioIO::AudioIO()
mLastRecordingOffset = 0.0;
mNumCaptureChannels = 0;
mPaused = false;
mPlayLooped = false;
mPlayMode = PLAY_STRAIGHT;
mListener = NULL;
mUpdateMeters = false;
@ -615,6 +906,12 @@ AudioIO::AudioIO()
#endif
mLastPlaybackTimeMillis = 0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
mScrubQueue = NULL;
mScrubDuration = 0;
mSilentScrub = false;
#endif
}
AudioIO::~AudioIO()
@ -647,6 +944,10 @@ AudioIO::~AudioIO()
DeleteSamples(mSilentBuf);
delete mThread;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
delete mScrubQueue;
#endif
}
void AudioIO::SetMixer(int inputSource)
@ -1148,13 +1449,8 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray midiPlaybackTracks,
#endif
TimeTrack *timeTrack, double sampleRate,
double t0, double t1,
AudioIOListener* listener,
bool playLooped /* = false */,
double cutPreviewGapStart /* = 0.0 */,
double cutPreviewGapLen, /* = 0.0 */
const double *pStartTime /* = 0 */)
double sampleRate, double t0, double t1,
const AudioIOStartStreamOptions &options)
{
if( IsBusy() )
return 0;
@ -1194,8 +1490,8 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
}
mSilenceLevel = (silenceLevelDB + dBRange)/(double)dBRange; // meter goes -dBRange dB -> 0dB
mTimeTrack = timeTrack;
mListener = listener;
mTimeTrack = options.timeTrack;
mListener = options.listener;
mRate = sampleRate;
mT0 = t0;
mT1 = t1;
@ -1207,21 +1503,59 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
mMidiPlaybackTracks = midiPlaybackTracks;
#endif
mPlayLooped = playLooped;
mCutPreviewGapStart = cutPreviewGapStart;
mCutPreviewGapLen = cutPreviewGapLen;
mPlayMode = options.playLooped ? PLAY_LOOPED : PLAY_STRAIGHT;
mCutPreviewGapStart = options.cutPreviewGapStart;
mCutPreviewGapLen = options.cutPreviewGapLen;
mPlaybackBuffers = NULL;
mPlaybackMixers = NULL;
mCaptureBuffers = NULL;
mResample = NULL;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// Scrubbing is not compatible with looping or recording or a time track!
const double scrubDelay = lrint(options.scrubDelay * sampleRate) / sampleRate;
bool scrubbing = (scrubDelay > 0);
double maxScrubSpeed = options.maxScrubSpeed;
double minScrubStutter = options.minScrubStutter;
if (scrubbing)
{
if (mCaptureTracks.GetCount() > 0 ||
mPlayMode == PLAY_LOOPED ||
mTimeTrack != NULL ||
options.maxScrubSpeed < GetMinScrubSpeed())
{
wxASSERT(false);
scrubbing = false;
}
}
if (scrubbing)
{
mPlayMode = 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;
if(mTimeTrack)
mWarpedLength = mTimeTrack->ComputeWarpedLength(mT0, mT1);
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (scrubbing)
mWarpedLength = 0.0;
else
mWarpedLength = mT1 - mT0;
#endif
{
if (mTimeTrack)
// Following gives negative when mT0 > mT1
mWarpedLength = mTimeTrack->ComputeWarpedLength(mT0, mT1);
else
mWarpedLength = mT1 - mT0;
// PRL allow backwards play
mWarpedLength = abs(mWarpedLength);
}
//
// The RingBuffer sizes, and the max amount of the buffer to
@ -1230,8 +1564,22 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
// killing performance.
//
// (warped) playback time to produce with each filling of the buffers
// by the Audio thread (except at the end of playback):
// usually, make fillings fewer and longer for less CPU usage.
// But for useful scrubbing, we can't run too far ahead without checking
// mouse input, so make fillings more and shorter.
// What Audio thread produces for playback is then consumed by the PortAudio
// thread, in many smaller pieces.
double playbackTime = 4.0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (scrubbing)
playbackTime = scrubDelay;
#endif
mPlaybackSamplesToCopy = playbackTime * mRate;
// Capacity of the playback buffer.
mPlaybackRingBufferSecs = 10.0;
mMaxPlaybackSecsToCopy = 4.0;
mCaptureRingBufferSecs = 4.5 + 0.5 * std::min(size_t(16), mCaptureTracks.GetCount());
mMinCaptureSecsToCopy = 0.2 + 0.2 * std::min(size_t(16), mCaptureTracks.GetCount());
@ -1307,9 +1655,9 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
// Allocate output buffers. For every output track we allocate
// a ring buffer of five seconds
sampleCount playbackBufferSize =
(sampleCount)(mRate * mPlaybackRingBufferSecs + 0.5f);
(sampleCount)lrint(mRate * mPlaybackRingBufferSecs);
sampleCount playbackMixBufferSize =
(sampleCount)(mRate * mMaxPlaybackSecsToCopy + 0.5f);
(sampleCount)mPlaybackSamplesToCopy;
// In the extraordinarily rare case that we can't even afford 100 samples, just give up.
if(playbackBufferSize < 100 || playbackMixBufferSize < 100)
@ -1326,13 +1674,20 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
memset(mPlaybackBuffers, 0, sizeof(RingBuffer*)*mPlaybackTracks.GetCount());
memset(mPlaybackMixers, 0, sizeof(Mixer*)*mPlaybackTracks.GetCount());
for( unsigned int i = 0; i < mPlaybackTracks.GetCount(); i++ )
const Mixer::WarpOptions &warpOptions =
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
scrubbing ? Mixer::WarpOptions(GetMinScrubSpeed(), GetMaxScrubSpeed()) :
#endif
Mixer::WarpOptions(mTimeTrack);
for (unsigned int i = 0; i < mPlaybackTracks.GetCount(); i++)
{
mPlaybackBuffers[i] = new RingBuffer(floatSample, playbackBufferSize);
// MB: use normal time for the end time, not warped time!
mPlaybackMixers[i] = new Mixer(1, &mPlaybackTracks[i],
mTimeTrack, mT0, mT1, 1,
warpOptions,
mT0, mT1, 1,
playbackMixBufferSize, false,
mRate, floatSample, false);
mPlaybackMixers[i]->ApplyTrackGains(false);
@ -1376,7 +1731,7 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
// try deleting everything, halving our buffer size, and try again.
StartStreamCleanup(true);
mPlaybackRingBufferSecs *= 0.5;
mMaxPlaybackSecsToCopy *= 0.5;
mPlaybackSamplesToCopy /= 2;
mCaptureRingBufferSecs *= 0.5;
mMinCaptureSecsToCopy *= 0.5;
bDone = false;
@ -1411,19 +1766,19 @@ int AudioIO::StartStream(WaveTrackArray playbackTracks,
AILASetStartTime();
#endif
if (pStartTime)
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
delete mScrubQueue;
if (scrubbing)
{
// Calculate the new time position
mTime = std::max(mT0, std::min(mT1, *pStartTime));
// Reset mixer positions for all playback tracks
unsigned numMixers = mPlaybackTracks.GetCount();
for (unsigned ii = 0; ii < numMixers; ++ii)
mPlaybackMixers[ii]->Reposition(mTime);
if(mTimeTrack)
mWarpedTime = mTimeTrack->ComputeWarpedLength(mT0, mTime);
else
mWarpedTime = mTime - mT0;
mScrubQueue =
new ScrubQueue(mT0, mT1, options.scrubStartClockTimeMillis,
sampleRate, maxScrubSpeed, minScrubStutter);
mScrubDuration = 0;
mSilentScrub = false;
}
else
mScrubQueue = NULL;
#endif
// We signal the audio thread to call FillBuffers, to prime the RingBuffers
// so that they will have data in them when the stream starts. Having the
@ -1542,6 +1897,14 @@ void AudioIO::StartStreamCleanup(bool bOnlyBuffers)
mPortStreamV19 = NULL;
mStreamToken = 0;
}
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (mScrubQueue)
{
delete mScrubQueue;
mScrubQueue = 0;
}
#endif
}
#ifdef EXPERIMENTAL_MIDI_OUT
@ -1954,6 +2317,14 @@ void AudioIO::StopStream()
mNumCaptureChannels = 0;
mNumPlaybackChannels = 0;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (mScrubQueue)
{
delete mScrubQueue;
mScrubQueue = 0;
}
#endif
}
void AudioIO::SetPaused(bool state)
@ -1978,6 +2349,24 @@ bool AudioIO::IsPaused()
return mPaused;
}
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool AudioIO::EnqueueScrubByPosition(double endTime, double maxSpeed, bool maySkip)
{
if (mScrubQueue)
return mScrubQueue->Producer(-1.0, endTime, maxSpeed, false, maySkip);
else
return false;
}
bool AudioIO::EnqueueScrubBySignedSpeed(double speed, double maxSpeed, bool maySkip)
{
if (mScrubQueue)
return mScrubQueue->Producer(-1.0, speed, maxSpeed, true, maySkip);
else
return false;
}
#endif
bool AudioIO::IsBusy()
{
if (mStreamToken != 0)
@ -2014,6 +2403,15 @@ bool AudioIO::IsMonitoring()
return ( mPortStreamV19 && mStreamToken==0 );
}
double AudioIO::LimitStreamTime(double absoluteTime) const
{
// Allows for forward or backward play
if (ReversedTime())
return std::max(mT1, std::min(mT0, absoluteTime));
else
return std::max(mT0, std::min(mT1, absoluteTime));
}
double AudioIO::NormalizeStreamTime(double absoluteTime) const
{
// dmazzoni: This function is needed for two reasons:
@ -2028,13 +2426,12 @@ double AudioIO::NormalizeStreamTime(double absoluteTime) const
// mode. In this case, we should jump over a defined "gap" in the
// audio.
// msmeyer: Just to be sure, the returned stream time should
// never be smaller than the actual start time.
if (absoluteTime < mT0)
absoluteTime = mT0;
if (absoluteTime > mT1)
absoluteTime = mT1;
#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)
#endif
absoluteTime = LimitStreamTime(absoluteTime);
if (mCutPreviewGapLen > 0)
{
@ -2818,12 +3215,7 @@ void AudioIO::FillBuffers()
// things simple, we only write as much data as is vacant in
// ALL buffers, and advance the global time by that much.
// MB: subtract a few samples because the code below has rounding errors
int commonlyAvail = GetCommonlyAvailPlayback() - 10;
//
// Determine how much this will globally advance playback time
//
double secsAvail = commonlyAvail / mRate;
int available = GetCommonlyAvailPlayback() - 10;
//
// Don't fill the buffers at all unless we can do the
@ -2834,35 +3226,44 @@ void AudioIO::FillBuffers()
// The exception is if we're at the end of the selected
// region - then we should just fill the buffer.
//
if (secsAvail >= mMaxPlaybackSecsToCopy ||
(!mPlayLooped && (secsAvail > 0 && mWarpedTime+secsAvail >= mWarpedLength)))
if (available >= mPlaybackSamplesToCopy ||
(mPlayMode == PLAY_STRAIGHT &&
available > 0 &&
mWarpedTime+(available/mRate) >= mWarpedLength))
{
// Limit maximum buffer size (increases performance)
if (secsAvail > mMaxPlaybackSecsToCopy)
secsAvail = mMaxPlaybackSecsToCopy;
double deltat; // this is warped time
if (available > mPlaybackSamplesToCopy)
available = mPlaybackSamplesToCopy;
// msmeyer: When playing a very short selection in looped
// mode, the selection must be copied to the buffer multiple
// times, to ensure, that the buffer has a reasonable size
// This is the purpose of this loop.
// PRL: or, when scrubbing, we may get work repeatedly from the
// scrub queue.
bool done = false;
do {
deltat = secsAvail;
if( mWarpedTime + deltat > mWarpedLength )
{
deltat = mWarpedLength - mWarpedTime;
mWarpedTime = mWarpedLength;
if( deltat < 0.0 ) // this should never happen
deltat = 0.0;
}
// How many samples to produce for each channel.
long frames = available;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (mPlayMode == PLAY_SCRUB)
// scrubbing does not use warped time and length
frames = std::min(frames, mScrubDuration);
else
#endif
{
mWarpedTime += deltat;
double deltat = frames / mRate;
if (mWarpedTime + deltat > mWarpedLength)
{
frames = (mWarpedLength - mWarpedTime) * mRate;
mWarpedTime = mWarpedLength;
if (frames < 0) // this should never happen
frames = 0;
}
else
mWarpedTime += deltat;
}
secsAvail -= deltat;
for( i = 0; i < mPlaybackTracks.GetCount(); i++ )
{
// The mixer here isn't actually mixing: it's just doing
@ -2872,41 +3273,100 @@ void AudioIO::FillBuffers()
samplePtr warpedSamples;
//don't do anything if we have no length. In particular, Process() will fail an wxAssert
//that causes a crash since this is not the GUI thread and wxASSERT is a GUI call.
if(deltat > 0.0)
// don't generate either if scrubbing at zero speed.
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
const bool silent = (mPlayMode == PLAY_SCRUB) && mSilentScrub;
#else
const bool silent = false;
#endif
if (!silent && frames > 0)
{
processed = mPlaybackMixers[i]->Process(lrint(deltat * mRate));
processed = mPlaybackMixers[i]->Process(frames);
wxASSERT(processed <= frames);
warpedSamples = mPlaybackMixers[i]->GetBuffer();
mPlaybackBuffers[i]->Put(warpedSamples, floatSample, processed);
}
//if looping and processed is less than the full chunk/block/buffer that gets pulled from
//other longer tracks, then we still need to advance the ring buffers or
//we'll trip up on ourselves when we start them back up again.
//if not looping we never start them up again, so its okay to not do anything
if(processed < lrint(deltat * mRate) && mPlayLooped)
// 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(mLastSilentBufSize < lrint(deltat * mRate))
if(mLastSilentBufSize < frames)
{
//delete old if necessary
if(mSilentBuf)
DeleteSamples(mSilentBuf);
mLastSilentBufSize=lrint(deltat * mRate);
mLastSilentBufSize = frames;
mSilentBuf = NewSamples(mLastSilentBufSize, floatSample);
ClearSamples(mSilentBuf, floatSample, 0, mLastSilentBufSize);
}
mPlaybackBuffers[i]->Put(mSilentBuf, floatSample, lrint(deltat * mRate) - processed);
mPlaybackBuffers[i]->Put(mSilentBuf, floatSample, frames - processed);
}
}
// msmeyer: If playing looped, check if we are at the end of the buffer
// and if yes, restart from the beginning.
if (mPlayLooped && mWarpedTime >= mWarpedLength)
{
for (i = 0; i < mPlaybackTracks.GetCount(); i++)
mPlaybackMixers[i]->Restart();
mWarpedTime = 0.0;
}
available -= frames;
wxASSERT(available >= 0);
} while (mPlayLooped && secsAvail > 0 && deltat > 0);
switch (mPlayMode)
{
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
case PLAY_SCRUB:
{
mScrubDuration -= frames;
wxASSERT(mScrubDuration >= 0);
done = (available == 0);
if (!done && mScrubDuration <= 0)
{
long startSample, endSample;
mScrubQueue->Transformer(startSample, endSample, mScrubDuration);
if (mScrubDuration < 0)
{
// Can't play anything
// Stop even if we don't fill up available
mScrubDuration = 0;
done = true;
}
else
{
mSilentScrub = (endSample == startSample);
if (!mSilentScrub)
{
double startTime, endTime, speed;
startTime = startSample / mRate;
endTime = endSample / mRate;
speed = double(abs(endSample - startSample)) / mScrubDuration;
for (i = 0; i < mPlaybackTracks.GetCount(); i++)
mPlaybackMixers[i]->SetTimesAndSpeed(startTime, endTime, speed);
}
}
}
}
break;
#endif
case PLAY_LOOPED:
{
done = (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)
{
for (i = 0; i < mPlaybackTracks.GetCount(); i++)
mPlaybackMixers[i]->Restart();
mWarpedTime = 0.0;
}
}
break;
default:
done = true;
break;
}
} while (!done);
}
} // end of playback buffering
@ -3559,6 +4019,12 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
}
}
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// While scrubbing, ignore seek requests
if (gAudioIO->mSeek && gAudioIO->mPlayMode == AudioIO::PLAY_SCRUB)
gAudioIO->mSeek = 0.0;
else
#endif
if (gAudioIO->mSeek)
{
int token = gAudioIO->mStreamToken;
@ -3576,17 +4042,20 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
// Calculate the new time position
gAudioIO->mTime += gAudioIO->mSeek;
if (gAudioIO->mTime < gAudioIO->mT0)
gAudioIO->mTime = gAudioIO->mT0;
else if (gAudioIO->mTime > gAudioIO->mT1)
gAudioIO->mTime = gAudioIO->mT1;
gAudioIO->mTime = gAudioIO->LimitStreamTime(gAudioIO->mTime);
gAudioIO->mSeek = 0.0;
// Reset mixer positions and flush buffers for all tracks
if(gAudioIO->mTimeTrack)
gAudioIO->mWarpedTime = gAudioIO->mTimeTrack->ComputeWarpedLength(gAudioIO->mT0, gAudioIO->mTime);
// Following gives negative when mT0 > mTime
gAudioIO->mWarpedTime =
gAudioIO->mTimeTrack->ComputeWarpedLength
(gAudioIO->mT0, gAudioIO->mTime);
else
gAudioIO->mWarpedTime = gAudioIO->mTime - gAudioIO->mT0;
gAudioIO->mWarpedTime = abs(gAudioIO->mWarpedTime);
// Reset mixer positions and flush buffers for all tracks
for (i = 0; i < (unsigned int)numPlaybackTracks; i++)
{
gAudioIO->mPlaybackMixers[i]->Reposition(gAudioIO->mTime);
@ -3631,6 +4100,7 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
int group = 0;
int chanCnt = 0;
float rate = 0.0;
int maxLen = 0;
for (t = 0; t < numPlaybackTracks; t++)
{
WaveTrack *vt = gAudioIO->mPlaybackTracks[t];
@ -3667,7 +4137,7 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
// this is original code prior to r10680 -RBD
if (cut)
{
gAudioIO->mPlaybackBuffers[t]->Discard(framesPerBuffer);
len = gAudioIO->mPlaybackBuffers[t]->Discard(framesPerBuffer);
// keep going here.
// we may still need to issue a paComplete.
}
@ -3678,6 +4148,9 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
(int)framesPerBuffer);
chanCnt++;
}
// There should not be a difference of len in different loop passes...
// but anyway take a max.
maxLen = std::max(maxLen, len);
if (linkFlag)
@ -3718,12 +4191,15 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
// the end, then we've actually finished playing the entire
// selection.
// msmeyer: We never finish if we are playing looped
if (len == 0 && gAudioIO->mTime >= gAudioIO->mT1 &&
!gAudioIO->mPlayLooped)
{
callbackReturn = paComplete;
// PRL: or scrubbing.
if (len == 0 &&
gAudioIO->mPlayMode == AudioIO::PLAY_STRAIGHT) {
if ((gAudioIO->ReversedTime()
? gAudioIO->mTime <= gAudioIO->mT1
: gAudioIO->mTime >= gAudioIO->mT1))
callbackReturn = paComplete;
}
if (cut) // no samples to process, they've been discarded
continue;
@ -3772,6 +4248,14 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
chanCnt = 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 (gAudioIO->mPlayMode == AudioIO::PLAY_SCRUB)
gAudioIO->mTime = gAudioIO->mScrubQueue->Consumer(maxLen);
#endif
em.RealtimeProcessEnd();
gAudioIO->mLastPlaybackTimeMillis = ::wxGetLocalTimeMillis();
@ -3866,21 +4350,35 @@ int audacityAudioCallback(const void *inputBuffer, void *outputBuffer,
}
}
// Update the current time position
if (gAudioIO->mTimeTrack) {
// MB: this is why SolveWarpedLength is needed :)
gAudioIO->mTime = gAudioIO->mTimeTrack->SolveWarpedLength(gAudioIO->mTime, framesPerBuffer / gAudioIO->mRate);
} else {
gAudioIO->mTime += framesPerBuffer / gAudioIO->mRate;
// Update the current time position if not scrubbing
// (Already did it above, for scrubbing)
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
if (gAudioIO->mPlayMode != AudioIO::PLAY_SCRUB)
#endif
{
double delta = framesPerBuffer / gAudioIO->mRate;
if (gAudioIO->ReversedTime())
delta *= -1.0;
if (gAudioIO->mTimeTrack)
// MB: this is why SolveWarpedLength is needed :)
gAudioIO->mTime =
gAudioIO->mTimeTrack->SolveWarpedLength(gAudioIO->mTime, delta);
else
gAudioIO->mTime += delta;
}
// Wrap to start if looping
while (gAudioIO->mPlayLooped && gAudioIO->mTime >= gAudioIO->mT1)
if (gAudioIO->mPlayMode == AudioIO::PLAY_LOOPED)
{
// 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!
gAudioIO->mTime -= gAudioIO->mT1 - gAudioIO->mT0;
while (gAudioIO->ReversedTime()
? gAudioIO->mTime <= gAudioIO->mT1
: gAudioIO->mTime >= gAudioIO->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!
gAudioIO->mTime -= gAudioIO->mT1 - gAudioIO->mT0;
}
}
// Record the reported latency from PortAudio.

View File

@ -42,6 +42,7 @@ class Resample;
class TimeTrack;
class AudioThread;
class Meter;
class SelectedRegion;
class TimeTrack;
class wxDialog;
@ -74,6 +75,49 @@ DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_AUDIOIO_PLAYBACK, -1);
DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_AUDIOIO_CAPTURE, -1);
DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_AUDIOIO_MONITOR, -1);
// To avoid growing the argument list of StartStream, add fields here
struct AudioIOStartStreamOptions
{
AudioIOStartStreamOptions()
: timeTrack(NULL)
, listener(NULL)
, playLooped(false)
, cutPreviewGapStart(0.0)
, cutPreviewGapLen(0.0)
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
, scrubDelay(0.0)
, maxScrubSpeed(1.0)
, minScrubStutter(0.0)
, scrubStartClockTimeMillis(-1)
#endif
{}
TimeTrack *timeTrack;
AudioIOListener* listener;
bool playLooped;
double cutPreviewGapStart;
double cutPreviewGapLen;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
// Positive value indicates that scrubbing will happen
// (do not specify a time track, looping, or recording, which
// are all incompatible with scrubbing):
double scrubDelay;
// We need a limiting value for the speed of the first scrub
// interval:
double maxScrubSpeed;
// When maximum speed scrubbing skips to follow the mouse,
// this is the minimum amount of playback at the maximum speed:
double minScrubStutter;
// Scrubbing needs the time of start of the mouse movement that began
// the scrub:
wxLongLong scrubStartClockTimeMillis;
#endif
};
class AUDACITY_DLL_API AudioIO {
public:
@ -103,15 +147,9 @@ class AUDACITY_DLL_API AudioIO {
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray midiTracks,
#endif
TimeTrack *timeTrack, double sampleRate,
double t0, double t1,
AudioIOListener* listener,
bool playLooped = false,
double cutPreviewGapStart = 0.0,
double cutPreviewGapLen = 0.0,
// May be other than t0,
// but will be constrained between t0 and t1
const double *pStartTime = 0);
double sampleRate, double t0, double t1,
const AudioIOStartStreamOptions &options =
AudioIOStartStreamOptions());
/** \brief Stop recording, playback or input monitoring.
*
@ -123,6 +161,37 @@ class AUDACITY_DLL_API AudioIO {
* by the specified amount from where it is now */
void SeekStream(double seconds) { mSeek = seconds; }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
static double GetMaxScrubSpeed() { return 32.0; } // Is five octaves enough for your amusement?
static double GetMinScrubSpeed() { return 0.01; }
/** \brief enqueue a new end time, using the last end as the new start,
* to be played over the same duration, as between this and the last
* enqueuing (or the starting of the stream). Except, we do not exceed maximum
* scrub speed, so may need to adjust either the start or the end.
* If maySkip is true, then when mouse movement exceeds maximum scrub speed,
* adjust the beginning of the scrub interval rather than the end, so that
* the scrub skips or "stutters" to stay near the cursor.
* But if the "stutter" is too short for the minimum, then there is no effect
* on the work queue.
* Return true if some work was really enqueued.
*/
bool EnqueueScrubByPosition(double endTime, double maxSpeed, bool maySkip);
/** \brief enqueue a new positive or negative scrubbing speed,
* using the last end as the new start,
* to be played over the same duration, as between this and the last
* enqueueing (or the starting of the stream). Except, we do not exceed maximum
* scrub speed, so may need to adjust either the start or the end.
* If maySkip is true, then when mouse movement exceeds maximum scrub speed,
* adjust the beginning of the scrub interval rather than the end, so that
* the scrub skips or "stutters" to stay near the cursor.
* But if the "stutter" is too short for the minimum, then there is no effect
* on the work queue.
* Return true if some work was really enqueued.
*/
bool EnqueueScrubBySignedSpeed(double speed, double maxSpeed, bool maySkip);
#endif
/** \brief Returns true if audio i/o is busy starting, stopping, playing,
* or recording.
*
@ -281,9 +350,8 @@ class AUDACITY_DLL_API AudioIO {
*/
static int GetOptimalSupportedSampleRate();
/** \brief The time the stream has been playing for
/** \brief During playback, the (unwarped) track time most recently played
*
* This is given in seconds based on starting at t0
* When playing looped, this will start from t0 again,
* too. So the returned time should be always between
* t0 and t1
@ -375,10 +443,10 @@ private:
#endif
/** \brief Get the number of audio samples free in all of the playback
* buffers.
*
* Returns the smallest of the buffer free space values in the event that
* they are different. */
* buffers.
*
* Returns the smallest of the buffer free space values in the event that
* they are different. */
int GetCommonlyAvailPlayback();
/** \brief Get the number of audio samples ready in all of the recording
@ -422,6 +490,12 @@ private:
/** \brief How many sample rates to try */
static const int NumRatesToTry;
bool ReversedTime() const
{
return mT1 < mT0;
}
double LimitStreamTime(double absoluteTime) const;
double NormalizeStreamTime(double absoluteTime) const;
/** \brief Clean up after StartStream if it fails.
@ -506,7 +580,7 @@ private:
double mSeek;
double mPlaybackRingBufferSecs;
double mCaptureRingBufferSecs;
double mMaxPlaybackSecsToCopy;
long mPlaybackSamplesToCopy;
double mMinCaptureSecsToCopy;
bool mPaused;
PaStream *mPortStreamV19;
@ -535,7 +609,7 @@ private:
Meter *mInputMeter;
Meter *mOutputMeter;
bool mUpdateMeters;
bool mUpdatingMeters;
volatile bool mUpdatingMeters;
#if USE_PORTMIXER
PxMixer *mPortMixer;
@ -553,7 +627,13 @@ private:
bool mInputMixerWorks;
float mMixerOutputVol;
bool mPlayLooped;
enum {
PLAY_STRAIGHT,
PLAY_LOOPED,
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
PLAY_SCRUB,
#endif
} mPlayMode;
double mCutPreviewGapStart;
double mCutPreviewGapLen;
@ -612,6 +692,14 @@ private:
// Serialize main thread and PortAudio thread's attempts to pause and change
// the state used by the third, Audio thread.
wxMutex mSuspendAudioThread;
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
struct ScrubQueue;
ScrubQueue *mScrubQueue;
bool mSilentScrub;
long mScrubDuration;
#endif
};
#endif

View File

@ -1430,31 +1430,42 @@ double Envelope::SolveIntegralOfInverse( double t0, double area )
{
if(area == 0.0)
return t0;
if(area < 0.0)
{
fprintf( stderr, "SolveIntegralOfInverse called with negative area, this is not supported!\n" );
return t0;
}
unsigned int count = mEnv.Count();
if(count == 0) // 'empty' envelope
return t0 + area * mDefaultValue;
double lastT, lastVal;
unsigned int i; // this is the next point to check
int i; // this is the next point to check
if(t0 < mEnv[0]->GetT()) // t0 preceding the first point
{
i = 1;
lastT = mEnv[0]->GetT();
lastVal = mEnv[0]->GetVal();
double added = (lastT - t0) / lastVal;
if(added >= area)
if (area < 0) {
return t0 + area * mEnv[0]->GetVal();
area -= added;
}
else {
i = 1;
lastT = mEnv[0]->GetT();
lastVal = mEnv[0]->GetVal();
double added = (lastT - t0) / lastVal;
if(added >= area)
return t0 + area * mEnv[0]->GetVal();
area -= added;
}
}
else if(t0 >= mEnv[count - 1]->GetT()) // t0 following the last point
{
return t0 + area * mEnv[count - 1]->GetVal();
if (area < 0) {
i = count - 2;
lastT = mEnv[count - 1]->GetT();
lastVal = mEnv[count - 1]->GetVal();
double added = (lastT - t0) / lastVal; // negative
if(added <= area)
return t0 + area * mEnv[count - 1]->GetVal();
area -= added;
}
else {
return t0 + area * mEnv[count - 1]->GetVal();
}
}
else // t0 enclosed by points
{
@ -1463,25 +1474,52 @@ double Envelope::SolveIntegralOfInverse( double t0, double area )
BinarySearchForTime(lo, hi, t0);
lastVal = InterpolatePoints(mEnv[lo]->GetVal(), mEnv[hi]->GetVal(), (t0 - mEnv[lo]->GetT()) / (mEnv[hi]->GetT() - mEnv[lo]->GetT()), mDB);
lastT = t0;
i = hi; // the point immediately after t0.
if (area < 0)
i = lo;
else
i = hi; // the point immediately after t0.
}
// loop through the rest of the envelope points until we get to t1
while (1)
{
if(i >= count) // the requested range extends beyond the last point
if (area < 0) {
// loop BACKWARDS through the rest of the envelope points until we get to t1
// (which is less than t0)
while (1)
{
return lastT + area * lastVal;
if(i < 0) // the requested range extends beyond the leftmost point
{
return lastT + area * lastVal;
}
else
{
double added =
-IntegrateInverseInterpolated(mEnv[i]->GetVal(), lastVal, lastT - mEnv[i]->GetT(), mDB);
if(added <= area)
return lastT - SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), lastT - mEnv[i]->GetT(), -area, mDB);
area -= added;
lastT = mEnv[i]->GetT();
lastVal = mEnv[i]->GetVal();
--i;
}
}
else
}
else {
// loop through the rest of the envelope points until we get to t1
while (1)
{
double added = IntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB);
if(added >= area)
return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, area, mDB);
area -= added;
lastT = mEnv[i]->GetT();
lastVal = mEnv[i]->GetVal();
i++;
if(i >= count) // the requested range extends beyond the last point
{
return lastT + area * lastVal;
}
else
{
double added = IntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, mDB);
if(added >= area)
return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i]->GetVal(), mEnv[i]->GetT() - lastT, area, mDB);
area -= added;
lastT = mEnv[i]->GetT();
lastVal = mEnv[i]->GetVal();
i++;
}
}
}
}

View File

@ -99,8 +99,8 @@ class Envelope : public XMLTagHandler {
void Flatten(double value);
int GetDragPoint(void) {return mDragPoint;}
double GetMinValue() { return mMinValue; }
double GetMaxValue() { return mMaxValue; }
double GetMinValue() const { return mMinValue; }
double GetMaxValue() const { return mMaxValue; }
void SetRange(double minValue, double maxValue);
double ClampValue(double value) { return std::max(mMinValue, std::min(mMaxValue, value)); }

View File

@ -107,9 +107,6 @@
// Paul Licameli (PRL) 5 Oct 2014
#define EXPERIMENTAL_SPECTRAL_EDITING
// Paul Licameli (PRL) 29 Nov 2014
// #define EXPERIMENTAL_SCRUBBING
// Paul Licameli (PRL) 29 Nov 2014
// #define EXPERIMENTAL_IMPROVED_SEEKING
@ -169,4 +166,8 @@
// Define to enable Nyquist audio clip boundary control (Steve Daulton Dec 2014)
// #define EXPERIMENTAL_NYQUIST_SPLIT_CONTROL
// Paul Licameli (PRL) 16 Apr 2015
//Support for scrubbing in the AudioIO engine, without calls to it
#define EXPERIMENTAL_SCRUBBING_SUPPORT
#endif

View File

@ -2013,7 +2013,8 @@ void AudacityProject::OnPlayOneSecond()
double pos = mTrackPanel->GetMostRecentXPos();
mLastPlayMode = oneSecondPlay;
GetControlToolBar()->PlayPlayRegion(pos - 0.5, pos + 0.5);
GetControlToolBar()->PlayPlayRegion
(SelectedRegion(pos - 0.5, pos + 0.5), GetDefaultPlayOptions());
}
@ -2056,7 +2057,8 @@ void AudacityProject::OnPlayToSelection()
// only when playing a short region, less than or equal to a second.
// mLastPlayMode = ((t1-t0) > 1.0) ? normalPlay : oneSecondPlay;
GetControlToolBar()->PlayPlayRegion(t0, t1);
GetControlToolBar()->PlayPlayRegion
(SelectedRegion(t0, t1), GetDefaultPlayOptions());
}
// The next 4 functions provide a limited version of the
@ -2073,7 +2075,7 @@ void AudacityProject::OnPlayBeforeSelectionStart()
mLastPlayMode = oneSecondPlay; // this disables auto scrolling, as in OnPlayToSelection()
GetControlToolBar()->PlayPlayRegion(t0 - beforeLen, t0);
GetControlToolBar()->PlayPlayRegion(SelectedRegion(t0 - beforeLen, t0), GetDefaultPlayOptions());
}
void AudacityProject::OnPlayAfterSelectionStart()
@ -2089,9 +2091,9 @@ void AudacityProject::OnPlayAfterSelectionStart()
mLastPlayMode = oneSecondPlay; // this disables auto scrolling, as in OnPlayToSelection()
if ( t1 - t0 > 0.0 && t1 - t0 < afterLen )
GetControlToolBar()->PlayPlayRegion(t0, t1);
GetControlToolBar()->PlayPlayRegion(SelectedRegion(t0, t1), GetDefaultPlayOptions());
else
GetControlToolBar()->PlayPlayRegion(t0, t0 + afterLen);
GetControlToolBar()->PlayPlayRegion(SelectedRegion(t0, t0 + afterLen), GetDefaultPlayOptions());
}
void AudacityProject::OnPlayBeforeSelectionEnd()
@ -2107,9 +2109,9 @@ void AudacityProject::OnPlayBeforeSelectionEnd()
mLastPlayMode = oneSecondPlay; // this disables auto scrolling, as in OnPlayToSelection()
if ( t1 - t0 > 0.0 && t1 - t0 < beforeLen )
GetControlToolBar()->PlayPlayRegion(t0, t1);
GetControlToolBar()->PlayPlayRegion(SelectedRegion(t0, t1), GetDefaultPlayOptions());
else
GetControlToolBar()->PlayPlayRegion(t1 - beforeLen, t1);
GetControlToolBar()->PlayPlayRegion(SelectedRegion(t1 - beforeLen, t1), GetDefaultPlayOptions());
}
@ -2124,7 +2126,7 @@ void AudacityProject::OnPlayAfterSelectionEnd()
mLastPlayMode = oneSecondPlay; // this disables auto scrolling, as in OnPlayToSelection()
GetControlToolBar()->PlayPlayRegion(t1, t1 + afterLen);
GetControlToolBar()->PlayPlayRegion(SelectedRegion(t1, t1 + afterLen), GetDefaultPlayOptions());
}
void AudacityProject::OnPlayLooped()

View File

@ -161,7 +161,8 @@ bool MixAndRender(TrackList *tracks, TrackFactory *trackFactory,
endTime = mixEndTime;
}
Mixer *mixer = new Mixer(numWaves, waveArray, tracks->GetTimeTrack(),
Mixer *mixer = new Mixer(numWaves, waveArray,
Mixer::WarpOptions(tracks->GetTimeTrack()),
startTime, endTime, mono ? 1 : 2, maxBlockLen, false,
rate, format);
@ -226,8 +227,28 @@ bool MixAndRender(TrackList *tracks, TrackFactory *trackFactory,
return (updateResult == eProgressSuccess || updateResult == eProgressStopped);
}
Mixer::WarpOptions::WarpOptions(double min, double max)
: timeTrack(0), minSpeed(min), maxSpeed(max)
{
if (minSpeed < 0)
{
wxASSERT(false);
minSpeed = 0;
}
if (maxSpeed < 0)
{
wxASSERT(false);
maxSpeed = 0;
}
if (minSpeed > maxSpeed)
{
wxASSERT(false);
std::swap(minSpeed, maxSpeed);
}
}
Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks,
TimeTrack *timeTrack,
const WarpOptions &warpOptions,
double startTime, double stopTime,
int numOutChannels, int outBufferSize, bool outInterleaved,
double outRate, sampleFormat outFormat,
@ -238,12 +259,15 @@ Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks,
mHighQuality = highQuality;
mNumInputTracks = numInputTracks;
mInputTrack = new WaveTrack*[mNumInputTracks];
// mSamplePos holds for each track the next sample position not
// yet processed.
mSamplePos = new sampleCount[mNumInputTracks];
for(i=0; i<mNumInputTracks; i++) {
mInputTrack[i] = inputTracks[i];
mSamplePos[i] = inputTracks[i]->TimeToLongSamples(startTime);
}
mTimeTrack = timeTrack;
mTimeTrack = warpOptions.timeTrack;
mT0 = startTime;
mT1 = stopTime;
mTime = startTime;
@ -251,6 +275,7 @@ Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks,
mBufferSize = outBufferSize;
mInterleaved = outInterleaved;
mRate = outRate;
mSpeed = 1.0;
mFormat = outFormat;
mApplyTrackGains = true;
mGains = new float[mNumChannels];
@ -277,23 +302,45 @@ Mixer::Mixer(int numInputTracks, WaveTrack **inputTracks,
}
mFloatBuffer = new float[mInterleavedBufferSize];
// This is the number of samples grabbed in one go from a track
// and placed in a queue, when mixing with resampling.
// (Should we use WaveTrack::GetBestBlockSize instead?)
mQueueMaxLen = 65536;
// But cut the queue into blocks of this finer size
// for variable rate resampling. Each block is resampled at some
// constant rate.
mProcessLen = 1024;
// Position in each queue of the start of the next block to resample.
mQueueStart = new int[mNumInputTracks];
// For each queue, the number of available samples after the queue start.
mQueueLen = new int[mNumInputTracks];
mSampleQueue = new float *[mNumInputTracks];
mResample = new Resample*[mNumInputTracks];
for(i=0; i<mNumInputTracks; i++) {
double factor = (mRate / mInputTrack[i]->GetRate());
if (timeTrack) {
double minFactor, maxFactor;
if (mTimeTrack) {
// variable rate resampling
mResample[i] = new Resample(mHighQuality,
factor / timeTrack->GetRangeUpper(),
factor / timeTrack->GetRangeLower());
} else {
mResample[i] = new Resample(mHighQuality, factor, factor); // constant rate resampling
mbVariableRates = true;
minFactor = factor / mTimeTrack->GetRangeUpper();
maxFactor = factor / mTimeTrack->GetRangeLower();
}
else if (warpOptions.minSpeed > 0.0 && warpOptions.maxSpeed > 0.0) {
// variable rate resampling
mbVariableRates = true;
minFactor = factor / warpOptions.maxSpeed;
maxFactor = factor / warpOptions.minSpeed;
}
else {
// constant rate resampling
mbVariableRates = false;
minFactor = maxFactor = factor;
}
mResample[i] = new Resample(mHighQuality, minFactor, maxFactor);
mSampleQueue[i] = new float[mQueueMaxLen];
mQueueStart[i] = 0;
mQueueLen[i] = 0;
@ -377,10 +424,9 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track,
int *queueStart, int *queueLen,
Resample * pResample)
{
double trackRate = track->GetRate();
double initialWarp = mRate / trackRate;
double tstep = 1.0 / trackRate;
double t = (*pos - *queueLen) / trackRate;
const double trackRate = track->GetRate();
const double initialWarp = mRate / mSpeed / trackRate;
const double tstep = 1.0 / trackRate;
int sampleSize = SAMPLE_SIZE(floatSample);
sampleCount out = 0;
@ -397,45 +443,64 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track,
*/
// Find the last sample
sampleCount endPos;
double endTime = track->GetEndTime();
if (endTime > mT1) {
endPos = track->TimeToLongSamples(mT1);
}
else {
endPos = track->TimeToLongSamples(endTime);
}
double startTime = track->GetStartTime();
const sampleCount endPos =
track->TimeToLongSamples(std::max(startTime, std::min(endTime, mT1)));
const sampleCount startPos =
track->TimeToLongSamples(std::max(startTime, std::min(endTime, mT0)));
const bool backwards = (endPos < startPos);
// Find the time corresponding to the start of the queue, for use with time track
double t = (*pos + (backwards ? *queueLen : - *queueLen)) / trackRate;
while (out < mMaxOut) {
if (*queueLen < mProcessLen) {
// Shift pending portion to start of the buffer
memmove(queue, &queue[*queueStart], (*queueLen) * sampleSize);
*queueStart = 0;
int getLen = mQueueMaxLen - *queueLen;
int getLen =
std::min((backwards ? *pos - endPos : endPos - *pos),
sampleCount(mQueueMaxLen - *queueLen));
// Constrain
if (*pos + getLen > endPos) {
getLen = endPos - *pos;
}
// Nothing to do if past end of track
// Nothing to do if past end of play interval
if (getLen > 0) {
track->Get((samplePtr)&queue[*queueLen],
floatSample,
*pos,
getLen);
if (backwards) {
track->Get((samplePtr)&queue[*queueLen],
floatSample,
*pos - (getLen - 1),
getLen);
track->GetEnvelopeValues(mEnvValues,
getLen,
(*pos) / trackRate,
tstep);
track->GetEnvelopeValues(mEnvValues,
getLen,
(*pos - (getLen- 1)) / trackRate,
tstep);
*pos -= getLen;
}
else {
track->Get((samplePtr)&queue[*queueLen],
floatSample,
*pos,
getLen);
track->GetEnvelopeValues(mEnvValues,
getLen,
(*pos) / trackRate,
tstep);
*pos += getLen;
}
for (int i = 0; i < getLen; i++) {
queue[(*queueLen) + i] *= mEnvValues[i];
}
if (backwards)
ReverseSamples((samplePtr)&queue[0], floatSample,
*queueStart, getLen);
*queueLen += getLen;
*pos += getLen;
}
}
@ -452,8 +517,13 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track,
// as a result of this the warp factor may be slightly wrong, so AudioIO will stop too soon
// or too late (resulting in missing sound or inserted silence). This can't be fixed
// without changing the way the resampler works, because the number of input samples that will be used
// is unpredictable. Maybe it can be compensated lated though.
factor *= mTimeTrack->ComputeWarpFactor(t, t + (double)thisProcessLen / trackRate);
// is unpredictable. Maybe it can be compensated later though.
if (backwards)
factor *= mTimeTrack->ComputeWarpFactor
(t - (double)thisProcessLen / trackRate + tstep, t + tstep);
else
factor *= mTimeTrack->ComputeWarpFactor
(t, t + (double)thisProcessLen / trackRate);
}
int input_used;
@ -472,7 +542,7 @@ sampleCount Mixer::MixVariableRates(int *channelFlags, WaveTrack *track,
*queueStart += input_used;
*queueLen -= input_used;
out += outgen;
t += (input_used / trackRate);
t += ((backwards ? -input_used : input_used) / trackRate);
if (last) {
break;
@ -504,24 +574,46 @@ sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrack *track,
{
int slen = mMaxOut;
int c;
double t = *pos / track->GetRate();
double trackEndTime = track->GetEndTime();
double tEnd = trackEndTime > mT1 ? mT1 : trackEndTime;
const double t = *pos / track->GetRate();
const double trackEndTime = track->GetEndTime();
const double trackStartTime = track->GetStartTime();
const double tEnd = std::max(trackStartTime, std::min(trackEndTime, mT1));
const double tStart = std::max(trackStartTime, std::min(trackEndTime, mT0));
const bool backwards = (tEnd < tStart);
//don't process if we're at the end of the selection or track.
if (t>=tEnd)
if ((backwards ? t <= tEnd : t >= tEnd))
return 0;
//if we're about to approach the end of the track or selection, figure out how much we need to grab
if (t + slen/track->GetRate() > tEnd)
slen = (int)((tEnd - t) * track->GetRate() + 0.5);
if (backwards) {
if (t - slen/track->GetRate() < tEnd)
slen = (int)((t - tEnd) * track->GetRate() + 0.5);
}
else {
if (t + slen/track->GetRate() > tEnd)
slen = (int)((tEnd - t) * track->GetRate() + 0.5);
}
if (slen > mMaxOut)
slen = mMaxOut;
track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen);
track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate);
for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
if (backwards) {
track->Get((samplePtr)mFloatBuffer, floatSample, *pos - (slen - 1), slen);
track->GetEnvelopeValues(mEnvValues, slen, t - (slen - 1) / mRate, 1.0 / mRate);
for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
ReverseSamples((samplePtr)mFloatBuffer, floatSample, 0, slen);
*pos -= slen;
}
else {
track->Get((samplePtr)mFloatBuffer, floatSample, *pos, slen);
track->GetEnvelopeValues(mEnvValues, slen, t, 1.0 / mRate);
for(int i=0; i<slen; i++)
mFloatBuffer[i] *= mEnvValues[i]; // Track gain control will go here?
*pos += slen;
}
for(c=0; c<mNumChannels; c++)
if (mApplyTrackGains)
@ -532,8 +624,6 @@ sampleCount Mixer::MixSameRate(int *channelFlags, WaveTrack *track,
MixBuffers(mNumChannels, channelFlags, mGains,
(samplePtr)mFloatBuffer, mTemp, slen, mInterleaved);
*pos += slen;
return slen;
}
@ -545,7 +635,6 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
// return 0;
int i, j;
sampleCount out;
sampleCount maxOut = 0;
int *channelFlags = new int[mNumChannels];
@ -580,42 +669,41 @@ sampleCount Mixer::Process(sampleCount maxToProcess)
break;
}
}
if (mTimeTrack || track->GetRate() != mRate)
out = MixVariableRates(channelFlags, track,
&mSamplePos[i], mSampleQueue[i],
&mQueueStart[i], &mQueueLen[i], mResample[i]);
if (mbVariableRates || track->GetRate() != mRate)
maxOut = std::max(maxOut,
MixVariableRates(channelFlags, track,
&mSamplePos[i], mSampleQueue[i],
&mQueueStart[i], &mQueueLen[i], mResample[i]));
else
out = MixSameRate(channelFlags, track, &mSamplePos[i]);
if (out > maxOut)
maxOut = out;
maxOut = std::max(maxOut,
MixSameRate(channelFlags, track, &mSamplePos[i]));
double t = (double)mSamplePos[i] / (double)track->GetRate();
if(t > mTime)
if (mT0 > mT1)
mTime = std::max(t, mT1);
else
mTime = std::min(t, mT1);
}
if(mInterleaved) {
for(int c=0; c<mNumChannels; c++) {
CopySamples(mTemp[0] + (c * SAMPLE_SIZE(floatSample)),
floatSample,
mBuffer[0] + (c * SAMPLE_SIZE(mFormat)),
mFormat,
maxOut,
mHighQuality,
mNumChannels,
mNumChannels);
floatSample,
mBuffer[0] + (c * SAMPLE_SIZE(mFormat)),
mFormat,
maxOut,
mHighQuality,
mNumChannels,
mNumChannels);
}
}
else {
for(int c=0; c<mNumBuffers; c++) {
CopySamples(mTemp[c],
floatSample,
mBuffer[c],
mFormat,
maxOut,
mHighQuality);
CopySamples(mTemp[c],
floatSample,
mBuffer[c],
mFormat,
maxOut,
mHighQuality);
}
}
// MB: this doesn't take warping into account, replaced with code based on mSamplePos
@ -661,10 +749,11 @@ void Mixer::Reposition(double t)
int i;
mTime = t;
if( mTime < mT0 )
mTime = mT0;
if( mTime > mT1 )
mTime = mT1;
const bool backwards = (mT1 < mT0);
if (backwards)
mTime = std::max(mT1, (std::min(mT0, mTime)));
else
mTime = std::max(mT0, (std::min(mT1, mTime)));
for(i=0; i<mNumInputTracks; i++) {
mSamplePos[i] = mInputTrack[i]->TimeToLongSamples(mTime);
@ -673,6 +762,15 @@ void Mixer::Reposition(double t)
}
}
void Mixer::SetTimesAndSpeed(double t0, double t1, double speed)
{
wxASSERT(isfinite(speed));
mT0 = t0;
mT1 = t1;
mSpeed = abs(speed);
Reposition(t0);
}
MixerSpec::MixerSpec( int numTracks, int maxNumChannels )
{
mNumTracks = mNumChannels = numTracks;

View File

@ -67,12 +67,29 @@ class AUDACITY_DLL_API MixerSpec
class AUDACITY_DLL_API Mixer {
public:
//
// An argument to Mixer's constructor
class WarpOptions
{
public:
explicit WarpOptions(TimeTrack *t)
: timeTrack(t), minSpeed(0.0), maxSpeed(0.0)
{}
WarpOptions(double min, double max);
private:
friend class Mixer;
TimeTrack *timeTrack;
double minSpeed, maxSpeed;
};
//
// Constructor / Destructor
//
Mixer(int numInputTracks, WaveTrack **inputTracks,
TimeTrack *timeTrack,
const WarpOptions &warpOptions,
double startTime, double stopTime,
int numOutChannels, int outBufferSize, bool outInterleaved,
double outRate, sampleFormat outFormat,
@ -104,6 +121,9 @@ class AUDACITY_DLL_API Mixer {
/// Process() is called.
void Reposition(double t);
// Used in scrubbing.
void SetTimesAndSpeed(double t0, double t1, double speed);
/// Current time in seconds (unwarped, i.e. always between startTime and stopTime)
/// This value is not accurate, it's useful for progress bars and indicators, but nothing else.
double MixGetCurrentTime();
@ -129,6 +149,7 @@ class AUDACITY_DLL_API Mixer {
// Input
int mNumInputTracks;
WaveTrack **mInputTrack;
bool mbVariableRates;
TimeTrack *mTimeTrack;
sampleCount *mSamplePos;
bool mApplyTrackGains;
@ -157,6 +178,7 @@ class AUDACITY_DLL_API Mixer {
samplePtr *mTemp;
float *mFloatBuffer;
double mRate;
double mSpeed;
bool mHighQuality;
};

View File

@ -1037,6 +1037,14 @@ AudacityProject::~AudacityProject()
wxGetApp().GetRecentFiles()->RemoveMenu(mRecentFilesMenu);
}
AudioIOStartStreamOptions AudacityProject::GetDefaultPlayOptions()
{
AudioIOStartStreamOptions options;
options.timeTrack = GetTracks()->GetTimeTrack();
options.listener = this;
return options;
}
void AudacityProject::UpdatePrefsVariables()
{
gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &mShowId3Dialog, true);

View File

@ -82,6 +82,7 @@ class LyricsWindow;
class MixerBoard;
class MixerBoardFrame;
struct AudioIOStartStreamOptions;
AudacityProject *CreateNewAudacityProject();
AUDACITY_DLL_API AudacityProject *GetActiveProject();
@ -135,6 +136,8 @@ class AUDACITY_DLL_API AudacityProject: public wxFrame,
const wxPoint & pos, const wxSize & size);
virtual ~AudacityProject();
AudioIOStartStreamOptions GetDefaultPlayOptions();
TrackList *GetTracks() { return mTracks; }
UndoManager *GetUndoManager() { return &mUndoManager; }

View File

@ -92,6 +92,24 @@ void ClearSamples(samplePtr src, sampleFormat format,
memset(src + start*size, 0, len*size);
}
void ReverseSamples(samplePtr src, sampleFormat format,
int start, int len)
{
int size = SAMPLE_SIZE(format);
samplePtr first = src + start * size;
samplePtr last = src + (start + len - 1) * size;
enum { fixedSize = SAMPLE_SIZE(floatSample) };
wxASSERT(size <= fixedSize);
char temp[fixedSize];
while (first < last) {
memcpy(temp, first, size);
memcpy(first, last, size);
memcpy(last, temp, size);
first += size;
last -= size;
}
}
void CopySamples(samplePtr src, sampleFormat srcFormat,
samplePtr dst, sampleFormat dstFormat,
unsigned int len,

View File

@ -70,6 +70,9 @@ void CopySamplesNoDither(samplePtr src, sampleFormat srcFormat,
void ClearSamples(samplePtr buffer, sampleFormat format,
int start, int len);
void ReverseSamples(samplePtr buffer, sampleFormat format,
int start, int len);
//
// This must be called on startup and everytime new ditherers
// are set in preferences.

View File

@ -1248,7 +1248,7 @@ bool Sequence::Set(samplePtr buffer, sampleFormat format,
}
bool Sequence::GetWaveDisplay(float *min, float *max, float *rms,int* bl,
int len, sampleCount *where,
int len, const sampleCount *where,
double samplesPerPixel)
{
sampleCount s0 = where[0];

View File

@ -80,7 +80,7 @@ class Sequence: public XMLTagHandler {
sampleCount start, sampleCount len);
bool GetWaveDisplay(float *min, float *max, float *rms,int* bl,
int len, sampleCount *where,
int len, const sampleCount *where,
double samplesPerPixel);
bool Copy(sampleCount s0, sampleCount s1, Sequence **dest);

View File

@ -585,12 +585,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
mSelStartValid = false;
mSelStart = 0;
#ifdef EXPERIMENTAL_SCRUBBING
mScrubbing = false;
mLastScrubTime = 0;
mLastScrubPosition = 0;
#endif
mInitialTrackSelection = new std::vector<bool>;
}
@ -979,7 +973,7 @@ void TrackPanel::OnTimer()
AudacityProject *p = GetProject();
if ((p->GetAudioIOToken() > 0) &&
gAudioIO->IsStreamActive(p->GetAudioIOToken()))
gAudioIO->IsStreamActive(p->GetAudioIOToken()))
{
// Update lyrics display.
LyricsWindow* pLyricsWindow = p->GetLyricsWindow();
@ -997,39 +991,13 @@ void TrackPanel::OnTimer()
// audacityAudioCallback where it calls gAudioIO->mOutputMeter->UpdateDisplay().
MixerBoard* pMixerBoard = this->GetMixerBoard();
if (pMixerBoard &&
(p->GetAudioIOToken() > 0) &&
gAudioIO->IsStreamActive(p->GetAudioIOToken()))
(p->GetAudioIOToken() > 0) &&
gAudioIO->IsStreamActive(p->GetAudioIOToken()))
{
pMixerBoard->UpdateMeters(gAudioIO->GetStreamTime(),
(p->mLastPlayMode == loopedPlay));
(p->mLastPlayMode == loopedPlay));
}
#ifdef EXPERIMENTAL_SCRUBBING
if (mScrubbing
&&
gAudioIO->IsStreamActive(GetProject()->GetAudioIOToken()))
{
if (gAudioIO->GetLastPlaybackTime() < mLastScrubTime) {
// Allow some audio catch up
}
else {
wxMouseState state(::wxGetMouseState());
wxCoord xx = state.GetX();
ScreenToClient(&xx, NULL);
double leadPosition = PositionToTime(xx, GetLeftOffset());
if (mLastScrubPosition != leadPosition) {
wxLongLong clockTime = ::wxGetLocalTimeMillis();
double lagPosition = gAudioIO->GetStreamTime();
gAudioIO->SeekStream(leadPosition - lagPosition);
mLastScrubPosition = leadPosition;
mLastScrubTime = clockTime;
}
}
}
#endif
// Check whether we were playing or recording, but the stream has stopped.
if (p->GetAudioIOToken()>0 &&
!gAudioIO->IsStreamActive(p->GetAudioIOToken()))
@ -1411,27 +1379,29 @@ void TrackPanel::OnPaint(wxPaintEvent & /* event */)
mRefreshBacking = false;
// Redraw the backing bitmap
DrawTracks( &mBackingDC );
DrawTracks(&mBackingDC);
// Copy it to the display
dc->Blit( 0, 0, mBacking->GetWidth(), mBacking->GetHeight(), &mBackingDC, 0, 0 );
dc->Blit(0, 0, mBacking->GetWidth(), mBacking->GetHeight(), &mBackingDC, 0, 0);
}
else
{
// Copy full, possibly clipped, damage rectange
dc->Blit( box.x, box.y, box.width, box.height, &mBackingDC, box.x, box.y );
dc->Blit(box.x, box.y, box.width, box.height, &mBackingDC, box.x, box.y);
}
// Done with the clipped DC
delete dc;
// Drawing now goes directly to the client area
wxClientDC cdc( this );
wxClientDC cdc(this);
// Update the indicator in case it was damaged if this project is playing
// PRL: mIndicatorShowing never becomes true!
AudacityProject* p = GetProject();
if (!gAudioIO->IsPaused() &&
( mIndicatorShowing || gAudioIO->IsStreamActive(p->GetAudioIOToken())))
(mIndicatorShowing || gAudioIO->IsStreamActive(p->GetAudioIOToken())))
{
// We just want to repair, not update the old, so set the second param to true.
// This is important because this onPaint could be for just some of the tracks.
@ -1439,8 +1409,8 @@ void TrackPanel::OnPaint(wxPaintEvent & /* event */)
}
// Draw the cursor
if( mViewInfo->selectedRegion.isPoint())
DoDrawCursor( cdc );
if (mViewInfo->selectedRegion.isPoint())
DoDrawCursor(cdc);
#if DEBUG_DRAW_TIMING
sw.Pause();
@ -2054,52 +2024,6 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
}
} else if (event.LeftUp() || event.RightUp()) {
#ifdef EXPERIMENTAL_SCRUBBING
if(mScrubbing) {
if (gAudioIO->IsBusy()) {
AudacityProject *p = GetActiveProject();
if (p) {
ControlToolBar * ctb = p->GetControlToolBar();
ctb->StopPlaying();
}
}
if (mAdjustSelectionEdges) {
if (event.ShiftDown()) {
// Adjust time selection as if shift-left click at end
const double selend = PositionToTime(event.m_x, GetLeftOffset());
SelectionBoundary boundary = ChooseTimeBoundary(selend, false);
switch (boundary)
{
case SBLeft:
mViewInfo->selectedRegion.setT0(selend);
break;
case SBRight:
mViewInfo->selectedRegion.setT1(selend);
break;
default:
wxASSERT(false);
}
UpdateSelectionDisplay();
}
else {
// Adjust time selection as if left click
StartSelection(event.m_x, r.x);
DisplaySelection();
}
}
mScrubbing = false;
}
else if (event.CmdDown()) {
// A control-click will set just the indicator to the clicked spot,
// and turn playback on -- but delayed until button up,
// and only if no intervening drag
StartOrJumpPlayback(event);
}
// Don't return yet
#endif
if (mSnapManager) {
delete mSnapManager;
mSnapManager = NULL;
@ -2197,7 +2121,8 @@ void TrackPanel::StartOrJumpPlayback(wxMouseEvent &event)
//the clicked point
ControlToolBar * ctb = p->GetControlToolBar();
//ctb->SetPlay(true);// Not needed as done in PlayPlayRegion
ctb->PlayPlayRegion(clicktime, endtime,false) ;
ctb->PlayPlayRegion
(SelectedRegion(clicktime, endtime), p->GetDefaultPlayOptions());
}
else
{
@ -2208,37 +2133,12 @@ void TrackPanel::StartOrJumpPlayback(wxMouseEvent &event)
//require a new method in ControlToolBar: SetPause();
ControlToolBar * ctb = p->GetControlToolBar();
ctb->StopPlaying();
ctb->PlayPlayRegion(clicktime,endtime,false) ;
ctb->PlayPlayRegion(SelectedRegion(clicktime, endtime), p->GetDefaultPlayOptions());
}
}
}
#ifdef EXPERIMENTAL_SCRUBBING
void TrackPanel::StartScrubbing(double position)
{
AudacityProject *p = GetActiveProject();
if (p &&
// Should I make a bigger tolerance than zero?
mLastScrubPosition != position) {
ControlToolBar * ctb = p->GetControlToolBar();
bool busy = gAudioIO->IsBusy();
double maxTime = p->GetTracks()->GetEndTime();
if (busy)
ctb->StopPlaying();
ctb->PlayPlayRegion(0, maxTime, false, false,
0,
&position);
mScrubbing = true;
mLastScrubPosition = position;
mLastScrubTime = ::wxGetLocalTimeMillis();
}
}
#endif
/// This method gets called when we're handling selection
/// and the mouse was just clicked.
void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
@ -2297,11 +2197,6 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
if (event.ShiftDown()
#ifdef EXPERIMENTAL_SCRUBBING
// Ctrl prevails over Shift with scrubbing enabled
&& !event.CmdDown()
#endif
#ifdef USE_MIDI
&& !stretch
#endif
@ -2369,17 +2264,10 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
&& !stretch
#endif
) {
#ifdef EXPERIMENTAL_SCRUBBING
// With scrubbing enabled, playback happens on button up, not down,
// and only if we do not start a scrub in the interim.
mScrubbing = false;
mLastScrubPosition = PositionToTime(event.m_x, GetLeftOffset());
#else
StartOrJumpPlayback(event);
// Not starting a drag
SetCapturedTrack(NULL, IsUncaptured);
#endif
return;
}
@ -3130,15 +3018,7 @@ void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
return;
if (event.CmdDown()) {
#ifdef EXPERIMENTAL_SCRUBBING
if (!mScrubbing) {
double position = PositionToTime(event.m_x, GetLeftOffset());
StartScrubbing(position);
}
else
#else
// Ctrl-drag has no meaning, fuhggeddaboudit
#endif
return;
}

View File

@ -313,9 +313,6 @@ class AUDACITY_DLL_API TrackPanel:public wxPanel {
virtual void HandleSelect(wxMouseEvent & event);
virtual void SelectionHandleDrag(wxMouseEvent &event, Track *pTrack);
void StartOrJumpPlayback(wxMouseEvent &event);
#ifdef EXPERIMENTAL_SCRUBBING
void StartScrubbing(double position);
#endif
virtual void SelectionHandleClick(wxMouseEvent &event,
Track* pTrack, wxRect r);
virtual void StartSelection (int mouseXCoordinate, int trackLeftEdge);
@ -764,12 +761,6 @@ protected:
int mMoveUpThreshold;
int mMoveDownThreshold;
#ifdef EXPERIMENTAL_SCRUBBING
bool mScrubbing;
wxLongLong mLastScrubTime; // milliseconds
double mLastScrubPosition;
#endif
wxCursor *mArrowCursor;
wxCursor *mPencilCursor;
wxCursor *mSelectCursor;

View File

@ -2265,7 +2265,7 @@ void Effect::Preview(bool dryOnly)
#ifdef EXPERIMENTAL_MIDI_OUT
empty,
#endif
NULL, rate, t0, t1, NULL);
rate, t0, t1);
if (token) {
int previewing = eProgressSuccess;
@ -2959,7 +2959,9 @@ void EffectUIHost::OnPlay(wxCommandEvent & WXUNUSED(evt))
mPlayPos = mRegion.t1();
}
mProject->GetControlToolBar()->PlayPlayRegion(mPlayPos, mRegion.t1());
mProject->GetControlToolBar()->PlayPlayRegion
(SelectedRegion(mPlayPos, mRegion.t1()),
mProject->GetDefaultPlayOptions());
}
}

View File

@ -277,7 +277,7 @@ Mixer* ExportPlugin::CreateMixer(int numInputTracks, WaveTrack **inputTracks,
{
// MB: the stop time should not be warped, this was a bug.
return new Mixer(numInputTracks, inputTracks,
timeTrack,
Mixer::WarpOptions(timeTrack),
startTime, stopTime,
numOutChannels, outBufferSize, outInterleaved,
outRate, outFormat,

View File

@ -467,34 +467,46 @@ bool ControlToolBar::IsRecordDown()
{
return mRecord->IsDown();
}
void ControlToolBar::PlayPlayRegion(double t0, double t1,
bool looped /* = false */,
bool cutpreview /* = false */,
TimeTrack *timetrack /* = NULL */,
const double *pStartTime /* = NULL */)
int ControlToolBar::PlayPlayRegion(const SelectedRegion &selectedRegion,
const AudioIOStartStreamOptions &options,
bool cutpreview, /* = false */
bool backwards /* = false */)
{
// Uncomment this for laughs!
// backwards = true;
double t0 = selectedRegion.t0();
double t1 = selectedRegion.t1();
// SelectedRegion guarantees t0 <= t1, so we need another boolean argument
// to indicate backwards play.
const bool looped = options.playLooped;
if (backwards)
std::swap(t0, t1);
SetPlay(true, looped, cutpreview);
if (gAudioIO->IsBusy()) {
SetPlay(false);
return;
return -1;
}
if (cutpreview && t0==t1) {
SetPlay(false);
return; /* msmeyer: makes no sense */
return -1; /* msmeyer: makes no sense */
}
AudacityProject *p = GetActiveProject();
if (!p) {
SetPlay(false);
return; // Should never happen, but...
return -1; // Should never happen, but...
}
TrackList *t = p->GetTracks();
if (!t) {
mPlay->PopUp();
return; // Should never happen, but...
return -1; // Should never happen, but...
}
bool hasaudio = false;
@ -512,7 +524,7 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1,
if (!hasaudio) {
SetPlay(false);
return; // No need to continue without audio tracks
return -1; // No need to continue without audio tracks
}
double maxofmins,minofmaxs;
@ -547,7 +559,9 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1,
t1 = t->GetEndTime();
}
else {
// always t0 < t1 right?
// maybe t1 < t0, with backwards scrubbing for instance
if (backwards)
std::swap(t0, t1);
// the set intersection between the play region and the
// valid range maximum of lower bounds
@ -565,61 +579,71 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1,
// we test if the intersection has no volume
if (minofmaxs <= maxofmins) {
// no volume; play nothing
return;
return -1;
}
else {
t0 = maxofmins;
t1 = minofmaxs;
}
if (backwards)
std::swap(t0, t1);
}
// Can't play before 0...either shifted or latencey corrected tracks
if (t0 < 0.0) {
// Can't play before 0...either shifted or latency corrected tracks
if (t0 < 0.0)
t0 = 0.0;
}
if (t1 < 0.0)
t1 = 0.0;
int token = -1;
bool success = false;
if (t1 > t0) {
int token;
if (t1 != t0) {
if (cutpreview) {
const double tless = std::min(t0, t1);
const double tgreater = std::max(t0, t1);
double beforeLen, afterLen;
gPrefs->Read(wxT("/AudioIO/CutPreviewBeforeLen"), &beforeLen, 2.0);
gPrefs->Read(wxT("/AudioIO/CutPreviewAfterLen"), &afterLen, 1.0);
double tcp0 = t0-beforeLen;
double tcp1 = (t1+afterLen) - (t1-t0);
SetupCutPreviewTracks(tcp0, t0, t1, tcp1);
double tcp0 = tless-beforeLen;
double diff = tgreater - tless;
double tcp1 = (tgreater+afterLen) - diff;
SetupCutPreviewTracks(tcp0, tless, tgreater, tcp1);
if (backwards)
std::swap(tcp0, tcp1);
if (mCutPreviewTracks)
{
AudioIOStartStreamOptions myOptions = options;
myOptions.cutPreviewGapStart = t0;
myOptions.cutPreviewGapLen = t1 - t0;
token = gAudioIO->StartStream(
mCutPreviewTracks->GetWaveTrackArray(false),
WaveTrackArray(),
#ifdef EXPERIMENTAL_MIDI_OUT
NoteTrackArray(),
#endif
timetrack, p->GetRate(), tcp0, tcp1, p, false,
t0, t1-t0,
pStartTime);
p->GetRate(), tcp0, tcp1, myOptions);
} else
{
// Cannot create cut preview tracks, clean up and exit
SetPlay(false);
SetStop(false);
SetRecord(false);
return;
return -1;
}
} else {
// Lifted the following into AudacityProject::GetDefaultPlayOptions()
/*
if (!timetrack) {
timetrack = t->GetTimeTrack();
}
*/
token = gAudioIO->StartStream(t->GetWaveTrackArray(false),
WaveTrackArray(),
#ifdef EXPERIMENTAL_MIDI_OUT
t->GetNoteTrackArray(false),
#endif
timetrack,
p->GetRate(), t0, t1, p, looped,
0, 0,
pStartTime);
p->GetRate(), t0, t1, options);
}
if (token != 0) {
success = true;
@ -648,7 +672,10 @@ void ControlToolBar::PlayPlayRegion(double t0, double t1,
SetPlay(false);
SetStop(false);
SetRecord(false);
return -1;
}
return token;
}
void ControlToolBar::PlayCurrentRegion(bool looped /* = false */,
@ -666,9 +693,12 @@ void ControlToolBar::PlayCurrentRegion(bool looped /* = false */,
double playRegionStart, playRegionEnd;
p->GetPlayRegion(&playRegionStart, &playRegionEnd);
PlayPlayRegion(playRegionStart,
playRegionEnd,
looped, cutpreview);
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
options.playLooped = looped;
if (cutpreview)
options.timeTrack = NULL;
PlayPlayRegion(SelectedRegion(playRegionStart, playRegionEnd),
options, cutpreview);
}
}
@ -898,14 +928,14 @@ void ControlToolBar::OnRecord(wxCommandEvent &evt)
#ifdef AUTOMATED_INPUT_LEVEL_ADJUSTMENT
gAudioIO->AILAInitialize();
#endif
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
int token = gAudioIO->StartStream(playbackTracks,
newRecordingTracks,
#ifdef EXPERIMENTAL_MIDI_OUT
midiTracks,
#endif
t->GetTimeTrack(),
p->GetRate(), t0, t1, p);
p->GetRate(), t0, t1, options);
bool success = (token != 0);

View File

@ -30,6 +30,9 @@ class AudacityProject;
class TrackList;
class TimeTrack;
struct AudioIOStartStreamOptions;
class SelectedRegion;
// In the GUI, ControlToolBar appears as the "Transport Toolbar". "Control Toolbar" is historic.
class ControlToolBar:public ToolBar {
@ -64,13 +67,10 @@ class ControlToolBar:public ToolBar {
// play from current cursor.
void PlayCurrentRegion(bool looped = false, bool cutpreview = false);
// Play the region [t0,t1]
void PlayPlayRegion(double t0, double t1,
bool looped = false,
bool cutpreview = false,
TimeTrack *timetrack = NULL,
// May be other than t0,
// but will be constrained between t0 and t1
const double *pStartTime = NULL);
// Return the Audio IO token or -1 for failure
int PlayPlayRegion(const SelectedRegion &selectedRegion,
const AudioIOStartStreamOptions &options,
bool cutpreview = false, bool backwards = false);
void PlayDefault();
// Stop playing

View File

@ -439,11 +439,13 @@ void TranscriptionToolBar::PlayAtSpeed(bool looped, bool cutPreview)
#ifdef EXPERIMENTAL_MIDI_OUT
gAudioIO->SetMidiPlaySpeed(mPlaySpeed);
#endif
p->GetControlToolBar()->PlayPlayRegion(playRegionStart,
playRegionEnd,
looped,
cutPreview,
mTimeTrack);
AudioIOStartStreamOptions options(p->GetDefaultPlayOptions());
options.playLooped = looped;
options.timeTrack = mTimeTrack;
p->GetControlToolBar()->PlayPlayRegion
(SelectedRegion(playRegionStart, playRegionEnd),
options,
cutPreview);
}
}