From 3b5c5d2593e5b1caffc223dbebd2c54f1e304561 Mon Sep 17 00:00:00 2001 From: Jim Vanaria <22546376+jvanaria@users.noreply.github.com> Date: Sun, 12 Apr 2026 16:43:23 -0500 Subject: [PATCH 1/9] Added UI to SupaTrigga --- cmake/CommonSettings.cmake | 1 + supatrigga/CMakeLists.txt | 1 + supatrigga/Source/PluginProcessor.cpp | 919 ++++++++++++------------- supatrigga/Source/PluginProcessor.h | 196 +++--- supatrigga/Source/SupaTriggaEditor.cpp | 326 +++++++++ supatrigga/Source/SupaTriggaEditor.h | 78 +++ supatrigga/Source/Theme.h | 34 + 7 files changed, 989 insertions(+), 566 deletions(-) create mode 100644 supatrigga/Source/SupaTriggaEditor.cpp create mode 100644 supatrigga/Source/SupaTriggaEditor.h create mode 100644 supatrigga/Source/Theme.h diff --git a/cmake/CommonSettings.cmake b/cmake/CommonSettings.cmake index 82620b0..a1841c1 100644 --- a/cmake/CommonSettings.cmake +++ b/cmake/CommonSettings.cmake @@ -3,6 +3,7 @@ # macOS deployment target (must be before project()) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum macOS version") +set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures for Mac OS X") # Build universal binaries on macOS (arm64 + x86_64) set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures for Mac OS X") diff --git a/supatrigga/CMakeLists.txt b/supatrigga/CMakeLists.txt index 3c09a98..a4ac146 100644 --- a/supatrigga/CMakeLists.txt +++ b/supatrigga/CMakeLists.txt @@ -22,6 +22,7 @@ juce_add_plugin(SupaTrigga target_sources(SupaTrigga PRIVATE Source/PluginProcessor.cpp + Source/SupaTriggaEditor.cpp ) smartelectronix_plugin_common(SupaTrigga) diff --git a/supatrigga/Source/PluginProcessor.cpp b/supatrigga/Source/PluginProcessor.cpp index 2100c42..0eb180b 100644 --- a/supatrigga/Source/PluginProcessor.cpp +++ b/supatrigga/Source/PluginProcessor.cpp @@ -1,131 +1,118 @@ #include "PluginProcessor.h" +#include "SupaTriggaEditor.h" #include SupaTriggaProcessor::SupaTriggaProcessor() - : AudioProcessor(BusesProperties() - .withInput("Input", juce::AudioChannelSet::stereo(), true) - .withOutput("Output", juce::AudioChannelSet::stereo(), true)), - apvts(*this, nullptr, "Parameters", createParameterLayout()) -{ - std::srand(static_cast(std::time(nullptr))); - - // Allocate buffers - leftBuffer = std::make_unique(MAXSIZE); - rightBuffer = std::make_unique(MAXSIZE); - - for (size_t i = 0; i < MAXSIZE; i++) - { - leftBuffer[i] = 0.0f; - rightBuffer[i] = 0.0f; - } - - // Initialize sequencer - for (int i = 0; i < MAXSLIDES; i++) - { - sequencer[i].offset = 0; - sequencer[i].reverse = false; - sequencer[i].stop = false; - sequencer[i].silence = false; - } - - // Get parameter pointers - granularityParam = apvts.getRawParameterValue(GRANULARITY_ID); - speedParam = apvts.getRawParameterValue(SPEED_ID); - probReverseParam = apvts.getRawParameterValue(PROB_REVERSE_ID); - probSpeedParam = apvts.getRawParameterValue(PROB_SPEED_ID); - probRearrangeParam = apvts.getRawParameterValue(PROB_REARRANGE_ID); - probSilenceParam = apvts.getRawParameterValue(PROB_SILENCE_ID); - probRepeatParam = apvts.getRawParameterValue(PROB_REPEAT_ID); - instantReverseParam = apvts.getRawParameterValue(INSTANT_REVERSE_ID); - instantSpeedParam = apvts.getRawParameterValue(INSTANT_SPEED_ID); - instantRepeatParam = apvts.getRawParameterValue(INSTANT_REPEAT_ID); - - randomize(); -} - -SupaTriggaProcessor::~SupaTriggaProcessor() -{ + : AudioProcessor( + BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true)), + apvts(*this, nullptr, "Parameters", createParameterLayout()) { + std::srand(static_cast(std::time(nullptr))); + + // Allocate buffers + leftBuffer = std::make_unique(MAXSIZE); + rightBuffer = std::make_unique(MAXSIZE); + + for (size_t i = 0; i < MAXSIZE; i++) { + leftBuffer[i] = 0.0f; + rightBuffer[i] = 0.0f; + } + + // Initialize sequencer + for (int i = 0; i < MAXSLIDES; i++) { + sequencer[i].offset = 0; + sequencer[i].reverse = false; + sequencer[i].stop = false; + sequencer[i].silence = false; + } + + // Get parameter pointers + granularityParam = apvts.getRawParameterValue(GRANULARITY_ID); + speedParam = apvts.getRawParameterValue(SPEED_ID); + probReverseParam = apvts.getRawParameterValue(PROB_REVERSE_ID); + probSpeedParam = apvts.getRawParameterValue(PROB_SPEED_ID); + probRearrangeParam = apvts.getRawParameterValue(PROB_REARRANGE_ID); + probSilenceParam = apvts.getRawParameterValue(PROB_SILENCE_ID); + probRepeatParam = apvts.getRawParameterValue(PROB_REPEAT_ID); + instantReverseParam = apvts.getRawParameterValue(INSTANT_REVERSE_ID); + instantSpeedParam = apvts.getRawParameterValue(INSTANT_SPEED_ID); + instantRepeatParam = apvts.getRawParameterValue(INSTANT_REPEAT_ID); + + randomize(); } -juce::AudioProcessorValueTreeState::ParameterLayout SupaTriggaProcessor::createParameterLayout() -{ - std::vector> params; - - // Granularity: displays as number of slices (1, 2, 4, 8, 16, 32, 64, 128) - params.push_back(std::make_unique( - juce::ParameterID(GRANULARITY_ID, 1), "Slices", - juce::NormalisableRange(0.0f, 1.0f, 0.001f), - 0.3f, - juce::String(), - juce::AudioProcessorParameter::genericParameter, - [](float value, int) { - int slices = 1 << static_cast(value * (BITSLIDES + 0.5f)); - return juce::String(slices) + " slices/measure"; - }, - nullptr)); - - // Speed: displays as speed multiplier - params.push_back(std::make_unique( - juce::ParameterID(SPEED_ID, 1), "Slow Speed", - juce::NormalisableRange(0.0f, 1.0f, 0.001f), - 0.25f, - juce::String(), - juce::AudioProcessorParameter::genericParameter, - [](float value, int) { - float speedVal = (1.0f - value + 0.01f) * 4.0f; - return juce::String(speedVal, 2) + "x"; - }, - nullptr)); - - // Probability parameters: display as percentage - auto probToString = [](float value, int) { - return juce::String(static_cast(value * 100.0f)) + "%"; - }; - - params.push_back(std::make_unique( - juce::ParameterID(PROB_REVERSE_ID, 1), "Reverse Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), - 0.15f, juce::String(), juce::AudioProcessorParameter::genericParameter, - probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_SPEED_ID, 1), "Slow Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), - 0.05f, juce::String(), juce::AudioProcessorParameter::genericParameter, - probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_REARRANGE_ID, 1), "Rearrange Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), - 0.95f, juce::String(), juce::AudioProcessorParameter::genericParameter, - probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_SILENCE_ID, 1), "Silence Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), - 0.0f, juce::String(), juce::AudioProcessorParameter::genericParameter, - probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_REPEAT_ID, 1), "Repeat Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), - 0.4f, juce::String(), juce::AudioProcessorParameter::genericParameter, - probToString, nullptr)); - - // Instant toggles - params.push_back(std::make_unique( - juce::ParameterID(INSTANT_REVERSE_ID, 1), "Instant Reverse", false)); - params.push_back(std::make_unique( - juce::ParameterID(INSTANT_SPEED_ID, 1), "Instant Slow", false)); - params.push_back(std::make_unique( - juce::ParameterID(INSTANT_REPEAT_ID, 1), "Instant Repeat", false)); - - return { params.begin(), params.end() }; +SupaTriggaProcessor::~SupaTriggaProcessor() {} + +juce::AudioProcessorValueTreeState::ParameterLayout +SupaTriggaProcessor::createParameterLayout() { + std::vector> params; + + // Granularity: displays as number of slices (1, 2, 4, 8, 16, 32, 64, 128) + params.push_back(std::make_unique( + juce::ParameterID(GRANULARITY_ID, 1), "Slices", + juce::NormalisableRange(0.0f, 1.0f, 0.001f), 0.3f, juce::String(), + juce::AudioProcessorParameter::genericParameter, + [](float value, int) { + int slices = 1 << static_cast(value * (BITSLIDES + 0.5f)); + return juce::String(slices); + }, + nullptr)); + + // Speed: displays as speed multiplier + params.push_back(std::make_unique( + juce::ParameterID(SPEED_ID, 1), "Slow Speed", + juce::NormalisableRange(0.0f, 1.0f, 0.001f), 0.25f, juce::String(), + juce::AudioProcessorParameter::genericParameter, + [](float value, int) { + float speedVal = (1.0f - value + 0.01f) * 4.0f; + return juce::String(speedVal, 2) + "x"; + }, + nullptr)); + + // Probability parameters: display as percentage + auto probToString = [](float value, int) { + return juce::String(static_cast(value * 100.0f)) + "%"; + }; + + params.push_back(std::make_unique( + juce::ParameterID(PROB_REVERSE_ID, 1), "Reverse Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.15f, juce::String(), + juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_SPEED_ID, 1), "Slow Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.05f, juce::String(), + juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_REARRANGE_ID, 1), "Rearrange Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.95f, juce::String(), + juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_SILENCE_ID, 1), "Silence Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.0f, juce::String(), + juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_REPEAT_ID, 1), "Repeat Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.4f, juce::String(), + juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); + + // Instant toggles + params.push_back(std::make_unique( + juce::ParameterID(INSTANT_REVERSE_ID, 1), "Instant Reverse", false)); + params.push_back(std::make_unique( + juce::ParameterID(INSTANT_SPEED_ID, 1), "Instant Slow", false)); + params.push_back(std::make_unique( + juce::ParameterID(INSTANT_REPEAT_ID, 1), "Instant Repeat", false)); + + return {params.begin(), params.end()}; } -const juce::String SupaTriggaProcessor::getName() const -{ - return JucePlugin_Name; +const juce::String SupaTriggaProcessor::getName() const { + return JucePlugin_Name; } bool SupaTriggaProcessor::acceptsMidi() const { return false; } @@ -136,400 +123,396 @@ double SupaTriggaProcessor::getTailLengthSeconds() const { return 0.0; } int SupaTriggaProcessor::getNumPrograms() { return 1; } int SupaTriggaProcessor::getCurrentProgram() { return 0; } void SupaTriggaProcessor::setCurrentProgram(int /*index*/) {} -const juce::String SupaTriggaProcessor::getProgramName(int /*index*/) { return {}; } -void SupaTriggaProcessor::changeProgramName(int /*index*/, const juce::String& /*newName*/) {} - -void SupaTriggaProcessor::prepareToPlay(double sampleRate, int /*samplesPerBlock*/) -{ - currentSampleRate = static_cast(sampleRate); - fadeCoeff = std::exp(std::log(0.01f) / FADETIME); - - // Reset state - positionInMeasure = 0; - previousSliceIndex = 0xffffffff; - granularityMask = 0; - granularity = 0; - gain = 0.0f; - speed = 0.0f; - first = true; - wasPlaying = false; - - // Clear buffers - for (size_t i = 0; i < MAXSIZE; i++) - { - leftBuffer[i] = 0.0f; - rightBuffer[i] = 0.0f; - } - - randomize(); -} - -void SupaTriggaProcessor::releaseResources() -{ +const juce::String SupaTriggaProcessor::getProgramName(int /*index*/) { + return {}; } - -bool SupaTriggaProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const -{ - if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) - return false; - if (layouts.getMainInputChannelSet() != juce::AudioChannelSet::stereo()) - return false; - return true; +void SupaTriggaProcessor::changeProgramName(int /*index*/, + const juce::String & /*newName*/) {} + +void SupaTriggaProcessor::prepareToPlay(double sampleRate, + int /*samplesPerBlock*/) { + currentSampleRate = static_cast(sampleRate); + fadeCoeff = std::exp(std::log(0.01f) / FADETIME); + + // Reset state + positionInMeasure = 0; + previousSliceIndex = 0xffffffff; + granularityMask = 0; + granularity = 0; + gain = 0.0f; + speed = 0.0f; + first = true; + wasPlaying = false; + + // Clear buffers + for (size_t i = 0; i < MAXSIZE; i++) { + leftBuffer[i] = 0.0f; + rightBuffer[i] = 0.0f; + } + + randomize(); } -void SupaTriggaProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& /*midiMessages*/) -{ - juce::ScopedNoDenormals noDenormals; - - auto* in1 = buffer.getReadPointer(0); - auto* in2 = buffer.getReadPointer(1); - auto* out1 = buffer.getWritePointer(0); - auto* out2 = buffer.getWritePointer(1); - const int sampleFrames = buffer.getNumSamples(); +void SupaTriggaProcessor::releaseResources() {} - // Get transport info from host - auto* playHead = getPlayHead(); - if (playHead == nullptr) - { - // No playhead, pass through - for (int i = 0; i < sampleFrames; i++) - { - out1[i] = in1[i]; - out2[i] = in2[i]; - } - return; - } +bool SupaTriggaProcessor::isBusesLayoutSupported( + const BusesLayout &layouts) const { + if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + if (layouts.getMainInputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + return true; +} - auto posInfo = playHead->getPosition(); - if (!posInfo.hasValue()) - { - // No position info, pass through - for (int i = 0; i < sampleFrames; i++) - { - out1[i] = in1[i]; - out2[i] = in2[i]; - } - return; +void SupaTriggaProcessor::processBlock(juce::AudioBuffer &buffer, + juce::MidiBuffer & /*midiMessages*/) { + juce::ScopedNoDenormals noDenormals; + + auto *in1 = buffer.getReadPointer(0); + auto *in2 = buffer.getReadPointer(1); + auto *out1 = buffer.getWritePointer(0); + auto *out2 = buffer.getWritePointer(1); + const int sampleFrames = buffer.getNumSamples(); + + // Get transport info from host + auto *playHead = getPlayHead(); + if (playHead == nullptr) { + // No playhead, pass through + for (int i = 0; i < sampleFrames; i++) { + out1[i] = in1[i]; + out2[i] = in2[i]; } - - // Check if we have all required info - bool isPlaying = posInfo->getIsPlaying(); - auto bpmOpt = posInfo->getBpm(); - auto timeSigOpt = posInfo->getTimeSignature(); - auto ppqPosOpt = posInfo->getPpqPosition(); - auto barPosOpt = posInfo->getPpqPositionOfLastBarStart(); - - if (!isPlaying || !bpmOpt.hasValue() || !timeSigOpt.hasValue() || !ppqPosOpt.hasValue()) - { - positionInMeasure = 0xffffffff; - for (int i = 0; i < sampleFrames; i++) - { - out1[i] = in1[i]; - out2[i] = in2[i]; - } - wasPlaying = isPlaying; - return; + return; + } + + auto posInfo = playHead->getPosition(); + if (!posInfo.hasValue()) { + // No position info, pass through + for (int i = 0; i < sampleFrames; i++) { + out1[i] = in1[i]; + out2[i] = in2[i]; } - - double tempo = *bpmOpt; - if (tempo < 20.0) - { - // Ridiculous tempo - for (int i = 0; i < sampleFrames; i++) - { - out1[i] = in1[i]; - out2[i] = in2[i]; - } - return; + return; + } + + // Check if we have all required info + bool isPlaying = posInfo->getIsPlaying(); + auto bpmOpt = posInfo->getBpm(); + auto timeSigOpt = posInfo->getTimeSignature(); + auto ppqPosOpt = posInfo->getPpqPosition(); + auto barPosOpt = posInfo->getPpqPositionOfLastBarStart(); + + if (!isPlaying || !bpmOpt.hasValue() || !timeSigOpt.hasValue() || + !ppqPosOpt.hasValue()) { + positionInMeasure = 0xffffffff; + for (int i = 0; i < sampleFrames; i++) { + out1[i] = in1[i]; + out2[i] = in2[i]; } - - auto timeSig = *timeSigOpt; - double ppqPos = *ppqPosOpt; - double barStartPos = barPosOpt.hasValue() ? *barPosOpt : ppqPos; - - // Calculate samples in measure - double tempoBPS = tempo / 60.0; - double numSamplesInBeat = currentSampleRate / tempoBPS; - double samplesInMeasureDouble = std::ceil(numSamplesInBeat * timeSig.numerator / (timeSig.denominator / 4.0)); - unsigned long samplesInMeasure = static_cast(samplesInMeasureDouble); - - // Detect transport changes - bool playbackChanged = (isPlaying != wasPlaying); wasPlaying = isPlaying; - - // Calculate distance to next bar - double beatsPerBar = static_cast(timeSig.numerator); - double distanceToNextBarPPQ; - if (std::fabs(barStartPos - ppqPos) < 1e-10) - distanceToNextBarPPQ = 0.0; - else - distanceToNextBarPPQ = barStartPos + beatsPerBar - ppqPos; - - while (distanceToNextBarPPQ < 0.0) - distanceToNextBarPPQ += beatsPerBar; - while (distanceToNextBarPPQ >= beatsPerBar) - distanceToNextBarPPQ -= beatsPerBar; - - double numSamplesToNextBar = (distanceToNextBarPPQ * currentSampleRate * 60.0) / tempo; - if (numSamplesToNextBar < 0.0) - numSamplesToNextBar = 0.0; - - unsigned long samplesToNextBar = static_cast(std::ceil(numSamplesToNextBar)); - - // If playback changed, recalculate position - if (playbackChanged) - { - positionInMeasure = static_cast(std::floor(samplesInMeasureDouble - numSamplesToNextBar)); - if (positionInMeasure >= samplesInMeasure) - positionInMeasure = samplesInMeasure - 1; + return; + } + + double tempo = *bpmOpt; + if (tempo < 20.0) { + // Ridiculous tempo + for (int i = 0; i < sampleFrames; i++) { + out1[i] = in1[i]; + out2[i] = in2[i]; + } + return; + } + + auto timeSig = *timeSigOpt; + double ppqPos = *ppqPosOpt; + double barStartPos = barPosOpt.hasValue() ? *barPosOpt : ppqPos; + + // Calculate samples in measure + double tempoBPS = tempo / 60.0; + double numSamplesInBeat = currentSampleRate / tempoBPS; + double samplesInMeasureDouble = std::ceil( + numSamplesInBeat * timeSig.numerator / (timeSig.denominator / 4.0)); + unsigned long samplesInMeasure = + static_cast(samplesInMeasureDouble); + + // Detect transport changes + bool playbackChanged = (isPlaying != wasPlaying); + wasPlaying = isPlaying; + + // Calculate distance to next bar + double beatsPerBar = static_cast(timeSig.numerator); + double distanceToNextBarPPQ; + if (std::fabs(barStartPos - ppqPos) < 1e-10) + distanceToNextBarPPQ = 0.0; + else + distanceToNextBarPPQ = barStartPos + beatsPerBar - ppqPos; + + while (distanceToNextBarPPQ < 0.0) + distanceToNextBarPPQ += beatsPerBar; + while (distanceToNextBarPPQ >= beatsPerBar) + distanceToNextBarPPQ -= beatsPerBar; + + double numSamplesToNextBar = + (distanceToNextBarPPQ * currentSampleRate * 60.0) / tempo; + if (numSamplesToNextBar < 0.0) + numSamplesToNextBar = 0.0; + + unsigned long samplesToNextBar = + static_cast(std::ceil(numSamplesToNextBar)); + + // If playback changed, recalculate position + if (playbackChanged) { + positionInMeasure = static_cast( + std::floor(samplesInMeasureDouble - numSamplesToNextBar)); + if (positionInMeasure >= samplesInMeasure) + positionInMeasure = samplesInMeasure - 1; + } + + // Get parameter values + float granularityRaw = granularityParam->load(); + float speedRaw = speedParam->load(); + + unsigned long granularityTmp = + static_cast(granularityRaw * (BITSLIDES + 0.5f)); + unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); + + float speedDiff = std::exp(std::log(0.01f) / (((1.0f - speedRaw) + 0.01f) * + currentSampleRate * 4.0f)); + + for (int i = 0; i < sampleFrames; i++) { + // Reset position at bar border + if (static_cast(i) == samplesToNextBar) + positionInMeasure = 0; + + // Store input in buffer + if (positionInMeasure < MAXSIZE) { + leftBuffer[positionInMeasure] = in1[i]; + rightBuffer[positionInMeasure] = in2[i]; } - // Get parameter values - float granularityRaw = granularityParam->load(); - float speedRaw = speedParam->load(); - - unsigned long granularityTmp = static_cast(granularityRaw * (BITSLIDES + 0.5f)); - unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); + unsigned long sliceIndex = + ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & granularityMask; + + if (instantRepeat && sliceIndex != 0) + sequencer[sliceIndex].offset = + (sequencer[(sliceIndex - 1) & granularityMaskTmp].offset + + (sliceIndex - ((sliceIndex - 1) & granularityMaskTmp))) & + granularityMask; + + unsigned long displacement = sequencer[sliceIndex].offset & granularityMask; + + if (granularityMaskTmp != granularityMask) { + unsigned long sliceIndexTmp = + ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & + granularityMaskTmp; + + if ((granularityTmp < granularity && sliceIndex == 0) || + (granularityTmp > granularity && sliceIndexTmp == 0) || + (sliceIndex == 0 && sliceIndexTmp == 0)) { + granularityMask = granularityMaskTmp; + granularity = granularityTmp; + sliceIndex = sliceIndexTmp; + displacement = sequencer[sliceIndex].offset & granularityMask; + } + } - float speedDiff = std::exp(std::log(0.01f) / (((1.0f - speedRaw) + 0.01f) * currentSampleRate * 4.0f)); + if (sliceIndex != previousSliceIndex) { + instantRepeat = instantRepeatParam->load() > 0.5f; + instantReverse = instantReverseParam->load() > 0.5f; + instantSlow = instantSpeedParam->load() > 0.5f; + previousSliceIndex = sliceIndex; + } - for (int i = 0; i < sampleFrames; i++) + // Gain calculation { - // Reset position at bar border - if (static_cast(i) == samplesToNextBar) - positionInMeasure = 0; - - // Store input in buffer - if (positionInMeasure < MAXSIZE) - { - leftBuffer[positionInMeasure] = in1[i]; - rightBuffer[positionInMeasure] = in2[i]; - } + unsigned long sliceIndexFar = + ((((positionInMeasure + FADETIME) % samplesInMeasure) * MAXSLIDES) / + samplesInMeasure) & + granularityMask; + unsigned long displacementFar = + sequencer[sliceIndexFar].offset & granularityMask; + bool reverseFar = sequencer[sliceIndexFar].reverse; - unsigned long sliceIndex = ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & granularityMask; + float targetGain = 1.0f; - if (instantRepeat && sliceIndex != 0) - sequencer[sliceIndex].offset = (sequencer[(sliceIndex - 1) & granularityMaskTmp].offset + - (sliceIndex - ((sliceIndex - 1) & granularityMaskTmp))) & granularityMask; + if (sequencer[sliceIndex].silence) + targetGain = 0.0f; - unsigned long displacement = sequencer[sliceIndex].offset & granularityMask; + if (displacementFar != displacement || + positionInMeasure + FADETIME > samplesInMeasure || + reverseFar != sequencer[sliceIndex].reverse) + targetGain = 0.0f; - if (granularityMaskTmp != granularityMask) - { - unsigned long sliceIndexTmp = ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & granularityMaskTmp; + gain = fadeCoeff * gain + (1.0f - fadeCoeff) * targetGain; + } - if ((granularityTmp < granularity && sliceIndex == 0) || - (granularityTmp > granularity && sliceIndexTmp == 0) || - (sliceIndex == 0 && sliceIndexTmp == 0)) - { - granularityMask = granularityMaskTmp; - granularity = granularityTmp; - sliceIndex = sliceIndexTmp; - displacement = sequencer[sliceIndex].offset & granularityMask; - } + if ((sequencer[sliceIndex].reverse || instantReverse) && + displacement != 0) { + if (!(sequencer[sliceIndex].stop || instantSlow)) { + unsigned long sliceSize = samplesInMeasure >> granularity; + unsigned long sliceStart = (positionInMeasure / sliceSize) * sliceSize; + unsigned long sliceDiff = positionInMeasure - sliceStart; + unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; + unsigned long difference = + (displacement * samplesInMeasure) / MAXSLIDES; + unsigned long bufferIndex = + difference <= sliceEnd ? sliceEnd - difference : 0; + + if (bufferIndex < MAXSIZE) { + out1[i] = leftBuffer[bufferIndex] * gain; + out2[i] = rightBuffer[bufferIndex] * gain; } - - if (sliceIndex != previousSliceIndex) - { - instantRepeat = instantRepeatParam->load() > 0.5f; - instantReverse = instantReverseParam->load() > 0.5f; - instantSlow = instantSpeedParam->load() > 0.5f; - previousSliceIndex = sliceIndex; + first = true; + } else { + if (first) { + unsigned long sliceSize = samplesInMeasure >> granularity; + unsigned long sliceStart = + (positionInMeasure / sliceSize) * sliceSize; + unsigned long sliceDiff = positionInMeasure - sliceStart; + unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; + unsigned long difference = + (displacement * samplesInMeasure) / MAXSLIDES; + + position = static_cast( + difference <= sliceEnd ? sliceEnd - difference : 0); + speed = 1.0f / speedDiff; + first = false; } - // Gain calculation - { - unsigned long sliceIndexFar = ((((positionInMeasure + FADETIME) % samplesInMeasure) * MAXSLIDES) / samplesInMeasure) & granularityMask; - unsigned long displacementFar = sequencer[sliceIndexFar].offset & granularityMask; - bool reverseFar = sequencer[sliceIndexFar].reverse; + speed *= speedDiff; + position -= speed; - float targetGain = 1.0f; + if (position < 0.0f) + position = 0.0f; - if (sequencer[sliceIndex].silence) - targetGain = 0.0f; + unsigned long bufferIndex = + static_cast(std::floor(position)); + float alpha = position - bufferIndex; - if (displacementFar != displacement || - positionInMeasure + FADETIME > samplesInMeasure || - reverseFar != sequencer[sliceIndex].reverse) - targetGain = 0.0f; - - gain = fadeCoeff * gain + (1.0f - fadeCoeff) * targetGain; + if (bufferIndex < MAXSIZE - 3) { + out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; + out2[i] = + hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; } - - if ((sequencer[sliceIndex].reverse || instantReverse) && displacement != 0) - { - if (!(sequencer[sliceIndex].stop || instantSlow)) - { - unsigned long sliceSize = samplesInMeasure >> granularity; - unsigned long sliceStart = (positionInMeasure / sliceSize) * sliceSize; - unsigned long sliceDiff = positionInMeasure - sliceStart; - unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; - unsigned long difference = (displacement * samplesInMeasure) / MAXSLIDES; - unsigned long bufferIndex = difference <= sliceEnd ? sliceEnd - difference : 0; - - if (bufferIndex < MAXSIZE) - { - out1[i] = leftBuffer[bufferIndex] * gain; - out2[i] = rightBuffer[bufferIndex] * gain; - } - first = true; - } - else - { - if (first) - { - unsigned long sliceSize = samplesInMeasure >> granularity; - unsigned long sliceStart = (positionInMeasure / sliceSize) * sliceSize; - unsigned long sliceDiff = positionInMeasure - sliceStart; - unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; - unsigned long difference = (displacement * samplesInMeasure) / MAXSLIDES; - - position = static_cast(difference <= sliceEnd ? sliceEnd - difference : 0); - speed = 1.0f / speedDiff; - first = false; - } - - speed *= speedDiff; - position -= speed; - - if (position < 0.0f) - position = 0.0f; - - unsigned long bufferIndex = static_cast(std::floor(position)); - float alpha = position - bufferIndex; - - if (bufferIndex < MAXSIZE - 3) - { - out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; - out2[i] = hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; - } - } + } + } else { + if (!(sequencer[sliceIndex].stop || instantSlow) || displacement == 0) { + unsigned long difference = + (displacement * samplesInMeasure) / MAXSLIDES; + unsigned long bufferIndex = + positionInMeasure > difference ? positionInMeasure - difference : 0; + + if (bufferIndex < MAXSIZE) { + out1[i] = leftBuffer[bufferIndex] * gain; + out2[i] = rightBuffer[bufferIndex] * gain; } - else - { - if (!(sequencer[sliceIndex].stop || instantSlow) || displacement == 0) - { - unsigned long difference = (displacement * samplesInMeasure) / MAXSLIDES; - unsigned long bufferIndex = positionInMeasure > difference ? positionInMeasure - difference : 0; - - if (bufferIndex < MAXSIZE) - { - out1[i] = leftBuffer[bufferIndex] * gain; - out2[i] = rightBuffer[bufferIndex] * gain; - } - first = true; - } - else - { - if (first) - { - position = static_cast(positionInMeasure - (displacement * samplesInMeasure) / MAXSLIDES); - speed = 1.0f / speedDiff; - first = false; - } - - speed *= speedDiff; - position += speed; - - unsigned long bufferIndex = static_cast(std::floor(position)); - float alpha = position - bufferIndex; - - if (bufferIndex < MAXSIZE - 3) - { - out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; - out2[i] = hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; - } - } + first = true; + } else { + if (first) { + position = + static_cast(positionInMeasure - + (displacement * samplesInMeasure) / MAXSLIDES); + speed = 1.0f / speedDiff; + first = false; } - positionInMeasure++; + speed *= speedDiff; + position += speed; - if (positionInMeasure >= samplesInMeasure) - { - randomize(); - positionInMeasure = 0; - } + unsigned long bufferIndex = + static_cast(std::floor(position)); + float alpha = position - bufferIndex; - if (std::fabs(gain) < 1e-10f) - gain = 0.0f; + if (bufferIndex < MAXSIZE - 3) { + out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; + out2[i] = + hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; + } + } } -} -void SupaTriggaProcessor::randomize() -{ - float granularityRaw = granularityParam->load(); - float probRearrangeRaw = probRearrangeParam->load(); - float probRepeatRaw = probRepeatParam->load(); - float probReverseRaw = probReverseParam->load(); - float probSpeedRaw = probSpeedParam->load(); - float probSilenceRaw = probSilenceParam->load(); + positionInMeasure++; - unsigned long granularityTmp = static_cast(granularityRaw * (BITSLIDES + 0.5f)); - unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); + if (positionInMeasure >= samplesInMeasure) { + randomize(); + positionInMeasure = 0; + } - for (unsigned long i = 0; i < MAXSLIDES; i++) - { - if ((std::rand() % 100) < static_cast(probRearrangeRaw * 101.0f)) - { - if ((std::rand() % 100) < static_cast(probRepeatRaw * 101.0f)) - { - if (i != 0) - sequencer[i].offset = (sequencer[(i - 1) & granularityMaskTmp].offset + - (i - ((i - 1) & granularityMaskTmp))) & granularityMask; - else - sequencer[i].offset = 0; - } - else - { - sequencer[i].offset = (i * static_cast(std::rand() % MAXSLIDES)) / MAXSLIDES; - } - } - else - { - sequencer[i].offset = 0; - } + if (std::fabs(gain) < 1e-10f) + gain = 0.0f; + } +} - if ((std::rand() % 100) < static_cast(probReverseRaw * 100.0f) && sequencer[i].offset > 0) - sequencer[i].reverse = true; +void SupaTriggaProcessor::randomize() { + float granularityRaw = granularityParam->load(); + float probRearrangeRaw = probRearrangeParam->load(); + float probRepeatRaw = probRepeatParam->load(); + float probReverseRaw = probReverseParam->load(); + float probSpeedRaw = probSpeedParam->load(); + float probSilenceRaw = probSilenceParam->load(); + + unsigned long granularityTmp = + static_cast(granularityRaw * (BITSLIDES + 0.5f)); + unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); + + for (unsigned long i = 0; i < MAXSLIDES; i++) { + if ((std::rand() % 100) < static_cast(probRearrangeRaw * 101.0f)) { + if ((std::rand() % 100) < static_cast(probRepeatRaw * 101.0f)) { + if (i != 0) + sequencer[i].offset = + (sequencer[(i - 1) & granularityMaskTmp].offset + + (i - ((i - 1) & granularityMaskTmp))) & + granularityMask; else - sequencer[i].reverse = false; + sequencer[i].offset = 0; + } else { + sequencer[i].offset = + (i * static_cast(std::rand() % MAXSLIDES)) / + MAXSLIDES; + } + } else { + sequencer[i].offset = 0; + } - if ((std::rand() % 100) < static_cast(probSpeedRaw * 101.0f)) - sequencer[i].stop = true; - else - sequencer[i].stop = false; + if ((std::rand() % 100) < static_cast(probReverseRaw * 100.0f) && + sequencer[i].offset > 0) + sequencer[i].reverse = true; + else + sequencer[i].reverse = false; - if ((std::rand() % 100) < static_cast(probSilenceRaw * 101.0f)) - sequencer[i].silence = true; - else - sequencer[i].silence = false; - } + if ((std::rand() % 100) < static_cast(probSpeedRaw * 101.0f)) + sequencer[i].stop = true; + else + sequencer[i].stop = false; + + if ((std::rand() % 100) < static_cast(probSilenceRaw * 101.0f)) + sequencer[i].silence = true; + else + sequencer[i].silence = false; + } } -bool SupaTriggaProcessor::hasEditor() const { return false; } +bool SupaTriggaProcessor::hasEditor() const { return true; } -juce::AudioProcessorEditor* SupaTriggaProcessor::createEditor() -{ - return new juce::GenericAudioProcessorEditor(*this); +juce::AudioProcessorEditor *SupaTriggaProcessor::createEditor() { + return new SupaTriggaEditor(*this, apvts); } -void SupaTriggaProcessor::getStateInformation(juce::MemoryBlock& destData) -{ - auto state = apvts.copyState(); - std::unique_ptr xml(state.createXml()); - copyXmlToBinary(*xml, destData); +void SupaTriggaProcessor::getStateInformation(juce::MemoryBlock &destData) { + auto state = apvts.copyState(); + std::unique_ptr xml(state.createXml()); + copyXmlToBinary(*xml, destData); } -void SupaTriggaProcessor::setStateInformation(const void* data, int sizeInBytes) -{ - std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); - if (xmlState != nullptr && xmlState->hasTagName(apvts.state.getType())) - { - apvts.replaceState(juce::ValueTree::fromXml(*xmlState)); - } +void SupaTriggaProcessor::setStateInformation(const void *data, + int sizeInBytes) { + std::unique_ptr xmlState( + getXmlFromBinary(data, sizeInBytes)); + if (xmlState != nullptr && xmlState->hasTagName(apvts.state.getType())) { + apvts.replaceState(juce::ValueTree::fromXml(*xmlState)); + } } -juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() -{ - return new SupaTriggaProcessor(); +juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { + return new SupaTriggaProcessor(); } diff --git a/supatrigga/Source/PluginProcessor.h b/supatrigga/Source/PluginProcessor.h index 769d9f7..1fa9a5a 100644 --- a/supatrigga/Source/PluginProcessor.h +++ b/supatrigga/Source/PluginProcessor.h @@ -1,8 +1,10 @@ #pragma once -#include #include #include +#include + +class SupaTriggaEditor; // Forward declaration // Constants from original constexpr int NUMBERIO = 2; @@ -12,121 +14,119 @@ constexpr int MAXSLIDES = 1 << BITSLIDES; constexpr int FADETIME = 150; // Glitch parameters for each slice -struct GlitchParams -{ - unsigned long offset = 0; - bool reverse = false; - bool stop = false; - bool silence = false; +struct GlitchParams { + unsigned long offset = 0; + bool reverse = false; + bool stop = false; + bool silence = false; }; // Hermite interpolation (inverse direction) -inline float hermiteInverse(float* wavetable, unsigned long nearest_sample, float x) -{ - float y3 = (nearest_sample == 0) ? 0.f : wavetable[nearest_sample - 1]; - float y2 = wavetable[nearest_sample]; - float y1 = wavetable[nearest_sample + 1]; - float y0 = wavetable[nearest_sample + 2]; +inline float hermiteInverse(float *wavetable, unsigned long nearest_sample, + float x) { + float y3 = (nearest_sample == 0) ? 0.f : wavetable[nearest_sample - 1]; + float y2 = wavetable[nearest_sample]; + float y1 = wavetable[nearest_sample + 1]; + float y0 = wavetable[nearest_sample + 2]; - x = 1.f - x; + x = 1.f - x; - float c0 = y1; - float c1 = 0.5f * (y2 - y0); - float c2 = y0 - 2.5f * y1 + 2.f * y2 - 0.5f * y3; - float c3 = 1.5f * (y1 - y2) + 0.5f * (y3 - y0); + float c0 = y1; + float c1 = 0.5f * (y2 - y0); + float c2 = y0 - 2.5f * y1 + 2.f * y2 - 0.5f * y3; + float c3 = 1.5f * (y1 - y2) + 0.5f * (y3 - y0); - return ((c3 * x + c2) * x + c1) * x + c0; + return ((c3 * x + c2) * x + c1) * x + c0; } -class SupaTriggaProcessor : public juce::AudioProcessor -{ +class SupaTriggaProcessor : public juce::AudioProcessor { public: - SupaTriggaProcessor(); - ~SupaTriggaProcessor() override; + SupaTriggaProcessor(); + ~SupaTriggaProcessor() override; - void prepareToPlay(double sampleRate, int samplesPerBlock) override; - void releaseResources() override; + void prepareToPlay(double sampleRate, int samplesPerBlock) override; + void releaseResources() override; - bool isBusesLayoutSupported(const BusesLayout& layouts) const override; + bool isBusesLayoutSupported(const BusesLayout &layouts) const override; - void processBlock(juce::AudioBuffer&, juce::MidiBuffer&) override; + void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; - juce::AudioProcessorEditor* createEditor() override; - bool hasEditor() const override; + juce::AudioProcessorEditor *createEditor() override; + bool hasEditor() const override; - const juce::String getName() const override; + const juce::String getName() const override; - bool acceptsMidi() const override; - bool producesMidi() const override; - bool isMidiEffect() const override; - double getTailLengthSeconds() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + bool isMidiEffect() const override; + double getTailLengthSeconds() const override; - int getNumPrograms() override; - int getCurrentProgram() override; - void setCurrentProgram(int index) override; - const juce::String getProgramName(int index) override; - void changeProgramName(int index, const juce::String& newName) override; + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram(int index) override; + const juce::String getProgramName(int index) override; + void changeProgramName(int index, const juce::String &newName) override; - void getStateInformation(juce::MemoryBlock& destData) override; - void setStateInformation(const void* data, int sizeInBytes) override; + void getStateInformation(juce::MemoryBlock &destData) override; + void setStateInformation(const void *data, int sizeInBytes) override; - // Parameter IDs - static constexpr const char* GRANULARITY_ID = "granularity"; - static constexpr const char* SPEED_ID = "speed"; - static constexpr const char* PROB_REVERSE_ID = "probReverse"; - static constexpr const char* PROB_SPEED_ID = "probSpeed"; - static constexpr const char* PROB_REARRANGE_ID = "probRearrange"; - static constexpr const char* PROB_SILENCE_ID = "probSilence"; - static constexpr const char* PROB_REPEAT_ID = "probRepeat"; - static constexpr const char* INSTANT_REVERSE_ID = "instantReverse"; - static constexpr const char* INSTANT_SPEED_ID = "instantSpeed"; - static constexpr const char* INSTANT_REPEAT_ID = "instantRepeat"; + // Parameter IDs + static constexpr const char *GRANULARITY_ID = "granularity"; + static constexpr const char *SPEED_ID = "speed"; + static constexpr const char *PROB_REVERSE_ID = "probReverse"; + static constexpr const char *PROB_SPEED_ID = "probSpeed"; + static constexpr const char *PROB_REARRANGE_ID = "probRearrange"; + static constexpr const char *PROB_SILENCE_ID = "probSilence"; + static constexpr const char *PROB_REPEAT_ID = "probRepeat"; + static constexpr const char *INSTANT_REVERSE_ID = "instantReverse"; + static constexpr const char *INSTANT_SPEED_ID = "instantSpeed"; + static constexpr const char *INSTANT_REPEAT_ID = "instantRepeat"; private: - juce::AudioProcessorValueTreeState apvts; - juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); - - void randomize(); - - // Buffers - std::unique_ptr leftBuffer; - std::unique_ptr rightBuffer; - - // Sequencer - GlitchParams sequencer[MAXSLIDES]; - - // State - unsigned long positionInMeasure = 0; - unsigned long previousSliceIndex = 0xffffffff; - unsigned long granularityMask = 0; - unsigned long granularity = 0; - - float gain = 0.0f; - float speed = 0.0f; - float position = 0.0f; - bool first = true; - - bool instantReverse = false; - bool instantSlow = false; - bool instantRepeat = false; - - float currentSampleRate = 44100.0f; - float fadeCoeff = 0.0f; - - // Last playback state for detecting transport changes - bool wasPlaying = false; - - // Parameter pointers - std::atomic* granularityParam = nullptr; - std::atomic* speedParam = nullptr; - std::atomic* probReverseParam = nullptr; - std::atomic* probSpeedParam = nullptr; - std::atomic* probRearrangeParam = nullptr; - std::atomic* probSilenceParam = nullptr; - std::atomic* probRepeatParam = nullptr; - std::atomic* instantReverseParam = nullptr; - std::atomic* instantSpeedParam = nullptr; - std::atomic* instantRepeatParam = nullptr; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SupaTriggaProcessor) + juce::AudioProcessorValueTreeState apvts; + juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); + + void randomize(); + + // Buffers + std::unique_ptr leftBuffer; + std::unique_ptr rightBuffer; + + // Sequencer + GlitchParams sequencer[MAXSLIDES]; + + // State + unsigned long positionInMeasure = 0; + unsigned long previousSliceIndex = 0xffffffff; + unsigned long granularityMask = 0; + unsigned long granularity = 0; + + float gain = 0.0f; + float speed = 0.0f; + float position = 0.0f; + bool first = true; + + bool instantReverse = false; + bool instantSlow = false; + bool instantRepeat = false; + + float currentSampleRate = 44100.0f; + float fadeCoeff = 0.0f; + + // Last playback state for detecting transport changes + bool wasPlaying = false; + + // Parameter pointers + std::atomic *granularityParam = nullptr; + std::atomic *speedParam = nullptr; + std::atomic *probReverseParam = nullptr; + std::atomic *probSpeedParam = nullptr; + std::atomic *probRearrangeParam = nullptr; + std::atomic *probSilenceParam = nullptr; + std::atomic *probRepeatParam = nullptr; + std::atomic *instantReverseParam = nullptr; + std::atomic *instantSpeedParam = nullptr; + std::atomic *instantRepeatParam = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SupaTriggaProcessor) }; diff --git a/supatrigga/Source/SupaTriggaEditor.cpp b/supatrigga/Source/SupaTriggaEditor.cpp new file mode 100644 index 0000000..2eb22bf --- /dev/null +++ b/supatrigga/Source/SupaTriggaEditor.cpp @@ -0,0 +1,326 @@ +#include "SupaTriggaEditor.h" +#include "Theme.h" + +SupaTriggaLookAndFeel::SupaTriggaLookAndFeel() { + + // Configure default colors + setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); + setColour(juce::Slider::rotarySliderOutlineColourId, Theme::Colors::track); + setColour(juce::Slider::textBoxTextColourId, Theme::Colors::textValue); + +#if JUCE_MAC + interFont = juce::Typeface::createSystemTypefaceFor(juce::FontOptions{}.withName("Inter").withHeight(12.0f).withStyle("Bold")); +#endif +} + +void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics &g, int x, int y, + int width, int height, + float sliderPos, + const float rotaryStartAngle, + const float rotaryEndAngle, + juce::Slider &slider) { + const float radius = 34.0f; + const float centreX = (float)x + (float)width * 0.5f; + const float centreY = (float)y + (float)height * 0.5f; + + // Track + const float trackThickness = 6.0f; + g.setColour(slider.findColour(juce::Slider::rotarySliderOutlineColourId)); + juce::Path trackPath; + trackPath.addCentredArc(centreX, centreY, radius, radius, 0.0f, rotaryStartAngle, rotaryEndAngle, true); + g.strokePath(trackPath, juce::PathStrokeType(trackThickness, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); + + // Fill + auto fillColour = slider.findColour(juce::Slider::rotarySliderFillColourId); + g.setColour(fillColour); + + // Only draw fill if greater than 0 + if (sliderPos > 0.0f) { + const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); + juce::Path fillPath; + fillPath.addCentredArc(centreX, centreY, radius, radius, 0.0f, rotaryStartAngle, angle, true); + g.strokePath(fillPath, juce::PathStrokeType(trackThickness, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); + } + + // Center Inner Circle + const float innerRadius = Theme::Metrics::innerRadius; + const float innerDiameter = innerRadius * 2.0f; + g.setColour(Theme::Colors::background); + g.fillEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter); + g.setColour(Theme::Colors::innerCircle); + g.drawEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter, 1.0f); + + // Text Value + g.setColour(slider.findColour(juce::Slider::textBoxTextColourId)); + + auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); + if (interFont != nullptr) { + font = juce::Font(juce::FontOptions(interFont).withHeight(12.0f)); + } + g.setFont(font); + + juce::String text = slider.getTextFromValue(slider.getValue()); + g.drawText(text, x, y, width, height, juce::Justification::centred, false); +} + +void SupaTriggaLookAndFeel::drawToggleButton( + juce::Graphics &g, juce::ToggleButton &button, + bool /*shouldDrawButtonAsHighlighted*/, + bool /*shouldDrawButtonAsDown*/) { + const float radius = 34.0f; + const float centreX = button.getLocalBounds().getCentreX(); + const float centreY = button.getLocalBounds().getCentreY(); + + // Toggle Track + g.setColour(Theme::Colors::track); + g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 6.0f); + + // Toggle Fill (ON/OFF) + if (button.getToggleState()) { + g.setColour(button.findColour(juce::ToggleButton::tickColourId)); + g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 6.0f); + } + + // Center Inner Circle + const float innerRadius = Theme::Metrics::innerRadius; + const float innerDiameter = innerRadius * 2.0f; + g.setColour(Theme::Colors::background); + g.fillEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter); + g.setColour(Theme::Colors::innerCircle); + g.drawEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter, 1.0f); + + // Text Label + g.setColour(button.getToggleState() + ? Theme::Colors::textValue + : Theme::Colors::textSecondary); + + auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); + if (interFont != nullptr) { + font = juce::Font(juce::FontOptions(interFont).withHeight(12.0f)); + } + g.setFont(font); + + g.drawText(button.getName(), button.getLocalBounds(), juce::Justification::centred, false); +} + +SupaTriggaEditor::SupaTriggaEditor(SupaTriggaProcessor &p, + juce::AudioProcessorValueTreeState &vts) + : AudioProcessorEditor(&p), audioProcessor(p), apvts(vts) { + setLookAndFeel(&customLookAndFeel); + + // Global section + setupKnob(rearrangeKnob, "global"); + rearrangeAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REARRANGE_ID, rearrangeKnob); + + setupKnob(slicesKnob, "global"); + slicesAttach = std::make_unique(apvts, SupaTriggaProcessor::GRANULARITY_ID, slicesKnob); + + setupKnob(silenceKnob, "global"); + silenceAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_SILENCE_ID, silenceKnob); + + // Brake section + setupKnob(brakeProbKnob, "speed"); + brakeProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_SPEED_ID, brakeProbKnob); + + setupKnob(brakeTimeKnob, "speed"); + brakeTimeAttach = std::make_unique(apvts, SupaTriggaProcessor::SPEED_ID, brakeTimeKnob); + + setupToggle(brakeInstantToggle, "speed"); + brakeInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_SPEED_ID, brakeInstantToggle); + + // Reverse section + setupKnob(reverseProbKnob, "reverse"); + reverseProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REVERSE_ID, reverseProbKnob); + + setupToggle(reverseInstantToggle, "reverse"); + reverseInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_REVERSE_ID, reverseInstantToggle); + + // Repeat section + setupKnob(repeatProbKnob, "repeat"); + repeatProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REPEAT_ID, repeatProbKnob); + + setupToggle(repeatInstantToggle, "repeat"); + repeatInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_REPEAT_ID, repeatInstantToggle); + + // Make window non-resizable + setResizable(false, false); + + // Set Window Size + setSize(Theme::Metrics::windowWidth, Theme::Metrics::windowHeight); +} + +SupaTriggaEditor::~SupaTriggaEditor() { setLookAndFeel(nullptr); } + +void SupaTriggaEditor::setupKnob(juce::Slider &slider, + const juce::String &styleClass) { + slider.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); // Text is drawn by LookAndFeel + slider.setRotaryParameters(juce::MathConstants::pi * 1.25f, juce::MathConstants::pi * 2.75f, true); + addAndMakeVisible(slider); + + // Set styling colors + if (styleClass == "global") { + slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); + } else if (styleClass == "speed") { + slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::speedAccent); + } else if (styleClass == "reverse") { + slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::reverseAccent); + } else if (styleClass == "repeat") { + slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::repeatAccent); + } +} + +void SupaTriggaEditor::setupToggle(juce::ToggleButton &toggle, + const juce::String &styleClass) { + // Configure text change on toggle state change using a listener + toggle.setName(toggle.getToggleState() ? "On" : "Off"); + toggle.onClick = [&toggle] { + toggle.setName(toggle.getToggleState() ? "On" : "Off"); + }; + addAndMakeVisible(toggle); + + if (styleClass == "global") { + toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::globalAccent); + } else if (styleClass == "speed") { + toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::speedAccent); + } else if (styleClass == "reverse") { + toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::reverseAccent); + } else if (styleClass == "repeat") { + toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::repeatAccent); + } +} + +void SupaTriggaEditor::paint(juce::Graphics &g) { + // Background + g.fillAll(Theme::Colors::background); + + // Header Panel is flush with the background + + // Draw thick bottom line of header + g.setColour(Theme::Colors::border); + g.drawLine(0.0f, 56.0f, (float)getWidth(), 56.0f, 1.0f); + + // Draw header text + g.setColour(Theme::Colors::textPrimary); + g.setFont(juce::FontOptions(20.0f).withStyle("Bold")); + g.drawText("SUPATRIGGA", 20, 0, 200, 56, juce::Justification::centredLeft, true); + + g.setColour(Theme::Colors::textSecondaryDim); + g.setFont(juce::FontOptions(11.0f).withStyle("Bold")); + g.drawText("BY SMARTELECTRONIX", getWidth() - 200, 0, 180, 56, juce::Justification::centredRight, true); + + // Section Dividers (the cross in the grid) + auto gridBounds = getLocalBounds().withTrimmedTop(56); + int midX = gridBounds.getCentreX(); + int midY = gridBounds.getCentreY(); + + g.setColour(Theme::Colors::border); + // Vertical line (indented top and bottom by 16px) + g.drawLine((float)midX, (float)(gridBounds.getY() + 16), (float)midX, (float)(gridBounds.getBottom() - 16), 1.0f); + // Horizontal line (indented left/right by 20px, gap in center) + g.drawLine(20.0f, (float)midY, (float)(midX - 16), (float)midY, 1.0f); + g.drawLine((float)(midX + 16), (float)midY, (float)(getWidth() - 20), (float)midY, 1.0f); + + // Draw Section Titles + auto drawSectionTitle = [&](const juce::String &title, juce::Colour c, int x, int y) { + g.setColour(c); + g.setFont(juce::FontOptions(14.0f).withStyle("Bold")); + g.drawText(title, x + 20, y + 16, 100, 20, juce::Justification::topLeft, true); + }; + + drawSectionTitle("GLOBAL", Theme::Colors::globalAccent, gridBounds.getX(), gridBounds.getY()); + drawSectionTitle("BRAKE", Theme::Colors::speedAccent, midX, gridBounds.getY()); + drawSectionTitle("REVERSE", Theme::Colors::reverseAccent, gridBounds.getX(), midY); + drawSectionTitle("REPEAT", Theme::Colors::repeatAccent, midX, midY); + + // Draw Sub-labels for controls + auto drawLabel = [&](const juce::String &text, juce::Component &comp) { + g.setColour(Theme::Colors::textSecondary); + g.setFont( juce::FontOptions(11.0f).withStyle("Bold")); + auto bounds = comp.getBounds(); + // Move the text up by drawing it in a box higher above the component + g.drawText(text, bounds.getX() - 20, bounds.getY() - 28, bounds.getWidth() + 40, 20, juce::Justification::centredBottom, false); + }; + + // Global Section Label centers + drawLabel("REARRANGE", rearrangeKnob); + drawLabel("SLICES", slicesKnob); + drawLabel("SILENCE", silenceKnob); + + // Brake Section Label centers + drawLabel("BRAKE PROB", brakeProbKnob); + drawLabel("BRAKE TIME", brakeTimeKnob); + drawLabel("INSTANT", brakeInstantToggle); + + // Reverse Section Label centers + drawLabel("REVERSE PROB", reverseProbKnob); + drawLabel("INSTANT", reverseInstantToggle); + + // Repeat Section Label centers + drawLabel("REPEAT PROB", repeatProbKnob); + drawLabel("INSTANT", repeatInstantToggle); +} + +void SupaTriggaEditor::resized() { + auto gridBounds = getLocalBounds().withTrimmedTop(56); + + int midX = gridBounds.getWidth() / 2; + int midY = gridBounds.getHeight() / 2; + + auto globalRect = gridBounds.withWidth(midX).withHeight(midY); + auto speedRect = globalRect.withX(midX); + auto reverseRect = globalRect.withY(gridBounds.getY() + midY); + auto repeatRect = reverseRect.withX(midX); + + // Controls Row bounds (leave room for title and labels) + auto globalRow = globalRect.withTrimmedTop(40).withTrimmedBottom(18); + auto speedRow = speedRect.withTrimmedTop(40).withTrimmedBottom(18); + auto reverseRow = reverseRect.withTrimmedTop(40).withTrimmedBottom(18); + auto repeatRow = repeatRect.withTrimmedTop(40).withTrimmedBottom(18); + + // juce::FlexBox is ideal here to distribute spacing + auto buildFlex = [](juce::FlexBox &fb) { + fb.justifyContent = juce::FlexBox::JustifyContent::spaceAround; + fb.alignItems = juce::FlexBox::AlignItems::flexEnd; // Bottom align to fix margin above divider + fb.flexDirection = juce::FlexBox::Direction::row; + }; + + auto mapItem = [](juce::Component &c) { + return juce::FlexItem(c).withWidth(80).withHeight(80).withMargin( + juce::FlexItem::Margin(0, 4, 0, 4)); + }; + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(rearrangeKnob)); + fb.items.add(mapItem(slicesKnob)); + fb.items.add(mapItem(silenceKnob)); + fb.performLayout(globalRow); + } + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(brakeProbKnob)); + fb.items.add(mapItem(brakeTimeKnob)); + fb.items.add(mapItem(brakeInstantToggle)); + fb.performLayout(speedRow); + } + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(reverseProbKnob)); + fb.items.add(mapItem(reverseInstantToggle)); + fb.performLayout(reverseRow); + } + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(repeatProbKnob)); + fb.items.add(mapItem(repeatInstantToggle)); + fb.performLayout(repeatRow); + } +} \ No newline at end of file diff --git a/supatrigga/Source/SupaTriggaEditor.h b/supatrigga/Source/SupaTriggaEditor.h new file mode 100644 index 0000000..37e5a37 --- /dev/null +++ b/supatrigga/Source/SupaTriggaEditor.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include "PluginProcessor.h" + +class SupaTriggaLookAndFeel : public juce::LookAndFeel_V4 +{ +public: + SupaTriggaLookAndFeel(); + + void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, + float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, + juce::Slider& slider) override; + + void drawToggleButton(juce::Graphics& g, juce::ToggleButton& button, + bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override; + +private: + juce::Typeface::Ptr interFont; +}; + +class SupaTriggaEditor : public juce::AudioProcessorEditor +{ +public: + SupaTriggaEditor(SupaTriggaProcessor&, juce::AudioProcessorValueTreeState&); + ~SupaTriggaEditor() override; + + void paint(juce::Graphics&) override; + void resized() override; + +private: + [[maybe_unused]] SupaTriggaProcessor& audioProcessor; + juce::AudioProcessorValueTreeState& apvts; + + SupaTriggaLookAndFeel customLookAndFeel; + + void setupKnob(juce::Slider& slider, const juce::String& styleClass); + void setupToggle(juce::ToggleButton& toggle, const juce::String& styleClass); + + // Header Components + // Not strictly needed as component, can be drawn in paint() instead to save overhead. + + // Global Section + juce::Slider rearrangeKnob; + juce::Slider slicesKnob; + juce::Slider silenceKnob; + + // Brake Section + juce::Slider brakeProbKnob; + juce::Slider brakeTimeKnob; + juce::ToggleButton brakeInstantToggle; + + // Reverse Section + juce::Slider reverseProbKnob; + juce::ToggleButton reverseInstantToggle; + + // Repeat Section + juce::Slider repeatProbKnob; + juce::ToggleButton repeatInstantToggle; + + // Attachments + using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; + using ButtonAttachment = juce::AudioProcessorValueTreeState::ButtonAttachment; + + std::unique_ptr rearrangeAttach; + std::unique_ptr slicesAttach; + std::unique_ptr silenceAttach; + std::unique_ptr brakeProbAttach; + std::unique_ptr brakeTimeAttach; + std::unique_ptr brakeInstantAttach; + std::unique_ptr reverseProbAttach; + std::unique_ptr reverseInstantAttach; + std::unique_ptr repeatProbAttach; + std::unique_ptr repeatInstantAttach; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SupaTriggaEditor) +}; diff --git a/supatrigga/Source/Theme.h b/supatrigga/Source/Theme.h new file mode 100644 index 0000000..5a20f24 --- /dev/null +++ b/supatrigga/Source/Theme.h @@ -0,0 +1,34 @@ +#pragma once +#include + +namespace Theme { + namespace Colors { + inline const juce::Colour background { 0xff16161e }; + inline const juce::Colour track { 0xff2a2a3a }; + inline const juce::Colour innerCircle { 0xff252535 }; + inline const juce::Colour border { 0xff2e2e40 }; + + // Text colors + inline const juce::Colour textPrimary { 0xffe8e8f0 }; + inline const juce::Colour textSecondary { 0xff8888a0 }; + inline const juce::Colour textSecondaryDim{ 0xcc8888a0 }; + inline const juce::Colour textValue { 0xffffffff }; + + // Accent colors + inline const juce::Colour globalAccent { 0xfff0c040 }; + inline const juce::Colour speedAccent { 0xffe05050 }; + inline const juce::Colour reverseAccent { 0xff40c8c8 }; + inline const juce::Colour repeatAccent { 0xffa060e0 }; + } // namespace Colors + + namespace Metrics { + constexpr int windowWidth = 640; + constexpr int windowHeight = 400; + constexpr int headerHeight = 56; + constexpr float knobRadius = 34.0f; + constexpr float innerRadius = 24.0f; + constexpr float trackThickness = 6.0f; + constexpr float rotaryStartAngle = juce::MathConstants::pi * 1.25f; + constexpr float rotaryEndAngle = juce::MathConstants::pi * 2.75f; + } +} \ No newline at end of file From 564a78606763f5c67b5be5e2c4718ef3449085ac Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:51:43 +0200 Subject: [PATCH 2/9] fix: remove duplicate CMAKE_OSX_ARCHITECTURES line from CommonSettings Co-Authored-By: Claude Opus 4.6 (1M context) --- cmake/CommonSettings.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/CommonSettings.cmake b/cmake/CommonSettings.cmake index a1841c1..82620b0 100644 --- a/cmake/CommonSettings.cmake +++ b/cmake/CommonSettings.cmake @@ -3,7 +3,6 @@ # macOS deployment target (must be before project()) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum macOS version") -set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures for Mac OS X") # Build universal binaries on macOS (arm64 + x86_64) set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Build architectures for Mac OS X") From 8b7de1e40c13c623087ed4bfc642f7ada064e3e2 Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:53:00 +0200 Subject: [PATCH 3/9] fix: revert PluginProcessor formatting, apply only functional editor changes Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/Source/PluginProcessor.cpp | 916 +++++++++++++------------- supatrigga/Source/PluginProcessor.h | 196 +++--- 2 files changed, 566 insertions(+), 546 deletions(-) diff --git a/supatrigga/Source/PluginProcessor.cpp b/supatrigga/Source/PluginProcessor.cpp index 0eb180b..7df7cac 100644 --- a/supatrigga/Source/PluginProcessor.cpp +++ b/supatrigga/Source/PluginProcessor.cpp @@ -3,116 +3,130 @@ #include SupaTriggaProcessor::SupaTriggaProcessor() - : AudioProcessor( - BusesProperties() - .withInput("Input", juce::AudioChannelSet::stereo(), true) - .withOutput("Output", juce::AudioChannelSet::stereo(), true)), - apvts(*this, nullptr, "Parameters", createParameterLayout()) { - std::srand(static_cast(std::time(nullptr))); - - // Allocate buffers - leftBuffer = std::make_unique(MAXSIZE); - rightBuffer = std::make_unique(MAXSIZE); - - for (size_t i = 0; i < MAXSIZE; i++) { - leftBuffer[i] = 0.0f; - rightBuffer[i] = 0.0f; - } - - // Initialize sequencer - for (int i = 0; i < MAXSLIDES; i++) { - sequencer[i].offset = 0; - sequencer[i].reverse = false; - sequencer[i].stop = false; - sequencer[i].silence = false; - } - - // Get parameter pointers - granularityParam = apvts.getRawParameterValue(GRANULARITY_ID); - speedParam = apvts.getRawParameterValue(SPEED_ID); - probReverseParam = apvts.getRawParameterValue(PROB_REVERSE_ID); - probSpeedParam = apvts.getRawParameterValue(PROB_SPEED_ID); - probRearrangeParam = apvts.getRawParameterValue(PROB_REARRANGE_ID); - probSilenceParam = apvts.getRawParameterValue(PROB_SILENCE_ID); - probRepeatParam = apvts.getRawParameterValue(PROB_REPEAT_ID); - instantReverseParam = apvts.getRawParameterValue(INSTANT_REVERSE_ID); - instantSpeedParam = apvts.getRawParameterValue(INSTANT_SPEED_ID); - instantRepeatParam = apvts.getRawParameterValue(INSTANT_REPEAT_ID); - - randomize(); + : AudioProcessor(BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true)), + apvts(*this, nullptr, "Parameters", createParameterLayout()) +{ + std::srand(static_cast(std::time(nullptr))); + + // Allocate buffers + leftBuffer = std::make_unique(MAXSIZE); + rightBuffer = std::make_unique(MAXSIZE); + + for (size_t i = 0; i < MAXSIZE; i++) + { + leftBuffer[i] = 0.0f; + rightBuffer[i] = 0.0f; + } + + // Initialize sequencer + for (int i = 0; i < MAXSLIDES; i++) + { + sequencer[i].offset = 0; + sequencer[i].reverse = false; + sequencer[i].stop = false; + sequencer[i].silence = false; + } + + // Get parameter pointers + granularityParam = apvts.getRawParameterValue(GRANULARITY_ID); + speedParam = apvts.getRawParameterValue(SPEED_ID); + probReverseParam = apvts.getRawParameterValue(PROB_REVERSE_ID); + probSpeedParam = apvts.getRawParameterValue(PROB_SPEED_ID); + probRearrangeParam = apvts.getRawParameterValue(PROB_REARRANGE_ID); + probSilenceParam = apvts.getRawParameterValue(PROB_SILENCE_ID); + probRepeatParam = apvts.getRawParameterValue(PROB_REPEAT_ID); + instantReverseParam = apvts.getRawParameterValue(INSTANT_REVERSE_ID); + instantSpeedParam = apvts.getRawParameterValue(INSTANT_SPEED_ID); + instantRepeatParam = apvts.getRawParameterValue(INSTANT_REPEAT_ID); + + randomize(); +} + +SupaTriggaProcessor::~SupaTriggaProcessor() +{ } -SupaTriggaProcessor::~SupaTriggaProcessor() {} - -juce::AudioProcessorValueTreeState::ParameterLayout -SupaTriggaProcessor::createParameterLayout() { - std::vector> params; - - // Granularity: displays as number of slices (1, 2, 4, 8, 16, 32, 64, 128) - params.push_back(std::make_unique( - juce::ParameterID(GRANULARITY_ID, 1), "Slices", - juce::NormalisableRange(0.0f, 1.0f, 0.001f), 0.3f, juce::String(), - juce::AudioProcessorParameter::genericParameter, - [](float value, int) { - int slices = 1 << static_cast(value * (BITSLIDES + 0.5f)); - return juce::String(slices); - }, - nullptr)); - - // Speed: displays as speed multiplier - params.push_back(std::make_unique( - juce::ParameterID(SPEED_ID, 1), "Slow Speed", - juce::NormalisableRange(0.0f, 1.0f, 0.001f), 0.25f, juce::String(), - juce::AudioProcessorParameter::genericParameter, - [](float value, int) { - float speedVal = (1.0f - value + 0.01f) * 4.0f; - return juce::String(speedVal, 2) + "x"; - }, - nullptr)); - - // Probability parameters: display as percentage - auto probToString = [](float value, int) { - return juce::String(static_cast(value * 100.0f)) + "%"; - }; - - params.push_back(std::make_unique( - juce::ParameterID(PROB_REVERSE_ID, 1), "Reverse Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.15f, juce::String(), - juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_SPEED_ID, 1), "Slow Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.05f, juce::String(), - juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_REARRANGE_ID, 1), "Rearrange Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.95f, juce::String(), - juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_SILENCE_ID, 1), "Silence Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.0f, juce::String(), - juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); - - params.push_back(std::make_unique( - juce::ParameterID(PROB_REPEAT_ID, 1), "Repeat Prob", - juce::NormalisableRange(0.0f, 1.0f, 0.01f), 0.4f, juce::String(), - juce::AudioProcessorParameter::genericParameter, probToString, nullptr)); - - // Instant toggles - params.push_back(std::make_unique( - juce::ParameterID(INSTANT_REVERSE_ID, 1), "Instant Reverse", false)); - params.push_back(std::make_unique( - juce::ParameterID(INSTANT_SPEED_ID, 1), "Instant Slow", false)); - params.push_back(std::make_unique( - juce::ParameterID(INSTANT_REPEAT_ID, 1), "Instant Repeat", false)); - - return {params.begin(), params.end()}; +juce::AudioProcessorValueTreeState::ParameterLayout SupaTriggaProcessor::createParameterLayout() +{ + std::vector> params; + + // Granularity: displays as number of slices (1, 2, 4, 8, 16, 32, 64, 128) + params.push_back(std::make_unique( + juce::ParameterID(GRANULARITY_ID, 1), "Slices", + juce::NormalisableRange(0.0f, 1.0f, 0.001f), + 0.3f, + juce::String(), + juce::AudioProcessorParameter::genericParameter, + [](float value, int) { + int slices = 1 << static_cast(value * (BITSLIDES + 0.5f)); + return juce::String(slices) + " slices/measure"; + }, + nullptr)); + + // Speed: displays as speed multiplier + params.push_back(std::make_unique( + juce::ParameterID(SPEED_ID, 1), "Slow Speed", + juce::NormalisableRange(0.0f, 1.0f, 0.001f), + 0.25f, + juce::String(), + juce::AudioProcessorParameter::genericParameter, + [](float value, int) { + float speedVal = (1.0f - value + 0.01f) * 4.0f; + return juce::String(speedVal, 2) + "x"; + }, + nullptr)); + + // Probability parameters: display as percentage + auto probToString = [](float value, int) { + return juce::String(static_cast(value * 100.0f)) + "%"; + }; + + params.push_back(std::make_unique( + juce::ParameterID(PROB_REVERSE_ID, 1), "Reverse Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), + 0.15f, juce::String(), juce::AudioProcessorParameter::genericParameter, + probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_SPEED_ID, 1), "Slow Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), + 0.05f, juce::String(), juce::AudioProcessorParameter::genericParameter, + probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_REARRANGE_ID, 1), "Rearrange Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), + 0.95f, juce::String(), juce::AudioProcessorParameter::genericParameter, + probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_SILENCE_ID, 1), "Silence Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), + 0.0f, juce::String(), juce::AudioProcessorParameter::genericParameter, + probToString, nullptr)); + + params.push_back(std::make_unique( + juce::ParameterID(PROB_REPEAT_ID, 1), "Repeat Prob", + juce::NormalisableRange(0.0f, 1.0f, 0.01f), + 0.4f, juce::String(), juce::AudioProcessorParameter::genericParameter, + probToString, nullptr)); + + // Instant toggles + params.push_back(std::make_unique( + juce::ParameterID(INSTANT_REVERSE_ID, 1), "Instant Reverse", false)); + params.push_back(std::make_unique( + juce::ParameterID(INSTANT_SPEED_ID, 1), "Instant Slow", false)); + params.push_back(std::make_unique( + juce::ParameterID(INSTANT_REPEAT_ID, 1), "Instant Repeat", false)); + + return { params.begin(), params.end() }; } -const juce::String SupaTriggaProcessor::getName() const { - return JucePlugin_Name; +const juce::String SupaTriggaProcessor::getName() const +{ + return JucePlugin_Name; } bool SupaTriggaProcessor::acceptsMidi() const { return false; } @@ -123,396 +137,400 @@ double SupaTriggaProcessor::getTailLengthSeconds() const { return 0.0; } int SupaTriggaProcessor::getNumPrograms() { return 1; } int SupaTriggaProcessor::getCurrentProgram() { return 0; } void SupaTriggaProcessor::setCurrentProgram(int /*index*/) {} -const juce::String SupaTriggaProcessor::getProgramName(int /*index*/) { - return {}; -} -void SupaTriggaProcessor::changeProgramName(int /*index*/, - const juce::String & /*newName*/) {} - -void SupaTriggaProcessor::prepareToPlay(double sampleRate, - int /*samplesPerBlock*/) { - currentSampleRate = static_cast(sampleRate); - fadeCoeff = std::exp(std::log(0.01f) / FADETIME); - - // Reset state - positionInMeasure = 0; - previousSliceIndex = 0xffffffff; - granularityMask = 0; - granularity = 0; - gain = 0.0f; - speed = 0.0f; - first = true; - wasPlaying = false; - - // Clear buffers - for (size_t i = 0; i < MAXSIZE; i++) { - leftBuffer[i] = 0.0f; - rightBuffer[i] = 0.0f; - } - - randomize(); +const juce::String SupaTriggaProcessor::getProgramName(int /*index*/) { return {}; } +void SupaTriggaProcessor::changeProgramName(int /*index*/, const juce::String& /*newName*/) {} + +void SupaTriggaProcessor::prepareToPlay(double sampleRate, int /*samplesPerBlock*/) +{ + currentSampleRate = static_cast(sampleRate); + fadeCoeff = std::exp(std::log(0.01f) / FADETIME); + + // Reset state + positionInMeasure = 0; + previousSliceIndex = 0xffffffff; + granularityMask = 0; + granularity = 0; + gain = 0.0f; + speed = 0.0f; + first = true; + wasPlaying = false; + + // Clear buffers + for (size_t i = 0; i < MAXSIZE; i++) + { + leftBuffer[i] = 0.0f; + rightBuffer[i] = 0.0f; + } + + randomize(); } -void SupaTriggaProcessor::releaseResources() {} +void SupaTriggaProcessor::releaseResources() +{ +} -bool SupaTriggaProcessor::isBusesLayoutSupported( - const BusesLayout &layouts) const { - if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) - return false; - if (layouts.getMainInputChannelSet() != juce::AudioChannelSet::stereo()) - return false; - return true; +bool SupaTriggaProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const +{ + if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + if (layouts.getMainInputChannelSet() != juce::AudioChannelSet::stereo()) + return false; + return true; } -void SupaTriggaProcessor::processBlock(juce::AudioBuffer &buffer, - juce::MidiBuffer & /*midiMessages*/) { - juce::ScopedNoDenormals noDenormals; - - auto *in1 = buffer.getReadPointer(0); - auto *in2 = buffer.getReadPointer(1); - auto *out1 = buffer.getWritePointer(0); - auto *out2 = buffer.getWritePointer(1); - const int sampleFrames = buffer.getNumSamples(); - - // Get transport info from host - auto *playHead = getPlayHead(); - if (playHead == nullptr) { - // No playhead, pass through - for (int i = 0; i < sampleFrames; i++) { - out1[i] = in1[i]; - out2[i] = in2[i]; - } - return; - } - - auto posInfo = playHead->getPosition(); - if (!posInfo.hasValue()) { - // No position info, pass through - for (int i = 0; i < sampleFrames; i++) { - out1[i] = in1[i]; - out2[i] = in2[i]; - } - return; - } - - // Check if we have all required info - bool isPlaying = posInfo->getIsPlaying(); - auto bpmOpt = posInfo->getBpm(); - auto timeSigOpt = posInfo->getTimeSignature(); - auto ppqPosOpt = posInfo->getPpqPosition(); - auto barPosOpt = posInfo->getPpqPositionOfLastBarStart(); - - if (!isPlaying || !bpmOpt.hasValue() || !timeSigOpt.hasValue() || - !ppqPosOpt.hasValue()) { - positionInMeasure = 0xffffffff; - for (int i = 0; i < sampleFrames; i++) { - out1[i] = in1[i]; - out2[i] = in2[i]; +void SupaTriggaProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& /*midiMessages*/) +{ + juce::ScopedNoDenormals noDenormals; + + auto* in1 = buffer.getReadPointer(0); + auto* in2 = buffer.getReadPointer(1); + auto* out1 = buffer.getWritePointer(0); + auto* out2 = buffer.getWritePointer(1); + const int sampleFrames = buffer.getNumSamples(); + + // Get transport info from host + auto* playHead = getPlayHead(); + if (playHead == nullptr) + { + // No playhead, pass through + for (int i = 0; i < sampleFrames; i++) + { + out1[i] = in1[i]; + out2[i] = in2[i]; + } + return; } - wasPlaying = isPlaying; - return; - } - - double tempo = *bpmOpt; - if (tempo < 20.0) { - // Ridiculous tempo - for (int i = 0; i < sampleFrames; i++) { - out1[i] = in1[i]; - out2[i] = in2[i]; + + auto posInfo = playHead->getPosition(); + if (!posInfo.hasValue()) + { + // No position info, pass through + for (int i = 0; i < sampleFrames; i++) + { + out1[i] = in1[i]; + out2[i] = in2[i]; + } + return; } - return; - } - - auto timeSig = *timeSigOpt; - double ppqPos = *ppqPosOpt; - double barStartPos = barPosOpt.hasValue() ? *barPosOpt : ppqPos; - - // Calculate samples in measure - double tempoBPS = tempo / 60.0; - double numSamplesInBeat = currentSampleRate / tempoBPS; - double samplesInMeasureDouble = std::ceil( - numSamplesInBeat * timeSig.numerator / (timeSig.denominator / 4.0)); - unsigned long samplesInMeasure = - static_cast(samplesInMeasureDouble); - - // Detect transport changes - bool playbackChanged = (isPlaying != wasPlaying); - wasPlaying = isPlaying; - - // Calculate distance to next bar - double beatsPerBar = static_cast(timeSig.numerator); - double distanceToNextBarPPQ; - if (std::fabs(barStartPos - ppqPos) < 1e-10) - distanceToNextBarPPQ = 0.0; - else - distanceToNextBarPPQ = barStartPos + beatsPerBar - ppqPos; - - while (distanceToNextBarPPQ < 0.0) - distanceToNextBarPPQ += beatsPerBar; - while (distanceToNextBarPPQ >= beatsPerBar) - distanceToNextBarPPQ -= beatsPerBar; - - double numSamplesToNextBar = - (distanceToNextBarPPQ * currentSampleRate * 60.0) / tempo; - if (numSamplesToNextBar < 0.0) - numSamplesToNextBar = 0.0; - - unsigned long samplesToNextBar = - static_cast(std::ceil(numSamplesToNextBar)); - - // If playback changed, recalculate position - if (playbackChanged) { - positionInMeasure = static_cast( - std::floor(samplesInMeasureDouble - numSamplesToNextBar)); - if (positionInMeasure >= samplesInMeasure) - positionInMeasure = samplesInMeasure - 1; - } - - // Get parameter values - float granularityRaw = granularityParam->load(); - float speedRaw = speedParam->load(); - - unsigned long granularityTmp = - static_cast(granularityRaw * (BITSLIDES + 0.5f)); - unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); - - float speedDiff = std::exp(std::log(0.01f) / (((1.0f - speedRaw) + 0.01f) * - currentSampleRate * 4.0f)); - - for (int i = 0; i < sampleFrames; i++) { - // Reset position at bar border - if (static_cast(i) == samplesToNextBar) - positionInMeasure = 0; - - // Store input in buffer - if (positionInMeasure < MAXSIZE) { - leftBuffer[positionInMeasure] = in1[i]; - rightBuffer[positionInMeasure] = in2[i]; + + // Check if we have all required info + bool isPlaying = posInfo->getIsPlaying(); + auto bpmOpt = posInfo->getBpm(); + auto timeSigOpt = posInfo->getTimeSignature(); + auto ppqPosOpt = posInfo->getPpqPosition(); + auto barPosOpt = posInfo->getPpqPositionOfLastBarStart(); + + if (!isPlaying || !bpmOpt.hasValue() || !timeSigOpt.hasValue() || !ppqPosOpt.hasValue()) + { + positionInMeasure = 0xffffffff; + for (int i = 0; i < sampleFrames; i++) + { + out1[i] = in1[i]; + out2[i] = in2[i]; + } + wasPlaying = isPlaying; + return; } - unsigned long sliceIndex = - ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & granularityMask; - - if (instantRepeat && sliceIndex != 0) - sequencer[sliceIndex].offset = - (sequencer[(sliceIndex - 1) & granularityMaskTmp].offset + - (sliceIndex - ((sliceIndex - 1) & granularityMaskTmp))) & - granularityMask; - - unsigned long displacement = sequencer[sliceIndex].offset & granularityMask; - - if (granularityMaskTmp != granularityMask) { - unsigned long sliceIndexTmp = - ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & - granularityMaskTmp; - - if ((granularityTmp < granularity && sliceIndex == 0) || - (granularityTmp > granularity && sliceIndexTmp == 0) || - (sliceIndex == 0 && sliceIndexTmp == 0)) { - granularityMask = granularityMaskTmp; - granularity = granularityTmp; - sliceIndex = sliceIndexTmp; - displacement = sequencer[sliceIndex].offset & granularityMask; - } + double tempo = *bpmOpt; + if (tempo < 20.0) + { + // Ridiculous tempo + for (int i = 0; i < sampleFrames; i++) + { + out1[i] = in1[i]; + out2[i] = in2[i]; + } + return; } - if (sliceIndex != previousSliceIndex) { - instantRepeat = instantRepeatParam->load() > 0.5f; - instantReverse = instantReverseParam->load() > 0.5f; - instantSlow = instantSpeedParam->load() > 0.5f; - previousSliceIndex = sliceIndex; + auto timeSig = *timeSigOpt; + double ppqPos = *ppqPosOpt; + double barStartPos = barPosOpt.hasValue() ? *barPosOpt : ppqPos; + + // Calculate samples in measure + double tempoBPS = tempo / 60.0; + double numSamplesInBeat = currentSampleRate / tempoBPS; + double samplesInMeasureDouble = std::ceil(numSamplesInBeat * timeSig.numerator / (timeSig.denominator / 4.0)); + unsigned long samplesInMeasure = static_cast(samplesInMeasureDouble); + + // Detect transport changes + bool playbackChanged = (isPlaying != wasPlaying); + wasPlaying = isPlaying; + + // Calculate distance to next bar + double beatsPerBar = static_cast(timeSig.numerator); + double distanceToNextBarPPQ; + if (std::fabs(barStartPos - ppqPos) < 1e-10) + distanceToNextBarPPQ = 0.0; + else + distanceToNextBarPPQ = barStartPos + beatsPerBar - ppqPos; + + while (distanceToNextBarPPQ < 0.0) + distanceToNextBarPPQ += beatsPerBar; + while (distanceToNextBarPPQ >= beatsPerBar) + distanceToNextBarPPQ -= beatsPerBar; + + double numSamplesToNextBar = (distanceToNextBarPPQ * currentSampleRate * 60.0) / tempo; + if (numSamplesToNextBar < 0.0) + numSamplesToNextBar = 0.0; + + unsigned long samplesToNextBar = static_cast(std::ceil(numSamplesToNextBar)); + + // If playback changed, recalculate position + if (playbackChanged) + { + positionInMeasure = static_cast(std::floor(samplesInMeasureDouble - numSamplesToNextBar)); + if (positionInMeasure >= samplesInMeasure) + positionInMeasure = samplesInMeasure - 1; } - // Gain calculation + // Get parameter values + float granularityRaw = granularityParam->load(); + float speedRaw = speedParam->load(); + + unsigned long granularityTmp = static_cast(granularityRaw * (BITSLIDES + 0.5f)); + unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); + + float speedDiff = std::exp(std::log(0.01f) / (((1.0f - speedRaw) + 0.01f) * currentSampleRate * 4.0f)); + + for (int i = 0; i < sampleFrames; i++) { - unsigned long sliceIndexFar = - ((((positionInMeasure + FADETIME) % samplesInMeasure) * MAXSLIDES) / - samplesInMeasure) & - granularityMask; - unsigned long displacementFar = - sequencer[sliceIndexFar].offset & granularityMask; - bool reverseFar = sequencer[sliceIndexFar].reverse; + // Reset position at bar border + if (static_cast(i) == samplesToNextBar) + positionInMeasure = 0; + + // Store input in buffer + if (positionInMeasure < MAXSIZE) + { + leftBuffer[positionInMeasure] = in1[i]; + rightBuffer[positionInMeasure] = in2[i]; + } - float targetGain = 1.0f; + unsigned long sliceIndex = ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & granularityMask; - if (sequencer[sliceIndex].silence) - targetGain = 0.0f; + if (instantRepeat && sliceIndex != 0) + sequencer[sliceIndex].offset = (sequencer[(sliceIndex - 1) & granularityMaskTmp].offset + + (sliceIndex - ((sliceIndex - 1) & granularityMaskTmp))) & granularityMask; - if (displacementFar != displacement || - positionInMeasure + FADETIME > samplesInMeasure || - reverseFar != sequencer[sliceIndex].reverse) - targetGain = 0.0f; + unsigned long displacement = sequencer[sliceIndex].offset & granularityMask; - gain = fadeCoeff * gain + (1.0f - fadeCoeff) * targetGain; - } + if (granularityMaskTmp != granularityMask) + { + unsigned long sliceIndexTmp = ((positionInMeasure * MAXSLIDES) / samplesInMeasure) & granularityMaskTmp; - if ((sequencer[sliceIndex].reverse || instantReverse) && - displacement != 0) { - if (!(sequencer[sliceIndex].stop || instantSlow)) { - unsigned long sliceSize = samplesInMeasure >> granularity; - unsigned long sliceStart = (positionInMeasure / sliceSize) * sliceSize; - unsigned long sliceDiff = positionInMeasure - sliceStart; - unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; - unsigned long difference = - (displacement * samplesInMeasure) / MAXSLIDES; - unsigned long bufferIndex = - difference <= sliceEnd ? sliceEnd - difference : 0; - - if (bufferIndex < MAXSIZE) { - out1[i] = leftBuffer[bufferIndex] * gain; - out2[i] = rightBuffer[bufferIndex] * gain; + if ((granularityTmp < granularity && sliceIndex == 0) || + (granularityTmp > granularity && sliceIndexTmp == 0) || + (sliceIndex == 0 && sliceIndexTmp == 0)) + { + granularityMask = granularityMaskTmp; + granularity = granularityTmp; + sliceIndex = sliceIndexTmp; + displacement = sequencer[sliceIndex].offset & granularityMask; + } } - first = true; - } else { - if (first) { - unsigned long sliceSize = samplesInMeasure >> granularity; - unsigned long sliceStart = - (positionInMeasure / sliceSize) * sliceSize; - unsigned long sliceDiff = positionInMeasure - sliceStart; - unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; - unsigned long difference = - (displacement * samplesInMeasure) / MAXSLIDES; - - position = static_cast( - difference <= sliceEnd ? sliceEnd - difference : 0); - speed = 1.0f / speedDiff; - first = false; + + if (sliceIndex != previousSliceIndex) + { + instantRepeat = instantRepeatParam->load() > 0.5f; + instantReverse = instantReverseParam->load() > 0.5f; + instantSlow = instantSpeedParam->load() > 0.5f; + previousSliceIndex = sliceIndex; } - speed *= speedDiff; - position -= speed; + // Gain calculation + { + unsigned long sliceIndexFar = ((((positionInMeasure + FADETIME) % samplesInMeasure) * MAXSLIDES) / samplesInMeasure) & granularityMask; + unsigned long displacementFar = sequencer[sliceIndexFar].offset & granularityMask; + bool reverseFar = sequencer[sliceIndexFar].reverse; - if (position < 0.0f) - position = 0.0f; + float targetGain = 1.0f; - unsigned long bufferIndex = - static_cast(std::floor(position)); - float alpha = position - bufferIndex; + if (sequencer[sliceIndex].silence) + targetGain = 0.0f; - if (bufferIndex < MAXSIZE - 3) { - out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; - out2[i] = - hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; + if (displacementFar != displacement || + positionInMeasure + FADETIME > samplesInMeasure || + reverseFar != sequencer[sliceIndex].reverse) + targetGain = 0.0f; + + gain = fadeCoeff * gain + (1.0f - fadeCoeff) * targetGain; } - } - } else { - if (!(sequencer[sliceIndex].stop || instantSlow) || displacement == 0) { - unsigned long difference = - (displacement * samplesInMeasure) / MAXSLIDES; - unsigned long bufferIndex = - positionInMeasure > difference ? positionInMeasure - difference : 0; - - if (bufferIndex < MAXSIZE) { - out1[i] = leftBuffer[bufferIndex] * gain; - out2[i] = rightBuffer[bufferIndex] * gain; + + if ((sequencer[sliceIndex].reverse || instantReverse) && displacement != 0) + { + if (!(sequencer[sliceIndex].stop || instantSlow)) + { + unsigned long sliceSize = samplesInMeasure >> granularity; + unsigned long sliceStart = (positionInMeasure / sliceSize) * sliceSize; + unsigned long sliceDiff = positionInMeasure - sliceStart; + unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; + unsigned long difference = (displacement * samplesInMeasure) / MAXSLIDES; + unsigned long bufferIndex = difference <= sliceEnd ? sliceEnd - difference : 0; + + if (bufferIndex < MAXSIZE) + { + out1[i] = leftBuffer[bufferIndex] * gain; + out2[i] = rightBuffer[bufferIndex] * gain; + } + first = true; + } + else + { + if (first) + { + unsigned long sliceSize = samplesInMeasure >> granularity; + unsigned long sliceStart = (positionInMeasure / sliceSize) * sliceSize; + unsigned long sliceDiff = positionInMeasure - sliceStart; + unsigned long sliceEnd = (sliceStart + sliceSize) - sliceDiff; + unsigned long difference = (displacement * samplesInMeasure) / MAXSLIDES; + + position = static_cast(difference <= sliceEnd ? sliceEnd - difference : 0); + speed = 1.0f / speedDiff; + first = false; + } + + speed *= speedDiff; + position -= speed; + + if (position < 0.0f) + position = 0.0f; + + unsigned long bufferIndex = static_cast(std::floor(position)); + float alpha = position - bufferIndex; + + if (bufferIndex < MAXSIZE - 3) + { + out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; + out2[i] = hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; + } + } } - first = true; - } else { - if (first) { - position = - static_cast(positionInMeasure - - (displacement * samplesInMeasure) / MAXSLIDES); - speed = 1.0f / speedDiff; - first = false; + else + { + if (!(sequencer[sliceIndex].stop || instantSlow) || displacement == 0) + { + unsigned long difference = (displacement * samplesInMeasure) / MAXSLIDES; + unsigned long bufferIndex = positionInMeasure > difference ? positionInMeasure - difference : 0; + + if (bufferIndex < MAXSIZE) + { + out1[i] = leftBuffer[bufferIndex] * gain; + out2[i] = rightBuffer[bufferIndex] * gain; + } + first = true; + } + else + { + if (first) + { + position = static_cast(positionInMeasure - (displacement * samplesInMeasure) / MAXSLIDES); + speed = 1.0f / speedDiff; + first = false; + } + + speed *= speedDiff; + position += speed; + + unsigned long bufferIndex = static_cast(std::floor(position)); + float alpha = position - bufferIndex; + + if (bufferIndex < MAXSIZE - 3) + { + out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; + out2[i] = hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; + } + } } - speed *= speedDiff; - position += speed; - - unsigned long bufferIndex = - static_cast(std::floor(position)); - float alpha = position - bufferIndex; + positionInMeasure++; - if (bufferIndex < MAXSIZE - 3) { - out1[i] = hermiteInverse(leftBuffer.get(), bufferIndex, alpha) * gain; - out2[i] = - hermiteInverse(rightBuffer.get(), bufferIndex, alpha) * gain; + if (positionInMeasure >= samplesInMeasure) + { + randomize(); + positionInMeasure = 0; } - } - } - - positionInMeasure++; - if (positionInMeasure >= samplesInMeasure) { - randomize(); - positionInMeasure = 0; + if (std::fabs(gain) < 1e-10f) + gain = 0.0f; } - - if (std::fabs(gain) < 1e-10f) - gain = 0.0f; - } } -void SupaTriggaProcessor::randomize() { - float granularityRaw = granularityParam->load(); - float probRearrangeRaw = probRearrangeParam->load(); - float probRepeatRaw = probRepeatParam->load(); - float probReverseRaw = probReverseParam->load(); - float probSpeedRaw = probSpeedParam->load(); - float probSilenceRaw = probSilenceParam->load(); - - unsigned long granularityTmp = - static_cast(granularityRaw * (BITSLIDES + 0.5f)); - unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); - - for (unsigned long i = 0; i < MAXSLIDES; i++) { - if ((std::rand() % 100) < static_cast(probRearrangeRaw * 101.0f)) { - if ((std::rand() % 100) < static_cast(probRepeatRaw * 101.0f)) { - if (i != 0) - sequencer[i].offset = - (sequencer[(i - 1) & granularityMaskTmp].offset + - (i - ((i - 1) & granularityMaskTmp))) & - granularityMask; +void SupaTriggaProcessor::randomize() +{ + float granularityRaw = granularityParam->load(); + float probRearrangeRaw = probRearrangeParam->load(); + float probRepeatRaw = probRepeatParam->load(); + float probReverseRaw = probReverseParam->load(); + float probSpeedRaw = probSpeedParam->load(); + float probSilenceRaw = probSilenceParam->load(); + + unsigned long granularityTmp = static_cast(granularityRaw * (BITSLIDES + 0.5f)); + unsigned long granularityMaskTmp = 0xffffffff << (BITSLIDES - granularityTmp); + + for (unsigned long i = 0; i < MAXSLIDES; i++) + { + if ((std::rand() % 100) < static_cast(probRearrangeRaw * 101.0f)) + { + if ((std::rand() % 100) < static_cast(probRepeatRaw * 101.0f)) + { + if (i != 0) + sequencer[i].offset = (sequencer[(i - 1) & granularityMaskTmp].offset + + (i - ((i - 1) & granularityMaskTmp))) & granularityMask; + else + sequencer[i].offset = 0; + } + else + { + sequencer[i].offset = (i * static_cast(std::rand() % MAXSLIDES)) / MAXSLIDES; + } + } else - sequencer[i].offset = 0; - } else { - sequencer[i].offset = - (i * static_cast(std::rand() % MAXSLIDES)) / - MAXSLIDES; - } - } else { - sequencer[i].offset = 0; - } + { + sequencer[i].offset = 0; + } - if ((std::rand() % 100) < static_cast(probReverseRaw * 100.0f) && - sequencer[i].offset > 0) - sequencer[i].reverse = true; - else - sequencer[i].reverse = false; + if ((std::rand() % 100) < static_cast(probReverseRaw * 100.0f) && sequencer[i].offset > 0) + sequencer[i].reverse = true; + else + sequencer[i].reverse = false; - if ((std::rand() % 100) < static_cast(probSpeedRaw * 101.0f)) - sequencer[i].stop = true; - else - sequencer[i].stop = false; + if ((std::rand() % 100) < static_cast(probSpeedRaw * 101.0f)) + sequencer[i].stop = true; + else + sequencer[i].stop = false; - if ((std::rand() % 100) < static_cast(probSilenceRaw * 101.0f)) - sequencer[i].silence = true; - else - sequencer[i].silence = false; - } + if ((std::rand() % 100) < static_cast(probSilenceRaw * 101.0f)) + sequencer[i].silence = true; + else + sequencer[i].silence = false; + } } bool SupaTriggaProcessor::hasEditor() const { return true; } -juce::AudioProcessorEditor *SupaTriggaProcessor::createEditor() { - return new SupaTriggaEditor(*this, apvts); +juce::AudioProcessorEditor* SupaTriggaProcessor::createEditor() +{ + return new SupaTriggaEditor(*this, apvts); } -void SupaTriggaProcessor::getStateInformation(juce::MemoryBlock &destData) { - auto state = apvts.copyState(); - std::unique_ptr xml(state.createXml()); - copyXmlToBinary(*xml, destData); +void SupaTriggaProcessor::getStateInformation(juce::MemoryBlock& destData) +{ + auto state = apvts.copyState(); + std::unique_ptr xml(state.createXml()); + copyXmlToBinary(*xml, destData); } -void SupaTriggaProcessor::setStateInformation(const void *data, - int sizeInBytes) { - std::unique_ptr xmlState( - getXmlFromBinary(data, sizeInBytes)); - if (xmlState != nullptr && xmlState->hasTagName(apvts.state.getType())) { - apvts.replaceState(juce::ValueTree::fromXml(*xmlState)); - } +void SupaTriggaProcessor::setStateInformation(const void* data, int sizeInBytes) +{ + std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); + if (xmlState != nullptr && xmlState->hasTagName(apvts.state.getType())) + { + apvts.replaceState(juce::ValueTree::fromXml(*xmlState)); + } } -juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { - return new SupaTriggaProcessor(); +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new SupaTriggaProcessor(); } diff --git a/supatrigga/Source/PluginProcessor.h b/supatrigga/Source/PluginProcessor.h index 1fa9a5a..7ef3b71 100644 --- a/supatrigga/Source/PluginProcessor.h +++ b/supatrigga/Source/PluginProcessor.h @@ -1,10 +1,10 @@ #pragma once +#include #include #include -#include -class SupaTriggaEditor; // Forward declaration +class SupaTriggaEditor; // Constants from original constexpr int NUMBERIO = 2; @@ -14,119 +14,121 @@ constexpr int MAXSLIDES = 1 << BITSLIDES; constexpr int FADETIME = 150; // Glitch parameters for each slice -struct GlitchParams { - unsigned long offset = 0; - bool reverse = false; - bool stop = false; - bool silence = false; +struct GlitchParams +{ + unsigned long offset = 0; + bool reverse = false; + bool stop = false; + bool silence = false; }; // Hermite interpolation (inverse direction) -inline float hermiteInverse(float *wavetable, unsigned long nearest_sample, - float x) { - float y3 = (nearest_sample == 0) ? 0.f : wavetable[nearest_sample - 1]; - float y2 = wavetable[nearest_sample]; - float y1 = wavetable[nearest_sample + 1]; - float y0 = wavetable[nearest_sample + 2]; +inline float hermiteInverse(float* wavetable, unsigned long nearest_sample, float x) +{ + float y3 = (nearest_sample == 0) ? 0.f : wavetable[nearest_sample - 1]; + float y2 = wavetable[nearest_sample]; + float y1 = wavetable[nearest_sample + 1]; + float y0 = wavetable[nearest_sample + 2]; - x = 1.f - x; + x = 1.f - x; - float c0 = y1; - float c1 = 0.5f * (y2 - y0); - float c2 = y0 - 2.5f * y1 + 2.f * y2 - 0.5f * y3; - float c3 = 1.5f * (y1 - y2) + 0.5f * (y3 - y0); + float c0 = y1; + float c1 = 0.5f * (y2 - y0); + float c2 = y0 - 2.5f * y1 + 2.f * y2 - 0.5f * y3; + float c3 = 1.5f * (y1 - y2) + 0.5f * (y3 - y0); - return ((c3 * x + c2) * x + c1) * x + c0; + return ((c3 * x + c2) * x + c1) * x + c0; } -class SupaTriggaProcessor : public juce::AudioProcessor { +class SupaTriggaProcessor : public juce::AudioProcessor +{ public: - SupaTriggaProcessor(); - ~SupaTriggaProcessor() override; + SupaTriggaProcessor(); + ~SupaTriggaProcessor() override; - void prepareToPlay(double sampleRate, int samplesPerBlock) override; - void releaseResources() override; + void prepareToPlay(double sampleRate, int samplesPerBlock) override; + void releaseResources() override; - bool isBusesLayoutSupported(const BusesLayout &layouts) const override; + bool isBusesLayoutSupported(const BusesLayout& layouts) const override; - void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; + void processBlock(juce::AudioBuffer&, juce::MidiBuffer&) override; - juce::AudioProcessorEditor *createEditor() override; - bool hasEditor() const override; + juce::AudioProcessorEditor* createEditor() override; + bool hasEditor() const override; - const juce::String getName() const override; + const juce::String getName() const override; - bool acceptsMidi() const override; - bool producesMidi() const override; - bool isMidiEffect() const override; - double getTailLengthSeconds() const override; + bool acceptsMidi() const override; + bool producesMidi() const override; + bool isMidiEffect() const override; + double getTailLengthSeconds() const override; - int getNumPrograms() override; - int getCurrentProgram() override; - void setCurrentProgram(int index) override; - const juce::String getProgramName(int index) override; - void changeProgramName(int index, const juce::String &newName) override; + int getNumPrograms() override; + int getCurrentProgram() override; + void setCurrentProgram(int index) override; + const juce::String getProgramName(int index) override; + void changeProgramName(int index, const juce::String& newName) override; - void getStateInformation(juce::MemoryBlock &destData) override; - void setStateInformation(const void *data, int sizeInBytes) override; + void getStateInformation(juce::MemoryBlock& destData) override; + void setStateInformation(const void* data, int sizeInBytes) override; - // Parameter IDs - static constexpr const char *GRANULARITY_ID = "granularity"; - static constexpr const char *SPEED_ID = "speed"; - static constexpr const char *PROB_REVERSE_ID = "probReverse"; - static constexpr const char *PROB_SPEED_ID = "probSpeed"; - static constexpr const char *PROB_REARRANGE_ID = "probRearrange"; - static constexpr const char *PROB_SILENCE_ID = "probSilence"; - static constexpr const char *PROB_REPEAT_ID = "probRepeat"; - static constexpr const char *INSTANT_REVERSE_ID = "instantReverse"; - static constexpr const char *INSTANT_SPEED_ID = "instantSpeed"; - static constexpr const char *INSTANT_REPEAT_ID = "instantRepeat"; + // Parameter IDs + static constexpr const char* GRANULARITY_ID = "granularity"; + static constexpr const char* SPEED_ID = "speed"; + static constexpr const char* PROB_REVERSE_ID = "probReverse"; + static constexpr const char* PROB_SPEED_ID = "probSpeed"; + static constexpr const char* PROB_REARRANGE_ID = "probRearrange"; + static constexpr const char* PROB_SILENCE_ID = "probSilence"; + static constexpr const char* PROB_REPEAT_ID = "probRepeat"; + static constexpr const char* INSTANT_REVERSE_ID = "instantReverse"; + static constexpr const char* INSTANT_SPEED_ID = "instantSpeed"; + static constexpr const char* INSTANT_REPEAT_ID = "instantRepeat"; private: - juce::AudioProcessorValueTreeState apvts; - juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); - - void randomize(); - - // Buffers - std::unique_ptr leftBuffer; - std::unique_ptr rightBuffer; - - // Sequencer - GlitchParams sequencer[MAXSLIDES]; - - // State - unsigned long positionInMeasure = 0; - unsigned long previousSliceIndex = 0xffffffff; - unsigned long granularityMask = 0; - unsigned long granularity = 0; - - float gain = 0.0f; - float speed = 0.0f; - float position = 0.0f; - bool first = true; - - bool instantReverse = false; - bool instantSlow = false; - bool instantRepeat = false; - - float currentSampleRate = 44100.0f; - float fadeCoeff = 0.0f; - - // Last playback state for detecting transport changes - bool wasPlaying = false; - - // Parameter pointers - std::atomic *granularityParam = nullptr; - std::atomic *speedParam = nullptr; - std::atomic *probReverseParam = nullptr; - std::atomic *probSpeedParam = nullptr; - std::atomic *probRearrangeParam = nullptr; - std::atomic *probSilenceParam = nullptr; - std::atomic *probRepeatParam = nullptr; - std::atomic *instantReverseParam = nullptr; - std::atomic *instantSpeedParam = nullptr; - std::atomic *instantRepeatParam = nullptr; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SupaTriggaProcessor) + juce::AudioProcessorValueTreeState apvts; + juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); + + void randomize(); + + // Buffers + std::unique_ptr leftBuffer; + std::unique_ptr rightBuffer; + + // Sequencer + GlitchParams sequencer[MAXSLIDES]; + + // State + unsigned long positionInMeasure = 0; + unsigned long previousSliceIndex = 0xffffffff; + unsigned long granularityMask = 0; + unsigned long granularity = 0; + + float gain = 0.0f; + float speed = 0.0f; + float position = 0.0f; + bool first = true; + + bool instantReverse = false; + bool instantSlow = false; + bool instantRepeat = false; + + float currentSampleRate = 44100.0f; + float fadeCoeff = 0.0f; + + // Last playback state for detecting transport changes + bool wasPlaying = false; + + // Parameter pointers + std::atomic* granularityParam = nullptr; + std::atomic* speedParam = nullptr; + std::atomic* probReverseParam = nullptr; + std::atomic* probSpeedParam = nullptr; + std::atomic* probRearrangeParam = nullptr; + std::atomic* probSilenceParam = nullptr; + std::atomic* probRepeatParam = nullptr; + std::atomic* instantReverseParam = nullptr; + std::atomic* instantSpeedParam = nullptr; + std::atomic* instantRepeatParam = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SupaTriggaProcessor) }; From 8f4db2a103c90930118281ed99b03e6a61bb1640 Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:54:22 +0200 Subject: [PATCH 4/9] fix: add missing trailing newlines to new editor files Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/Source/SupaTriggaEditor.cpp | 2 +- supatrigga/Source/Theme.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/supatrigga/Source/SupaTriggaEditor.cpp b/supatrigga/Source/SupaTriggaEditor.cpp index 2eb22bf..8f5e473 100644 --- a/supatrigga/Source/SupaTriggaEditor.cpp +++ b/supatrigga/Source/SupaTriggaEditor.cpp @@ -323,4 +323,4 @@ void SupaTriggaEditor::resized() { fb.items.add(mapItem(repeatInstantToggle)); fb.performLayout(repeatRow); } -} \ No newline at end of file +} diff --git a/supatrigga/Source/Theme.h b/supatrigga/Source/Theme.h index 5a20f24..8317c02 100644 --- a/supatrigga/Source/Theme.h +++ b/supatrigga/Source/Theme.h @@ -31,4 +31,4 @@ namespace Theme { constexpr float rotaryStartAngle = juce::MathConstants::pi * 1.25f; constexpr float rotaryEndAngle = juce::MathConstants::pi * 2.75f; } -} \ No newline at end of file +} From 868c7092a5e64c74d4f83ff9c77f51256bb6d6b9 Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:54:39 +0200 Subject: [PATCH 5/9] fix: remove platform-specific Inter font, use system default on all platforms Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/Source/SupaTriggaEditor.cpp | 10 ---------- supatrigga/Source/SupaTriggaEditor.h | 2 -- 2 files changed, 12 deletions(-) diff --git a/supatrigga/Source/SupaTriggaEditor.cpp b/supatrigga/Source/SupaTriggaEditor.cpp index 8f5e473..7113869 100644 --- a/supatrigga/Source/SupaTriggaEditor.cpp +++ b/supatrigga/Source/SupaTriggaEditor.cpp @@ -7,10 +7,6 @@ SupaTriggaLookAndFeel::SupaTriggaLookAndFeel() { setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); setColour(juce::Slider::rotarySliderOutlineColourId, Theme::Colors::track); setColour(juce::Slider::textBoxTextColourId, Theme::Colors::textValue); - -#if JUCE_MAC - interFont = juce::Typeface::createSystemTypefaceFor(juce::FontOptions{}.withName("Inter").withHeight(12.0f).withStyle("Bold")); -#endif } void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics &g, int x, int y, @@ -54,9 +50,6 @@ void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics &g, int x, int y, g.setColour(slider.findColour(juce::Slider::textBoxTextColourId)); auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); - if (interFont != nullptr) { - font = juce::Font(juce::FontOptions(interFont).withHeight(12.0f)); - } g.setFont(font); juce::String text = slider.getTextFromValue(slider.getValue()); @@ -95,9 +88,6 @@ void SupaTriggaLookAndFeel::drawToggleButton( : Theme::Colors::textSecondary); auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); - if (interFont != nullptr) { - font = juce::Font(juce::FontOptions(interFont).withHeight(12.0f)); - } g.setFont(font); g.drawText(button.getName(), button.getLocalBounds(), juce::Justification::centred, false); diff --git a/supatrigga/Source/SupaTriggaEditor.h b/supatrigga/Source/SupaTriggaEditor.h index 37e5a37..d85a077 100644 --- a/supatrigga/Source/SupaTriggaEditor.h +++ b/supatrigga/Source/SupaTriggaEditor.h @@ -16,8 +16,6 @@ class SupaTriggaLookAndFeel : public juce::LookAndFeel_V4 void drawToggleButton(juce::Graphics& g, juce::ToggleButton& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override; -private: - juce::Typeface::Ptr interFont; }; class SupaTriggaEditor : public juce::AudioProcessorEditor From b9e3758ef818ace7cc1921ddd83aa1a21cffc7c1 Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:55:42 +0200 Subject: [PATCH 6/9] refactor: replace string-based style dispatch with Section enum Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/Source/SupaTriggaEditor.cpp | 59 ++++++++++++-------------- supatrigga/Source/SupaTriggaEditor.h | 6 ++- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/supatrigga/Source/SupaTriggaEditor.cpp b/supatrigga/Source/SupaTriggaEditor.cpp index 7113869..5b0c477 100644 --- a/supatrigga/Source/SupaTriggaEditor.cpp +++ b/supatrigga/Source/SupaTriggaEditor.cpp @@ -99,37 +99,37 @@ SupaTriggaEditor::SupaTriggaEditor(SupaTriggaProcessor &p, setLookAndFeel(&customLookAndFeel); // Global section - setupKnob(rearrangeKnob, "global"); + setupKnob(rearrangeKnob, Section::Global); rearrangeAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REARRANGE_ID, rearrangeKnob); - setupKnob(slicesKnob, "global"); + setupKnob(slicesKnob, Section::Global); slicesAttach = std::make_unique(apvts, SupaTriggaProcessor::GRANULARITY_ID, slicesKnob); - setupKnob(silenceKnob, "global"); + setupKnob(silenceKnob, Section::Global); silenceAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_SILENCE_ID, silenceKnob); // Brake section - setupKnob(brakeProbKnob, "speed"); + setupKnob(brakeProbKnob, Section::Speed); brakeProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_SPEED_ID, brakeProbKnob); - setupKnob(brakeTimeKnob, "speed"); + setupKnob(brakeTimeKnob, Section::Speed); brakeTimeAttach = std::make_unique(apvts, SupaTriggaProcessor::SPEED_ID, brakeTimeKnob); - setupToggle(brakeInstantToggle, "speed"); + setupToggle(brakeInstantToggle, Section::Speed); brakeInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_SPEED_ID, brakeInstantToggle); // Reverse section - setupKnob(reverseProbKnob, "reverse"); + setupKnob(reverseProbKnob, Section::Reverse); reverseProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REVERSE_ID, reverseProbKnob); - setupToggle(reverseInstantToggle, "reverse"); + setupToggle(reverseInstantToggle, Section::Reverse); reverseInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_REVERSE_ID, reverseInstantToggle); // Repeat section - setupKnob(repeatProbKnob, "repeat"); + setupKnob(repeatProbKnob, Section::Repeat); repeatProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REPEAT_ID, repeatProbKnob); - setupToggle(repeatInstantToggle, "repeat"); + setupToggle(repeatInstantToggle, Section::Repeat); repeatInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_REPEAT_ID, repeatInstantToggle); // Make window non-resizable @@ -141,27 +141,24 @@ SupaTriggaEditor::SupaTriggaEditor(SupaTriggaProcessor &p, SupaTriggaEditor::~SupaTriggaEditor() { setLookAndFeel(nullptr); } -void SupaTriggaEditor::setupKnob(juce::Slider &slider, - const juce::String &styleClass) { +void SupaTriggaEditor::setupKnob(juce::Slider &slider, Section section) { slider.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); // Text is drawn by LookAndFeel slider.setRotaryParameters(juce::MathConstants::pi * 1.25f, juce::MathConstants::pi * 2.75f, true); addAndMakeVisible(slider); - // Set styling colors - if (styleClass == "global") { - slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); - } else if (styleClass == "speed") { - slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::speedAccent); - } else if (styleClass == "reverse") { - slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::reverseAccent); - } else if (styleClass == "repeat") { - slider.setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::repeatAccent); + juce::Colour accent; + switch (section) + { + case Section::Global: accent = Theme::Colors::globalAccent; break; + case Section::Speed: accent = Theme::Colors::speedAccent; break; + case Section::Reverse: accent = Theme::Colors::reverseAccent; break; + case Section::Repeat: accent = Theme::Colors::repeatAccent; break; } + slider.setColour(juce::Slider::rotarySliderFillColourId, accent); } -void SupaTriggaEditor::setupToggle(juce::ToggleButton &toggle, - const juce::String &styleClass) { +void SupaTriggaEditor::setupToggle(juce::ToggleButton &toggle, Section section) { // Configure text change on toggle state change using a listener toggle.setName(toggle.getToggleState() ? "On" : "Off"); toggle.onClick = [&toggle] { @@ -169,15 +166,15 @@ void SupaTriggaEditor::setupToggle(juce::ToggleButton &toggle, }; addAndMakeVisible(toggle); - if (styleClass == "global") { - toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::globalAccent); - } else if (styleClass == "speed") { - toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::speedAccent); - } else if (styleClass == "reverse") { - toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::reverseAccent); - } else if (styleClass == "repeat") { - toggle.setColour(juce::ToggleButton::tickColourId, Theme::Colors::repeatAccent); + juce::Colour accent; + switch (section) + { + case Section::Global: accent = Theme::Colors::globalAccent; break; + case Section::Speed: accent = Theme::Colors::speedAccent; break; + case Section::Reverse: accent = Theme::Colors::reverseAccent; break; + case Section::Repeat: accent = Theme::Colors::repeatAccent; break; } + toggle.setColour(juce::ToggleButton::tickColourId, accent); } void SupaTriggaEditor::paint(juce::Graphics &g) { diff --git a/supatrigga/Source/SupaTriggaEditor.h b/supatrigga/Source/SupaTriggaEditor.h index d85a077..1325fdc 100644 --- a/supatrigga/Source/SupaTriggaEditor.h +++ b/supatrigga/Source/SupaTriggaEditor.h @@ -33,8 +33,10 @@ class SupaTriggaEditor : public juce::AudioProcessorEditor SupaTriggaLookAndFeel customLookAndFeel; - void setupKnob(juce::Slider& slider, const juce::String& styleClass); - void setupToggle(juce::ToggleButton& toggle, const juce::String& styleClass); + enum class Section { Global, Speed, Reverse, Repeat }; + + void setupKnob(juce::Slider& slider, Section section); + void setupToggle(juce::ToggleButton& toggle, Section section); // Header Components // Not strictly needed as component, can be drawn in paint() instead to save overhead. From f0d1a5b8868f13ddda09d0b3be49cb62f9f2ccf2 Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:56:56 +0200 Subject: [PATCH 7/9] style: reformat editor files to match project conventions (4-space, Allman) Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/Source/SupaTriggaEditor.cpp | 602 +++++++++++++------------ 1 file changed, 315 insertions(+), 287 deletions(-) diff --git a/supatrigga/Source/SupaTriggaEditor.cpp b/supatrigga/Source/SupaTriggaEditor.cpp index 5b0c477..82c3d4a 100644 --- a/supatrigga/Source/SupaTriggaEditor.cpp +++ b/supatrigga/Source/SupaTriggaEditor.cpp @@ -1,313 +1,341 @@ #include "SupaTriggaEditor.h" #include "Theme.h" -SupaTriggaLookAndFeel::SupaTriggaLookAndFeel() { - - // Configure default colors - setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); - setColour(juce::Slider::rotarySliderOutlineColourId, Theme::Colors::track); - setColour(juce::Slider::textBoxTextColourId, Theme::Colors::textValue); +SupaTriggaLookAndFeel::SupaTriggaLookAndFeel() +{ + // Configure default colors + setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); + setColour(juce::Slider::rotarySliderOutlineColourId, Theme::Colors::track); + setColour(juce::Slider::textBoxTextColourId, Theme::Colors::textValue); } -void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics &g, int x, int y, +void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, const float rotaryStartAngle, const float rotaryEndAngle, - juce::Slider &slider) { - const float radius = 34.0f; - const float centreX = (float)x + (float)width * 0.5f; - const float centreY = (float)y + (float)height * 0.5f; - - // Track - const float trackThickness = 6.0f; - g.setColour(slider.findColour(juce::Slider::rotarySliderOutlineColourId)); - juce::Path trackPath; - trackPath.addCentredArc(centreX, centreY, radius, radius, 0.0f, rotaryStartAngle, rotaryEndAngle, true); - g.strokePath(trackPath, juce::PathStrokeType(trackThickness, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); - - // Fill - auto fillColour = slider.findColour(juce::Slider::rotarySliderFillColourId); - g.setColour(fillColour); - - // Only draw fill if greater than 0 - if (sliderPos > 0.0f) { - const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); - juce::Path fillPath; - fillPath.addCentredArc(centreX, centreY, radius, radius, 0.0f, rotaryStartAngle, angle, true); - g.strokePath(fillPath, juce::PathStrokeType(trackThickness, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); - } - - // Center Inner Circle - const float innerRadius = Theme::Metrics::innerRadius; - const float innerDiameter = innerRadius * 2.0f; - g.setColour(Theme::Colors::background); - g.fillEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter); - g.setColour(Theme::Colors::innerCircle); - g.drawEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter, 1.0f); - - // Text Value - g.setColour(slider.findColour(juce::Slider::textBoxTextColourId)); - - auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); - g.setFont(font); - - juce::String text = slider.getTextFromValue(slider.getValue()); - g.drawText(text, x, y, width, height, juce::Justification::centred, false); + juce::Slider& slider) +{ + const float radius = 34.0f; + const float centreX = (float)x + (float)width * 0.5f; + const float centreY = (float)y + (float)height * 0.5f; + + // Track + const float trackThickness = 6.0f; + g.setColour(slider.findColour(juce::Slider::rotarySliderOutlineColourId)); + juce::Path trackPath; + trackPath.addCentredArc(centreX, centreY, radius, radius, 0.0f, + rotaryStartAngle, rotaryEndAngle, true); + g.strokePath(trackPath, juce::PathStrokeType(trackThickness, + juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); + + // Fill + auto fillColour = slider.findColour(juce::Slider::rotarySliderFillColourId); + g.setColour(fillColour); + + // Only draw fill if greater than 0 + if (sliderPos > 0.0f) + { + const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); + juce::Path fillPath; + fillPath.addCentredArc(centreX, centreY, radius, radius, 0.0f, + rotaryStartAngle, angle, true); + g.strokePath(fillPath, juce::PathStrokeType(trackThickness, + juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); + } + + // Center Inner Circle + const float innerRadius = Theme::Metrics::innerRadius; + const float innerDiameter = innerRadius * 2.0f; + g.setColour(Theme::Colors::background); + g.fillEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter); + g.setColour(Theme::Colors::innerCircle); + g.drawEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter, 1.0f); + + // Text Value + g.setColour(slider.findColour(juce::Slider::textBoxTextColourId)); + + auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); + g.setFont(font); + + juce::String text = slider.getTextFromValue(slider.getValue()); + g.drawText(text, x, y, width, height, juce::Justification::centred, false); } -void SupaTriggaLookAndFeel::drawToggleButton( - juce::Graphics &g, juce::ToggleButton &button, - bool /*shouldDrawButtonAsHighlighted*/, - bool /*shouldDrawButtonAsDown*/) { - const float radius = 34.0f; - const float centreX = button.getLocalBounds().getCentreX(); - const float centreY = button.getLocalBounds().getCentreY(); - - // Toggle Track - g.setColour(Theme::Colors::track); - g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 6.0f); - - // Toggle Fill (ON/OFF) - if (button.getToggleState()) { - g.setColour(button.findColour(juce::ToggleButton::tickColourId)); - g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 6.0f); - } - - // Center Inner Circle - const float innerRadius = Theme::Metrics::innerRadius; - const float innerDiameter = innerRadius * 2.0f; - g.setColour(Theme::Colors::background); - g.fillEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter); - g.setColour(Theme::Colors::innerCircle); - g.drawEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter, 1.0f); +void SupaTriggaLookAndFeel::drawToggleButton(juce::Graphics& g, juce::ToggleButton& button, + bool /*shouldDrawButtonAsHighlighted*/, + bool /*shouldDrawButtonAsDown*/) +{ + const float radius = 34.0f; + const float centreX = button.getLocalBounds().getCentreX(); + const float centreY = button.getLocalBounds().getCentreY(); - // Text Label - g.setColour(button.getToggleState() - ? Theme::Colors::textValue - : Theme::Colors::textSecondary); + // Toggle Track + g.setColour(Theme::Colors::track); + g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 6.0f); - auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); - g.setFont(font); + // Toggle Fill (ON/OFF) + if (button.getToggleState()) + { + g.setColour(button.findColour(juce::ToggleButton::tickColourId)); + g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 6.0f); + } + + // Center Inner Circle + const float innerRadius = Theme::Metrics::innerRadius; + const float innerDiameter = innerRadius * 2.0f; + g.setColour(Theme::Colors::background); + g.fillEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter); + g.setColour(Theme::Colors::innerCircle); + g.drawEllipse(centreX - innerRadius, centreY - innerRadius, innerDiameter, innerDiameter, 1.0f); + + // Text Label + g.setColour(button.getToggleState() + ? Theme::Colors::textValue + : Theme::Colors::textSecondary); + + auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); + g.setFont(font); + + g.drawText(button.getName(), button.getLocalBounds(), juce::Justification::centred, false); +} - g.drawText(button.getName(), button.getLocalBounds(), juce::Justification::centred, false); +SupaTriggaEditor::SupaTriggaEditor(SupaTriggaProcessor& p, + juce::AudioProcessorValueTreeState& vts) + : AudioProcessorEditor(&p), audioProcessor(p), apvts(vts) +{ + setLookAndFeel(&customLookAndFeel); + + // Global section + setupKnob(rearrangeKnob, Section::Global); + rearrangeAttach = std::make_unique( + apvts, SupaTriggaProcessor::PROB_REARRANGE_ID, rearrangeKnob); + + setupKnob(slicesKnob, Section::Global); + slicesAttach = std::make_unique( + apvts, SupaTriggaProcessor::GRANULARITY_ID, slicesKnob); + + setupKnob(silenceKnob, Section::Global); + silenceAttach = std::make_unique( + apvts, SupaTriggaProcessor::PROB_SILENCE_ID, silenceKnob); + + // Brake section + setupKnob(brakeProbKnob, Section::Speed); + brakeProbAttach = std::make_unique( + apvts, SupaTriggaProcessor::PROB_SPEED_ID, brakeProbKnob); + + setupKnob(brakeTimeKnob, Section::Speed); + brakeTimeAttach = std::make_unique( + apvts, SupaTriggaProcessor::SPEED_ID, brakeTimeKnob); + + setupToggle(brakeInstantToggle, Section::Speed); + brakeInstantAttach = std::make_unique( + apvts, SupaTriggaProcessor::INSTANT_SPEED_ID, brakeInstantToggle); + + // Reverse section + setupKnob(reverseProbKnob, Section::Reverse); + reverseProbAttach = std::make_unique( + apvts, SupaTriggaProcessor::PROB_REVERSE_ID, reverseProbKnob); + + setupToggle(reverseInstantToggle, Section::Reverse); + reverseInstantAttach = std::make_unique( + apvts, SupaTriggaProcessor::INSTANT_REVERSE_ID, reverseInstantToggle); + + // Repeat section + setupKnob(repeatProbKnob, Section::Repeat); + repeatProbAttach = std::make_unique( + apvts, SupaTriggaProcessor::PROB_REPEAT_ID, repeatProbKnob); + + setupToggle(repeatInstantToggle, Section::Repeat); + repeatInstantAttach = std::make_unique( + apvts, SupaTriggaProcessor::INSTANT_REPEAT_ID, repeatInstantToggle); + + // Make window non-resizable + setResizable(false, false); + + // Set Window Size + setSize(Theme::Metrics::windowWidth, Theme::Metrics::windowHeight); } -SupaTriggaEditor::SupaTriggaEditor(SupaTriggaProcessor &p, - juce::AudioProcessorValueTreeState &vts) - : AudioProcessorEditor(&p), audioProcessor(p), apvts(vts) { - setLookAndFeel(&customLookAndFeel); - - // Global section - setupKnob(rearrangeKnob, Section::Global); - rearrangeAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REARRANGE_ID, rearrangeKnob); - - setupKnob(slicesKnob, Section::Global); - slicesAttach = std::make_unique(apvts, SupaTriggaProcessor::GRANULARITY_ID, slicesKnob); - - setupKnob(silenceKnob, Section::Global); - silenceAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_SILENCE_ID, silenceKnob); - - // Brake section - setupKnob(brakeProbKnob, Section::Speed); - brakeProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_SPEED_ID, brakeProbKnob); - - setupKnob(brakeTimeKnob, Section::Speed); - brakeTimeAttach = std::make_unique(apvts, SupaTriggaProcessor::SPEED_ID, brakeTimeKnob); - - setupToggle(brakeInstantToggle, Section::Speed); - brakeInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_SPEED_ID, brakeInstantToggle); - - // Reverse section - setupKnob(reverseProbKnob, Section::Reverse); - reverseProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REVERSE_ID, reverseProbKnob); - - setupToggle(reverseInstantToggle, Section::Reverse); - reverseInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_REVERSE_ID, reverseInstantToggle); - - // Repeat section - setupKnob(repeatProbKnob, Section::Repeat); - repeatProbAttach = std::make_unique(apvts, SupaTriggaProcessor::PROB_REPEAT_ID, repeatProbKnob); - - setupToggle(repeatInstantToggle, Section::Repeat); - repeatInstantAttach = std::make_unique(apvts, SupaTriggaProcessor::INSTANT_REPEAT_ID, repeatInstantToggle); - - // Make window non-resizable - setResizable(false, false); - - // Set Window Size - setSize(Theme::Metrics::windowWidth, Theme::Metrics::windowHeight); +SupaTriggaEditor::~SupaTriggaEditor() +{ + setLookAndFeel(nullptr); } -SupaTriggaEditor::~SupaTriggaEditor() { setLookAndFeel(nullptr); } - -void SupaTriggaEditor::setupKnob(juce::Slider &slider, Section section) { - slider.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); - slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); // Text is drawn by LookAndFeel - slider.setRotaryParameters(juce::MathConstants::pi * 1.25f, juce::MathConstants::pi * 2.75f, true); - addAndMakeVisible(slider); - - juce::Colour accent; - switch (section) - { - case Section::Global: accent = Theme::Colors::globalAccent; break; - case Section::Speed: accent = Theme::Colors::speedAccent; break; - case Section::Reverse: accent = Theme::Colors::reverseAccent; break; - case Section::Repeat: accent = Theme::Colors::repeatAccent; break; - } - slider.setColour(juce::Slider::rotarySliderFillColourId, accent); +void SupaTriggaEditor::setupKnob(juce::Slider& slider, Section section) +{ + slider.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); + slider.setRotaryParameters(juce::MathConstants::pi * 1.25f, + juce::MathConstants::pi * 2.75f, true); + addAndMakeVisible(slider); + + juce::Colour accent; + switch (section) + { + case Section::Global: accent = Theme::Colors::globalAccent; break; + case Section::Speed: accent = Theme::Colors::speedAccent; break; + case Section::Reverse: accent = Theme::Colors::reverseAccent; break; + case Section::Repeat: accent = Theme::Colors::repeatAccent; break; + } + slider.setColour(juce::Slider::rotarySliderFillColourId, accent); } -void SupaTriggaEditor::setupToggle(juce::ToggleButton &toggle, Section section) { - // Configure text change on toggle state change using a listener - toggle.setName(toggle.getToggleState() ? "On" : "Off"); - toggle.onClick = [&toggle] { +void SupaTriggaEditor::setupToggle(juce::ToggleButton& toggle, Section section) +{ toggle.setName(toggle.getToggleState() ? "On" : "Off"); - }; - addAndMakeVisible(toggle); - - juce::Colour accent; - switch (section) - { - case Section::Global: accent = Theme::Colors::globalAccent; break; - case Section::Speed: accent = Theme::Colors::speedAccent; break; - case Section::Reverse: accent = Theme::Colors::reverseAccent; break; - case Section::Repeat: accent = Theme::Colors::repeatAccent; break; - } - toggle.setColour(juce::ToggleButton::tickColourId, accent); + toggle.onClick = [&toggle] { + toggle.setName(toggle.getToggleState() ? "On" : "Off"); + }; + addAndMakeVisible(toggle); + + juce::Colour accent; + switch (section) + { + case Section::Global: accent = Theme::Colors::globalAccent; break; + case Section::Speed: accent = Theme::Colors::speedAccent; break; + case Section::Reverse: accent = Theme::Colors::reverseAccent; break; + case Section::Repeat: accent = Theme::Colors::repeatAccent; break; + } + toggle.setColour(juce::ToggleButton::tickColourId, accent); } -void SupaTriggaEditor::paint(juce::Graphics &g) { - // Background - g.fillAll(Theme::Colors::background); - - // Header Panel is flush with the background - - // Draw thick bottom line of header - g.setColour(Theme::Colors::border); - g.drawLine(0.0f, 56.0f, (float)getWidth(), 56.0f, 1.0f); - - // Draw header text - g.setColour(Theme::Colors::textPrimary); - g.setFont(juce::FontOptions(20.0f).withStyle("Bold")); - g.drawText("SUPATRIGGA", 20, 0, 200, 56, juce::Justification::centredLeft, true); - - g.setColour(Theme::Colors::textSecondaryDim); - g.setFont(juce::FontOptions(11.0f).withStyle("Bold")); - g.drawText("BY SMARTELECTRONIX", getWidth() - 200, 0, 180, 56, juce::Justification::centredRight, true); - - // Section Dividers (the cross in the grid) - auto gridBounds = getLocalBounds().withTrimmedTop(56); - int midX = gridBounds.getCentreX(); - int midY = gridBounds.getCentreY(); - - g.setColour(Theme::Colors::border); - // Vertical line (indented top and bottom by 16px) - g.drawLine((float)midX, (float)(gridBounds.getY() + 16), (float)midX, (float)(gridBounds.getBottom() - 16), 1.0f); - // Horizontal line (indented left/right by 20px, gap in center) - g.drawLine(20.0f, (float)midY, (float)(midX - 16), (float)midY, 1.0f); - g.drawLine((float)(midX + 16), (float)midY, (float)(getWidth() - 20), (float)midY, 1.0f); - - // Draw Section Titles - auto drawSectionTitle = [&](const juce::String &title, juce::Colour c, int x, int y) { - g.setColour(c); - g.setFont(juce::FontOptions(14.0f).withStyle("Bold")); - g.drawText(title, x + 20, y + 16, 100, 20, juce::Justification::topLeft, true); - }; - - drawSectionTitle("GLOBAL", Theme::Colors::globalAccent, gridBounds.getX(), gridBounds.getY()); - drawSectionTitle("BRAKE", Theme::Colors::speedAccent, midX, gridBounds.getY()); - drawSectionTitle("REVERSE", Theme::Colors::reverseAccent, gridBounds.getX(), midY); - drawSectionTitle("REPEAT", Theme::Colors::repeatAccent, midX, midY); - - // Draw Sub-labels for controls - auto drawLabel = [&](const juce::String &text, juce::Component &comp) { - g.setColour(Theme::Colors::textSecondary); - g.setFont( juce::FontOptions(11.0f).withStyle("Bold")); - auto bounds = comp.getBounds(); - // Move the text up by drawing it in a box higher above the component - g.drawText(text, bounds.getX() - 20, bounds.getY() - 28, bounds.getWidth() + 40, 20, juce::Justification::centredBottom, false); - }; - - // Global Section Label centers - drawLabel("REARRANGE", rearrangeKnob); - drawLabel("SLICES", slicesKnob); - drawLabel("SILENCE", silenceKnob); - - // Brake Section Label centers - drawLabel("BRAKE PROB", brakeProbKnob); - drawLabel("BRAKE TIME", brakeTimeKnob); - drawLabel("INSTANT", brakeInstantToggle); - - // Reverse Section Label centers - drawLabel("REVERSE PROB", reverseProbKnob); - drawLabel("INSTANT", reverseInstantToggle); - - // Repeat Section Label centers - drawLabel("REPEAT PROB", repeatProbKnob); - drawLabel("INSTANT", repeatInstantToggle); +void SupaTriggaEditor::paint(juce::Graphics& g) +{ + // Background + g.fillAll(Theme::Colors::background); + + // Draw thick bottom line of header + g.setColour(Theme::Colors::border); + g.drawLine(0.0f, 56.0f, (float)getWidth(), 56.0f, 1.0f); + + // Draw header text + g.setColour(Theme::Colors::textPrimary); + g.setFont(juce::FontOptions(20.0f).withStyle("Bold")); + g.drawText("SUPATRIGGA", 20, 0, 200, 56, juce::Justification::centredLeft, true); + + g.setColour(Theme::Colors::textSecondaryDim); + g.setFont(juce::FontOptions(11.0f).withStyle("Bold")); + g.drawText("BY SMARTELECTRONIX", getWidth() - 200, 0, 180, 56, + juce::Justification::centredRight, true); + + // Section Dividers (the cross in the grid) + auto gridBounds = getLocalBounds().withTrimmedTop(56); + int midX = gridBounds.getCentreX(); + int midY = gridBounds.getCentreY(); + + g.setColour(Theme::Colors::border); + // Vertical line (indented top and bottom by 16px) + g.drawLine((float)midX, (float)(gridBounds.getY() + 16), + (float)midX, (float)(gridBounds.getBottom() - 16), 1.0f); + // Horizontal line (indented left/right by 20px, gap in center) + g.drawLine(20.0f, (float)midY, (float)(midX - 16), (float)midY, 1.0f); + g.drawLine((float)(midX + 16), (float)midY, (float)(getWidth() - 20), (float)midY, 1.0f); + + // Draw Section Titles + auto drawSectionTitle = [&](const juce::String& title, juce::Colour c, int x, int y) + { + g.setColour(c); + g.setFont(juce::FontOptions(14.0f).withStyle("Bold")); + g.drawText(title, x + 20, y + 16, 100, 20, juce::Justification::topLeft, true); + }; + + drawSectionTitle("GLOBAL", Theme::Colors::globalAccent, gridBounds.getX(), gridBounds.getY()); + drawSectionTitle("BRAKE", Theme::Colors::speedAccent, midX, gridBounds.getY()); + drawSectionTitle("REVERSE", Theme::Colors::reverseAccent, gridBounds.getX(), midY); + drawSectionTitle("REPEAT", Theme::Colors::repeatAccent, midX, midY); + + // Draw Sub-labels for controls + auto drawLabel = [&](const juce::String& text, juce::Component& comp) + { + g.setColour(Theme::Colors::textSecondary); + g.setFont(juce::FontOptions(11.0f).withStyle("Bold")); + auto bounds = comp.getBounds(); + g.drawText(text, bounds.getX() - 20, bounds.getY() - 28, + bounds.getWidth() + 40, 20, juce::Justification::centredBottom, false); + }; + + // Global Section Label centers + drawLabel("REARRANGE", rearrangeKnob); + drawLabel("SLICES", slicesKnob); + drawLabel("SILENCE", silenceKnob); + + // Brake Section Label centers + drawLabel("BRAKE PROB", brakeProbKnob); + drawLabel("BRAKE TIME", brakeTimeKnob); + drawLabel("INSTANT", brakeInstantToggle); + + // Reverse Section Label centers + drawLabel("REVERSE PROB", reverseProbKnob); + drawLabel("INSTANT", reverseInstantToggle); + + // Repeat Section Label centers + drawLabel("REPEAT PROB", repeatProbKnob); + drawLabel("INSTANT", repeatInstantToggle); } -void SupaTriggaEditor::resized() { - auto gridBounds = getLocalBounds().withTrimmedTop(56); - - int midX = gridBounds.getWidth() / 2; - int midY = gridBounds.getHeight() / 2; - - auto globalRect = gridBounds.withWidth(midX).withHeight(midY); - auto speedRect = globalRect.withX(midX); - auto reverseRect = globalRect.withY(gridBounds.getY() + midY); - auto repeatRect = reverseRect.withX(midX); - - // Controls Row bounds (leave room for title and labels) - auto globalRow = globalRect.withTrimmedTop(40).withTrimmedBottom(18); - auto speedRow = speedRect.withTrimmedTop(40).withTrimmedBottom(18); - auto reverseRow = reverseRect.withTrimmedTop(40).withTrimmedBottom(18); - auto repeatRow = repeatRect.withTrimmedTop(40).withTrimmedBottom(18); - - // juce::FlexBox is ideal here to distribute spacing - auto buildFlex = [](juce::FlexBox &fb) { - fb.justifyContent = juce::FlexBox::JustifyContent::spaceAround; - fb.alignItems = juce::FlexBox::AlignItems::flexEnd; // Bottom align to fix margin above divider - fb.flexDirection = juce::FlexBox::Direction::row; - }; - - auto mapItem = [](juce::Component &c) { - return juce::FlexItem(c).withWidth(80).withHeight(80).withMargin( - juce::FlexItem::Margin(0, 4, 0, 4)); - }; - - { - juce::FlexBox fb; - buildFlex(fb); - fb.items.add(mapItem(rearrangeKnob)); - fb.items.add(mapItem(slicesKnob)); - fb.items.add(mapItem(silenceKnob)); - fb.performLayout(globalRow); - } - - { - juce::FlexBox fb; - buildFlex(fb); - fb.items.add(mapItem(brakeProbKnob)); - fb.items.add(mapItem(brakeTimeKnob)); - fb.items.add(mapItem(brakeInstantToggle)); - fb.performLayout(speedRow); - } - - { - juce::FlexBox fb; - buildFlex(fb); - fb.items.add(mapItem(reverseProbKnob)); - fb.items.add(mapItem(reverseInstantToggle)); - fb.performLayout(reverseRow); - } - - { - juce::FlexBox fb; - buildFlex(fb); - fb.items.add(mapItem(repeatProbKnob)); - fb.items.add(mapItem(repeatInstantToggle)); - fb.performLayout(repeatRow); - } +void SupaTriggaEditor::resized() +{ + auto gridBounds = getLocalBounds().withTrimmedTop(56); + + int midX = gridBounds.getWidth() / 2; + int midY = gridBounds.getHeight() / 2; + + auto globalRect = gridBounds.withWidth(midX).withHeight(midY); + auto speedRect = globalRect.withX(midX); + auto reverseRect = globalRect.withY(gridBounds.getY() + midY); + auto repeatRect = reverseRect.withX(midX); + + // Controls Row bounds (leave room for title and labels) + auto globalRow = globalRect.withTrimmedTop(40).withTrimmedBottom(18); + auto speedRow = speedRect.withTrimmedTop(40).withTrimmedBottom(18); + auto reverseRow = reverseRect.withTrimmedTop(40).withTrimmedBottom(18); + auto repeatRow = repeatRect.withTrimmedTop(40).withTrimmedBottom(18); + + auto buildFlex = [](juce::FlexBox& fb) + { + fb.justifyContent = juce::FlexBox::JustifyContent::spaceAround; + fb.alignItems = juce::FlexBox::AlignItems::flexEnd; + fb.flexDirection = juce::FlexBox::Direction::row; + }; + + auto mapItem = [](juce::Component& c) + { + return juce::FlexItem(c).withWidth(80).withHeight(80).withMargin( + juce::FlexItem::Margin(0, 4, 0, 4)); + }; + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(rearrangeKnob)); + fb.items.add(mapItem(slicesKnob)); + fb.items.add(mapItem(silenceKnob)); + fb.performLayout(globalRow); + } + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(brakeProbKnob)); + fb.items.add(mapItem(brakeTimeKnob)); + fb.items.add(mapItem(brakeInstantToggle)); + fb.performLayout(speedRow); + } + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(reverseProbKnob)); + fb.items.add(mapItem(reverseInstantToggle)); + fb.performLayout(reverseRow); + } + + { + juce::FlexBox fb; + buildFlex(fb); + fb.items.add(mapItem(repeatProbKnob)); + fb.items.add(mapItem(repeatInstantToggle)); + fb.performLayout(repeatRow); + } } From b74e0ef8f1256b788b3c87f80e58986ac72159cf Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:54:27 +0200 Subject: [PATCH 8/9] feat: embed Inter Bold font as binary resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inter-Bold.otf (SIL OFL) is embedded via juce_add_binary_data and loaded with Typeface::createSystemTypefaceFor — works on all platforms without requiring the font to be installed. Falls back to system bold if loading fails. Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/CMakeLists.txt | 5 +++++ supatrigga/Resources/Inter-Bold.otf | Bin 0 -> 271436 bytes supatrigga/Source/SupaTriggaEditor.cpp | 13 +++++++++++-- supatrigga/Source/SupaTriggaEditor.h | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 supatrigga/Resources/Inter-Bold.otf diff --git a/supatrigga/CMakeLists.txt b/supatrigga/CMakeLists.txt index a4ac146..1a44b5d 100644 --- a/supatrigga/CMakeLists.txt +++ b/supatrigga/CMakeLists.txt @@ -20,9 +20,14 @@ juce_add_plugin(SupaTrigga AU_MAIN_TYPE kAudioUnitType_Effect ) +juce_add_binary_data(SupaTrigga_Assets SOURCES + Resources/Inter-Bold.otf +) + target_sources(SupaTrigga PRIVATE Source/PluginProcessor.cpp Source/SupaTriggaEditor.cpp ) smartelectronix_plugin_common(SupaTrigga) +target_link_libraries(SupaTrigga PRIVATE SupaTrigga_Assets) diff --git a/supatrigga/Resources/Inter-Bold.otf b/supatrigga/Resources/Inter-Bold.otf new file mode 100644 index 0000000000000000000000000000000000000000..c74cc0c6c13ccda9d3beac6c5a7aac5176da4a7c GIT binary patch literal 271436 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
eA*oHF==1$> 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ë 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 Z4)6Z6Xp%P-{R7NjGO@orpUJK5{{f-JOD+Ha literal 0 HcmV?d00001 diff --git a/supatrigga/Source/SupaTriggaEditor.cpp b/supatrigga/Source/SupaTriggaEditor.cpp index 82c3d4a..7f6f6af 100644 --- a/supatrigga/Source/SupaTriggaEditor.cpp +++ b/supatrigga/Source/SupaTriggaEditor.cpp @@ -1,5 +1,6 @@ #include "SupaTriggaEditor.h" #include "Theme.h" +#include "BinaryData.h" SupaTriggaLookAndFeel::SupaTriggaLookAndFeel() { @@ -7,6 +8,10 @@ SupaTriggaLookAndFeel::SupaTriggaLookAndFeel() setColour(juce::Slider::rotarySliderFillColourId, Theme::Colors::globalAccent); setColour(juce::Slider::rotarySliderOutlineColourId, Theme::Colors::track); setColour(juce::Slider::textBoxTextColourId, Theme::Colors::textValue); + + // Load embedded Inter Bold font + interBold = juce::Typeface::createSystemTypefaceFor( + BinaryData::InterBold_otf, BinaryData::InterBold_otfSize); } void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, @@ -55,7 +60,9 @@ void SupaTriggaLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, // Text Value g.setColour(slider.findColour(juce::Slider::textBoxTextColourId)); - auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); + auto font = interBold != nullptr + ? juce::Font(juce::FontOptions(interBold).withHeight(12.0f)) + : juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); g.setFont(font); juce::String text = slider.getTextFromValue(slider.getValue()); @@ -94,7 +101,9 @@ void SupaTriggaLookAndFeel::drawToggleButton(juce::Graphics& g, juce::ToggleButt ? Theme::Colors::textValue : Theme::Colors::textSecondary); - auto font = juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); + auto font = interBold != nullptr + ? juce::Font(juce::FontOptions(interBold).withHeight(12.0f)) + : juce::Font(juce::FontOptions{}.withHeight(12.0f).withStyle("Bold")); g.setFont(font); g.drawText(button.getName(), button.getLocalBounds(), juce::Justification::centred, false); diff --git a/supatrigga/Source/SupaTriggaEditor.h b/supatrigga/Source/SupaTriggaEditor.h index 1325fdc..3eefa8b 100644 --- a/supatrigga/Source/SupaTriggaEditor.h +++ b/supatrigga/Source/SupaTriggaEditor.h @@ -16,6 +16,8 @@ class SupaTriggaLookAndFeel : public juce::LookAndFeel_V4 void drawToggleButton(juce::Graphics& g, juce::ToggleButton& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override; +private: + juce::Typeface::Ptr interBold; }; class SupaTriggaEditor : public juce::AudioProcessorEditor From b0c01e5fe63420b3c8ef36b658d794692430f20b Mon Sep 17 00:00:00 2001 From: Bram de Jong <805269+bdejong@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:54:32 +0200 Subject: [PATCH 9/9] fix: remove slices/measure suffix from Granularity parameter display The custom UI has a "SLICES" label; the suffix was redundant and cluttered the knob's value display. Co-Authored-By: Claude Opus 4.6 (1M context) --- supatrigga/Source/PluginProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supatrigga/Source/PluginProcessor.cpp b/supatrigga/Source/PluginProcessor.cpp index 7df7cac..563bad2 100644 --- a/supatrigga/Source/PluginProcessor.cpp +++ b/supatrigga/Source/PluginProcessor.cpp @@ -61,7 +61,7 @@ juce::AudioProcessorValueTreeState::ParameterLayout SupaTriggaProcessor::createP juce::AudioProcessorParameter::genericParameter, [](float value, int) { int slices = 1 << static_cast(value * (BITSLIDES + 0.5f)); - return juce::String(slices) + " slices/measure"; + return juce::String(slices); }, nullptr));