diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index f5320d5..bb92e8d 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -543,6 +543,27 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p) scaleLabel.setJustificationType(juce::Justification::centredRight); addAndMakeVisible(scaleLabel); + auto setupTransposeLabel = [&](juce::Label& label, const juce::String& text) { + label.setText(text, juce::dontSendNotification); + label.setFont(juce::Font(9.0f)); + label.setColour(juce::Label::textColourId, juce::Colour(0xff888888)); + label.setJustificationType(juce::Justification::centredLeft); + addAndMakeVisible(label); + }; + setupTransposeLabel(octaveTransposeLabel, "OCT"); + setupTransposeLabel(semitoneTransposeLabel, "SEMI"); + + setupCombo(octaveTransposeBox); + octaveTransposeBox.addItemList(juce::StringArray{"-3", "-2", "-1", "0", "+1", "+2", "+3"}, 1); + octaveTransposeAttach = std::make_unique( + processorRef.apvts, "octaveTranspose", octaveTransposeBox); + + setupCombo(semitoneTransposeBox); + semitoneTransposeBox.addItemList( + juce::StringArray{getSemitoneChoices()}, 1); + semitoneTransposeAttach = std::make_unique( + processorRef.apvts, "semitoneTranspose", semitoneTransposeBox); + addAndMakeVisible(vuMeter); addAndMakeVisible(waveformDisplay); addAndMakeVisible(spectrumAnalyzer); @@ -886,6 +907,11 @@ void MainContentComponent::resized() { patchLCD.setBounds(118, 8, 360, 28); prevPresetButton.setBounds(486, 8, 28, 28); nextPresetButton.setBounds(518, 8, 28, 28); + octaveTransposeLabel.setBounds(1112, 10, 30, 20); + octaveTransposeBox.setBounds(1142, 8, 64, 24); + semitoneTransposeLabel.setBounds(1214, 10, 32, 20); + semitoneTransposeBox.setBounds(1246, 8, 74, 24); + scaleLabel.setBounds(1520, 10, 42, 20); uiScaleBox.setBounds(1566, 8, 80, 24); diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 6f7c3f9..b3a5070 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -118,7 +118,9 @@ private: juce::Label osc1Label, osc2Label, filterLabel, envLabel, fEnvLabel, globalLabel; juce::Label fxLabel, lfoLabel, fx2Label; juce::Label scaleLabel; + juce::Label octaveTransposeLabel, semitoneTransposeLabel; + juce::ComboBox octaveTransposeBox, semitoneTransposeBox; juce::ComboBox osc1WaveBox; juce::Slider osc1OctKnob, osc1SemiKnob, osc1FineKnob, osc1LevelKnob; @@ -164,6 +166,7 @@ private: std::unique_ptr osc1OctAttach, osc1SemiAttach, osc1FineAttach, osc1LevelAttach; std::unique_ptr osc2OctAttach, osc2SemiAttach, osc2FineAttach, osc2LevelAttach, phaseOffsetAttach; std::unique_ptr osc1WaveAttach, osc2WaveAttach, filterTypeAttach; + std::unique_ptr octaveTransposeAttach, semitoneTransposeAttach; std::unique_ptr filterCutoffAttach, filterResAttach, filterEnvAmtAttach, keyTrackAttach; std::unique_ptr envAttackAttach, envDecayAttach, envSustainAttach, envReleaseAttach; std::unique_ptr fEnvAttackAttach, fEnvDecayAttach, fEnvSustainAttach, fEnvReleaseAttach; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 263b761..a95930d 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -133,6 +133,14 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create juce::ParameterID{"pitchBendRange", 1}, "Pitch Bend Range", juce::NormalisableRange(1.0f, 12.0f, 1.0f), 2.0f)); + // TRANSPOSE + layout.add(std::make_unique( + juce::ParameterID{"octaveTranspose", 1}, "Octave Transpose", + juce::StringArray{"-3", "-2", "-1", "0", "+1", "+2", "+3"}, 3)); + layout.add(std::make_unique( + juce::ParameterID{"semitoneTranspose", 1}, "Halftone Transpose", + getSemitoneChoices(), 0)); + // DISTORTION layout.add(std::make_unique( juce::ParameterID{"distType", 1}, "Dist Type", diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 5b25b1d..aa7de11 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -11,6 +11,8 @@ #include #include +const juce::StringArray& getSemitoneChoices(); + class ChromaFlockProcessor : public juce::AudioProcessor { public: ChromaFlockProcessor(); @@ -55,18 +57,20 @@ public: void noteOn(int midiNote, float velocity) { if (midiNote >= 0 && midiNote < 128) activeNotes[midiNote].store(true, std::memory_order_relaxed); - synth.noteOn(1, midiNote, velocity); + synth.noteOn(1, juce::jlimit(0, 127, midiNote + getTransposeSemitones()), velocity); } void noteOff(int midiNote) { if (midiNote >= 0 && midiNote < 128) activeNotes[midiNote].store(false, std::memory_order_relaxed); - synth.noteOff(1, midiNote, 0.0f, true); + synth.noteOff(1, juce::jlimit(0, 127, midiNote + getTransposeSemitones()), 0.0f, true); } bool isNoteActive(int midiNote) const { return midiNote >= 0 && midiNote < 128 && activeNotes[midiNote].load(std::memory_order_relaxed); } + int getTransposeSemitones() const; + std::atomic pitchBendValue{0.0f}; float getPitchBend() const { return pitchBendValue.load(std::memory_order_relaxed); }