mastering.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. #include "config.h"
  2. #include "mastering.h"
  3. #include <algorithm>
  4. #include <cmath>
  5. #include <cstddef>
  6. #include <functional>
  7. #include <iterator>
  8. #include <limits>
  9. #include <new>
  10. #include "alnumeric.h"
  11. #include "alspan.h"
  12. #include "opthelpers.h"
  13. /* These structures assume BufferLineSize is a power of 2. */
  14. static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2");
  15. struct SlidingHold {
  16. alignas(16) FloatBufferLine mValues;
  17. std::array<uint,BufferLineSize> mExpiries;
  18. uint mLowerIndex;
  19. uint mUpperIndex;
  20. uint mLength;
  21. };
  22. namespace {
  23. template<std::size_t A, typename T, std::size_t N>
  24. constexpr auto assume_aligned_span(const al::span<T,N> s) noexcept -> al::span<T,N>
  25. { return al::span<T,N>{al::assume_aligned<A>(s.data()), s.size()}; }
  26. /* This sliding hold follows the input level with an instant attack and a
  27. * fixed duration hold before an instant release to the next highest level.
  28. * It is a sliding window maximum (descending maxima) implementation based on
  29. * Richard Harter's ascending minima algorithm available at:
  30. *
  31. * http://www.richardhartersworld.com/cri/2001/slidingmin.html
  32. */
  33. float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in)
  34. {
  35. static constexpr uint mask{BufferLineSize - 1};
  36. const uint length{Hold->mLength};
  37. const al::span values{Hold->mValues};
  38. const al::span expiries{Hold->mExpiries};
  39. uint lowerIndex{Hold->mLowerIndex};
  40. uint upperIndex{Hold->mUpperIndex};
  41. if(i >= expiries[upperIndex])
  42. upperIndex = (upperIndex + 1) & mask;
  43. if(in >= values[upperIndex])
  44. {
  45. values[upperIndex] = in;
  46. expiries[upperIndex] = i + length;
  47. lowerIndex = upperIndex;
  48. }
  49. else
  50. {
  51. auto findLowerIndex = [&lowerIndex,in,values]() noexcept -> bool
  52. {
  53. do {
  54. if(!(in >= values[lowerIndex]))
  55. return true;
  56. } while(lowerIndex--);
  57. return false;
  58. };
  59. while(!findLowerIndex())
  60. lowerIndex = mask;
  61. lowerIndex = (lowerIndex + 1) & mask;
  62. values[lowerIndex] = in;
  63. expiries[lowerIndex] = i + length;
  64. }
  65. Hold->mLowerIndex = lowerIndex;
  66. Hold->mUpperIndex = upperIndex;
  67. return values[upperIndex];
  68. }
  69. void ShiftSlidingHold(SlidingHold *Hold, const uint n)
  70. {
  71. auto exp_upper = Hold->mExpiries.begin() + Hold->mUpperIndex;
  72. if(Hold->mLowerIndex < Hold->mUpperIndex)
  73. {
  74. std::transform(exp_upper, Hold->mExpiries.end(), exp_upper,
  75. [n](const uint e) noexcept { return e - n; });
  76. exp_upper = Hold->mExpiries.begin();
  77. }
  78. const auto exp_lower = Hold->mExpiries.begin() + Hold->mLowerIndex;
  79. std::transform(exp_upper, exp_lower+1, exp_upper,
  80. [n](const uint e) noexcept { return e - n; });
  81. }
  82. } // namespace
  83. /* Multichannel compression is linked via the absolute maximum of all
  84. * channels.
  85. */
  86. void Compressor::linkChannels(const uint SamplesToDo,
  87. const al::span<const FloatBufferLine> OutBuffer)
  88. {
  89. ASSUME(SamplesToDo > 0);
  90. ASSUME(SamplesToDo <= BufferLineSize);
  91. const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
  92. std::fill_n(sideChain.begin(), sideChain.size(), 0.0f);
  93. auto fill_max = [sideChain](const FloatBufferLine &input) -> void
  94. {
  95. const auto buffer = assume_aligned_span<16>(al::span{input});
  96. auto max_abs = [](const float s0, const float s1) noexcept -> float
  97. { return std::max(s0, std::fabs(s1)); };
  98. std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(),
  99. max_abs);
  100. };
  101. std::for_each(OutBuffer.begin(), OutBuffer.end(), fill_max);
  102. }
  103. /* This calculates the squared crest factor of the control signal for the
  104. * basic automation of the attack/release times. As suggested by the paper,
  105. * it uses an instantaneous squared peak detector and a squared RMS detector
  106. * both with 200ms release times.
  107. */
  108. void Compressor::crestDetector(const uint SamplesToDo)
  109. {
  110. const float a_crest{mCrestCoeff};
  111. float y2_peak{mLastPeakSq};
  112. float y2_rms{mLastRmsSq};
  113. ASSUME(SamplesToDo > 0);
  114. ASSUME(SamplesToDo <= BufferLineSize);
  115. auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float
  116. {
  117. const float x2{std::clamp(x_abs*x_abs, 0.000001f, 1000000.0f)};
  118. y2_peak = std::max(x2, lerpf(x2, y2_peak, a_crest));
  119. y2_rms = lerpf(x2, y2_rms, a_crest);
  120. return y2_peak / y2_rms;
  121. };
  122. const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
  123. std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest);
  124. mLastPeakSq = y2_peak;
  125. mLastRmsSq = y2_rms;
  126. }
  127. /* The side-chain starts with a simple peak detector (based on the absolute
  128. * value of the incoming signal) and performs most of its operations in the
  129. * log domain.
  130. */
  131. void Compressor::peakDetector(const uint SamplesToDo)
  132. {
  133. ASSUME(SamplesToDo > 0);
  134. ASSUME(SamplesToDo <= BufferLineSize);
  135. /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */
  136. const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
  137. std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(),
  138. [](float s) { return std::log(std::max(0.000001f, s)); });
  139. }
  140. /* An optional hold can be used to extend the peak detector so it can more
  141. * solidly detect fast transients. This is best used when operating as a
  142. * limiter.
  143. */
  144. void Compressor::peakHoldDetector(const uint SamplesToDo)
  145. {
  146. ASSUME(SamplesToDo > 0);
  147. ASSUME(SamplesToDo <= BufferLineSize);
  148. SlidingHold *hold{mHold.get()};
  149. uint i{0};
  150. auto detect_peak = [&i,hold](const float x_abs) -> float
  151. {
  152. const float x_G{std::log(std::max(0.000001f, x_abs))};
  153. return UpdateSlidingHold(hold, i++, x_G);
  154. };
  155. auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo);
  156. std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), detect_peak);
  157. ShiftSlidingHold(hold, SamplesToDo);
  158. }
  159. /* This is the heart of the feed-forward compressor. It operates in the log
  160. * domain (to better match human hearing) and can apply some basic automation
  161. * to knee width, attack/release times, make-up/post gain, and clipping
  162. * reduction.
  163. */
  164. void Compressor::gainCompressor(const uint SamplesToDo)
  165. {
  166. const bool autoKnee{mAuto.Knee};
  167. const bool autoAttack{mAuto.Attack};
  168. const bool autoRelease{mAuto.Release};
  169. const bool autoPostGain{mAuto.PostGain};
  170. const bool autoDeclip{mAuto.Declip};
  171. const float threshold{mThreshold};
  172. const float slope{mSlope};
  173. const float attack{mAttack};
  174. const float release{mRelease};
  175. const float c_est{mGainEstimate};
  176. const float a_adp{mAdaptCoeff};
  177. auto lookAhead = mSideChain.cbegin() + mLookAhead;
  178. auto crestFactor = mCrestFactor.cbegin();
  179. float postGain{mPostGain};
  180. float knee{mKnee};
  181. float t_att{attack};
  182. float t_rel{release - attack};
  183. float a_att{std::exp(-1.0f / t_att)};
  184. float a_rel{std::exp(-1.0f / t_rel)};
  185. float y_1{mLastRelease};
  186. float y_L{mLastAttack};
  187. float c_dev{mLastGainDev};
  188. ASSUME(SamplesToDo > 0);
  189. auto process = [&](const float input) -> float
  190. {
  191. if(autoKnee)
  192. knee = std::max(0.0f, 2.5f * (c_dev + c_est));
  193. const float knee_h{0.5f * knee};
  194. /* This is the gain computer. It applies a static compression curve
  195. * to the control signal.
  196. */
  197. const float x_over{*(lookAhead++) - threshold};
  198. const float y_G{
  199. (x_over <= -knee_h) ? 0.0f :
  200. (std::fabs(x_over) < knee_h) ? (x_over+knee_h) * (x_over+knee_h) / (2.0f * knee) :
  201. x_over};
  202. const float y2_crest{*(crestFactor++)};
  203. if(autoAttack)
  204. {
  205. t_att = 2.0f*attack/y2_crest;
  206. a_att = std::exp(-1.0f / t_att);
  207. }
  208. if(autoRelease)
  209. {
  210. t_rel = 2.0f*release/y2_crest - t_att;
  211. a_rel = std::exp(-1.0f / t_rel);
  212. }
  213. /* Gain smoothing (ballistics) is done via a smooth decoupled peak
  214. * detector. The attack time is subtracted from the release time
  215. * above to compensate for the chained operating mode.
  216. */
  217. const float x_L{-slope * y_G};
  218. y_1 = std::max(x_L, lerpf(x_L, y_1, a_rel));
  219. y_L = lerpf(y_1, y_L, a_att);
  220. /* Knee width and make-up gain automation make use of a smoothed
  221. * measurement of deviation between the control signal and estimate.
  222. * The estimate is also used to bias the measurement to hot-start its
  223. * average.
  224. */
  225. c_dev = lerpf(-(y_L+c_est), c_dev, a_adp);
  226. if(autoPostGain)
  227. {
  228. /* Clipping reduction is only viable when make-up gain is being
  229. * automated. It modifies the deviation to further attenuate the
  230. * control signal when clipping is detected. The adaptation time
  231. * is sufficiently long enough to suppress further clipping at the
  232. * same output level.
  233. */
  234. if(autoDeclip)
  235. c_dev = std::max(c_dev, input - y_L - threshold - c_est);
  236. postGain = -(c_dev + c_est);
  237. }
  238. return std::exp(postGain - y_L);
  239. };
  240. auto sideChain = al::span{mSideChain}.first(SamplesToDo);
  241. std::transform(sideChain.begin(), sideChain.end(), sideChain.begin(), process);
  242. mLastRelease = y_1;
  243. mLastAttack = y_L;
  244. mLastGainDev = c_dev;
  245. }
  246. /* Combined with the hold time, a look-ahead delay can improve handling of
  247. * fast transients by allowing the envelope time to converge prior to
  248. * reaching the offending impulse. This is best used when operating as a
  249. * limiter.
  250. */
  251. void Compressor::signalDelay(const uint SamplesToDo, const al::span<FloatBufferLine> OutBuffer)
  252. {
  253. const auto lookAhead = mLookAhead;
  254. ASSUME(SamplesToDo > 0);
  255. ASSUME(SamplesToDo <= BufferLineSize);
  256. ASSUME(lookAhead > 0);
  257. ASSUME(lookAhead < BufferLineSize);
  258. auto delays = mDelay.begin();
  259. for(auto &buffer : OutBuffer)
  260. {
  261. const auto inout = al::span{buffer}.first(SamplesToDo);
  262. const auto delaybuf = al::span{*(delays++)}.first(lookAhead);
  263. if(SamplesToDo >= delaybuf.size()) LIKELY
  264. {
  265. const auto inout_start = inout.end() - ptrdiff_t(delaybuf.size());
  266. const auto delay_end = std::rotate(inout.begin(), inout_start, inout.end());
  267. std::swap_ranges(inout.begin(), delay_end, delaybuf.begin());
  268. }
  269. else
  270. {
  271. auto delay_start = std::swap_ranges(inout.begin(), inout.end(), delaybuf.begin());
  272. std::rotate(delaybuf.begin(), delay_start, delaybuf.end());
  273. }
  274. }
  275. }
  276. std::unique_ptr<Compressor> Compressor::Create(const size_t NumChans, const float SampleRate,
  277. const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain,
  278. const bool AutoDeclip, const float LookAheadTime, const float HoldTime, const float PreGainDb,
  279. const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb,
  280. const float AttackTime, const float ReleaseTime)
  281. {
  282. const auto lookAhead = static_cast<uint>(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f,
  283. BufferLineSize-1.0f));
  284. const auto hold = static_cast<uint>(std::clamp(std::round(HoldTime*SampleRate), 0.0f,
  285. BufferLineSize-1.0f));
  286. auto Comp = CompressorPtr{new Compressor{}};
  287. Comp->mNumChans = NumChans;
  288. Comp->mAuto.Knee = AutoKnee;
  289. Comp->mAuto.Attack = AutoAttack;
  290. Comp->mAuto.Release = AutoRelease;
  291. Comp->mAuto.PostGain = AutoPostGain;
  292. Comp->mAuto.Declip = AutoPostGain && AutoDeclip;
  293. Comp->mLookAhead = lookAhead;
  294. Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f);
  295. Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb;
  296. Comp->mThreshold = std::log(10.0f)/20.0f * ThresholdDb;
  297. Comp->mSlope = 1.0f / std::max(1.0f, Ratio) - 1.0f;
  298. Comp->mKnee = std::max(0.0f, std::log(10.0f)/20.0f * KneeDb);
  299. Comp->mAttack = std::max(1.0f, AttackTime * SampleRate);
  300. Comp->mRelease = std::max(1.0f, ReleaseTime * SampleRate);
  301. /* Knee width automation actually treats the compressor as a limiter. By
  302. * varying the knee width, it can effectively be seen as applying
  303. * compression over a wide range of ratios.
  304. */
  305. if(AutoKnee)
  306. Comp->mSlope = -1.0f;
  307. if(lookAhead > 0)
  308. {
  309. /* The sliding hold implementation doesn't handle a length of 1. A 1-
  310. * sample hold is useless anyway, it would only ever give back what was
  311. * just given to it.
  312. */
  313. if(hold > 1)
  314. {
  315. Comp->mHold = std::make_unique<SlidingHold>();
  316. Comp->mHold->mValues[0] = -std::numeric_limits<float>::infinity();
  317. Comp->mHold->mExpiries[0] = hold;
  318. Comp->mHold->mLength = hold;
  319. }
  320. Comp->mDelay.resize(NumChans, FloatBufferLine{});
  321. }
  322. Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms
  323. Comp->mGainEstimate = Comp->mThreshold * -0.5f * Comp->mSlope;
  324. Comp->mAdaptCoeff = std::exp(-1.0f / (2.0f * SampleRate)); // 2s
  325. return Comp;
  326. }
  327. Compressor::~Compressor() = default;
  328. void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer)
  329. {
  330. const size_t numChans{mNumChans};
  331. ASSUME(SamplesToDo > 0);
  332. ASSUME(SamplesToDo <= BufferLineSize);
  333. ASSUME(numChans > 0);
  334. const auto output = al::span{OutBuffer, numChans};
  335. const float preGain{mPreGain};
  336. if(preGain != 1.0f)
  337. {
  338. auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void
  339. {
  340. const auto buffer = assume_aligned_span<16>(al::span{input}.first(SamplesToDo));
  341. std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(),
  342. [preGain](const float s) noexcept { return s * preGain; });
  343. };
  344. std::for_each(output.begin(), output.end(), apply_gain);
  345. }
  346. linkChannels(SamplesToDo, output);
  347. if(mAuto.Attack || mAuto.Release)
  348. crestDetector(SamplesToDo);
  349. if(mHold)
  350. peakHoldDetector(SamplesToDo);
  351. else
  352. peakDetector(SamplesToDo);
  353. gainCompressor(SamplesToDo);
  354. if(!mDelay.empty())
  355. signalDelay(SamplesToDo, output);
  356. const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo));
  357. auto apply_comp = [gains](const FloatBufferSpan input) noexcept -> void
  358. {
  359. const auto buffer = assume_aligned_span<16>(input);
  360. std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(),
  361. std::multiplies{});
  362. };
  363. std::for_each(output.begin(), output.end(), apply_comp);
  364. const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead);
  365. std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin());
  366. }