#include "PluginProcessor.h" #include "PluginEditor.h" // --------------------------------------------------------------------------- class RMX19AudioProcessor::FxEngine { public: void prepare (double sampleRate, int maxSamples) { sr = sampleRate; chorusL.prepare ({ sampleRate, 1u, (juce::uint32) maxSamples }); chorusR.prepare ({ sampleRate, 1u, (juce::uint32) maxSamples }); delL.prepare ({ sampleRate, 1u, (juce::uint32) maxSamples }); delR.prepare ({ sampleRate, 1u, (juce::uint32) maxSamples }); const int maxDelay = (int) (sampleRate * 1.5); chorusL.setMaximumDelayInSamples (maxDelay); chorusR.setMaximumDelayInSamples (maxDelay); delL.setMaximumDelayInSamples (maxDelay); delR.setMaximumDelayInSamples (maxDelay); } void reset() { chorusL.reset(); chorusR.reset(); delL.reset(); delR.reset(); chPh = 0.0; } void process (const VParams& p, float* L, float* R, int num) { const int type = (int) p.fxType; if (type == 0) return; const float wet = p.fxAmt; const float dry = 1.0f - wet * 0.5f; const float chBase = 0.008f * (float) sr; // 8 ms chorus base const float chDepth = 0.0035f * (float) sr; // +-3.5 ms wobble const float delSamples = (float) (p.fxTime * sr); const float feedback = 0.40f; for (int s = 0; s < num; ++s) { const float inL = L[s], inR = R[s]; float outL = inL * dry, outR = inR * dry; if (type == 1 || type == 3) { const double rate = type == 3 ? 0.55 : 0.38; chPh += rate / sr; if (chPh >= 1.0) chPh -= 1.0; const float m = 0.5f + 0.5f * (float) std::sin (chPh * 2.0 * juce::MathConstants::pi); chorusL.pushSample (0, inL); chorusR.pushSample (0, inR); const float mL = chorusL.popSample (0, chBase + chDepth * m); const float mR = chorusR.popSample (0, chBase + chDepth * (1.0f - m)); outL += mL * wet * 0.7f; outR += mR * wet * 0.7f; } if (type == 2 || type == 3) { const float rL = delL.popSample (0, delSamples); const float rR = delR.popSample (0, delSamples); delL.pushSample (0, inL + rR * feedback); delR.pushSample (0, inR + rL * feedback); outL += rL * wet; outR += rR * wet; } L[s] = outL; R[s] = outR; } } private: juce::dsp::DelayLine chorusL, chorusR, delL, delR; double sr = 48000.0; double chPh = 0.0; }; // --------------------------------------------------------------------------- RMX19AudioProcessor::RMX19AudioProcessor() : AudioProcessor (BusesProperties().withOutput ("Output", juce::AudioChannelSet::stereo(), true)) , apvts (*this, nullptr, "Params", createParams()) { } RMX19AudioProcessor::~RMX19AudioProcessor() {} juce::AudioProcessorValueTreeState::ParameterLayout RMX19AudioProcessor::createParams() { return prm::createLayout(); } const juce::String RMX19AudioProcessor::getName() const { return "RMX-19"; } void RMX19AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { engine.setSampleRate (sampleRate); engine.allNotesOff (true); fx = std::make_unique(); fx->prepare (sampleRate, samplesPerBlock); } void RMX19AudioProcessor::releaseResources() { engine.allNotesOff (true); if (fx) fx->reset(); } void RMX19AudioProcessor::processMidi (juce::MidiBuffer& midi, const VParams& p) { (void) p; constexpr double bendSemis = 2.0; for (const auto meta : midi) { const auto msg = meta.getMessage(); lastMidi = juce::Time::getMillisecondCounter(); if (msg.isNoteOn()) { engine.noteOn (msg.getNoteNumber(), msg.getFloatVelocity()); } else if (msg.isNoteOff()) { engine.noteOff (msg.getNoteNumber()); } else if (msg.isPitchWheel()) { engine.pitchBend ((float) ((msg.getPitchWheelValue() - 8192) / 8192.0 * bendSemis * 100.0)); } else if (msg.isController()) { const int cc = msg.getControllerNumber(); const float v = msg.getControllerValue() / 127.0f; switch (cc) { case 64: engine.sustainPedal (msg.getControllerValue() >= 64); break; case 1: engine.modWheel (v); break; case 7: if (auto* par = apvts.getParameter ("vol")) par->setValueNotifyingHost (v); break; case 10: if (auto* par = apvts.getParameter ("pan")) par->setValueNotifyingHost (v * 2.0f - 1.0f); break; case 71: if (auto* par = apvts.getParameter ("fltres")) par->setValueNotifyingHost (v); break; case 72: if (auto* par = apvts.getParameter ("ampR")) par->setValueNotifyingHost (v); break; case 73: if (auto* par = apvts.getParameter ("ampA")) par->setValueNotifyingHost (v); break; case 74: if (auto* par = apvts.getParameter ("fltcut")) par->setValueNotifyingHost (v); break; case 76: if (auto* par = apvts.getParameter ("lforate")) par->setValueNotifyingHost (v); break; case 77: if (auto* par = apvts.getParameter ("lfodepth"))par->setValueNotifyingHost (v); break; case 91: if (auto* par = apvts.getParameter ("fxamt")) par->setValueNotifyingHost (v); break; default: break; } } } } void RMX19AudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midi) { juce::ScopedNoDenormals noDenormals; const juce::uint32 start = juce::Time::getMillisecondCounter(); const int numSamples = buffer.getNumSamples(); const int numChannels = buffer.getNumChannels(); if (numSamples == 0) return; VParams p; p.update (apvts); // patch follow const int sel = (int) apvts.getRawParameterValue ("patchsel")->load(); if (sel != lastPatchParam && sel >= 0 && sel < bank.size()) { lastPatchParam = sel; patchIndex = sel; bank.writeTo (sel, apvts); } processMidi (midi, p); const int chL = 0, chR = 1; float* L = buffer.getNumChannels() > 0 ? buffer.getWritePointer (juce::jmin (chL, numChannels - 1)) : nullptr; float* R = buffer.getNumChannels() > 1 ? buffer.getWritePointer (juce::jmin (chR, numChannels - 1)) : nullptr; for (int c = 0; c < numChannels; ++c) buffer.clear (c, 0, numSamples); if (L != nullptr && R != nullptr) { engine.render (p, L, R, numSamples); if (fx) fx->process (p, L, R, numSamples); if (p.vol != 1.0f) buffer.applyGain (p.vol); } else if (L != nullptr) { juce::AudioBuffer dummy (1, numSamples); dummy.clear(); float* dL = dummy.getWritePointer (0); float* dR = dummy.getWritePointer (0); engine.render (p, dL, dR, numSamples); for (int c = 0; c < numChannels; ++c) buffer.copyFrom (c, 0, dummy, 0, 0, numSamples); buffer.applyGain (p.vol); } cpu = 0.7f * cpu + 0.3f * (float) (juce::Time::getMillisecondCounter() - start); } void RMX19AudioProcessor::selectPatch (int i) { i = juce::jlimit (0, bank.size() - 1, i); patchIndex = i; lastPatchParam = i; if (auto* par = apvts.getParameter ("patchsel")) par->setValueNotifyingHost (par->convertTo0to1 ((float) i)); bank.writeTo (i, apvts); } int RMX19AudioProcessor::getNumPrograms() { return bank.size(); } int RMX19AudioProcessor::getCurrentProgram() { return patchIndex; } void RMX19AudioProcessor::setCurrentProgram (int index) { selectPatch (index); } const juce::String RMX19AudioProcessor::getProgramName (int index) { if (index >= 0 && index < bank.size()) return bank.get (index).name; return {}; } void RMX19AudioProcessor::changeProgramName (int, const juce::String&) {} void RMX19AudioProcessor::getStateInformation (juce::MemoryBlock& destData) { auto state = apvts.copyState(); std::unique_ptr xml (state.createXml()); copyXmlToBinary (*xml, destData); } void RMX19AudioProcessor::setStateInformation (const void* data, int sizeInBytes) { std::unique_ptr xml (getXmlFromBinary (data, sizeInBytes)); if (xml == nullptr) return; apvts.replaceState (juce::ValueTree::fromXml (*xml)); const int sel = juce::jlimit (0, bank.size() - 1, (int) apvts.getRawParameterValue ("patchsel")->load()); lastPatchParam = sel; patchIndex = sel; } juce::AudioProcessorEditor* RMX19AudioProcessor::createEditor() { return new RackEditor (*this); } bool RMX19AudioProcessor::hasEditor() const { return true; } juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new RMX19AudioProcessor(); }