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();
};