/********************************************************************** 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 #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