audacia/src/effects/SoundTouchEffect.cpp
james.k.crook@gmail.com f5e593cc4c Paul L's new SelectedRegion class replacing use of t0 and t1. Also LabelTrack.h no longer in TrackPanel.h includes.
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.
2014-10-05 17:10:09 +00:00

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