diff --git a/Magical8bitPlug2.jucer b/Magical8bitPlug2.jucer index a8b4e52..1e81946 100644 --- a/Magical8bitPlug2.jucer +++ b/Magical8bitPlug2.jucer @@ -50,6 +50,10 @@ file="Source/EnvelopeParamsComponent.cpp"/> + + + + gain) * velocity; // velocity value range is 0.0f-1.0f +} + + void BaseVoice::calculateAngleDelta() { auto cyclesPerSecond = MidiMessage::getMidiNoteInHertz (noteNumber); diff --git a/Source/BaseVoice.h b/Source/BaseVoice.h index 6acc79e..9a95a38 100644 --- a/Source/BaseVoice.h +++ b/Source/BaseVoice.h @@ -24,6 +24,8 @@ struct BaseVoice : public SynthesiserVoice void pitchWheelMoved (int) override {} void controllerMoved (int, int) override {} void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override; + + void changeNote (int midiNoteNumber, float velocity); virtual float voltageForAngle (double angle) = 0; virtual void onFrameAdvanced() {}; diff --git a/Source/BasicParamsComponent.cpp b/Source/BasicParamsComponent.cpp index 0a765e6..841450e 100644 --- a/Source/BasicParamsComponent.cpp +++ b/Source/BasicParamsComponent.cpp @@ -49,7 +49,7 @@ BasicParamsComponent::BasicParamsComponent (Magical8bitPlug2AudioProcessor& p, M addAndMakeVisible (gainSlider.get()); gainSlider->setName ("gain slider"); - gainSlider->setBounds (0, 32, 360, 32); + gainSlider->setBounds (0, 32, 380, 32); oscChoice.reset (new ChoiceComponent (p, "osc", "OSC Type")); addAndMakeVisible (oscChoice.get()); @@ -66,7 +66,7 @@ BasicParamsComponent::BasicParamsComponent (Magical8bitPlug2AudioProcessor& p, M polyNumberInput->setBounds (268, 4, 86, 24); - advancedSwitch.reset (new CheckBoxComponent (p, "isAdvancedPanelOpen_raw", "Show Advanced Options")); + advancedSwitch.reset (new CheckBoxComponent (p, "isAdvancedPanelOpen_raw", "Advanced Options")); addAndMakeVisible (advancedSwitch.get()); advancedSwitch->setName ("advanced option switch"); @@ -74,6 +74,13 @@ BasicParamsComponent::BasicParamsComponent (Magical8bitPlug2AudioProcessor& p, M addAndMakeVisible (colorSchemeChoice.get()); colorSchemeChoice->setName ("color selector"); + monoButton.reset (new juce::TextButton ("mono button")); + addAndMakeVisible (monoButton.get()); + monoButton->setButtonText (TRANS("mono")); + monoButton->addListener (this); + + monoButton->setBounds (360, 4, 54, 24); + //[UserPreSize] //[/UserPreSize] @@ -104,6 +111,7 @@ BasicParamsComponent::~BasicParamsComponent() polyNumberInput = nullptr; advancedSwitch = nullptr; colorSchemeChoice = nullptr; + monoButton = nullptr; //[Destructor]. You can add your own custom destruction code here.. @@ -125,7 +133,7 @@ void BasicParamsComponent::resized() //[UserPreResize] Add your own custom resize code here.. //[/UserPreResize] - advancedSwitch->setBounds (getWidth() - 240, 4, 240, 28); + advancedSwitch->setBounds (getWidth() - 180, 4, 180, 28); colorSchemeChoice->setBounds (getWidth() - 4 - 185, 32, 185, 28); //[UserResized] Add your own custom resize handling here.. //[/UserResized] @@ -140,6 +148,7 @@ void BasicParamsComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved) { //[UserSliderCode_polyNumberInput] -- add your slider handling code here.. processor.setupVoice(); + editor.resizeWholePanel(); //[/UserSliderCode_polyNumberInput] } @@ -147,6 +156,25 @@ void BasicParamsComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved) //[/UsersliderValueChanged_Post] } +void BasicParamsComponent::buttonClicked (juce::Button* buttonThatWasClicked) +{ + //[UserbuttonClicked_Pre] + //[/UserbuttonClicked_Pre] + + if (buttonThatWasClicked == monoButton.get()) + { + //[UserButtonCode_monoButton] -- add your button handler code here.. + polyNumberInput.get()->setValue(1); + return; + //[/UserButtonCode_monoButton] + } + + //[UserbuttonClicked_Post] + colorSchemeChoice->setVisible (buttonThatWasClicked->getToggleState()); + editor.resizeWholePanel(); + //[/UserbuttonClicked_Post] +} + //[MiscUserCode] You can add your own definitions of your custom methods or any other code here... @@ -156,12 +184,6 @@ void BasicParamsComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged) editor.resized(); printf ("setup voice!!\n"); } - -void BasicParamsComponent::buttonClicked (Button* buttonThatWasClicked) -{ - colorSchemeChoice->setVisible (buttonThatWasClicked->getToggleState()); - editor.resizeWholePanel(); -} //[/MiscUserCode] @@ -175,8 +197,7 @@ void BasicParamsComponent::buttonClicked (Button* buttonThatWasClicked) BEGIN_JUCER_METADATA @@ -187,7 +208,7 @@ BEGIN_JUCER_METADATA focusDiscardsChanges="0" fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" italic="0" justification="33"/> + virtualName="" explicitFocusOrder="0" pos="0Rr 4 180 28" class="CheckBoxComponent" + params="p, "isAdvancedPanelOpen_raw", "Advanced Options""/> + END_JUCER_METADATA diff --git a/Source/BasicParamsComponent.h b/Source/BasicParamsComponent.h index 732101c..9ab13a4 100644 --- a/Source/BasicParamsComponent.h +++ b/Source/BasicParamsComponent.h @@ -40,8 +40,8 @@ class Magical8bitPlug2AudioProcessorEditor; */ class BasicParamsComponent : public Component, public ComboBox::Listener, - public Button::Listener, - public juce::Slider::Listener + public juce::Slider::Listener, + public juce::Button::Listener { public: //============================================================================== @@ -51,12 +51,12 @@ public: //============================================================================== //[UserMethods] -- You can add your own custom methods in this section. void comboBoxChanged (ComboBox* comboBoxThatHasChanged) override; - void buttonClicked (Button* buttonThatWasClicked) override; //[/UserMethods] void paint (juce::Graphics& g) override; void resized() override; void sliderValueChanged (juce::Slider* sliderThatWasMoved) override; + void buttonClicked (juce::Button* buttonThatWasClicked) override; @@ -75,6 +75,7 @@ private: std::unique_ptr polyNumberInput; std::unique_ptr advancedSwitch; std::unique_ptr colorSchemeChoice; + std::unique_ptr monoButton; //============================================================================== diff --git a/Source/ChoiceComponent.cpp b/Source/ChoiceComponent.cpp index 72afed8..1bb7b6e 100644 --- a/Source/ChoiceComponent.cpp +++ b/Source/ChoiceComponent.cpp @@ -115,7 +115,7 @@ void ChoiceComponent::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged) if (comboBoxThatHasChanged == comboBox.get()) { //[UserComboBoxCode_comboBox] -- add your combo box handling code here.. - printf ("value = %d\n", comboBoxThatHasChanged->getSelectedId()); + // printf ("value = %d\n", comboBoxThatHasChanged->getSelectedId()); //[/UserComboBoxCode_comboBox] } diff --git a/Source/CustomSynth.cpp b/Source/CustomSynth.cpp new file mode 100644 index 0000000..a39c966 --- /dev/null +++ b/Source/CustomSynth.cpp @@ -0,0 +1,147 @@ +/* + ============================================================================== + + CustomSynth.cpp + Created: 17 May 2021 11:29:59pm + Author: 除村武志 + + ============================================================================== +*/ + +#include "CustomSynth.h" +#include "BaseVoice.h" +#include "TonalVoice.h" +#include "PluginProcessor.h" + +CustomSynth::CustomSynth(Magical8bitPlug2AudioProcessor& p) : processor(p) {} + +TonalVoice* CustomSynth::getVoiceIfShouldProcessInMonoMode() { + if (!processor.settingRefs.isMonophonic()) { + return nullptr; + } + if (processor.settingRefs.monophonicBehavior() == kNonLegato) { + return nullptr; + } + + // Try casting into TonalVoice* and return it if success(otherwise returns nullptr) + return dynamic_cast(voices.getFirst()); +} + +void CustomSynth::noteOn(int midiChannel, int midiNoteNumber, float velocity) { + TonalVoice *voice = getVoiceIfShouldProcessInMonoMode(); + + if (voice == nullptr) { + Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity); + return; + } + + // Mono + + // Start thread guard + const ScopedLock sl (lock); + + if (voice->isKeyDown()) { + // Already key on + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + voice->addLegatoNote(midiNoteNumber, velocity); + break; + case kArpeggioUp: + voice->addArpeggioNoteAscending(midiNoteNumber); + break; + case kArpeggioDown: + voice->addArpeggioNoteDescending(midiNoteNumber); + break; + default: + // no-op + break; + } + } else { + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + // start note and set legato mode + Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity); + voice->setLegatoMode(*(processor.settingRefs.portamentoTime), midiChannel); + break; + case kArpeggioUp: + case kArpeggioDown: + // start note and calc arpeggio interval + Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity); + voice->setArpeggioMode(calcArpeggioInterval(), midiChannel); + break; + default: + // no-op + break; + } + } +} + +double CustomSynth::calcArpeggioInterval() { + switch (processor.settingRefs.apreggioIntervalType()) { + case k1frame: + return 1.0 / 60.0; + case k2frames: + return 1.0 / 30.0; + case k3frames: + return 1.0 / 20.0; + case k64th: + return 240.0 / (processor.getCurrentBPM() * 64); + case k48th: + return 240.0 / (processor.getCurrentBPM() * 48); + case k32nd: + return 240.0 / (processor.getCurrentBPM() * 32); + case k24th: + return 240.0 / (processor.getCurrentBPM() * 24); + case kSlider: + return *(processor.settingRefs.arpeggioIntervalSliderValue); + default: + return 1.0 / 60.0; + break; + } +} + +void CustomSynth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) { + TonalVoice *voice = getVoiceIfShouldProcessInMonoMode(); + + if (voice == nullptr) { + Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff); + return; + } + + // mono + + // Start thread guard + const ScopedLock sl (lock); + + if (!voice->isKeyDown()) { + // key is already off + return; + } + + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + { + int numBuffer = voice->removeLegatoNote(midiNoteNumber); + if (numBuffer < 1) { + Synthesiser::noteOff(voice->primaryMidiChannel, voice->getCurrentlyPlayingNote(), velocity, allowTailOff); + } + } + break; + case kArpeggioUp: + case kArpeggioDown: + { + int numBuffer = voice->removeArpeggioNote(midiNoteNumber); + if (numBuffer < 1) { + Synthesiser::noteOff(voice->primaryMidiChannel, voice->getCurrentlyPlayingNote(), velocity, allowTailOff); + } + } + break; + default: + break; + } +} + +void CustomSynth::allNotesOff (const int midiChannel, const bool allowTailOff) { + Synthesiser::allNotesOff(midiChannel, allowTailOff); +} + diff --git a/Source/CustomSynth.h b/Source/CustomSynth.h new file mode 100644 index 0000000..d339295 --- /dev/null +++ b/Source/CustomSynth.h @@ -0,0 +1,29 @@ +/* + ============================================================================== + + CustomSynth.h + Created: 17 May 2021 11:29:59pm + Author: 除村武志 + + ============================================================================== +*/ + +#pragma once +#include "../JuceLibraryCode/JuceHeader.h" + +class Magical8bitPlug2AudioProcessor; +class TonalVoice; + +class CustomSynth : public Synthesiser { +public: + CustomSynth(Magical8bitPlug2AudioProcessor& p); + + void noteOn(int midiChannel, int midiNoteNumber, float velocity) override; + void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) override; + void allNotesOff (const int midiChannel, const bool allowTailOff) override; + +private: + TonalVoice* getVoiceIfShouldProcessInMonoMode(); + double calcArpeggioInterval(); + Magical8bitPlug2AudioProcessor& processor; +}; diff --git a/Source/MonophonicComponent.cpp b/Source/MonophonicComponent.cpp new file mode 100644 index 0000000..57f61b1 --- /dev/null +++ b/Source/MonophonicComponent.cpp @@ -0,0 +1,200 @@ +/* + ============================================================================== + + This is an automatically generated GUI class created by the Projucer! + + Be careful when adding custom code to these files, as only the code within + the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded + and re-saved. + + Created with Projucer version: 6.0.8 + + ------------------------------------------------------------------------------ + + The Projucer is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited. + + ============================================================================== +*/ + +//[Headers] You can add your own extra header files here... +//[/Headers] + +#include "MonophonicComponent.h" + + +//[MiscUserDefs] You can add your own user definitions and misc code here... +//[/MiscUserDefs] + +//============================================================================== +MonophonicComponent::MonophonicComponent (Magical8bitPlug2AudioProcessor& p) + : processor(p) +{ + //[Constructor_pre] You can add your own custom stuff here.. + //[/Constructor_pre] + + label.reset (new juce::Label ("label", + TRANS("Monophonic Options"))); + addAndMakeVisible (label.get()); + label->setFont (juce::Font (17.00f, juce::Font::plain).withTypefaceStyle ("Regular")); + label->setJustificationType (juce::Justification::centredLeft); + label->setEditable (false, false, false); + label->setColour (juce::TextEditor::textColourId, juce::Colours::black); + label->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); + + label->setBounds (0, 4, 150, 22); + + behaviorChoice.reset (new ChoiceComponent (p, "monophonicBehavior_raw", "Behavior")); + addAndMakeVisible (behaviorChoice.get()); + behaviorChoice->setName ("behavior selector"); + + behaviorChoice->setBounds (0, 28, 224, 26); + + intervalChoice.reset (new ChoiceComponent (p, "arpeggioIntervalType_raw", "Interval")); + addAndMakeVisible (intervalChoice.get()); + intervalChoice->setName ("interval selector"); + + intervalChoice->setBounds (228, 28, 185, 28); + + intervalSlider.reset (new juce::Slider ("interval slider")); + addAndMakeVisible (intervalSlider.get()); + intervalSlider->setRange (0, 10, 0.01); + intervalSlider->setSliderStyle (juce::Slider::LinearHorizontal); + intervalSlider->setTextBoxStyle (juce::Slider::TextBoxRight, false, 50, 20); + + portamentoSlider.reset (new SliderComponent (p, "portamentoTime", "Portamento")); + addAndMakeVisible (portamentoSlider.get()); + portamentoSlider->setName ("portamento slider"); + + + //[UserPreSize] + //[/UserPreSize] + + setSize (640, 82); + + + //[Constructor] You can add your own custom stuff here.. + attc.reset (new SliderAttachment (p.parameters, "arpeggioIntervalSliderValue", *intervalSlider)); + behaviorChoice->setListener(this); + intervalChoice->setListener(this); + updateVisibility(); + //[/Constructor] +} + +MonophonicComponent::~MonophonicComponent() +{ + //[Destructor_pre]. You can add your own custom destruction code here.. + attc.reset(); + //[/Destructor_pre] + + label = nullptr; + behaviorChoice = nullptr; + intervalChoice = nullptr; + intervalSlider = nullptr; + portamentoSlider = nullptr; + + + //[Destructor]. You can add your own custom destruction code here.. + //[/Destructor] +} + +//============================================================================== +void MonophonicComponent::paint (juce::Graphics& g) +{ + //[UserPrePaint] Add your own custom painting code here.. + //[/UserPrePaint] + + //[UserPaint] Add your own custom painting code here.. + //[/UserPaint] +} + +void MonophonicComponent::resized() +{ + //[UserPreResize] Add your own custom resize code here.. + //[/UserPreResize] + + intervalSlider->setBounds (getWidth() - (getWidth() - 420), 28, getWidth() - 420, 24); + portamentoSlider->setBounds (getWidth() - proportionOfWidth (0.5000f), 28, proportionOfWidth (0.5000f), 28); + //[UserResized] Add your own custom resize handling here.. + //[/UserResized] +} + + + +//[MiscUserCode] You can add your own definitions of your custom methods or any other code here... +void MonophonicComponent::comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) { + updateVisibility(); +} + +void MonophonicComponent::updateVisibility() +{ + portamentoSlider->setVisible(false); + intervalSlider->setVisible(false); + intervalChoice->setVisible(false); + + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + portamentoSlider->setVisible(true); + break; + case kArpeggioUp: + case kArpeggioDown: + intervalChoice->setVisible(true); + switch (processor.settingRefs.apreggioIntervalType()) { + case kSlider: + intervalSlider->setVisible(true); + break; + default: + break; + } + break; + default: + break; + } +} +//[/MiscUserCode] + + +//============================================================================== +#if 0 +/* -- Projucer information section -- + + This is where the Projucer stores the metadata that describe this GUI layout, so + make changes in here at your peril! + +BEGIN_JUCER_METADATA + + + + + +END_JUCER_METADATA +*/ +#endif + + +//[EndFile] You can add extra defines here... +//[/EndFile] + diff --git a/Source/MonophonicComponent.h b/Source/MonophonicComponent.h new file mode 100644 index 0000000..2c653e2 --- /dev/null +++ b/Source/MonophonicComponent.h @@ -0,0 +1,78 @@ +/* + ============================================================================== + + This is an automatically generated GUI class created by the Projucer! + + Be careful when adding custom code to these files, as only the code within + the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded + and re-saved. + + Created with Projucer version: 6.0.8 + + ------------------------------------------------------------------------------ + + The Projucer is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited. + + ============================================================================== +*/ + +#pragma once + +//[Headers] -- You can add your own extra header files here -- +#include +#include "SliderComponent.h" +#include "ChoiceComponent.h" +//[/Headers] + + + +//============================================================================== +/** + //[Comments] + An auto-generated component, created by the Projucer. + + Describe your class and how it works here! + //[/Comments] +*/ +class MonophonicComponent : public Component, + public ComboBox::Listener +{ +public: + //============================================================================== + MonophonicComponent (Magical8bitPlug2AudioProcessor& p); + ~MonophonicComponent() override; + + + //============================================================================== + //[UserMethods] -- You can add your own custom methods in this section. + void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override; + void updateVisibility(); + //[/UserMethods] + + void paint (juce::Graphics& g) override; + void resized() override; + + + +private: + //[UserVariables] -- You can add your own custom variables in this section. + std::unique_ptr attc; + Magical8bitPlug2AudioProcessor& processor; + //[/UserVariables] + + //============================================================================== + std::unique_ptr label; + std::unique_ptr behaviorChoice; + std::unique_ptr intervalChoice; + std::unique_ptr intervalSlider; + std::unique_ptr portamentoSlider; + + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MonophonicComponent) +}; + +//[EndFile] You can add extra defines here... +//[/EndFile] + diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index d165238..dfc759e 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -11,6 +11,7 @@ #include "PluginProcessor.h" #include "PluginEditor.h" #include "AdvancedParamsComponent.h" +#include "MonophonicComponent.h" #include "PulseParamsComponent.h" #include "BasicParamsComponent.h" #include "EnvelopeParamsComponent.h" @@ -25,10 +26,15 @@ Magical8bitPlug2AudioProcessorEditor::Magical8bitPlug2AudioProcessorEditor (Magi { // Make sure that before the constructor has finished, you've set the // editor's size to whatever you need it to be. + isComponentsReady = false; + applyLookAndFeel(); basicCompo.reset (new BasicParamsComponent (p, *this)); addAndMakeVisible (basicCompo.get()); + + monoCompo.reset (new MonophonicComponent (p)); + addAndMakeVisible(monoCompo.get()); envCompo.reset (new EnvelopeParamsComponent (p)); addAndMakeVisible (envCompo.get()); @@ -54,7 +60,7 @@ Magical8bitPlug2AudioProcessorEditor::Magical8bitPlug2AudioProcessorEditor (Magi (p.parameters.getParameter ("isVolumeSequenceEnabled_raw"))->addListener (this); (p.parameters.getParameter ("isDutySequenceEnabled_raw"))->addListener (this); - + isComponentsReady = true; resizeWholePanel(); } @@ -130,6 +136,9 @@ struct const int basCompoHeight = componentMargin * 2 + genericControlHeight * 2; + const int monoCompoHeight = componentMargin * 2 + + indexHeight + + customEnvelopeHeight; const int toneSpecificControlHeight = componentMargin * 2 + indexHeight + genericControlHeight; @@ -148,30 +157,25 @@ struct const int advCompoHeight = componentMargin * 2 + indexHeight + customEnvelopeHeight * 3; - const int totalHeight (bool isAdvOptOn) + const int totalHeight (bool isAdvOptOn, bool isMono) { + int retHeight = topMargin + + basCompoHeight + + sectionSeparatorHeight + + toneSpecificControlHeight + + envCompoHeight + + bendCompoHeight + + bottomMargin; if (isAdvOptOn) { - return topMargin - + basCompoHeight - + sectionSeparatorHeight - + toneSpecificControlHeight - + envCompoHeight - + bendCompoHeight - + sectionSeparatorHeight - + advCompoHeight - + bottomMargin; + retHeight += sectionSeparatorHeight + + advCompoHeight; } - else + if (isMono) { - return topMargin - + basCompoHeight - + sectionSeparatorHeight - + toneSpecificControlHeight - + envCompoHeight - + bendCompoHeight - + bottomMargin; + retHeight += monoCompoHeight; } + return retHeight; } } sizes; @@ -204,27 +208,38 @@ void Magical8bitPlug2AudioProcessorEditor::resized() int y = sizes.topMargin; int w = sizes.halfComponentWidth; basicCompo->setBounds (x, y, sizes.fullComponentWidth, sizes.basCompoHeight); + + y += sizes.basCompoHeight + sizes.sectionSeparatorHeight; + + // + // Monophonic + // + if (processor.settingRefs.isMonophonic()) { + monoCompo->setBounds(x, y, sizes.fullComponentWidth, sizes.monoCompoHeight); + y += sizes.monoCompoHeight; + } + + int y1 = y; + int y2 = y; // // Main part - Left // - y = sizes.topMargin + sizes.basCompoHeight + sizes.sectionSeparatorHeight; - pulCompo->setBounds (x, y, w, sizes.toneSpecificControlHeight); - noiCompo->setBounds (x, y, w, sizes.toneSpecificControlHeight); - y += sizes.toneSpecificControlHeight; + pulCompo->setBounds (x, y1, w, sizes.toneSpecificControlHeight); + noiCompo->setBounds (x, y1, w, sizes.toneSpecificControlHeight); + y1 += sizes.toneSpecificControlHeight; - envCompo->setBounds (x, y, w, sizes.envCompoHeight); - y += sizes.envCompoHeight; + envCompo->setBounds (x, y1, w, sizes.envCompoHeight); + y1 += sizes.envCompoHeight; - bendCompo->setBounds (x, y, w, sizes.bendCompoHeight); - y += sizes.bendCompoHeight; + bendCompo->setBounds (x, y1, w, sizes.bendCompoHeight); + y1 += sizes.bendCompoHeight; // // Main part - Right // x = sizes.leftMargin + sizes.halfComponentWidth + sizes.verticalSeparatorWidth; - int y2 = sizes.topMargin + sizes.basCompoHeight + sizes.sectionSeparatorHeight; sweepCompo->setBounds (x, y2, w, sizes.sweepCompoHeight); y2 += sizes.sweepCompoHeight; @@ -236,7 +251,7 @@ void Magical8bitPlug2AudioProcessorEditor::resized() // Advanced part // x = sizes.leftMargin; - int y3 = y > y2 ? y : y2; + int y3 = y1 > y2 ? y1 : y2; y3 += sizes.sectionSeparatorHeight; advCompo->setBounds (x, y3, sizes.fullComponentWidth, sizes.advCompoHeight); @@ -260,6 +275,8 @@ void Magical8bitPlug2AudioProcessorEditor::resized() noiCompo->setVisible (false); break; } + + monoCompo->setVisible(processor.settingRefs.isMonophonic()); // // Enable/Disable @@ -272,7 +289,10 @@ void Magical8bitPlug2AudioProcessorEditor::resized() void Magical8bitPlug2AudioProcessorEditor::resizeWholePanel() { - setSize (sizes.totalWidth, sizes.totalHeight (processor.settingRefs.isAdvancedPanelOpen())); + if (!isComponentsReady) { + return; + } + setSize (sizes.totalWidth, sizes.totalHeight (processor.settingRefs.isAdvancedPanelOpen(), processor.settingRefs.isMonophonic())); } void Magical8bitPlug2AudioProcessorEditor::parameterValueChanged (int parameterIndex, float newValue) diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 385b278..1677ceb 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -13,6 +13,7 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "PluginProcessor.h" class AdvancedParamsComponent; +class MonophonicComponent; class PulseParamsComponent; class BasicParamsComponent; class EnvelopeParamsComponent; @@ -44,6 +45,7 @@ private: Magical8bitPlug2AudioProcessor& processor; std::unique_ptr basicCompo; + std::unique_ptr monoCompo; std::unique_ptr envCompo; std::unique_ptr advCompo; std::unique_ptr pulCompo; @@ -51,6 +53,8 @@ private: std::unique_ptr bendCompo; std::unique_ptr sweepCompo; std::unique_ptr vibCompo; + + bool isComponentsReady; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Magical8bitPlug2AudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 27b41f7..dca5428 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -66,11 +66,18 @@ Magical8bitPlug2AudioProcessor::Magical8bitPlug2AudioProcessor() 0.5f), //skew 0.0f), //default // - // Arpeggio + // Monophonic // - std::make_unique ("isArpeggioEnabled_raw", "Enabled", false), - std::make_unique ("arpeggioTime", "Time", 0.0f, 0.3f, 0.033f), - std::make_unique ("arpeggioDirection", "Direction", StringArray ({"up", "down"}), 0), + std::make_unique ("monophonicBehavior_raw", "Behavior", StringArray ({"Legato", "Arpeggio Up", "Arpeggio Down", "Non-legato"}), 0), + std::make_unique ("arpeggioIntervalType_raw", "Interval", StringArray ({"1 frame", "2 frames", "3 frames", "96th", "64th", "48th", "32nd", "24th", "Slider"}), 0), + std::make_unique ("arpeggioIntervalSliderValue", //ID + "Interval", //name + NormalisableRange (0.001f, //min + 0.3f, //max + 0.001f, //step + 0.5f), //skew + 0.001f), //default + std::make_unique ("portamentoTime", "Portamento Time", 0.0f, 1.0f, 0.0f), // // Bend // @@ -118,6 +125,7 @@ Magical8bitPlug2AudioProcessor::Magical8bitPlug2AudioProcessor() } ) , settingRefs (¶meters) +, synth(*this) #ifndef JucePlugin_PreferredChannelConfigurations , AudioProcessor (BusesProperties() #if ! JucePlugin_IsMidiEffect @@ -175,6 +183,17 @@ void Magical8bitPlug2AudioProcessor::setupVoice() } } +double Magical8bitPlug2AudioProcessor::getCurrentBPM() +{ + auto ph = getPlayHead(); + if (ph == NULL) { + return 120.0; + } + juce::AudioPlayHead::CurrentPositionInfo result; + ph->getCurrentPosition(result); + + return result.bpm > 0 ? result.bpm : 120.0; +} //============================================================================== const String Magical8bitPlug2AudioProcessor::getName() const @@ -281,67 +300,13 @@ bool Magical8bitPlug2AudioProcessor::isBusesLayoutSupported (const BusesLayout& void Magical8bitPlug2AudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { - - synth.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples()); // [5] - - /* - buffer.clear(); - MidiBuffer processedMidi; - int time; - MidiMessage m; - for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);) - { - if (m.isNoteOn()) - { - uint8 newVel = (uint8)noteOnVel; - m = MidiMessage::noteOn(m.getChannel(), m.getNoteNumber(), newVel); - } - else if (m.isNoteOff()) - { - } - else if (m.isAftertouch()) - { - } - else if (m.isPitchWheel()) - { - } - processedMidi.addEvent (m, time); - } - midiMessages.swapWith (processedMidi); - - - ScopedNoDenormals noDenormals; - auto totalNumInputChannels = getTotalNumInputChannels(); - auto totalNumOutputChannels = getTotalNumOutputChannels(); - - // In case we have more outputs than inputs, this code clears any output - // channels that didn't contain input data, (because these aren't - // guaranteed to be empty - they may contain garbage). - // This is here to avoid people getting screaming feedback - // when they first compile a plugin, but obviously you don't need to keep - // this code if your algorithm always overwrites all the output channels. - for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) - buffer.clear (i, 0, buffer.getNumSamples()); - - // This is the place where you'd normally do the guts of your plugin's - // audio processing... - // Make sure to reset the state if your inner loop is processing - // the samples and the outer loop is handling the channels. - // Alternatively, you can process the samples with the channels - // interleaved by keeping the same state. - for (int channel = 0; channel < totalNumInputChannels; ++channel) - { - auto* channelData = buffer.getWritePointer (channel); - - // ..do something to the data... - } - */ + synth.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples()); } //============================================================================== bool Magical8bitPlug2AudioProcessor::hasEditor() const { - return true; // (change this to false if you choose to not supply an editor) + return true; } AudioProcessorEditor* Magical8bitPlug2AudioProcessor::createEditor() diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 8a9e9da..efc9ca1 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -13,6 +13,7 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "Settings.h" #include "Voices.h" +#include "CustomSynth.h" //============================================================================== @@ -71,13 +72,14 @@ public: //============================================================================== void setupVoice(); + double getCurrentBPM(); AudioProcessorValueTreeState parameters; SettingRefs settingRefs; private: //============================================================================== - Synthesiser synth; + CustomSynth synth; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Magical8bitPlug2AudioProcessor) }; diff --git a/Source/Settings.h b/Source/Settings.h index 738aa72..5afbd4d 100644 --- a/Source/Settings.h +++ b/Source/Settings.h @@ -35,6 +35,32 @@ struct PluginSettings double bendRange = 2; }; +//--------------------------------------------- +// +// Monophonic Options +// +//--------------------------------------------- +enum MonophonicBehavior +{ + kLegato = 0, + kArpeggioUp, + kArpeggioDown, + kNonLegato, +}; + +enum ArpeggioIntervalType +{ + k1frame = 0, + k2frames, + k3frames, + k96th, + k64th, + k48th, + k32nd, + k24th, + kSlider, +}; + //--------------------------------------------- // // Tone Specific @@ -95,10 +121,11 @@ struct SettingRefs float* decay = nullptr; float* suslevel = nullptr; float* release = nullptr; - // Arpeggio - float* isArpeggioEnabled_raw = nullptr; - float* arpeggioTime = nullptr; - float* arpeggioDirection = nullptr; + // Monophonic + float* monophonicBehavior_raw = nullptr; + float* arpeggioIntervalType_raw = nullptr; + float* arpeggioIntervalSliderValue = nullptr; + float* portamentoTime = nullptr; // Bend float* bendRange = nullptr; // Vibrato @@ -139,10 +166,10 @@ struct SettingRefs // accessors // int oscillatorType() { return (int) (*osc); } + bool isMonophonic() { return (int)(*maxPoly) == 1; } bool isAdvancedPanelOpen() { return *isAdvancedPanelOpen_raw > 0.5; } ColorSchemeType colorSchemeType() { return (ColorSchemeType) ((int) (*colorScheme)); } - bool isArpeggioEnabled() { return *isArpeggioEnabled_raw > 0.5; } NoiseAlgorithm noiseAlgorithm() { return (NoiseAlgorithm) ((int) (*noiseAlgorithm_raw)); } bool vibratoIgnoresWheel() { return *vibratoIgnoresWheel_raw > 0.5; } @@ -151,7 +178,8 @@ struct SettingRefs bool isPitchSequenceEnabled() { return *isPitchSequenceEnabled_raw > 0.5; } bool isDutySequenceEnabled() { return *isDutySequenceEnabled_raw > 0.5; } PitchSequenceMode pitchSequenceMode() { return (PitchSequenceMode) ((int) (*pitchSequenceMode_raw)); } - + MonophonicBehavior monophonicBehavior() { return (MonophonicBehavior) ((int) (*monophonicBehavior_raw)); } + ArpeggioIntervalType apreggioIntervalType() { return (ArpeggioIntervalType) ((int) (*arpeggioIntervalType_raw)); } // // constructor @@ -170,10 +198,11 @@ struct SettingRefs decay = (float*) parameters->getRawParameterValue ("decay"); suslevel = (float*) parameters->getRawParameterValue ("suslevel"); release = (float*) parameters->getRawParameterValue ("release"); - // Arpeggio - isArpeggioEnabled_raw = (float*) parameters->getRawParameterValue ("isArpeggioEnabled_raw"); - arpeggioTime = (float*) parameters->getRawParameterValue ("arpeggioTime"); - arpeggioDirection = (float*) parameters->getRawParameterValue ("arpeggioDirection"); + // Monophonic + monophonicBehavior_raw = (float*) parameters->getRawParameterValue ("monophonicBehavior_raw"); + arpeggioIntervalType_raw = (float*) parameters->getRawParameterValue ("arpeggioIntervalType_raw"); + arpeggioIntervalSliderValue = (float*) parameters->getRawParameterValue ("arpeggioIntervalSliderValue"); + portamentoTime = (float*) parameters->getRawParameterValue ("portamentoTime"); // Bend bendRange = (float*) parameters->getRawParameterValue ("bendRange"); // Vibrato diff --git a/Source/TonalVoice.cpp b/Source/TonalVoice.cpp index f0471bc..e98e3a9 100644 --- a/Source/TonalVoice.cpp +++ b/Source/TonalVoice.cpp @@ -30,6 +30,15 @@ void TonalVoice::startNote (int midiNoteNumber, float velocity, SynthesiserSound float time = * (settingRefs->sweepTime); currentAutoBendAmount = iniPitch; autoBendDelta = -1.0 * iniPitch / (time * getSampleRate()); + + portamentoTime = 0; + currentArpeggioFrame = 0; + arpeggioFrameTimer = 0; + arpeggioFrameLength = 0; + currentNumNoteBuffer = 0; + for (int i=0; ivibratoIgnoresWheel() ? 1.0 : currentModWheelValue; double vibratoAmount = * (settingRefs->vibratoDepth) * sin (getVibratoPhase()) * byWheel; double noteNoInDouble = noteNumber @@ -90,6 +99,108 @@ void TonalVoice::controllerMoved (int type, int amount) } } + +void TonalVoice::setLegatoMode(double time, int midiCh) { + portamentoTime = time; + primaryMidiChannel = midiCh; +} + +// The interface says "add" but the implementation is just using the latest value. +// It is because the original intension was to keep all the pressing keys and choose the apropriate one with certain algorithm +void TonalVoice::addLegatoNote (int midiNoteNumber, float velocity) { + if (currentNumNoteBuffer >= NUMNOTEBUFFER) { + return; + } + + int previousNoteNo = noteNumber; + BaseVoice::changeNote(midiNoteNumber, velocity); + + // Portamento implementation is just applying auto bend + if (portamentoTime > 0) { + currentAutoBendAmount = (double)(previousNoteNo - midiNoteNumber); + autoBendDelta = -1.0 * currentAutoBendAmount / (portamentoTime * getSampleRate()); + } +} + +int TonalVoice::removeLegatoNote(int midiNoteNumber) { + if (midiNoteNumber == noteNumber) { + return 0; + } else { + return 1; + } +} + + +void TonalVoice::setArpeggioMode(double interval, int midiCh) +{ + arpeggioFrameLength = interval; + arpeggioFrameTimer = 0; + currentArpeggioFrame = 0; + currentNumNoteBuffer = 1; + noteBuffer[0] = noteNumber; + primaryMidiChannel = midiCh; +} + +void TonalVoice::addArpeggioNoteAscending(int midiNoteNumber) +{ + if (currentNumNoteBuffer >= NUMNOTEBUFFER) { + return; + } + int i; + for (i = 0; i midiNoteNumber) break; + } + + pushNoteBuffer(i, midiNoteNumber); + + currentNumNoteBuffer++; +} + +void TonalVoice::addArpeggioNoteDescending(int midiNoteNumber) +{ + if (currentNumNoteBuffer >= NUMNOTEBUFFER) { + return; + } + int i; + for (i = 0; iindex; i--) { + noteBuffer[i] = noteBuffer[i-1]; + } + noteBuffer[index] = value; +} + +void TonalVoice::shiftNoteBuffer(int index) { + for (int i=index; i 0) { // Arpeggio mode is on + arpeggioFrameTimer += 1.0 / getSampleRate(); + + if (arpeggioFrameTimer >= arpeggioFrameLength) + { // Arpeggio phase advances + if (!isInReleasePhase()) { + // Process the retirements first + for(int j = 0; j= currentNumNoteBuffer) { + currentArpeggioFrame = 0; + } + } /* else { + In cases like retirement happens, or new arpeggio note is added + the note at currentArpeggioFrame is no longer the same, + so currentArpeggioFrame doesn't have to be updated. + Moreover, updating currentArpeggioFrame in this case + often results in sounding the same note over two frames. + } */ + + noteNumber = noteBuffer[currentArpeggioFrame]; + + while (arpeggioFrameTimer >= arpeggioFrameLength) + { + arpeggioFrameTimer -= arpeggioFrameLength; + } + } + } + + }; + +bool TonalVoice::isInReleasePhase() { + if (settingRefs->isVolumeSequenceEnabled()) { + return settingRefs->volumeSequence.isInRelease(currentVolumeSequenceFrame); + } else { + return envelopePhase == kEnvelopePhaseR; + } +} diff --git a/Source/TonalVoice.h b/Source/TonalVoice.h index 5146fb7..2ca091a 100644 --- a/Source/TonalVoice.h +++ b/Source/TonalVoice.h @@ -11,9 +11,10 @@ #pragma once #include "BaseVoice.h" +#define NUMNOTEBUFFER 10 + struct TonalVoice : public BaseVoice // The base for Pulse and Triangle { - TonalVoice (SettingRefs* sRefs); float voltageForAngle (double angle) override = 0; @@ -35,6 +36,21 @@ struct TonalVoice : public BaseVoice // The base for Pulse and Triangle // Custom Pitch/Note states int currentPitchSequenceFrame = 0; + + // Legato/Arpeggio + int noteBuffer[NUMNOTEBUFFER]; + int currentNumNoteBuffer = 0; + int primaryMidiChannel = 1; + + // Legato + double portamentoTime = 0; + + // Arpeggio + int currentArpeggioFrame = 0; + double arpeggioFrameTimer = 0; + double arpeggioFrameLength = 0; // Unit: seconds. Set non-zero value to enable arpeggio + int retireBuffer[NUMNOTEBUFFER]; + int currentNumRetireBuffer = 0; void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int currentPitchWheelPosition) override; @@ -43,7 +59,24 @@ struct TonalVoice : public BaseVoice // The base for Pulse and Triangle void pitchWheelMoved (int) override; void controllerMoved (int, int) override; + void setLegatoMode(double time, int midiCh); + void addLegatoNote (int midiNoteNumber, float velocity); + int removeLegatoNote(int midiNoteNumber); + + void setArpeggioMode(double interval, int midiCh); + void addArpeggioNoteAscending(int midiNoteNumber); + void addArpeggioNoteDescending(int midiNoteNumber); + int removeArpeggioNote(int midiNoteNumber); + + void pushNoteBuffer(int index, int value); + void shiftNoteBuffer(int index); + double noteNoToHeltzDouble (double noteNoInDouble, const double frequencyOfA = 440); void onFrameAdvanced() override; + + bool isArpeggioEnabled() { + return arpeggioFrameLength > 0; + } + bool isInReleasePhase(); };