#include "PluginProcessor.h" #include "PluginEditor.h" #include "BuildInfo_generated.h" SM26AudioProcessor::SM26AudioProcessor() : AudioProcessor (BusesProperties().withOutput ("Output", juce::AudioChannelSet::stereo(), true)), apvts (*this, nullptr, "SM26State", { std::make_unique ("k1", "Knob 1", juce::NormalisableRange(0.f,1.f,0.001f), 0.5f), std::make_unique ("k2", "Knob 2", juce::NormalisableRange(0.f,1.f,0.001f), 0.5f), std::make_unique ("k3", "Knob 3", juce::NormalisableRange(0.f,1.f,0.001f), 0.5f), std::make_unique ("k4", "Knob 4", juce::NormalisableRange(0.f,1.f,0.001f), 0.5f), std::make_unique ("preset", "Preset", 0, sm26::PresetStore::count() - 1, 0) }) { engine.setPresetIndex (0); apvts.addParameterListener ("k1", this); apvts.addParameterListener ("k2", this); apvts.addParameterListener ("k3", this); apvts.addParameterListener ("k4", this); apvts.addParameterListener ("preset", this); paramsReady.store (true); } SM26AudioProcessor::~SM26AudioProcessor() { apvts.removeParameterListener ("k1", this); apvts.removeParameterListener ("k2", this); apvts.removeParameterListener ("k3", this); apvts.removeParameterListener ("k4", this); apvts.removeParameterListener ("preset", this); } void SM26AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { engine.prepare (sampleRate, samplesPerBlock); } void SM26AudioProcessor::releaseResources() {} void SM26AudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midi) { juce::ScopedNoDenormals noDenormals; const int n = buffer.getNumSamples(); double bpm = 120.0; bool playing = false; if (auto* ph = getPlayHead()) { auto pos = ph->getPosition(); if (pos.hasValue()) { if (pos->getBpm().hasValue()) bpm = *pos->getBpm(); playing = pos->getIsPlaying(); } } for (int ch = 0; ch < buffer.getNumChannels(); ++ch) buffer.clear (ch, 0, n); engine.process (buffer, midi, n, bpm, playing); } juce::AudioProcessorEditor* SM26AudioProcessor::createEditor() { return new SM26AudioProcessorEditor (*this); } void SM26AudioProcessor::getStateInformation (juce::MemoryBlock& destData) { auto xmlStr = apvts.copyState().createXml()->toString(); destData.replaceAll (xmlStr.toRawUTF8(), (size_t) xmlStr.length()); } void SM26AudioProcessor::setStateInformation (const void* data, int sizeInBytes) { auto xml = juce::XmlDocument::parse (juce::String::fromUTF8 ((const char*) data, sizeInBytes)); if (xml != nullptr) { auto tree = juce::ValueTree::fromXml (*xml); if (tree.isValid()) { paramsReady.store (false); apvts.replaceState (tree); paramsReady.store (true); int idx = (int) *apvts.getRawParameterValue ("preset"); engine.setPresetIndex (idx); applyPresetKnobs(); } } } void SM26AudioProcessor::setKnob (int knob, float value01) { if (knob < 0 || knob >= 4) return; auto* p = apvts.getParameter (juce::String ("k") + juce::String (knob + 1)); if (p != nullptr) p->setValueNotifyingHost (p->convertTo0to1 (value01)); } void SM26AudioProcessor::adjustParam (int paramId, float delta01) { float cur = engine.getParam (paramId); engine.setParam (paramId, sm26::clamp01 (cur + delta01)); for (int k = 0; k < 4; ++k) { auto kv = engine.knobView (k); if (kv.param == paramId) { float pval = engine.getParam (paramId); float knobVal = sm26::clamp01 ((pval - kv.lo) / (kv.hi - kv.lo + 1e-9f)); setKnob (k, knobVal); } } } void SM26AudioProcessor::setPresetByIndex (int i) { auto* p = apvts.getParameter ("preset"); if (p != nullptr) p->setValueNotifyingHost (p->convertTo0to1 ((float) i)); } void SM26AudioProcessor::parameterChanged (const juce::String& parameterID, float newValue) { if (! paramsReady.load()) return; if (parameterID == "preset") { int idx = (int) std::round (newValue * (float) (sm26::PresetStore::count() - 1)); idx = juce::jlimit (0, sm26::PresetStore::count() - 1, idx); engine.setPresetIndex (idx); applyPresetKnobs(); } else if (parameterID == "k1" || parameterID == "k2" || parameterID == "k3" || parameterID == "k4") { int k = parameterID.getIntValue() - 1; auto kv = engine.knobView (k); float paramVal = sm26::clamp01 (kv.lo + newValue * (kv.hi - kv.lo)); engine.setParam (kv.param, paramVal); } } void SM26AudioProcessor::applyPresetKnobs() { for (int k = 0; k < 4; ++k) { auto kv = engine.knobView (k); float paramVal = engine.getParam (kv.param); float knobVal = sm26::clamp01 ((paramVal - kv.lo) / (kv.hi - kv.lo + 1e-9f)); setKnob (k, knobVal); } } juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new SM26AudioProcessor(); }