From f8a9c576a6a15d8d40db02a62451662f6d9a8c7c Mon Sep 17 00:00:00 2001 From: Katherine Whitlock Date: Sat, 18 Jan 2025 17:27:00 -0500 Subject: [PATCH] Audio pipeline buffers to std::span (#3276) * remove addAudio * Spans in audio_engine * Metronome to span * AbsoluteValue to span * Compressor to span * Stutterer to span * Granular processor to span * SampleRecorder to span * Song to std::span * ModControllableAudio signatures to std::span * Refactor ModControllableAudio to use element-wise for loops * GlobalEffectable parameters to std::span * ModFX to span (and minor parameter refactoring) * Output::renderOutput to std::span * Sound::render parameter to std::span * Refactor Sound::render() around std::span * GlobalEffectableForClip::render parameter to std::span * Refactor GlobalEffectableForClip::renderOutput to utilize std::span * renderGlobalEffectableForClip parameter to std::span * Refactor audio_output use of std::span * AudioClip::render signature to std::span * Add span include to GranularProcessor.h * span include for AbsValueFollower * include span in stutterer * include span in modfxprocessor --- src/deluge/dsp/compressor/rms_feedback.cpp | 72 +++---- src/deluge/dsp/compressor/rms_feedback.h | 7 +- .../dsp/envelope_follower/absolute_value.cpp | 22 +- .../dsp/envelope_follower/absolute_value.h | 4 +- src/deluge/dsp/granular/GranularProcessor.cpp | 25 +-- src/deluge/dsp/granular/GranularProcessor.h | 9 +- src/deluge/dsp/stereo_sample.h | 7 + src/deluge/model/clip/audio_clip.cpp | 15 +- src/deluge/model/clip/audio_clip.h | 2 +- src/deluge/model/fx/stutterer.cpp | 43 ++-- src/deluge/model/fx/stutterer.h | 3 +- .../global_effectable/global_effectable.cpp | 19 +- .../global_effectable/global_effectable.h | 7 +- .../global_effectable_for_clip.cpp | 96 ++++----- .../global_effectable_for_clip.h | 24 ++- src/deluge/model/instrument/kit.cpp | 27 +-- src/deluge/model/instrument/kit.h | 13 +- .../model/instrument/non_audio_instrument.cpp | 9 +- .../model/instrument/non_audio_instrument.h | 6 +- .../model/mod_controllable/ModFXProcessor.cpp | 132 ++++++------ .../model/mod_controllable/ModFXProcessor.h | 27 +-- .../mod_controllable_audio.cpp | 92 ++++---- .../mod_controllable/mod_controllable_audio.h | 18 +- src/deluge/model/output.h | 6 +- src/deluge/model/sample/sample_recorder.cpp | 120 ++++------- src/deluge/model/sample/sample_recorder.h | 2 +- src/deluge/model/song/song.cpp | 17 +- src/deluge/model/song/song.h | 3 +- src/deluge/processing/audio_output.cpp | 200 ++++++------------ src/deluge/processing/audio_output.h | 12 +- .../processing/engines/audio_engine.cpp | 100 +++++---- src/deluge/processing/metronome/metronome.cpp | 25 +-- src/deluge/processing/metronome/metronome.h | 3 +- src/deluge/processing/sound/sound.cpp | 150 ++++++------- src/deluge/processing/sound/sound.h | 4 +- .../processing/sound/sound_instrument.cpp | 15 +- .../processing/sound/sound_instrument.h | 6 +- src/deluge/util/functions.cpp | 14 -- src/deluge/util/functions.h | 2 - 39 files changed, 590 insertions(+), 768 deletions(-) diff --git a/src/deluge/dsp/compressor/rms_feedback.cpp b/src/deluge/dsp/compressor/rms_feedback.cpp index 3e0ae0c624..7587e37713 100644 --- a/src/deluge/dsp/compressor/rms_feedback.cpp +++ b/src/deluge/dsp/compressor/rms_feedback.cpp @@ -17,7 +17,7 @@ #include "dsp/compressor/rms_feedback.h" #include "util/fixedpoint.h" -StereoSample dryBuffer[SSI_TX_BUFFER_NUM_SAMPLES]{0}; +std::array dryBuffer; RMSFeedbackCompressor::RMSFeedbackCompressor() { setAttack(5 << 24); @@ -49,34 +49,34 @@ void RMSFeedbackCompressor::updateER(float numSamples, q31_t finalVolume) { er = runEnvelope(lastER, er, numSamples); } /// This renders at a 'neutral' volume, so that at threshold zero the volume in unchanged -void RMSFeedbackCompressor::renderVolNeutral(StereoSample* buffer, uint16_t numSamples, q31_t finalVolume) { +void RMSFeedbackCompressor::renderVolNeutral(std::span buffer, q31_t finalVolume) { // this is a bit gross - the compressor can inherently apply volume changes, but in the case of the per clip // compressor that's already been handled by the reverb send, and the logic there is tightly coupled such that // I couldn't extract correct volume levels from it. - render(buffer, numSamples, 1 << 27, 1 << 27, finalVolume >> 3); + render(buffer, 1 << 27, 1 << 27, finalVolume >> 3); } constexpr uint8_t saturationAmount = 3; -void RMSFeedbackCompressor::render(StereoSample* buffer, uint16_t numSamples, q31_t volAdjustL, q31_t volAdjustR, +void RMSFeedbackCompressor::render(std::span buffer, q31_t volAdjustL, q31_t volAdjustR, q31_t finalVolume) { // make a copy for blending if we need to if (wet != ONE_Q31) { - memcpy(dryBuffer, buffer, numSamples * sizeof(StereoSample)); + memcpy(dryBuffer.data(), buffer.data(), buffer.size_bytes()); } if (!onLastTime) { // sets the "working level" for interpolation and anti aliasing lastSaturationTanHWorkingValue[0] = - (uint32_t)lshiftAndSaturateUnknown(buffer->l, saturationAmount) + 2147483648u; + (uint32_t)lshiftAndSaturateUnknown(buffer[0].l, saturationAmount) + 2147483648u; lastSaturationTanHWorkingValue[1] = - (uint32_t)lshiftAndSaturateUnknown(buffer->r, saturationAmount) + 2147483648u; + (uint32_t)lshiftAndSaturateUnknown(buffer[0].r, saturationAmount) + 2147483648u; onLastTime = true; } // we update this every time since we won't know if the song volume changed - updateER(numSamples, finalVolume); + updateER(buffer.size(), finalVolume); float over = std::max(0, (rms - threshdb)); - state = runEnvelope(state, over, numSamples); + state = runEnvelope(state, over, buffer.size()); float reduction = -state * fraction; @@ -93,40 +93,38 @@ void RMSFeedbackCompressor::render(StereoSample* buffer, uint16_t numSamples, q3 float finalVolumeR = gain * float(volAdjustR >> 9); // The amount we need to step the current volume so that by the end of the rendering window - q31_t amplitudeIncrementL = ((int32_t)((finalVolumeL - (currentVolumeL >> 8)) / float(numSamples))) << 8; - q31_t amplitudeIncrementR = ((int32_t)((finalVolumeR - (currentVolumeR >> 8)) / float(numSamples))) << 8; + q31_t amplitudeIncrementL = ((int32_t)((finalVolumeL - (currentVolumeL >> 8)) / float(buffer.size()))) << 8; + q31_t amplitudeIncrementR = ((int32_t)((finalVolumeR - (currentVolumeR >> 8)) / float(buffer.size()))) << 8; - StereoSample* thisSample = buffer; - StereoSample* drySample = dryBuffer; - StereoSample* bufferEnd = buffer + numSamples; - - do { + auto dry_it = dryBuffer.begin(); + for (StereoSample& sample : buffer) { currentVolumeL += amplitudeIncrementL; currentVolumeR += amplitudeIncrementR; + // Need to shift left by 4 because currentVolumeL is a 5.26 signed number rather than a 1.30 signed. - thisSample->l = multiply_32x32_rshift32(thisSample->l, currentVolumeL) << 4; - thisSample->l = getTanHAntialiased(thisSample->l, &lastSaturationTanHWorkingValue[0], saturationAmount); + sample.l = multiply_32x32_rshift32(sample.l, currentVolumeL) << 4; + sample.l = getTanHAntialiased(sample.l, &lastSaturationTanHWorkingValue[0], saturationAmount); - thisSample->r = multiply_32x32_rshift32(thisSample->r, currentVolumeR) << 4; - thisSample->r = getTanHAntialiased(thisSample->r, &lastSaturationTanHWorkingValue[1], saturationAmount); + sample.r = multiply_32x32_rshift32(sample.r, currentVolumeR) << 4; + sample.r = getTanHAntialiased(sample.r, &lastSaturationTanHWorkingValue[1], saturationAmount); // wet/dry blend if (wet != ONE_Q31) { - thisSample->l = multiply_32x32_rshift32(thisSample->l, wet); - thisSample->l = multiply_accumulate_32x32_rshift32_rounded(thisSample->l, drySample->l, dry); - thisSample->l <<= 1; // correct for the two multiplications + sample.l = multiply_32x32_rshift32(sample.l, wet); + sample.l = multiply_accumulate_32x32_rshift32_rounded(sample.l, dry_it->l, dry); + sample.l <<= 1; // correct for the two multiplications // same for r because StereoSample is a dumb class - thisSample->r = multiply_32x32_rshift32(thisSample->r, wet); - thisSample->r = multiply_accumulate_32x32_rshift32_rounded(thisSample->r, drySample->r, dry); - thisSample->r <<= 1; - ++drySample; // this is a little gross but it's fine + sample.r = multiply_32x32_rshift32(sample.r, wet); + sample.r = multiply_accumulate_32x32_rshift32_rounded(sample.r, dry_it->r, dry); + sample.r <<= 1; + ++dry_it; // this is a little gross but it's fine } + } - } while (++thisSample != bufferEnd); // for LEDs // 4 converts to dB, then quadrupled for display range since a 30db reduction is basically killing the signal gainReduction = std::clamp(-(reduction) * 4 * 4, 0, 127); // calc compression for next round (feedback compressor) - rms = calcRMS(buffer, numSamples); + rms = calcRMS(buffer); } float RMSFeedbackCompressor::runEnvelope(float current, float desired, float numSamples) const { @@ -142,21 +140,19 @@ float RMSFeedbackCompressor::runEnvelope(float current, float desired, float num // output range is 0-21 (2^31) // dac clipping is at 16 -float RMSFeedbackCompressor::calcRMS(StereoSample* buffer, uint16_t numSamples) { - StereoSample* thisSample = buffer; - StereoSample* bufferEnd = buffer + numSamples; +float RMSFeedbackCompressor::calcRMS(std::span buffer) { q31_t sum = 0; q31_t offset = 0; // to remove dc offset float lastMean = mean; - do { - q31_t l = thisSample->l - hpfL.doFilter(thisSample->l, hpfA_); - q31_t r = thisSample->r - hpfL.doFilter(thisSample->r, hpfA_); + + for (StereoSample sample : buffer) { + q31_t l = sample.l - hpfL.doFilter(sample.l, hpfA_); + q31_t r = sample.r - hpfL.doFilter(sample.r, hpfA_); q31_t s = std::max(std::abs(l), std::abs(r)); sum += multiply_32x32_rshift32(s, s); + } - } while (++thisSample != bufferEnd); - - float ns = float(numSamples * 2); + float ns = buffer.size() * 2; mean = (float(sum) / ONE_Q31f) / ns; // warning this is not good math but it's pretty close and way cheaper than doing it properly // good math would use a long FIR, this is a one pole IIR instead diff --git a/src/deluge/dsp/compressor/rms_feedback.h b/src/deluge/dsp/compressor/rms_feedback.h index 2201f77259..86a27eb516 100644 --- a/src/deluge/dsp/compressor/rms_feedback.h +++ b/src/deluge/dsp/compressor/rms_feedback.h @@ -21,6 +21,7 @@ #include "dsp/filter/ladder_components.h" #include "dsp/stereo_sample.h" #include +#include class [[gnu::hot]] RMSFeedbackCompressor { public: @@ -54,12 +55,12 @@ class [[gnu::hot]] RMSFeedbackCompressor { /// @param volAdjustL Linear gain to apply to the left channel as a 4.27 signed fixed point number. /// @param volAdjustL Linear gain to apply to the right channel as a 4.27 signed fixed point number. /// @param finalVolume Linear peak-to-peak volume scale, as a 3.29 fixed-point integer. - void render(StereoSample* buffer, uint16_t numSamples, q31_t volAdjustL, q31_t volAdjustR, q31_t finalVolume); + void render(std::span buffer, q31_t volAdjustL, q31_t volAdjustR, q31_t finalVolume); /// Render the compressor with neutral left/right gain and with the finalVolume tweaked so the compressor applies /// 0db gain change at theshold zero. Used by the per-clip compressors because the clip volume is applied without /// the compressor being involved. - void renderVolNeutral(StereoSample* buffer, uint16_t numSamples, q31_t finalVolume); + void renderVolNeutral(std::span buffer, q31_t finalVolume); /// Compute an updated envelope value, using the attack time constant if desired > current and the release time /// constant otherwise. @@ -169,7 +170,7 @@ class [[gnu::hot]] RMSFeedbackCompressor { void updateER(float numSamples, q31_t finalVolume); /// Calculate the RMS amplitude, post internal HPF, of the samples. - float calcRMS(StereoSample* buffer, uint16_t numSamples); + float calcRMS(std::span buffer); /// Amount of gain reduction applied during the last render pass, in 6.2 fixed point decibels uint8_t gainReduction = 0; diff --git a/src/deluge/dsp/envelope_follower/absolute_value.cpp b/src/deluge/dsp/envelope_follower/absolute_value.cpp index 1ec862b650..a933148054 100644 --- a/src/deluge/dsp/envelope_follower/absolute_value.cpp +++ b/src/deluge/dsp/envelope_follower/absolute_value.cpp @@ -31,21 +31,19 @@ float AbsValueFollower::runEnvelope(float current, float desired, float numSampl // output range is 0-21 (2^31) // dac clipping is at 16 -StereoFloatSample AbsValueFollower::calcApproxRMS(StereoSample* buffer, uint16_t numSamples) { - StereoSample* thisSample = buffer; - StereoSample* bufferEnd = buffer + numSamples; +StereoFloatSample AbsValueFollower::calcApproxRMS(std::span buffer) { q31_t l = 0; q31_t r = 0; StereoFloatSample logMean; - do { - l += std::abs(thisSample->l); - r += std::abs(thisSample->r); - } while (++thisSample != bufferEnd); + for (StereoSample sample : buffer) { + l += std::abs(sample.l); + r += std::abs(sample.r); + } - auto ns = float(numSamples); - meanL = (float(l)) / ns; - meanR = (float(r)) / ns; + float ns = buffer.size(); + meanL = l / ns; + meanR = r / ns; // warning this is not good math but it's pretty close and way cheaper than doing it properly // good math would use a long FIR, this is a one pole IIR instead // the more samples we have, the more weight we put on the current mean to avoid response slowing down @@ -53,10 +51,10 @@ StereoFloatSample AbsValueFollower::calcApproxRMS(StereoSample* buffer, uint16_t meanL = (meanL * ns + lastMeanL) / (1 + ns); meanR = (meanR * ns + lastMeanR) / (1 + ns); - lastMeanL = runEnvelope(lastMeanL, meanL, numSamples); + lastMeanL = runEnvelope(lastMeanL, meanL, ns); logMean.l = std::log(lastMeanL + 1e-24f); - lastMeanR = runEnvelope(lastMeanR, meanR, numSamples); + lastMeanR = runEnvelope(lastMeanR, meanR, ns); logMean.r = std::log(lastMeanR + 1e-24f); return logMean; diff --git a/src/deluge/dsp/envelope_follower/absolute_value.h b/src/deluge/dsp/envelope_follower/absolute_value.h index c30ba5b298..4c5daac6e6 100644 --- a/src/deluge/dsp/envelope_follower/absolute_value.h +++ b/src/deluge/dsp/envelope_follower/absolute_value.h @@ -19,6 +19,8 @@ #include "dsp/stereo_sample.h" #include +#include + class AbsValueFollower { public: AbsValueFollower() = default; @@ -53,7 +55,7 @@ class AbsValueFollower { return releaseMS; }; - StereoFloatSample calcApproxRMS(StereoSample* buffer, uint16_t numSamples); + StereoFloatSample calcApproxRMS(std::span buffer); private: float runEnvelope(float current, float desired, float numSamples); diff --git a/src/deluge/dsp/granular/GranularProcessor.cpp b/src/deluge/dsp/granular/GranularProcessor.cpp index d58ac6c959..f0333e13ca 100644 --- a/src/deluge/dsp/granular/GranularProcessor.cpp +++ b/src/deluge/dsp/granular/GranularProcessor.cpp @@ -43,8 +43,8 @@ void GranularProcessor::setWrapsToShutdown() { grainBuffer->inUse = true; } -void GranularProcessor::processGrainFX(StereoSample* buffer, int32_t grainRate, int32_t grainMix, int32_t grainDensity, - int32_t pitchRandomness, int32_t* postFXVolume, const StereoSample* bufferEnd, +void GranularProcessor::processGrainFX(std::span buffer, int32_t grainRate, int32_t grainMix, + int32_t grainDensity, int32_t pitchRandomness, int32_t* postFXVolume, bool anySoundComingIn, float tempoBPM, q31_t reverbAmount) { if (anySoundComingIn || wrapsToShutdown >= 0) { if (anySoundComingIn) { @@ -57,23 +57,24 @@ void GranularProcessor::processGrainFX(StereoSample* buffer, int32_t grainRate, } } setupGrainFX(grainRate, grainMix, grainDensity, pitchRandomness, postFXVolume, tempoBPM); - StereoSample* currentSample = buffer; int i = 0; - do { - StereoSample grainWet = processOneGrainSample(currentSample); + for (StereoSample& sample : buffer) { + StereoSample grainWet = processOneGrainSample(sample); auto wetl = q31_mult(grainWet.l, _grainVol); auto wetr = q31_mult(grainWet.r, _grainVol); + // filter slightly - one pole at 12ish khz wetl = lpf_l.doFilter(wetl, 1 << 29); wetr = lpf_r.doFilter(wetr, 1 << 29); + // WET and DRY Vol - currentSample->l = add_saturation(q31_mult(currentSample->l, _grainDryVol), wetl); - currentSample->r = add_saturation(q31_mult(currentSample->r, _grainDryVol), wetr); + sample.l = add_saturation(q31_mult(sample.l, _grainDryVol), wetl); + sample.r = add_saturation(q31_mult(sample.r, _grainDryVol), wetr); + // adding a small amount of extra reverb covers a lot of the granular artifacts AudioEngine::feedReverbBackdoorForGrain(i, q31_mult((wetl + wetr), reverbAmount)); i += 1; - - } while (++currentSample != bufferEnd); + } if (wrapsToShutdown < 0) { grainBuffer->inUse = false; @@ -123,7 +124,7 @@ void GranularProcessor::setupGrainFX(int32_t grainRate, int32_t grainMix, int32_ _grainFeedbackVol = _grainVol >> 1; } } -StereoSample GranularProcessor::processOneGrainSample(StereoSample* currentSample) { +StereoSample GranularProcessor::processOneGrainSample(StereoSample currentSample) { if (bufferWriteIndex >= kModFXGrainBufferSize) { bufferWriteIndex = 0; wrapsToShutdown -= 1; @@ -163,9 +164,9 @@ StereoSample GranularProcessor::processOneGrainSample(StereoSample* currentSampl grains_r <<= 3; // Feedback (Below grainFeedbackVol means "grainVol >> 4") (*grainBuffer)[writeIndex].l = - multiply_accumulate_32x32_rshift32_rounded(currentSample->l, grains_l, _grainFeedbackVol); + multiply_accumulate_32x32_rshift32_rounded(currentSample.l, grains_l, _grainFeedbackVol); (*grainBuffer)[writeIndex].r = - multiply_accumulate_32x32_rshift32_rounded(currentSample->r, grains_r, _grainFeedbackVol); + multiply_accumulate_32x32_rshift32_rounded(currentSample.r, grains_r, _grainFeedbackVol); bufferWriteIndex++; return StereoSample{grains_l, grains_r}; diff --git a/src/deluge/dsp/granular/GranularProcessor.h b/src/deluge/dsp/granular/GranularProcessor.h index 0ec0082470..7fa3d651a2 100644 --- a/src/deluge/dsp/granular/GranularProcessor.h +++ b/src/deluge/dsp/granular/GranularProcessor.h @@ -24,6 +24,7 @@ #include "dsp/stereo_sample.h" #include "memory/stealable.h" #include "modulation/lfo.h" +#include class UnpatchedParamSet; @@ -53,9 +54,9 @@ class GranularProcessor { void startSkippingRendering(); /// preset is currently converted from a param to a 0-4 preset inside the grain, which is probably not great - void processGrainFX(StereoSample* buffer, int32_t grainRate, int32_t grainMix, int32_t grainDensity, - int32_t pitchRandomness, int32_t* postFXVolume, const StereoSample* bufferEnd, - bool anySoundComingIn, float tempoBPM, q31_t reverbAmount); + void processGrainFX(std::span buffer, int32_t grainRate, int32_t grainMix, int32_t grainDensity, + int32_t pitchRandomness, int32_t* postFXVolume, bool anySoundComingIn, float tempoBPM, + q31_t reverbAmount); void clearGrainFXBuffer(); void grainBufferStolen() { grainBuffer = nullptr; } @@ -63,7 +64,7 @@ class GranularProcessor { private: void setupGrainFX(int32_t grainRate, int32_t grainMix, int32_t grainDensity, int32_t pitchRandomness, int32_t* postFXVolume, float timePerInternalTick); - StereoSample processOneGrainSample(StereoSample* currentSample); + StereoSample processOneGrainSample(StereoSample currentSample); void getBuffer(); void setWrapsToShutdown(); void setupGrainsIfNeeded(int32_t writeIndex); diff --git a/src/deluge/dsp/stereo_sample.h b/src/deluge/dsp/stereo_sample.h index ebcb5df210..6afd0d59c0 100644 --- a/src/deluge/dsp/stereo_sample.h +++ b/src/deluge/dsp/stereo_sample.h @@ -23,6 +23,13 @@ #include "util/functions.h" struct StereoSample { + [[gnu::always_inline]] static constexpr StereoSample fromMono(q31_t sampleValue) { + return StereoSample{ + .l = sampleValue, + .r = sampleValue, + }; + } + inline void addMono(q31_t sampleValue) { l += sampleValue; r += sampleValue; diff --git a/src/deluge/model/clip/audio_clip.cpp b/src/deluge/model/clip/audio_clip.cpp index bc32d9131c..f3638e9bb7 100644 --- a/src/deluge/model/clip/audio_clip.cpp +++ b/src/deluge/model/clip/audio_clip.cpp @@ -38,6 +38,7 @@ #include "processing/audio_output.h" #include "processing/engines/audio_engine.h" #include "storage/storage_manager.h" +#include "util/fixedpoint.h" #include namespace params = deluge::modulation::params; @@ -543,8 +544,8 @@ int64_t AudioClip::getNumSamplesTilLoop(ModelStackWithTimelineCounter* modelStac return loopTime - AudioEngine::audioSampleTimer; } -void AudioClip::render(ModelStackWithTimelineCounter* modelStack, int32_t* outputBuffer, int32_t numSamples, - int32_t amplitude, int32_t amplitudeIncrement, int32_t pitchAdjust) { +void AudioClip::render(ModelStackWithTimelineCounter* modelStack, std::span outputBuffer, int32_t amplitude, + int32_t amplitudeIncrement, int32_t pitchAdjust) { if (!voiceSample) { return; @@ -554,7 +555,7 @@ void AudioClip::render(ModelStackWithTimelineCounter* modelStack, int32_t* outpu // First, if we're still attempting to do a "late start", see if we can do that (perhaps not if relevant audio data // hasn't loaded yet) - if (doingLateStart && ((AudioOutput*)output)->envelope.state < EnvelopeStage::FAST_RELEASE) { + if (doingLateStart && static_cast(this->output)->envelope.state < EnvelopeStage::FAST_RELEASE) { uint64_t numSamplesIn = guide.getSyncedNumSamplesIn(); LateStartAttemptStatus result = voiceSample->attemptLateSampleStart(&guide, sample, numSamplesIn); @@ -726,7 +727,7 @@ void AudioClip::render(ModelStackWithTimelineCounter* modelStack, int32_t* outpu // forgot?) It's perhaps a little bit surprising, but this even works and sounds perfect (you never hear any of // the margin) when time-stretching is happening! Down to about half speed. Below that, you hear some of the // margin. - if (((AudioOutput*)output)->envelope.state < EnvelopeStage::FAST_RELEASE) { + if (static_cast(this->output)->envelope.state < EnvelopeStage::FAST_RELEASE) { ModelStackWithNoteRow* modelStackWithNoteRow = modelStack->addNoteRow(0, nullptr); @@ -739,7 +740,7 @@ void AudioClip::render(ModelStackWithTimelineCounter* modelStack, int32_t* outpu int32_t timeTilLoop = loopTime - AudioEngine::audioSampleTimer; if (timeTilLoop < 1024) { - ((AudioOutput*)output) + static_cast(this->output) ->envelope.unconditionalRelease(EnvelopeStage::FAST_RELEASE, 8192); // Let's make it extra fast? } } @@ -760,8 +761,8 @@ void AudioClip::render(ModelStackWithTimelineCounter* modelStack, int32_t* outpu { LoopType loopingType = getLoopingType(modelStack); - stillActive = voiceSample->render(&guide, outputBuffer, numSamples, sample, sample->numChannels, loopingType, - phaseIncrement, timeStretchRatio, amplitude, amplitudeIncrement, + stillActive = voiceSample->render(&guide, outputBuffer.data(), outputBuffer.size(), sample, sample->numChannels, + loopingType, phaseIncrement, timeStretchRatio, amplitude, amplitudeIncrement, sampleControls.getInterpolationBufferSize(phaseIncrement), sampleControls.interpolationMode, 1); } diff --git a/src/deluge/model/clip/audio_clip.h b/src/deluge/model/clip/audio_clip.h index 79dbc79ab2..c9d50e1adb 100644 --- a/src/deluge/model/clip/audio_clip.h +++ b/src/deluge/model/clip/audio_clip.h @@ -39,7 +39,7 @@ class AudioClip final : public Clip { void processCurrentPos(ModelStackWithTimelineCounter* modelStack, uint32_t ticksSinceLast) override; void expectNoFurtherTicks(Song* song, bool actuallySoundChange = true) override; Error clone(ModelStackWithTimelineCounter* modelStack, bool shouldFlattenReversing = false) const override; - void render(ModelStackWithTimelineCounter* modelStack, int32_t* outputBuffer, int32_t numSamples, int32_t amplitude, + void render(ModelStackWithTimelineCounter* modelStack, std::span output, int32_t amplitude, int32_t amplitudeIncrement, int32_t pitchAdjust); void detachFromOutput(ModelStackWithTimelineCounter* modelStack, bool shouldRememberDrumName, bool shouldDeleteEmptyNoteRowsAtEndOfList = false, bool shouldRetainLinksToSounds = false, diff --git a/src/deluge/model/fx/stutterer.cpp b/src/deluge/model/fx/stutterer.cpp index 9db06d39a0..e437f31bed 100644 --- a/src/deluge/model/fx/stutterer.cpp +++ b/src/deluge/model/fx/stutterer.cpp @@ -106,17 +106,14 @@ Error Stutterer::beginStutter(void* source, ParamManagerForTimeline* paramManage return error; } -void Stutterer::processStutter(StereoSample* audio, int32_t numSamples, ParamManager* paramManager, int32_t magnitude, +void Stutterer::processStutter(std::span audio, ParamManager* paramManager, int32_t magnitude, uint32_t timePerTickInverse, bool reverse) { - StereoSample* audioEnd = audio + numSamples; - StereoSample* thisSample = audio; - int32_t rate = getStutterRate(paramManager, magnitude, timePerTickInverse); buffer.setupForRender(rate); if (status == Status::RECORDING) { - do { + for (StereoSample sample : audio) { int32_t strength1; int32_t strength2; @@ -132,15 +129,15 @@ void Stutterer::processStutter(StereoSample* audio, int32_t numSamples, ParamMan strength1 = 65536 - strength2; } - buffer.write(*thisSample, strength1, strength2); - } while (++thisSample != audioEnd); + buffer.write(sample, strength1, strength2); + } if (sizeLeftUntilRecordFinished < 0) { status = Status::PLAYING; } } else { // PLAYING - do { + for (StereoSample& sample : audio) { int32_t strength1; int32_t strength2; @@ -151,8 +148,8 @@ void Stutterer::processStutter(StereoSample* audio, int32_t numSamples, ParamMan else { buffer.moveBack(); // move backward in the buffer } - thisSample->l = buffer.current().l; - thisSample->r = buffer.current().r; + sample.l = buffer.current().l; + sample.r = buffer.current().r; } else { if (reverse == false) { @@ -170,12 +167,12 @@ void Stutterer::processStutter(StereoSample* audio, int32_t numSamples, ParamMan } StereoSample& fromDelay1 = buffer.current(); StereoSample& fromDelay2 = *nextPos; - thisSample->l = (multiply_32x32_rshift32(fromDelay1.l, strength1 << 14) - + multiply_32x32_rshift32(fromDelay2.l, strength2 << 14)) - << 2; - thisSample->r = (multiply_32x32_rshift32(fromDelay1.r, strength1 << 14) - + multiply_32x32_rshift32(fromDelay2.r, strength2 << 14)) - << 2; + sample.l = (multiply_32x32_rshift32(fromDelay1.l, strength1 << 14) + + multiply_32x32_rshift32(fromDelay2.l, strength2 << 14)) + << 2; + sample.r = (multiply_32x32_rshift32(fromDelay1.r, strength1 << 14) + + multiply_32x32_rshift32(fromDelay2.r, strength2 << 14)) + << 2; } else { StereoSample* prevPos = &buffer.current() - 1; @@ -184,15 +181,15 @@ void Stutterer::processStutter(StereoSample* audio, int32_t numSamples, ParamMan } StereoSample& fromDelay1 = buffer.current(); StereoSample& fromDelay2 = *prevPos; - thisSample->l = (multiply_32x32_rshift32(fromDelay1.l, strength1 << 14) - + multiply_32x32_rshift32(fromDelay2.l, strength2 << 14)) - << 2; - thisSample->r = (multiply_32x32_rshift32(fromDelay1.r, strength1 << 14) - + multiply_32x32_rshift32(fromDelay2.r, strength2 << 14)) - << 2; + sample.l = (multiply_32x32_rshift32(fromDelay1.l, strength1 << 14) + + multiply_32x32_rshift32(fromDelay2.l, strength2 << 14)) + << 2; + sample.r = (multiply_32x32_rshift32(fromDelay1.r, strength1 << 14) + + multiply_32x32_rshift32(fromDelay2.r, strength2 << 14)) + << 2; } } - } while (++thisSample != audioEnd); + } } } diff --git a/src/deluge/model/fx/stutterer.h b/src/deluge/model/fx/stutterer.h index 5634a1df6d..5ea8e4887c 100644 --- a/src/deluge/model/fx/stutterer.h +++ b/src/deluge/model/fx/stutterer.h @@ -19,6 +19,7 @@ #include "dsp/delay/delay_buffer.h" #include +#include class ParamManagerForTimeline; class ParamManager; @@ -33,7 +34,7 @@ class Stutterer { // on currentSong and playbackhandler... [[nodiscard]] Error beginStutter(void* source, ParamManagerForTimeline* paramManager, bool quantize, int32_t magnitude, uint32_t timePerTickInverse); - void processStutter(StereoSample* buffer, int32_t numSamples, ParamManager* paramManager, int32_t magnitude, + void processStutter(std::span buffer, ParamManager* paramManager, int32_t magnitude, uint32_t timePerTickInverse, bool reverse); void endStutter(ParamManagerForTimeline* paramManager = nullptr); diff --git a/src/deluge/model/global_effectable/global_effectable.cpp b/src/deluge/model/global_effectable/global_effectable.cpp index 045e327234..4fd8695391 100644 --- a/src/deluge/model/global_effectable/global_effectable.cpp +++ b/src/deluge/model/global_effectable/global_effectable.cpp @@ -17,6 +17,7 @@ #include "model/global_effectable/global_effectable.h" #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "gui/l10n/l10n.h" #include "gui/views/performance_view.h" #include "gui/views/view.h" @@ -778,8 +779,8 @@ void GlobalEffectable::setupFilterSetConfig(int32_t* postFXVolume, ParamManager* hpfModeForRender, hpfMorph, *postFXVolume, filterRoute, false, NULL); } -[[gnu::hot]] void GlobalEffectable::processFilters(StereoSample* buffer, int32_t numSamples) { - filterSet.renderLongStereo(&buffer->l, &(buffer + numSamples)->l); +[[gnu::hot]] void GlobalEffectable::processFilters(std::span buffer) { + filterSet.renderLongStereo(&buffer.data()->l, &(buffer.data() + buffer.size())->l); } void GlobalEffectable::writeAttributesToFile(Serializer& writer, bool writeAutomation) { @@ -1128,13 +1129,9 @@ Delay::State GlobalEffectable::createDelayWorkingState(ParamManager& paramManage return delayWorkingState; } -void GlobalEffectable::processFXForGlobalEffectable(StereoSample* inputBuffer, int32_t numSamples, - int32_t* postFXVolume, ParamManager* paramManager, - const Delay::State& delayWorkingState, bool anySoundComingIn, - q31_t verbAmount) { - - StereoSample* inputBufferEnd = inputBuffer + numSamples; - +void GlobalEffectable::processFXForGlobalEffectable(std::span buffer, int32_t* postFXVolume, + ParamManager* paramManager, const Delay::State& delayWorkingState, + bool anySoundComingIn, q31_t verbAmount) { UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); int32_t modFXRate = @@ -1166,8 +1163,8 @@ void GlobalEffectable::processFXForGlobalEffectable(StereoSample* inputBuffer, i disableGrain(); } - processFX(inputBuffer, numSamples, modFXTypeNow, modFXRate, modFXDepth, delayWorkingState, postFXVolume, - paramManager, anySoundComingIn, verbAmount); + processFX(buffer, modFXTypeNow, modFXRate, modFXDepth, delayWorkingState, postFXVolume, paramManager, + anySoundComingIn, verbAmount); } namespace modfx { diff --git a/src/deluge/model/global_effectable/global_effectable.h b/src/deluge/model/global_effectable/global_effectable.h index 2ba39dc2e0..695691417c 100644 --- a/src/deluge/model/global_effectable/global_effectable.h +++ b/src/deluge/model/global_effectable/global_effectable.h @@ -37,11 +37,10 @@ class GlobalEffectable : public ModControllableAudio { ModelStackWithAutoParam* getParamFromModEncoder(int32_t whichModEncoder, ModelStackWithThreeMainThings* modelStack, bool allowCreation = true) override; void setupFilterSetConfig(int32_t* postFXVolume, ParamManager* paramManager); - void processFilters(StereoSample* buffer, int32_t numSamples); + void processFilters(std::span buffer); void compensateVolumeForResonance(ParamManagerForTimeline* paramManager); - void processFXForGlobalEffectable(StereoSample* inputBuffer, int32_t numSamples, int32_t* postFXVolume, - ParamManager* paramManager, const Delay::State& delayWorkingState, - bool anySoundComingIn, q31_t verbAmount); + void processFXForGlobalEffectable(std::span buffer, int32_t* postFXVolume, ParamManager* paramManager, + const Delay::State& delayWorkingState, bool anySoundComingIn, q31_t verbAmount); void writeAttributesToFile(Serializer& writer, bool writeToFile); void writeTagsToFile(Serializer& writer, ParamManager* paramManager, bool writeToFile); diff --git a/src/deluge/model/global_effectable/global_effectable_for_clip.cpp b/src/deluge/model/global_effectable/global_effectable_for_clip.cpp index 82f6ce2d0f..887646b953 100644 --- a/src/deluge/model/global_effectable/global_effectable_for_clip.cpp +++ b/src/deluge/model/global_effectable/global_effectable_for_clip.cpp @@ -16,12 +16,15 @@ */ #include "model/global_effectable/global_effectable_for_clip.h" +#include "definitions.h" #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "gui/l10n/l10n.h" #include "gui/views/view.h" #include "model/action/action.h" #include "model/action/action_logger.h" #include "processing/engines/audio_engine.h" +#include #include // #include #include "hid/buttons.h" @@ -39,17 +42,13 @@ extern "C" { namespace params = deluge::modulation::params; GlobalEffectableForClip::GlobalEffectableForClip() { - postReverbVolumeLastTime = paramNeutralValues[params::GLOBAL_VOLUME_POST_REVERB_SEND]; - - lastSaturationTanHWorkingValue[0] = 2147483648; - lastSaturationTanHWorkingValue[1] = 2147483648; - renderedLastTime = false; + this->postReverbVolumeLastTime = paramNeutralValues[params::GLOBAL_VOLUME_POST_REVERB_SEND]; } // Beware - unlike usual, modelStack might have a NULL timelineCounter. [[gnu::hot]] void GlobalEffectableForClip::renderOutput(ModelStackWithTimelineCounter* modelStack, - ParamManager* paramManagerForClip, StereoSample* outputBuffer, - int32_t numSamples, int32_t* reverbBuffer, + ParamManager* paramManagerForClip, + std::span output, int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive, OutputType outputType, SampleRecorder* recorder) { @@ -66,12 +65,8 @@ GlobalEffectableForClip::GlobalEffectableForClip() { // Make it a bit bigger so that default filter resonance doesn't reduce volume overall. // Unfortunately when I first implemented this for Kits, I just fudged a number which didn't give the 100% accuracy // that I need for AudioOutputs, and I now have to maintain both for backwards compatibility - if (outputType == OutputType::AUDIO) { - volumePostFX += multiply_32x32_rshift32_rounded(volumeAdjustment, 471633397); - } - else { - volumePostFX += (volumeAdjustment >> 2); - } + volumePostFX += (outputType == OutputType::AUDIO) ? multiply_32x32_rshift32_rounded(volumeAdjustment, 471633397) + : (volumeAdjustment >> 2); int32_t reverbAmountAdjustForDrums = multiply_32x32_rshift32_rounded(reverbAmountAdjust, volumeAdjustment) << 5; @@ -95,80 +90,79 @@ GlobalEffectableForClip::GlobalEffectableForClip() { // Render sidechain int32_t sidechainVolumeParam = unpatchedParams->getValue(params::UNPATCHED_SIDECHAIN_VOLUME); int32_t postReverbVolume = paramNeutralValues[params::GLOBAL_VOLUME_POST_REVERB_SEND]; - if (sidechainVolumeParam != -2147483648) { + if (sidechainVolumeParam != std::numeric_limits::min()) { if (sideChainHitPending != 0) { sidechain.registerHit(sideChainHitPending); } int32_t sidechainOutput = - sidechain.render(numSamples, unpatchedParams->getValue(params::UNPATCHED_SIDECHAIN_SHAPE)); + sidechain.render(output.size(), unpatchedParams->getValue(params::UNPATCHED_SIDECHAIN_SHAPE)); int32_t positivePatchedValue = multiply_32x32_rshift32(sidechainOutput, getSidechainVolumeAmountAsPatchCableDepth(paramManagerForClip)) + 536870912; - postReverbVolume = (positivePatchedValue >> 15) - * (positivePatchedValue - >> 16); // This is tied to getParamNeutralValue(params::GLOBAL_VOLUME_POST_REVERB_SEND) - // returning 134217728 + + // This is tied to getParamNeutralValue(params::GLOBAL_VOLUME_POST_REVERB_SEND) returning 134217728 + postReverbVolume = (positivePatchedValue >> 15) * (positivePatchedValue >> 16); } - q31_t compThreshold = unpatchedParams->getValue(params::UNPATCHED_COMPRESSOR_THRESHOLD); + + const q31_t compThreshold = unpatchedParams->getValue(params::UNPATCHED_COMPRESSOR_THRESHOLD); compressor.setThreshold(compThreshold); - StereoSample globalEffectableBuffer[SSI_TX_BUFFER_NUM_SAMPLES] __attribute__((aligned(CACHE_LINE_SIZE))); - memset(globalEffectableBuffer, 0, sizeof(StereoSample) * numSamples); + alignas(CACHE_LINE_SIZE) StereoSample global_effectable_memory[SSI_TX_BUFFER_NUM_SAMPLES]; + memset(global_effectable_memory, 0, sizeof(StereoSample) * output.size()); + std::span global_effectable_audio{global_effectable_memory, output.size()}; // Render actual Drums / AudioClip renderedLastTime = renderGlobalEffectableForClip( - modelStack, globalEffectableBuffer, nullptr, numSamples, reverbBuffer, reverbAmountAdjustForDrums, - sideChainHitPending, shouldLimitDelayFeedback, isClipActive, pitchAdjust, 134217728, 134217728); + modelStack, global_effectable_audio, nullptr, reverbBuffer, reverbAmountAdjustForDrums, sideChainHitPending, + shouldLimitDelayFeedback, isClipActive, pitchAdjust, 134217728, 134217728); // Render saturation - if (clippingAmount) { - StereoSample const* const bufferEnd = globalEffectableBuffer + numSamples; - - StereoSample* __restrict__ currentSample = globalEffectableBuffer; - do { - saturate(¤tSample->l, &lastSaturationTanHWorkingValue[0]); - saturate(¤tSample->r, &lastSaturationTanHWorkingValue[1]); - } while (++currentSample != bufferEnd); + if (clippingAmount != 0u) { + for (StereoSample& sample : global_effectable_audio) { + sample.l = saturate(sample.l, &lastSaturationTanHWorkingValue[0]); + sample.r = saturate(sample.r, &lastSaturationTanHWorkingValue[1]); + } } // Render filters - processFilters(globalEffectableBuffer, numSamples); + processFilters(global_effectable_audio); // Render FX - processSRRAndBitcrushing(globalEffectableBuffer, numSamples, &volumePostFX, paramManagerForClip); - processFXForGlobalEffectable(globalEffectableBuffer, numSamples, &volumePostFX, paramManagerForClip, - delayWorkingState, renderedLastTime, reverbSendAmount); - processStutter(globalEffectableBuffer, numSamples, paramManagerForClip); + processSRRAndBitcrushing(global_effectable_audio, &volumePostFX, paramManagerForClip); + processFXForGlobalEffectable(global_effectable_audio, &volumePostFX, paramManagerForClip, delayWorkingState, + renderedLastTime, reverbSendAmount); + processStutter(global_effectable_audio, paramManagerForClip); // record before pan/compression/volume to keep volumes consistent - if (recorder && recorder->status < RecorderStatus::FINISHED_CAPTURING_BUT_STILL_WRITING) { + if (recorder != nullptr && recorder->status < RecorderStatus::FINISHED_CAPTURING_BUT_STILL_WRITING) { // we need to double it because for reasons I don't understand audio clips max volume is half the sample volume - recorder->feedAudio((int32_t*)globalEffectableBuffer, numSamples, true, 2); + recorder->feedAudio(global_effectable_audio, true, 2); } - processReverbSendAndVolume(globalEffectableBuffer, numSamples, reverbBuffer, volumePostFX, postReverbVolume, - reverbSendAmount, pan, true); + processReverbSendAndVolume(global_effectable_audio, reverbBuffer, volumePostFX, postReverbVolume, reverbSendAmount, + pan, true); + if (compThreshold > 0) { - compressor.renderVolNeutral(globalEffectableBuffer, numSamples, volumePostFX); + compressor.renderVolNeutral(global_effectable_audio, volumePostFX); } else { compressor.reset(); } - addAudio(globalEffectableBuffer, outputBuffer, numSamples); + // Add the global effectable data to the output + std::ranges::transform(global_effectable_audio, output, output.begin(), std::plus{}); postReverbVolumeLastTime = postReverbVolume; - if (playbackHandler.isEitherClockActive() && !playbackHandler.ticksLeftInCountIn && isClipActive) { - const bool result = - params::kMaxNumUnpatchedParams > 32 - ? paramManagerForClip->getUnpatchedParamSetSummary()->whichParamsAreInterpolating[0] - || paramManagerForClip->getUnpatchedParamSetSummary()->whichParamsAreInterpolating[1] - : paramManagerForClip->getUnpatchedParamSetSummary()->whichParamsAreInterpolating[0]; + if (playbackHandler.isEitherClockActive() && (playbackHandler.ticksLeftInCountIn == 0) && isClipActive) { + auto& interpolating_params = paramManagerForClip->getUnpatchedParamSetSummary()->whichParamsAreInterpolating; + const bool result = (params::kMaxNumUnpatchedParams > 32) + ? (interpolating_params[0] != 0u || interpolating_params[1] != 0u) + : (interpolating_params[0] != 0u); if (result) { ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStack->addOtherTwoThingsButNoNoteRow(this, paramManagerForClip); - paramManagerForClip->toForTimeline()->tickSamples(numSamples, modelStackWithThreeMainThings); + paramManagerForClip->toForTimeline()->tickSamples(output.size(), modelStackWithThreeMainThings); } } } @@ -198,7 +192,7 @@ void GlobalEffectableForClip::modButtonAction(uint8_t whichModButton, bool on, P return; } - return GlobalEffectable::modButtonAction(whichModButton, on, paramManager); + GlobalEffectable::modButtonAction(whichModButton, on, paramManager); } bool GlobalEffectableForClip::modEncoderButtonAction(uint8_t whichModEncoder, bool on, diff --git a/src/deluge/model/global_effectable/global_effectable_for_clip.h b/src/deluge/model/global_effectable/global_effectable_for_clip.h index 862a6bce15..a95e592054 100644 --- a/src/deluge/model/global_effectable/global_effectable_for_clip.h +++ b/src/deluge/model/global_effectable/global_effectable_for_clip.h @@ -18,6 +18,7 @@ #pragma once #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "model/global_effectable/global_effectable.h" #include "model/sample/sample_recorder.h" @@ -42,33 +43,34 @@ class GlobalEffectableForClip : public GlobalEffectable { GlobalEffectableForClip** globalEffectableWithMostReverb, int32_t* highestReverbAmountFound); - inline void saturate(int32_t* data, uint32_t* workingValue) { + [[gnu::always_inline]] q31_t saturate(q31_t data, uint32_t* workingValue) { // Clipping - if (clippingAmount) { + if (clippingAmount != 0u) { int32_t shiftAmount = (clippingAmount >= 3) ? (clippingAmount - 3) : 0; //*data = getTanHUnknown(*data, 5 + clippingAmount) << (shiftAmount); - *data = getTanHAntialiased(*data, workingValue, 3 + clippingAmount) << (shiftAmount); + return getTanHAntialiased(data, workingValue, 3 + clippingAmount) << (shiftAmount); } + return data; } - uint32_t lastSaturationTanHWorkingValue[2]; + std::array lastSaturationTanHWorkingValue = {2147483648u, 2147483648u}; protected: int32_t getParameterFromKnob(int32_t whichModEncoder) final; void renderOutput(ModelStackWithTimelineCounter* modelStack, ParamManager* paramManagerForClip, - StereoSample* outputBuffer, int32_t numSamples, int32_t* reverbBuffer, int32_t reverbAmountAdjust, + std::span output, int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive, OutputType outputType, SampleRecorder* recorder); virtual bool renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, - StereoSample* globalEffectableBuffer, int32_t* bufferToTransferTo, - int32_t numSamples, int32_t* reverbBuffer, int32_t reverbAmountAdjust, - int32_t sideChainHitPending, bool shouldLimitDelayFeedback, - bool isClipActive, int32_t pitchAdjust, int32_t amplitudeAtStart, - int32_t amplitudeAtEnd) = 0; + std::span globalEffectableBuffer, + int32_t* bufferToTransferTo, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, + bool shouldLimitDelayFeedback, bool isClipActive, int32_t pitchAdjust, + int32_t amplitudeAtStart, int32_t amplitudeAtEnd) = 0; virtual bool willRenderAsOneChannelOnlyWhichWillNeedCopying() { return false; } private: - bool renderedLastTime; + bool renderedLastTime = false; }; diff --git a/src/deluge/model/instrument/kit.cpp b/src/deluge/model/instrument/kit.cpp index 49534db6ac..9bdc2aae46 100644 --- a/src/deluge/model/instrument/kit.cpp +++ b/src/deluge/model/instrument/kit.cpp @@ -17,6 +17,7 @@ #include "model/instrument/kit.h" #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "gui/ui/sound_editor.h" #include "gui/ui/ui.h" #include "gui/views/automation_view.h" @@ -518,9 +519,9 @@ void Kit::cutAllSound() { } // Beware - unlike usual, modelStack, a ModelStackWithThreeMainThings*, might have a NULL timelineCounter -bool Kit::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, StereoSample* globalEffectableBuffer, - int32_t* bufferToTransferTo, int32_t numSamples, int32_t* reverbBuffer, - int32_t reverbAmountAdjust, int32_t sideChainHitPending, +bool Kit::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, + std::span globalEffectableBuffer, int32_t* bufferToTransferTo, + int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive, int32_t pitchAdjust, int32_t amplitudeAtStart, int32_t amplitudeAtEnd) { bool rendered = false; @@ -562,8 +563,8 @@ bool Kit::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStac ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStack->addNoteRow(noteRowIndex, thisNoteRow)->addOtherTwoThings(soundDrum, drumParamManager); - soundDrum->render(modelStackWithThreeMainThings, globalEffectableBuffer, numSamples, reverbBuffer, - sideChainHitPending, reverbAmountAdjust, shouldLimitDelayFeedback, pitchAdjust, + soundDrum->render(modelStackWithThreeMainThings, globalEffectableBuffer, reverbBuffer, sideChainHitPending, + reverbAmountAdjust, shouldLimitDelayFeedback, pitchAdjust, nullptr); // According to our volume, we tell Drums to send less reverb rendered = true; } @@ -597,7 +598,7 @@ bool Kit::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStac ModelStackWithThreeMainThings* modelStackWithThreeMainThings = modelStack->addNoteRow(i, thisNoteRow) ->addOtherTwoThings((SoundDrum*)thisNoteRow->drum, &thisNoteRow->paramManager); - thisNoteRow->paramManager.tickSamples(numSamples, modelStackWithThreeMainThings); + thisNoteRow->paramManager.tickSamples(globalEffectableBuffer.size(), modelStackWithThreeMainThings); continue; } @@ -652,18 +653,18 @@ bool Kit::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStac return rendered; } -void Kit::renderOutput(ModelStack* modelStack, StereoSample* outputBuffer, StereoSample* outputBufferEnd, - int32_t numSamples, int32_t* reverbBuffer, int32_t reverbAmountAdjust, - int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive) { +void Kit::renderOutput(ModelStack* modelStack, std::span output, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) { ParamManager* paramManager = getParamManager(modelStack->song); ModelStackWithTimelineCounter* modelStackWithTimelineCounter = modelStack->addTimelineCounter(activeClip); // Beware - modelStackWithThreeMainThings might have a NULL timelineCounter - GlobalEffectableForClip::renderOutput(modelStackWithTimelineCounter, paramManager, outputBuffer, numSamples, - reverbBuffer, reverbAmountAdjust, sideChainHitPending, - shouldLimitDelayFeedback, isClipActive, OutputType::KIT, recorder); + GlobalEffectableForClip::renderOutput(modelStackWithTimelineCounter, paramManager, output, reverbBuffer, + reverbAmountAdjust, sideChainHitPending, shouldLimitDelayFeedback, + isClipActive, OutputType::KIT, recorder); for (int32_t i = 0; i < ((InstrumentClip*)activeClip)->noteRows.getNumElements(); i++) { NoteRow* thisNoteRow = ((InstrumentClip*)activeClip)->noteRows.getElement(i); @@ -684,7 +685,7 @@ void Kit::renderOutput(ModelStack* modelStack, StereoSample* outputBuffer, Stere cableToExpParamShortcut(nonAudioDrum->arpSettings.rate))); ArpReturnInstruction instruction; - nonAudioDrum->arpeggiator.render(&nonAudioDrum->arpSettings, &instruction, numSamples, gateThreshold, + nonAudioDrum->arpeggiator.render(&nonAudioDrum->arpSettings, &instruction, output.size(), gateThreshold, phaseIncrement); for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { if (instruction.noteCodeOffPostArp[n] == ARP_NOTE_NONE) { diff --git a/src/deluge/model/instrument/kit.h b/src/deluge/model/instrument/kit.h index a07290d34d..fc23bfff59 100644 --- a/src/deluge/model/instrument/kit.h +++ b/src/deluge/model/instrument/kit.h @@ -18,6 +18,7 @@ #pragma once #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "model/global_effectable/global_effectable_for_clip.h" #include "model/instrument/instrument.h" class InstrumentClip; @@ -45,9 +46,9 @@ class Kit final : public Instrument, public GlobalEffectableForClip { Error loadAllAudioFiles(bool mayActuallyReadFiles) override; void cutAllSound() override; - void renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, int32_t numSamples, - int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, - bool shouldLimitDelayFeedback, bool isClipActive) override; + void renderOutput(ModelStack* modelStack, std::span buffer, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) override; void offerReceivedCC(ModelStackWithTimelineCounter* modelStackWithTimelineCounter, MIDICable& cable, uint8_t channel, uint8_t ccNumber, uint8_t value, bool* doingMidiThru) override; @@ -129,9 +130,9 @@ class Kit final : public Instrument, public GlobalEffectableForClip { void offerBendRangeUpdate(ModelStack* modelStack, MIDICable& cable, int32_t channelOrZone, int32_t whichBendRange, int32_t bendSemitones) override; - bool renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, StereoSample* globalEffectableBuffer, - int32_t* bufferToTransferTo, int32_t numSamples, int32_t* reverbBuffer, - int32_t reverbAmountAdjust, int32_t sideChainHitPending, + bool renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, + std::span globalEffectableBuffer, int32_t* bufferToTransferTo, + int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive, int32_t pitchAdjust, int32_t amplitudeAtStart, int32_t amplitudeAtEnd) override; diff --git a/src/deluge/model/instrument/non_audio_instrument.cpp b/src/deluge/model/instrument/non_audio_instrument.cpp index 5d66fb0439..f0adaf4cb3 100644 --- a/src/deluge/model/instrument/non_audio_instrument.cpp +++ b/src/deluge/model/instrument/non_audio_instrument.cpp @@ -17,6 +17,7 @@ #include "model/instrument/non_audio_instrument.h" #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "model/clip/instrument_clip.h" #include "model/model_stack.h" #include "modulation/arpeggiator.h" @@ -26,9 +27,9 @@ #include "util/functions.h" #include -void NonAudioInstrument::renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, - int32_t numSamples, int32_t* reverbBuffer, int32_t reverbAmountAdjust, - int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive) { +void NonAudioInstrument::renderOutput(ModelStack* modelStack, std::span output, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, + bool shouldLimitDelayFeedback, bool isClipActive) { // MIDI / CV arpeggiator if (activeClip) { @@ -42,7 +43,7 @@ void NonAudioInstrument::renderOutput(ModelStack* modelStack, StereoSample* star ArpReturnInstruction instruction; - arpeggiator.render(&activeInstrumentClip->arpSettings, &instruction, numSamples, gateThreshold, + arpeggiator.render(&activeInstrumentClip->arpSettings, &instruction, output.size(), gateThreshold, phaseIncrement); for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { diff --git a/src/deluge/model/instrument/non_audio_instrument.h b/src/deluge/model/instrument/non_audio_instrument.h index 672df7e661..588d5e6512 100644 --- a/src/deluge/model/instrument/non_audio_instrument.h +++ b/src/deluge/model/instrument/non_audio_instrument.h @@ -31,9 +31,9 @@ class NonAudioInstrument : public MelodicInstrument, public ModControllable { cachedBendRanges[BEND_RANGE_FINGER_LEVEL] = FlashStorage::defaultBendRange[BEND_RANGE_FINGER_LEVEL]; } - void renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, int32_t numSamples, - int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, - bool shouldLimitDelayFeedback, bool isClipActive) override; + void renderOutput(ModelStack* modelStack, std::span buffer, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) override; void sendNote(ModelStackWithThreeMainThings* modelStack, bool isOn, int32_t noteCode, int16_t const* mpeValues, int32_t fromMIDIChannel = 16, uint8_t velocity = 64, uint32_t sampleSyncLength = 0, int32_t ticksLate = 0, uint32_t samplesLate = 0) override; diff --git a/src/deluge/model/mod_controllable/ModFXProcessor.cpp b/src/deluge/model/mod_controllable/ModFXProcessor.cpp index 7b86bb92ae..902749a29f 100644 --- a/src/deluge/model/mod_controllable/ModFXProcessor.cpp +++ b/src/deluge/model/mod_controllable/ModFXProcessor.cpp @@ -16,14 +16,18 @@ */ #include "ModFXProcessor.h" +#include "definitions_cxx.hpp" #include "mem_functions.h" #include "memory/general_memory_allocator.h" #include "modulation/params/param_set.h" #include "processing/engines/audio_engine.h" +#include "util/comparison.h" +#include + /// NOT GRAIN! - this only does the comb filter based mod fx -void ModFXProcessor::processModFX(StereoSample* buffer, const ModFXType& modFXType, int32_t modFXRate, +void ModFXProcessor::processModFX(std::span buffer, const ModFXType& modFXType, int32_t modFXRate, int32_t modFXDepth, int32_t* postFXVolume, UnpatchedParamSet* unpatchedParams, - const StereoSample* bufferEnd, bool anySoundComingIn) { + bool anySoundComingIn) { if (modFXType != ModFXType::NONE) { @@ -48,36 +52,31 @@ void ModFXProcessor::processModFX(StereoSample* buffer, const ModFXType& modFXTy case ModFXType::NONE: break; case ModFXType::FLANGER: - processModFXBuffer(buffer, modFXRate, modFXDepth, bufferEnd, modFXLFOWaveType, - modFXDelayOffset, thisModFXDelayDepth, feedback, - AudioEngine::renderInStereo); + processModFXBuffer(buffer, modFXRate, modFXDepth, modFXLFOWaveType, modFXDelayOffset, + thisModFXDelayDepth, feedback, AudioEngine::renderInStereo); break; case ModFXType::CHORUS: - processModFXBuffer(buffer, modFXRate, modFXDepth, bufferEnd, modFXLFOWaveType, - modFXDelayOffset, thisModFXDelayDepth, feedback, - AudioEngine::renderInStereo); + processModFXBuffer(buffer, modFXRate, modFXDepth, modFXLFOWaveType, modFXDelayOffset, + thisModFXDelayDepth, feedback, AudioEngine::renderInStereo); break; case ModFXType::PHASER: - processModFXBuffer(buffer, modFXRate, modFXDepth, bufferEnd, modFXLFOWaveType, - modFXDelayOffset, thisModFXDelayDepth, feedback, - AudioEngine::renderInStereo); + processModFXBuffer(buffer, modFXRate, modFXDepth, modFXLFOWaveType, modFXDelayOffset, + thisModFXDelayDepth, feedback, AudioEngine::renderInStereo); break; case ModFXType::CHORUS_STEREO: - processModFXBuffer(buffer, modFXRate, modFXDepth, bufferEnd, modFXLFOWaveType, + processModFXBuffer(buffer, modFXRate, modFXDepth, modFXLFOWaveType, modFXDelayOffset, thisModFXDelayDepth, feedback, AudioEngine::renderInStereo); break; case ModFXType::WARBLE: - processModFXBuffer(buffer, modFXRate, modFXDepth, bufferEnd, modFXLFOWaveType, - modFXDelayOffset, thisModFXDelayDepth, feedback, - AudioEngine::renderInStereo); + processModFXBuffer(buffer, modFXRate, modFXDepth, modFXLFOWaveType, modFXDelayOffset, + thisModFXDelayDepth, feedback, AudioEngine::renderInStereo); break; case ModFXType::GRAIN: break; case ModFXType::DIMENSION: - processModFXBuffer(buffer, modFXRate, modFXDepth, bufferEnd, modFXLFOWaveType, - modFXDelayOffset, thisModFXDelayDepth, feedback, - AudioEngine::renderInStereo); + processModFXBuffer(buffer, modFXRate, modFXDepth, modFXLFOWaveType, modFXDelayOffset, + thisModFXDelayDepth, feedback, AudioEngine::renderInStereo); break; } } @@ -138,59 +137,48 @@ void ModFXProcessor::setupModFXWFeedback(const ModFXType& modFXType, int32_t mod } } template -void ModFXProcessor::processModFXBuffer(StereoSample* buffer, int32_t modFXRate, int32_t modFXDepth, - const StereoSample* bufferEnd, LFOType& modFXLFOWaveType, - int32_t modFXDelayOffset, int32_t thisModFXDelayDepth, int32_t feedback, - bool stereo) { - StereoSample* currentSample = buffer; +void ModFXProcessor::processModFXBuffer(std::span buffer, int32_t modFXRate, int32_t modFXDepth, + LFOType& modFXLFOWaveType, int32_t modFXDelayOffset, + int32_t thisModFXDelayDepth, int32_t feedback, bool stereo) { if constexpr (modFXType == ModFXType::PHASER) { - do { - int32_t lfoOutput = modFXLFO.render(1, modFXLFOWaveType, modFXRate); - processOnePhaserSample(modFXDepth, feedback, currentSample, lfoOutput); - - } while (++currentSample != bufferEnd); + for (StereoSample& sample : buffer) { + int32_t lfo = modFXLFO.render(1, modFXLFOWaveType, modFXRate); + sample = processOnePhaserSample(sample, modFXDepth, feedback, lfo); + } + return; } - else if (stereo) { - do { - int32_t lfoOutput; - int32_t lfo2Output; - processModLFOs(modFXRate, modFXLFOWaveType, lfoOutput, lfo2Output); - processOneModFXSample(modFXDelayOffset, thisModFXDelayDepth, feedback, currentSample, - lfoOutput, lfo2Output); - - } while (++currentSample != bufferEnd); - } - else { - do { - int32_t lfoOutput; - int32_t lfo2Output; - processModLFOs(modFXRate, modFXLFOWaveType, lfoOutput, lfo2Output); - processOneModFXSample(modFXDelayOffset, thisModFXDelayDepth, feedback, currentSample, - lfoOutput, -lfoOutput); - - } while (++currentSample != bufferEnd); + for (StereoSample& sample : buffer) { + auto [lfo1, lfo2] = processModLFOs(modFXRate, modFXLFOWaveType); + sample = stereo ? processOneModFXSample(sample, modFXDelayOffset, thisModFXDelayDepth, + feedback, lfo1, lfo2) + : processOneModFXSample(sample, modFXDelayOffset, thisModFXDelayDepth, + feedback, lfo1, -lfo1); } } + template -void ModFXProcessor::processModLFOs(int32_t modFXRate, LFOType& modFXLFOWaveType, int32_t& lfoOutput, - int32_t& lfo2Output) { - lfoOutput = modFXLFO.render(1, modFXLFOWaveType, modFXRate); +std::pair ModFXProcessor::processModLFOs(int32_t modFXRate, LFOType modFXLFOWaveType) { + int32_t lfo1 = modFXLFO.render(1, modFXLFOWaveType, modFXRate); + int32_t lfo2 = 0; // anymore and they get audibly out of sync, this just sounds wobblier constexpr q31_t width = 0.97 * ONE_Q31; if constexpr (modFXType == ModFXType::WARBLE) { // this needs a second lfo because it's a random process - we can't flip it to make a second sample but // these will always be different anyway - lfo2Output = modFXLFOStereo.render(1, modFXLFOWaveType, multiply_32x32_rshift32(modFXRate, width) << 1); + lfo2 = modFXLFOStereo.render(1, modFXLFOWaveType, multiply_32x32_rshift32(modFXRate, width) << 1); } else { - lfo2Output = -lfoOutput; + lfo2 = -lfo1; } + return {lfo1, lfo2}; } + template -void ModFXProcessor::processOneModFXSample(int32_t modFXDelayOffset, int32_t thisModFXDelayDepth, int32_t feedback, - StereoSample* currentSample, int32_t lfoOutput, int32_t lfo2Output) { +StereoSample ModFXProcessor::processOneModFXSample(StereoSample sample, int32_t modFXDelayOffset, + int32_t thisModFXDelayDepth, int32_t feedback, int32_t lfoOutput, + int32_t lfo2Output) { int32_t delayTime = multiply_32x32_rshift32(lfoOutput, thisModFXDelayDepth) + modFXDelayOffset; int32_t strength2 = (delayTime & 65535) << 15; @@ -219,44 +207,46 @@ void ModFXProcessor::processOneModFXSample(int32_t modFXDelayOffset, int32_t thi // feedback also controls the mix? Weird but ok, I guess it makes it work on one knob if constexpr (modFXType == ModFXType::FLANGER) { modFXOutputL = multiply_32x32_rshift32_rounded(modFXOutputL, feedback) << 2; - modFXBuffer[modFXBufferWriteIndex].l = modFXOutputL + currentSample->l; // Feedback + modFXBuffer[modFXBufferWriteIndex].l = modFXOutputL + sample.l; // Feedback modFXOutputR = multiply_32x32_rshift32_rounded(modFXOutputR, feedback) << 2; - modFXBuffer[modFXBufferWriteIndex].r = modFXOutputR + currentSample->r; // Feedback + modFXBuffer[modFXBufferWriteIndex].r = modFXOutputR + sample.r; // Feedback } else if constexpr (modFXType == ModFXType::WARBLE) { auto fback = multiply_32x32_rshift32_rounded(modFXOutputL, feedback); - modFXBuffer[modFXBufferWriteIndex].l = fback + currentSample->l; // Feedback + modFXBuffer[modFXBufferWriteIndex].l = fback + sample.l; // Feedback fback = multiply_32x32_rshift32_rounded(modFXOutputR, feedback); - modFXBuffer[modFXBufferWriteIndex].r = fback + currentSample->r; // Feedback + modFXBuffer[modFXBufferWriteIndex].r = fback + sample.r; // Feedback modFXOutputL <<= 1; modFXOutputR <<= 1; } else { // Chorus, Dimension modFXOutputL <<= 1; - modFXBuffer[modFXBufferWriteIndex].l = currentSample->l; // Feedback + modFXBuffer[modFXBufferWriteIndex].l = sample.l; // Feedback modFXOutputR <<= 1; - modFXBuffer[modFXBufferWriteIndex].r = currentSample->r; // Feedback + modFXBuffer[modFXBufferWriteIndex].r = sample.r; // Feedback } if constexpr (modFXType == ModFXType::DIMENSION || modFXType == ModFXType::WARBLE) { - currentSample->l = modFXOutputL << 1; - currentSample->r = modFXOutputR << 1; + sample.l = modFXOutputL << 1; + sample.r = modFXOutputR << 1; } else { - currentSample->l += modFXOutputL; - currentSample->r += modFXOutputR; + sample.l += modFXOutputL; + sample.r += modFXOutputR; } modFXBufferWriteIndex = (modFXBufferWriteIndex + 1) & kModFXBufferIndexMask; + return sample; } -void ModFXProcessor::processOnePhaserSample(int32_t modFXDepth, int32_t feedback, StereoSample* currentSample, - int32_t lfoOutput) { // "1" is sorta represented by 1073741824 here + +StereoSample ModFXProcessor::processOnePhaserSample(StereoSample sample, int32_t modFXDepth, int32_t feedback, + int32_t lfoOutput) { // "1" is sorta represented by 1073741824 here int32_t _a1 = 1073741824 - multiply_32x32_rshift32_rounded((((uint32_t)lfoOutput + (uint32_t)2147483648) >> 1), modFXDepth); - phaserMemory.l = currentSample->l + (multiply_32x32_rshift32_rounded(phaserMemory.l, feedback) << 1); - phaserMemory.r = currentSample->r + (multiply_32x32_rshift32_rounded(phaserMemory.r, feedback) << 1); + phaserMemory.l = sample.l + (multiply_32x32_rshift32_rounded(phaserMemory.l, feedback) << 1); + phaserMemory.r = sample.r + (multiply_32x32_rshift32_rounded(phaserMemory.r, feedback) << 1); // Do the allpass filters for (auto& sample : allpassMemory) { @@ -269,8 +259,10 @@ void ModFXProcessor::processOnePhaserSample(int32_t modFXDepth, int32_t feedback sample.r = (multiply_32x32_rshift32_rounded(phaserMemory.r, _a1) << 2) + whatWasInput.r; } - currentSample->l += phaserMemory.l; - currentSample->r += phaserMemory.r; + sample.l += phaserMemory.l; + sample.r += phaserMemory.r; + + return sample; } void ModFXProcessor::resetMemory() { diff --git a/src/deluge/model/mod_controllable/ModFXProcessor.h b/src/deluge/model/mod_controllable/ModFXProcessor.h index b3bc516085..8173a4b936 100644 --- a/src/deluge/model/mod_controllable/ModFXProcessor.h +++ b/src/deluge/model/mod_controllable/ModFXProcessor.h @@ -14,34 +14,38 @@ * You should have received a copy of the GNU General Public License along with this program. * If not, see . */ - -#ifndef DELUGE_MODFXPROCESSOR_H -#define DELUGE_MODFXPROCESSOR_H +#pragma once #include "definitions_cxx.hpp" #include "dsp/stereo_sample.h" #include "modulation/lfo.h" #include "util/containers.h" +#include +#include + class ModFXProcessor { void setupChorus(const ModFXType& modFXType, int32_t modFXDepth, int32_t* postFXVolume, UnpatchedParamSet* unpatchedParams, LFOType& modFXLFOWaveType, int32_t& modFXDelayOffset, int32_t& thisModFXDelayDepth) const; + /// flanger, phaser, warble - generally any modulated delay tap based effect with feedback void setupModFXWFeedback(const ModFXType& modFXType, int32_t modFXDepth, int32_t* postFXVolume, UnpatchedParamSet* unpatchedParams, LFOType& modFXLFOWaveType, int32_t& modFXDelayOffset, int32_t& thisModFXDelayDepth, int32_t& feedback) const; // not grain! template - void processModFXBuffer(StereoSample* buffer, int32_t modFXRate, int32_t modFXDepth, const StereoSample* bufferEnd, + void processModFXBuffer(std::span buffer, int32_t modFXRate, int32_t modFXDepth, LFOType& modFXLFOWaveType, int32_t modFXDelayOffset, int32_t thisModFXDelayDepth, int32_t feedback, bool stereo); + template - void processModLFOs(int32_t modFXRate, LFOType& modFXLFOWaveType, int32_t& lfoOutput, int32_t& lfo2Output); + std::pair processModLFOs(int32_t modFXRate, LFOType modFXLFOWaveType); + template - void processOneModFXSample(int32_t modFXDelayOffset, int32_t thisModFXDelayDepth, int32_t feedback, - StereoSample* currentSample, int32_t lfoOutput, int32_t lfo2Output); - void processOnePhaserSample(int32_t modFXDepth, int32_t feedback, StereoSample* currentSample, int32_t lfoOutput); + StereoSample processOneModFXSample(StereoSample sample, int32_t modFXDelayOffset, int32_t thisModFXDelayDepth, + int32_t feedback, int32_t lfoOutput, int32_t lfo2Output); + StereoSample processOnePhaserSample(StereoSample sample, int32_t modFXDepth, int32_t feedback, int32_t lfoOutput); public: ModFXProcessor() { @@ -63,9 +67,8 @@ class ModFXProcessor { uint16_t modFXBufferWriteIndex{0}; LFO modFXLFO; LFO modFXLFOStereo; - void processModFX(StereoSample* buffer, const ModFXType& modFXType, int32_t modFXRate, int32_t modFXDepth, - int32_t* postFXVolume, UnpatchedParamSet* unpatchedParams, const StereoSample* bufferEnd, - bool anySoundComingIn); + void processModFX(std::span buffer, const ModFXType& modFXType, int32_t modFXRate, int32_t modFXDepth, + int32_t* postFXVolume, UnpatchedParamSet* unpatchedParams, bool anySoundComingIn); void resetMemory(); void setupBuffer(); void disableBuffer(); @@ -82,5 +85,3 @@ const char* getParamName(ModFXType type, ModFXParam param); const char* modFXToString(ModFXType type); } // namespace modfx - -#endif // DELUGE_MODFXPROCESSOR_H diff --git a/src/deluge/model/mod_controllable/mod_controllable_audio.cpp b/src/deluge/model/mod_controllable/mod_controllable_audio.cpp index b7a4bb7496..e23ea1f36e 100644 --- a/src/deluge/model/mod_controllable/mod_controllable_audio.cpp +++ b/src/deluge/model/mod_controllable/mod_controllable_audio.cpp @@ -129,22 +129,19 @@ bool ModControllableAudio::hasTrebleAdjusted(ParamManager* paramManager) { return (unpatchedParams->getValue(params::UNPATCHED_TREBLE) != 0); } -void ModControllableAudio::processFX(StereoSample* buffer, int32_t numSamples, ModFXType modFXType, int32_t modFXRate, +void ModControllableAudio::processFX(std::span buffer, ModFXType modFXType, int32_t modFXRate, int32_t modFXDepth, const Delay::State& delayWorkingState, int32_t* postFXVolume, ParamManager* paramManager, bool anySoundComingIn, q31_t reverbSendAmount) { UnpatchedParamSet* unpatchedParams = paramManager->getUnpatchedParamSet(); - StereoSample* bufferEnd = buffer + numSamples; - // Mod FX ----------------------------------------------------------------------------------- if (modFXType == ModFXType::GRAIN) { - processGrainFX(buffer, modFXRate, modFXDepth, postFXVolume, unpatchedParams, bufferEnd, anySoundComingIn, + processGrainFX(buffer, modFXRate, modFXDepth, postFXVolume, unpatchedParams, anySoundComingIn, reverbSendAmount); } else { - modfx.processModFX(buffer, modFXType, modFXRate, modFXDepth, postFXVolume, unpatchedParams, bufferEnd, - anySoundComingIn); + modfx.processModFX(buffer, modFXType, modFXRate, modFXDepth, postFXVolume, unpatchedParams, anySoundComingIn); } // EQ ------------------------------------------------------------------------------------- @@ -169,18 +166,17 @@ void ModControllableAudio::processFX(StereoSample* buffer, int32_t numSamples, M trebleFreq = getExp(700000000, (unpatchedParams->getValue(params::UNPATCHED_TREBLE_FREQ) >> 5) * 6); } - StereoSample* currentSample = buffer; - do { - doEQ(thisDoBass, thisDoTreble, ¤tSample->l, ¤tSample->r, bassAmount, trebleAmount); - } while (++currentSample != bufferEnd); + for (StereoSample& sample : buffer) { + doEQ(thisDoBass, thisDoTreble, &sample.l, &sample.r, bassAmount, trebleAmount); + } } // Delay ---------------------------------------------------------------------------------- - delay.process({buffer, static_cast(numSamples)}, delayWorkingState); + delay.process(buffer, delayWorkingState); } -void ModControllableAudio::processGrainFX(StereoSample* buffer, int32_t modFXRate, int32_t modFXDepth, +void ModControllableAudio::processGrainFX(std::span buffer, int32_t modFXRate, int32_t modFXDepth, int32_t* postFXVolume, UnpatchedParamSet* unpatchedParams, - const StereoSample* bufferEnd, bool anySoundComingIn, q31_t verbAmount) { + bool anySoundComingIn, q31_t verbAmount) { // this shouldn't be possible but just in case if (anySoundComingIn && !grainFX) [[unlikely]] { enableGrain(); @@ -190,18 +186,16 @@ void ModControllableAudio::processGrainFX(StereoSample* buffer, int32_t modFXRat int32_t reverbSendAmountAndPostFXVolume = multiply_32x32_rshift32(*postFXVolume, verbAmount) << 5; grainFX->processGrainFX(buffer, modFXRate, modFXDepth, unpatchedParams->getValue(params::UNPATCHED_MOD_FX_OFFSET), - unpatchedParams->getValue(params::UNPATCHED_MOD_FX_FEEDBACK), postFXVolume, bufferEnd, + unpatchedParams->getValue(params::UNPATCHED_MOD_FX_FEEDBACK), postFXVolume, anySoundComingIn, currentSong->calculateBPM(), reverbSendAmountAndPostFXVolume); } } -void ModControllableAudio::processReverbSendAndVolume(StereoSample* buffer, int32_t numSamples, int32_t* reverbBuffer, +void ModControllableAudio::processReverbSendAndVolume(std::span buffer, int32_t* reverbBuffer, int32_t postFXVolume, int32_t postReverbVolume, int32_t reverbSendAmount, int32_t pan, bool doAmplitudeIncrement) { - StereoSample* bufferEnd = buffer + numSamples; - int32_t reverbSendAmountAndPostFXVolume = multiply_32x32_rshift32(postFXVolume, reverbSendAmount) << 5; int32_t postFXAndReverbVolumeL, postFXAndReverbVolumeR, amplitudeIncrementL, amplitudeIncrementR; @@ -211,7 +205,7 @@ void ModControllableAudio::processReverbSendAndVolume(StereoSample* buffer, int3 // sidechain volume ducking, which is done through post-FX volume. if (doAmplitudeIncrement) { auto postReverbSendVolumeIncrement = - (int32_t)((double)(postReverbVolume - postReverbVolumeLastTime) / (double)numSamples); + (int32_t)((double)(postReverbVolume - postReverbVolumeLastTime) / (double)buffer.size()); amplitudeIncrementL = amplitudeIncrementR = (multiply_32x32_rshift32(postFXVolume, postReverbSendVolumeIncrement) << 5); } @@ -229,15 +223,10 @@ void ModControllableAudio::processReverbSendAndVolume(StereoSample* buffer, int3 amplitudeIncrementR = multiply_32x32_rshift32(amplitudeIncrementR, amplitudeR) << 2; } - StereoSample* inputSample = buffer; - - do { - StereoSample processingSample = *inputSample; - + for (StereoSample& sample : buffer) { // Send to reverb if (reverbSendAmount != 0) { - *(reverbBuffer++) += - multiply_32x32_rshift32(processingSample.l + processingSample.r, reverbSendAmountAndPostFXVolume) << 1; + *(reverbBuffer++) += multiply_32x32_rshift32(sample.l + sample.r, reverbSendAmountAndPostFXVolume) << 1; } if (doAmplitudeIncrement) { @@ -246,10 +235,9 @@ void ModControllableAudio::processReverbSendAndVolume(StereoSample* buffer, int3 } // Apply post-fx and post-reverb-send volume - inputSample->l = multiply_32x32_rshift32(processingSample.l, postFXAndReverbVolumeL) << 5; - inputSample->r = multiply_32x32_rshift32(processingSample.r, postFXAndReverbVolumeR) << 5; - - } while (++inputSample != bufferEnd); + sample.l = multiply_32x32_rshift32(sample.l, postFXAndReverbVolumeL) << 5; + sample.r = multiply_32x32_rshift32(sample.r, postFXAndReverbVolumeR) << 5; + } // We've generated some sound. If reverb is happening, make note if (reverbSendAmount != 0) { @@ -268,10 +256,8 @@ bool ModControllableAudio::isSRREnabled(ParamManager* paramManager) { return (unpatchedParams->getValue(params::UNPATCHED_SAMPLE_RATE_REDUCTION) != -2147483648); } -void ModControllableAudio::processSRRAndBitcrushing(StereoSample* buffer, int32_t numSamples, int32_t* postFXVolume, +void ModControllableAudio::processSRRAndBitcrushing(std::span buffer, int32_t* postFXVolume, ParamManager* paramManager) { - StereoSample const* const bufferEnd = buffer + numSamples; - uint32_t bitCrushMaskForSRR = 0xFFFFFFFF; bool srrEnabled = isSRREnabled(paramManager); @@ -287,11 +273,10 @@ void ModControllableAudio::processSRRAndBitcrushing(StereoSample* buffer, int32_ // If not also doing SRR if (!srrEnabled) { uint32_t mask = 0xFFFFFFFF << (19 + (positivePreset)); - StereoSample* __restrict__ currentSample = buffer; - do { - currentSample->l &= mask; - currentSample->r &= mask; - } while (++currentSample != bufferEnd); + for (StereoSample& sample : buffer) { + sample.l &= mask; + sample.r &= mask; + } } else { @@ -319,9 +304,7 @@ void ModControllableAudio::processSRRAndBitcrushing(StereoSample* buffer, int32_ int32_t highSampleRateIncrement = ((uint32_t)0xFFFFFFFF / (lowSampleRateIncrement >> 6)) << 6; // int32_t highSampleRateIncrement = getExp(4194304, -(int32_t)(positivePreset >> 3)); // This would work too - StereoSample* currentSample = buffer; - do { - + for (StereoSample& sample : buffer) { // Convert down. // If time to "grab" another sample for down-conversion... if (lowSampleRatePos < 4194304) { @@ -330,9 +313,9 @@ void ModControllableAudio::processSRRAndBitcrushing(StereoSample* buffer, int32_ lastGrabbedSample = grabbedSample; // What was current is now last grabbedSample.l = multiply_32x32_rshift32_rounded(lastSample.l, strength1 << 9) - + multiply_32x32_rshift32_rounded(currentSample->l, strength2 << 9); + + multiply_32x32_rshift32_rounded(sample.l, strength2 << 9); grabbedSample.r = multiply_32x32_rshift32_rounded(lastSample.r, strength1 << 9) - + multiply_32x32_rshift32_rounded(currentSample->r, strength2 << 9); + + multiply_32x32_rshift32_rounded(sample.r, strength2 << 9); grabbedSample.l &= bitCrushMaskForSRR; grabbedSample.r &= bitCrushMaskForSRR; @@ -346,22 +329,21 @@ void ModControllableAudio::processSRRAndBitcrushing(StereoSample* buffer, int32_ multiply_32x32_rshift32_rounded(lowSampleRatePos & 4194303, highSampleRateIncrement << 8) << 2; } lowSampleRatePos -= 4194304; // We're one step closer to grabbing our next sample for down-conversion - lastSample = *currentSample; + lastSample = sample; // Convert up - int32_t strength2 = - std::min(highSampleRatePos, - (uint32_t)4194303); // Would only overshoot if we raised the sample rate during playback + // Would only overshoot if we raised the sample rate during playback + int32_t strength2 = std::min(highSampleRatePos, (uint32_t)4194303); int32_t strength1 = 4194303 - strength2; - currentSample->l = (multiply_32x32_rshift32_rounded(lastGrabbedSample.l, strength1 << 9) - + multiply_32x32_rshift32_rounded(grabbedSample.l, strength2 << 9)) - << 2; - currentSample->r = (multiply_32x32_rshift32_rounded(lastGrabbedSample.r, strength1 << 9) - + multiply_32x32_rshift32_rounded(grabbedSample.r, strength2 << 9)) - << 2; + sample.l = (multiply_32x32_rshift32_rounded(lastGrabbedSample.l, strength1 << 9) + + multiply_32x32_rshift32_rounded(grabbedSample.l, strength2 << 9)) + << 2; + sample.r = (multiply_32x32_rshift32_rounded(lastGrabbedSample.r, strength1 << 9) + + multiply_32x32_rshift32_rounded(grabbedSample.r, strength2 << 9)) + << 2; highSampleRatePos += highSampleRateIncrement; - } while (++currentSample != bufferEnd); + } } else { sampleRateReductionOnLastTime = false; @@ -1130,9 +1112,9 @@ void ModControllableAudio::beginStutter(ParamManagerForTimeline* paramManager) { } } -void ModControllableAudio::processStutter(StereoSample* buffer, int32_t numSamples, ParamManager* paramManager) { +void ModControllableAudio::processStutter(std::span buffer, ParamManager* paramManager) { if (stutterer.isStuttering(this)) { - stutterer.processStutter(buffer, numSamples, paramManager, currentSong->getInputTickMagnitude(), + stutterer.processStutter(buffer, paramManager, currentSong->getInputTickMagnitude(), playbackHandler.getTimePerInternalTickInverse(), runtimeFeatureSettings.isOn(RuntimeFeatureSettingType::ReverseStutterRate)); } diff --git a/src/deluge/model/mod_controllable/mod_controllable_audio.h b/src/deluge/model/mod_controllable/mod_controllable_audio.h index 6df1f0bd9c..942654555d 100644 --- a/src/deluge/model/mod_controllable/mod_controllable_audio.h +++ b/src/deluge/model/mod_controllable/mod_controllable_audio.h @@ -49,16 +49,15 @@ class ModControllableAudio : public ModControllable { virtual ~ModControllableAudio(); virtual void cloneFrom(ModControllableAudio* other); - void processStutter(StereoSample* buffer, int32_t numSamples, ParamManager* paramManager); - void processReverbSendAndVolume(StereoSample* buffer, int32_t numSamples, int32_t* reverbBuffer, - int32_t postFXVolume, int32_t postReverbVolume, int32_t reverbSendAmount, - int32_t pan = 0, bool doAmplitudeIncrement = false); + void processStutter(std::span buffer, ParamManager* paramManager); + void processReverbSendAndVolume(std::span buffer, int32_t* reverbBuffer, int32_t postFXVolume, + int32_t postReverbVolume, int32_t reverbSendAmount, int32_t pan = 0, + bool doAmplitudeIncrement = false); void writeAttributesToFile(Serializer& writer); void writeTagsToFile(Serializer& writer); virtual Error readTagFromFile(Deserializer& reader, char const* tagName, ParamManagerForTimeline* paramManager, int32_t readAutomationUpToPos, Song* song); - void processSRRAndBitcrushing(StereoSample* buffer, int32_t numSamples, int32_t* postFXVolume, - ParamManager* paramManager); + void processSRRAndBitcrushing(std::span buffer, int32_t* postFXVolume, ParamManager* paramManager); static void writeParamAttributesToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, int32_t* valuesForOverride = nullptr); static void writeParamTagsToFile(Serializer& writer, ParamManager* paramManager, bool writeAutomation, @@ -126,7 +125,7 @@ class ModControllableAudio : public ModControllable { int32_t postReverbVolumeLastTime{}; protected: - void processFX(StereoSample* buffer, int32_t numSamples, ModFXType modFXType, int32_t modFXRate, int32_t modFXDepth, + void processFX(std::span buffer, ModFXType modFXType, int32_t modFXRate, int32_t modFXDepth, const Delay::State& delayWorkingState, int32_t* postFXVolume, ParamManager* paramManager, bool anySoundComingIn, q31_t reverbSendAmount); void switchDelayPingPong(); @@ -170,7 +169,6 @@ class ModControllableAudio : public ModControllable { void switchHPFModeWithOff(); void switchLPFModeWithOff(); - void processGrainFX(StereoSample* buffer, int32_t modFXRate, int32_t modFXDepth, int32_t* postFXVolume, - UnpatchedParamSet* unpatchedParams, const StereoSample* bufferEnd, bool anySoundComingIn, - q31_t verbAmount); + void processGrainFX(std::span buffer, int32_t modFXRate, int32_t modFXDepth, int32_t* postFXVolume, + UnpatchedParamSet* unpatchedParams, bool anySoundComingIn, q31_t verbAmount); }; diff --git a/src/deluge/model/output.h b/src/deluge/model/output.h index 864bc19bb3..7a6b641851 100644 --- a/src/deluge/model/output.h +++ b/src/deluge/model/output.h @@ -112,9 +112,9 @@ class Output { // reverbAmountAdjust has "1" as 67108864 // Only gets called if there's an activeClip - virtual void renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, int32_t numSamples, - int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, - bool shouldLimitDelayFeedback, bool isClipActive) = 0; + virtual void renderOutput(ModelStack* modelStack, std::span outputBuffer, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) = 0; virtual void setupWithoutActiveClip(ModelStack* modelStack); virtual bool setActiveClip( diff --git a/src/deluge/model/sample/sample_recorder.cpp b/src/deluge/model/sample/sample_recorder.cpp index c4ea5c2715..54815f6925 100644 --- a/src/deluge/model/sample/sample_recorder.cpp +++ b/src/deluge/model/sample/sample_recorder.cpp @@ -31,7 +31,9 @@ #include "storage/audio/audio_file_manager.h" #include "storage/cluster/cluster.h" #include "util/exceptions.h" +#include "util/fixedpoint.h" #include "util/functions.h" +#include #include extern "C" { @@ -906,14 +908,10 @@ void SampleRecorder::finishCapturing() { // Only call this after checking that status < RecorderStatus::FINISHED_CAPTURING_BUT_STILL_WRITING // Watch out - this could be called during SD writing - including during cardRoutine() for this class! -void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSamples, bool applyGain, - uint8_t gainToApply) { - +void SampleRecorder::feedAudio(std::span input, bool applyGain, uint8_t gainToApply) { + int32_t numSamples = input.size(); do { - int32_t numSamplesThisCycle = numSamples; - if (ALPHA_OR_BETA_VERSION && numSamplesThisCycle <= 0) { - FREEZE_WITH_ERROR("cccc"); - } + int32_t numSamplesThisCycle = input.size(); // If haven't actually started recording yet cos we're compensating for lag... if (numSamplesBeenRunning < (uint32_t)numSamplesToRunBeforeBeginningCapturing) { @@ -962,7 +960,8 @@ void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSa if (bytesTilClusterEnd <= bytesWeWantToWrite - bytesPerSample) { int32_t samplesTilClusterEnd = - (uint16_t)(bytesTilClusterEnd - 1) / (uint8_t)bytesPerSample + 1; // Rounds up + (static_cast(bytesTilClusterEnd - 1) / static_cast(bytesPerSample)) + + 1; // Rounds up numSamplesThisCycle = std::min(numSamplesThisCycle, samplesTilClusterEnd); } @@ -970,56 +969,32 @@ void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSa FREEZE_WITH_ERROR("aaaa"); } - int32_t* beginInputNow = inputAddress; - int32_t* endInputNow = inputAddress + (numSamplesThisCycle << NUM_MONO_INPUT_CHANNELS_MAGNITUDE); - - char* __restrict__ writePosNow = writePos; - - // Balanced input. For this, we skip a bunch of stat-grabbing, cos we knob this is just for AudioClips. + std::byte* __restrict__ writePosNow = reinterpret_cast(writePos); + // Balanced input. For this, we skip a bunch of stat-grabbing, cos we know this is just for AudioClips. // We also know that applyGain is false - that's just for the MIX option if (mode == AudioInputChannel::BALANCED) { + for (StereoSample sample : input.first(numSamplesThisCycle)) { + q31_t rxBalanced = (sample.l / 2) - (sample.r / 2); - do { - int32_t rxL = *inputAddress; - int32_t rxR = *(inputAddress + 1); - int32_t rxBalanced = (rxL >> 1) - (rxR >> 1); - - char* __restrict__ readPos = (char*)&rxBalanced + 1; - *(writePosNow++) = *(readPos++); - *(writePosNow++) = *(readPos++); - *(writePosNow++) = *(readPos++); - - inputAddress += NUM_MONO_INPUT_CHANNELS; - } while (inputAddress < endInputNow); + // Copy the last 24 bits (lower 3 bytes) to writePosNow + writePosNow = std::copy_n(&reinterpret_cast(&rxBalanced)[1], 3, writePosNow); + } } // Or, all other, non-balanced input types else { - do { - int32_t rxL = *inputAddress; + for (auto [rxL, rxR] : input.first(numSamplesThisCycle)) { if (applyGain) { rxL = lshiftAndSaturateUnknown(rxL, gainToApply); } - char* __restrict__ readPos = (char*)&rxL + 1; - *(writePosNow++) = *(readPos++); - *(writePosNow++) = *(readPos++); - *(writePosNow++) = *(readPos++); + // Copy the last 24 bits (lower 3 bytes) to writePosNow + writePosNow = std::copy_n(&reinterpret_cast(&rxL)[1], 3, writePosNow); - if (rxL > recordMax) { - recordMax = rxL; - } - if (rxL < recordMin) { - recordMin = rxL; - } + recordMax = std::max(rxL, recordMax); + recordMin = std::min(rxL, recordMin); - int32_t absL; - if (rxL >= 0) { - absL = rxL; - } - else { - absL = -1 - rxL; - } + q31_t absL = (rxL >= 0) ? rxL : -1 - rxL; recordSumL += absL; if (rxL < recordPeakL) { @@ -1028,51 +1003,29 @@ void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSa else if (-rxL < recordPeakL) { recordPeakL = -rxL; } - if (rxL == 2147483647 || rxL == -2147483648) { + if (rxL == std::numeric_limits::max() || rxL == std::numeric_limits::min()) { recordingClippedRecently = true; } // recording stereo if (recordingNumChannels == 2) { - int32_t rxR = *(inputAddress + 1); if (applyGain) { rxR = lshiftAndSaturateUnknown(rxR, gainToApply); } - readPos = (char*)&rxR + 1; - *(writePosNow++) = *(readPos++); - *(writePosNow++) = *(readPos++); - *(writePosNow++) = *(readPos++); + // Copy the last 24 bits (lower 3 bytes) to writePosNow + writePosNow = std::copy_n(&reinterpret_cast(&rxR)[1], 3, writePosNow); - if (rxR > recordMax) { - recordMax = rxR; - } - if (rxR < recordMin) { - recordMin = rxR; - } + recordMax = std::max(rxR, recordMax); + recordMin = std::min(rxR, recordMin); - if (rxR >= 0) { - recordSumR += rxR; - } - else { - recordSumR += -1 - rxR; - } + recordSumR += (rxR >= 0) ? rxR : -1 - rxR; int32_t lPlusR = (rxL >> 1) + (rxR >> 1); - if (lPlusR >= 0) { - recordSumLPlusR += lPlusR; - } - else { - recordSumLPlusR += -1 - lPlusR; - } + recordSumLPlusR += (lPlusR >= 0) ? lPlusR : -1 - lPlusR; int32_t lMinusR = (rxL >> 1) - (rxR >> 1); - if (lMinusR >= 0) { - recordSumLMinusR += lMinusR; - } - else { - recordSumLMinusR += -1 - lMinusR; - } + recordSumLMinusR += (lMinusR >= 0) ? lMinusR : -1 - lMinusR; if (rxR < recordPeakR) { recordPeakR = rxR; @@ -1080,7 +1033,7 @@ void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSa else if (-rxR < recordPeakR) { recordPeakR = -rxR; } - if (rxR == 2147483647 || rxR == -2147483648) { + if (rxR == std::numeric_limits::max() || rxR == std::numeric_limits::min()) { recordingClippedRecently = true; } @@ -1091,23 +1044,22 @@ void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSa recordPeakLMinusR = -lMinusR; } } - - inputAddress += NUM_MONO_INPUT_CHANNELS; - } while (inputAddress < endInputNow); + } } + // update our input view to exclude the chunk we just processed + input = input.subspan(numSamplesThisCycle); // if we're not threshold recording or we're threshold recording and detected audio if (!thresholdRecording || sample->audioStartDetected) { - writePos = writePosNow; + writePos = reinterpret_cast(writePosNow); numSamplesCaptured += numSamplesThisCycle; } // if we're threshold recording and didn't detect audio in previous cycles // check if there's any audio in this cycle else { - StereoFloatSample approxRMSLevel = - envelopeFollower.calcApproxRMS((StereoSample*)beginInputNow, numSamplesThisCycle); + StereoFloatSample approxRMSLevel = envelopeFollower.calcApproxRMS(input); if (std::max(approxRMSLevel.l, approxRMSLevel.r) > startValueThreshold) { - writePos = writePosNow; + writePos = reinterpret_cast(writePosNow); numSamplesCaptured += numSamplesThisCycle; sample->audioStartDetected = true; } @@ -1117,7 +1069,7 @@ void SampleRecorder::feedAudio(int32_t* __restrict__ inputAddress, int32_t numSa numSamplesBeenRunning += numSamplesThisCycle; numSamples -= numSamplesThisCycle; - } while (numSamples); + } while (numSamples > 0); } void SampleRecorder::endSyncedRecording(int32_t buttonLatencyForTempolessRecording) { diff --git a/src/deluge/model/sample/sample_recorder.h b/src/deluge/model/sample/sample_recorder.h index fcfcea2034..9d5828b5f9 100644 --- a/src/deluge/model/sample/sample_recorder.h +++ b/src/deluge/model/sample/sample_recorder.h @@ -53,7 +53,7 @@ class SampleRecorder { bool shouldRecordExtraMargins, AudioRecordingFolder newFolderID, int32_t buttonPressLatency, Output* outputRecordingFrom); void setRecordingThreshold(); - void feedAudio(int32_t* inputAddress, int32_t numSamples, bool applyGain = false, uint8_t gainToApply = 5); + void feedAudio(std::span input, bool applyGain = false, uint8_t gainToApply = 5); Error cardRoutine(); void endSyncedRecording(int32_t buttonLatencyForTempolessRecording); bool inputLooksDifferential(); diff --git a/src/deluge/model/song/song.cpp b/src/deluge/model/song/song.cpp index 16fd7938e1..99dcbcc475 100644 --- a/src/deluge/model/song/song.cpp +++ b/src/deluge/model/song/song.cpp @@ -2393,8 +2393,7 @@ void Song::deleteSoundsWhichWontSound() { deleteAllBackedUpParamManagersWithClips(); } -void Song::renderAudio(StereoSample* outputBuffer, int32_t numSamples, int32_t* reverbBuffer, - int32_t sideChainHitPending) { +void Song::renderAudio(std::span outputBuffer, int32_t* reverbBuffer, int32_t sideChainHitPending) { // int32_t volumePostFX = getParamNeutralValue(params::GLOBAL_VOLUME_POST_FX); int32_t volumePostFX = @@ -2419,8 +2418,8 @@ void Song::renderAudio(StereoSample* outputBuffer, int32_t numSamples, int32_t* (output->getActiveClip() && isClipActive(output->getActiveClip()->getClipBeingRecordedFrom())); DISABLE_ALL_INTERRUPTS(); if (output->shouldRenderInSong()) { - output->renderOutput(modelStack, outputBuffer, outputBuffer + numSamples, numSamples, reverbBuffer, - volumePostFX >> 1, sideChainHitPending, !isClipActiveNow, isClipActiveNow); + output->renderOutput(modelStack, outputBuffer, reverbBuffer, volumePostFX >> 1, sideChainHitPending, + !isClipActiveNow, isClipActiveNow); } ENABLE_INTERRUPTS(); #if DO_AUDIO_LOG @@ -2439,7 +2438,7 @@ void Song::renderAudio(StereoSample* outputBuffer, int32_t numSamples, int32_t* } if (recorder->mode == AudioInputChannel::MIX) { - recorder->feedAudio((int32_t*)outputBuffer, numSamples, true); + recorder->feedAudio(outputBuffer, true); } } @@ -2453,10 +2452,10 @@ void Song::renderAudio(StereoSample* outputBuffer, int32_t numSamples, int32_t* // don't bother checking if sound is coming in - its just to save resources and if nothing is being rendered we // don't need to - globalEffectable.processFXForGlobalEffectable(outputBuffer, numSamples, &volumePostFX, ¶mManager, - delayWorkingState, true, reverbSendAmount >> 3); + globalEffectable.processFXForGlobalEffectable(outputBuffer, &volumePostFX, ¶mManager, delayWorkingState, true, + reverbSendAmount >> 3); - globalEffectable.processReverbSendAndVolume(outputBuffer, numSamples, reverbBuffer, volumePostFX, postReverbVolume, + globalEffectable.processReverbSendAndVolume(outputBuffer, reverbBuffer, volumePostFX, postReverbVolume, reverbSendAmount >> 1); if (playbackHandler.isEitherClockActive() && !playbackHandler.ticksLeftInCountIn @@ -2467,7 +2466,7 @@ void Song::renderAudio(StereoSample* outputBuffer, int32_t numSamples, int32_t* : paramManager.getUnpatchedParamSetSummary()->whichParamsAreInterpolating[0]; if (result) { ModelStackWithThreeMainThings* modelStackWithThreeMainThings = addToModelStack(modelStack); - paramManager.tickSamples(numSamples, modelStackWithThreeMainThings); + paramManager.tickSamples(outputBuffer.size(), modelStackWithThreeMainThings); } } } diff --git a/src/deluge/model/song/song.h b/src/deluge/model/song/song.h index 5279e9ab20..09d8481d65 100644 --- a/src/deluge/model/song/song.h +++ b/src/deluge/model/song/song.h @@ -277,8 +277,7 @@ class Song final : public TimelineCounter { Error readFromFile(Deserializer& reader); void writeToFile(); void loadAllSamples(bool mayActuallyReadFiles = true); - void renderAudio(StereoSample* outputBuffer, int32_t numSamples, int32_t* reverbBuffer, - int32_t sideChainHitPending); + void renderAudio(std::span outputBuffer, int32_t* reverbBuffer, int32_t sideChainHitPending); bool isYNoteAllowed(int32_t yNote, bool inKeyMode); Clip* syncScalingClip; void setTimePerTimerTick(uint64_t newTimeBig, bool shouldLogAction = false); diff --git a/src/deluge/processing/audio_output.cpp b/src/deluge/processing/audio_output.cpp index bf3add5600..567e31c329 100644 --- a/src/deluge/processing/audio_output.cpp +++ b/src/deluge/processing/audio_output.cpp @@ -16,7 +16,9 @@ */ #include "processing/audio_output.h" +#include "definitions.h" #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "gui/views/view.h" #include "memory/general_memory_allocator.h" #include "model/clip/audio_clip.h" @@ -30,7 +32,9 @@ #include "storage/audio/audio_file.h" #include "storage/storage_manager.h" #include "util/lookuptables/lookuptables.h" +#include #include +#include extern "C" { #include "drivers/ssi/ssi.h" @@ -90,17 +94,17 @@ void AudioOutput::cloneFrom(ModControllableAudio* other) { } } -void AudioOutput::renderOutput(ModelStack* modelStack, StereoSample* outputBuffer, StereoSample* outputBufferEnd, - int32_t numSamples, int32_t* reverbBuffer, int32_t reverbAmountAdjust, - int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive) { +void AudioOutput::renderOutput(ModelStack* modelStack, std::span output, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) { ParamManager* paramManager = getParamManager(modelStack->song); ModelStackWithTimelineCounter* modelStackWithTimelineCounter = modelStack->addTimelineCounter(activeClip); - GlobalEffectableForClip::renderOutput(modelStackWithTimelineCounter, paramManager, outputBuffer, numSamples, - reverbBuffer, reverbAmountAdjust, sideChainHitPending, - shouldLimitDelayFeedback, isClipActive, OutputType::AUDIO, recorder); + GlobalEffectableForClip::renderOutput(modelStackWithTimelineCounter, paramManager, output, reverbBuffer, + reverbAmountAdjust, sideChainHitPending, shouldLimitDelayFeedback, + isClipActive, OutputType::AUDIO, recorder); } void AudioOutput::resetEnvelope() { @@ -115,33 +119,34 @@ void AudioOutput::resetEnvelope() { } // Beware - unlike usual, modelStack, a ModelStackWithThreeMainThings*, might have a NULL timelineCounter -bool AudioOutput::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, StereoSample* renderBuffer, - int32_t* bufferToTransferTo, int32_t numSamples, int32_t* reverbBuffer, - int32_t reverbAmountAdjust, int32_t sideChainHitPending, - bool shouldLimitDelayFeedback, bool isClipActive, int32_t pitchAdjust, - int32_t amplitudeAtStart, int32_t amplitudeAtEnd) { +bool AudioOutput::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, + std::span render, int32_t* bufferToTransferTo, + int32_t* reverbBuffer, int32_t reverbAmountAdjust, + int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive, int32_t pitchAdjust, int32_t amplitudeAtStart, + int32_t amplitudeAtEnd) { bool rendered = false; // audio outputs can have an activeClip while being muted if (isClipActive) { - AudioClip* activeAudioClip = (AudioClip*)activeClip; - if (activeAudioClip->voiceSample) { + auto& activeAudioClip = static_cast(*activeClip); + if (activeAudioClip.voiceSample) { int32_t attackNeutralValue = paramNeutralValues[params::LOCAL_ENV_0_ATTACK]; - int32_t attack = getExp(attackNeutralValue, -(activeAudioClip->attack >> 2)); + int32_t attack = getExp(attackNeutralValue, -(activeAudioClip.attack / 4)); renderEnvelope: int32_t amplitudeLocal = - (envelope.render(numSamples, attack, 8388608, 2147483647, 0, decayTableSmall4) >> 1) + 1073741824; + (envelope.render(render.size(), attack, 8388608, 2147483647, 0, decayTableSmall4) >> 1) + 1073741824; if (envelope.state >= EnvelopeStage::OFF) { - if (activeAudioClip->doingLateStart) { + if (activeAudioClip.doingLateStart) { resetEnvelope(); goto renderEnvelope; } else { // I think we can only be here for one shot audio clips, so maybe we shouldn't keep it? - activeAudioClip->unassignVoiceSample(false); + activeAudioClip.unassignVoiceSample(false); } } @@ -156,170 +161,97 @@ bool AudioOutput::renderGlobalEffectableForClip(ModelStackWithTimelineCounter* m int32_t amplitudeEffectiveEnd = multiply_32x32_rshift32(amplitudeLocal, amplitudeAtEnd); // Reduces amplitude another >>1 - int32_t amplitudeIncrementEffective = (amplitudeEffectiveEnd - amplitudeEffectiveStart) / numSamples; + int32_t amplitudeIncrementEffective = + static_cast(static_cast(amplitudeEffectiveEnd - amplitudeEffectiveStart) + / static_cast(render.size())); - int32_t* intBuffer = (int32_t*)renderBuffer; - activeAudioClip->render(modelStack, intBuffer, numSamples, amplitudeEffectiveStart, - amplitudeIncrementEffective, pitchAdjust); + std::span render_mono{reinterpret_cast(render.data()), render.size()}; + activeAudioClip.render(modelStack, render_mono, amplitudeEffectiveStart, amplitudeIncrementEffective, + pitchAdjust); rendered = true; amplitudeLastTime = amplitudeLocal; // If we need to duplicate mono to stereo... if (AudioOutput::willRenderAsOneChannelOnlyWhichWillNeedCopying()) { - // If can write directly into Song buffer... - if (bufferToTransferTo) { - int32_t const* __restrict__ input = intBuffer; - int32_t const* const inputBufferEnd = input + numSamples; - int32_t* __restrict__ output = bufferToTransferTo; - - int32_t const* const remainderEnd = input + (numSamples & 1); - - while (input != remainderEnd) { - *output += *input; - output++; - *output += *input; - output++; - - input++; - } - - while (input != inputBufferEnd) { - *output += *input; - output++; - *output += *input; - output++; - input++; - - *output += *input; - output++; - *output += *input; - output++; - input++; - } + if (bufferToTransferTo != nullptr) { + std::ranges::transform(render_mono, reinterpret_cast(bufferToTransferTo), + StereoSample::fromMono); } // Or, if duplicating within same rendering buffer (cos there's FX to be applied) else { - int32_t i = numSamples - 1; - - int32_t firstStopAt = numSamples & ~3; - - while (i >= firstStopAt) { - int32_t sampleValue = intBuffer[i]; - intBuffer[(i << 1)] = sampleValue; - intBuffer[(i << 1) + 1] = sampleValue; - i--; - } - - while (i >= 0) { - { - int32_t sampleValue = intBuffer[i]; - intBuffer[(i << 1)] = sampleValue; - intBuffer[(i << 1) + 1] = sampleValue; - i--; - } - - { - int32_t sampleValue = intBuffer[i]; - intBuffer[(i << 1)] = sampleValue; - intBuffer[(i << 1) + 1] = sampleValue; - i--; - } - - { - int32_t sampleValue = intBuffer[i]; - intBuffer[(i << 1)] = sampleValue; - intBuffer[(i << 1) + 1] = sampleValue; - i--; - } - - { - int32_t sampleValue = intBuffer[i]; - intBuffer[(i << 1)] = sampleValue; - intBuffer[(i << 1) + 1] = sampleValue; - i--; - } - } + std::ranges::transform(render_mono.rbegin(), render_mono.rend(), render.rbegin(), + StereoSample::fromMono); } } } } } + + std::span output = (bufferToTransferTo != nullptr) + ? std::span{reinterpret_cast(bufferToTransferTo), render.size()} + : render; + // add in the monitored audio if in sampler or looper mode if (mode != AudioOutputMode::player && modelStack->song->isOutputActiveInArrangement(this) && inputChannel != AudioInputChannel::SPECIFIC_OUTPUT) { rendered = true; - StereoSample* __restrict__ outputPos = bufferToTransferTo ? (StereoSample*)bufferToTransferTo : renderBuffer; - StereoSample const* const outputPosEnd = outputPos + numSamples; - - int32_t const* __restrict__ inputReadPos = (int32_t const*)AudioEngine::i2sRXBufferPos; + int32_t const* __restrict__ input_ptr = (int32_t const*)AudioEngine::i2sRXBufferPos; - AudioInputChannel inputChannelNow = inputChannel; - if (inputChannelNow == AudioInputChannel::STEREO && !AudioEngine::renderInStereo) { - inputChannelNow = AudioInputChannel::NONE; // 0 means combine channels + AudioInputChannel input_channel = this->inputChannel; + if (input_channel == AudioInputChannel::STEREO && !AudioEngine::renderInStereo) { + input_channel = AudioInputChannel::NONE; // 0 means combine channels } - int32_t amplitudeIncrement = (int32_t)((double)(amplitudeAtEnd - amplitudeAtStart) / (double)numSamples); - int32_t amplitudeNow = amplitudeAtStart; - - do { + auto amplitude_increment = static_cast((static_cast(amplitudeAtEnd - amplitudeAtStart) // + / static_cast(render.size()))); + int32_t amplitude = amplitudeAtStart; - amplitudeNow += amplitudeIncrement; + for (StereoSample& output_sample : output) { + amplitude += amplitude_increment; - int32_t inputL = multiply_32x32_rshift32(inputReadPos[0], amplitudeNow) << 2; - int32_t inputR = multiply_32x32_rshift32(inputReadPos[1], amplitudeNow) << 2; + StereoSample input = { + .l = multiply_32x32_rshift32(input_ptr[0], amplitude) << 2, + .r = multiply_32x32_rshift32(input_ptr[1], amplitude) << 2, + }; - switch (inputChannelNow) { + switch (input_channel) { case AudioInputChannel::LEFT: - outputPos->l += inputL; - outputPos->r += inputL; + output_sample += StereoSample::fromMono(input.l); break; case AudioInputChannel::RIGHT: - outputPos->l += inputR; - outputPos->r += inputR; + output_sample += StereoSample::fromMono(input.r); break; - case AudioInputChannel::BALANCED: { - int32_t difference = (inputL >> 1) - (inputR >> 1); - outputPos->l += difference; - outputPos->r += difference; + case AudioInputChannel::BALANCED: + output_sample += StereoSample::fromMono((input.l / 2) - (input.r / 2)); break; - } case AudioInputChannel::NONE: // Means combine channels - { - int32_t sum = (inputL >> 1) + (inputR >> 1); - outputPos->l += sum; - outputPos->r += sum; + output_sample += StereoSample::fromMono((input.l / 2) + (input.r / 2)); break; - } default: // STEREO - outputPos->l += inputL; - outputPos->r += inputR; - + output_sample += input; + break; // Remember, there is no case for echoing out the "output" channel - that function doesn't exist, cos // you're obviously already hearing the output channel } - outputPos++; - - inputReadPos += NUM_MONO_INPUT_CHANNELS; - if (inputReadPos >= getRxBufferEnd()) { - inputReadPos -= SSI_RX_BUFFER_NUM_SAMPLES * NUM_MONO_INPUT_CHANNELS; + input_ptr += NUM_MONO_INPUT_CHANNELS; + if (input_ptr >= getRxBufferEnd()) { + input_ptr -= SSI_RX_BUFFER_NUM_SAMPLES * NUM_MONO_INPUT_CHANNELS; } - } while (outputPos < outputPosEnd); + } } else if (mode != AudioOutputMode::player && modelStack->song->isOutputActiveInArrangement(this) - && inputChannel == AudioInputChannel::SPECIFIC_OUTPUT && outputRecordingFrom) { + && inputChannel == AudioInputChannel::SPECIFIC_OUTPUT && (outputRecordingFrom != nullptr)) { rendered = true; - StereoSample* __restrict__ outputBuffer = bufferToTransferTo ? (StereoSample*)bufferToTransferTo : renderBuffer; - char modelStackMemory[MODEL_STACK_MAX_SIZE]; + std::byte modelStackMemory[MODEL_STACK_MAX_SIZE]; ModelStack* songModelStack = setupModelStackWithSong(modelStackMemory, currentSong); - outputRecordingFrom->renderOutput(songModelStack, outputBuffer, outputBuffer + numSamples, numSamples, - reverbBuffer, reverbAmountAdjust, sideChainHitPending, + outputRecordingFrom->renderOutput(songModelStack, output, reverbBuffer, reverbAmountAdjust, sideChainHitPending, shouldLimitDelayFeedback, isClipActive); } return rendered; diff --git a/src/deluge/processing/audio_output.h b/src/deluge/processing/audio_output.h index 58bfa83b6c..9956942ee7 100644 --- a/src/deluge/processing/audio_output.h +++ b/src/deluge/processing/audio_output.h @@ -40,13 +40,13 @@ class AudioOutput final : public Output, public GlobalEffectableForClip { ~AudioOutput() override; void cloneFrom(ModControllableAudio* other) override; - void renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, int32_t numSamples, - int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, - bool shouldLimitDelayFeedback, bool isClipActive) override; + void renderOutput(ModelStack* modelStack, std::span buffer, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) override; - bool renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, StereoSample* globalEffectableBuffer, - int32_t* bufferToTransferTo, int32_t numSamples, int32_t* reverbBuffer, - int32_t reverbAmountAdjust, int32_t sideChainHitPending, + bool renderGlobalEffectableForClip(ModelStackWithTimelineCounter* modelStack, + std::span globalEffectableBuffer, int32_t* bufferToTransferTo, + int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive, int32_t pitchAdjust, int32_t amplitudeAtStart, int32_t amplitudeAtEnd) override; diff --git a/src/deluge/processing/engines/audio_engine.cpp b/src/deluge/processing/engines/audio_engine.cpp index 3ebfa3f944..53683991be 100644 --- a/src/deluge/processing/engines/audio_engine.cpp +++ b/src/deluge/processing/engines/audio_engine.cpp @@ -16,6 +16,7 @@ */ #include "processing/engines/audio_engine.h" +#include "definitions.h" #include "definitions_cxx.hpp" #include "dsp/reverb/reverb.hpp" #include "dsp/timestretch/time_stretcher.h" @@ -185,11 +186,11 @@ LiveInputBuffer* liveInputBuffers[3]; // For debugging uint16_t lastRoutineTime; -std::array renderingBuffer __attribute__((aligned(CACHE_LINE_SIZE))); -std::array reverbBuffer __attribute__((aligned(CACHE_LINE_SIZE))); +alignas(CACHE_LINE_SIZE) std::array renderingMemory; +alignas(CACHE_LINE_SIZE) std::array reverbMemory; -StereoSample* renderingBufferOutputPos = renderingBuffer.begin(); -StereoSample* renderingBufferOutputEnd = renderingBuffer.begin(); +StereoSample* renderingBufferOutputPos = renderingMemory.begin(); +StereoSample* renderingBufferOutputEnd = renderingMemory.begin(); int32_t masterVolumeAdjustmentL; int32_t masterVolumeAdjustmentR; @@ -674,8 +675,11 @@ void renderAudioForStemExport(size_t numSamples); bypassCulling = false; } void renderAudio(size_t numSamples) { - memset(&renderingBuffer, 0, numSamples * sizeof(StereoSample)); - memset(&reverbBuffer, 0, numSamples * sizeof(StereoSample)); + std::span renderingBuffer{renderingMemory.data(), numSamples}; + std::span reverbBuffer{reverbMemory.data(), numSamples}; + + memset(&renderingMemory, 0, renderingBuffer.size_bytes()); + memset(&reverbMemory, 0, reverbBuffer.size_bytes()); if (sideChainHitPending) { timeLastSideChainHit = audioSampleTimer; @@ -687,7 +691,7 @@ void renderAudio(size_t numSamples) { // Render audio for song if (currentSong) { - currentSong->renderAudio(renderingBuffer.data(), numSamples, reverbBuffer.data(), sideChainHitPending); + currentSong->renderAudio(renderingBuffer, reverbBuffer.data(), sideChainHitPending); } renderReverb(numSamples); @@ -696,19 +700,22 @@ void renderAudio(size_t numSamples) { renderSongFX(numSamples); - metronome.render(renderingBuffer.data(), numSamples); + metronome.render(renderingBuffer); - approxRMSLevel = envelopeFollower.calcApproxRMS(renderingBuffer.data(), numSamples); + approxRMSLevel = envelopeFollower.calcApproxRMS(renderingBuffer); setMonitoringMode(); - renderingBufferOutputPos = renderingBuffer.begin(); - renderingBufferOutputEnd = renderingBuffer.begin() + numSamples; + renderingBufferOutputPos = renderingMemory.begin(); + renderingBufferOutputEnd = renderingMemory.begin() + numSamples; } void renderAudioForStemExport(size_t numSamples) { - memset(&renderingBuffer, 0, numSamples * sizeof(StereoSample)); - memset(&reverbBuffer, 0, numSamples * sizeof(StereoSample)); + std::span renderingBuffer{renderingMemory.data(), numSamples}; + std::span reverbBuffer{reverbMemory.data(), numSamples}; + + memset(&renderingMemory, 0, renderingBuffer.size_bytes()); + memset(&reverbMemory, 0, reverbBuffer.size_bytes()); if (sideChainHitPending) { timeLastSideChainHit = audioSampleTimer; @@ -718,9 +725,8 @@ void renderAudioForStemExport(size_t numSamples) { numHopsEndedThisRoutineCall = 0; // Render audio for song - if (currentSong) { - - currentSong->renderAudio(renderingBuffer.data(), numSamples, reverbBuffer.data(), sideChainHitPending); + if (currentSong != nullptr) { + currentSong->renderAudio(renderingBuffer, reverbBuffer.data(), sideChainHitPending); } if (stemExport.includeSongFX) { @@ -734,17 +740,17 @@ void renderAudioForStemExport(size_t numSamples) { if (recorder && recorder->mode == AudioInputChannel::OFFLINE_OUTPUT) { // continue feeding audio if we're not finished recording if (recorder->status < RecorderStatus::FINISHED_CAPTURING_BUT_STILL_WRITING) { - recorder->feedAudio((int32_t*)renderingBuffer.data(), numSamples, true); + recorder->feedAudio(renderingBuffer, true); } } - approxRMSLevel = envelopeFollower.calcApproxRMS(renderingBuffer.data(), numSamples); + approxRMSLevel = envelopeFollower.calcApproxRMS(renderingBuffer); doMonitoring = false; monitoringAction = MonitoringAction::NONE; - renderingBufferOutputPos = renderingBuffer.begin(); - renderingBufferOutputEnd = renderingBuffer.begin() + numSamples; + renderingBufferOutputPos = renderingMemory.begin(); + renderingBufferOutputEnd = renderingMemory.begin() + numSamples; } void flushMIDIGateBuffers() { // Flush everything out of the MIDI buffer now. At this stage, it would only really have @@ -849,9 +855,12 @@ void tickSongFinalizeWindows(size_t& numSamples, int32_t& timeWithinWindowAtWhic } void feedReverbBackdoorForGrain(int index, q31_t value) { - reverbBuffer[index] += value; + reverbMemory[index] += value; } void renderReverb(size_t numSamples) { + std::span renderingBuffer{renderingMemory.data(), numSamples}; + std::span reverbBuffer{reverbMemory.data(), numSamples}; + if (currentSong && mustUpdateReverbParamsBeforeNextRender) { updateReverbParams(); mustUpdateReverbParamsBeforeNextRender = false; @@ -892,16 +901,16 @@ void renderReverb(size_t numSamples) { reverbAmplitudeL = reverbAmplitudeR = reverbOutputVolume; } - auto reverb_buffer_slice = std::span{reverbBuffer.data(), numSamples}; - auto render_buffer_slice = std::span{renderingBuffer.data(), numSamples}; - // Mix reverb into main render reverb.setPanLevels(reverbAmplitudeL, reverbAmplitudeR); - reverb.process(reverb_buffer_slice, render_buffer_slice); + reverb.process(reverbBuffer, renderingBuffer); logAction("Reverb complete"); } } void renderSamplePreview(size_t numSamples) { // Previewing sample + std::span renderingBuffer{renderingMemory.data(), numSamples}; + std::span reverbBuffer{reverbMemory.data(), numSamples}; + if (getCurrentUI() == &sampleBrowser || getCurrentUI() == &gui::context_menu::sample_browser::kit || getCurrentUI() == &gui::context_menu::sample_browser::synth || getCurrentUI() == &slicer) { @@ -909,25 +918,27 @@ void renderSamplePreview(size_t numSamples) { // Previewing sample ModelStackWithThreeMainThings* modelStack = setupModelStackWithThreeMainThingsButNoNoteRow( modelStackMemory, currentSong, sampleForPreview, NULL, paramManagerForSamplePreview); - sampleForPreview->render(modelStack, renderingBuffer.data(), numSamples, reverbBuffer.data(), - sideChainHitPending); + sampleForPreview->render(modelStack, renderingBuffer, reverbBuffer.data(), sideChainHitPending); } } void renderSongFX(size_t numSamples) { // LPF and stutter for song (must happen after reverb mixed in, which is why it's // happening all the way out here + std::span renderingBuffer{renderingMemory.data(), numSamples}; + std::span reverbBuffer{reverbMemory.data(), numSamples}; + masterVolumeAdjustmentL = 167763968; // getParamNeutralValue(params::GLOBAL_VOLUME_POST_FX); masterVolumeAdjustmentR = 167763968; // getParamNeutralValue(params::GLOBAL_VOLUME_POST_FX); // 167763968 is 134217728 made a bit bigger so that default filter resonance doesn't reduce volume overall if (currentSong) { currentSong->globalEffectable.setupFilterSetConfig(&masterVolumeAdjustmentL, ¤tSong->paramManager); - currentSong->globalEffectable.processFilters(renderingBuffer.data(), numSamples); - currentSong->globalEffectable.processSRRAndBitcrushing(renderingBuffer.data(), numSamples, - &masterVolumeAdjustmentL, ¤tSong->paramManager); + currentSong->globalEffectable.processFilters(renderingBuffer); + currentSong->globalEffectable.processSRRAndBitcrushing(renderingBuffer, &masterVolumeAdjustmentL, + ¤tSong->paramManager); masterVolumeAdjustmentR = masterVolumeAdjustmentL; // This might have changed in the above function calls - currentSong->globalEffectable.processStutter(renderingBuffer.data(), numSamples, ¤tSong->paramManager); + currentSong->globalEffectable.processStutter(renderingBuffer, ¤tSong->paramManager); // And we do panning for song here too - must be post reverb, and we had to do a volume adjustment below // anyway @@ -953,9 +964,8 @@ void renderSongFX(size_t numSamples) { // LPF and stutter for song (must happen >> 1; // there used to be a static subtraction of 2 nepers (natural log based dB), this is the multiplicative // equivalent - currentSong->globalEffectable.compressor.render(renderingBuffer.data(), numSamples, - masterVolumeAdjustmentL >> 1, masterVolumeAdjustmentR >> 1, - songVolume >> 3); + currentSong->globalEffectable.compressor.render(renderingBuffer, masterVolumeAdjustmentL >> 1, + masterVolumeAdjustmentR >> 1, songVolume >> 3); masterVolumeAdjustmentL = ONE_Q31; masterVolumeAdjustmentR = ONE_Q31; logAction("mastercomp end"); @@ -1114,7 +1124,7 @@ bool doSomeOutputting() { // Copy to actual output buffer, and apply heaps of gain too, with clipping int32_t numSamplesOutputted = 0; - StereoSample* __restrict__ outputBufferForResampling = (StereoSample*)spareRenderingBuffer; + std::span outputBufferForResampling{reinterpret_cast(spareRenderingBuffer), 128 * 2}; StereoSample* __restrict__ renderingBufferOutputPosNow = renderingBufferOutputPos; int32_t* __restrict__ i2sTXBufferPosNow = (int32_t*)i2sTXBufferPos; int32_t* __restrict__ inputReadPos = (int32_t*)i2sRXBufferPos; @@ -1231,7 +1241,7 @@ bool doSomeOutputting() { } // Go through each SampleRecorder, feeding them audio - for (SampleRecorder* recorder = firstRecorder; recorder; recorder = recorder->next) { + for (SampleRecorder* recorder = firstRecorder; recorder != nullptr; recorder = recorder->next) { if (recorder->status >= RecorderStatus::FINISHED_CAPTURING_BUT_STILL_WRITING) { continue; @@ -1239,7 +1249,7 @@ bool doSomeOutputting() { // Recording final output if (recorder->mode == AudioInputChannel::OUTPUT) { - recorder->feedAudio((int32_t*)outputBufferForResampling, numSamplesOutputted); + recorder->feedAudio(outputBufferForResampling.first(numSamplesOutputted)); } // Recording from an input source @@ -1251,19 +1261,21 @@ bool doSomeOutputting() { uint32_t stopPos = (i2sRXBufferPos < (uint32_t)recorder->sourcePos) ? (uint32_t)getRxBufferEnd() : i2sRXBufferPos; - int32_t* streamToRecord = recorder->sourcePos; - int32_t numSamplesFeedingNow = + size_t numSamplesFeedingNow = (stopPos - (uint32_t)recorder->sourcePos) >> (2 + NUM_MONO_INPUT_CHANNELS_MAGNITUDE); // We also enforce a firm limit on how much to feed, to keep things sane. Any remaining will get // done next time. - numSamplesFeedingNow = std::min(numSamplesFeedingNow, 256_i32); + numSamplesFeedingNow = std::min(numSamplesFeedingNow, 256u); - if (recorder->mode == AudioInputChannel::RIGHT) { - streamToRecord++; - } + // this is extremely janky, but essentially because feedAudio only works on an interleaved stream of + // stereo samples, we can offset it one sample to get it to operate on the right channel + std::span streamToRecord = + (recorder->mode == AudioInputChannel::RIGHT) + ? std::span{reinterpret_cast(recorder->sourcePos + 1), numSamplesFeedingNow} + : std::span{reinterpret_cast(recorder->sourcePos), numSamplesFeedingNow}; - recorder->feedAudio(streamToRecord, numSamplesFeedingNow); + recorder->feedAudio(streamToRecord); recorder->sourcePos += numSamplesFeedingNow << NUM_MONO_INPUT_CHANNELS_MAGNITUDE; if (recorder->sourcePos >= getRxBufferEnd()) { diff --git a/src/deluge/processing/metronome/metronome.cpp b/src/deluge/processing/metronome/metronome.cpp index ef73a70643..d643cc3523 100644 --- a/src/deluge/processing/metronome/metronome.cpp +++ b/src/deluge/processing/metronome/metronome.cpp @@ -21,6 +21,7 @@ #include "modulation/params/param_set.h" #include "processing/engines/audio_engine.h" #include "storage/flash_storage.h" +#include "util/fixedpoint.h" Metronome::Metronome() { sounding = false; @@ -34,7 +35,7 @@ void Metronome::trigger(uint32_t newPhaseIncrement) { timeSinceTrigger = 0; } -void Metronome::render(StereoSample* buffer, uint16_t numSamples) { +void Metronome::render(std::span buffer) { if (!sounding) { return; } @@ -51,26 +52,16 @@ void Metronome::render(StereoSample* buffer, uint16_t numSamples) { } q31_t high = multiply_32x32_rshift32(metronomeVolume, volumePostFX); - StereoSample* thisSample = buffer; - StereoSample* bufferEnd = buffer + numSamples; - do { - - int32_t value; - if (phase < 2147483648u) { - value = high; - } - else { - value = -high; - } + for (StereoSample& sample : buffer) { + q31_t value = (phase <= ONE_Q31) ? high : -high; phase += phaseIncrement; - thisSample->l += value; - thisSample->r += value; - - } while (++thisSample != bufferEnd); + sample.l += value; + sample.r += value; + } - timeSinceTrigger += numSamples; + timeSinceTrigger += buffer.size(); if (timeSinceTrigger > 1000) { sounding = false; } diff --git a/src/deluge/processing/metronome/metronome.h b/src/deluge/processing/metronome/metronome.h index 381726630a..128769e5f8 100644 --- a/src/deluge/processing/metronome/metronome.h +++ b/src/deluge/processing/metronome/metronome.h @@ -19,6 +19,7 @@ #include #include +#include class StereoSample; @@ -26,7 +27,7 @@ class Metronome { public: Metronome(); void trigger(uint32_t newPhaseIncrement); - void render(StereoSample* buffer, uint16_t numSamples); + void render(std::span buffer); void setVolume(int32_t linearParam) { metronomeVolume = (exp(float(linearParam) / 200.0f) - 1.0) * float(1 << 27); } uint32_t phase; diff --git a/src/deluge/processing/sound/sound.cpp b/src/deluge/processing/sound/sound.cpp index 275d068ca6..9944941ef8 100644 --- a/src/deluge/processing/sound/sound.cpp +++ b/src/deluge/processing/sound/sound.cpp @@ -58,6 +58,8 @@ #include "util/firmware_version.h" #include "util/functions.h" #include "util/misc.h" +#include +#include namespace params = deluge::modulation::params; @@ -2173,9 +2175,9 @@ void Sound::stopParamLPF(ModelStackWithSoundFlags* modelStack) { } } -void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outputBuffer, int32_t numSamples, - int32_t* reverbBuffer, int32_t sideChainHitPending, int32_t reverbAmountAdjust, - bool shouldLimitDelayFeedback, int32_t pitchAdjust, SampleRecorder* recorder) { +void Sound::render(ModelStackWithThreeMainThings* modelStack, std::span output, int32_t* reverbBuffer, + int32_t sideChainHitPending, int32_t reverbAmountAdjust, bool shouldLimitDelayFeedback, + int32_t pitchAdjust, SampleRecorder* recorder) { if (skippingRendering) { compressor.gainReduction = 0; @@ -2194,14 +2196,14 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp // already cause a resync. Maybe tempo changes do too? If so, this could be part of // the resync logic. Note: same issue exists with LFO2 now that it supports sync. globalSourceValues[patchSourceLFOGlobalUnderlying] = - globalLFO.render(numSamples, lfoConfig[LFO1_ID], getGlobalLFOPhaseIncrement()); + globalLFO.render(output.size(), lfoConfig[LFO1_ID], getGlobalLFOPhaseIncrement()); uint32_t anyChange = (old != globalSourceValues[patchSourceLFOGlobalUnderlying]); sourcesChanged |= anyChange << patchSourceLFOGlobalUnderlying; } for (int s = 0; s < kNumSources; s++) { if (sources[s].oscType == OscType::DX7 and sources[s].dxPatch) { - sources[s].dxPatch->computeLfo(numSamples); + sources[s].dxPatch->computeLfo(output.size()); } } @@ -2215,7 +2217,7 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp int32_t old = globalSourceValues[patchSourceSidechainUnderlying]; globalSourceValues[patchSourceSidechainUnderlying] = sidechain.render( - numSamples, paramManager->getUnpatchedParamSet()->getValue(params::UNPATCHED_SIDECHAIN_SHAPE)); + output.size(), paramManager->getUnpatchedParamSet()->getValue(params::UNPATCHED_SIDECHAIN_SHAPE)); uint32_t anyChange = (old != globalSourceValues[patchSourceSidechainUnderlying]); sourcesChanged |= anyChange << patchSourceSidechainUnderlying; } @@ -2266,7 +2268,7 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp ArpReturnInstruction instruction; - getArp()->render(arpSettings, &instruction, numSamples, gateThreshold, phaseIncrement); + getArp()->render(arpSettings, &instruction, output.size(), gateThreshold, phaseIncrement); for (int32_t n = 0; n < ARP_MAX_INSTRUCTION_NOTES; n++) { if (instruction.noteCodeOffPostArp[n] == ARP_NOTE_NONE) { @@ -2294,7 +2296,7 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp delayWorkingState.delayFeedbackAmount = paramFinalValues[params::GLOBAL_DELAY_FEEDBACK - params::FIRST_GLOBAL]; if (shouldLimitDelayFeedback) { delayWorkingState.delayFeedbackAmount = - std::min(delayWorkingState.delayFeedbackAmount, (int32_t)(1 << 30) - (1 << 26)); + std::min(delayWorkingState.delayFeedbackAmount, (q31_t)(1 << 30) - (1 << 26)); } delayWorkingState.userDelayRate = paramFinalValues[params::GLOBAL_DELAY_RATE - params::FIRST_GLOBAL]; uint32_t timePerTickInverse = playbackHandler.getTimePerInternalTickInverse(true); @@ -2302,9 +2304,15 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp delayWorkingState.analog_saturation = 8; // Render each voice into a local buffer here - bool renderingInStereo = renderingVoicesInStereo(modelStackWithSoundFlags); - static int32_t soundBuffer[SSI_TX_BUFFER_NUM_SAMPLES * 2]; - memset(soundBuffer, 0, (numSamples * sizeof(int32_t)) << renderingInStereo); + bool voice_rendered_in_stereo = renderingVoicesInStereo(modelStackWithSoundFlags); + + // FIXME(@stellar-aria): if we have simulataneous sounds rendering, they'll overwrite + // each other in this buffer. It probably should be object or thread-local + alignas(CACHE_LINE_SIZE) static q31_t sound_memory[SSI_TX_BUFFER_NUM_SAMPLES * 2]; + memset(sound_memory, 0, output.size() * sizeof(q31_t) * (voice_rendered_in_stereo ? 2 : 1)); + + std::span sound_mono{sound_memory, output.size()}; + std::span sound_stereo{(StereoSample*)sound_memory, output.size()}; if (numVoicesAssigned) { @@ -2319,39 +2327,25 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp q31_t lpfFreq = getSmoothedPatchedParamValue(params::LOCAL_LPF_FREQ, paramManager); q31_t hpfMorph = getSmoothedPatchedParamValue(params::LOCAL_HPF_MORPH, paramManager); q31_t hpfFreq = getSmoothedPatchedParamValue(params::LOCAL_HPF_FREQ, paramManager); - bool doLPF = (thisHasFilters - && (lpfMode == FilterMode::TRANSISTOR_24DB_DRIVE - || paramManager->getPatchCableSet()->doesParamHaveSomethingPatchedToIt(params::LOCAL_LPF_FREQ) - || (lpfFreq < 2147483602) || (lpfMorph > -2147483648))); - bool doHPF = (thisHasFilters - && (paramManager->getPatchCableSet()->doesParamHaveSomethingPatchedToIt(params::LOCAL_HPF_FREQ) - || (hpfFreq != -2147483648) || (hpfMorph > -2147483648))); - // Each voice will potentially alter the "sources changed" flags, so store a backup to restore between each - // voice - /* - bool backedUpSourcesChanged[FIRST_UNCHANGEABLE_SOURCE - Local::FIRST_SOURCE]; - bool doneFirstVoice = false; - */ + bool doLPF = thisHasFilters + && (lpfMode == FilterMode::TRANSISTOR_24DB_DRIVE + || paramManager->getPatchCableSet()->doesParamHaveSomethingPatchedToIt(params::LOCAL_LPF_FREQ) + || (lpfFreq < 0x7FFFFFD2) || (lpfMorph > std::numeric_limits::min())); + bool doHPF = + thisHasFilters + && (paramManager->getPatchCableSet()->doesParamHaveSomethingPatchedToIt(params::LOCAL_HPF_FREQ) + || (hpfFreq != std::numeric_limits::min()) || (hpfMorph > std::numeric_limits::min())); int32_t ends[2]; AudioEngine::activeVoices.getRangeForSound(this, ends); for (int32_t v = ends[0]; v < ends[1]; v++) { Voice* thisVoice = AudioEngine::activeVoices.getVoice(v); - /* - if (!doneFirstVoice) { - if (numVoicesAssigned > 1) { - memcpy(backedUpSourcesChanged, &sourcesChanged[Local::FIRST_SOURCE], FIRST_UNCHANGEABLE_SOURCE - - Local::FIRST_SOURCE); doneFirstVoice = true; - } - } - else memcpy(&sourcesChanged[Local::FIRST_SOURCE], backedUpSourcesChanged, FIRST_UNCHANGEABLE_SOURCE - - Local::FIRST_SOURCE); - */ ModelStackWithVoice* modelStackWithVoice = modelStackWithSoundFlags->addVoice(thisVoice); - bool stillGoing = thisVoice->render(modelStackWithVoice, soundBuffer, numSamples, renderingInStereo, - applyingPanAtVoiceLevel, sourcesChanged, doLPF, doHPF, pitchAdjust); + bool stillGoing = + thisVoice->render(modelStackWithVoice, sound_mono.data(), sound_mono.size(), voice_rendered_in_stereo, + applyingPanAtVoiceLevel, sourcesChanged, doLPF, doHPF, pitchAdjust); if (!stillGoing) { AudioEngine::activeVoices.checkVoiceExists(thisVoice, this, "E201"); AudioEngine::unassignVoice(thisVoice, this, modelStackWithSoundFlags); @@ -2360,52 +2354,34 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp } } - // If just rendered in mono, double that up to stereo now - if (!renderingInStereo) { - // We know that nothing's patched to pan, so can read it in this very basic way. - int32_t pan = paramManager->getPatchedParamSet()->getValue(params::LOCAL_PAN) >> 1; + // We know that nothing's patched to pan, so can read it in this very basic way. + int32_t pan = paramManager->getPatchedParamSet()->getValue(params::LOCAL_PAN) >> 1; + int32_t amplitudeL, amplitudeR; + bool doPanning = AudioEngine::renderInStereo && shouldDoPanning(pan, &litudeL, &litudeR); - int32_t amplitudeL, amplitudeR; - bool doPanning; - doPanning = (AudioEngine::renderInStereo && shouldDoPanning(pan, &litudeL, &litudeR)); + // If just rendered in mono, double that up to stereo now + if (!voice_rendered_in_stereo) { if (doPanning) { - for (int32_t i = numSamples - 1; i >= 0; i--) { - int32_t sampleValue = soundBuffer[i]; - soundBuffer[(i << 1)] = multiply_32x32_rshift32(sampleValue, amplitudeL) << 2; - soundBuffer[(i << 1) + 1] = multiply_32x32_rshift32(sampleValue, amplitudeR) << 2; - } + // right to left because of in-place mono to stereo expansion + std::transform(sound_mono.rbegin(), sound_mono.rend(), sound_stereo.rbegin(), [=](q31_t sample) { + return StereoSample{ + .l = multiply_32x32_rshift32(sample, amplitudeL) << 2, + .r = multiply_32x32_rshift32(sample, amplitudeR) << 2, + }; + }); } else { - for (int32_t i = numSamples - 1; i >= 0; i--) { - int32_t sampleValue = soundBuffer[i]; - soundBuffer[(i << 1)] = sampleValue; - soundBuffer[(i << 1) + 1] = sampleValue; - } + // right to left because of in-place mono to stereo expansion + std::transform(sound_mono.rbegin(), sound_mono.rend(), sound_stereo.rbegin(), StereoSample::fromMono); } } // Or if rendered in stereo... - else { - // And if we're only applying pan here at the Sound level... - if (!applyingPanAtVoiceLevel) { - - // We know that nothing's patched to pan, so can read it in this very basic way. - int32_t pan = paramManager->getPatchedParamSet()->getValue(params::LOCAL_PAN) >> 1; - - int32_t amplitudeL, amplitudeR; - bool doPanning; - doPanning = (AudioEngine::renderInStereo && shouldDoPanning(pan, &litudeL, &litudeR)); - if (doPanning) { - int32_t* thisSample = soundBuffer; - int32_t* endSamples = thisSample + (numSamples << 1); - do { - *thisSample = multiply_32x32_rshift32(*thisSample, amplitudeL) << 2; - thisSample++; - - *thisSample = multiply_32x32_rshift32(*thisSample, amplitudeR) << 2; - thisSample++; - } while (thisSample != endSamples); - } + // And if we're only applying pan here at the Sound level... + else if (!applyingPanAtVoiceLevel && doPanning) { + for (StereoSample& sample : sound_stereo) { + sample.l = multiply_32x32_rshift32(sample.l, amplitudeL) << 2; + sample.r = multiply_32x32_rshift32(sample.r, amplitudeR) << 2; } } } @@ -2414,8 +2390,9 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp reassessRenderSkippingStatus(modelStackWithSoundFlags); } - if (!renderingInStereo) { - memset(&soundBuffer[numSamples], 0, numSamples * sizeof(int32_t)); + if (!voice_rendered_in_stereo) { + // Clear the non-overlapping portion of the stereo buffer (yes this is janky) + memset(&sound_mono[sound_mono.size()], 0, sound_stereo.size_bytes() - sound_mono.size_bytes()); } } @@ -2429,18 +2406,17 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp int32_t modFXDepth = paramFinalValues[params::GLOBAL_MOD_FX_DEPTH - params::FIRST_GLOBAL]; int32_t modFXRate = paramFinalValues[params::GLOBAL_MOD_FX_RATE - params::FIRST_GLOBAL]; - processSRRAndBitcrushing((StereoSample*)soundBuffer, numSamples, &postFXVolume, paramManager); - processFX((StereoSample*)soundBuffer, numSamples, modFXType_, modFXRate, modFXDepth, delayWorkingState, - &postFXVolume, paramManager, numVoicesAssigned != 0, reverbSendAmount >> 1); - processStutter((StereoSample*)soundBuffer, numSamples, paramManager); + processSRRAndBitcrushing(sound_stereo, &postFXVolume, paramManager); + processFX(sound_stereo, modFXType_, modFXRate, modFXDepth, delayWorkingState, &postFXVolume, paramManager, + numVoicesAssigned != 0, reverbSendAmount >> 1); + processStutter(sound_stereo, paramManager); - processReverbSendAndVolume((StereoSample*)soundBuffer, numSamples, reverbBuffer, postFXVolume, postReverbVolume, - reverbSendAmount, 0, true); + processReverbSendAndVolume(sound_stereo, reverbBuffer, postFXVolume, postReverbVolume, reverbSendAmount, 0, true); q31_t compThreshold = paramManager->getUnpatchedParamSet()->getValue(params::UNPATCHED_COMPRESSOR_THRESHOLD); compressor.setThreshold(compThreshold); if (compThreshold > 0) { - compressor.renderVolNeutral((StereoSample*)soundBuffer, numSamples, postFXVolume); + compressor.renderVolNeutral(sound_stereo, postFXVolume); } else { compressor.reset(); @@ -2448,9 +2424,11 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp if (recorder && recorder->status < RecorderStatus::FINISHED_CAPTURING_BUT_STILL_WRITING) { // we need to double it because for reasons I don't understand audio clips max volume is half the sample volume - recorder->feedAudio(soundBuffer, numSamples, true, 2); + recorder->feedAudio(sound_stereo, true, 2); } - addAudio((StereoSample*)soundBuffer, outputBuffer, numSamples); + + // add the sound to the output, i.e. output = output + sound + std::ranges::transform(output, sound_stereo, output.begin(), std::plus{}); postReverbVolumeLastTime = postReverbVolume; @@ -2466,7 +2444,7 @@ void Sound::render(ModelStackWithThreeMainThings* modelStack, StereoSample* outp reassessRenderSkippingStatus(modelStackWithSoundFlags); } - doParamLPF(numSamples, modelStackWithSoundFlags); + doParamLPF(output.size(), modelStackWithSoundFlags); } // This is virtual, and gets extended by drums! diff --git a/src/deluge/processing/sound/sound.h b/src/deluge/processing/sound/sound.h index 3de9ff586d..01ec464ffb 100644 --- a/src/deluge/processing/sound/sound.h +++ b/src/deluge/processing/sound/sound.h @@ -162,8 +162,8 @@ class Sound : public ModControllableAudio { void patchedParamPresetValueChanged(uint8_t p, ModelStackWithSoundFlags* modelStack, int32_t oldValue, int32_t newValue); - void render(ModelStackWithThreeMainThings* modelStack, StereoSample* outputBuffer, int32_t numSamples, - int32_t* reverbBuffer, int32_t sideChainHitPending, int32_t reverbAmountAdjust = 134217728, + void render(ModelStackWithThreeMainThings* modelStack, std::span output, int32_t* reverbBuffer, + int32_t sideChainHitPending, int32_t reverbAmountAdjust = 134217728, bool shouldLimitDelayFeedback = false, int32_t pitchAdjust = kMaxSampleValue, SampleRecorder* recorder = nullptr); void unassignAllVoices(); diff --git a/src/deluge/processing/sound/sound_instrument.cpp b/src/deluge/processing/sound/sound_instrument.cpp index a1027ef538..aa54508574 100644 --- a/src/deluge/processing/sound/sound_instrument.cpp +++ b/src/deluge/processing/sound/sound_instrument.cpp @@ -17,6 +17,7 @@ #include "processing/sound/sound_instrument.h" #include "definitions_cxx.hpp" +#include "dsp/stereo_sample.h" #include "gui/views/view.h" #include "model/clip/instrument_clip.h" #include "model/model_stack.h" @@ -89,9 +90,9 @@ void SoundInstrument::cutAllSound() { Sound::unassignAllVoices(); } -void SoundInstrument::renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, - int32_t numSamples, int32_t* reverbBuffer, int32_t reverbAmountAdjust, - int32_t sideChainHitPending, bool shouldLimitDelayFeedback, bool isClipActive) { +void SoundInstrument::renderOutput(ModelStack* modelStack, std::span output, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, + bool shouldLimitDelayFeedback, bool isClipActive) { // this should only happen in the rare case that this is called while replacing an instrument but after the clips // have been cleared if (!activeClip) [[unlikely]] { @@ -106,8 +107,8 @@ void SoundInstrument::renderOutput(ModelStack* modelStack, StereoSample* startPo compressor.gainReduction = 0; } else { - Sound::render(modelStackWithThreeMainThings, startPos, numSamples, reverbBuffer, sideChainHitPending, - reverbAmountAdjust, shouldLimitDelayFeedback, kMaxSampleValue, recorder); + Sound::render(modelStackWithThreeMainThings, output, reverbBuffer, sideChainHitPending, reverbAmountAdjust, + shouldLimitDelayFeedback, kMaxSampleValue, recorder); } if (playbackHandler.isEitherClockActive() && !playbackHandler.ticksLeftInCountIn && isClipActive) { @@ -126,7 +127,7 @@ void SoundInstrument::renderOutput(ModelStack* modelStack, StereoSample* startPo } if (anyInterpolating) { yesTickParamManagerForClip: - modelStackWithThreeMainThings->paramManager->toForTimeline()->tickSamples(numSamples, + modelStackWithThreeMainThings->paramManager->toForTimeline()->tickSamples(output.size(), modelStackWithThreeMainThings); } else { @@ -193,7 +194,7 @@ void SoundInstrument::renderOutput(ModelStack* modelStack, StereoSample* startPo if (result) { modelStackWithThreeMainThings->setNoteRow(thisNoteRow, thisNoteRow->y); modelStackWithThreeMainThings->paramManager = &thisNoteRow->paramManager; - thisNoteRow->paramManager.tickSamples(numSamples, modelStackWithThreeMainThings); + thisNoteRow->paramManager.tickSamples(output.size(), modelStackWithThreeMainThings); } } } diff --git a/src/deluge/processing/sound/sound_instrument.h b/src/deluge/processing/sound/sound_instrument.h index 527c88396f..8dd1be5913 100644 --- a/src/deluge/processing/sound/sound_instrument.h +++ b/src/deluge/processing/sound/sound_instrument.h @@ -35,9 +35,9 @@ class SoundInstrument final : public Sound, public MelodicInstrument { void cutAllSound() override; bool noteIsOn(int32_t noteCode, bool resetTimeEntered); - void renderOutput(ModelStack* modelStack, StereoSample* startPos, StereoSample* endPos, int32_t numSamples, - int32_t* reverbBuffer, int32_t reverbAmountAdjust, int32_t sideChainHitPending, - bool shouldLimitDelayFeedback, bool isClipActive) override; + void renderOutput(ModelStack* modelStack, std::span buffer, int32_t* reverbBuffer, + int32_t reverbAmountAdjust, int32_t sideChainHitPending, bool shouldLimitDelayFeedback, + bool isClipActive) override; // A timelineCounter is required void offerReceivedCCToLearnedParams(MIDICable& cable, uint8_t channel, uint8_t ccNumber, uint8_t value, diff --git a/src/deluge/util/functions.cpp b/src/deluge/util/functions.cpp index f48795f1fa..4a387e9e84 100644 --- a/src/deluge/util/functions.cpp +++ b/src/deluge/util/functions.cpp @@ -254,20 +254,6 @@ int32_t getFinalParameterValueExpWithDumbEnvelopeHack(int32_t paramNeutralValue, return getFinalParameterValueExp(paramNeutralValue, patchedValue); } -void addAudio(StereoSample* inputBuffer, StereoSample* outputBuffer, int32_t numSamples) { - StereoSample* inputSample = inputBuffer; - StereoSample* outputSample = outputBuffer; - - StereoSample* inputBufferEnd = inputBuffer + numSamples; - - do { - outputSample->l += inputSample->l; - outputSample->r += inputSample->r; - - outputSample++; - } while (++inputSample != inputBufferEnd); -} - int32_t cableToLinearParamShortcut(int32_t sourceValue) { return sourceValue >> 2; } diff --git a/src/deluge/util/functions.h b/src/deluge/util/functions.h index 9a712e6a5f..934f8cf933 100644 --- a/src/deluge/util/functions.h +++ b/src/deluge/util/functions.h @@ -218,8 +218,6 @@ int32_t getFinalParameterValueHybrid(int32_t paramNeutralValue, int32_t patchedV int32_t getFinalParameterValueExp(int32_t paramNeutralValue, int32_t patchedValue); int32_t getFinalParameterValueExpWithDumbEnvelopeHack(int32_t paramNeutralValue, int32_t patchedValue, int32_t p); -void addAudio(StereoSample* inputBuffer, StereoSample* outputBuffer, int32_t numSamples); - char const* getSourceDisplayNameForOLED(PatchSource s); char const* sourceToString(PatchSource source);