You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1767 lines
53 KiB

  1. /**********************************************************************
  2. Audacity: A Digital Audio Editor
  3. Compressor2.cpp
  4. Max Maisel
  5. *******************************************************************//**
  6. \class EffectCompressor2
  7. \brief An Effect which reduces the dynamic level.
  8. *//*******************************************************************/
  9. #include "../Audacity.h" // for rint from configwin.h
  10. #include "Compressor2.h"
  11. #include <math.h>
  12. #include <numeric>
  13. #include <wx/intl.h>
  14. #include <wx/valgen.h>
  15. #include "../AColor.h"
  16. #include "../Internat.h"
  17. #include "../Prefs.h"
  18. #include "../ProjectFileManager.h"
  19. #include "../Shuttle.h"
  20. #include "../ShuttleGui.h"
  21. #include "../WaveTrack.h"
  22. #include "../widgets/valnum.h"
  23. #include "../widgets/Plot.h"
  24. #include "../widgets/ProgressDialog.h"
  25. #include "../widgets/Ruler.h"
  26. #include "../widgets/SliderTextCtrl.h"
  27. #include "LoadEffects.h"
  28. //#define DEBUG_COMPRESSOR2_DUMP_BUFFERS
  29. //#define DEBUG_COMPRESSOR2_ENV
  30. //#define DEBUG_COMPRESSOR2_TRACE
  31. //#define DEBUG_COMPRESSOR2_TRACE2
  32. #if defined(DEBUG_COMPRESSOR2_DUMP_BUFFERS) or defined(DEBUG_COMPRESSOR2_TRACE2)
  33. #include <fstream>
  34. int buf_num;
  35. std::fstream debugfile;
  36. #endif
  37. enum kAlgorithms
  38. {
  39. kExpFit,
  40. kEnvPT1,
  41. nAlgos
  42. };
  43. static const ComponentInterfaceSymbol kAlgorithmStrings[nAlgos] =
  44. {
  45. { XO("Exponential-Fit") },
  46. { XO("Analog Model") }
  47. };
  48. enum kCompressBy
  49. {
  50. kAmplitude,
  51. kRMS,
  52. nBy
  53. };
  54. static const ComponentInterfaceSymbol kCompressByStrings[nBy] =
  55. {
  56. { XO("peak amplitude") },
  57. { XO("RMS") }
  58. };
  59. // Define keys, defaults, minimums, and maximums for the effect parameters
  60. //
  61. // Name Type Key Def Min Max Scale
  62. Param( Algorithm, int, wxT("Algorithm"), kEnvPT1, 0, nAlgos-1, 1 );
  63. Param( CompressBy, int, wxT("CompressBy"), kAmplitude, 0, nBy-1, 1 );
  64. Param( StereoInd, bool, wxT("StereoIndependent"), false, false, true, 1 );
  65. Param( Threshold, double, wxT("Threshold"), -12.0, -60.0, -1.0, 1.0 );
  66. Param( Ratio, double, wxT("Ratio"), 2.0, 1.1, 100.0, 20.0 );
  67. Param( KneeWidth, double, wxT("KneeWidth"), 10.0, 0.0, 20.0, 10.0 );
  68. Param( AttackTime, double, wxT("AttackTime"), 0.2, 0.0001, 30.0, 2000.0 );
  69. Param( ReleaseTime, double, wxT("ReleaseTime"), 1.0, 0.0001, 30.0, 2000.0 );
  70. Param( LookaheadTime, double, wxT("LookaheadTime"), 0.0, 0.0, 10.0, 200.0 );
  71. Param( LookbehindTime, double, wxT("LookbehindTime"), 0.1, 0.0, 10.0, 200.0 );
  72. Param( OutputGain, double, wxT("OutputGain"), 0.0, 0.0, 50.0, 10.0 );
  73. struct FactoryPreset
  74. {
  75. const TranslatableString name;
  76. int algorithm;
  77. int compressBy;
  78. bool stereoInd;
  79. double thresholdDB;
  80. double ratio;
  81. double kneeWidthDB;
  82. double attackTime;
  83. double releaseTime;
  84. double lookaheadTime;
  85. double lookbehindTime;
  86. double outputGainDB;
  87. };
  88. static const FactoryPreset FactoryPresets[] =
  89. {
  90. { XO("Dynamic Reduction"), kEnvPT1, kAmplitude, false, -40, 2.5, 6, 0.3, 0.3, 0.5, 0.5, 23 },
  91. { XO("Peak Reduction"), kEnvPT1, kAmplitude, false, -10, 10, 0, 0.001, 0.05, 0, 0, 0 },
  92. { XO("Analog Limiter"), kEnvPT1, kAmplitude, false, -6, 100, 6, 0.0001, 0.0001, 0, 0, 0 }
  93. };
  94. inline int ScaleToPrecision(double scale)
  95. {
  96. return ceil(log10(scale));
  97. }
  98. inline bool IsInRange(double val, double min, double max)
  99. {
  100. return val >= min && val <= max;
  101. }
  102. BEGIN_EVENT_TABLE(EffectCompressor2, wxEvtHandler)
  103. EVT_CHECKBOX(wxID_ANY, EffectCompressor2::OnUpdateUI)
  104. EVT_CHOICE(wxID_ANY, EffectCompressor2::OnUpdateUI)
  105. EVT_SLIDERTEXT(wxID_ANY, EffectCompressor2::OnUpdateUI)
  106. END_EVENT_TABLE()
  107. const ComponentInterfaceSymbol EffectCompressor2::Symbol
  108. { XO("Dynamic Compressor") };
  109. namespace{ BuiltinEffectsModule::Registration< EffectCompressor2 > reg; }
  110. SlidingRmsPreprocessor::SlidingRmsPreprocessor(size_t windowSize, float gain)
  111. : mSum(0),
  112. mGain(gain),
  113. mWindow(windowSize, 0),
  114. mPos(0),
  115. mInsertCount(0)
  116. {
  117. }
  118. float SlidingRmsPreprocessor::ProcessSample(float value)
  119. {
  120. return DoProcessSample(value * value);
  121. }
  122. float SlidingRmsPreprocessor::ProcessSample(float valueL, float valueR)
  123. {
  124. return DoProcessSample((valueL * valueL + valueR * valueR) / 2.0);
  125. }
  126. void SlidingRmsPreprocessor::Reset(float level)
  127. {
  128. mSum = (level / mGain) * (level / mGain) * float(mWindow.size());
  129. mPos = 0;
  130. mInsertCount = 0;
  131. std::fill(mWindow.begin(), mWindow.end(), 0);
  132. }
  133. void SlidingRmsPreprocessor::SetWindowSize(size_t windowSize)
  134. {
  135. mWindow.resize(windowSize);
  136. Reset();
  137. }
  138. float SlidingRmsPreprocessor::DoProcessSample(float value)
  139. {
  140. if(mInsertCount > REFRESH_WINDOW_EVERY)
  141. {
  142. // Update RMS sum directly from the circle buffer every
  143. // REFRESH_WINDOW_EVERY samples to avoid accumulation of rounding errors.
  144. mWindow[mPos] = value;
  145. Refresh();
  146. }
  147. else
  148. {
  149. // Calculate current level from root-mean-squared of
  150. // circular buffer ("RMS").
  151. mSum -= mWindow[mPos];
  152. mWindow[mPos] = value;
  153. mSum += mWindow[mPos];
  154. ++mInsertCount;
  155. }
  156. // Also refresh if there are severe rounding errors that
  157. // caused mRMSSum to be negative.
  158. if(mSum < 0)
  159. Refresh();
  160. mPos = (mPos + 1) % mWindow.size();
  161. // Multiply by gain (usually two) to approximately correct peak level
  162. // of standard audio (avoid clipping).
  163. return mGain * sqrt(mSum/float(mWindow.size()));
  164. }
  165. void SlidingRmsPreprocessor::Refresh()
  166. {
  167. // Recompute the RMS sum periodically to prevent accumulation
  168. // of rounding errors during long waveforms.
  169. mSum = 0;
  170. for(const auto& sample : mWindow)
  171. mSum += sample;
  172. mInsertCount = 0;
  173. }
  174. SlidingMaxPreprocessor::SlidingMaxPreprocessor(size_t windowSize)
  175. : mWindow(windowSize, 0),
  176. mMaxes(windowSize, 0),
  177. mPos(0)
  178. {
  179. }
  180. float SlidingMaxPreprocessor::ProcessSample(float value)
  181. {
  182. return DoProcessSample(fabs(value));
  183. }
  184. float SlidingMaxPreprocessor::ProcessSample(float valueL, float valueR)
  185. {
  186. return DoProcessSample((fabs(valueL) + fabs(valueR)) / 2.0);
  187. }
  188. void SlidingMaxPreprocessor::Reset(float value)
  189. {
  190. mPos = 0;
  191. std::fill(mWindow.begin(), mWindow.end(), value);
  192. std::fill(mMaxes.begin(), mMaxes.end(), value);
  193. }
  194. void SlidingMaxPreprocessor::SetWindowSize(size_t windowSize)
  195. {
  196. mWindow.resize(windowSize);
  197. mMaxes.resize(windowSize);
  198. Reset();
  199. }
  200. float SlidingMaxPreprocessor::DoProcessSample(float value)
  201. {
  202. size_t oldHead = (mPos-1) % mWindow.size();
  203. size_t currentHead = mPos;
  204. size_t nextHead = (mPos+1) % mWindow.size();
  205. mWindow[mPos] = value;
  206. mMaxes[mPos] = std::max(value, mMaxes[oldHead]);
  207. if(mPos % ((mWindow.size()+1)/2) == 0)
  208. {
  209. mMaxes[mPos] = mWindow[mPos];
  210. for(size_t i = 1; i < mWindow.size(); ++i)
  211. {
  212. size_t pos1 = (mPos-i+mWindow.size()) % mWindow.size();
  213. size_t pos2 = (mPos-i+mWindow.size()+1) % mWindow.size();
  214. mMaxes[pos1] = std::max(mWindow[pos1], mMaxes[pos2]);
  215. }
  216. }
  217. mPos = nextHead;
  218. return std::max(mMaxes[currentHead], mMaxes[nextHead]);
  219. }
  220. EnvelopeDetector::EnvelopeDetector(size_t buffer_size)
  221. : mPos(0),
  222. mInitialCondition(0),
  223. mInitialBlockSize(0),
  224. mLookaheadBuffer(buffer_size, 0),
  225. mProcessingBuffer(buffer_size, 0),
  226. mProcessedBuffer(buffer_size, 0)
  227. {
  228. }
  229. float EnvelopeDetector::AttackFactor()
  230. {
  231. return 0;
  232. }
  233. float EnvelopeDetector::DecayFactor()
  234. {
  235. return 0;
  236. }
  237. float EnvelopeDetector::ProcessSample(float value)
  238. {
  239. float retval = mProcessedBuffer[mPos];
  240. mLookaheadBuffer[mPos++] = value;
  241. if(mPos == mProcessingBuffer.size())
  242. {
  243. Follow();
  244. mPos = 0;
  245. mProcessedBuffer.swap(mProcessingBuffer);
  246. mLookaheadBuffer.swap(mProcessingBuffer);
  247. }
  248. return retval;
  249. }
  250. void EnvelopeDetector::CalcInitialCondition(float value)
  251. {
  252. }
  253. size_t EnvelopeDetector::GetBlockSize() const
  254. {
  255. wxASSERT(mProcessedBuffer.size() == mProcessingBuffer.size());
  256. wxASSERT(mProcessedBuffer.size() == mLookaheadBuffer.size());
  257. return mLookaheadBuffer.size();
  258. }
  259. const float* EnvelopeDetector::GetBuffer(int idx) const
  260. {
  261. if(idx == 0)
  262. return mProcessedBuffer.data();
  263. else if(idx == 1)
  264. return mProcessingBuffer.data();
  265. else if(idx == 2)
  266. return mLookaheadBuffer.data();
  267. else
  268. wxASSERT(false);
  269. return nullptr;
  270. }
  271. ExpFitEnvelopeDetector::ExpFitEnvelopeDetector(
  272. float rate, float attackTime, float releaseTime, size_t bufferSize)
  273. : EnvelopeDetector(bufferSize)
  274. {
  275. SetParams(rate, attackTime, releaseTime);
  276. }
  277. void ExpFitEnvelopeDetector::Reset(float value)
  278. {
  279. std::fill(mProcessedBuffer.begin(), mProcessedBuffer.end(), value);
  280. std::fill(mProcessingBuffer.begin(), mProcessingBuffer.end(), value);
  281. std::fill(mLookaheadBuffer.begin(), mLookaheadBuffer.end(), value);
  282. }
  283. void ExpFitEnvelopeDetector::SetParams(
  284. float sampleRate, float attackTime, float releaseTime)
  285. {
  286. attackTime = std::max(attackTime, 1.0f / sampleRate);
  287. releaseTime = std::max(releaseTime, 1.0f / sampleRate);
  288. mAttackFactor = exp(-1.0 / (sampleRate * attackTime));
  289. mReleaseFactor = exp(-1.0 / (sampleRate * releaseTime));
  290. }
  291. void ExpFitEnvelopeDetector::Follow()
  292. {
  293. /*
  294. "Follow"ing algorithm by Roger B. Dannenberg, taken from
  295. Nyquist. His description follows. -DMM
  296. Description: this is a sophisticated envelope follower.
  297. The input is an envelope, e.g. something produced with
  298. the AVG function. The purpose of this function is to
  299. generate a smooth envelope that is generally not less
  300. than the input signal. In other words, we want to "ride"
  301. the peaks of the signal with a smooth function. The
  302. algorithm is as follows: keep a current output value
  303. (called the "value"). The value is allowed to increase
  304. by at most rise_factor and decrease by at most fall_factor.
  305. Therefore, the next value should be between
  306. value * rise_factor and value * fall_factor. If the input
  307. is in this range, then the next value is simply the input.
  308. If the input is less than value * fall_factor, then the
  309. next value is just value * fall_factor, which will be greater
  310. than the input signal. If the input is greater than value *
  311. rise_factor, then we compute a rising envelope that meets
  312. the input value by working backwards in time, changing the
  313. previous values to input / rise_factor, input / rise_factor^2,
  314. input / rise_factor^3, etc. until this NEW envelope intersects
  315. the previously computed values. There is only a limited buffer
  316. in which we can work backwards, so if the NEW envelope does not
  317. intersect the old one, then make yet another pass, this time
  318. from the oldest buffered value forward, increasing on each
  319. sample by rise_factor to produce a maximal envelope. This will
  320. still be less than the input.
  321. The value has a lower limit of floor to make sure value has a
  322. reasonable positive value from which to begin an attack.
  323. */
  324. wxASSERT(mProcessedBuffer.size() == mProcessingBuffer.size());
  325. wxASSERT(mProcessedBuffer.size() == mLookaheadBuffer.size());
  326. // First apply a peak detect with the requested release rate.
  327. size_t buffer_size = mProcessingBuffer.size();
  328. double env = mProcessedBuffer[buffer_size-1];
  329. for(size_t i = 0; i < buffer_size; ++i)
  330. {
  331. env *= mReleaseFactor;
  332. if(mProcessingBuffer[i] > env)
  333. env = mProcessingBuffer[i];
  334. mProcessingBuffer[i] = env;
  335. }
  336. // Preprocess lookahead buffer as well.
  337. for(size_t i = 0; i < buffer_size; ++i)
  338. {
  339. env *= mReleaseFactor;
  340. if(mLookaheadBuffer[i] > env)
  341. env = mLookaheadBuffer[i];
  342. mLookaheadBuffer[i] = env;
  343. }
  344. // Next do the same process in reverse direction to get the
  345. // requested attack rate and preprocess lookahead buffer.
  346. for(ssize_t i = buffer_size - 1; i >= 0; --i)
  347. {
  348. env *= mAttackFactor;
  349. if(mLookaheadBuffer[i] < env)
  350. mLookaheadBuffer[i] = env;
  351. else
  352. env = mLookaheadBuffer[i];
  353. }
  354. for(ssize_t i = buffer_size - 1; i >= 0; --i)
  355. {
  356. if(mProcessingBuffer[i] < env * mAttackFactor)
  357. {
  358. env *= mAttackFactor;
  359. mProcessingBuffer[i] = env;
  360. }
  361. else if(mProcessingBuffer[i] > env)
  362. // Intersected the previous envelope buffer, so we are finished
  363. return;
  364. else
  365. ; // Do nothing if we are on a plateau from peak look-around
  366. }
  367. }
  368. Pt1EnvelopeDetector::Pt1EnvelopeDetector(
  369. float rate, float attackTime, float releaseTime, size_t bufferSize,
  370. bool correctGain)
  371. : EnvelopeDetector(bufferSize),
  372. mCorrectGain(correctGain)
  373. {
  374. SetParams(rate, attackTime, releaseTime);
  375. }
  376. float Pt1EnvelopeDetector::AttackFactor()
  377. {
  378. return mAttackFactor;
  379. }
  380. float Pt1EnvelopeDetector::DecayFactor()
  381. {
  382. return mReleaseFactor;
  383. }
  384. void Pt1EnvelopeDetector::Reset(float value)
  385. {
  386. value *= mGainCorrection;
  387. std::fill(mProcessedBuffer.begin(), mProcessedBuffer.end(), value);
  388. std::fill(mProcessingBuffer.begin(), mProcessingBuffer.end(), value);
  389. std::fill(mLookaheadBuffer.begin(), mLookaheadBuffer.end(), value);
  390. }
  391. void Pt1EnvelopeDetector::SetParams(
  392. float sampleRate, float attackTime, float releaseTime)
  393. {
  394. attackTime = std::max(attackTime, 1.0f / sampleRate);
  395. releaseTime = std::max(releaseTime, 1.0f / sampleRate);
  396. // Approximate peak amplitude correction factor.
  397. if(mCorrectGain)
  398. mGainCorrection = 1.0 + exp(attackTime / 30.0);
  399. else
  400. mGainCorrection = 1.0;
  401. mAttackFactor = 1.0 / (attackTime * sampleRate);
  402. mReleaseFactor = 1.0 / (releaseTime * sampleRate);
  403. mInitialBlockSize = std::min(size_t(sampleRate * sqrt(attackTime)), mLookaheadBuffer.size());
  404. }
  405. void Pt1EnvelopeDetector::CalcInitialCondition(float value)
  406. {
  407. mLookaheadBuffer[mPos++] = value;
  408. if(mPos == mInitialBlockSize)
  409. {
  410. float level = 0;
  411. for(size_t i = 0; i < mPos; ++i)
  412. {
  413. if(mLookaheadBuffer[i] >= level)
  414. if(i < mInitialBlockSize / 5)
  415. level += 5 * mAttackFactor * (mLookaheadBuffer[i] - level);
  416. else
  417. level += mAttackFactor * (mLookaheadBuffer[i] - level);
  418. else
  419. level += mReleaseFactor * (mLookaheadBuffer[i] - level);
  420. }
  421. mInitialCondition = level;
  422. mPos = 0;
  423. }
  424. }
  425. void Pt1EnvelopeDetector::Follow()
  426. {
  427. wxASSERT(mProcessedBuffer.size() == mProcessingBuffer.size());
  428. wxASSERT(mProcessedBuffer.size() == mLookaheadBuffer.size());
  429. // Simulate analog compressor with PT1 characteristic.
  430. size_t buffer_size = mProcessingBuffer.size();
  431. float level = mProcessedBuffer[buffer_size-1] / mGainCorrection;
  432. for(size_t i = 0; i < buffer_size; ++i)
  433. {
  434. if(mProcessingBuffer[i] >= level)
  435. level += mAttackFactor * (mProcessingBuffer[i] - level);
  436. else
  437. level += mReleaseFactor * (mProcessingBuffer[i] - level);
  438. mProcessingBuffer[i] = level * mGainCorrection;
  439. }
  440. }
  441. void PipelineBuffer::pad_to(size_t len, float value, bool stereo)
  442. {
  443. if(size < len)
  444. {
  445. size = len;
  446. std::fill(mBlockBuffer[0].get() + trackSize,
  447. mBlockBuffer[0].get() + size, value);
  448. if(stereo)
  449. std::fill(mBlockBuffer[1].get() + trackSize,
  450. mBlockBuffer[1].get() + size, value);
  451. }
  452. }
  453. void PipelineBuffer::swap(PipelineBuffer& other)
  454. {
  455. std::swap(trackPos, other.trackPos);
  456. std::swap(trackSize, other.trackSize);
  457. std::swap(size, other.size);
  458. std::swap(mBlockBuffer[0], other.mBlockBuffer[0]);
  459. std::swap(mBlockBuffer[1], other.mBlockBuffer[1]);
  460. }
  461. void PipelineBuffer::init(size_t capacity, bool stereo)
  462. {
  463. trackPos = 0;
  464. trackSize = 0;
  465. size = 0;
  466. mCapacity = capacity;
  467. mBlockBuffer[0].reinit(capacity);
  468. if(stereo)
  469. mBlockBuffer[1].reinit(capacity);
  470. fill(0, stereo);
  471. }
  472. void PipelineBuffer::fill(float value, bool stereo)
  473. {
  474. std::fill(mBlockBuffer[0].get(), mBlockBuffer[0].get() + mCapacity, value);
  475. if(stereo)
  476. std::fill(mBlockBuffer[1].get(), mBlockBuffer[1].get() + mCapacity, value);
  477. }
  478. void PipelineBuffer::free()
  479. {
  480. mBlockBuffer[0].reset();
  481. mBlockBuffer[1].reset();
  482. }
  483. EffectCompressor2::EffectCompressor2()
  484. : mIgnoreGuiEvents(false),
  485. mAlgorithmCtrl(0),
  486. mPreprocCtrl(0),
  487. mAttackTimeCtrl(0),
  488. mLookaheadTimeCtrl(0)
  489. {
  490. mAlgorithm = DEF_Algorithm;
  491. mCompressBy = DEF_CompressBy;
  492. mStereoInd = DEF_StereoInd;
  493. mThresholdDB = DEF_Threshold;
  494. mRatio = DEF_Ratio; // positive number > 1.0
  495. mKneeWidthDB = DEF_KneeWidth;
  496. mAttackTime = DEF_AttackTime; // seconds
  497. mReleaseTime = DEF_ReleaseTime; // seconds
  498. mLookaheadTime = DEF_LookaheadTime;
  499. mLookbehindTime = DEF_LookbehindTime;
  500. mOutputGainDB = DEF_OutputGain;
  501. SetLinearEffectFlag(false);
  502. }
  503. EffectCompressor2::~EffectCompressor2()
  504. {
  505. }
  506. // ComponentInterface implementation
  507. ComponentInterfaceSymbol EffectCompressor2::GetSymbol()
  508. {
  509. return Symbol;
  510. }
  511. TranslatableString EffectCompressor2::GetDescription()
  512. {
  513. return XO("Reduces the dynamic of one or more tracks");
  514. }
  515. ManualPageID EffectCompressor2::ManualPage()
  516. {
  517. return L"Dynamic_Compressor";
  518. }
  519. // EffectDefinitionInterface implementation
  520. EffectType EffectCompressor2::GetType()
  521. {
  522. return EffectTypeProcess;
  523. }
  524. bool EffectCompressor2::SupportsRealtime()
  525. {
  526. #if defined(EXPERIMENTAL_REALTIME_AUDACITY_EFFECTS)
  527. return false;
  528. #else
  529. return false;
  530. #endif
  531. }
  532. unsigned EffectCompressor2::GetAudioInCount()
  533. {
  534. return 2;
  535. }
  536. unsigned EffectCompressor2::GetAudioOutCount()
  537. {
  538. return 2;
  539. }
  540. bool EffectCompressor2::RealtimeInitialize()
  541. {
  542. SetBlockSize(512);
  543. AllocRealtimePipeline();
  544. mAlgorithmCtrl->Enable(false);
  545. mPreprocCtrl->Enable(false);
  546. mLookaheadTimeCtrl->Enable(false);
  547. if(mAlgorithm == kExpFit)
  548. mAttackTimeCtrl->Enable(false);
  549. return true;
  550. }
  551. bool EffectCompressor2::RealtimeAddProcessor(
  552. unsigned WXUNUSED(numChannels), float sampleRate)
  553. {
  554. mSampleRate = sampleRate;
  555. mProcStereo = true;
  556. mPreproc = InitPreprocessor(mSampleRate);
  557. mEnvelope = InitEnvelope(mSampleRate, mPipeline[0].size);
  558. mProgressVal = 0;
  559. #ifdef DEBUG_COMPRESSOR2_TRACE2
  560. debugfile.close();
  561. debugfile.open("/tmp/audio.out", std::ios::trunc | std::ios::out);
  562. #endif
  563. return true;
  564. }
  565. bool EffectCompressor2::RealtimeFinalize()
  566. {
  567. mPreproc.reset(nullptr);
  568. mEnvelope.reset(nullptr);
  569. FreePipeline();
  570. mAlgorithmCtrl->Enable(true);
  571. mPreprocCtrl->Enable(true);
  572. mLookaheadTimeCtrl->Enable(true);
  573. if(mAlgorithm == kExpFit)
  574. mAttackTimeCtrl->Enable(true);
  575. #ifdef DEBUG_COMPRESSOR2_TRACE2
  576. debugfile.close();
  577. #endif
  578. return true;
  579. }
  580. size_t EffectCompressor2::RealtimeProcess(
  581. int group, float **inbuf, float **outbuf, size_t numSamples)
  582. {
  583. std::lock_guard<std::mutex> guard(mRealtimeMutex);
  584. const size_t j = PIPELINE_DEPTH-1;
  585. for(size_t i = 0; i < numSamples; ++i)
  586. {
  587. if(mPipeline[j].trackSize == mPipeline[j].size)
  588. {
  589. ProcessPipeline();
  590. mPipeline[j].trackSize = 0;
  591. SwapPipeline();
  592. }
  593. outbuf[0][i] = mPipeline[j][0][mPipeline[j].trackSize];
  594. outbuf[1][i] = mPipeline[j][1][mPipeline[j].trackSize];
  595. mPipeline[j][0][mPipeline[j].trackSize] = inbuf[0][i];
  596. mPipeline[j][1][mPipeline[j].trackSize] = inbuf[1][i];
  597. ++mPipeline[j].trackSize;
  598. }
  599. return numSamples;
  600. }
  601. // EffectClientInterface implementation
  602. bool EffectCompressor2::DefineParams( ShuttleParams & S )
  603. {
  604. S.SHUTTLE_PARAM(mAlgorithm, Algorithm);
  605. S.SHUTTLE_PARAM(mCompressBy, CompressBy);
  606. S.SHUTTLE_PARAM(mStereoInd, StereoInd);
  607. S.SHUTTLE_PARAM(mThresholdDB, Threshold);
  608. S.SHUTTLE_PARAM(mRatio, Ratio);
  609. S.SHUTTLE_PARAM(mKneeWidthDB, KneeWidth);
  610. S.SHUTTLE_PARAM(mAttackTime, AttackTime);
  611. S.SHUTTLE_PARAM(mReleaseTime, ReleaseTime);
  612. S.SHUTTLE_PARAM(mLookaheadTime, LookaheadTime);
  613. S.SHUTTLE_PARAM(mLookbehindTime, LookbehindTime);
  614. S.SHUTTLE_PARAM(mOutputGainDB, OutputGain);
  615. return true;
  616. }
  617. bool EffectCompressor2::GetAutomationParameters(CommandParameters & parms)
  618. {
  619. parms.Write(KEY_Algorithm, mAlgorithm);
  620. parms.Write(KEY_CompressBy, mCompressBy);
  621. parms.Write(KEY_StereoInd, mStereoInd);
  622. parms.Write(KEY_Threshold, mThresholdDB);
  623. parms.Write(KEY_Ratio, mRatio);
  624. parms.Write(KEY_KneeWidth, mKneeWidthDB);
  625. parms.Write(KEY_AttackTime, mAttackTime);
  626. parms.Write(KEY_ReleaseTime, mReleaseTime);
  627. parms.Write(KEY_LookaheadTime, mLookaheadTime);
  628. parms.Write(KEY_LookbehindTime, mLookbehindTime);
  629. parms.Write(KEY_OutputGain, mOutputGainDB);
  630. return true;
  631. }
  632. bool EffectCompressor2::SetAutomationParameters(CommandParameters & parms)
  633. {
  634. ReadAndVerifyInt(Algorithm);
  635. ReadAndVerifyInt(CompressBy);
  636. ReadAndVerifyBool(StereoInd);
  637. ReadAndVerifyDouble(Threshold);
  638. ReadAndVerifyDouble(Ratio);
  639. ReadAndVerifyDouble(KneeWidth);
  640. ReadAndVerifyDouble(AttackTime);
  641. ReadAndVerifyDouble(ReleaseTime);
  642. ReadAndVerifyDouble(LookaheadTime);
  643. ReadAndVerifyDouble(LookbehindTime);
  644. ReadAndVerifyDouble(OutputGain);
  645. mAlgorithm = Algorithm;
  646. mCompressBy = CompressBy;
  647. mStereoInd = StereoInd;
  648. mThresholdDB = Threshold;
  649. mRatio = Ratio;
  650. mKneeWidthDB = KneeWidth;
  651. mAttackTime = AttackTime;
  652. mReleaseTime = ReleaseTime;
  653. mLookaheadTime = LookaheadTime;
  654. mLookbehindTime = LookbehindTime;
  655. mOutputGainDB = OutputGain;
  656. return true;
  657. }
  658. RegistryPaths EffectCompressor2::GetFactoryPresets()
  659. {
  660. RegistryPaths names;
  661. for (size_t i = 0; i < WXSIZEOF(FactoryPresets); i++)
  662. names.push_back( FactoryPresets[i].name.Translation() );
  663. return names;
  664. }
  665. bool EffectCompressor2::LoadFactoryPreset(int id)
  666. {
  667. if (id < 0 || id >= int(WXSIZEOF(FactoryPresets)))
  668. return false;
  669. const FactoryPreset* preset = &FactoryPresets[id];
  670. mAlgorithm = preset->algorithm;
  671. mCompressBy = preset->compressBy;
  672. mStereoInd = preset->stereoInd;
  673. mThresholdDB = preset->thresholdDB;
  674. mRatio = preset->ratio;
  675. mKneeWidthDB = preset->kneeWidthDB;
  676. mAttackTime = preset->attackTime;
  677. mReleaseTime = preset->releaseTime;
  678. mLookaheadTime = preset->lookaheadTime;
  679. mLookbehindTime = preset->lookbehindTime;
  680. mOutputGainDB = preset->outputGainDB;
  681. TransferDataToWindow();
  682. return true;
  683. }
  684. // Effect implementation
  685. bool EffectCompressor2::CheckWhetherSkipEffect()
  686. {
  687. return false;
  688. }
  689. bool EffectCompressor2::Startup()
  690. {
  691. wxString base = wxT("/Effects/Compressor2/");
  692. // Load the old "current" settings
  693. if (gPrefs->Exists(base))
  694. {
  695. mAlgorithm = DEF_Algorithm;
  696. mCompressBy = DEF_CompressBy;
  697. mStereoInd = DEF_StereoInd;
  698. mThresholdDB = DEF_Threshold;
  699. mRatio = DEF_Ratio; // positive number > 1.0
  700. mKneeWidthDB = DEF_KneeWidth;
  701. mAttackTime = DEF_AttackTime; // seconds
  702. mReleaseTime = DEF_ReleaseTime; // seconds
  703. mLookaheadTime = DEF_LookaheadTime;
  704. mLookbehindTime = DEF_LookbehindTime;
  705. mOutputGainDB = DEF_OutputGain;
  706. SaveUserPreset(GetCurrentSettingsGroup());
  707. gPrefs->Flush();
  708. }
  709. return true;
  710. }
  711. bool EffectCompressor2::Process()
  712. {
  713. // Iterate over each track
  714. this->CopyInputTracks(); // Set up mOutputTracks.
  715. bool bGoodResult = true;
  716. AllocPipeline();
  717. mProgressVal = 0;
  718. #ifdef DEBUG_COMPRESSOR2_TRACE2
  719. debugfile.close();
  720. debugfile.open("/tmp/audio.out", std::ios::trunc | std::ios::out);
  721. #endif
  722. for(auto track : mOutputTracks->Selected<WaveTrack>()
  723. + (mStereoInd ? &Track::Any : &Track::IsLeader))
  724. {
  725. // Get start and end times from track
  726. // PRL: No accounting for multiple channels ?
  727. double trackStart = track->GetStartTime();
  728. double trackEnd = track->GetEndTime();
  729. // Set the current bounds to whichever left marker is
  730. // greater and whichever right marker is less:
  731. mCurT0 = mT0 < trackStart? trackStart: mT0;
  732. mCurT1 = mT1 > trackEnd? trackEnd: mT1;
  733. // Get the track rate
  734. mSampleRate = track->GetRate();
  735. auto range = mStereoInd
  736. ? TrackList::SingletonRange(track)
  737. : TrackList::Channels(track);
  738. mProcStereo = range.size() > 1;
  739. mPreproc = InitPreprocessor(mSampleRate);
  740. mEnvelope = InitEnvelope(mSampleRate, mPipeline[0].capacity());
  741. if(!ProcessOne(range))
  742. {
  743. // Processing failed -> abort
  744. bGoodResult = false;
  745. break;
  746. }
  747. }
  748. this->ReplaceProcessedTracks(bGoodResult);
  749. mPreproc.reset(nullptr);
  750. mEnvelope.reset(nullptr);
  751. FreePipeline();
  752. #ifdef DEBUG_COMPRESSOR2_TRACE2
  753. debugfile.close();
  754. #endif
  755. return bGoodResult;
  756. }
  757. void EffectCompressor2::PopulateOrExchange(ShuttleGui & S)
  758. {
  759. S.SetBorder(10);
  760. S.StartHorizontalLay(wxEXPAND, 1);
  761. {
  762. PlotData* plot;
  763. S.StartVerticalLay();
  764. S.AddVariableText(XO("Envelope dependent gain"), 0,
  765. wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
  766. mGainPlot = S.MinSize( { 400, 200 } )
  767. .AddPlot({}, -60, 0, -60, 0, XO("dB"), XO("dB"),
  768. Ruler::LinearDBFormat, Ruler::LinearDBFormat);
  769. plot = mGainPlot->GetPlotData(0);
  770. plot->pen = std::unique_ptr<wxPen>(
  771. safenew wxPen(AColor::WideEnvelopePen));
  772. plot->xdata.resize(61);
  773. plot->ydata.resize(61);
  774. std::iota(plot->xdata.begin(), plot->xdata.end(), -60);
  775. S.EndVerticalLay();
  776. S.StartVerticalLay();
  777. S.AddVariableText(XO("Compressor step response"), 0,
  778. wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
  779. mResponsePlot = S.MinSize( { 400, 200 } )
  780. .AddPlot({}, 0, 5, -0.2, 1.2, XO("s"), XO(""),
  781. Ruler::IntFormat, Ruler::RealFormat, 2);
  782. mResponsePlot->SetName(XO("Compressor step response plot"));
  783. plot = mResponsePlot->GetPlotData(0);
  784. plot->pen = std::unique_ptr<wxPen>(
  785. safenew wxPen(AColor::WideEnvelopePen));
  786. plot->xdata = {0, RESPONSE_PLOT_STEP_START, RESPONSE_PLOT_STEP_START,
  787. RESPONSE_PLOT_STEP_STOP, RESPONSE_PLOT_STEP_STOP, 5};
  788. plot->ydata = {0.1, 0.1, 1, 1, 0.1, 0.1};
  789. plot = mResponsePlot->GetPlotData(1);
  790. plot->pen = std::unique_ptr<wxPen>(
  791. safenew wxPen(AColor::WideEnvelopePen));
  792. plot->pen->SetColour(wxColor( 230,80,80 )); // Same color as TrackArtist RMS red.
  793. plot->pen->SetWidth(2);
  794. plot->xdata.resize(RESPONSE_PLOT_SAMPLES+1);
  795. plot->ydata.resize(RESPONSE_PLOT_SAMPLES+1);
  796. for(size_t x = 0; x < plot->xdata.size(); ++x)
  797. plot->xdata[x] = x * float(RESPONSE_PLOT_TIME) / float(RESPONSE_PLOT_SAMPLES);
  798. S.EndVerticalLay();
  799. }
  800. S.EndHorizontalLay();
  801. S.SetBorder(5);
  802. S.StartStatic(XO("Algorithm"));
  803. {
  804. wxSize box_size;
  805. int width;
  806. S.StartHorizontalLay(wxEXPAND, 1);
  807. S.StartVerticalLay(1);
  808. S.StartMultiColumn(2, wxALIGN_LEFT);
  809. {
  810. S.SetStretchyCol(1);
  811. mAlgorithmCtrl = S.Validator<wxGenericValidator>(&mAlgorithm)
  812. .AddChoice(XO("Envelope Algorithm:"),
  813. Msgids(kAlgorithmStrings, nAlgos),
  814. mAlgorithm);
  815. box_size = mAlgorithmCtrl->GetMinSize();
  816. width = S.GetParent()->GetTextExtent(wxString::Format(
  817. "%sxxxx", kAlgorithmStrings[nAlgos-1].Translation())).GetWidth();
  818. box_size.SetWidth(width);
  819. mAlgorithmCtrl->SetMinSize(box_size);
  820. }
  821. S.EndMultiColumn();
  822. S.EndVerticalLay();
  823. S.AddSpace(15, 0);
  824. S.StartVerticalLay(1);
  825. S.StartMultiColumn(2, wxALIGN_LEFT);
  826. {
  827. S.SetStretchyCol(1);
  828. mPreprocCtrl = S.Validator<wxGenericValidator>(&mCompressBy)
  829. .AddChoice(XO("Compress based on:"),
  830. Msgids(kCompressByStrings, nBy),
  831. mCompressBy);
  832. mPreprocCtrl->SetMinSize(box_size);
  833. }
  834. S.EndMultiColumn();
  835. S.EndVerticalLay();
  836. S.EndHorizontalLay();
  837. S.Validator<wxGenericValidator>(&mStereoInd)
  838. .AddCheckBox(XO("Compress stereo channels independently"),
  839. DEF_StereoInd);
  840. }
  841. S.EndStatic();
  842. S.StartStatic(XO("Compressor"));
  843. {
  844. int textbox_width = S.GetParent()->GetTextExtent("10.00001XX").GetWidth();
  845. SliderTextCtrl* ctrl = nullptr;
  846. S.StartHorizontalLay(wxEXPAND, true);
  847. S.StartVerticalLay(1);
  848. S.StartMultiColumn(3, wxEXPAND);
  849. {
  850. S.SetStretchyCol(1);
  851. S.AddVariableText(XO("Threshold:"), true,
  852. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  853. ctrl = S.Name(XO("Threshold"))
  854. .Style(SliderTextCtrl::HORIZONTAL)
  855. .AddSliderTextCtrl({}, DEF_Threshold, MAX_Threshold,
  856. MIN_Threshold, ScaleToPrecision(SCL_Threshold), &mThresholdDB);
  857. ctrl->SetMinTextboxWidth(textbox_width);
  858. S.AddVariableText(XO("dB"), true,
  859. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  860. S.AddVariableText(XO("Ratio:"), true,
  861. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  862. ctrl = S.Name(XO("Ratio"))
  863. .Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
  864. .AddSliderTextCtrl({}, DEF_Ratio, MAX_Ratio, MIN_Ratio,
  865. ScaleToPrecision(SCL_Ratio), &mRatio);
  866. /* i18n-hint: Unless your language has a different convention for ratios,
  867. * like 8:1, leave as is.*/
  868. ctrl->SetMinTextboxWidth(textbox_width);
  869. S.AddVariableText(XO(":1"), true,
  870. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  871. S.AddVariableText(XO("Knee Width:"), true,
  872. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  873. ctrl = S.Name(XO("Knee Width"))
  874. .Style(SliderTextCtrl::HORIZONTAL)
  875. .AddSliderTextCtrl({}, DEF_KneeWidth, MAX_KneeWidth,
  876. MIN_KneeWidth, ScaleToPrecision(SCL_KneeWidth),
  877. &mKneeWidthDB);
  878. ctrl->SetMinTextboxWidth(textbox_width);
  879. S.AddVariableText(XO("dB"), true,
  880. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  881. S.AddVariableText(XO("Output Gain:"), true,
  882. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  883. ctrl = S.Name(XO("Output Gain"))
  884. .Style(SliderTextCtrl::HORIZONTAL)
  885. .AddSliderTextCtrl({}, DEF_OutputGain, MAX_OutputGain,
  886. MIN_OutputGain, ScaleToPrecision(SCL_OutputGain),
  887. &mOutputGainDB);
  888. ctrl->SetMinTextboxWidth(textbox_width);
  889. S.AddVariableText(XO("dB"), true,
  890. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  891. }
  892. S.EndMultiColumn();
  893. S.EndVerticalLay();
  894. S.AddSpace(15, 0, 0);
  895. S.StartHorizontalLay(wxEXPAND, true);
  896. S.StartVerticalLay(1);
  897. S.StartMultiColumn(3, wxEXPAND);
  898. {
  899. S.SetStretchyCol(1);
  900. S.AddVariableText(XO("Attack:"), true,
  901. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  902. mAttackTimeCtrl = S.Name(XO("Attack"))
  903. .Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
  904. .AddSliderTextCtrl({}, DEF_AttackTime, MAX_AttackTime,
  905. MIN_AttackTime, ScaleToPrecision(SCL_AttackTime),
  906. &mAttackTime, SCL_AttackTime / 100, 0.033);
  907. mAttackTimeCtrl->SetMinTextboxWidth(textbox_width);
  908. S.AddVariableText(XO("s"), true,
  909. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  910. S.AddVariableText(XO("Release:"), true,
  911. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  912. ctrl = S.Name(XO("Release"))
  913. .Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
  914. .AddSliderTextCtrl({}, DEF_ReleaseTime, MAX_ReleaseTime,
  915. MIN_ReleaseTime, ScaleToPrecision(SCL_ReleaseTime),
  916. &mReleaseTime, SCL_ReleaseTime / 100, 0.033);
  917. ctrl->SetMinTextboxWidth(textbox_width);
  918. S.AddVariableText(XO("s"), true,
  919. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  920. S.AddVariableText(XO("Lookahead Time:"), true,
  921. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  922. mLookaheadTimeCtrl = S.Name(XO("Lookahead Time"))
  923. .Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
  924. .AddSliderTextCtrl({}, DEF_LookaheadTime, MAX_LookaheadTime,
  925. MIN_LookaheadTime, ScaleToPrecision(SCL_LookaheadTime),
  926. &mLookaheadTime, SCL_LookaheadTime / 10);
  927. mLookaheadTimeCtrl->SetMinTextboxWidth(textbox_width);
  928. S.AddVariableText(XO("s"), true,
  929. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  930. S.AddVariableText(XO("Hold Time:"), true,
  931. wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
  932. ctrl = S.Name(XO("Hold Time"))
  933. .Style(SliderTextCtrl::HORIZONTAL | SliderTextCtrl::LOG)
  934. .AddSliderTextCtrl({}, DEF_LookbehindTime, MAX_LookbehindTime,
  935. MIN_LookbehindTime, ScaleToPrecision(SCL_LookbehindTime),
  936. &mLookbehindTime, SCL_LookbehindTime / 10);
  937. ctrl->SetMinTextboxWidth(textbox_width);
  938. S.AddVariableText(XO("s"), true,
  939. wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
  940. }
  941. S.EndMultiColumn();
  942. S.EndVerticalLay();
  943. S.EndHorizontalLay();
  944. }
  945. S.EndVerticalLay();
  946. }
  947. bool EffectCompressor2::TransferDataToWindow()
  948. {
  949. // Transferring data to window causes spurious UpdateUI events
  950. // which would reset the UI values to the previous value.
  951. // This guard lets the program ignore them.
  952. mIgnoreGuiEvents = true;
  953. if (!mUIParent->TransferDataToWindow())
  954. {
  955. mIgnoreGuiEvents = false;
  956. return false;
  957. }
  958. UpdateUI();
  959. mIgnoreGuiEvents = false;
  960. return true;
  961. }
  962. bool EffectCompressor2::TransferDataFromWindow()
  963. {
  964. if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
  965. {
  966. return false;
  967. }
  968. return true;
  969. }
  970. // EffectCompressor2 implementation
  971. double EffectCompressor2::CompressorGain(double env)
  972. {
  973. double kneeCond;
  974. double envDB = LINEAR_TO_DB(env);
  975. // envDB can become NaN is env is exactly zero.
  976. // As solution, use a very low dB value to prevent NaN propagation.
  977. if(isnan(envDB))
  978. envDB = -200;
  979. kneeCond = 2.0 * (envDB - mThresholdDB);
  980. if(kneeCond < -mKneeWidthDB)
  981. {
  982. // Below threshold: only apply make-up gain
  983. return DB_TO_LINEAR(mOutputGainDB);
  984. }
  985. else if(kneeCond >= mKneeWidthDB)
  986. {
  987. // Above threshold: apply compression and make-up gain
  988. return DB_TO_LINEAR(mThresholdDB +
  989. (envDB - mThresholdDB) / mRatio + mOutputGainDB - envDB);
  990. }
  991. else
  992. {
  993. // Within knee: apply interpolated compression and make-up gain
  994. return DB_TO_LINEAR(
  995. (1.0 / mRatio - 1.0)
  996. * pow(envDB - mThresholdDB + mKneeWidthDB / 2.0, 2)
  997. / (2.0 * mKneeWidthDB) + mOutputGainDB);
  998. }
  999. }
  1000. std::unique_ptr<SamplePreprocessor> EffectCompressor2::InitPreprocessor(
  1001. double rate, bool preview)
  1002. {
  1003. size_t window_size = CalcWindowLength(rate);
  1004. if(mCompressBy == kAmplitude)
  1005. return std::unique_ptr<SamplePreprocessor>(safenew
  1006. SlidingMaxPreprocessor(window_size));
  1007. else
  1008. return std::unique_ptr<SamplePreprocessor>(safenew
  1009. SlidingRmsPreprocessor(window_size, preview ? 1.0 : 2.0));
  1010. }
  1011. std::unique_ptr<EnvelopeDetector> EffectCompressor2::InitEnvelope(
  1012. double rate, size_t blockSize, bool preview)
  1013. {
  1014. if(mAlgorithm == kExpFit)
  1015. return std::unique_ptr<EnvelopeDetector>(safenew
  1016. ExpFitEnvelopeDetector(rate, mAttackTime, mReleaseTime, blockSize));
  1017. else
  1018. return std::unique_ptr<EnvelopeDetector>(safenew
  1019. Pt1EnvelopeDetector(rate, mAttackTime, mReleaseTime, blockSize,
  1020. !preview && mCompressBy != kAmplitude));
  1021. }
  1022. size_t EffectCompressor2::CalcBufferSize(double sampleRate)
  1023. {
  1024. size_t capacity;
  1025. mLookaheadLength = CalcLookaheadLength(sampleRate);
  1026. capacity = mLookaheadLength +
  1027. size_t(float(TAU_FACTOR) * (1.0 + mAttackTime) * sampleRate);
  1028. if(capacity < MIN_BUFFER_CAPACITY)
  1029. capacity = MIN_BUFFER_CAPACITY;
  1030. return capacity;
  1031. }
  1032. size_t EffectCompressor2::CalcLookaheadLength(double rate)
  1033. {
  1034. return std::max(0, int(round(mLookaheadTime * rate)));
  1035. }
  1036. size_t EffectCompressor2::CalcWindowLength(double rate)
  1037. {
  1038. return std::max(1, int(round((mLookaheadTime + mLookbehindTime) * rate)));
  1039. }
  1040. /// Get required buffer size for the largest whole track and allocate buffers.
  1041. /// This reduces the amount of allocations required.
  1042. void EffectCompressor2::AllocPipeline()
  1043. {
  1044. bool stereoTrackFound = false;
  1045. double maxSampleRate = 0;
  1046. size_t capacity;
  1047. mProcStereo = false;
  1048. for(auto track : mOutputTracks->Selected<WaveTrack>() + &Track::Any)
  1049. {
  1050. maxSampleRate = std::max(maxSampleRate, track->GetRate());
  1051. // There is a stereo track
  1052. if(track->IsLeader())
  1053. stereoTrackFound = true;
  1054. }
  1055. // Initiate a processing quad-buffer. This buffer will (most likely)
  1056. // be shorter than the length of the track being processed.
  1057. stereoTrackFound = stereoTrackFound && !mStereoInd;
  1058. capacity = CalcBufferSize(maxSampleRate);
  1059. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1060. mPipeline[i].init(capacity, stereoTrackFound);
  1061. }
  1062. void EffectCompressor2::AllocRealtimePipeline()
  1063. {
  1064. mLookaheadLength = CalcLookaheadLength(mSampleRate);
  1065. size_t blockSize = std::max(mLookaheadLength, size_t(512));
  1066. if(mAlgorithm == kExpFit)
  1067. {
  1068. size_t riseTime = round(5.0 * (0.1 + mAttackTime)) * mSampleRate;
  1069. blockSize = std::max(blockSize, riseTime);
  1070. }
  1071. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1072. {
  1073. mPipeline[i].init(blockSize, true);
  1074. mPipeline[i].size = blockSize;
  1075. }
  1076. }
  1077. void EffectCompressor2::FreePipeline()
  1078. {
  1079. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1080. mPipeline[i].free();
  1081. }
  1082. void EffectCompressor2::SwapPipeline()
  1083. {
  1084. #ifdef DEBUG_COMPRESSOR2_DUMP_BUFFERS
  1085. wxString blockname = wxString::Format("/tmp/blockbuf.%d.bin", buf_num);
  1086. std::cerr << "Writing to " << blockname << "\n" << std::flush;
  1087. std::fstream blockbuffer = std::fstream();
  1088. blockbuffer.open(blockname, std::ios::binary | std::ios::out);
  1089. for(size_t i = 0; i < PIPELINE_DEPTH; ++i) {
  1090. float val = mPipeline[i].trackSize;
  1091. blockbuffer.write((char*)&val, sizeof(float));
  1092. val = mPipeline[i].size;
  1093. blockbuffer.write((char*)&val, sizeof(float));
  1094. val = mPipeline[i].capacity();
  1095. blockbuffer.write((char*)&val, sizeof(float));
  1096. }
  1097. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1098. blockbuffer.write((char*)mPipeline[i][0], mPipeline[i].capacity() * sizeof(float));
  1099. wxString envname = wxString::Format("/tmp/envbuf.%d.bin", buf_num++);
  1100. std::cerr << "Writing to " << envname << "\n" << std::flush;
  1101. std::fstream envbuffer = std::fstream();
  1102. envbuffer.open(envname, std::ios::binary | std::ios::out);
  1103. envbuffer.write((char*)mEnvelope->GetBuffer(0),
  1104. mEnvelope->GetBlockSize() * sizeof(float));
  1105. envbuffer.write((char*)mEnvelope->GetBuffer(1),
  1106. mEnvelope->GetBlockSize() * sizeof(float));
  1107. envbuffer.write((char*)mEnvelope->GetBuffer(2),
  1108. mEnvelope->GetBlockSize() * sizeof(float));
  1109. std::cerr << "PipelineState: ";
  1110. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1111. std::cerr << !!mPipeline[i].size;
  1112. std::cerr << " ";
  1113. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1114. std::cerr << !!mPipeline[i].trackSize;
  1115. std::cerr << "\ntrackSize: ";
  1116. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1117. std::cerr << mPipeline[i].trackSize << " ";
  1118. std::cerr << "\ntrackPos: ";
  1119. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1120. std::cerr << mPipeline[i].trackPos.as_size_t() << " ";
  1121. std::cerr << "\nsize: ";
  1122. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1123. std::cerr << mPipeline[i].size << " ";
  1124. std::cerr << "\n" << std::flush;
  1125. #endif
  1126. for(size_t i = 0; i < PIPELINE_DEPTH-1; ++i)
  1127. mPipeline[i].swap(mPipeline[i+1]);
  1128. #ifdef DEBUG_COMPRESSOR2_TRACE
  1129. std::cerr << "\n";
  1130. #endif
  1131. }
  1132. /// ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
  1133. /// and executes ProcessData, on it...
  1134. bool EffectCompressor2::ProcessOne(TrackIterRange<WaveTrack> range)
  1135. {
  1136. WaveTrack* track = *range.begin();
  1137. // Transform the marker timepoints to samples
  1138. const auto start = track->TimeToLongSamples(mCurT0);
  1139. const auto end = track->TimeToLongSamples(mCurT1);
  1140. // Get the length of the buffer (as double). len is
  1141. // used simply to calculate a progress meter, so it is easier
  1142. // to make it a double now than it is to do it later
  1143. mTrackLen = (end - start).as_double();
  1144. // Abort if the right marker is not to the right of the left marker
  1145. if(mCurT1 <= mCurT0)
  1146. return false;
  1147. // Go through the track one buffer at a time. s counts which
  1148. // sample the current buffer starts at.
  1149. auto pos = start;
  1150. #ifdef DEBUG_COMPRESSOR2_TRACE
  1151. std::cerr << "ProcLen: " << (end - start).as_size_t() << "\n" << std::flush;
  1152. std::cerr << "EnvBlockLen: " << mEnvelope->GetBlockSize() << "\n" << std::flush;
  1153. std::cerr << "PipeBlockLen: " << mPipeline[0].capacity() << "\n" << std::flush;
  1154. std::cerr << "LookaheadLen: " << mLookaheadLength << "\n" << std::flush;
  1155. #endif
  1156. bool first = true;
  1157. mProgressVal = 0;
  1158. #ifdef DEBUG_COMPRESSOR2_DUMP_BUFFERS
  1159. buf_num = 0;
  1160. #endif
  1161. while(pos < end)
  1162. {
  1163. #ifdef DEBUG_COMPRESSOR2_TRACE
  1164. std::cerr << "ProcessBlock at: " << pos.as_size_t() << "\n" << std::flush;
  1165. #endif
  1166. StorePipeline(range);
  1167. SwapPipeline();
  1168. const size_t remainingLen = (end - pos).as_size_t();
  1169. // Get a block of samples (smaller than the size of the buffer)
  1170. // Adjust the block size if it is the final block in the track
  1171. const auto blockLen = limitSampleBufferSize(
  1172. remainingLen, mPipeline[PIPELINE_DEPTH-1].capacity());
  1173. mPipeline[PIPELINE_DEPTH-1].trackPos = pos;
  1174. if(!LoadPipeline(range, blockLen))
  1175. return false;
  1176. if(first)
  1177. {
  1178. first = false;
  1179. size_t sampleCount = mEnvelope->InitialConditionSize();
  1180. for(size_t i = 0; i < sampleCount; ++i)
  1181. {
  1182. size_t rp = i % mPipeline[PIPELINE_DEPTH-1].trackSize;
  1183. mEnvelope->CalcInitialCondition(
  1184. PreprocSample(mPipeline[PIPELINE_DEPTH-1], rp));
  1185. }
  1186. mPipeline[PIPELINE_DEPTH-2].fill(
  1187. mEnvelope->InitialCondition(), mProcStereo);
  1188. mPreproc->Reset();
  1189. }
  1190. if(mPipeline[0].size == 0)
  1191. FillPipeline();
  1192. else
  1193. ProcessPipeline();
  1194. // Increment s one blockfull of samples
  1195. pos += blockLen;
  1196. if(!UpdateProgress())
  1197. return false;
  1198. }
  1199. // Handle short selections
  1200. while(mPipeline[1].size == 0)
  1201. {
  1202. #ifdef DEBUG_COMPRESSOR2_TRACE
  1203. std::cerr << "PaddingLoop: ";
  1204. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1205. std::cerr << !!mPipeline[i].size;
  1206. std::cerr << " ";
  1207. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1208. std::cerr << !!mPipeline[i].trackSize;
  1209. std::cerr << "\n" << std::flush;
  1210. #endif
  1211. SwapPipeline();
  1212. FillPipeline();
  1213. if(!UpdateProgress())
  1214. return false;
  1215. }
  1216. while(PipelineHasData())
  1217. {
  1218. StorePipeline(range);
  1219. SwapPipeline();
  1220. DrainPipeline();
  1221. if(!UpdateProgress())
  1222. return false;
  1223. }
  1224. #ifdef DEBUG_COMPRESSOR2_TRACE
  1225. std::cerr << "StoreLastBlock\n" << std::flush;
  1226. #endif
  1227. StorePipeline(range);
  1228. // Return true because the effect processing succeeded ... unless cancelled
  1229. return true;
  1230. }
  1231. bool EffectCompressor2::LoadPipeline(
  1232. TrackIterRange<WaveTrack> range, size_t len)
  1233. {
  1234. sampleCount read_size = -1;
  1235. sampleCount last_read_size = -1;
  1236. #ifdef DEBUG_COMPRESSOR2_TRACE
  1237. std::cerr << "LoadBlock at: " <<
  1238. mPipeline[PIPELINE_DEPTH-1].trackPos.as_size_t() <<
  1239. " with len: " << len << "\n" << std::flush;
  1240. #endif
  1241. // Get the samples from the track and put them in the buffer
  1242. int idx = 0;
  1243. for(auto channel : range)
  1244. {
  1245. channel->Get((samplePtr) mPipeline[PIPELINE_DEPTH-1][idx],
  1246. floatSample, mPipeline[PIPELINE_DEPTH-1].trackPos, len,
  1247. fillZero, true, &read_size);
  1248. // WaveTrack::Get returns the amount of read samples excluding zero
  1249. // filled samples from clip gaps. But in case of stereo tracks with
  1250. // assymetric gaps it still returns the same number for both channels.
  1251. //
  1252. // Fail if we read different sample count from stereo pair tracks.
  1253. // Ignore this check during first iteration (last_read_size == -1).
  1254. if(read_size != last_read_size && last_read_size.as_long_long() != -1)
  1255. return false;
  1256. mPipeline[PIPELINE_DEPTH-1].trackSize = read_size.as_size_t();
  1257. mPipeline[PIPELINE_DEPTH-1].size = read_size.as_size_t();
  1258. ++idx;
  1259. }
  1260. wxASSERT(mPipeline[PIPELINE_DEPTH-2].trackSize == 0 ||
  1261. mPipeline[PIPELINE_DEPTH-2].trackSize >=
  1262. mPipeline[PIPELINE_DEPTH-1].trackSize);
  1263. return true;
  1264. }
  1265. void EffectCompressor2::FillPipeline()
  1266. {
  1267. #ifdef DEBUG_COMPRESSOR2_TRACE
  1268. std::cerr << "FillBlock: " <<
  1269. !!mPipeline[0].size << !!mPipeline[1].size <<
  1270. !!mPipeline[2].size << !!mPipeline[3].size <<
  1271. "\n" << std::flush;
  1272. std::cerr << " from " << -int(mLookaheadLength)
  1273. << " to " << mPipeline[PIPELINE_DEPTH-1].size - mLookaheadLength << "\n" << std::flush;
  1274. std::cerr << "Padding from " << mPipeline[PIPELINE_DEPTH-1].trackSize
  1275. << " to " << mEnvelope->GetBlockSize() << "\n" << std::flush;
  1276. #endif
  1277. // TODO: correct end conditions
  1278. mPipeline[PIPELINE_DEPTH-1].pad_to(mEnvelope->GetBlockSize(), 0, mProcStereo);
  1279. size_t length = mPipeline[PIPELINE_DEPTH-1].size;
  1280. for(size_t rp = mLookaheadLength, wp = 0; wp < length; ++rp, ++wp)
  1281. {
  1282. if(rp < length)
  1283. EnvelopeSample(mPipeline[PIPELINE_DEPTH-2], rp);
  1284. else
  1285. EnvelopeSample(mPipeline[PIPELINE_DEPTH-1], rp % length);
  1286. }
  1287. }
  1288. void EffectCompressor2::ProcessPipeline()
  1289. {
  1290. #ifdef DEBUG_COMPRESSOR2_TRACE
  1291. std::cerr << "ProcessBlock: " <<
  1292. !!mPipeline[0].size << !!mPipeline[1].size <<
  1293. !!mPipeline[2].size << !!mPipeline[3].size <<
  1294. "\n" << std::flush;
  1295. #endif
  1296. float env;
  1297. size_t length = mPipeline[0].size;
  1298. for(size_t i = 0; i < PIPELINE_DEPTH-2; ++i)
  1299. { wxASSERT(mPipeline[0].size == mPipeline[i+1].size); }
  1300. #ifdef DEBUG_COMPRESSOR2_TRACE
  1301. std::cerr << "LookaheadLen: " << mLookaheadLength << "\n" << std::flush;
  1302. std::cerr << "PipeLength: " <<
  1303. mPipeline[0].size << " " << mPipeline[1].size << " " <<
  1304. mPipeline[2].size << " " << mPipeline[3].size <<
  1305. "\n" << std::flush;
  1306. #endif
  1307. for(size_t rp = mLookaheadLength, wp = 0; wp < length; ++rp, ++wp)
  1308. {
  1309. if(rp < length)
  1310. env = EnvelopeSample(mPipeline[PIPELINE_DEPTH-2], rp);
  1311. else if((rp % length) < mPipeline[PIPELINE_DEPTH-1].size)
  1312. env = EnvelopeSample(mPipeline[PIPELINE_DEPTH-1], rp % length);
  1313. else
  1314. // TODO: correct end condition
  1315. env = mEnvelope->ProcessSample(mPreproc->ProcessSample(0.0));
  1316. CompressSample(env, wp);
  1317. }
  1318. }
  1319. inline float EffectCompressor2::PreprocSample(PipelineBuffer& pbuf, size_t rp)
  1320. {
  1321. if(mProcStereo)
  1322. return mPreproc->ProcessSample(pbuf[0][rp], pbuf[1][rp]);
  1323. else
  1324. return mPreproc->ProcessSample(pbuf[0][rp]);
  1325. }
  1326. inline float EffectCompressor2::EnvelopeSample(PipelineBuffer& pbuf, size_t rp)
  1327. {
  1328. return mEnvelope->ProcessSample(PreprocSample(pbuf, rp));
  1329. }
  1330. inline void EffectCompressor2::CompressSample(float env, size_t wp)
  1331. {
  1332. float gain = CompressorGain(env);
  1333. #ifdef DEBUG_COMPRESSOR2_TRACE2
  1334. float ThresholdDB = mThresholdDB;
  1335. float Ratio = mRatio;
  1336. float KneeWidthDB = mKneeWidthDB;
  1337. float AttackTime = mAttackTime;
  1338. float ReleaseTime = mReleaseTime;
  1339. float LookaheadTime = mLookaheadTime;
  1340. float LookbehindTime = mLookbehindTime;
  1341. float OutputGainDB = mOutputGainDB;
  1342. debugfile.write((char*)&ThresholdDB, sizeof(float));
  1343. debugfile.write((char*)&Ratio, sizeof(float));
  1344. debugfile.write((char*)&KneeWidthDB, sizeof(float));
  1345. debugfile.write((char*)&AttackTime, sizeof(float));
  1346. debugfile.write((char*)&ReleaseTime, sizeof(float));
  1347. debugfile.write((char*)&LookaheadTime, sizeof(float));
  1348. debugfile.write((char*)&LookbehindTime, sizeof(float));
  1349. debugfile.write((char*)&OutputGainDB, sizeof(float));
  1350. debugfile.write((char*)&mPipeline[0][0][wp], sizeof(float));
  1351. if(mProcStereo)
  1352. debugfile.write((char*)&mPipeline[0][1][wp], sizeof(float));
  1353. debugfile.write((char*)&env, sizeof(float));
  1354. debugfile.write((char*)&gain, sizeof(float));
  1355. #endif
  1356. #ifdef DEBUG_COMPRESSOR2_ENV
  1357. if(wp < 100)
  1358. mPipeline[0][0][wp] = 0;
  1359. else
  1360. mPipeline[0][0][wp] = env;
  1361. #else
  1362. mPipeline[0][0][wp] = mPipeline[0][0][wp] * gain;
  1363. #endif
  1364. if(mProcStereo)
  1365. mPipeline[0][1][wp] = mPipeline[0][1][wp] * gain;
  1366. #ifdef DEBUG_COMPRESSOR2_TRACE2
  1367. debugfile.write((char*)&mPipeline[0][0][wp], sizeof(float));
  1368. if(mProcStereo)
  1369. debugfile.write((char*)&mPipeline[0][1][wp], sizeof(float));
  1370. #endif
  1371. }
  1372. bool EffectCompressor2::PipelineHasData()
  1373. {
  1374. for(size_t i = 0; i < PIPELINE_DEPTH; ++i)
  1375. {
  1376. if(mPipeline[i].size != 0)
  1377. return true;
  1378. }
  1379. return false;
  1380. }
  1381. void EffectCompressor2::DrainPipeline()
  1382. {
  1383. #ifdef DEBUG_COMPRESSOR2_TRACE
  1384. std::cerr << "DrainBlock: " <<
  1385. !!mPipeline[0].size << !!mPipeline[1].size <<
  1386. !!mPipeline[2].size << !!mPipeline[3].size <<
  1387. "\n" << std::flush;
  1388. bool once = false;
  1389. #endif
  1390. float env;
  1391. size_t length = mPipeline[0].size;
  1392. size_t length2 = mPipeline[PIPELINE_DEPTH-2].size;
  1393. #ifdef DEBUG_COMPRESSOR2_TRACE
  1394. std::cerr << "LookaheadLen: " << mLookaheadLength << "\n" << std::flush;
  1395. std::cerr << "PipeLength: " <<
  1396. mPipeline[0].size << " " << mPipeline[1].size << " " <<
  1397. mPipeline[2].size << " " << mPipeline[3].size <<
  1398. "\n" << std::flush;
  1399. #endif
  1400. for(size_t rp = mLookaheadLength, wp = 0; wp < length; ++rp, ++wp)
  1401. {
  1402. if(rp < length2 && mPipeline[PIPELINE_DEPTH-2].size != 0)
  1403. {
  1404. #ifdef DEBUG_COMPRESSOR2_TRACE
  1405. if(!once)
  1406. {
  1407. once = true;
  1408. std::cerr << "Draining overlapping buffer\n" << std::flush;
  1409. }
  1410. #endif
  1411. env = EnvelopeSample(mPipeline[PIPELINE_DEPTH-2], rp);
  1412. }
  1413. else
  1414. // TODO: correct end condition
  1415. env = mEnvelope->ProcessSample(mPreproc->ProcessSample(0.0));
  1416. CompressSample(env, wp);
  1417. }
  1418. }
  1419. void EffectCompressor2::StorePipeline(TrackIterRange<WaveTrack> range)
  1420. {
  1421. #ifdef DEBUG_COMPRESSOR2_TRACE
  1422. std::cerr << "StoreBlock at: " << mPipeline[0].trackPos.as_size_t() <<
  1423. " with len: " << mPipeline[0].trackSize << "\n" << std::flush;
  1424. #endif
  1425. int idx = 0;
  1426. for(auto channel : range)
  1427. {
  1428. // Copy the newly-changed samples back onto the track.
  1429. channel->Set((samplePtr) mPipeline[0][idx],
  1430. floatSample, mPipeline[0].trackPos, mPipeline[0].trackSize);
  1431. ++idx;
  1432. }
  1433. mPipeline[0].trackSize = 0;
  1434. mPipeline[0].size = 0;
  1435. }
  1436. bool EffectCompressor2::UpdateProgress()
  1437. {
  1438. mProgressVal +=
  1439. (double(1+mProcStereo) * mPipeline[PIPELINE_DEPTH-1].trackSize)
  1440. / (double(GetNumWaveTracks()) * mTrackLen);
  1441. return !TotalProgress(mProgressVal);
  1442. }
  1443. void EffectCompressor2::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
  1444. {
  1445. if(!mIgnoreGuiEvents)
  1446. TransferDataFromWindow();
  1447. UpdateUI();
  1448. }
  1449. void EffectCompressor2::UpdateUI()
  1450. {
  1451. UpdateCompressorPlot();
  1452. UpdateResponsePlot();
  1453. if(mEnvelope.get() != nullptr)
  1454. UpdateRealtimeParams();
  1455. }
  1456. void EffectCompressor2::UpdateCompressorPlot()
  1457. {
  1458. PlotData* plot;
  1459. plot = mGainPlot->GetPlotData(0);
  1460. wxASSERT(plot->xdata.size() == plot->ydata.size());
  1461. if(!IsInRange(mThresholdDB, MIN_Threshold, MAX_Threshold))
  1462. return;
  1463. if(!IsInRange(mRatio, MIN_Ratio, MAX_Ratio))
  1464. return;
  1465. if(!IsInRange(mKneeWidthDB, MIN_KneeWidth, MAX_KneeWidth))
  1466. return;
  1467. if(!IsInRange(mOutputGainDB, MIN_OutputGain, MAX_OutputGain))
  1468. return;
  1469. size_t xsize = plot->xdata.size();
  1470. for(size_t i = 0; i < xsize; ++i)
  1471. plot->ydata[i] = plot->xdata[i] +
  1472. LINEAR_TO_DB(CompressorGain(DB_TO_LINEAR(plot->xdata[i])));
  1473. mGainPlot->SetName(XO("Compressor gain reduction: %.1f dB").
  1474. Format(plot->ydata[xsize-1]));
  1475. mGainPlot->Refresh(false);
  1476. }
  1477. void EffectCompressor2::UpdateResponsePlot()
  1478. {
  1479. PlotData* plot;
  1480. plot = mResponsePlot->GetPlotData(1);
  1481. wxASSERT(plot->xdata.size() == plot->ydata.size());
  1482. if(!IsInRange(mAttackTime, MIN_AttackTime, MAX_AttackTime))
  1483. return;
  1484. if(!IsInRange(mReleaseTime, MIN_ReleaseTime, MAX_ReleaseTime))
  1485. return;
  1486. if(!IsInRange(mLookaheadTime, MIN_LookaheadTime, MAX_LookaheadTime))
  1487. return;
  1488. if(!IsInRange(mLookbehindTime, MIN_LookbehindTime, MAX_LookbehindTime))
  1489. return;
  1490. std::unique_ptr<SamplePreprocessor> preproc;
  1491. std::unique_ptr<EnvelopeDetector> envelope;
  1492. float plot_rate = RESPONSE_PLOT_SAMPLES / RESPONSE_PLOT_TIME;
  1493. size_t lookahead_size = CalcLookaheadLength(plot_rate);
  1494. lookahead_size -= (lookahead_size > 0);
  1495. ssize_t block_size = float(TAU_FACTOR) * (mAttackTime + 1.0) * plot_rate;
  1496. preproc = InitPreprocessor(plot_rate, true);
  1497. envelope = InitEnvelope(plot_rate, block_size, true);
  1498. preproc->Reset(0.1);
  1499. envelope->Reset(0.1);
  1500. ssize_t step_start = RESPONSE_PLOT_STEP_START * plot_rate - lookahead_size;
  1501. ssize_t step_stop = RESPONSE_PLOT_STEP_STOP * plot_rate - lookahead_size;
  1502. ssize_t xsize = plot->xdata.size();
  1503. for(ssize_t i = -lookahead_size; i < 2*block_size; ++i)
  1504. {
  1505. if(i < step_start || i > step_stop)
  1506. envelope->ProcessSample(preproc->ProcessSample(0.1));
  1507. else
  1508. envelope->ProcessSample(preproc->ProcessSample(1));
  1509. }
  1510. for(ssize_t i = 0; i < xsize; ++i)
  1511. {
  1512. float x = 1;
  1513. if(i < RESPONSE_PLOT_STEP_START * plot_rate ||
  1514. i > RESPONSE_PLOT_STEP_STOP * plot_rate)
  1515. x = 0.1;
  1516. plot->ydata[i] = x * CompressorGain(
  1517. envelope->ProcessSample(preproc->ProcessSample(0.1)));
  1518. }
  1519. mResponsePlot->Refresh(false);
  1520. }
  1521. void EffectCompressor2::UpdateRealtimeParams()
  1522. {
  1523. std::lock_guard<std::mutex> guard(mRealtimeMutex);
  1524. size_t window_size = CalcWindowLength(mSampleRate);
  1525. mLookaheadLength = CalcLookaheadLength(mSampleRate);
  1526. mPreproc->SetWindowSize(window_size);
  1527. mEnvelope->SetParams(mSampleRate, mAttackTime, mReleaseTime);
  1528. }