mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-08 07:42:39 +02:00
This change is believed to be a direct refactoring that does not change functionality. It paves the way for more complex kinds of selection, such as selections involving frequency as well as time. It also reduces risk of left and right edges being swapped in future code using SelectedRegion, as the default is to swap on assignment if needed.
396 lines
13 KiB
C++
396 lines
13 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
SoundTouchEffect.cpp
|
|
|
|
Dominic Mazzoni, Vaughan Johnson
|
|
|
|
This abstract class contains all of the common code for an
|
|
effect that uses SoundTouch to do its processing (ChangeTempo
|
|
and ChangePitch).
|
|
|
|
**********************************************************************/
|
|
|
|
#include "../Audacity.h"
|
|
|
|
#if USE_SOUNDTOUCH
|
|
|
|
#include <math.h>
|
|
|
|
#include "../LabelTrack.h"
|
|
#include "../WaveTrack.h"
|
|
#include "../Project.h"
|
|
#include "SoundTouchEffect.h"
|
|
#include "TimeWarper.h"
|
|
#include "../NoteTrack.h"
|
|
|
|
bool EffectSoundTouch::ProcessLabelTrack(Track *track)
|
|
{
|
|
// SetTimeWarper(new RegionTimeWarper(mCurT0, mCurT1,
|
|
// new LinearTimeWarper(mCurT0, mCurT0,
|
|
// mCurT1, mCurT0 + (mCurT1-mCurT0)*mFactor)));
|
|
LabelTrack *lt = (LabelTrack*)track;
|
|
if (lt == NULL) return false;
|
|
lt->WarpLabels(*GetTimeWarper());
|
|
return true;
|
|
}
|
|
|
|
#ifdef USE_MIDI
|
|
bool EffectSoundTouch::ProcessNoteTrack(Track *track)
|
|
{
|
|
NoteTrack *nt = (NoteTrack *) track;
|
|
if (nt == NULL) return false;
|
|
nt->WarpAndTransposeNotes(mCurT0, mCurT1, *GetTimeWarper(), mSemitones);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool EffectSoundTouch::Process()
|
|
{
|
|
// Assumes that mSoundTouch has already been initialized
|
|
// by the subclass for subclass-specific parameters. The
|
|
// time warper should also be set.
|
|
|
|
// Check if this effect will alter the selection length; if so, we need
|
|
// to operate on sync-lock selected tracks.
|
|
bool mustSync = true;
|
|
if (mT1 == GetTimeWarper()->Warp(mT1)) {
|
|
mustSync = false;
|
|
}
|
|
|
|
//Iterate over each track
|
|
// Needs Track::All for sync-lock grouping.
|
|
this->CopyInputTracks(Track::All);
|
|
bool bGoodResult = true;
|
|
|
|
TrackListIterator iter(mOutputTracks);
|
|
Track* t;
|
|
mCurTrackNum = 0;
|
|
m_maxNewLength = 0.0;
|
|
|
|
t = iter.First();
|
|
while (t != NULL) {
|
|
if (t->GetKind() == Track::Label &&
|
|
(t->GetSelected() || (mustSync && t->IsSyncLockSelected())) )
|
|
{
|
|
if (!ProcessLabelTrack(t))
|
|
{
|
|
bGoodResult = false;
|
|
break;
|
|
}
|
|
}
|
|
#ifdef USE_MIDI
|
|
else if (t->GetKind() == Track::Note &&
|
|
(t->GetSelected() || (mustSync && t->IsSyncLockSelected())))
|
|
{
|
|
if (!ProcessNoteTrack(t))
|
|
{
|
|
bGoodResult = false;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
else if (t->GetKind() == Track::Wave && t->GetSelected())
|
|
{
|
|
WaveTrack* leftTrack = (WaveTrack*)t;
|
|
//Get start and end times from track
|
|
mCurT0 = leftTrack->GetStartTime();
|
|
mCurT1 = leftTrack->GetEndTime();
|
|
|
|
//Set the current bounds to whichever left marker is
|
|
//greater and whichever right marker is less
|
|
mCurT0 = wxMax(mT0, mCurT0);
|
|
mCurT1 = wxMin(mT1, mCurT1);
|
|
|
|
// Process only if the right marker is to the right of the left marker
|
|
if (mCurT1 > mCurT0) {
|
|
sampleCount start, end;
|
|
|
|
if (leftTrack->GetLinked()) {
|
|
double t;
|
|
WaveTrack* rightTrack = (WaveTrack*)(iter.Next());
|
|
|
|
//Adjust bounds by the right tracks markers
|
|
t = rightTrack->GetStartTime();
|
|
t = wxMax(mT0, t);
|
|
mCurT0 = wxMin(mCurT0, t);
|
|
t = rightTrack->GetEndTime();
|
|
t = wxMin(mT1, t);
|
|
mCurT1 = wxMax(mCurT1, t);
|
|
|
|
//Transform the marker timepoints to samples
|
|
start = leftTrack->TimeToLongSamples(mCurT0);
|
|
end = leftTrack->TimeToLongSamples(mCurT1);
|
|
|
|
//Inform soundtouch there's 2 channels
|
|
mSoundTouch->setChannels(2);
|
|
|
|
//ProcessStereo() (implemented below) processes a stereo track
|
|
if (!ProcessStereo(leftTrack, rightTrack, start, end))
|
|
{
|
|
bGoodResult = false;
|
|
break;
|
|
}
|
|
mCurTrackNum++; // Increment for rightTrack, too.
|
|
} else {
|
|
//Transform the marker timepoints to samples
|
|
start = leftTrack->TimeToLongSamples(mCurT0);
|
|
end = leftTrack->TimeToLongSamples(mCurT1);
|
|
|
|
//Inform soundtouch there's a single channel
|
|
mSoundTouch->setChannels(1);
|
|
|
|
//ProcessOne() (implemented below) processes a single track
|
|
if (!ProcessOne(leftTrack, start, end))
|
|
{
|
|
bGoodResult = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
mCurTrackNum++;
|
|
}
|
|
else if (mustSync && t->IsSyncLockSelected()) {
|
|
t->SyncLockAdjust(mT1, GetTimeWarper()->Warp(mT1));
|
|
}
|
|
|
|
//Iterate to the next track
|
|
t = iter.Next();
|
|
}
|
|
|
|
if (bGoodResult)
|
|
ReplaceProcessedTracks(bGoodResult);
|
|
|
|
delete mSoundTouch;
|
|
mSoundTouch = NULL;
|
|
|
|
// mT0 = mCurT0;
|
|
// mT1 = mCurT0 + m_maxNewLength; // Update selection.
|
|
|
|
return bGoodResult;
|
|
}
|
|
|
|
//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
|
|
//and executes ProcessSoundTouch on these blocks
|
|
bool EffectSoundTouch::ProcessOne(WaveTrack *track,
|
|
sampleCount start, sampleCount end)
|
|
{
|
|
WaveTrack *outputTrack;
|
|
sampleCount s;
|
|
|
|
mSoundTouch->setSampleRate((unsigned int)(track->GetRate()+0.5));
|
|
|
|
outputTrack = mFactory->NewWaveTrack(track->GetSampleFormat(), track->GetRate());
|
|
|
|
//Get the length of the buffer (as double). len is
|
|
//used simple to calculate a progress meter, so it is easier
|
|
//to make it a double now than it is to do it later
|
|
double len = (double)(end - start);
|
|
|
|
//Initiate a processing buffer. This buffer will (most likely)
|
|
//be shorter than the length of the track being processed.
|
|
float *buffer = new float[track->GetMaxBlockSize()];
|
|
|
|
//Go through the track one buffer at a time. s counts which
|
|
//sample the current buffer starts at.
|
|
s = start;
|
|
while (s < end) {
|
|
//Get a block of samples (smaller than the size of the buffer)
|
|
sampleCount block = track->GetBestBlockSize(s);
|
|
|
|
//Adjust the block size if it is the final block in the track
|
|
if (s + block > end)
|
|
block = end - s;
|
|
|
|
//Get the samples from the track and put them in the buffer
|
|
track->Get((samplePtr) buffer, floatSample, s, block);
|
|
|
|
//Add samples to SoundTouch
|
|
mSoundTouch->putSamples(buffer, block);
|
|
|
|
//Get back samples from SoundTouch
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
if (outputCount > 0) {
|
|
float *buffer2 = new float[outputCount];
|
|
mSoundTouch->receiveSamples(buffer2, outputCount);
|
|
outputTrack->Append((samplePtr)buffer2, floatSample, outputCount);
|
|
delete[] buffer2;
|
|
}
|
|
|
|
//Increment s one blockfull of samples
|
|
s += block;
|
|
|
|
//Update the Progress meter
|
|
if (TrackProgress(mCurTrackNum, (s - start) / len))
|
|
return false;
|
|
}
|
|
|
|
// Tell SoundTouch to finish processing any remaining samples
|
|
mSoundTouch->flush(); // this should only be used for changeTempo - it dumps data otherwise with pRateTransposer->clear();
|
|
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
if (outputCount > 0) {
|
|
float *buffer2 = new float[outputCount];
|
|
mSoundTouch->receiveSamples(buffer2, outputCount);
|
|
outputTrack->Append((samplePtr)buffer2, floatSample, outputCount);
|
|
delete[] buffer2;
|
|
}
|
|
|
|
// Flush the output WaveTrack (since it's buffered, too)
|
|
outputTrack->Flush();
|
|
|
|
// Clean up the buffer
|
|
delete[]buffer;
|
|
|
|
// Take the output track and insert it in place of the original
|
|
// sample data
|
|
track->ClearAndPaste(mCurT0, mCurT1, outputTrack, true, false, GetTimeWarper());
|
|
|
|
double newLength = outputTrack->GetEndTime();
|
|
m_maxNewLength = wxMax(m_maxNewLength, newLength);
|
|
|
|
// Delete the outputTrack now that its data is inserted in place
|
|
delete outputTrack;
|
|
|
|
//Return true because the effect processing succeeded.
|
|
return true;
|
|
}
|
|
|
|
bool EffectSoundTouch::ProcessStereo(WaveTrack* leftTrack, WaveTrack* rightTrack,
|
|
sampleCount start, sampleCount end)
|
|
{
|
|
mSoundTouch->setSampleRate((unsigned int)(leftTrack->GetRate()+0.5));
|
|
|
|
WaveTrack* outputLeftTrack = mFactory->NewWaveTrack(leftTrack->GetSampleFormat(),
|
|
leftTrack->GetRate());
|
|
WaveTrack* outputRightTrack = mFactory->NewWaveTrack(rightTrack->GetSampleFormat(),
|
|
rightTrack->GetRate());
|
|
|
|
//Get the length of the buffer (as double). len is
|
|
//used simple to calculate a progress meter, so it is easier
|
|
//to make it a double now than it is to do it later
|
|
double len = (double)(end - start);
|
|
|
|
//Initiate a processing buffer. This buffer will (most likely)
|
|
//be shorter than the length of the track being processed.
|
|
// Make soundTouchBuffer twice as big as MaxBlockSize for each channel,
|
|
// because Soundtouch wants them interleaved, i.e., each
|
|
// Soundtouch sample is left-right pair.
|
|
sampleCount maxBlockSize = leftTrack->GetMaxBlockSize();
|
|
float* leftBuffer = new float[maxBlockSize];
|
|
float* rightBuffer = new float[maxBlockSize];
|
|
float* soundTouchBuffer = new float[maxBlockSize * 2];
|
|
|
|
// Go through the track one stereo buffer at a time.
|
|
// sourceSampleCount counts the sample at which the current buffer starts,
|
|
// per channel.
|
|
sampleCount sourceSampleCount = start;
|
|
while (sourceSampleCount < end) {
|
|
//Get a block of samples (smaller than the size of the buffer)
|
|
sampleCount blockSize = leftTrack->GetBestBlockSize(sourceSampleCount);
|
|
|
|
//Adjust the block size if it is the final block in the track
|
|
if (sourceSampleCount + blockSize > end)
|
|
blockSize = end - sourceSampleCount;
|
|
|
|
// Get the samples from the tracks and put them in the buffers.
|
|
leftTrack->Get((samplePtr)(leftBuffer), floatSample, sourceSampleCount, blockSize);
|
|
rightTrack->Get((samplePtr)(rightBuffer), floatSample, sourceSampleCount, blockSize);
|
|
|
|
// Interleave into soundTouchBuffer.
|
|
for (int index = 0; index < blockSize; index++) {
|
|
soundTouchBuffer[index*2] = leftBuffer[index];
|
|
soundTouchBuffer[(index*2)+1] = rightBuffer[index];
|
|
}
|
|
|
|
//Add samples to SoundTouch
|
|
mSoundTouch->putSamples(soundTouchBuffer, blockSize);
|
|
|
|
//Get back samples from SoundTouch
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
if (outputCount > 0)
|
|
this->ProcessStereoResults(outputCount, outputLeftTrack, outputRightTrack);
|
|
|
|
//Increment sourceSampleCount one blockfull of samples
|
|
sourceSampleCount += blockSize;
|
|
|
|
//Update the Progress meter
|
|
// mCurTrackNum is left track. Include right track.
|
|
int nWhichTrack = mCurTrackNum;
|
|
double frac = (sourceSampleCount - start) / len;
|
|
if (frac < 0.5)
|
|
frac *= 2.0; // Show twice as far for each track, because we're doing 2 at once.
|
|
else
|
|
{
|
|
nWhichTrack++;
|
|
frac -= 0.5;
|
|
frac *= 2.0; // Show twice as far for each track, because we're doing 2 at once.
|
|
}
|
|
if (TrackProgress(nWhichTrack, frac))
|
|
return false;
|
|
}
|
|
|
|
// Tell SoundTouch to finish processing any remaining samples
|
|
mSoundTouch->flush();
|
|
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
if (outputCount > 0)
|
|
this->ProcessStereoResults(outputCount, outputLeftTrack, outputRightTrack);
|
|
|
|
// Flush the output WaveTracks (since they're buffered, too)
|
|
outputLeftTrack->Flush();
|
|
outputRightTrack->Flush();
|
|
|
|
// Clean up the buffers.
|
|
delete [] leftBuffer;
|
|
delete [] rightBuffer;
|
|
delete [] soundTouchBuffer;
|
|
|
|
// Take the output tracks and insert in place of the original
|
|
// sample data.
|
|
leftTrack->ClearAndPaste(mCurT0, mCurT1, outputLeftTrack, true, false, GetTimeWarper());
|
|
rightTrack->ClearAndPaste(mCurT0, mCurT1, outputRightTrack, true, false, GetTimeWarper());
|
|
|
|
// Track the longest result length
|
|
double newLength = outputLeftTrack->GetEndTime();
|
|
m_maxNewLength = wxMax(m_maxNewLength, newLength);
|
|
newLength = outputRightTrack->GetEndTime();
|
|
m_maxNewLength = wxMax(m_maxNewLength, newLength);
|
|
|
|
// Delete the outputTracks now that their data are inserted in place.
|
|
delete outputLeftTrack;
|
|
delete outputRightTrack;
|
|
|
|
//Return true because the effect processing succeeded.
|
|
return true;
|
|
}
|
|
|
|
bool EffectSoundTouch::ProcessStereoResults(const unsigned int outputCount,
|
|
WaveTrack* outputLeftTrack,
|
|
WaveTrack* outputRightTrack)
|
|
{
|
|
float* outputSoundTouchBuffer = new float[outputCount*2];
|
|
mSoundTouch->receiveSamples(outputSoundTouchBuffer, outputCount);
|
|
|
|
// Dis-interleave outputSoundTouchBuffer into separate track buffers.
|
|
float* outputLeftBuffer = new float[outputCount];
|
|
float* outputRightBuffer = new float[outputCount];
|
|
for (unsigned int index = 0; index < outputCount; index++)
|
|
{
|
|
outputLeftBuffer[index] = outputSoundTouchBuffer[index*2];
|
|
outputRightBuffer[index] = outputSoundTouchBuffer[(index*2)+1];
|
|
}
|
|
|
|
outputLeftTrack->Append((samplePtr)outputLeftBuffer, floatSample, outputCount);
|
|
outputRightTrack->Append((samplePtr)outputRightBuffer, floatSample, outputCount);
|
|
|
|
delete[] outputSoundTouchBuffer;
|
|
delete[] outputLeftBuffer;
|
|
delete[] outputRightBuffer;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // USE_SOUNDTOUCH
|