#include "PluginProcessor.h" #include "PluginEditor.h" ChromaFlockProcessor::ChromaFlockProcessor() : AudioProcessor(BusesProperties() .withOutput("Output", juce::AudioChannelSet::stereo(), true)), apvts(*this, nullptr, "Parameters", createParameterLayout()) { synth.addSound(new SubtractiveSound()); for (int i = 0; i < maxVoices; ++i) synth.addVoice(new SubtractiveVoice()); } ChromaFlockProcessor::~ChromaFlockProcessor() {} juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::createParameterLayout() { juce::AudioProcessorValueTreeState::ParameterLayout layout; auto zeroOne = juce::NormalisableRange(0.0f, 1.0f, 0.01f); // OSC 1 layout.add(std::make_unique( juce::ParameterID{"osc1Wave", 1}, "OSC1 Wave", juce::StringArray{"Sine", "Saw", "Square", "Triangle", "Noise"}, 1)); layout.add(std::make_unique( juce::ParameterID{"osc1Oct", 1}, "OSC1 Octave", juce::NormalisableRange(-3.0f, 3.0f, 1.0f), 0.0f)); layout.add(std::make_unique( juce::ParameterID{"osc1Semi", 1}, "OSC1 Semi", juce::NormalisableRange(-12.0f, 12.0f, 1.0f), 0.0f)); layout.add(std::make_unique( juce::ParameterID{"osc1Fine", 1}, "OSC1 Fine", juce::NormalisableRange(-100.0f, 100.0f, 1.0f), 0.0f)); layout.add(std::make_unique( juce::ParameterID{"osc1Level", 1}, "OSC1 Level", zeroOne, 0.7f)); // OSC 2 layout.add(std::make_unique( juce::ParameterID{"osc2Wave", 1}, "OSC2 Wave", juce::StringArray{"Sine", "Saw", "Square", "Triangle", "Noise"}, 2)); layout.add(std::make_unique( juce::ParameterID{"osc2Oct", 1}, "OSC2 Octave", juce::NormalisableRange(-3.0f, 3.0f, 1.0f), -1.0f)); layout.add(std::make_unique( juce::ParameterID{"osc2Semi", 1}, "OSC2 Semi", juce::NormalisableRange(-12.0f, 12.0f, 1.0f), 0.0f)); layout.add(std::make_unique( juce::ParameterID{"osc2Fine", 1}, "OSC2 Fine", juce::NormalisableRange(-100.0f, 100.0f, 1.0f), 7.0f)); layout.add(std::make_unique( juce::ParameterID{"osc2Level", 1}, "OSC2 Level", zeroOne, 0.7f)); layout.add(std::make_unique( juce::ParameterID{"phaseOffset", 1}, "Phase Offset", zeroOne, 0.5f)); // FILTER layout.add(std::make_unique( juce::ParameterID{"filterType", 1}, "Filter Type", juce::StringArray{"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"}, 0)); layout.add(std::make_unique( juce::ParameterID{"filterCutoff", 1}, "Filter Cutoff", juce::NormalisableRange(50.0f, 20000.0f, 0.1f, 0.25f), 8000.0f)); layout.add(std::make_unique( juce::ParameterID{"filterRes", 1}, "Filter Resonance", zeroOne, 0.7f)); layout.add(std::make_unique( juce::ParameterID{"filterEnvAmt", 1}, "Filter Env Amount", zeroOne, 0.0f)); layout.add(std::make_unique( juce::ParameterID{"keyTrack", 1}, "Key Tracking", zeroOne, 0.5f)); // FILTER ENVELOPE layout.add(std::make_unique( juce::ParameterID{"fEnvAttack", 1}, "Filter Attack", juce::NormalisableRange(0.001f, 5.0f, 0.001f, 0.3f), 0.01f)); layout.add(std::make_unique( juce::ParameterID{"fEnvDecay", 1}, "Filter Decay", juce::NormalisableRange(0.001f, 5.0f, 0.001f, 0.3f), 0.3f)); layout.add(std::make_unique( juce::ParameterID{"fEnvSustain", 1}, "Filter Sustain", zeroOne, 0.5f)); layout.add(std::make_unique( juce::ParameterID{"fEnvRelease", 1}, "Filter Release", juce::NormalisableRange(0.001f, 10.0f, 0.001f, 0.3f), 0.5f)); // AMP ENVELOPE layout.add(std::make_unique( juce::ParameterID{"envAttack", 1}, "Attack", juce::NormalisableRange(0.001f, 5.0f, 0.001f, 0.3f), 0.01f)); layout.add(std::make_unique( juce::ParameterID{"envDecay", 1}, "Decay", juce::NormalisableRange(0.001f, 5.0f, 0.001f, 0.3f), 0.3f)); layout.add(std::make_unique( juce::ParameterID{"envSustain", 1}, "Sustain", zeroOne, 0.7f)); layout.add(std::make_unique( juce::ParameterID{"envRelease", 1}, "Release", juce::NormalisableRange(0.001f, 10.0f, 0.001f, 0.3f), 0.5f)); // GLOBAL layout.add(std::make_unique( juce::ParameterID{"pan", 1}, "Pan", juce::NormalisableRange(-1.0f, 1.0f, 0.01f), 0.0f)); layout.add(std::make_unique( juce::ParameterID{"drive", 1}, "Drive", juce::NormalisableRange(1.0f, 5.0f, 0.01f, 0.5f), 1.5f)); layout.add(std::make_unique( juce::ParameterID{"masterLevel", 1}, "Master Level", zeroOne, 0.8f)); // PITCH BEND layout.add(std::make_unique( juce::ParameterID{"pitchBendRange", 1}, "Pitch Bend Range", juce::NormalisableRange(1.0f, 12.0f, 1.0f), 2.0f)); // DISTORTION layout.add(std::make_unique( juce::ParameterID{"distType", 1}, "Dist Type", juce::StringArray{"Soft Clip", "Hard Clip", "Foldback", "Overdrive"}, 0)); layout.add(std::make_unique( juce::ParameterID{"distAmount", 1}, "Dist Amount", zeroOne, 0.0f)); layout.add(std::make_unique( juce::ParameterID{"distMix", 1}, "Dist Mix", zeroOne, 0.0f)); // COMPRESSOR layout.add(std::make_unique( juce::ParameterID{"compThreshold", 1}, "Comp Threshold", juce::NormalisableRange(-60.0f, 0.0f, 0.1f), -20.0f)); layout.add(std::make_unique( juce::ParameterID{"compRatio", 1}, "Comp Ratio", juce::NormalisableRange(1.0f, 20.0f, 0.1f), 4.0f)); layout.add(std::make_unique( juce::ParameterID{"compAttack", 1}, "Comp Attack", juce::NormalisableRange(0.1f, 100.0f, 0.1f), 10.0f)); layout.add(std::make_unique( juce::ParameterID{"compRelease", 1}, "Comp Release", juce::NormalisableRange(10.0f, 1000.0f, 1.0f), 100.0f)); layout.add(std::make_unique( juce::ParameterID{"compMakeup", 1}, "Comp Makeup", juce::NormalisableRange(0.0f, 24.0f, 0.1f), 0.0f)); layout.add(std::make_unique( juce::ParameterID{"compMix", 1}, "Comp Mix", zeroOne, 1.0f)); // AUTO-PAN layout.add(std::make_unique( juce::ParameterID{"autoPanRate", 1}, "Pan Rate", juce::NormalisableRange(0.05f, 20.0f, 0.01f, 0.4f), 2.0f)); layout.add(std::make_unique( juce::ParameterID{"autoPanDepth", 1}, "Pan Depth", zeroOne, 0.0f)); // LFO 1 layout.add(std::make_unique( juce::ParameterID{"lfo1Rate", 1}, "LFO1 Rate", juce::NormalisableRange(0.05f, 20.0f, 0.01f, 0.4f), 2.0f)); layout.add(std::make_unique( juce::ParameterID{"lfo1Depth", 1}, "LFO1 Depth", zeroOne, 0.0f)); layout.add(std::make_unique( juce::ParameterID{"lfo1Shape", 1}, "LFO1 Shape", juce::StringArray{"Sine", "Triangle", "Saw", "Square"}, 0)); layout.add(std::make_unique( juce::ParameterID{"lfo1Dest", 1}, "LFO1 Dest", juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0)); // LFO 2 layout.add(std::make_unique( juce::ParameterID{"lfo2Rate", 1}, "LFO2 Rate", juce::NormalisableRange(0.05f, 20.0f, 0.01f, 0.4f), 2.0f)); layout.add(std::make_unique( juce::ParameterID{"lfo2Depth", 1}, "LFO2 Depth", zeroOne, 0.0f)); layout.add(std::make_unique( juce::ParameterID{"lfo2Shape", 1}, "LFO2 Shape", juce::StringArray{"Sine", "Triangle", "Saw", "Square"}, 0)); layout.add(std::make_unique( juce::ParameterID{"lfo2Dest", 1}, "LFO2 Dest", juce::StringArray{"Filter", "OSC1", "OSC2", "Both OSC"}, 0)); // DELAY layout.add(std::make_unique( juce::ParameterID{"delayTime", 1}, "Delay Time", juce::NormalisableRange(10.0f, 1000.0f, 1.0f, 0.3f), 200.0f)); layout.add(std::make_unique( juce::ParameterID{"delayFeedback", 1}, "Delay Feedback", zeroOne, 0.3f)); layout.add(std::make_unique( juce::ParameterID{"delayMix", 1}, "Delay Mix", zeroOne, 0.25f)); layout.add(std::make_unique( juce::ParameterID{"delayPingPong", 1}, "Delay PingPong", juce::StringArray{"Off", "On"}, 0)); layout.add(std::make_unique( juce::ParameterID{"delaySpread", 1}, "Delay Spread", juce::NormalisableRange(0.25f, 0.75f, 0.01f), 0.5f)); // REVERB layout.add(std::make_unique( juce::ParameterID{"reverbSize", 1}, "Reverb Size", zeroOne, 0.5f)); layout.add(std::make_unique( juce::ParameterID{"reverbDamping", 1}, "Reverb Damping", zeroOne, 0.5f)); layout.add(std::make_unique( juce::ParameterID{"reverbMix", 1}, "Reverb Mix", zeroOne, 0.2f)); return layout; } void ChromaFlockProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { currentSampleRate = sampleRate; synth.setCurrentPlaybackSampleRate(sampleRate); distortion.prepare(sampleRate); compressor.prepare(sampleRate); delay.prepare(sampleRate); reverbFX.prepare(sampleRate); autoPanPhase = 0.0f; updateVoiceParameters(); } void ChromaFlockProcessor::releaseResources() {} void ChromaFlockProcessor::updateVoiceParameters() { auto getParam = [&](const juce::String& id) -> float { auto* p = apvts.getRawParameterValue(id); return p != nullptr ? p->load() : 0.0f; }; for (int i = 0; i < synth.getNumVoices(); ++i) { if (auto* voice = dynamic_cast(synth.getVoice(i))) { float pbRange = getParam("pitchBendRange"); voice->setPitchBend(getPitchBend() * pbRange); voice->setParameters( static_cast(static_cast(getParam("osc1Wave"))), static_cast(static_cast(getParam("osc2Wave"))), getParam("osc1Oct"), getParam("osc2Oct"), getParam("osc1Semi"), getParam("osc2Semi"), getParam("osc1Fine"), getParam("osc2Fine"), getParam("osc1Level"), getParam("osc2Level"), getParam("phaseOffset"), getParam("filterCutoff"), getParam("filterRes"), static_cast(static_cast(getParam("filterType"))), getParam("filterEnvAmt"), getParam("keyTrack"), getParam("envAttack"), getParam("envDecay"), getParam("envSustain"), getParam("envRelease"), getParam("fEnvAttack"), getParam("fEnvDecay"), getParam("fEnvSustain"), getParam("fEnvRelease"), getParam("pan"), getParam("drive"), getParam("masterLevel"), getParam("lfo1Rate"), getParam("lfo1Depth"), static_cast(getParam("lfo1Shape")), static_cast(getParam("lfo1Dest")), getParam("lfo2Rate"), getParam("lfo2Depth"), static_cast(getParam("lfo2Shape")), static_cast(getParam("lfo2Dest")) ); } } } void ChromaFlockProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; buffer.clear(); updateVoiceParameters(); for (const auto metadata : midiMessages) if (metadata.getMessage().isNoteOn()) activeNotes[metadata.getMessage().getNoteNumber()].store(true, std::memory_order_relaxed); else if (metadata.getMessage().isNoteOff()) activeNotes[metadata.getMessage().getNoteNumber()].store(false, std::memory_order_relaxed); synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples()); // Read FX params auto getParam = [&](const juce::String& id) -> float { auto* p = apvts.getRawParameterValue(id); return p != nullptr ? p->load() : 0.0f; }; distortion.setParameters( static_cast(static_cast(getParam("distType"))), getParam("distAmount"), getParam("distMix")); compressor.setParameters( getParam("compThreshold"), getParam("compRatio"), getParam("compAttack"), getParam("compRelease"), getParam("compMakeup"), getParam("compMix")); float panRate = getParam("autoPanRate"); float panDepth = getParam("autoPanDepth"); delay.setParameters(getParam("delayTime"), getParam("delayFeedback"), getParam("delayMix"), static_cast(getParam("delayPingPong")) == 1, getParam("delaySpread")); reverbFX.setParameters(getParam("reverbSize"), getParam("reverbDamping"), 0.8f, getParam("reverbMix")); // FX processing: distortion → compression → auto-pan int numSamples = buffer.getNumSamples(); float* leftData = buffer.getWritePointer(0); float* rightData = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : leftData; float twoPi = 6.2831853f; for (int i = 0; i < numSamples; ++i) { float left = leftData[i]; float right = rightData[i]; // Distortion (stereo) left = distortion.process(left); right = distortion.process(right); // Compression (stereo) left = compressor.process(left); right = compressor.process(right); // Auto-pan if (panDepth > 0.001f) { float panLfo = std::sin(autoPanPhase * twoPi); float leftGain = 1.0f - panDepth * 0.5f * (1.0f + panLfo); float rightGain = 1.0f - panDepth * 0.5f * (1.0f - panLfo); left *= leftGain; right *= rightGain; autoPanPhase += panRate / static_cast(currentSampleRate); if (autoPanPhase >= 1.0f) autoPanPhase -= 1.0f; } // Delay delay.process(left, right); leftData[i] = left; rightData[i] = right; } // Reverb (block-based) reverbFX.process(leftData, rightData, numSamples); // Metering float sumSquares = 0.0f; float peak = 0.0f; for (int i = 0; i < numSamples; ++i) { float mix = (leftData[i] + rightData[i]) * 0.5f; int writePos = scopeWritePos.load(std::memory_order_relaxed); scopeBuffer[writePos] = mix; scopeWritePos.store((writePos + 1) % scopeBufferSize, std::memory_order_release); int fftPos = fftWritePos.load(std::memory_order_relaxed); fftInput[fftPos] = mix; fftWritePos.store((fftPos + 1) % fftSize, std::memory_order_release); float absMix = std::abs(mix); if (absMix > peak) peak = absMix; sumSquares += mix * mix; } float rms = std::sqrt(sumSquares / static_cast(numSamples)); float level = juce::jmax(rms, peak); level = std::min(level * 5.0f, 1.0f); float prev = rmsLevel.load(std::memory_order_relaxed); if (level > prev) rmsLevel.store(level, std::memory_order_relaxed); else rmsLevel.store(prev * 0.95f, std::memory_order_relaxed); } juce::AudioProcessorEditor* ChromaFlockProcessor::createEditor() { return new ChromaFlockEditor(*this); } void ChromaFlockProcessor::getStateInformation(juce::MemoryBlock& destData) { auto state = apvts.copyState(); std::unique_ptr xml(state.createXml()); copyXmlToBinary(*xml, destData); } void ChromaFlockProcessor::setStateInformation(const void* data, int sizeInBytes) { std::unique_ptr xml(getXmlFromBinary(data, sizeInBytes)); if (xml && xml->hasTagName(apvts.state.getType())) apvts.replaceState(juce::ValueTree::fromXml(*xml)); } juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new ChromaFlockProcessor(); }