| 2039 | } |
| 2040 | |
| 2041 | void FireAudioProcessor::applyDownsamplingEffect(juce::AudioBuffer<float>& buffer, const juce::AudioBuffer<float>& lfoOutputs) |
| 2042 | { |
| 2043 | // First, check if the entire effect is bypassed. |
| 2044 | if (! (*treeState.getRawParameterValue(DOWNSAMPLE_BYPASS_ID) > 0.5f)) |
| 2045 | return; |
| 2046 | |
| 2047 | // --- 1. Prepare Dry Signal & Mixer --- |
| 2048 | // A copy of the original signal is needed for the dry/wet mix. |
| 2049 | juce::AudioBuffer<float> dryBuffer; |
| 2050 | dryBuffer.makeCopyOf(buffer); |
| 2051 | |
| 2052 | // Set up the mixer with the correct wet proportion from its parameter. |
| 2053 | lofiMixer.setWetMixProportion(lfoManager->getModulatedValue(DOWNSAMPLE_MIX_ID)); |
| 2054 | lofiMixer.pushDrySamples(juce::dsp::AudioBlock<float>(dryBuffer)); |
| 2055 | |
| 2056 | // --- 2. Get All Parameter Values Once Per Block --- |
| 2057 | const int bits = static_cast<int>(lfoManager->getModulatedValue(BIT_DEPTH_ID)); |
| 2058 | const float jitter = lfoManager->getModulatedValue(JITTER_ID); |
| 2059 | const float rateReduceValue = lfoManager->getModulatedValue(DOWNSAMPLE_ID); |
| 2060 | |
| 2061 | // --- 3. Process Audio --- |
| 2062 | for (int channel = 0; channel < getTotalNumInputChannels(); ++channel) |
| 2063 | { |
| 2064 | auto* channelData = buffer.getWritePointer(channel); |
| 2065 | int samplesToHold = 0; |
| 2066 | float sampleToHold = 0.0f; |
| 2067 | |
| 2068 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) |
| 2069 | { |
| 2070 | // --- Rate Reduction (Sample & Hold) --- |
| 2071 | if (samplesToHold <= 0) |
| 2072 | { |
| 2073 | // It's time to grab a new sample. |
| 2074 | sampleToHold = channelData[sample]; |
| 2075 | |
| 2076 | // Determine the hold duration for this new sample. |
| 2077 | float currentRateReduce = rateReduceValue; |
| 2078 | |
| 2079 | // Apply Jitter if the parameter is active. |
| 2080 | if (jitter > 0.0f) |
| 2081 | { |
| 2082 | // Introduce a random variation to the hold time. |
| 2083 | // random.nextFloat() returns [0, 1]. We map it to [-1, 1]. |
| 2084 | float randomFactor = 1.0f + (random.nextFloat() * 2.0f - 1.0f) * jitter; |
| 2085 | currentRateReduce *= randomFactor; |
| 2086 | } |
| 2087 | |
| 2088 | // Set how many samples we need to hold for. Must be at least 1. |
| 2089 | samplesToHold = juce::jmax(1, static_cast<int>(currentRateReduce)); |
| 2090 | } |
| 2091 | |
| 2092 | // Output the held sample. |
| 2093 | channelData[sample] = sampleToHold; |
| 2094 | samplesToHold--; |
| 2095 | |
| 2096 | // --- Bit Crushing --- |
| 2097 | // Apply this effect after the sample has been selected (or held). |
| 2098 | if (bits < 32) |
no test coverage detected