#include "PluginProcessor.h" #include "PluginEditor.h" #include #include using namespace juce; TrommelkisteProcessor::TrommelkisteProcessor() : AudioProcessor(BusesProperties() .withOutput("Output", juce::AudioChannelSet::stereo(), true)), apvts(*this, nullptr, "Parameters", createParameterLayout()) { formatManager.registerBasicFormats(); scanSamples(); loadKit(0); } TrommelkisteProcessor::~TrommelkisteProcessor() {} std::optional TrommelkisteProcessor::getNameForMidiNoteNumber(int note, int) { static const char* names[] = {"BD", "RS", "SN", "CL", "CH", "PH", "OH", "RD", "LT", "MT"}; int t = note - BASE_NOTE; if (t >= 0 && t < NUM_TRACKS) return juce::String(names[t]); return std::nullopt; } void TrommelkisteProcessor::prepareToPlay(double sr, int) { currentSampleRate = sr; juce::dsp::ProcessSpec spec; spec.sampleRate = sr; spec.maximumBlockSize = 512; spec.numChannels = 2; reverb.prepare(spec); reverbParams.roomSize = 0.5f; reverbParams.damping = 0.5f; reverbParams.wetLevel = 0.33f; reverbParams.dryLevel = 0.8f; reverbParams.width = 1.0f; reverbParams.freezeMode = 0.0f; reverb.setParameters(reverbParams); const int delaySamples = DELAY_BUFFER_SECONDS * (int)sr; delayBuffer.setSize(2, delaySamples); delayBuffer.clear(); delayWritePos = 0; } void TrommelkisteProcessor::releaseResources() {} void TrommelkisteProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midi) { juce::ScopedNoDenormals nd; const int numSamples = buffer.getNumSamples(); buffer.clear(); for (int i = 0; i < NUM_TRACKS; ++i) trackActive[i] = false; // Shuffle: add ghost notes (bar-quantized) const float globalShuffleAmt = apvts.getRawParameterValue("global_shuffleAmount")->load(); const float globalGroove = apvts.getRawParameterValue("global_groove")->load(); const int shuffleDivIdx = (int)apvts.getRawParameterValue("global_shuffleDiv")->load(); // 0=OFF, 1=every 4 bars, 2=every 8, 3=every 16, 4=every 32 static constexpr int shuffleDivBars[] = { 0, 4, 8, 16, 32 }; const int divBars = shuffleDivBars[juce::jlimit(0, 4, shuffleDivIdx)]; // Try to get transport info from play head bool hasTransport = false; int64_t currentBar = 0; double ppq = 0.0; double bpm = 120.0; if (auto* ph = getPlayHead()) { auto info = ph->getPosition(); if (info.hasValue() && info->getPpqPosition().hasValue()) { hasTransport = true; ppq = *info->getPpqPosition(); if (info->getBpm().hasValue()) bpm = *info->getBpm(); double beatsPerBar = 4.0; if (info->getTimeSignature().hasValue()) beatsPerBar = (double)(*info->getTimeSignature()).numerator; currentBar = (int64_t)(ppq / beatsPerBar); } } const bool barAllowed = (divBars == 0) || !hasTransport || (currentBar % divBars == 0); // 16th note grid quantization (DAW shuffle is reflected in PPQ) const double ppqPerSample = bpm / (60.0 * currentSampleRate); const double sixteenthPpq = 0.25; auto nextGridPpq = [&](double fromPpq) -> double { return std::ceil((fromPpq + 0.0001) / sixteenthPpq) * sixteenthPpq; }; // Process pending shuffle events for (int i = numPendingShuffle - 1; i >= 0; --i) { pendingShuffle[i].delaySamples -= numSamples; if (pendingShuffle[i].delaySamples <= 0) { handleNoteOn(pendingShuffle[i].trackIndex, pendingShuffle[i].velocity); pendingShuffle[i] = pendingShuffle[--numPendingShuffle]; } } std::uniform_real_distribution dist(0.0f, 1.0f); for (const auto metadata : midi) { auto msg = metadata.getMessage(); if (msg.isNoteOn()) { int t = msg.getNoteNumber() - BASE_NOTE; if (t >= 0 && t < NUM_TRACKS) { handleNoteOn(t, msg.getFloatVelocity()); lastTriggeredTrack = t; const float shuffleAmt = apvts.getRawParameterValue( "t" + juce::String(t) + "_shuffle")->load(); const float effShuffle = shuffleAmt * globalShuffleAmt; if (effShuffle > 0.0f && barAllowed) { int numEvents = juce::jmax(1, (int)(effShuffle * 3.0f + 0.5f)); numEvents = std::uniform_int_distribution(0, numEvents)(rng); for (int e = 0; e < numEvents; ++e) { float vel = msg.getFloatVelocity() * (0.3f + dist(rng) * 0.7f) * effShuffle; int stepsAhead = std::uniform_int_distribution(1, 4)(rng); double gridPpq = nextGridPpq(ppq) + stepsAhead * sixteenthPpq; int delay = juce::jmax(1, (int)((gridPpq - ppq) / ppqPerSample)); if (numPendingShuffle < MAX_PENDING_SHUFFLE) pendingShuffle[numPendingShuffle++] = { t, vel, delay }; } } } } else if (msg.isNoteOff()) { int t = msg.getNoteNumber() - BASE_NOTE; if (t >= 0 && t < NUM_TRACKS) handleNoteOff(t); } } auto* outL = buffer.getWritePointer(0); auto* outR = buffer.getNumChannels() > 1 ? buffer.getWritePointer(1) : nullptr; // Temporary send buffers juce::AudioBuffer delaySend(2, numSamples); juce::AudioBuffer reverbSend(2, numSamples); delaySend.clear(); reverbSend.clear(); for (auto& v : voices) { if (!v.active) continue; const int ti = v.trackIndex; trackActive[ti] = true; auto& track = tracks[ti]; const juce::SpinLock::ScopedLockType sl(track.lock); const int bufLen = track.buffer.getNumSamples(); if (bufLen == 0) { v.active = false; continue; } const juce::String p = "t" + juce::String(ti) + "_"; const float level = apvts.getRawParameterValue(p + "level")->load(); const float length = apvts.getRawParameterValue(p + "length")->load(); const float velAmt = apvts.getRawParameterValue(p + "velocity")->load(); const float pitchSemitones = apvts.getRawParameterValue(p + "pitch")->load(); const float toneHz = apvts.getRawParameterValue(p + "tone")->load(); const float pan = apvts.getRawParameterValue(p + "pan")->load(); const bool decayGate = apvts.getRawParameterValue(p + "decay")->load() > 0.5f; const float delaySendAmt = apvts.getRawParameterValue(p + "delaySend")->load(); const float reverbSendAmt = apvts.getRawParameterValue(p + "reverbSend")->load(); const float ringModAmt = apvts.getRawParameterValue(p + "ringMod")->load(); const float destructionAmt = apvts.getRawParameterValue(p + "destruction")->load(); float pitchRatio = std::pow(2.0f, pitchSemitones / 12.0f) * (float)track.fileSampleRate / (float)currentSampleRate; const float maxSamples = length * (float)currentSampleRate; const float toneA = (toneHz < 19000.0f) ? std::exp(-2.0f * juce::MathConstants::pi * toneHz / (float)currentSampleRate) : 0.0f; const float effVel = 1.0f - velAmt + velAmt * v.noteVelocity; const float gain = level * effVel; const float panAngle = (pan + 1.0f) * 0.25f * juce::MathConstants::pi; const float panL = std::cos(panAngle) * juce::Decibels::decibelsToGain(-3.0f); const float panR = std::sin(panAngle) * juce::Decibels::decibelsToGain(-3.0f); const float* sData = track.buffer.getReadPointer(0); const bool hasR = track.buffer.getNumChannels() > 1; const float* sDataR = hasR ? track.buffer.getReadPointer(1) : nullptr; const float globalRingFreq = apvts.getRawParameterValue("global_ringFreq")->load(); const float globalDestr = apvts.getRawParameterValue("global_destruction")->load(); const float ringModInc = 2.0f * juce::MathConstants::pi * globalRingFreq / (float)currentSampleRate; for (int s = 0; s < numSamples; ++s) { if (!v.active) break; float env = 1.0f; if (!decayGate) { env = 1.0f - (float)v.samplesPlayed / maxSamples; if (env <= 0.0f) { v.active = false; break; } } else { if (v.samplesPlayed >= (int)maxSamples) { v.active = false; break; } } int pos0 = (int)v.position; if (pos0 >= bufLen) { v.active = false; break; } const float frac = v.position - (float)pos0; int pos1 = pos0 + 1; if (pos1 >= bufLen) pos1 = bufLen - 1; float smplL = sData[pos0] + (sData[pos1] - sData[pos0]) * frac; float smplR = hasR ? (sDataR[pos0] + (sDataR[pos1] - sDataR[pos0]) * frac) : smplL; if (toneA > 0.0f) { v.filterStateL = (1.0f - toneA) * smplL + toneA * v.filterStateL; smplL = v.filterStateL; v.filterStateR = (1.0f - toneA) * smplR + toneA * v.filterStateR; smplR = v.filterStateR; } // Ring modulation if (ringModAmt > 0.0f) { float mod = std::sin(v.ringModPhase); smplL = smplL * (1.0f - ringModAmt + ringModAmt * mod); smplR = smplR * (1.0f - ringModAmt + ringModAmt * mod); v.ringModPhase += ringModInc; if (v.ringModPhase >= 2.0f * juce::MathConstants::pi) v.ringModPhase -= 2.0f * juce::MathConstants::pi; } // Destruction (soft clip + bitcrush) const float effDestr = juce::jmin(1.0f, destructionAmt + globalDestr); if (effDestr > 0.0f) { float drive = 1.0f + effDestr * 19.0f; smplL = std::tanh(smplL * drive); smplR = std::tanh(smplR * drive); float bits = juce::jmax(2.0f, 16.0f - effDestr * 14.0f); float levels = std::pow(2.0f, bits); smplL = std::round(smplL * levels) / levels; smplR = std::round(smplR * levels) / levels; } smplL *= env * gain; smplR *= env * gain; outL[s] += smplL * panL; if (outR) outR[s] += smplR * panR; // Send to effects delaySend.addSample(0, s, smplL * delaySendAmt); delaySend.addSample(1, s, smplR * delaySendAmt); reverbSend.addSample(0, s, smplL * reverbSendAmt); reverbSend.addSample(1, s, smplR * reverbSendAmt); v.position += pitchRatio; ++v.samplesPlayed; if (v.position >= (float)bufLen) v.active = false; } } // Read master effect levels const float masterDelayMix = apvts.getRawParameterValue("master_delay")->load(); const float masterReverbMix = apvts.getRawParameterValue("master_reverb")->load(); // Delay sync const int delaySyncDiv = (int)apvts.getRawParameterValue("delay_syncDiv")->load(); float delayTimeSec = 0.45f; // default free-running delay time if (delaySyncDiv >= 1 && delaySyncDiv <= 8) { double bpm = 120.0; if (auto* ph = getPlayHead()) { auto info = ph->getPosition(); if (info.hasValue() && info->getBpm().hasValue()) bpm = *info->getBpm(); } double beats = delaySyncDiv * 0.25; delayTimeSec = (float)(beats * 60.0 / bpm); } // Process delay const int delaySamples = delayBuffer.getNumSamples(); auto* delL = delayBuffer.getWritePointer(0); auto* delR = delayBuffer.getNumChannels() > 1 ? delayBuffer.getWritePointer(1) : nullptr; const float feedback = 0.4f; const float delayMix = 0.3f; const int delayTimeSamples = (int)(delayTimeSec * currentSampleRate); for (int s = 0; s < numSamples; ++s) { const int readPos = (delayWritePos - delayTimeSamples + delaySamples) % delaySamples; const float delOutL = delL[readPos]; const float delOutR = delR ? delR[readPos] : delOutL; delL[delayWritePos] = delaySend.getSample(0, s) + delOutL * feedback; if (delR) delR[delayWritePos] = delaySend.getSample(1, s) + delOutR * feedback; outL[s] += delOutL * delayMix * masterDelayMix; if (outR) outR[s] += delOutR * delayMix * masterDelayMix; delayWritePos = (delayWritePos + 1) % delaySamples; } // Process reverb juce::dsp::AudioBlock reverbBlock(reverbSend); juce::dsp::ProcessContextReplacing reverbCtx(reverbBlock); reverb.process(reverbCtx); for (int s = 0; s < numSamples; ++s) { outL[s] += reverbSend.getSample(0, s) * masterReverbMix; if (outR) outR[s] += reverbSend.getSample(1, s) * masterReverbMix; } // Master volume & pan const float masterVol = apvts.getRawParameterValue("master_volume")->load(); const float masterPan = apvts.getRawParameterValue("master_pan")->load(); const float mPanAngle = (masterPan + 1.0f) * 0.25f * MathConstants::pi; const float mPanL = std::cos(mPanAngle) * Decibels::decibelsToGain(-3.0f); const float mPanR = std::sin(mPanAngle) * Decibels::decibelsToGain(-3.0f); for (int s = 0; s < numSamples; ++s) { float tmpL = outL[s] * mPanL * masterVol; float tmpR = outR ? outR[s] * mPanR * masterVol : tmpL; outL[s] = tmpL; if (outR) outR[s] = tmpR; } float peak = 0.0f; for (int s = 0; s < numSamples; ++s) { float a = std::abs(outL[s]); if (outR) a = jmax(a, std::abs(outR[s])); if (a > peak) peak = a; } outputLevel.store(peak, std::memory_order_relaxed); } int TrommelkisteProcessor::findFreeVoice(int trackIndex) { for (int i = 0; i < MAX_VOICES; ++i) if (!voices[i].active) return i; int oldest = 0, bestAge = -1; for (int i = 0; i < MAX_VOICES; ++i) { if (voices[i].trackIndex == trackIndex && voices[i].samplesPlayed > bestAge) { bestAge = voices[i].samplesPlayed; oldest = i; } } if (bestAge >= 0) return oldest; oldest = 0; bestAge = -1; for (int i = 0; i < MAX_VOICES; ++i) { if (voices[i].samplesPlayed > bestAge) { bestAge = voices[i].samplesPlayed; oldest = i; } } return oldest; } void TrommelkisteProcessor::handleNoteOn(int trackIndex, float velocity) { int idx = findFreeVoice(trackIndex); auto& v = voices[idx]; v.active = true; v.trackIndex = trackIndex; v.position = 0.0f; v.samplesPlayed = 0; v.filterStateL = 0.0f; v.filterStateR = 0.0f; v.noteVelocity = velocity; } void TrommelkisteProcessor::handleNoteOff(int trackIndex) { for (auto& v : voices) { if (v.active && v.trackIndex == trackIndex) { const bool decayGate = apvts.getRawParameterValue( "t" + juce::String(trackIndex) + "_decay")->load() > 0.5f; if (decayGate) v.active = false; } } } void TrommelkisteProcessor::loadSample(int trackIndex, const juce::File& file) { if (trackIndex < 0 || trackIndex >= NUM_TRACKS) return; std::unique_ptr reader(formatManager.createReaderFor(file)); if (reader == nullptr) return; juce::AudioBuffer newBuf(reader->numChannels, (int)reader->lengthInSamples); reader->read(&newBuf, 0, (int)reader->lengthInSamples, 0, true, true); { const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock); tracks[trackIndex].buffer = std::move(newBuf); tracks[trackIndex].fileSampleRate = (int)reader->sampleRate; tracks[trackIndex].filePath = file.getFullPathName(); } } void TrommelkisteProcessor::clearSample(int trackIndex) { if (trackIndex < 0 || trackIndex >= NUM_TRACKS) return; const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock); tracks[trackIndex].buffer.setSize(1, 0); tracks[trackIndex].filePath = ""; } static void readAudioFromBuffer(juce::AudioFormatManager& fmtMgr, juce::AudioBuffer& dest, int& destSampleRate, const char* data, int size) { auto memStream = std::make_unique(data, size, false); std::unique_ptr reader(fmtMgr.createReaderFor(std::move(memStream))); if (reader == nullptr) return; dest.setSize((int)reader->numChannels, (int)reader->lengthInSamples); reader->read(&dest, 0, (int)reader->lengthInSamples, 0, true, true); destSampleRate = (int)reader->sampleRate; } // ── Sample library ───────────────────────────────────────────── static bool matchesPrefix(const juce::String& name, const juce::String& prefix) { return name.toUpperCase().startsWith(prefix.toUpperCase()); } void TrommelkisteProcessor::scanSamples() { static const char* prefixes[][8] = { { "BT", nullptr }, // 0 BD { "RIM", nullptr }, // 1 RS { "ST", "STAT", nullptr }, // 2 SN { "HANDCLP", nullptr }, // 3 CL { "HHCD", nullptr }, // 4 CH { "CLOP", nullptr }, // 5 PH { "HHOD", nullptr }, // 6 OH { "RIDE", nullptr }, // 7 RD { "LT", nullptr }, // 8 LT { "MT", nullptr }, // 9 MT }; for (int t = 0; t < NUM_TRACKS; ++t) { trackSamples[t].clear(); currentSampleIndex[t] = -1; } for (int i = 0; i < numEmbeddedSamples; ++i) { const auto& es = embeddedSamples[i]; juce::String name(es.name); for (int t = 0; t < NUM_TRACKS; ++t) { for (int p = 0; prefixes[t][p] != nullptr; ++p) { if (matchesPrefix(name, prefixes[t][p])) { SampleRef ref; ref.name = name; ref.data = es.data; ref.dataSize = es.size; trackSamples[t].add(ref); break; } } } } struct SampleRefSorter { int compareElements(const SampleRef& a, const SampleRef& b) const { return a.name.compare(b.name); } }; SampleRefSorter sorter; for (int t = 0; t < NUM_TRACKS; ++t) trackSamples[t].sort(sorter); } void TrommelkisteProcessor::loadSampleByIndex(int trackIndex, int sampleIndex) { if (trackIndex < 0 || trackIndex >= NUM_TRACKS) return; if (sampleIndex < 0 || sampleIndex >= trackSamples[trackIndex].size()) return; currentSampleIndex[trackIndex] = sampleIndex; const auto& ref = trackSamples[trackIndex][sampleIndex]; if (ref.data != nullptr && ref.dataSize > 0) { const juce::SpinLock::ScopedLockType sl(tracks[trackIndex].lock); readAudioFromBuffer(formatManager, tracks[trackIndex].buffer, tracks[trackIndex].fileSampleRate, ref.data, ref.dataSize); tracks[trackIndex].filePath = ""; } else if (ref.file.existsAsFile()) { loadSample(trackIndex, ref.file); } } void TrommelkisteProcessor::nextSample(int trackIndex) { if (trackIndex < 0 || trackIndex >= NUM_TRACKS) return; const int n = trackSamples[trackIndex].size(); if (n == 0) return; int idx = currentSampleIndex[trackIndex] + 1; if (idx >= n) idx = 0; loadSampleByIndex(trackIndex, idx); } void TrommelkisteProcessor::prevSample(int trackIndex) { if (trackIndex < 0 || trackIndex >= NUM_TRACKS) return; const int n = trackSamples[trackIndex].size(); if (n == 0) return; int idx = currentSampleIndex[trackIndex] - 1; if (idx < 0) idx = n - 1; loadSampleByIndex(trackIndex, idx); } // ── Kit / Preset system ─────────────────────────────────────── static const TrommelkisteProcessor::Kit kits[] = { { "Classic", { "BT0AADA", "RIM63", "ST0T0SA", "HANDCLP2", "HHCD6", "CLOP2", "HHOD6", "RIDED6", "LT0DA", "MT0DA" } }, { "Punchy", { "BT7A0D3", "RIM127", "ST7T7S3", "HANDCLP1", "HHCD2", "CLOP1", "HHOD2", "RIDED2", "LT7D3", "MT7D3" } }, { "Soft", { "BT0A0DA", "RIM63", "ST0T0S7", "HANDCLP2", "HHCD8", "CLOP4", "HHOD8", "RIDED8", "LT0D7", "MT0D7" } }, { "Raw", { "BTAAADA", "RIM127", "STATASA", "HANDCLP1", "HHCDA", "CLOP3", "HHODA", "RIDEDA", "LTADA", "MTADA" } }, }; const TrommelkisteProcessor::Kit* TrommelkisteProcessor::getKitList() { return kits; } int TrommelkisteProcessor::getNumKits() const { return 4; } juce::String TrommelkisteProcessor::getCurrentKitName() const { if (currentKitIndex >= 0 && currentKitIndex < getNumKits()) return kits[currentKitIndex].name; return {}; } void TrommelkisteProcessor::loadKit(int index) { if (index < 0 || index >= getNumKits()) return; currentKitIndex = index; const auto& kit = kits[index]; for (int t = 0; t < NUM_TRACKS; ++t) { if (kit.samples[t] == nullptr || kit.samples[t][0] == '\0') { currentSampleIndex[t] = -1; clearSample(t); continue; } juce::String target(kit.samples[t]); int found = -1; for (int s = 0; s < trackSamples[t].size(); ++s) { if (trackSamples[t][s].name.equalsIgnoreCase(target)) { found = s; break; } } if (found >= 0) loadSampleByIndex(t, found); else clearSample(t); } } void TrommelkisteProcessor::nextKit() { int idx = currentKitIndex + 1; if (idx >= getNumKits()) idx = 0; loadKit(idx); } void TrommelkisteProcessor::prevKit() { int idx = currentKitIndex - 1; if (idx < 0) idx = getNumKits() - 1; loadKit(idx); } juce::AudioProcessorEditor* TrommelkisteProcessor::createEditor() { return new TrommelkisteEditor(*this); } void TrommelkisteProcessor::getStateInformation(juce::MemoryBlock& destData) { auto state = apvts.copyState(); state.setProperty("kitIndex", currentKitIndex, nullptr); juce::ValueTree samples("Samples"); for (int i = 0; i < NUM_TRACKS; ++i) { juce::ValueTree tr("Track" + juce::String(i)); tr.setProperty("path", tracks[i].filePath, nullptr); tr.setProperty("sampleIndex", currentSampleIndex[i], nullptr); if (currentSampleIndex[i] >= 0 && currentSampleIndex[i] < trackSamples[i].size()) tr.setProperty("sampleName", trackSamples[i][currentSampleIndex[i]].name, nullptr); samples.appendChild(tr, nullptr); } state.appendChild(samples, nullptr); std::unique_ptr xml(state.createXml()); copyXmlToBinary(*xml, destData); } void TrommelkisteProcessor::setStateInformation(const void* data, int sizeInBytes) { std::unique_ptr xml(getXmlFromBinary(data, sizeInBytes)); if (xml == nullptr) return; auto state = juce::ValueTree::fromXml(*xml); apvts.replaceState(state); currentKitIndex = (int)state.getProperty("kitIndex", 0); auto samples = state.getChildWithName("Samples"); if (samples.isValid()) { for (int i = 0; i < NUM_TRACKS; ++i) { auto tr = samples.getChildWithName("Track" + juce::String(i)); if (!tr.isValid()) continue; auto path = tr.getProperty("path", "").toString(); auto sampleName = tr.getProperty("sampleName", "").toString(); if (path.isNotEmpty()) { loadSample(i, juce::File(path)); currentSampleIndex[i] = (int)tr.getProperty("sampleIndex", -1); } else if (sampleName.isNotEmpty()) { int found = -1; for (int s = 0; s < trackSamples[i].size(); ++s) { if (trackSamples[i][s].name.equalsIgnoreCase(sampleName)) { found = s; break; } } if (found >= 0) loadSampleByIndex(i, found); } } } } juce::AudioProcessorValueTreeState::ParameterLayout TrommelkisteProcessor::createParameterLayout() { AudioProcessorValueTreeState::ParameterLayout layout; for (int i = 0; i < NUM_TRACKS; ++i) { const String p = "t" + String(i) + "_"; const String tn = "T" + String(i + 1) + " "; layout.add(std::make_unique( p + "level", tn + "Level", NormalisableRange(0.0f, 1.0f), 0.8f)); layout.add(std::make_unique( p + "length", tn + "Length", NormalisableRange(0.01f, 2.0f, 0.001f, 0.45f), 0.5f)); layout.add(std::make_unique( p + "velocity", tn + "Velocity", NormalisableRange(0.0f, 1.0f), 1.0f)); layout.add(std::make_unique( p + "pitch", tn + "Pitch", NormalisableRange(-12.0f, 12.0f, 0.1f), 0.0f)); layout.add(std::make_unique( p + "tone", tn + "Tone", NormalisableRange(20.0f, 20000.0f, 1.0f, 0.25f), 20000.0f)); layout.add(std::make_unique( p + "pan", tn + "Pan", NormalisableRange(-1.0f, 1.0f), 0.0f)); layout.add(std::make_unique( p + "decay", tn + "Decay", false)); layout.add(std::make_unique( p + "delaySend", tn + "Delay", NormalisableRange(0.0f, 1.0f), 0.0f)); layout.add(std::make_unique( p + "reverbSend", tn + "Reverb", NormalisableRange(0.0f, 1.0f), 0.0f)); layout.add(std::make_unique( p + "ringMod", tn + "RingMod", NormalisableRange(0.0f, 1.0f), 0.0f)); layout.add(std::make_unique( p + "destruction", tn + "Destr", NormalisableRange(0.0f, 1.0f), 0.0f)); layout.add(std::make_unique( p + "shuffle", tn + "Shuffle", NormalisableRange(0.0f, 1.0f), 0.0f)); } // Global controls layout.add(std::make_unique( "master_volume", "Master Volume", NormalisableRange(0.0f, 1.2f, 0.01f), 1.0f)); layout.add(std::make_unique( "master_pan", "Master Pan", NormalisableRange(-1.0f, 1.0f, 0.01f), 0.0f)); layout.add(std::make_unique( "master_delay", "Master Delay", NormalisableRange(0.0f, 1.0f, 0.01f), 1.0f)); layout.add(std::make_unique( "master_reverb", "Master Reverb", NormalisableRange(0.0f, 1.0f, 0.01f), 1.0f)); layout.add(std::make_unique( "delay_syncDiv", "Delay Sync", NormalisableRange(0.0f, 8.0f, 1.0f), 0.0f)); layout.add(std::make_unique( "global_shuffleAmount", "Shuffle Amt", NormalisableRange(0.0f, 1.0f, 0.01f), 0.0f)); layout.add(std::make_unique( "global_groove", "Groove", NormalisableRange(0.0f, 1.0f, 0.01f), 0.0f)); layout.add(std::make_unique( "global_shuffleDiv", "Shuffle Div", NormalisableRange(0.0f, 4.0f, 1.0f), 0.0f)); layout.add(std::make_unique( "global_ringFreq", "Ring Freq", NormalisableRange(20.0f, 2000.0f, 1.0f, 0.3f), 80.0f)); layout.add(std::make_unique( "global_destruction", "Destruction", NormalisableRange(0.0f, 1.0f), 0.0f)); return layout; } AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new TrommelkisteProcessor(); }