55#include " OpusController.h"
66#include " ../parameterIds.h"
77#include " ../Mp3ControllerManager.h"
8+ #include " ../SpectrumRescale.h"
89OpusController::OpusController (juce::AudioProcessorValueTreeState& _parameters)
910 : bInitialized(false ),
1011 opusEncoder(nullptr ),
@@ -23,7 +24,14 @@ OpusController::OpusController (juce::AudioProcessorValueTreeState& _parameters)
2324 parameters.addParameterListener (PACKET_LOSS_STICK_PARAM_ID , this );
2425 parameters.addParameterListener (PACKET_LOSS_MODE_PARAM_ID , this );
2526 parameters.addParameterListener (ERROR_PARAM_ID , this );
27+ parameters.addParameterListener (MDCT_PITCH_SHIFT_PARAM_ID , this );
28+ parameters.addParameterListener (MDCT_AMPLITUDE_SHIFT_PARAM_ID , this );
29+ parameters.addParameterListener (MDCT_FEEDBACK_PARAM_ID , this );
30+ for (const auto & bandOrderParamId : BAND_ORDER_PARAM_IDS ) {
31+ parameters.addParameterListener (bandOrderParamId, this );
32+ }
2633 parametersNeedUpdating = true ;
34+ startTimerHz (30 );
2735}
2836
2937OpusController::~OpusController ()
@@ -38,6 +46,12 @@ OpusController::~OpusController()
3846 parameters.removeParameterListener (PACKET_LOSS_STICK_PARAM_ID , this );
3947 parameters.removeParameterListener (PACKET_LOSS_MODE_PARAM_ID , this );
4048 parameters.removeParameterListener (ERROR_PARAM_ID , this );
49+ parameters.removeParameterListener (MDCT_PITCH_SHIFT_PARAM_ID , this );
50+ parameters.removeParameterListener (MDCT_AMPLITUDE_SHIFT_PARAM_ID , this );
51+ parameters.removeParameterListener (MDCT_FEEDBACK_PARAM_ID , this );
52+ for (const auto & bandOrderParamId : BAND_ORDER_PARAM_IDS ) {
53+ parameters.removeParameterListener (bandOrderParamId, this );
54+ }
4155}
4256
4357bool OpusController::init (int sampleRate, int , int )
@@ -64,6 +78,20 @@ bool OpusController::init (int sampleRate, int, int)
6478
6579 packetLossModel = std::make_unique<PacketLossModel>(sampleRate);
6680
81+ fftOrder = 1 ;
82+ while ((1 << fftOrder) < samplesPerFrame) {
83+ ++fftOrder;
84+ }
85+ fftSize = 1 << fftOrder;
86+ fft = std::make_unique<juce::dsp::FFT >(fftOrder);
87+
88+ const auto numBins = fftSize / 2 + 1 ;
89+ for (auto channel = 0 ; channel < 2 ; ++channel) {
90+ fftBuffers[static_cast <size_t >(channel)].assign (static_cast <size_t >(fftSize) * 2 , 0 .0f );
91+ feedbackSpectrum[static_cast <size_t >(channel)].assign (static_cast <size_t >(numBins), std::complex <float >(0 .0f , 0 .0f ));
92+ }
93+ reassignmentScratch.assign (static_cast <size_t >(numBins), std::complex <float >(0 .0f , 0 .0f ));
94+
6795 return true ;
6896}
6997
@@ -89,6 +117,7 @@ void OpusController::processBlock (juce::AudioBuffer<float>& buffer)
89117 output[static_cast <size_t >(sampleCounter * 2 + 1 )] = 0 ;
90118 sampleCounter++;
91119 if (sampleCounter == samplesPerFrame) {
120+ applyFrequencyBends ();
92121 int framesizeDownscaleFactor = std::round (random.nextFloat () * turbo * 3 );
93122 framesizeDownscaleFactor = 1 << std::min (std::max (0 , framesizeDownscaleFactor), 2 );
94123 auto subframeSize = samplesPerFrame / framesizeDownscaleFactor;
@@ -164,4 +193,184 @@ void OpusController::updateParameters()
164193
165194 error = *((juce::AudioParameterFloat*)parameters.getParameter (ERROR_PARAM_ID ));
166195 error = std::pow (error, 3 );
196+
197+ setMDCTpostshiftBends (
198+ ((juce::AudioParameterInt*) parameters.getParameter (MDCT_PITCH_SHIFT_PARAM_ID ))->get (),
199+ ((juce::AudioParameterFloat*) parameters.getParameter (MDCT_AMPLITUDE_SHIFT_PARAM_ID ))->get ()
200+ );
201+
202+ setMDCTfeedback (
203+ ((juce::AudioParameterFloat*) parameters.getParameter (MDCT_FEEDBACK_PARAM_ID ))->get ()
204+ );
205+
206+ int bandReassign[OpusBendFlags::numReassignmentBands];
207+ int i;
208+ for (i = 0 ; i < NUM_REASSIGNMENT_BANDS ; ++i) {
209+ bandReassign[i] = ((juce::AudioParameterInt*) parameters.getParameter (BAND_ORDER_PARAM_IDS [static_cast <size_t >(i)]))->get ();
210+ }
211+ for (; i < OpusBendFlags::numReassignmentBands; ++i) {
212+ bandReassign[i] = i;
213+ }
214+ setMDCTBandReassignmentBends (bandReassign);
215+ }
216+
217+ void OpusController::applyFrequencyBends ()
218+ {
219+ const auto numBins = fftSize / 2 + 1 ;
220+ const bool identity = bendFlags.isIdentity ();
221+
222+ // Accumulated across both channels, for the live "before/after" spectrum display - this
223+ // runs whether or not any bend is active, mirroring how lame/blade always snapshot their
224+ // MDCT spectrum for the graph regardless of bend settings.
225+ std::array<float , mdctDisplaySize> preSnapshot{};
226+ std::array<float , mdctDisplaySize> postSnapshot{};
227+
228+ for (auto channel = 0 ; channel < 2 ; ++channel) {
229+ auto & fftBuffer = fftBuffers[static_cast <size_t >(channel)];
230+ std::fill (fftBuffer.begin (), fftBuffer.end (), 0 .0f );
231+ for (auto s = 0 ; s < samplesPerFrame; ++s) {
232+ fftBuffer[static_cast <size_t >(s)] = input[static_cast <size_t >(s * 2 + channel)];
233+ }
234+
235+ fft->performRealOnlyForwardTransform (fftBuffer.data (), true );
236+
237+ auto * bins = reinterpret_cast <std::complex <float >*>(fftBuffer.data ());
238+
239+ accumulateSpectrumSnapshot (bins, numBins, preSnapshot);
240+
241+ // When every bend is at its neutral value, skip mutating the spectrum and skip the
242+ // inverse transform entirely, so the encoded audio is bit-for-bit identical to before
243+ // this FFT stage existed - only the (otherwise unused) snapshot above is computed.
244+ if (!identity) {
245+ applyBandReassignment (bins, numBins);
246+ applyPostShift (bins, numBins);
247+ applyFeedback (bins, numBins, channel);
248+ }
249+
250+ accumulateSpectrumSnapshot (bins, numBins, postSnapshot);
251+
252+ if (!identity) {
253+ fft->performRealOnlyInverseTransform (fftBuffer.data ());
254+
255+ for (auto s = 0 ; s < samplesPerFrame; ++s) {
256+ input[static_cast <size_t >(s * 2 + channel)] = fftBuffer[static_cast <size_t >(s)];
257+ }
258+ }
259+ }
260+
261+ for (auto i = 0 ; i < mdctDisplaySize; ++i) {
262+ mdctPreBend[static_cast <size_t >(i)] = preSnapshot[static_cast <size_t >(i)] / 2 .0f ;
263+ mdctPostBend[static_cast <size_t >(i)] = postSnapshot[static_cast <size_t >(i)] / 2 .0f ;
264+ }
265+ }
266+
267+ void OpusController::accumulateSpectrumSnapshot (const std::complex <float >* bins, int numBins, std::array<float , mdctDisplaySize>& target) const
268+ {
269+ // Normalizes FFT bin magnitude to roughly the 0-1(+) range rescaleMDCT() expects, and
270+ // resamples the (sample-rate-dependent) bin count down to the graph's fixed display width.
271+ const auto normalization = static_cast <float >(fftSize) / 2 .0f ;
272+ for (auto i = 0 ; i < mdctDisplaySize; ++i) {
273+ auto sourceBin = std::min (numBins - 1 , (i * numBins) / mdctDisplaySize);
274+ target[static_cast <size_t >(i)] += std::abs (bins[static_cast <size_t >(sourceBin)]) / normalization;
275+ }
276+ }
277+
278+ void OpusController::applyBandReassignment (std::complex <float >* bins, int numBins)
279+ {
280+ const auto numBands = OpusBendFlags::numReassignmentBands;
281+ if (bendFlags.mdctBandReassignment == OpusBendFlags::identityReassignment ()) {
282+ return ;
283+ }
284+
285+ std::copy (bins, bins + numBins, reassignmentScratch.begin ());
286+
287+ auto bandBoundary = [numBins, numBands] (int band) {
288+ return (band * numBins) / numBands;
289+ };
290+
291+ for (auto band = 0 ; band < numBands; ++band) {
292+ auto destStart = bandBoundary (band);
293+ auto destEnd = bandBoundary (band + 1 );
294+ auto srcBand = juce::jlimit (0 , numBands - 1 , bendFlags.mdctBandReassignment [static_cast <size_t >(band)]);
295+ auto srcStart = bandBoundary (srcBand);
296+ auto srcEnd = bandBoundary (srcBand + 1 );
297+ auto count = std::min (destEnd - destStart, srcEnd - srcStart);
298+ for (auto i = 0 ; i < count; ++i) {
299+ bins[destStart + i] = reassignmentScratch[static_cast <size_t >(srcStart + i)];
300+ }
301+ }
302+ }
303+
304+ void OpusController::applyPostShift (std::complex <float >* bins, int numBins)
305+ {
306+ const auto hShift = bendFlags.mdctPostHShift ;
307+ if (hShift != 0 ) {
308+ std::copy (bins, bins + numBins, reassignmentScratch.begin ());
309+ for (auto i = 0 ; i < numBins; ++i) {
310+ auto srcIndex = i - hShift;
311+ bins[i] = (srcIndex >= 0 && srcIndex < numBins)
312+ ? reassignmentScratch[static_cast <size_t >(srcIndex)]
313+ : std::complex <float >(0 .0f , 0 .0f );
314+ }
315+ }
316+
317+ const auto vShift = bendFlags.mdctPostVShift ;
318+ if (vShift < 0 .0f || vShift > 0 .0f ) {
319+ auto peak = 0 .0f ;
320+ for (auto i = 0 ; i < numBins; ++i) {
321+ peak = std::max (peak, std::abs (bins[i]));
322+ }
323+ if (peak > 0 .0f ) {
324+ for (auto i = 0 ; i < numBins; ++i) {
325+ auto mag = std::abs (bins[i]);
326+ float newMag;
327+ if (vShift > 0 .0f ) {
328+ newMag = mag + vShift * (peak - mag);
329+ } else {
330+ auto attenuation = std::max (0 .0f , 1 .0f + vShift * (1 .0f - mag / peak));
331+ newMag = mag * attenuation;
332+ }
333+ if (mag > 1 .0e-8f ) {
334+ bins[i] *= (newMag / mag);
335+ } else if (vShift > 0 .0f ) {
336+ bins[i] = std::complex <float >(newMag, 0 .0f );
337+ }
338+ }
339+ }
340+ }
341+ }
342+
343+ void OpusController::applyFeedback (std::complex <float >* bins, int numBins, int channel)
344+ {
345+ auto & previous = feedbackSpectrum[static_cast <size_t >(channel)];
346+ const auto feedback = bendFlags.mdctFeedback ;
347+ if (feedback > 0 .0f ) {
348+ for (auto i = 0 ; i < numBins; ++i) {
349+ bins[i] = bins[i] * (1 .0f - feedback) + previous[static_cast <size_t >(i)] * feedback;
350+ }
351+ }
352+ std::copy (bins, bins + numBins, previous.begin ());
353+ }
354+
355+ void OpusController::timerCallback ()
356+ {
357+ // Mp3ControllerManager (lame/blade) owns this same "mdct" ValueTree node while it's the
358+ // active encoder - only push while Opus is actually selected, so the two don't fight.
359+ bool isOpus = (((juce::AudioParameterChoice*)parameters.getParameter (ENCODER_PARAM_ID ))->getIndex () == 2 );
360+ if (!isOpus) {
361+ return ;
362+ }
363+
364+ auto mdctSamples = parameters.state .getChildWithName (" mdct" );
365+ if (!mdctSamples.isValid ()) {
366+ return ;
367+ }
368+
369+ juce::var preBendV, postBendV;
370+ for (auto i = 0 ; i < mdctDisplaySize; ++i) {
371+ preBendV.append (rescaleMDCT (mdctPreBend[static_cast <size_t >(i)]));
372+ postBendV.append (rescaleMDCT (mdctPostBend[static_cast <size_t >(i)]));
373+ }
374+ mdctSamples.setProperty (" pre" , preBendV, nullptr );
375+ mdctSamples.setProperty (" post" , postBendV, nullptr );
167376}
0 commit comments