mirror of
https://github.com/cookiengineer/audacity
synced 2025-11-06 17:13:49 +01:00
Implement compressor2 offline processing.
Signed-off-by: Max Maisel <max.maisel@posteo.de>
This commit is contained in:
@@ -166,7 +166,7 @@ SlidingMaxPreprocessor::SlidingMaxPreprocessor(size_t windowSize)
|
|||||||
|
|
||||||
float SlidingMaxPreprocessor::ProcessSample(float value)
|
float SlidingMaxPreprocessor::ProcessSample(float value)
|
||||||
{
|
{
|
||||||
return DoProcessSample(value);
|
return DoProcessSample(fabs(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
float SlidingMaxPreprocessor::ProcessSample(float valueL, float valueR)
|
float SlidingMaxPreprocessor::ProcessSample(float valueL, float valueR)
|
||||||
@@ -226,8 +226,8 @@ size_t EnvelopeDetector::GetBlockSize() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
ExpFitEnvelopeDetector::ExpFitEnvelopeDetector(
|
ExpFitEnvelopeDetector::ExpFitEnvelopeDetector(
|
||||||
float rate, float attackTime, float releaseTime)
|
float rate, float attackTime, float releaseTime, size_t bufferSize)
|
||||||
: EnvelopeDetector(TAU_FACTOR * (attackTime + 1.0) * rate)
|
: EnvelopeDetector(bufferSize)
|
||||||
{
|
{
|
||||||
mAttackFactor = exp(-1.0 / (rate * attackTime));
|
mAttackFactor = exp(-1.0 / (rate * attackTime));
|
||||||
mReleaseFactor = exp(-1.0 / (rate * releaseTime));
|
mReleaseFactor = exp(-1.0 / (rate * releaseTime));
|
||||||
@@ -316,8 +316,9 @@ void ExpFitEnvelopeDetector::Follow()
|
|||||||
}
|
}
|
||||||
|
|
||||||
Pt1EnvelopeDetector::Pt1EnvelopeDetector(
|
Pt1EnvelopeDetector::Pt1EnvelopeDetector(
|
||||||
float rate, float attackTime, float releaseTime, bool correctGain)
|
float rate, float attackTime, float releaseTime, size_t bufferSize,
|
||||||
: EnvelopeDetector(TAU_FACTOR * (attackTime + 1.0) * rate)
|
bool correctGain)
|
||||||
|
: EnvelopeDetector(bufferSize)
|
||||||
{
|
{
|
||||||
// Approximate peak amplitude correction factor.
|
// Approximate peak amplitude correction factor.
|
||||||
if(correctGain)
|
if(correctGain)
|
||||||
@@ -347,6 +348,49 @@ void Pt1EnvelopeDetector::Follow()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PipelineBuffer::pad_to(size_t len, float value, bool stereo)
|
||||||
|
{
|
||||||
|
if(size < len)
|
||||||
|
{
|
||||||
|
size = len;
|
||||||
|
std::fill(mBlockBuffer[0].get() + trackSize,
|
||||||
|
mBlockBuffer[0].get() + size, value);
|
||||||
|
if(stereo)
|
||||||
|
std::fill(mBlockBuffer[1].get() + trackSize,
|
||||||
|
mBlockBuffer[1].get() + size, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineBuffer::swap(PipelineBuffer& other)
|
||||||
|
{
|
||||||
|
std::swap(trackPos, other.trackPos);
|
||||||
|
std::swap(trackSize, other.trackSize);
|
||||||
|
std::swap(size, other.size);
|
||||||
|
std::swap(mBlockBuffer[0], other.mBlockBuffer[0]);
|
||||||
|
std::swap(mBlockBuffer[1], other.mBlockBuffer[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineBuffer::init(size_t capacity, bool stereo)
|
||||||
|
{
|
||||||
|
trackPos = 0;
|
||||||
|
trackSize = 0;
|
||||||
|
size = 0;
|
||||||
|
mCapacity = capacity;
|
||||||
|
mBlockBuffer[0].reinit(capacity);
|
||||||
|
std::fill(mBlockBuffer[0].get(), mBlockBuffer[0].get() + capacity, 0);
|
||||||
|
if(stereo)
|
||||||
|
{
|
||||||
|
mBlockBuffer[1].reinit(capacity);
|
||||||
|
std::fill(mBlockBuffer[1].get(), mBlockBuffer[1].get() + capacity, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineBuffer::free()
|
||||||
|
{
|
||||||
|
mBlockBuffer[0].reset();
|
||||||
|
mBlockBuffer[1].reset();
|
||||||
|
}
|
||||||
|
|
||||||
EffectCompressor2::EffectCompressor2()
|
EffectCompressor2::EffectCompressor2()
|
||||||
: mIgnoreGuiEvents(false)
|
: mIgnoreGuiEvents(false)
|
||||||
{
|
{
|
||||||
@@ -503,7 +547,52 @@ bool EffectCompressor2::Startup()
|
|||||||
|
|
||||||
bool EffectCompressor2::Process()
|
bool EffectCompressor2::Process()
|
||||||
{
|
{
|
||||||
return false;
|
// Iterate over each track
|
||||||
|
this->CopyInputTracks(); // Set up mOutputTracks.
|
||||||
|
bool bGoodResult = true;
|
||||||
|
|
||||||
|
AllocPipeline();
|
||||||
|
mProgressVal = 0;
|
||||||
|
|
||||||
|
for(auto track : mOutputTracks->Selected<WaveTrack>()
|
||||||
|
+ (mStereoInd ? &Track::Any : &Track::IsLeader))
|
||||||
|
{
|
||||||
|
// Get start and end times from track
|
||||||
|
// PRL: No accounting for multiple channels ?
|
||||||
|
double trackStart = track->GetStartTime();
|
||||||
|
double trackEnd = track->GetEndTime();
|
||||||
|
|
||||||
|
// Set the current bounds to whichever left marker is
|
||||||
|
// greater and whichever right marker is less:
|
||||||
|
mCurT0 = mT0 < trackStart? trackStart: mT0;
|
||||||
|
mCurT1 = mT1 > trackEnd? trackEnd: mT1;
|
||||||
|
|
||||||
|
// Get the track rate
|
||||||
|
mSampleRate = track->GetRate();
|
||||||
|
|
||||||
|
auto range = mStereoInd
|
||||||
|
? TrackList::SingletonRange(track)
|
||||||
|
: TrackList::Channels(track);
|
||||||
|
|
||||||
|
mProcStereo = range.size() > 1;
|
||||||
|
|
||||||
|
InitGainCalculation();
|
||||||
|
mPreproc = InitPreprocessor(mSampleRate);
|
||||||
|
mEnvelope = InitEnvelope(mSampleRate, mPipeline[0].capacity());
|
||||||
|
|
||||||
|
if(!ProcessOne(range))
|
||||||
|
{
|
||||||
|
// Processing failed -> abort
|
||||||
|
bGoodResult = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->ReplaceProcessedTracks(bGoodResult);
|
||||||
|
mPreproc.reset(nullptr);
|
||||||
|
mEnvelope.reset(nullptr);
|
||||||
|
FreePipeline();
|
||||||
|
return bGoodResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
|
void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
|
||||||
@@ -726,6 +815,7 @@ bool EffectCompressor2::TransferDataFromWindow()
|
|||||||
|
|
||||||
void EffectCompressor2::InitGainCalculation()
|
void EffectCompressor2::InitGainCalculation()
|
||||||
{
|
{
|
||||||
|
mDryWet = mDryWetPct / 100.0;
|
||||||
mMakeupGainDB = mMakeupGainPct / 100.0 *
|
mMakeupGainDB = mMakeupGainPct / 100.0 *
|
||||||
-(mThresholdDB * (1.0 - 1.0 / mRatio));
|
-(mThresholdDB * (1.0 - 1.0 / mRatio));
|
||||||
mMakeupGain = DB_TO_LINEAR(mMakeupGainDB);
|
mMakeupGain = DB_TO_LINEAR(mMakeupGainDB);
|
||||||
@@ -763,6 +853,282 @@ double EffectCompressor2::CompressorGain(double env)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SamplePreprocessor> EffectCompressor2::InitPreprocessor(
|
||||||
|
double rate, bool preview)
|
||||||
|
{
|
||||||
|
size_t window_size =
|
||||||
|
std::max(1, int(round((mLookaheadTime + mLookbehindTime) * rate)));
|
||||||
|
|
||||||
|
if(mCompressBy == kAmplitude)
|
||||||
|
return std::unique_ptr<SamplePreprocessor>(safenew
|
||||||
|
SlidingMaxPreprocessor(window_size));
|
||||||
|
else
|
||||||
|
return std::unique_ptr<SamplePreprocessor>(safenew
|
||||||
|
SlidingRmsPreprocessor(window_size, preview ? 1.0 : 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<EnvelopeDetector> EffectCompressor2::InitEnvelope(
|
||||||
|
double rate, size_t blockSize, bool preview)
|
||||||
|
{
|
||||||
|
if(mAlgorithm == kExpFit)
|
||||||
|
return std::unique_ptr<EnvelopeDetector>(safenew
|
||||||
|
ExpFitEnvelopeDetector(rate, mAttackTime, mReleaseTime, blockSize));
|
||||||
|
else
|
||||||
|
return std::unique_ptr<EnvelopeDetector>(safenew
|
||||||
|
Pt1EnvelopeDetector(rate, mAttackTime, mReleaseTime, blockSize,
|
||||||
|
!preview && mCompressBy != kAmplitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t EffectCompressor2::CalcBufferSize(size_t sampleRate)
|
||||||
|
{
|
||||||
|
size_t capacity;
|
||||||
|
|
||||||
|
mLookaheadLength =
|
||||||
|
std::max(0, int(round(mLookaheadTime * sampleRate)));
|
||||||
|
capacity = mLookaheadLength +
|
||||||
|
size_t(float(TAU_FACTOR) * (1.0 + mAttackTime) * sampleRate);
|
||||||
|
if(capacity < MIN_BUFFER_CAPACITY)
|
||||||
|
capacity = MIN_BUFFER_CAPACITY;
|
||||||
|
return capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get required buffer size for the largest whole track and allocate buffers.
|
||||||
|
/// This reduces the amount of allocations required.
|
||||||
|
void EffectCompressor2::AllocPipeline()
|
||||||
|
{
|
||||||
|
bool stereoTrackFound = false;
|
||||||
|
double maxSampleRate = 0;
|
||||||
|
size_t capacity;
|
||||||
|
|
||||||
|
mProcStereo = false;
|
||||||
|
|
||||||
|
for(auto track : mOutputTracks->Selected<WaveTrack>() + &Track::Any)
|
||||||
|
{
|
||||||
|
maxSampleRate = std::max(maxSampleRate, track->GetRate());
|
||||||
|
|
||||||
|
// There is a stereo track
|
||||||
|
if(track->IsLeader())
|
||||||
|
stereoTrackFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate a processing quad-buffer. This buffer will (most likely)
|
||||||
|
// be shorter than the length of the track being processed.
|
||||||
|
stereoTrackFound = stereoTrackFound && !mStereoInd;
|
||||||
|
capacity = CalcBufferSize(maxSampleRate);
|
||||||
|
for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
|
||||||
|
mPipeline[i].init(capacity, stereoTrackFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectCompressor2::FreePipeline()
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
|
||||||
|
mPipeline[i].free();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectCompressor2::SwapPipeline()
|
||||||
|
{
|
||||||
|
++mProgressVal;
|
||||||
|
for(size_t i = 0; i < PIPELINE_DEPTH-1; ++i)
|
||||||
|
mPipeline[i].swap(mPipeline[i+1]);
|
||||||
|
std::cerr << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
|
||||||
|
/// and executes ProcessData, on it...
|
||||||
|
bool EffectCompressor2::ProcessOne(TrackIterRange<WaveTrack> range)
|
||||||
|
{
|
||||||
|
WaveTrack* track = *range.begin();
|
||||||
|
|
||||||
|
// Transform the marker timepoints to samples
|
||||||
|
const auto start = track->TimeToLongSamples(mCurT0);
|
||||||
|
const auto end = track->TimeToLongSamples(mCurT1);
|
||||||
|
|
||||||
|
// Get the length of the buffer (as double). len is
|
||||||
|
// used simply to calculate a progress meter, so it is easier
|
||||||
|
// to make it a double now than it is to do it later
|
||||||
|
mTrackLen = (end - start).as_double();
|
||||||
|
|
||||||
|
// Abort if the right marker is not to the right of the left marker
|
||||||
|
if(mCurT1 <= mCurT0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Go through the track one buffer at a time. s counts which
|
||||||
|
// sample the current buffer starts at.
|
||||||
|
auto pos = start;
|
||||||
|
|
||||||
|
mProgressVal = 0;
|
||||||
|
while(pos < end)
|
||||||
|
{
|
||||||
|
StorePipeline(range);
|
||||||
|
SwapPipeline();
|
||||||
|
|
||||||
|
const size_t remainingLen = (end - pos).as_size_t();
|
||||||
|
|
||||||
|
// Get a block of samples (smaller than the size of the buffer)
|
||||||
|
// Adjust the block size if it is the final block in the track
|
||||||
|
const auto blockLen = limitSampleBufferSize(
|
||||||
|
remainingLen, mPipeline[PIPELINE_DEPTH-1].capacity());
|
||||||
|
|
||||||
|
mPipeline[PIPELINE_DEPTH-1].trackPos = pos;
|
||||||
|
if(!LoadPipeline(range, blockLen))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(mPipeline[0].size == 0)
|
||||||
|
FillPipeline();
|
||||||
|
else
|
||||||
|
ProcessPipeline();
|
||||||
|
|
||||||
|
// Increment s one blockfull of samples
|
||||||
|
pos += blockLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle short selections
|
||||||
|
while(mPipeline[1].size == 0)
|
||||||
|
{
|
||||||
|
SwapPipeline();
|
||||||
|
FillPipeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
while(PipelineHasData())
|
||||||
|
{
|
||||||
|
StorePipeline(range);
|
||||||
|
SwapPipeline();
|
||||||
|
DrainPipeline();
|
||||||
|
}
|
||||||
|
StorePipeline(range);
|
||||||
|
|
||||||
|
// Return true because the effect processing succeeded ... unless cancelled
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EffectCompressor2::LoadPipeline(
|
||||||
|
TrackIterRange<WaveTrack> range, size_t len)
|
||||||
|
{
|
||||||
|
sampleCount read_size = -1;
|
||||||
|
sampleCount last_read_size = -1;
|
||||||
|
// Get the samples from the track and put them in the buffer
|
||||||
|
int idx = 0;
|
||||||
|
for(auto channel : range)
|
||||||
|
{
|
||||||
|
channel->Get((samplePtr) mPipeline[PIPELINE_DEPTH-1][idx],
|
||||||
|
floatSample, mPipeline[PIPELINE_DEPTH-1].trackPos, len,
|
||||||
|
fillZero, true, &read_size);
|
||||||
|
// WaveTrack::Get returns the amount of read samples excluding zero
|
||||||
|
// filled samples from clip gaps. But in case of stereo tracks with
|
||||||
|
// assymetric gaps it still returns the same number for both channels.
|
||||||
|
//
|
||||||
|
// Fail if we read different sample count from stereo pair tracks.
|
||||||
|
// Ignore this check during first iteration (last_read_size == -1).
|
||||||
|
if(read_size != last_read_size && last_read_size.as_long_long() != -1)
|
||||||
|
return false;
|
||||||
|
mPipeline[PIPELINE_DEPTH-1].trackSize = read_size.as_size_t();
|
||||||
|
mPipeline[PIPELINE_DEPTH-1].size = read_size.as_size_t();
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxASSERT(mPipeline[PIPELINE_DEPTH-2].trackSize == 0 ||
|
||||||
|
mPipeline[PIPELINE_DEPTH-2].trackSize >=
|
||||||
|
mPipeline[PIPELINE_DEPTH-1].trackSize);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectCompressor2::FillPipeline()
|
||||||
|
{
|
||||||
|
// TODO: correct end conditions
|
||||||
|
mPipeline[PIPELINE_DEPTH-1].pad_to(mEnvelope->GetBlockSize(), 0, mProcStereo);
|
||||||
|
|
||||||
|
size_t length = mPipeline[PIPELINE_DEPTH-1].size;
|
||||||
|
for(size_t rp = mLookaheadLength, wp = 0; wp < length; ++rp, ++wp)
|
||||||
|
{
|
||||||
|
// TODO: correct initial conditions
|
||||||
|
if(rp < length)
|
||||||
|
EnvelopeSample(mPipeline[PIPELINE_DEPTH-2], rp);
|
||||||
|
else
|
||||||
|
EnvelopeSample(mPipeline[PIPELINE_DEPTH-1], rp % length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectCompressor2::ProcessPipeline()
|
||||||
|
{
|
||||||
|
float env;
|
||||||
|
size_t length = mPipeline[0].size;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < PIPELINE_DEPTH-2; ++i)
|
||||||
|
{ wxASSERT(mPipeline[0].size == mPipeline[i+1].size); }
|
||||||
|
|
||||||
|
for(size_t rp = mLookaheadLength, wp = 0; wp < length; ++rp, ++wp)
|
||||||
|
{
|
||||||
|
if(rp < length)
|
||||||
|
env = EnvelopeSample(mPipeline[PIPELINE_DEPTH-2], rp);
|
||||||
|
else if((rp % length) < mPipeline[PIPELINE_DEPTH-1].size)
|
||||||
|
env = EnvelopeSample(mPipeline[PIPELINE_DEPTH-1], rp % length);
|
||||||
|
else
|
||||||
|
// TODO: correct end condition
|
||||||
|
env = mEnvelope->ProcessSample(mPreproc->ProcessSample(0.0));
|
||||||
|
CompressSample(env, wp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float EffectCompressor2::EnvelopeSample(PipelineBuffer& pbuf, size_t rp)
|
||||||
|
{
|
||||||
|
float preprocessed;
|
||||||
|
if(mProcStereo)
|
||||||
|
preprocessed = mPreproc->ProcessSample(pbuf[0][rp], pbuf[1][rp]);
|
||||||
|
else
|
||||||
|
preprocessed = mPreproc->ProcessSample(pbuf[0][rp]);
|
||||||
|
return mEnvelope->ProcessSample(preprocessed);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void EffectCompressor2::CompressSample(float env, size_t wp)
|
||||||
|
{
|
||||||
|
float gain = (1.0 - mDryWet) + CompressorGain(env) * mDryWet;
|
||||||
|
mPipeline[0][0][wp] = mPipeline[0][0][wp] * gain;
|
||||||
|
if(mProcStereo)
|
||||||
|
mPipeline[0][1][wp] = mPipeline[0][1][wp] * gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EffectCompressor2::PipelineHasData()
|
||||||
|
{
|
||||||
|
for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
|
||||||
|
{
|
||||||
|
if(mPipeline[i].size != 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectCompressor2::DrainPipeline()
|
||||||
|
{
|
||||||
|
float env;
|
||||||
|
size_t length = mPipeline[0].size;
|
||||||
|
size_t length2 = mPipeline[PIPELINE_DEPTH-2].size;
|
||||||
|
for(size_t rp = mLookaheadLength, wp = 0; wp < length; ++rp, ++wp)
|
||||||
|
{
|
||||||
|
if(rp < length2 && mPipeline[PIPELINE_DEPTH-2].size != 0)
|
||||||
|
{
|
||||||
|
env = EnvelopeSample(mPipeline[PIPELINE_DEPTH-2], rp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// TODO: correct end condition
|
||||||
|
env = mEnvelope->ProcessSample(mPreproc->ProcessSample(0.0));
|
||||||
|
CompressSample(env, wp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectCompressor2::StorePipeline(TrackIterRange<WaveTrack> range)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
for(auto channel : range)
|
||||||
|
{
|
||||||
|
// Copy the newly-changed samples back onto the track.
|
||||||
|
channel->Set((samplePtr) mPipeline[0][idx],
|
||||||
|
floatSample, mPipeline[0].trackPos, mPipeline[0].trackSize);
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
mPipeline[0].trackSize = 0;
|
||||||
|
mPipeline[0].size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
|
||||||
{
|
{
|
||||||
if(!mIgnoreGuiEvents)
|
if(!mIgnoreGuiEvents)
|
||||||
@@ -804,30 +1170,17 @@ void EffectCompressor2::UpdateResponsePlot()
|
|||||||
std::unique_ptr<EnvelopeDetector> envelope;
|
std::unique_ptr<EnvelopeDetector> envelope;
|
||||||
float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
|
float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
|
||||||
|
|
||||||
size_t window_size =
|
|
||||||
std::max(1, int(round((mLookaheadTime + mLookbehindTime) * plot_rate)));
|
|
||||||
size_t lookahead_size =
|
size_t lookahead_size =
|
||||||
std::max(0, int(round(mLookaheadTime * plot_rate)));
|
std::max(0, int(round(mLookaheadTime * plot_rate)));
|
||||||
|
ssize_t block_size = float(TAU_FACTOR) * (mAttackTime + 1.0) * plot_rate;
|
||||||
|
|
||||||
if(mCompressBy == kRMS)
|
preproc = InitPreprocessor(plot_rate, true);
|
||||||
preproc = std::unique_ptr<SamplePreprocessor>(
|
envelope = InitEnvelope(plot_rate, block_size, true);
|
||||||
safenew SlidingRmsPreprocessor(window_size, 1.0));
|
|
||||||
else
|
|
||||||
preproc = std::unique_ptr<SamplePreprocessor>(
|
|
||||||
safenew SlidingMaxPreprocessor(window_size));
|
|
||||||
|
|
||||||
if(mAlgorithm == kExpFit)
|
|
||||||
envelope = std::unique_ptr<EnvelopeDetector>(
|
|
||||||
safenew ExpFitEnvelopeDetector(plot_rate, mAttackTime, mReleaseTime));
|
|
||||||
else
|
|
||||||
envelope = std::unique_ptr<EnvelopeDetector>(
|
|
||||||
safenew Pt1EnvelopeDetector(plot_rate, mAttackTime, mReleaseTime, false));
|
|
||||||
|
|
||||||
ssize_t step_start = RESPONSE_PLOT_STEP_START * plot_rate - lookahead_size;
|
ssize_t step_start = RESPONSE_PLOT_STEP_START * plot_rate - lookahead_size;
|
||||||
ssize_t step_stop = RESPONSE_PLOT_STEP_STOP * plot_rate - lookahead_size;
|
ssize_t step_stop = RESPONSE_PLOT_STEP_STOP * plot_rate - lookahead_size;
|
||||||
|
|
||||||
ssize_t xsize = plot->xdata.size();
|
ssize_t xsize = plot->xdata.size();
|
||||||
ssize_t block_size = envelope->GetBlockSize();
|
|
||||||
for(ssize_t i = -lookahead_size; i < 2*block_size; ++i)
|
for(ssize_t i = -lookahead_size; i < 2*block_size; ++i)
|
||||||
{
|
{
|
||||||
if(i < step_start || i > step_stop)
|
if(i < step_start || i > step_stop)
|
||||||
|
|||||||
@@ -75,8 +75,6 @@ class EnvelopeDetector
|
|||||||
float ProcessSample(float value);
|
float ProcessSample(float value);
|
||||||
size_t GetBlockSize() const;
|
size_t GetBlockSize() const;
|
||||||
protected:
|
protected:
|
||||||
static const int TAU_FACTOR = 5;
|
|
||||||
|
|
||||||
size_t mPos;
|
size_t mPos;
|
||||||
std::vector<float> mLookaheadBuffer;
|
std::vector<float> mLookaheadBuffer;
|
||||||
std::vector<float> mProcessingBuffer;
|
std::vector<float> mProcessingBuffer;
|
||||||
@@ -88,7 +86,8 @@ class EnvelopeDetector
|
|||||||
class ExpFitEnvelopeDetector : public EnvelopeDetector
|
class ExpFitEnvelopeDetector : public EnvelopeDetector
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ExpFitEnvelopeDetector(float rate, float attackTime, float releaseTime);
|
ExpFitEnvelopeDetector(float rate, float attackTime, float releaseTime,
|
||||||
|
size_t buffer_size = 0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double mAttackFactor;
|
double mAttackFactor;
|
||||||
@@ -101,7 +100,7 @@ class Pt1EnvelopeDetector : public EnvelopeDetector
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Pt1EnvelopeDetector(float rate, float attackTime, float releaseTime,
|
Pt1EnvelopeDetector(float rate, float attackTime, float releaseTime,
|
||||||
bool correctGain = true);
|
size_t buffer_size = 0, bool correctGain = true);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double mGainCorrection;
|
double mGainCorrection;
|
||||||
@@ -111,6 +110,27 @@ class Pt1EnvelopeDetector : public EnvelopeDetector
|
|||||||
virtual void Follow();
|
virtual void Follow();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PipelineBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
sampleCount trackPos;
|
||||||
|
size_t trackSize;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
inline float* operator[](size_t idx)
|
||||||
|
{ return mBlockBuffer[idx].get(); }
|
||||||
|
|
||||||
|
void pad_to(size_t len, float value, bool stereo);
|
||||||
|
void swap(PipelineBuffer& other);
|
||||||
|
void init(size_t size, bool stereo);
|
||||||
|
inline size_t capacity() const { return mCapacity; }
|
||||||
|
void free();
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t mCapacity;
|
||||||
|
Floats mBlockBuffer[2];
|
||||||
|
};
|
||||||
|
|
||||||
class EffectCompressor2 final : public Effect
|
class EffectCompressor2 final : public Effect
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -148,6 +168,24 @@ private:
|
|||||||
// EffectCompressor2 implementation
|
// EffectCompressor2 implementation
|
||||||
void InitGainCalculation();
|
void InitGainCalculation();
|
||||||
double CompressorGain(double env);
|
double CompressorGain(double env);
|
||||||
|
std::unique_ptr<SamplePreprocessor> InitPreprocessor(
|
||||||
|
double rate, bool preview = false);
|
||||||
|
std::unique_ptr<EnvelopeDetector> InitEnvelope(
|
||||||
|
double rate, size_t blockSize = 0, bool preview = false);
|
||||||
|
size_t CalcBufferSize(size_t sampleRate);
|
||||||
|
|
||||||
|
void AllocPipeline();
|
||||||
|
void FreePipeline();
|
||||||
|
void SwapPipeline();
|
||||||
|
bool ProcessOne(TrackIterRange<WaveTrack> range);
|
||||||
|
bool LoadPipeline(TrackIterRange<WaveTrack> range, size_t len);
|
||||||
|
void FillPipeline();
|
||||||
|
void ProcessPipeline();
|
||||||
|
inline float EnvelopeSample(PipelineBuffer& pbuf, size_t rp);
|
||||||
|
inline void CompressSample(float env, size_t wp);
|
||||||
|
bool PipelineHasData();
|
||||||
|
void DrainPipeline();
|
||||||
|
void StorePipeline(TrackIterRange<WaveTrack> range);
|
||||||
|
|
||||||
bool UpdateProgress();
|
bool UpdateProgress();
|
||||||
void OnUpdateUI(wxCommandEvent & evt);
|
void OnUpdateUI(wxCommandEvent & evt);
|
||||||
@@ -155,6 +193,21 @@ private:
|
|||||||
void UpdateCompressorPlot();
|
void UpdateCompressorPlot();
|
||||||
void UpdateResponsePlot();
|
void UpdateResponsePlot();
|
||||||
|
|
||||||
|
static const int TAU_FACTOR = 5;
|
||||||
|
static const size_t MIN_BUFFER_CAPACITY = 1048576; // 1MB
|
||||||
|
|
||||||
|
static const size_t PIPELINE_DEPTH = 4;
|
||||||
|
PipelineBuffer mPipeline[PIPELINE_DEPTH];
|
||||||
|
|
||||||
|
double mCurT0;
|
||||||
|
double mCurT1;
|
||||||
|
double mProgressVal;
|
||||||
|
double mTrackLen;
|
||||||
|
bool mProcStereo;
|
||||||
|
|
||||||
|
std::unique_ptr<SamplePreprocessor> mPreproc;
|
||||||
|
std::unique_ptr<EnvelopeDetector> mEnvelope;
|
||||||
|
|
||||||
int mAlgorithm;
|
int mAlgorithm;
|
||||||
int mCompressBy;
|
int mCompressBy;
|
||||||
bool mStereoInd;
|
bool mStereoInd;
|
||||||
@@ -170,8 +223,10 @@ private:
|
|||||||
double mDryWetPct;
|
double mDryWetPct;
|
||||||
|
|
||||||
// cached intermediate values
|
// cached intermediate values
|
||||||
|
double mDryWet;
|
||||||
double mMakeupGain;
|
double mMakeupGain;
|
||||||
double mMakeupGainDB;
|
double mMakeupGainDB;
|
||||||
|
size_t mLookaheadLength;
|
||||||
|
|
||||||
static const size_t RESPONSE_PLOT_SAMPLES = 200;
|
static const size_t RESPONSE_PLOT_SAMPLES = 200;
|
||||||
static const size_t RESPONSE_PLOT_TIME = 5;
|
static const size_t RESPONSE_PLOT_TIME = 5;
|
||||||
|
|||||||
Reference in New Issue
Block a user