2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#if USE_SOUNDTOUCH
|
2018-11-10 19:47:12 +00:00
|
|
|
#include "SoundTouchEffect.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
2014-10-05 17:10:09 +00:00
|
|
|
#include "../LabelTrack.h"
|
2020-09-18 04:22:00 +00:00
|
|
|
#include "../WaveClip.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "../WaveTrack.h"
|
2010-10-28 23:07:33 +00:00
|
|
|
#include "../NoteTrack.h"
|
2020-06-13 15:20:45 +00:00
|
|
|
#include "TimeWarper.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2018-07-23 18:27:44 +00:00
|
|
|
// Soundtouch defines these as well, which are also in generated configmac.h
|
|
|
|
// and configunix.h, so get rid of them before including,
|
|
|
|
// to avoid compiler warnings, and be sure to do this
|
|
|
|
// after all other #includes, to avoid any mischief that might result
|
|
|
|
// from doing the un-definitions before seeing any wx headers.
|
|
|
|
#undef PACKAGE_NAME
|
|
|
|
#undef PACKAGE_STRING
|
|
|
|
#undef PACKAGE_TARNAME
|
|
|
|
#undef PACKAGE_VERSION
|
|
|
|
#undef PACKAGE_BUGREPORT
|
|
|
|
#undef PACKAGE
|
|
|
|
#undef VERSION
|
|
|
|
#include "SoundTouch.h"
|
|
|
|
|
|
|
|
#ifdef USE_MIDI
|
|
|
|
EffectSoundTouch::EffectSoundTouch()
|
|
|
|
{
|
|
|
|
mSemitones = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
EffectSoundTouch::~EffectSoundTouch()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-12-16 17:37:15 +00:00
|
|
|
bool EffectSoundTouch::ProcessLabelTrack(
|
|
|
|
LabelTrack *lt, const TimeWarper &warper)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-08-10 03:44:00 +00:00
|
|
|
// SetTimeWarper(std::make_unique<RegionTimeWarper>(mCurT0, mCurT1,
|
2016-08-04 11:33:22 +00:00
|
|
|
// std::make_unique<LinearTimeWarper>(mCurT0, mCurT0,
|
2010-01-23 19:44:49 +00:00
|
|
|
// mCurT1, mCurT0 + (mCurT1-mCurT0)*mFactor)));
|
2016-12-16 17:37:15 +00:00
|
|
|
lt->WarpLabels(warper);
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-07-18 03:44:34 +00:00
|
|
|
#ifdef USE_MIDI
|
2016-12-16 17:37:15 +00:00
|
|
|
bool EffectSoundTouch::ProcessNoteTrack(NoteTrack *nt, const TimeWarper &warper)
|
2010-10-28 17:57:14 +00:00
|
|
|
{
|
2016-12-16 17:37:15 +00:00
|
|
|
nt->WarpAndTransposeNotes(mCurT0, mCurT1, warper, mSemitones);
|
2010-10-28 17:57:14 +00:00
|
|
|
return true;
|
|
|
|
}
|
2012-07-18 03:44:34 +00:00
|
|
|
#endif
|
2010-10-28 17:57:14 +00:00
|
|
|
|
2020-09-18 04:22:00 +00:00
|
|
|
bool EffectSoundTouch::ProcessWithTimeWarper(InitFunction initer,
|
|
|
|
const TimeWarper &warper,
|
|
|
|
bool preserveLength)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
// Assumes that mSoundTouch has already been initialized
|
2010-02-16 20:50:38 +00:00
|
|
|
// 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
|
2010-08-11 22:47:26 +00:00
|
|
|
// to operate on sync-lock selected tracks.
|
2010-02-16 20:50:38 +00:00
|
|
|
bool mustSync = true;
|
2016-12-16 17:37:15 +00:00
|
|
|
if (mT1 == warper.Warp(mT1)) {
|
2010-02-16 20:50:38 +00:00
|
|
|
mustSync = false;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Iterate over each track
|
2016-12-31 09:54:52 +00:00
|
|
|
// Needs all for sync-lock grouping.
|
|
|
|
this->CopyInputTracks(true);
|
2010-01-23 19:44:49 +00:00
|
|
|
bool bGoodResult = true;
|
|
|
|
|
2020-09-18 04:22:00 +00:00
|
|
|
mPreserveLength = preserveLength;
|
2010-01-23 19:44:49 +00:00
|
|
|
mCurTrackNum = 0;
|
2010-02-16 20:50:38 +00:00
|
|
|
m_maxNewLength = 0.0;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-04-11 21:55:19 +00:00
|
|
|
mOutputTracks->Leaders().VisitWhile( bGoodResult,
|
|
|
|
[&]( LabelTrack *lt, const Track::Fallthrough &fallthrough ) {
|
|
|
|
if ( !(lt->GetSelected() || (mustSync && lt->IsSyncLockSelected())) )
|
|
|
|
return fallthrough();
|
|
|
|
if (!ProcessLabelTrack(lt, warper))
|
2010-01-23 19:44:49 +00:00
|
|
|
bGoodResult = false;
|
2017-04-11 21:55:19 +00:00
|
|
|
},
|
2010-10-28 17:57:14 +00:00
|
|
|
#ifdef USE_MIDI
|
2017-04-11 21:55:19 +00:00
|
|
|
[&]( NoteTrack *nt, const Track::Fallthrough &fallthrough ) {
|
|
|
|
if ( !(nt->GetSelected() || (mustSync && nt->IsSyncLockSelected())) )
|
|
|
|
return fallthrough();
|
|
|
|
if (!ProcessNoteTrack(nt, warper))
|
2010-10-28 17:57:14 +00:00
|
|
|
bGoodResult = false;
|
2017-04-11 21:55:19 +00:00
|
|
|
},
|
2010-10-28 17:57:14 +00:00
|
|
|
#endif
|
2017-04-11 21:55:19 +00:00
|
|
|
[&]( WaveTrack *leftTrack, const Track::Fallthrough &fallthrough ) {
|
|
|
|
if (!leftTrack->GetSelected())
|
|
|
|
return fallthrough();
|
|
|
|
|
2020-09-18 04:22:00 +00:00
|
|
|
//Get start and end times from selection
|
|
|
|
mCurT0 = mT0;
|
|
|
|
mCurT1 = mT1;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2021-01-30 05:23:54 +00:00
|
|
|
//Set the current bounds to whichever left marker is
|
|
|
|
//greater and whichever right marker is less
|
|
|
|
mCurT0 = wxMax(mT0, mCurT0);
|
|
|
|
mCurT1 = wxMin(mT1, mCurT1);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Process only if the right marker is to the right of the left marker
|
|
|
|
if (mCurT1 > mCurT0) {
|
2020-09-06 08:37:57 +00:00
|
|
|
mSoundTouch = std::make_unique<soundtouch::SoundTouch>();
|
|
|
|
initer(mSoundTouch.get());
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2017-04-20 15:45:18 +00:00
|
|
|
// TODO: more-than-two-channels
|
|
|
|
auto channels = TrackList::Channels(leftTrack);
|
2019-03-29 15:05:01 +00:00
|
|
|
auto rightTrack = (channels.size() > 1)
|
|
|
|
? * ++ channels.first
|
|
|
|
: nullptr;
|
|
|
|
if ( rightTrack ) {
|
2010-01-23 19:44:49 +00:00
|
|
|
double t;
|
|
|
|
|
|
|
|
//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
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = leftTrack->TimeToLongSamples(mCurT0);
|
|
|
|
auto end = leftTrack->TimeToLongSamples(mCurT1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Inform soundtouch there's 2 channels
|
|
|
|
mSoundTouch->setChannels(2);
|
|
|
|
|
|
|
|
//ProcessStereo() (implemented below) processes a stereo track
|
2016-12-16 17:37:15 +00:00
|
|
|
if (!ProcessStereo(leftTrack, rightTrack, start, end, warper))
|
2010-01-23 19:44:49 +00:00
|
|
|
bGoodResult = false;
|
|
|
|
mCurTrackNum++; // Increment for rightTrack, too.
|
|
|
|
} else {
|
|
|
|
//Transform the marker timepoints to samples
|
2016-08-24 15:24:26 +00:00
|
|
|
auto start = leftTrack->TimeToLongSamples(mCurT0);
|
|
|
|
auto end = leftTrack->TimeToLongSamples(mCurT1);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
//Inform soundtouch there's a single channel
|
|
|
|
mSoundTouch->setChannels(1);
|
|
|
|
|
|
|
|
//ProcessOne() (implemented below) processes a single track
|
2016-12-16 17:37:15 +00:00
|
|
|
if (!ProcessOne(leftTrack, start, end, warper))
|
2010-01-23 19:44:49 +00:00
|
|
|
bGoodResult = false;
|
|
|
|
}
|
2020-09-06 08:37:57 +00:00
|
|
|
|
|
|
|
mSoundTouch.reset();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
|
|
|
mCurTrackNum++;
|
2017-04-11 21:55:19 +00:00
|
|
|
},
|
|
|
|
[&]( Track *t ) {
|
|
|
|
if (mustSync && t->IsSyncLockSelected()) {
|
|
|
|
t->SyncLockAdjust(mT1, warper.Warp(mT1));
|
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2017-04-11 21:55:19 +00:00
|
|
|
);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2021-01-30 05:23:54 +00:00
|
|
|
if (bGoodResult) {
|
2014-06-03 20:30:19 +00:00
|
|
|
ReplaceProcessedTracks(bGoodResult);
|
2021-01-30 05:23:54 +00:00
|
|
|
}
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
return bGoodResult;
|
|
|
|
}
|
|
|
|
|
2016-12-15 12:30:14 +00:00
|
|
|
void EffectSoundTouch::End()
|
|
|
|
{
|
|
|
|
mSoundTouch.reset();
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
|
|
|
|
//and executes ProcessSoundTouch on these blocks
|
|
|
|
bool EffectSoundTouch::ProcessOne(WaveTrack *track,
|
2016-12-16 17:37:15 +00:00
|
|
|
sampleCount start, sampleCount end,
|
|
|
|
const TimeWarper &warper)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
|
|
|
mSoundTouch->setSampleRate((unsigned int)(track->GetRate()+0.5));
|
2014-06-03 20:30:19 +00:00
|
|
|
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
auto outputTrack = track->EmptyCopy();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get the length of the buffer (as double). len is
|
|
|
|
//used simple to calculate a progress meter, so it is easier
|
2014-06-03 20:30:19 +00:00
|
|
|
//to make it a double now than it is to do it later
|
2016-08-25 12:53:59 +00:00
|
|
|
auto len = (end - start).as_double();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
{
|
|
|
|
//Initiate a processing buffer. This buffer will (most likely)
|
|
|
|
//be shorter than the length of the track being processed.
|
|
|
|
Floats buffer{ track->GetMaxBlockSize() };
|
|
|
|
|
|
|
|
//Go through the track one buffer at a time. s counts which
|
|
|
|
//sample the current buffer starts at.
|
|
|
|
auto s = start;
|
|
|
|
while (s < end) {
|
|
|
|
//Get a block of samples (smaller than the size of the buffer)
|
2021-01-30 10:56:53 +00:00
|
|
|
const auto block = wxMin(8192,
|
|
|
|
limitSampleBufferSize( track->GetBestBlockSize(s), end - s ));
|
2016-04-14 16:35:15 +00:00
|
|
|
|
|
|
|
//Get the samples from the track and put them in the buffer
|
2021-05-23 21:43:38 +00:00
|
|
|
track->GetFloats(buffer.get(), s, block);
|
2016-04-14 16:35:15 +00:00
|
|
|
|
|
|
|
//Add samples to SoundTouch
|
|
|
|
mSoundTouch->putSamples(buffer.get(), block);
|
|
|
|
|
|
|
|
//Get back samples from SoundTouch
|
|
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
|
|
if (outputCount > 0) {
|
|
|
|
Floats buffer2{ outputCount };
|
|
|
|
mSoundTouch->receiveSamples(buffer2.get(), outputCount);
|
|
|
|
outputTrack->Append((samplePtr)buffer2.get(), floatSample, outputCount);
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
//Increment s one blockfull of samples
|
|
|
|
s += block;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
//Update the Progress meter
|
|
|
|
if (TrackProgress(mCurTrackNum, (s - start).as_double() / len))
|
|
|
|
return false;
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
// Tell SoundTouch to finish processing any remaining samples
|
|
|
|
mSoundTouch->flush(); // this should only be used for changeTempo - it dumps data otherwise with pRateTransposer->clear();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
|
|
if (outputCount > 0) {
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats buffer2{ outputCount };
|
|
|
|
mSoundTouch->receiveSamples(buffer2.get(), outputCount);
|
|
|
|
outputTrack->Append((samplePtr)buffer2.get(), floatSample, outputCount);
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
// Flush the output WaveTrack (since it's buffered, too)
|
|
|
|
outputTrack->Flush();
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2020-09-18 04:22:00 +00:00
|
|
|
// Transfer output samples to the original
|
|
|
|
Finalize(track, outputTrack.get(), warper);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
|
|
|
double newLength = outputTrack->GetEndTime();
|
2010-01-23 19:44:49 +00:00
|
|
|
m_maxNewLength = wxMax(m_maxNewLength, newLength);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Return true because the effect processing succeeded.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-12-16 17:37:15 +00:00
|
|
|
bool EffectSoundTouch::ProcessStereo(
|
|
|
|
WaveTrack* leftTrack, WaveTrack* rightTrack,
|
|
|
|
sampleCount start, sampleCount end, const TimeWarper &warper)
|
2010-01-23 19:44:49 +00:00
|
|
|
{
|
2016-04-14 16:35:15 +00:00
|
|
|
mSoundTouch->setSampleRate((unsigned int)(leftTrack->GetRate() + 0.5));
|
2014-06-03 20:30:19 +00:00
|
|
|
|
Bug2346: Complete fix...
... without making undesirable dependency cycles.
Eliminate calls to NewWaveTrack in effects, but in Edit>Copy too, which was
not mentioned in the bug report. (Copying a track, deselecting all, and pasting
preserved CLIP colors, but not the TRACK color setting which applies to newly
generated clips.)
Instead, always use the new function WaveTrack::EmptyCopy from the track to be
later replaced, getting color information.
NewWaveTrack is still used in benchmark test, import, the Track menu
commands that make new tracks, recording to new tracks, and generators without
a selection, where there is no track to copy from.
Also when deserializing tracks from the .aup file, in which case the saved
color is later retrieved from the file.
Also, in mix-and-render, where other logic decides whether to copy colors
afterward.
See commit a9658e6ef7f7eaefce4dc37a93d389cca7705f41
2020-03-11 01:40:14 +00:00
|
|
|
auto outputLeftTrack = leftTrack->EmptyCopy();
|
|
|
|
auto outputRightTrack = rightTrack->EmptyCopy();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Get the length of the buffer (as double). len is
|
|
|
|
//used simple to calculate a progress meter, so it is easier
|
2014-06-03 20:30:19 +00:00
|
|
|
//to make it a double now than it is to do it later
|
2016-08-25 12:53:59 +00:00
|
|
|
double len = (end - start).as_double();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Initiate a processing buffer. This buffer will (most likely)
|
|
|
|
//be shorter than the length of the track being processed.
|
2014-06-03 20:30:19 +00:00
|
|
|
// Make soundTouchBuffer twice as big as MaxBlockSize for each channel,
|
|
|
|
// because Soundtouch wants them interleaved, i.e., each
|
|
|
|
// Soundtouch sample is left-right pair.
|
2016-08-24 15:24:26 +00:00
|
|
|
auto maxBlockSize = leftTrack->GetMaxBlockSize();
|
2016-04-14 16:35:15 +00:00
|
|
|
{
|
|
|
|
Floats leftBuffer{ maxBlockSize };
|
|
|
|
Floats rightBuffer{ maxBlockSize };
|
|
|
|
Floats soundTouchBuffer{ maxBlockSize * 2 };
|
|
|
|
|
|
|
|
// Go through the track one stereo buffer at a time.
|
|
|
|
// sourceSampleCount counts the sample at which the current buffer starts,
|
|
|
|
// per channel.
|
|
|
|
auto sourceSampleCount = start;
|
|
|
|
while (sourceSampleCount < end) {
|
|
|
|
auto blockSize = limitSampleBufferSize(
|
|
|
|
leftTrack->GetBestBlockSize(sourceSampleCount),
|
|
|
|
end - sourceSampleCount
|
|
|
|
);
|
|
|
|
|
|
|
|
// Get the samples from the tracks and put them in the buffers.
|
2021-05-23 21:43:38 +00:00
|
|
|
leftTrack->GetFloats((leftBuffer.get()), sourceSampleCount, blockSize);
|
|
|
|
rightTrack->GetFloats((rightBuffer.get()), sourceSampleCount, blockSize);
|
2016-04-14 16:35:15 +00:00
|
|
|
|
|
|
|
// Interleave into soundTouchBuffer.
|
|
|
|
for (decltype(blockSize) index = 0; index < blockSize; index++) {
|
|
|
|
soundTouchBuffer[index * 2] = leftBuffer[index];
|
|
|
|
soundTouchBuffer[(index * 2) + 1] = rightBuffer[index];
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
//Add samples to SoundTouch
|
|
|
|
mSoundTouch->putSamples(soundTouchBuffer.get(), blockSize);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
//Get back samples from SoundTouch
|
|
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
|
|
if (outputCount > 0)
|
|
|
|
this->ProcessStereoResults(outputCount, outputLeftTrack.get(), outputRightTrack.get());
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
//Increment sourceSampleCount one blockfull of samples
|
|
|
|
sourceSampleCount += blockSize;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
//Update the Progress meter
|
|
|
|
// mCurTrackNum is left track. Include right track.
|
|
|
|
int nWhichTrack = mCurTrackNum;
|
|
|
|
double frac = (sourceSampleCount - start).as_double() / 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;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
// Tell SoundTouch to finish processing any remaining samples
|
|
|
|
mSoundTouch->flush();
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
unsigned int outputCount = mSoundTouch->numSamples();
|
|
|
|
if (outputCount > 0)
|
|
|
|
this->ProcessStereoResults(outputCount, outputLeftTrack.get(), outputRightTrack.get());
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
// Flush the output WaveTracks (since they're buffered, too)
|
|
|
|
outputLeftTrack->Flush();
|
|
|
|
outputRightTrack->Flush();
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2020-09-18 04:22:00 +00:00
|
|
|
// Transfer output samples to the original
|
|
|
|
Finalize(leftTrack, outputLeftTrack.get(), warper);
|
|
|
|
Finalize(rightTrack, outputRightTrack.get(), warper);
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// Track the longest result length
|
|
|
|
double newLength = outputLeftTrack->GetEndTime();
|
|
|
|
m_maxNewLength = wxMax(m_maxNewLength, newLength);
|
|
|
|
newLength = outputRightTrack->GetEndTime();
|
|
|
|
m_maxNewLength = wxMax(m_maxNewLength, newLength);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
//Return true because the effect processing succeeded.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-12-15 12:30:14 +00:00
|
|
|
bool EffectSoundTouch::ProcessStereoResults(const size_t outputCount,
|
2014-06-03 20:30:19 +00:00
|
|
|
WaveTrack* outputLeftTrack,
|
2010-01-23 19:44:49 +00:00
|
|
|
WaveTrack* outputRightTrack)
|
|
|
|
{
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats outputSoundTouchBuffer{ outputCount * 2 };
|
|
|
|
mSoundTouch->receiveSamples(outputSoundTouchBuffer.get(), outputCount);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
// Dis-interleave outputSoundTouchBuffer into separate track buffers.
|
2016-04-14 16:35:15 +00:00
|
|
|
Floats outputLeftBuffer{ outputCount };
|
|
|
|
Floats outputRightBuffer{ outputCount };
|
2010-01-23 19:44:49 +00:00
|
|
|
for (unsigned int index = 0; index < outputCount; index++)
|
|
|
|
{
|
|
|
|
outputLeftBuffer[index] = outputSoundTouchBuffer[index*2];
|
|
|
|
outputRightBuffer[index] = outputSoundTouchBuffer[(index*2)+1];
|
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-04-14 16:35:15 +00:00
|
|
|
outputLeftTrack->Append((samplePtr)outputLeftBuffer.get(), floatSample, outputCount);
|
|
|
|
outputRightTrack->Append((samplePtr)outputRightBuffer.get(), floatSample, outputCount);
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-09-18 04:22:00 +00:00
|
|
|
void EffectSoundTouch::Finalize(WaveTrack* orig, WaveTrack* out, const TimeWarper &warper)
|
|
|
|
{
|
|
|
|
if (mPreserveLength) {
|
|
|
|
auto newLen = out->GetNumSamples();
|
|
|
|
auto oldLen = out->TimeToLongSamples(mCurT1) - out->TimeToLongSamples(mCurT0);
|
|
|
|
|
|
|
|
// Pad output track to original length since SoundTouch may remove samples
|
|
|
|
if (newLen < oldLen) {
|
|
|
|
out->InsertSilence(out->LongSamplesToTime(newLen - 1),
|
|
|
|
out->LongSamplesToTime(oldLen - newLen));
|
|
|
|
}
|
|
|
|
// Trim output track to original length since SoundTouch may add extra samples
|
|
|
|
else if (newLen > oldLen) {
|
|
|
|
out->Trim(0, out->LongSamplesToTime(oldLen));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Silenced samples will be inserted in gaps between clips, so capture where these
|
|
|
|
// gaps are for later deletion
|
|
|
|
std::vector<std::pair<double, double>> gaps;
|
2021-05-15 10:35:39 +00:00
|
|
|
double last = mCurT0;
|
2020-09-18 04:22:00 +00:00
|
|
|
auto clips = orig->SortedClipArray();
|
|
|
|
auto front = clips.front();
|
|
|
|
auto back = clips.back();
|
|
|
|
for (auto &clip : clips) {
|
|
|
|
auto st = clip->GetStartTime();
|
|
|
|
auto et = clip->GetEndTime();
|
|
|
|
|
|
|
|
if (st >= mCurT0 || et < mCurT1) {
|
|
|
|
if (mCurT0 < st && clip == front) {
|
|
|
|
gaps.push_back(std::make_pair(mCurT0, st));
|
|
|
|
}
|
2021-05-15 10:35:39 +00:00
|
|
|
else if (last < st && mCurT0 <= last ) {
|
2020-09-18 04:22:00 +00:00
|
|
|
gaps.push_back(std::make_pair(last, st));
|
|
|
|
}
|
2021-05-15 10:35:39 +00:00
|
|
|
|
|
|
|
if (et < mCurT1 && clip == back) {
|
|
|
|
gaps.push_back(std::make_pair(et, mCurT1));
|
|
|
|
}
|
2020-09-18 04:22:00 +00:00
|
|
|
}
|
|
|
|
last = et;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Take the output track and insert it in place of the original sample data
|
|
|
|
orig->ClearAndPaste(mCurT0, mCurT1, out, true, true, &warper);
|
|
|
|
|
|
|
|
// Finally, recreate the gaps
|
|
|
|
for (auto gap : gaps) {
|
|
|
|
auto st = orig->LongSamplesToTime(orig->TimeToLongSamples(gap.first));
|
|
|
|
auto et = orig->LongSamplesToTime(orig->TimeToLongSamples(gap.second));
|
2021-01-30 05:23:54 +00:00
|
|
|
if (st >= mCurT0 && et <= mCurT1 && st != et)
|
|
|
|
{
|
|
|
|
orig->SplitDelete(warper.Warp(st), warper.Warp(et));
|
|
|
|
}
|
2020-09-18 04:22:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif // USE_SOUNDTOUCH
|