aboutsummaryrefslogtreecommitdiffstats
path: root/core/mastering.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/mastering.cpp')
-rw-r--r--core/mastering.cpp237
1 files changed, 110 insertions, 127 deletions
diff --git a/core/mastering.cpp b/core/mastering.cpp
index 4445719b..9eaabc35 100644
--- a/core/mastering.cpp
+++ b/core/mastering.cpp
@@ -21,8 +21,8 @@
static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2");
struct SlidingHold {
- alignas(16) float mValues[BufferLineSize];
- uint mExpiries[BufferLineSize];
+ alignas(16) FloatBufferLine mValues;
+ std::array<uint,BufferLineSize> mExpiries;
uint mLowerIndex;
uint mUpperIndex;
uint mLength;
@@ -44,8 +44,8 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
{
static constexpr uint mask{BufferLineSize - 1};
const uint length{Hold->mLength};
- float (&values)[BufferLineSize] = Hold->mValues;
- uint (&expiries)[BufferLineSize] = Hold->mExpiries;
+ const al::span values{Hold->mValues};
+ const al::span expiries{Hold->mExpiries};
uint lowerIndex{Hold->mLowerIndex};
uint upperIndex{Hold->mUpperIndex};
@@ -60,14 +60,16 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
}
else
{
- do {
+ auto findLowerIndex = [&lowerIndex,in,values]() noexcept -> bool
+ {
do {
if(!(in >= values[lowerIndex]))
- goto found_place;
+ return true;
} while(lowerIndex--);
+ return false;
+ };
+ while(!findLowerIndex())
lowerIndex = mask;
- } while(true);
- found_place:
lowerIndex = (lowerIndex + 1) & mask;
values[lowerIndex] = in;
@@ -82,38 +84,38 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
void ShiftSlidingHold(SlidingHold *Hold, const uint n)
{
- auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex;
- auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex;
- if(exp_last-exp_begin < 0)
+ auto exp_upper = Hold->mExpiries.begin() + Hold->mUpperIndex;
+ if(Hold->mLowerIndex < Hold->mUpperIndex)
{
- std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin,
- [n](uint e){ return e - n; });
- exp_begin = std::begin(Hold->mExpiries);
+ std::transform(exp_upper, Hold->mExpiries.end(), exp_upper,
+ [n](const uint e) noexcept { return e - n; });
+ exp_upper = Hold->mExpiries.begin();
}
- std::transform(exp_begin, exp_last+1, exp_begin, [n](uint e){ return e - n; });
+ const auto exp_lower = Hold->mExpiries.begin() + Hold->mLowerIndex;
+ std::transform(exp_upper, exp_lower+1, exp_upper,
+ [n](const uint e) noexcept { return e - n; });
}
+} // namespace
/* Multichannel compression is linked via the absolute maximum of all
* channels.
*/
-void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLine *OutBuffer)
+void Compressor::linkChannels(const uint SamplesToDo, const FloatBufferLine *OutBuffer)
{
- const size_t numChans{Comp->mNumChans};
-
ASSUME(SamplesToDo > 0);
- ASSUME(numChans > 0);
- auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ const auto side_begin = mSideChain.begin() + mLookAhead;
std::fill(side_begin, side_begin+SamplesToDo, 0.0f);
auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void
{
const float *RESTRICT buffer{al::assume_aligned<16>(input.data())};
- auto max_abs = std::bind(maxf, _1, std::bind(static_cast<float(&)(float)>(std::fabs), _2));
+ auto max_abs = [](const float s0, const float s1) noexcept -> float
+ { return std::max(s0, std::fabs(s1)); };
std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs);
};
- std::for_each(OutBuffer, OutBuffer+numChans, fill_max);
+ std::for_each(OutBuffer, OutBuffer+mNumChans, fill_max);
}
/* This calculates the squared crest factor of the control signal for the
@@ -121,59 +123,59 @@ void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLin
* it uses an instantaneous squared peak detector and a squared RMS detector
* both with 200ms release times.
*/
-void CrestDetector(Compressor *Comp, const uint SamplesToDo)
+void Compressor::crestDetector(const uint SamplesToDo)
{
- const float a_crest{Comp->mCrestCoeff};
- float y2_peak{Comp->mLastPeakSq};
- float y2_rms{Comp->mLastRmsSq};
+ const float a_crest{mCrestCoeff};
+ float y2_peak{mLastPeakSq};
+ float y2_rms{mLastRmsSq};
ASSUME(SamplesToDo > 0);
auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float
{
- const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)};
+ const float x2{clampf(x_abs*x_abs, 0.000001f, 1000000.0f)};
- y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest));
+ y2_peak = std::max(x2, lerpf(x2, y2_peak, a_crest));
y2_rms = lerpf(x2, y2_rms, a_crest);
return y2_peak / y2_rms;
};
- auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
- std::transform(side_begin, side_begin+SamplesToDo, std::begin(Comp->mCrestFactor), calc_crest);
+ const auto side_begin = mSideChain.begin() + mLookAhead;
+ std::transform(side_begin, side_begin+SamplesToDo, mCrestFactor.begin(), calc_crest);
- Comp->mLastPeakSq = y2_peak;
- Comp->mLastRmsSq = y2_rms;
+ mLastPeakSq = y2_peak;
+ mLastRmsSq = y2_rms;
}
/* The side-chain starts with a simple peak detector (based on the absolute
* value of the incoming signal) and performs most of its operations in the
* log domain.
*/
-void PeakDetector(Compressor *Comp, const uint SamplesToDo)
+void Compressor::peakDetector(const uint SamplesToDo)
{
ASSUME(SamplesToDo > 0);
- /* Clamp the minimum amplitude to near-zero and convert to logarithm. */
- auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */
+ const auto side_begin = mSideChain.begin() + mLookAhead;
std::transform(side_begin, side_begin+SamplesToDo, side_begin,
- [](float s) { return std::log(maxf(0.000001f, s)); });
+ [](float s) { return std::log(std::max(0.000001f, s)); });
}
/* An optional hold can be used to extend the peak detector so it can more
* solidly detect fast transients. This is best used when operating as a
* limiter.
*/
-void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo)
+void Compressor::peakHoldDetector(const uint SamplesToDo)
{
ASSUME(SamplesToDo > 0);
- SlidingHold *hold{Comp->mHold};
+ SlidingHold *hold{mHold.get()};
uint i{0};
auto detect_peak = [&i,hold](const float x_abs) -> float
{
- const float x_G{std::log(maxf(0.000001f, x_abs))};
+ const float x_G{std::log(std::max(0.000001f, x_abs))};
return UpdateSlidingHold(hold, i++, x_G);
};
- auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead;
+ auto side_begin = mSideChain.begin() + mLookAhead;
std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak);
ShiftSlidingHold(hold, SamplesToDo);
@@ -184,46 +186,46 @@ void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo)
* to knee width, attack/release times, make-up/post gain, and clipping
* reduction.
*/
-void GainCompressor(Compressor *Comp, const uint SamplesToDo)
+void Compressor::gainCompressor(const uint SamplesToDo)
{
- const bool autoKnee{Comp->mAuto.Knee};
- const bool autoAttack{Comp->mAuto.Attack};
- const bool autoRelease{Comp->mAuto.Release};
- const bool autoPostGain{Comp->mAuto.PostGain};
- const bool autoDeclip{Comp->mAuto.Declip};
- const uint lookAhead{Comp->mLookAhead};
- const float threshold{Comp->mThreshold};
- const float slope{Comp->mSlope};
- const float attack{Comp->mAttack};
- const float release{Comp->mRelease};
- const float c_est{Comp->mGainEstimate};
- const float a_adp{Comp->mAdaptCoeff};
- const float *crestFactor{Comp->mCrestFactor};
- float postGain{Comp->mPostGain};
- float knee{Comp->mKnee};
+ const bool autoKnee{mAuto.Knee};
+ const bool autoAttack{mAuto.Attack};
+ const bool autoRelease{mAuto.Release};
+ const bool autoPostGain{mAuto.PostGain};
+ const bool autoDeclip{mAuto.Declip};
+ const float threshold{mThreshold};
+ const float slope{mSlope};
+ const float attack{mAttack};
+ const float release{mRelease};
+ const float c_est{mGainEstimate};
+ const float a_adp{mAdaptCoeff};
+ auto lookAhead = mSideChain.cbegin() + mLookAhead;
+ auto crestFactor = mCrestFactor.cbegin();
+ float postGain{mPostGain};
+ float knee{mKnee};
float t_att{attack};
float t_rel{release - attack};
float a_att{std::exp(-1.0f / t_att)};
float a_rel{std::exp(-1.0f / t_rel)};
- float y_1{Comp->mLastRelease};
- float y_L{Comp->mLastAttack};
- float c_dev{Comp->mLastGainDev};
+ float y_1{mLastRelease};
+ float y_L{mLastAttack};
+ float c_dev{mLastGainDev};
ASSUME(SamplesToDo > 0);
- for(float &sideChain : al::span<float>{Comp->mSideChain, SamplesToDo})
+ auto process = [&](const float input) -> float
{
if(autoKnee)
- knee = maxf(0.0f, 2.5f * (c_dev + c_est));
+ knee = std::max(0.0f, 2.5f * (c_dev + c_est));
const float knee_h{0.5f * knee};
/* This is the gain computer. It applies a static compression curve
* to the control signal.
*/
- const float x_over{std::addressof(sideChain)[lookAhead] - threshold};
+ const float x_over{*(lookAhead++) - threshold};
const float y_G{
(x_over <= -knee_h) ? 0.0f :
- (std::fabs(x_over) < knee_h) ? (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee) :
+ (std::fabs(x_over) < knee_h) ? (x_over+knee_h) * (x_over+knee_h) / (2.0f * knee) :
x_over};
const float y2_crest{*(crestFactor++)};
@@ -243,7 +245,7 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
* above to compensate for the chained operating mode.
*/
const float x_L{-slope * y_G};
- y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel));
+ y_1 = std::max(x_L, lerpf(x_L, y_1, a_rel));
y_L = lerpf(y_1, y_L, a_att);
/* Knee width and make-up gain automation make use of a smoothed
@@ -262,17 +264,19 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
* same output level.
*/
if(autoDeclip)
- c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est);
+ c_dev = std::max(c_dev, input - y_L - threshold - c_est);
postGain = -(c_dev + c_est);
}
- sideChain = std::exp(postGain - y_L);
- }
+ return std::exp(postGain - y_L);
+ };
+ auto sideChain = al::span{mSideChain}.first(SamplesToDo);
+ std::transform(sideChain.begin(), sideChain.end(), sideChain.begin(), process);
- Comp->mLastRelease = y_1;
- Comp->mLastAttack = y_L;
- Comp->mLastGainDev = c_dev;
+ mLastRelease = y_1;
+ mLastAttack = y_L;
+ mLastGainDev = c_dev;
}
/* Combined with the hold time, a look-ahead delay can improve handling of
@@ -280,10 +284,10 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo)
* reaching the offending impulse. This is best used when operating as a
* limiter.
*/
-void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutBuffer)
+void Compressor::signalDelay(const uint SamplesToDo, FloatBufferLine *OutBuffer)
{
- const size_t numChans{Comp->mNumChans};
- const uint lookAhead{Comp->mLookAhead};
+ const size_t numChans{mNumChans};
+ const uint lookAhead{mLookAhead};
ASSUME(SamplesToDo > 0);
ASSUME(numChans > 0);
@@ -292,7 +296,7 @@ void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutB
for(size_t c{0};c < numChans;c++)
{
float *inout{al::assume_aligned<16>(OutBuffer[c].data())};
- float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())};
+ float *delaybuf{al::assume_aligned<16>(mDelay[c].data())};
auto inout_end = inout + SamplesToDo;
if(SamplesToDo >= lookAhead) LIKELY
@@ -308,8 +312,6 @@ void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutB
}
}
-} // namespace
-
std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const float SampleRate,
const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain,
@@ -317,24 +319,12 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb,
const float AttackTime, const float ReleaseTime)
{
- const auto lookAhead = static_cast<uint>(
- clampf(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1));
- const auto hold = static_cast<uint>(
- clampf(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1));
+ const auto lookAhead = static_cast<uint>(clampf(std::round(LookAheadTime*SampleRate), 0.0f,
+ BufferLineSize-1));
+ const auto hold = static_cast<uint>(clampf(std::round(HoldTime*SampleRate), 0.0f,
+ BufferLineSize-1));
- size_t size{sizeof(Compressor)};
- if(lookAhead > 0)
- {
- size += sizeof(*Compressor::mDelay) * NumChans;
- /* The sliding hold implementation doesn't handle a length of 1. A 1-
- * sample hold is useless anyway, it would only ever give back what was
- * just given to it.
- */
- if(hold > 1)
- size += sizeof(*Compressor::mHold);
- }
-
- auto Comp = CompressorPtr{al::construct_at(static_cast<Compressor*>(al_calloc(16, size)))};
+ auto Comp = CompressorPtr{new Compressor{}};
Comp->mNumChans = NumChans;
Comp->mAuto.Knee = AutoKnee;
Comp->mAuto.Attack = AutoAttack;
@@ -343,12 +333,12 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
Comp->mAuto.Declip = AutoPostGain && AutoDeclip;
Comp->mLookAhead = lookAhead;
Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f);
- Comp->mPostGain = PostGainDb * std::log(10.0f) / 20.0f;
- Comp->mThreshold = ThresholdDb * std::log(10.0f) / 20.0f;
- Comp->mSlope = 1.0f / maxf(1.0f, Ratio) - 1.0f;
- Comp->mKnee = maxf(0.0f, KneeDb * std::log(10.0f) / 20.0f);
- Comp->mAttack = maxf(1.0f, AttackTime * SampleRate);
- Comp->mRelease = maxf(1.0f, ReleaseTime * SampleRate);
+ Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb;
+ Comp->mThreshold = std::log(10.0f)/20.0f * ThresholdDb;
+ Comp->mSlope = 1.0f / std::max(1.0f, Ratio) - 1.0f;
+ Comp->mKnee = std::max(0.0f, std::log(10.0f)/20.0f * KneeDb);
+ Comp->mAttack = std::max(1.0f, AttackTime * SampleRate);
+ Comp->mRelease = std::max(1.0f, ReleaseTime * SampleRate);
/* Knee width automation actually treats the compressor as a limiter. By
* varying the knee width, it can effectively be seen as applying
@@ -359,17 +349,18 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
if(lookAhead > 0)
{
+ /* The sliding hold implementation doesn't handle a length of 1. A 1-
+ * sample hold is useless anyway, it would only ever give back what was
+ * just given to it.
+ */
if(hold > 1)
{
- Comp->mHold = al::construct_at(reinterpret_cast<SlidingHold*>(Comp.get() + 1));
+ Comp->mHold = std::make_unique<SlidingHold>();
Comp->mHold->mValues[0] = -std::numeric_limits<float>::infinity();
Comp->mHold->mExpiries[0] = hold;
Comp->mHold->mLength = hold;
- Comp->mDelay = reinterpret_cast<FloatBufferLine*>(Comp->mHold + 1);
}
- else
- Comp->mDelay = reinterpret_cast<FloatBufferLine*>(Comp.get() + 1);
- std::uninitialized_fill_n(Comp->mDelay, NumChans, FloatBufferLine{});
+ Comp->mDelay.resize(NumChans, FloatBufferLine{});
}
Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms
@@ -379,15 +370,7 @@ std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const floa
return Comp;
}
-Compressor::~Compressor()
-{
- if(mHold)
- std::destroy_at(mHold);
- mHold = nullptr;
- if(mDelay)
- std::destroy_n(mDelay, mNumChans);
- mDelay = nullptr;
-}
+Compressor::~Compressor() = default;
void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
@@ -404,36 +387,36 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
{
float *buffer{al::assume_aligned<16>(input.data())};
std::transform(buffer, buffer+SamplesToDo, buffer,
- [preGain](float s) { return s * preGain; });
+ [preGain](const float s) noexcept { return s * preGain; });
};
std::for_each(OutBuffer, OutBuffer+numChans, apply_gain);
}
- LinkChannels(this, SamplesToDo, OutBuffer);
+ linkChannels(SamplesToDo, OutBuffer);
if(mAuto.Attack || mAuto.Release)
- CrestDetector(this, SamplesToDo);
+ crestDetector(SamplesToDo);
if(mHold)
- PeakHoldDetector(this, SamplesToDo);
+ peakHoldDetector(SamplesToDo);
else
- PeakDetector(this, SamplesToDo);
+ peakDetector(SamplesToDo);
- GainCompressor(this, SamplesToDo);
+ gainCompressor(SamplesToDo);
- if(mDelay)
- SignalDelay(this, SamplesToDo, OutBuffer);
+ if(!mDelay.empty())
+ signalDelay(SamplesToDo, OutBuffer);
- const float (&sideChain)[BufferLineSize*2] = mSideChain;
- auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void
+ const auto sideChain = al::span{mSideChain};
+ auto apply_comp = [SamplesToDo,sideChain](FloatBufferLine &input) noexcept -> void
{
float *buffer{al::assume_aligned<16>(input.data())};
- const float *gains{al::assume_aligned<16>(&sideChain[0])};
+ const float *gains{al::assume_aligned<16>(sideChain.data())};
std::transform(gains, gains+SamplesToDo, buffer, buffer,
- [](float g, float s) { return g * s; });
+ [](const float g, const float s) noexcept { return g * s; });
};
std::for_each(OutBuffer, OutBuffer+numChans, apply_comp);
- auto side_begin = std::begin(mSideChain) + SamplesToDo;
- std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain));
+ auto side_begin = mSideChain.begin() + SamplesToDo;
+ std::copy(side_begin, side_begin+mLookAhead, mSideChain.begin());
}