diff --git a/plugin.cpp b/plugin.cpp new file mode 100644 index 0000000..d269d6f --- /dev/null +++ b/plugin.cpp @@ -0,0 +1,35 @@ +#include "plugin.hpp" + + +Plugin* pluginInstance; + + +void init(Plugin* p) { + pluginInstance = p; + + // Add modules here + p->addModel(modelSteps); + p->addModel(modelEnvelopeArray); + p->addModel(modelPentaSequencer); + p->addModel(modelImpulseController); + p->addModel(modelSignals); + p->addModel(modelRanges); + p->addModel(modelHexMod); + p->addModel(modelCollatz); + p->addModel(modelStrings); + p->addModel(modelMagnets); + p->addModel(modelOuros); + p->addModel(modelPressedDuck); + p->addModel(modelFlowerPatch); + p->addModel(modelSyncro); + p->addModel(modelNona); + p->addModel(modelDecima); + p->addModel(modelMorta); + p->addModel(modelStepWave); + p->addModel(modelPreeeeeeeeeeessedDuck); + p->addModel(modelArrange); + + + // Any other plugin initialization may go here. + // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. +} diff --git a/plugin.hpp b/plugin.hpp new file mode 100644 index 0000000..9746876 --- /dev/null +++ b/plugin.hpp @@ -0,0 +1,32 @@ +#pragma once +#include + + +using namespace rack; + +// Declare the Plugin, defined in plugin.cpp +extern Plugin* pluginInstance; + +// Declare each Model, defined in each module source file +extern Model* modelSteps; +extern Model* modelEnvelopeArray; +extern Model* modelPentaSequencer; +extern Model* modelImpulseController; +extern Model* modelSignals; +extern Model* modelRanges; +extern Model* modelHexMod; +extern Model* modelCollatz; +extern Model* modelStrings; +extern Model* modelMagnets; +extern Model* modelOuros; +extern Model* modelPressedDuck; +extern Model* modelFlowerPatch; +extern Model* modelSyncro; +extern Model* modelNona; +extern Model* modelDecima; +extern Model* modelMorta; +extern Model* modelStepWave; +extern Model* modelPreeeeeeeeeeessedDuck; +extern Model* modelArrange; + + diff --git a/plugin.json b/plugin.json index ef7acde..b107af9 100644 --- a/plugin.json +++ b/plugin.json @@ -129,13 +129,13 @@ "Voltage-controlled Amplifier" ] }, - { "slug": "FlowerPatch", "name": "Flower Patch", "description": "A 12-tone scale polar visualizer with FFT analysis.", "tags": [ - "Visual" + "Visual", + "Utility" ] }, { @@ -180,13 +180,22 @@ "description": "Single-knob macro-controller utility with 16 simultaneous outputs in different ranges.", "tags": [ "Controller", - "Utility" + "Utility", + "Polyphonic" ] }, { "slug": "StepWave", "name": "Step Wave", - "description": "8-step programmable sequencer/envelope generator with rhythmic displacement and variable shape", + "description": "8-step programmable sequencer/envelope generator with rhythmic displacement and variable shape.", + "tags": [ + "Sequencer" + ] + }, + { + "slug": "Arrange", + "name": "Arrange", + "description": "A seven channel sequencer with quantize/volt option per channel, and adjustable length.", "tags": [ "Sequencer" ] diff --git a/res/Arrange-dark.svg b/res/Arrange-dark.svg new file mode 100644 index 0000000..e14e797 --- /dev/null +++ b/res/Arrange-dark.svg @@ -0,0 +1,310 @@ + + + +image/svg+xml diff --git a/res/Arrange.svg b/res/Arrange.svg new file mode 100644 index 0000000..3929acb --- /dev/null +++ b/res/Arrange.svg @@ -0,0 +1,303 @@ + + + +image/svg+xml diff --git a/src/Arrange.cpp b/src/Arrange.cpp new file mode 100644 index 0000000..8d7d77c --- /dev/null +++ b/src/Arrange.cpp @@ -0,0 +1,446 @@ +//////////////////////////////////////////////////////////// +// +// Arrange +// +// written by Cody Geary +// Copyright 2024, MIT License +// +// Seven channel sequencer +// +//////////////////////////////////////////////////////////// + +#include "rack.hpp" +#include "plugin.hpp" +#include "digital_display.hpp" + +using namespace rack; + +struct DiscreteRoundBlackKnob : RoundBlackKnob { + void onDragEnd(const DragEndEvent& e) override { + ParamQuantity* paramQuantity = getParamQuantity(); + + if (paramQuantity) { + // Get the raw value from the knob + float rawValue = paramQuantity->getValue(); + + // Round the value to the nearest integer + float discreteValue = round(rawValue); + + // Set the snapped value + paramQuantity->setValue(discreteValue); + } + + // Call the base class implementation to ensure proper behavior + RoundBlackKnob::onDragEnd(e); + } +}; + +// Define our Module derived from Rack's Module class +struct Arrange : Module { + enum ParamIds { + STAGE_SELECT, + MAX_STAGES, + FORWARD_BUTTON, + BACKWARDS_BUTTON, + RESET_BUTTON, + CHAN_1_BUTTON, CHAN_2_BUTTON, CHAN_3_BUTTON, CHAN_4_BUTTON, CHAN_5_BUTTON, CHAN_6_BUTTON, CHAN_7_BUTTON, + CHAN_1_KNOB, CHAN_2_KNOB, CHAN_3_KNOB, CHAN_4_KNOB, CHAN_5_KNOB, CHAN_6_KNOB, CHAN_7_KNOB, + NUM_PARAMS + }; + enum InputIds { + RESET_INPUT, + FORWARD_INPUT, + BACKWARDS_INPUT, + NUM_INPUTS + }; + enum OutputIds { + CHAN_1_OUTPUT, CHAN_2_OUTPUT, CHAN_3_OUTPUT, CHAN_4_OUTPUT, CHAN_5_OUTPUT, CHAN_6_OUTPUT, CHAN_7_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + CHAN_1_LIGHT, CHAN_2_LIGHT, CHAN_3_LIGHT, CHAN_4_LIGHT, CHAN_5_LIGHT, CHAN_6_LIGHT, CHAN_7_LIGHT, + NUM_LIGHTS + }; + + DigitalDisplay* digitalDisplay = nullptr; + DigitalDisplay* chanDisplays[7] = {nullptr}; + + dsp::SchmittTrigger resetTrigger, forwardTrigger, backwardTrigger; + dsp::SchmittTrigger channelButtonTriggers[7]; + int currentStage = 0; + int maxStages = 16; + int prevMaxStages = -1; + bool channelButton[7] = {false}; // store button press state for each channel + float outputValues[128][7] = {{0.0f}}; // 2D array to store output values for each stage and channel + bool initializingFlag = true; + // To store the current state for the latch es + bool resetLatched = false; + bool forwardLatched = false; + bool backwardLatched = false; + // Previous state variables to detect rising edges + bool prevResetState = false; + bool prevForwardState = false; + bool prevBackwardState = false; + + json_t* dataToJson() override { + json_t* rootJ = json_object(); + + // Store channelButton array as a JSON array + json_t* channelButtonJ = json_array(); + for (int i = 0; i < 7; i++) { + json_array_append_new(channelButtonJ, json_boolean(channelButton[i])); + } + json_object_set_new(rootJ, "channelButton", channelButtonJ); + + // Store outputValues 2D array as a JSON array of arrays + json_t* outputValuesJ = json_array(); + for (int stage = 0; stage < 128; stage++) { + json_t* stageArrayJ = json_array(); + for (int channel = 0; channel < 7; channel++) { + json_array_append_new(stageArrayJ, json_real(outputValues[stage][channel])); + } + json_array_append_new(outputValuesJ, stageArrayJ); + } + json_object_set_new(rootJ, "outputValues", outputValuesJ); + + return rootJ; + } + + void dataFromJson(json_t* rootJ) override { + // Load channelButton array + json_t* channelButtonJ = json_object_get(rootJ, "channelButton"); + if (channelButtonJ) { + for (int i = 0; i < 7; i++) { + json_t* buttonJ = json_array_get(channelButtonJ, i); + if (buttonJ) + channelButton[i] = json_boolean_value(buttonJ); + } + } + + // Load outputValues 2D array + json_t* outputValuesJ = json_object_get(rootJ, "outputValues"); + if (outputValuesJ) { + for (int stage = 0; stage < 128; stage++) { + json_t* stageArrayJ = json_array_get(outputValuesJ, stage); + if (stageArrayJ) { + for (int channel = 0; channel < 7; channel++) { + json_t* valueJ = json_array_get(stageArrayJ, channel); + if (valueJ) + outputValues[stage][channel] = json_number_value(valueJ); + } + } + } + } + } + + Arrange() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + + configParam(MAX_STAGES, 1.f, 128.f, 16.f, "Max Stages"); + configParam(STAGE_SELECT, 0.f, 1.f, 0.f, "Stage"); + + // Button parameters + configParam(FORWARD_BUTTON, 0.f, 1.f, 0.f, "Forward"); + configParam(BACKWARDS_BUTTON, 0.f, 1.f, 0.f, "Backward"); + configParam(RESET_BUTTON, 0.f, 1.f, 0.f, "Reset"); + configParam(CHAN_1_BUTTON, 0.f, 1.f, 0.f, "Channel 1 Voct/V"); + configParam(CHAN_2_BUTTON, 0.f, 1.f, 0.f, "Channel 2 Voct/V"); + configParam(CHAN_3_BUTTON, 0.f, 1.f, 0.f, "Channel 3 Voct/V"); + configParam(CHAN_4_BUTTON, 0.f, 1.f, 0.f, "Channel 4 Voct/V"); + configParam(CHAN_5_BUTTON, 0.f, 1.f, 0.f, "Channel 5 Voct/V"); + configParam(CHAN_6_BUTTON, 0.f, 1.f, 0.f, "Channel 6 Voct/V"); + configParam(CHAN_7_BUTTON, 0.f, 1.f, 0.f, "Channel 7 Voct/V"); + + // Knob parameters for each channel + configParam(CHAN_1_KNOB, -10.f, 10.f, 0.f, "Channel 1 Knob"); + configParam(CHAN_2_KNOB, -10.f, 10.f, 0.f, "Channel 2 Knob"); + configParam(CHAN_3_KNOB, -10.f, 10.f, 0.f, "Channel 3 Knob"); + configParam(CHAN_4_KNOB, -10.f, 10.f, 0.f, "Channel 4 Knob"); + configParam(CHAN_5_KNOB, -10.f, 10.f, 0.f, "Channel 5 Knob"); + configParam(CHAN_6_KNOB, -10.f, 10.f, 0.f, "Channel 6 Knob"); + configParam(CHAN_7_KNOB, -10.f, 10.f, 0.f, "Channel 7 Knob"); + + // Configure inputs + configInput(RESET_INPUT, "Reset"); + configInput(FORWARD_INPUT, "Forward"); + configInput(BACKWARDS_INPUT, "Backward"); + + // Configure outputs + configOutput(CHAN_1_OUTPUT, "Channel 1 Output"); + configOutput(CHAN_2_OUTPUT, "Channel 2 Output"); + configOutput(CHAN_3_OUTPUT, "Channel 3 Output"); + configOutput(CHAN_4_OUTPUT, "Channel 4 Output"); + configOutput(CHAN_5_OUTPUT, "Channel 5 Output"); + configOutput(CHAN_6_OUTPUT, "Channel 6 Output"); + configOutput(CHAN_7_OUTPUT, "Channel 7 Output"); + } + + void onRandomize(const RandomizeEvent& e) override { + // Randomize only the channel knob parameters + for (int i = 0; i < 7; i++) { + params[CHAN_1_KNOB + i].setValue(random::uniform()*10-5); + } + } + + void process(const ProcessArgs &args) override { + // Update maxStages + maxStages = std::max(1, static_cast(params[MAX_STAGES].getValue())); + currentStage = clamp(static_cast(params[STAGE_SELECT].getValue() * (maxStages - 1)), 0, maxStages - 1); + + // Process stage changes only when needed + static int prevStage = -1; + + // Check for Resize Event + bool resizeEvent = (maxStages != prevMaxStages); + if (resizeEvent) { + paramQuantities[STAGE_SELECT]->setDisplayValue(static_cast(currentStage) / (maxStages - 1)); + paramQuantities[STAGE_SELECT]->displayMultiplier = static_cast(maxStages - 1); + prevMaxStages = maxStages; + } + + // Handle Reset Button or Input + if (resetTrigger.process(params[RESET_BUTTON].getValue()) || + (inputs[RESET_INPUT].isConnected() && inputs[RESET_INPUT].getVoltage() > 0.05f && !prevResetState)) { + currentStage = 0; + paramQuantities[STAGE_SELECT]->setDisplayValue(currentStage); + prevResetState = true; + } else if (inputs[RESET_INPUT].isConnected() && inputs[RESET_INPUT].getVoltage() <= 0.05f) { + prevResetState = false; + } + + // Handle Forward Button or Input + if (forwardTrigger.process(params[FORWARD_BUTTON].getValue()) || + (inputs[FORWARD_INPUT].isConnected() && inputs[FORWARD_INPUT].getVoltage() > 0.05f && !prevForwardState)) { + currentStage = (currentStage + 1) % maxStages; + paramQuantities[STAGE_SELECT]->setDisplayValue(currentStage); + prevForwardState = true; + } else if (inputs[FORWARD_INPUT].isConnected() && inputs[FORWARD_INPUT].getVoltage() <= 0.05f) { + prevForwardState = false; + } + + // Handle Backward Button or Input + if (backwardTrigger.process(params[BACKWARDS_BUTTON].getValue()) || + (inputs[BACKWARDS_INPUT].isConnected() && inputs[BACKWARDS_INPUT].getVoltage() > 0.05f && !prevBackwardState)) { + currentStage = (currentStage == 0) ? maxStages - 1 : currentStage - 1; + paramQuantities[STAGE_SELECT]->setDisplayValue(currentStage); + prevBackwardState = true; + } else if (inputs[BACKWARDS_INPUT].isConnected() && inputs[BACKWARDS_INPUT].getVoltage() <= 0.05f) { + prevBackwardState = false; + } + + // Recall and update knobs only if the stage actually changes + if (currentStage != prevStage || resizeEvent) { + for (int i = 0; i < 7; i++) { + paramQuantities[CHAN_1_KNOB + i]->setDisplayValue(outputValues[currentStage][i]); + } + prevStage = currentStage; // Update the previous stage tracker + } + + // Handle Channel Button Toggles and Store Values + for (int i = 0; i < 7; i++) { + if (channelButtonTriggers[i].process(params[CHAN_1_BUTTON + i].getValue())) { + channelButton[i] = !channelButton[i]; // Toggle state + } + + float outputValue = params[CHAN_1_KNOB + i].getValue(); + if (channelButton[i]) { + outputValue = round(outputValue * 12.0f) / 12.0f; // Quantize to nearest 1/12V + } + + // Set output voltage + outputs[CHAN_1_OUTPUT + i].setVoltage(outputValue); + + // Store output value for the current stage (if valid) + if (currentStage >= 0 && currentStage < 128) { + outputValues[currentStage][i] = outputValue; + } + } + } + +}; + +struct ProgressDisplay : TransparentWidget { + Arrange* module; + + void drawLayer(const DrawArgs& args, int layer) override { + if (!module || layer != 1) return; // Only draw on the correct layer + + // Make sure we have a valid drawing area + if (box.size.x <= 0 || box.size.y <= 0) return; // Prevent any drawing if size is invalid + + // Clear the drawing area + nvgBeginPath(args.vg); + nvgRect(args.vg, 0, 0, box.size.x, box.size.y); + nvgFillColor(args.vg, nvgRGBA(0, 0, 0, 0)); // Transparent background + nvgFill(args.vg); + + // Variables for drawing + int dotsToMake = module->maxStages; + int currentDot = module->currentStage; + float inactiveDotRadius = 2.0f; // Inactive dot size, half of the active dot size + float activeDotRadius = 4.0f; // Active dot size (current stage) + float yPosition = box.size.y * 0.5f; // Centered vertically + float dotSpacing = box.size.x / dotsToMake; // Space between dots + + // Safety checks + if (dotsToMake <= 0) dotsToMake = 1; // Avoid division by zero + + // Draw the dots + for (int i = 0; i < dotsToMake; i++) { + float xPosition = i * dotSpacing + dotSpacing / 2; // Center the dots within each segment + + nvgBeginPath(args.vg); + + // Check if this dot represents the current stage + if (i == currentDot) { + // Draw the current stage dot (active, larger size) + nvgCircle(args.vg, xPosition, yPosition, activeDotRadius); + nvgFillColor(args.vg, nvgRGBA(255, 255, 255, 255)); // Bright white color for active dot + } else { + // Draw inactive dots (smaller size) + nvgCircle(args.vg, xPosition, yPosition, inactiveDotRadius); + nvgFillColor(args.vg, nvgRGBA(100, 100, 100, 255)); // Light grey color for inactive dots + } + + nvgFill(args.vg); + } + } +}; + + +struct ArrangeWidget : ModuleWidget { + ArrangeWidget(Arrange* module) { + setModule(module); + + setPanel(createPanel( + asset::plugin(pluginInstance, "res/Arrange.svg"), + asset::plugin(pluginInstance, "res/Arrange-dark.svg") + )); + + // Add screws or additional design elements as needed + addChild(createWidget(Vec(0, 0))); + addChild(createWidget(Vec(box.size.x - 1 * RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(0, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(box.size.x - 1 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + box.size = Vec(12 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT); // 8HP wide screen + + // Configure and add the first digital display + DigitalDisplay* digitalDisplay = new DigitalDisplay(); + digitalDisplay->fontPath = asset::plugin(pluginInstance, "res/fonts/DejaVuSansMono.ttf"); + digitalDisplay->box.pos = Vec(41.5, 34); // Position on the module + digitalDisplay->box.size = Vec(100, 18); // Size of the display + digitalDisplay->text = "Stage : Max"; // Initial text + digitalDisplay->fgColor = nvgRGB(208, 140, 89); // White color text + digitalDisplay->textPos = Vec(0, 15); // Text position + digitalDisplay->setFontSize(16.0f); // Set the font size as desired + addChild(digitalDisplay); + + if (module) { + module->digitalDisplay = digitalDisplay; // Link the module to the display + } + + // Create and add the ProgressBar Display + ProgressDisplay* progressDisplay = createWidget(Vec(46.5, 50)); // Positioning + progressDisplay->box.size = Vec(90, 25); // Size of the display widget + progressDisplay->module = module; + addChild(progressDisplay); + + // Knobs + addParam(createParamCentered(Vec(20, 50), module, Arrange::STAGE_SELECT)); + addParam(createParamCentered(Vec(160, 50), module, Arrange::MAX_STAGES)); + + addParam(createParamCentered (Vec(45, 90), module, Arrange::RESET_BUTTON)); + addInput(createInputCentered (Vec(20, 90), module, Arrange::RESET_INPUT)); + + addParam(createParamCentered (Vec(100, 90), module, Arrange::BACKWARDS_BUTTON)); + addInput(createInputCentered (Vec(75, 90), module, Arrange::BACKWARDS_INPUT)); + + addParam(createParamCentered (Vec(135, 90), module, Arrange::FORWARD_BUTTON)); + addInput(createInputCentered (Vec(160, 90), module, Arrange::FORWARD_INPUT)); + + float initialYPos = 135; + float spacing = 35; + for (int i = 0; i < 7; ++i) { + float yPos = initialYPos + i * spacing; // Adjusted positioning and spacing + + addParam(createParamCentered (Vec(20, yPos), module, Arrange::CHAN_1_BUTTON + i)); + addChild(createLightCentered>(Vec(20, yPos), module, Arrange::CHAN_1_LIGHT + i)); + addParam(createParamCentered (Vec(50, yPos), module, Arrange::CHAN_1_KNOB + i)); + + if (module) { + // Ratio Displays Initialization + module->chanDisplays[i] = createDigitalDisplay(Vec(75, yPos - 10), "Ready"); + addChild(module->chanDisplays[i]); + } + + addOutput(createOutputCentered (Vec(157, yPos), module, Arrange::CHAN_1_OUTPUT + i)); + } + } + + void draw(const DrawArgs& args) override { + ModuleWidget::draw(args); + Arrange* module = dynamic_cast(this->module); + if (!module) return; + + // Update Stage progress display + if (module->digitalDisplay) { + module->digitalDisplay->text = std::to_string(module->currentStage + 1) + " / " + std::to_string(module->maxStages); + } + + // Update channel quantizer displays + for (int i = 0; i < 7; i++) { + + if (module->chanDisplays[i]) { + if (module->channelButton[i]) { + + // Compute the note name of the note and the octave + float pitchVoltage = module->outputs[Arrange::CHAN_1_OUTPUT + i].getVoltage(); + int octave = static_cast(pitchVoltage + 4); // The integer part represents the octave + + // Calculate the semitone + double fractionalPart = fmod(pitchVoltage, 1.0); + int semitone = round(fractionalPart * 12); + semitone = (semitone % 12 + 12) % 12; // Ensure it's a valid semitone (0 to 11) + + // Define the note names + const char* noteNames[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + const char* noteName = noteNames[semitone]; + + // Create a buffer to hold the full note name + char fullNote[7]; // Enough space for note name + octave + null terminator + snprintf(fullNote, sizeof(fullNote), "%s%d", noteName, octave); // Combine note and octave + + module->chanDisplays[i]->text = fullNote; + module->lights[Arrange::CHAN_1_LIGHT + i].setBrightness(1.0f); + } else { + char buffer[32]; // Create a buffer to hold the formatted string + float value = module->params[Arrange::CHAN_1_KNOB + i].getValue(); // Get the value + + // Format the value to 3 significant figures + snprintf(buffer, sizeof(buffer), "%.3f", value); + + // Set the formatted text + std::string voltageDisplay = std::string(buffer) + " V"; + module->chanDisplays[i]->text = voltageDisplay; + module->lights[Arrange::CHAN_1_LIGHT + i].setBrightness(0.0f); + + } + } + } + } + + DigitalDisplay* createDigitalDisplay(Vec position, std::string initialValue) { + DigitalDisplay* display = new DigitalDisplay(); + display->box.pos = position; + display->box.size = Vec(50, 18); + display->text = initialValue; + display->fgColor = nvgRGB(208, 140, 89); // Gold color text + display->fontPath = asset::plugin(pluginInstance, "res/fonts/DejaVuSansMono.ttf"); + display->setFontSize(14.0f); + return display; + } + +}; + +Model* modelArrange = createModel("Arrange"); \ No newline at end of file diff --git a/src/Strings.cpp b/src/Strings.cpp index d345de6..326b37f 100644 --- a/src/Strings.cpp +++ b/src/Strings.cpp @@ -731,10 +731,6 @@ struct Strings : Module { } // Update display logic if (digitalDisplay && fingeringDisplay) { - // Static variables to remember the last displayed chord, row indices, and fingering - static int lastDisplayedChordIndex = -1; - static int lastDisplayedRowIndex = -1; - static int lastFingering = -1; // Define a small tolerance value for comparison const float capoTolerance = 0.01f; // Adjust tolerance as needed @@ -745,155 +741,146 @@ struct Strings : Module { // Determine if CapoAmount has "effectively" changed using the tolerance bool capoAmountChanged = std::abs(CapoAmount - lastCapoAmount) > capoTolerance; - // Proceed with checking if an update is needed - if (currentChordIndex != lastDisplayedChordIndex || currentRowIndex != lastDisplayedRowIndex || - fingeringVersion != lastFingering || capoAmountChanged) { - // Update the last displayed indices to the current selection - lastDisplayedChordIndex = currentChordIndex; - lastDisplayedRowIndex = currentRowIndex; - lastFingering = fingeringVersion; - - // Only update lastCapoAmount if it has effectively changed - if (capoAmountChanged) { - lastCapoAmount = CapoAmount; - } - // Retrieve the current chord name based on the selection for the digital display - std::string currentChordName = currentNames[currentChordIndex][fingeringVersion]; - // Update the digital display text with the current chord name - digitalDisplay->text = currentChordName; - - // Retrieve the current fingering pattern for the fingering display - std::string currentFingeringPattern = currentChords[currentChordIndex][fingeringVersion]; - // Update the fingering display text with the current fingering pattern - - int capoAmountInt = static_cast(floor(CapoAmount*12)); // Round to the nearest whole number if necessary - std::string capoAmountStr = std::to_string(capoAmountInt); // Convert the integer to a string - - // Compute the note name of the capoed root - float pitchVoltage = currentRoots[currentChordIndex] + CapoAmount; - double fractionalPart = fmod(pitchVoltage, 1.0); - int semitone = round(fractionalPart * 12); - semitone = (semitone % 12 + 12) % 12; - const char* noteNames[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; - const char* noteName = noteNames[semitone]; - - if (CapoAmount ==0){ - fingeringDisplay->text = currentFingeringPattern; + // Only update lastCapoAmount if it has effectively changed + if (capoAmountChanged) { + lastCapoAmount = CapoAmount; + } + // Retrieve the current chord name based on the selection for the digital display + std::string currentChordName = currentNames[currentChordIndex][fingeringVersion]; + // Update the digital display text with the current chord name + digitalDisplay->text = currentChordName; + + // Retrieve the current fingering pattern for the fingering display + std::string currentFingeringPattern = currentChords[currentChordIndex][fingeringVersion]; + // Update the fingering display text with the current fingering pattern + + int capoAmountInt = static_cast(floor(CapoAmount*12)); // Round to the nearest whole number if necessary + std::string capoAmountStr = std::to_string(capoAmountInt); // Convert the integer to a string + + // Compute the note name of the capoed root + float pitchVoltage = currentRoots[currentChordIndex] + CapoAmount; + double fractionalPart = fmod(pitchVoltage, 1.0); + int semitone = round(fractionalPart * 12); + semitone = (semitone % 12 + 12) % 12; + const char* noteNames[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + const char* noteName = noteNames[semitone]; + + if (CapoAmount ==0){ + fingeringDisplay->text = currentFingeringPattern; + } else { + // Update the fingering display text with the current fingering pattern and capo setting + if (CapoAmount > -0.01){ + fingeringDisplay->text = currentFingeringPattern + " +" + capoAmountStr + " " + noteName; } else { - // Update the fingering display text with the current fingering pattern and capo setting - if (CapoAmount > -0.01){ - fingeringDisplay->text = currentFingeringPattern + " +" + capoAmountStr + " " + noteName; - } else { - fingeringDisplay->text = currentFingeringPattern + " " + capoAmountStr + " " + noteName; - } + fingeringDisplay->text = currentFingeringPattern + " " + capoAmountStr + " " + noteName; + } + } + + if (chordDiagram) { + auto semitoneShifts = fingeringToSemitoneShifts(currentChords[currentChordIndex][fingeringVersion]); + chordDiagram->setFingering(semitoneShifts); + } + + if (!ChordBank){ + if (Row1Display) { + //{"B7", "B" , "Bsus4", "Badd9"}, + auto row1text = "Row1"; + if (fingeringVersion == 0){row1text = "7";} + else if (fingeringVersion == 1){row1text = "7 Bar";} + else if (fingeringVersion == 2){row1text = "sus4";} + else if (fingeringVersion == 3){row1text = "add9";} + Row1Display->text = row1text; } - - if (chordDiagram) { - auto semitoneShifts = fingeringToSemitoneShifts(currentChords[currentChordIndex][fingeringVersion]); - chordDiagram->setFingering(semitoneShifts); - } - - if (!ChordBank){ - if (Row1Display) { - //{"B7", "B" , "Bsus4", "Badd9"}, - auto row1text = "Row1"; - if (fingeringVersion == 0){row1text = "7";} - else if (fingeringVersion == 1){row1text = "7 Bar";} - else if (fingeringVersion == 2){row1text = "sus4";} - else if (fingeringVersion == 3){row1text = "add9";} - Row1Display->text = row1text; - } - if (Row2Display) { - //{"A" ,"A-Bar" ,"Amaj7" ,"Aaug"} - auto row2text = "Row2"; - if (fingeringVersion == 0){row2text = "Maj";} - else if (fingeringVersion == 1){row2text = "M Bar";} - else if (fingeringVersion == 2){row2text = "Maj7";} - else if (fingeringVersion == 3){row2text = "aug";} - Row2Display->text = row2text; - } + if (Row2Display) { + //{"A" ,"A-Bar" ,"Amaj7" ,"Aaug"} + auto row2text = "Row2"; + if (fingeringVersion == 0){row2text = "Maj";} + else if (fingeringVersion == 1){row2text = "M Bar";} + else if (fingeringVersion == 2){row2text = "Maj7";} + else if (fingeringVersion == 3){row2text = "aug";} + Row2Display->text = row2text; + } - if (Row3Display) { - //{"Em" ,"Em-Bar" ,"Em7","Em6 "}, - auto row3text = "Row3"; - if (fingeringVersion == 0){row3text = "min";} - else if (fingeringVersion == 1){row3text = "m Bar";} - else if (fingeringVersion == 2){row3text = "m7";} - else if (fingeringVersion == 3){row3text = "m6";} - Row3Display->text = row3text; - } + if (Row3Display) { + //{"Em" ,"Em-Bar" ,"Em7","Em6 "}, + auto row3text = "Row3"; + if (fingeringVersion == 0){row3text = "min";} + else if (fingeringVersion == 1){row3text = "m Bar";} + else if (fingeringVersion == 2){row3text = "m7";} + else if (fingeringVersion == 3){row3text = "m6";} + Row3Display->text = row3text; + } - if (Row4Display) { - //{"Asus2" ,"A6" ,"A7sus4" ,"Am9"}, - auto row4text = "Row4"; - if (fingeringVersion == 0){row4text = "sus2";} - else if (fingeringVersion == 1){row4text = "6";} - else if (fingeringVersion == 2){row4text = "7sus4";} - else if (fingeringVersion == 3){row4text = "m9";} - Row4Display->text = row4text; - } - if (CVModeDisplay) { - //Mark the knob with the mode setting, - auto CVdisplaytext = "V/oct"; - if (VOctCV){CVdisplaytext = "(V/Oct)";} - else {CVdisplaytext = " ";} - CVModeDisplay->text = CVdisplaytext; - } - - } else { + if (Row4Display) { + //{"Asus2" ,"A6" ,"A7sus4" ,"Am9"}, + auto row4text = "Row4"; + if (fingeringVersion == 0){row4text = "sus2";} + else if (fingeringVersion == 1){row4text = "6";} + else if (fingeringVersion == 2){row4text = "7sus4";} + else if (fingeringVersion == 3){row4text = "m9";} + Row4Display->text = row4text; + } + if (CVModeDisplay) { + //Mark the knob with the mode setting, + auto CVdisplaytext = "V/oct"; + if (VOctCV){CVdisplaytext = "(V/Oct)";} + else {CVdisplaytext = " ";} + CVModeDisplay->text = CVdisplaytext; + } - if (Row1Display) { - //{"B7", "B" , "B2", "B6"}, - auto row1text = "Row1"; - if (fingeringVersion == 0){row1text = "7";} - else if (fingeringVersion == 1){row1text = "7 Bar";} - else if (fingeringVersion == 2){row1text = "2";} - else if (fingeringVersion == 3){row1text = "6";} - Row1Display->text = row1text; - } + } else { - if (Row2Display) { - // {"A" ,"A-Bar" ,"Amaj7" ,"A7+5"}, - auto row2text = "Row2"; - if (fingeringVersion == 0){row2text = "Maj";} - else if (fingeringVersion == 1){row2text = "M Bar";} - else if (fingeringVersion == 2){row2text = "Maj7";} - else if (fingeringVersion == 3){row2text = "7+5";} - Row2Display->text = row2text; - } + if (Row1Display) { + //{"B7", "B" , "B2", "B6"}, + auto row1text = "Row1"; + if (fingeringVersion == 0){row1text = "7";} + else if (fingeringVersion == 1){row1text = "7 Bar";} + else if (fingeringVersion == 2){row1text = "2";} + else if (fingeringVersion == 3){row1text = "6";} + Row1Display->text = row1text; + } - if (Row3Display) { - //{{"Em" ,"Em-Bar" ,"Em7" ,"Em6"}, - auto row3text = "Row3"; - if (fingeringVersion == 0){row3text = "min";} - else if (fingeringVersion == 1){row3text = "m Bar";} - else if (fingeringVersion == 2){row3text = "m7";} - else if (fingeringVersion == 3){row3text = "m6";} - Row3Display->text = row3text; - } + if (Row2Display) { + // {"A" ,"A-Bar" ,"Amaj7" ,"A7+5"}, + auto row2text = "Row2"; + if (fingeringVersion == 0){row2text = "Maj";} + else if (fingeringVersion == 1){row2text = "M Bar";} + else if (fingeringVersion == 2){row2text = "Maj7";} + else if (fingeringVersion == 3){row2text = "7+5";} + Row2Display->text = row2text; + } - if (Row4Display) { - //"Adim" ,"Adim7" ,"A9" ,"Aaug"}, - auto row4text = "Row4"; - if (fingeringVersion == 0){row4text = "dim";} - else if (fingeringVersion == 1){row4text = "dim7";} - else if (fingeringVersion == 2){row4text = "9";} - else if (fingeringVersion == 3){row4text = "aug";} - Row4Display->text = row4text; - } - - if (CVModeDisplay) { - //Mark the knob with the mode setting, - auto CVdisplaytext = "V/oct"; - if (VOctCV){CVdisplaytext = "(V/Oct)";} - else {CVdisplaytext = " ";} - CVModeDisplay->text = CVdisplaytext; - } + if (Row3Display) { + //{{"Em" ,"Em-Bar" ,"Em7" ,"Em6"}, + auto row3text = "Row3"; + if (fingeringVersion == 0){row3text = "min";} + else if (fingeringVersion == 1){row3text = "m Bar";} + else if (fingeringVersion == 2){row3text = "m7";} + else if (fingeringVersion == 3){row3text = "m6";} + Row3Display->text = row3text; } - - triggerPulse.trigger(0.001f); // 1ms pulse + + if (Row4Display) { + //"Adim" ,"Adim7" ,"A9" ,"Aaug"}, + auto row4text = "Row4"; + if (fingeringVersion == 0){row4text = "dim";} + else if (fingeringVersion == 1){row4text = "dim7";} + else if (fingeringVersion == 2){row4text = "9";} + else if (fingeringVersion == 3){row4text = "aug";} + Row4Display->text = row4text; + } + + if (CVModeDisplay) { + //Mark the knob with the mode setting, + auto CVdisplaytext = "V/oct"; + if (VOctCV){CVdisplaytext = "(V/Oct)";} + else {CVdisplaytext = " ";} + CVModeDisplay->text = CVdisplaytext; + } } + + triggerPulse.trigger(0.001f); // 1ms pulse } }