// FIR-based early reflections with 6 taps and HRTF for binaural stereo. Based on Dattorro #include "EarlyReflections.h" EarlyReflections::EarlyReflections() = default; EarlyReflections::~EarlyReflections() = default; void EarlyReflections::prepare(const juce::dsp::ProcessSpec& spec) { juce::dsp::ProcessSpec monoSpec; monoSpec.sampleRate = spec.sampleRate; monoSpec.maximumBlockSize = spec.maximumBlockSize; monoSpec.numChannels = 1; earlyReflectionsDelayLine.prepare(monoSpec); // leftHRTFDelay.prepare(monoSpec); // rightHRTFDelay.prepare(monoSpec); // leftHRTFFilter.prepare(monoSpec); // rightHRTFFilter.prepare(monoSpec); hrtfDelays.resize(spec.numChannels); hrtfFilters.resize(spec.numChannels); channelOutputs.resize(spec.numChannels, 0.0); for (auto& delay : hrtfDelays) { delay.prepare(monoSpec); delay.setMaximumDelayInSamples(441); } for (auto& filter : hrtfFilters) filter.prepare(monoSpec); } void EarlyReflections::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; int numSamples = buffer.getNumSamples(); int numChannels = buffer.getNumChannels(); juce::AudioBuffer monoBuffer(1, numSamples); monoBuffer.clear(); monoBuffer.copyFrom(0, 0, buffer, 0, 0, numSamples); if (numChannels > 1) { monoBuffer.addFrom(0, 0, buffer, 1, 0, numSamples); monoBuffer.applyGain(0.5f); } // leftHRTFDelay.setDelay(35); // rightHRTFDelay.setDelay(35); for (auto& delay : hrtfDelays) delay.setDelay(35); auto* channelData = monoBuffer.getWritePointer(0); for (int sample = 0; sample < numSamples; ++sample) { earlyReflectionsDelayLine.pushSample(0, channelData[sample]); for (int channel = 0; channel < numChannels; ++channel) { for (int delTime = 0; delTime < hrtfDelayTimes.size(); ++delTime) { // sum 3 left/ 3 right taps (interleaved w/ each other) // channel % 2 because could be many channels, but only 2 lists of delay times channelOutputs[channel] = earlyReflectionsDelayLine.getSampleAtDelay( 0, hrtfDelayTimes[channel % 2][delTime] * parameters.roomSize) * initialLevel * pow(parameters.decayTime, delTime); } } for (int channel = 0; channel < numChannels; ++channel) { if (!monoFlag) { // right into left HRTF and vice versa hrtfDelays[channel].pushSample(0, channelOutputs[(channel + 1) % numChannels]); // filter HRTFs and add to outputs channelOutputs[channel] += hrtfFilters[channel].processSample(0, hrtfDelays[channel].popSample(0)); } // outputs into original stereo buffer buffer.setSample(0, sample, channelOutputs[channel]); } } // for (int sample = 0; sample < numSamples; ++sample) // { // earlyReflectionsDelayLine.pushSample(0, channelData[sample]); // // sum left 3 taps // channel0Output = earlyReflectionsDelayLine.getSampleAtDelay(0, 441 * parameters.roomSize) * initialLevel; // channel0Output += earlyReflectionsDelayLine.getSampleAtDelay(0, 2929 * parameters.roomSize) * initialLevel * // parameters.decayTime; channel0Output += earlyReflectionsDelayLine.getSampleAtDelay(0, 6319 * // parameters.roomSize) * initialLevel * pow(parameters.decayTime, 2); // // sum right 3 taps (interleaved w/ left) // channel1Output = earlyReflectionsDelayLine.getSampleAtDelay(0, 1191 * parameters.roomSize) * initialLevel; // channel1Output += earlyReflectionsDelayLine.getSampleAtDelay(0, 3948 * parameters.roomSize) * initialLevel * // parameters.decayTime; channel1Output += earlyReflectionsDelayLine.getSampleAtDelay(0, 9462 * // parameters.roomSize) * initialLevel * pow(parameters.decayTime, 2); // if (!monoFlag) // { // // right into left HRTF and vice versa // leftHRTFDelay.pushSample(0, channel1Output); // rightHRTFDelay.pushSample(0, channel0Output); // // filter HRTFs and add to outputs // channel0Output += leftHRTFFilter.processSample(0, leftHRTFDelay.popSample(0)); // channel1Output += rightHRTFFilter.processSample(0, rightHRTFDelay.popSample(0)); // } // // outputs into original stereo buffer // for (int destChannel = 0; destChannel < numChannels; ++destChannel) // { // if (destChannel == 0) // buffer.setSample(0, sample, channel0Output); // else if (destChannel == 1) // buffer.setSample(1, sample, channel1Output); // } // } } void EarlyReflections::reset() { earlyReflectionsDelayLine.reset(); for (auto& delay : hrtfDelays) delay.reset(); for (auto& filter : hrtfFilters) filter.reset(); // leftHRTFDelay.reset(); // rightHRTFDelay.reset(); // leftHRTFFilter.reset(); // rightHRTFFilter.reset(); } ReverbProcessorParameters& EarlyReflections::getParameters() { return parameters; } void EarlyReflections::setParameters(const ReverbProcessorParameters& params) { if (!(params == parameters)) { parameters = params; parameters.roomSize = scale(parameters.roomSize, 0.0f, 1.0f, 0.25f, 1.75f); } } void EarlyReflections::setMonoFlag(const bool newMonoFlag) { monoFlag = newMonoFlag; }