1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-17 16:40:07 +02:00
audacity/src/effects/SBSMSEffect.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

435 lines
15 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
SBSMSEffect.cpp
Clayton Otey
This class contains all of the common code for an
effect that uses SBSMS to do its processing (TimeScale)
**********************************************************************/
#include "../Audacity.h"
#if USE_SBSMS
#include <math.h>
#include "SBSMSEffect.h"
#include "../LabelTrack.h"
#include "../WaveTrack.h"
#include "../Project.h"
#include "TimeWarper.h"
enum {
SBSMSOutBlockSize = 512
};
class ResampleBuf
{
public:
ResampleBuf()
{
processed = 0;
buf = NULL;
leftBuffer = NULL;
rightBuffer = NULL;
quality = NULL;
iface = NULL;
sbsms = NULL;
resampler = NULL;
SBSMSBuf = NULL;
outputLeftTrack = NULL;
outputRightTrack = NULL;
}
~ResampleBuf()
{
if(buf) free(buf);
if(leftBuffer) free(leftBuffer);
if(rightBuffer) free(rightBuffer);
if(SBSMSBuf) free(SBSMSBuf);
if(outputLeftTrack) delete outputLeftTrack;
if(outputRightTrack) delete outputRightTrack;
if(quality) delete quality;
if(sbsms) delete sbsms;
if(iface) delete iface;
if(resampler) delete resampler;
}
bool bPitch;
audio *buf;
double ratio;
sampleCount processed;
sampleCount blockSize;
sampleCount SBSMSBlockSize;
sampleCount offset;
sampleCount end;
float *leftBuffer;
float *rightBuffer;
WaveTrack *leftTrack;
WaveTrack *rightTrack;
SBSMS *sbsms;
SBSMSInterface *iface;
audio *SBSMSBuf;
// Not required by callbacks, but makes for easier cleanup
Resampler *resampler;
SBSMSQuality *quality;
WaveTrack *outputLeftTrack;
WaveTrack *outputRightTrack;
};
class SBSMSEffectInterface : public SBSMSInterfaceSliding {
public:
SBSMSEffectInterface(Resampler *resampler,
Slide *rateSlide, Slide *pitchSlide,
bool bReferenceInput,
long samples, long preSamples,
SBSMSQuality *quality)
: SBSMSInterfaceSliding(rateSlide,pitchSlide,bReferenceInput,samples,preSamples,quality)
{
this->resampler = resampler;
}
virtual ~SBSMSEffectInterface() {}
long samples(audio *buf, long n) {
return resampler->read(buf, n);
}
protected:
Resampler *resampler;
};
long resampleCB(void *cb_data, SBSMSFrame *data)
{
ResampleBuf *r = (ResampleBuf*) cb_data;
long blockSize = r->leftTrack->GetBestBlockSize(r->offset);
//Adjust the block size if it is the final block in the track
if (r->offset + blockSize > r->end)
blockSize = r->end - r->offset;
// Get the samples from the tracks and put them in the buffers.
r->leftTrack->Get((samplePtr)(r->leftBuffer), floatSample, r->offset, blockSize);
r->rightTrack->Get((samplePtr)(r->rightBuffer), floatSample, r->offset, blockSize);
// convert to sbsms audio format
for(int i=0; i<blockSize; i++) {
r->buf[i][0] = r->leftBuffer[i];
r->buf[i][1] = r->rightBuffer[i];
}
data->buf = r->buf;
data->size = blockSize;
if(r->bPitch) {
float t0 = (float)(r->processed) / r->iface->getSamplesToInput();
float t1 = (float)(r->processed + blockSize) / r->iface->getSamplesToInput();
data->ratio0 = r->iface->getStretch(t0);
data->ratio1 = r->iface->getStretch(t1);
} else {
data->ratio0 = r->ratio;
data->ratio1 = r->ratio;
}
r->processed += blockSize;
r->offset += blockSize;
return blockSize;
}
long postResampleCB(void *cb_data, SBSMSFrame *data)
{
ResampleBuf *r = (ResampleBuf*) cb_data;
long sampleCount = r->sbsms->read(r->iface, r->SBSMSBuf, r->SBSMSBlockSize);
data->buf = r->SBSMSBuf;
data->size = sampleCount;
data->ratio0 = 1.0 / r->ratio;
data->ratio1 = 1.0 / r->ratio;
return sampleCount;
}
void EffectSBSMS :: setParameters(double rateStart, double rateEnd, double pitchStart, double pitchEnd,
SlideType rateSlideType, SlideType pitchSlideType,
bool bLinkRatePitch, bool bRateReferenceInput, bool bPitchReferenceInput)
{
this->rateStart = rateStart;
this->rateEnd = rateEnd;
this->pitchStart = pitchStart;
this->pitchEnd = pitchEnd;
this->bLinkRatePitch = bLinkRatePitch;
this->rateSlideType = rateSlideType;
this->pitchSlideType = pitchSlideType;
this->bRateReferenceInput = bRateReferenceInput;
this->bPitchReferenceInput = bPitchReferenceInput;
}
TimeWarper *createTimeWarper(double t0, double t1, double duration,
double rateStart, double rateEnd, SlideType rateSlideType)
{
TimeWarper *warper = NULL;
if (rateStart == rateEnd || rateSlideType == SlideConstant) {
warper = new LinearTimeWarper(t0, t0, t1, t0+duration);
} else if(rateSlideType == SlideLinearInputRate) {
warper = new LinearInputRateTimeWarper(t0, t1, rateStart, rateEnd);
} else if(rateSlideType == SlideLinearOutputRate) {
warper = new LinearOutputRateTimeWarper(t0, t1, rateStart, rateEnd);
} else if(rateSlideType == SlideLinearInputStretch) {
warper = new LinearInputStretchTimeWarper(t0, t1, rateStart, rateEnd);
} else if(rateSlideType == SlideLinearOutputStretch) {
warper = new LinearOutputStretchTimeWarper(t0, t1, rateStart, rateEnd);
} else if(rateSlideType == SlideGeometricInput) {
warper = new GeometricInputTimeWarper(t0, t1, rateStart, rateEnd);
} else if(rateSlideType == SlideGeometricOutput) {
warper = new GeometricOutputTimeWarper(t0, t1, rateStart, rateEnd);
}
return warper;
}
// Labels inside the affected region are moved to match the audio; labels after
// it are shifted along appropriately.
bool EffectSBSMS::ProcessLabelTrack(Track *t)
{
TimeWarper *warper = createTimeWarper(mT0,mT1,(mT1-mT0)*mTotalStretch,rateStart,rateEnd,rateSlideType);
SetTimeWarper(new RegionTimeWarper(mT0, mT1, warper));
LabelTrack *lt = (LabelTrack*)t;
if (lt == NULL) return false;
lt->WarpLabels(*GetTimeWarper());
return true;
}
bool EffectSBSMS::Process()
{
bool bGoodResult = true;
//Iterate over each track
//Track::All is needed because this effect needs to introduce silence in the group tracks to keep sync
this->CopyInputTracks(Track::All); // Set up mOutputTracks.
TrackListIterator iter(mOutputTracks);
Track* t;
mCurTrackNum = 0;
double maxDuration = 0.0;
// Must sync if selection length will change
bool mustSync = (rateStart != rateEnd);
Slide rateSlide(rateSlideType,rateStart,rateEnd);
Slide pitchSlide(pitchSlideType,pitchStart,pitchEnd);
mTotalStretch = rateSlide.getTotalStretch();
t = iter.First();
while (t != NULL) {
if (t->GetKind() == Track::Label &&
(t->GetSelected() || (mustSync && t->IsSyncLockSelected())) )
{
if (!ProcessLabelTrack(t)) {
bGoodResult = false;
break;
}
}
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;
sampleCount end;
start = leftTrack->TimeToLongSamples(mCurT0);
end = leftTrack->TimeToLongSamples(mCurT1);
WaveTrack* rightTrack = NULL;
if (leftTrack->GetLinked()) {
double t;
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);
mCurTrackNum++; // Increment for rightTrack, too.
}
sampleCount trackStart = leftTrack->TimeToLongSamples(leftTrack->GetStartTime());
sampleCount trackEnd = leftTrack->TimeToLongSamples(leftTrack->GetEndTime());
// SBSMS has a fixed sample rate - we just convert to its sample rate and then convert back
float srTrack = leftTrack->GetRate();
float srProcess = bLinkRatePitch?srTrack:44100.0;
// the resampler needs a callback to supply its samples
ResampleBuf rb;
sampleCount maxBlockSize = leftTrack->GetMaxBlockSize();
rb.blockSize = maxBlockSize;
rb.buf = (audio*)calloc(rb.blockSize,sizeof(audio));
rb.leftTrack = leftTrack;
rb.rightTrack = rightTrack?rightTrack:leftTrack;
rb.leftBuffer = (float*)calloc(maxBlockSize,sizeof(float));
rb.rightBuffer = (float*)calloc(maxBlockSize,sizeof(float));
// Samples in selection
sampleCount samplesIn = end-start;
// Samples for SBSMS to process after resampling
sampleCount samplesToProcess = (sampleCount) ((float)samplesIn*(srProcess/srTrack));
SlideType outSlideType;
SBSMSResampleCB outResampleCB;
sampleCount processPresamples = 0;
sampleCount trackPresamples = 0;
if(bLinkRatePitch) {
rb.bPitch = true;
outSlideType = rateSlideType;
outResampleCB = resampleCB;
rb.offset = start;
rb.end = end;
rb.iface = new SBSMSInterfaceSliding(&rateSlide,&pitchSlide,
bPitchReferenceInput,
samplesToProcess,0,
NULL);
} else {
rb.bPitch = false;
outSlideType = (srProcess==srTrack?SlideIdentity:SlideConstant);
outResampleCB = postResampleCB;
rb.ratio = srProcess/srTrack;
rb.quality = new SBSMSQuality(&SBSMSQualityStandard);
rb.resampler = new Resampler(resampleCB, &rb, srProcess==srTrack?SlideIdentity:SlideConstant);
rb.sbsms = new SBSMS(rightTrack?2:1,rb.quality,true);
rb.SBSMSBlockSize = rb.sbsms->getInputFrameSize();
rb.SBSMSBuf = (audio*)calloc(rb.SBSMSBlockSize,sizeof(audio));
processPresamples = wxMin(rb.quality->getMaxPresamples(),
(long)((float)(start-trackStart)*(srProcess/srTrack)));
trackPresamples = wxMin(start-trackStart,
(long)((float)(processPresamples)*(srTrack/srProcess)));
rb.offset = start - trackPresamples;
rb.end = trackEnd;
rb.iface = new SBSMSEffectInterface(rb.resampler,
&rateSlide,&pitchSlide,
bPitchReferenceInput,
samplesToProcess,processPresamples,
rb.quality);
}
Resampler resampler(outResampleCB,&rb,outSlideType);
audio outBuf[SBSMSOutBlockSize];
float outBufLeft[2*SBSMSOutBlockSize];
float outBufRight[2*SBSMSOutBlockSize];
// Samples in output after SBSMS
sampleCount samplesToOutput = rb.iface->getSamplesToOutput();
// Samples in output after resampling back
sampleCount samplesOut = (sampleCount) ((float)samplesToOutput * (srTrack/srProcess));
// Duration in track time
double duration = (mCurT1-mCurT0) * mTotalStretch;
if(duration > maxDuration)
maxDuration = duration;
TimeWarper *warper = createTimeWarper(mCurT0,mCurT1,maxDuration,rateStart,rateEnd,rateSlideType);
SetTimeWarper(warper);
rb.outputLeftTrack = mFactory->NewWaveTrack(leftTrack->GetSampleFormat(),
leftTrack->GetRate());
if(rightTrack)
rb.outputRightTrack = mFactory->NewWaveTrack(rightTrack->GetSampleFormat(),
rightTrack->GetRate());
long pos = 0;
long outputCount = -1;
// process
while(pos<samplesOut && outputCount) {
long frames;
if(pos+SBSMSOutBlockSize>samplesOut) {
frames = samplesOut - pos;
} else {
frames = SBSMSOutBlockSize;
}
outputCount = resampler.read(outBuf,frames);
for(int i = 0; i < outputCount; i++) {
outBufLeft[i] = outBuf[i][0];
if(rightTrack)
outBufRight[i] = outBuf[i][1];
}
pos += outputCount;
rb.outputLeftTrack->Append((samplePtr)outBufLeft, floatSample, outputCount);
if(rightTrack)
rb.outputRightTrack->Append((samplePtr)outBufRight, floatSample, outputCount);
double frac = (double)pos/(double)samplesOut;
int nWhichTrack = mCurTrackNum;
if(rightTrack) {
nWhichTrack = 2*(mCurTrackNum/2);
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;
}
rb.outputLeftTrack->Flush();
if(rightTrack)
rb.outputRightTrack->Flush();
bool bResult =
leftTrack->ClearAndPaste(mCurT0, mCurT1, rb.outputLeftTrack,
true, false, GetTimeWarper());
wxASSERT(bResult); // TO DO: Actually handle this.
if(rightTrack)
{
bResult =
rightTrack->ClearAndPaste(mCurT0, mCurT1, rb.outputRightTrack,
true, false, GetTimeWarper());
wxASSERT(bResult); // TO DO: Actually handle this.
}
}
mCurTrackNum++;
}
else if (mustSync && t->IsSyncLockSelected())
{
t->SyncLockAdjust(mCurT1, mCurT0 + (mCurT1 - mCurT0) * mTotalStretch);
}
//Iterate to the next track
t = iter.Next();
}
if (bGoodResult)
ReplaceProcessedTracks(bGoodResult);
// Update selection
mT0 = mCurT0;
mT1 = mCurT0 + maxDuration;
return bGoodResult;
}
#endif