Merge pull request #19 from yokemura/feature/monoMode

Feature/mono mode
This commit is contained in:
Takeshi Yokemura 2022-02-05 15:58:16 +09:00 committed by GitHub
commit 098b268001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 847 additions and 121 deletions

View File

@ -50,6 +50,10 @@
file="Source/EnvelopeParamsComponent.cpp"/>
<FILE id="zBFywa" name="EnvelopeParamsComponent.h" compile="0" resource="0"
file="Source/EnvelopeParamsComponent.h"/>
<FILE id="SHaDYI" name="MonophonicComponent.cpp" compile="1" resource="0"
file="Source/MonophonicComponent.cpp"/>
<FILE id="cyIRRW" name="MonophonicComponent.h" compile="0" resource="0"
file="Source/MonophonicComponent.h"/>
<FILE id="RF89Wr" name="NoiseParamsComponent.cpp" compile="1" resource="0"
file="Source/NoiseParamsComponent.cpp"/>
<FILE id="jPVIvX" name="NoiseParamsComponent.h" compile="0" resource="0"
@ -68,6 +72,8 @@
file="Source/VibratoParamsComponent.h"/>
</GROUP>
<GROUP id="{630C27C7-03AA-CFC6-2FC4-4CE2DBE3640A}" name="Source">
<FILE id="LHv5E3" name="CustomSynth.cpp" compile="1" resource="0" file="Source/CustomSynth.cpp"/>
<FILE id="kpCPIX" name="CustomSynth.h" compile="0" resource="0" file="Source/CustomSynth.h"/>
<FILE id="H2Vgbj" name="FrameSequenceParseErrors.cpp" compile="1" resource="0"
file="Source/FrameSequenceParseErrors.cpp"/>
<FILE id="Gwt2vB" name="FrameSequenceParseErrors.h" compile="0" resource="0"

View File

@ -218,6 +218,13 @@ void BaseVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSampl
}
}
void BaseVoice::changeNote (int midiNoteNumber, float velocity) {
noteNumber = midiNoteNumber;
ampByVelocityAndGain = * (settingRefs->gain) * velocity; // velocity value range is 0.0f-1.0f
}
void BaseVoice::calculateAngleDelta()
{
auto cyclesPerSecond = MidiMessage::getMidiNoteInHertz (noteNumber);

View File

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

View File

@ -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
<JUCER_COMPONENT documentType="Component" className="BasicParamsComponent" componentName=""
parentClasses="public Component, public ComboBox::Listener, public Button::Listener"
constructorParams="Magical8bitPlug2AudioProcessor&amp; p, Magical8bitPlug2AudioProcessorEditor&amp; e"
parentClasses="public Component, public ComboBox::Listener" constructorParams="Magical8bitPlug2AudioProcessor&amp; p, Magical8bitPlug2AudioProcessorEditor&amp; e"
variableInitialisers="processor(p),editor(e)" snapPixels="8"
snapActive="1" snapShown="1" overlayOpacity="0.330" fixedSize="1"
initialWidth="700" initialHeight="64">
@ -187,7 +208,7 @@ BEGIN_JUCER_METADATA
focusDiscardsChanges="0" fontname="Default font" fontsize="15.0"
kerning="0.0" bold="0" italic="0" justification="33"/>
<GENERICCOMPONENT name="gain slider" id="4bb22b329fed19e9" memberName="gainSlider"
virtualName="" explicitFocusOrder="0" pos="0 32 360 32" class="SliderComponent"
virtualName="" explicitFocusOrder="0" pos="0 32 380 32" class="SliderComponent"
params="p, &quot;gain&quot;, &quot;Gain&quot;"/>
<GENERICCOMPONENT name="osc selector" id="fa2387d441a3005d" memberName="oscChoice"
virtualName="" explicitFocusOrder="0" pos="0 4 224 28" class="ChoiceComponent"
@ -198,11 +219,14 @@ BEGIN_JUCER_METADATA
textBoxEditable="1" textBoxWidth="30" textBoxHeight="20" skewFactor="1.0"
needsCallback="1"/>
<GENERICCOMPONENT name="advanced option switch" id="9d35239102eeb521" memberName="advancedSwitch"
virtualName="" explicitFocusOrder="0" pos="0Rr 4 240 28" class="CheckBoxComponent"
params="p, &quot;isAdvancedPanelOpen_raw&quot;, &quot;Show Advanced Options&quot;"/>
virtualName="" explicitFocusOrder="0" pos="0Rr 4 180 28" class="CheckBoxComponent"
params="p, &quot;isAdvancedPanelOpen_raw&quot;, &quot;Advanced Options&quot;"/>
<GENERICCOMPONENT name="color selector" id="21d73ddc37680dd7" memberName="colorSchemeChoice"
virtualName="" explicitFocusOrder="0" pos="4Rr 32 185 28" class="ChoiceComponent"
params="p, &quot;colorScheme&quot;, &quot;Color&quot;"/>
<TEXTBUTTON name="mono button" id="9265bb1a11f90786" memberName="monoButton"
virtualName="" explicitFocusOrder="0" pos="360 4 54 24" buttonText="mono"
connectedEdges="0" needsCallback="1" radioGroupId="0"/>
</JUCER_COMPONENT>
END_JUCER_METADATA

View File

@ -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<juce::Slider> polyNumberInput;
std::unique_ptr<CheckBoxComponent> advancedSwitch;
std::unique_ptr<ChoiceComponent> colorSchemeChoice;
std::unique_ptr<juce::TextButton> monoButton;
//==============================================================================

View File

@ -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]
}

147
Source/CustomSynth.cpp Normal file
View File

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

29
Source/CustomSynth.h Normal file
View File

@ -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;
};

View File

@ -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
<JUCER_COMPONENT documentType="Component" className="MonophonicComponent" componentName=""
parentClasses="public Component, public ComboBox::Listener" constructorParams="Magical8bitPlug2AudioProcessor&amp; p"
variableInitialisers="processor(p)" snapPixels="8" snapActive="1"
snapShown="1" overlayOpacity="0.330" fixedSize="1" initialWidth="640"
initialHeight="82">
<BACKGROUND backgroundColour="ffffff"/>
<LABEL name="label" id="bae3132bcad681ce" memberName="label" virtualName=""
explicitFocusOrder="0" pos="0 4 150 22" edTextCol="ff000000"
edBkgCol="0" labelText="Monophonic Options" editableSingleClick="0"
editableDoubleClick="0" focusDiscardsChanges="0" fontname="Default font"
fontsize="17.0" kerning="0.0" bold="0" italic="0" justification="33"/>
<GENERICCOMPONENT name="behavior selector" id="fa2387d441a3005d" memberName="behaviorChoice"
virtualName="" explicitFocusOrder="0" pos="0 28 224 26" class="ChoiceComponent"
params="p, &quot;monophonicBehavior_raw&quot;, &quot;Behavior&quot;"/>
<GENERICCOMPONENT name="interval selector" id="21d73ddc37680dd7" memberName="intervalChoice"
virtualName="" explicitFocusOrder="0" pos="228 28 185 28" class="ChoiceComponent"
params="p, &quot;arpeggioIntervalType_raw&quot;, &quot;Interval&quot;"/>
<SLIDER name="interval slider" id="2d6901c46e73c1e" memberName="intervalSlider"
virtualName="" explicitFocusOrder="0" pos="0Rr 28 420M 24" min="0.0"
max="10.0" int="0.01" style="LinearHorizontal" textBoxPos="TextBoxRight"
textBoxEditable="1" textBoxWidth="50" textBoxHeight="20" skewFactor="1.0"
needsCallback="0"/>
<GENERICCOMPONENT name="portamento slider" id="b01ddc412ec6dc27" memberName="portamentoSlider"
virtualName="" explicitFocusOrder="0" pos="0Rr 28 50% 28" class="SliderComponent"
params="p, &quot;portamentoTime&quot;, &quot;Portamento&quot;"/>
</JUCER_COMPONENT>
END_JUCER_METADATA
*/
#endif
//[EndFile] You can add extra defines here...
//[/EndFile]

View File

@ -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 <JuceHeader.h>
#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<SliderAttachment> attc;
Magical8bitPlug2AudioProcessor& processor;
//[/UserVariables]
//==============================================================================
std::unique_ptr<juce::Label> label;
std::unique_ptr<ChoiceComponent> behaviorChoice;
std::unique_ptr<ChoiceComponent> intervalChoice;
std::unique_ptr<juce::Slider> intervalSlider;
std::unique_ptr<SliderComponent> portamentoSlider;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MonophonicComponent)
};
//[EndFile] You can add extra defines here...
//[/EndFile]

View File

@ -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)

View File

@ -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<BasicParamsComponent> basicCompo;
std::unique_ptr<MonophonicComponent> monoCompo;
std::unique_ptr<EnvelopeParamsComponent> envCompo;
std::unique_ptr<AdvancedParamsComponent> advCompo;
std::unique_ptr<PulseParamsComponent> pulCompo;
@ -51,6 +53,8 @@ private:
std::unique_ptr<BendParamsComponent> bendCompo;
std::unique_ptr<SweepParamsComponent> sweepCompo;
std::unique_ptr<VibratoParamsComponent> vibCompo;
bool isComponentsReady;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Magical8bitPlug2AudioProcessorEditor)
};

View File

@ -66,11 +66,18 @@ Magical8bitPlug2AudioProcessor::Magical8bitPlug2AudioProcessor()
0.5f), //skew
0.0f), //default
//
// Arpeggio
// Monophonic
//
std::make_unique<AudioParameterBool> ("isArpeggioEnabled_raw", "Enabled", false),
std::make_unique<AudioParameterFloat> ("arpeggioTime", "Time", 0.0f, 0.3f, 0.033f),
std::make_unique<AudioParameterChoice> ("arpeggioDirection", "Direction", StringArray ({"up", "down"}), 0),
std::make_unique<AudioParameterChoice> ("monophonicBehavior_raw", "Behavior", StringArray ({"Legato", "Arpeggio Up", "Arpeggio Down", "Non-legato"}), 0),
std::make_unique<AudioParameterChoice> ("arpeggioIntervalType_raw", "Interval", StringArray ({"1 frame", "2 frames", "3 frames", "96th", "64th", "48th", "32nd", "24th", "Slider"}), 0),
std::make_unique<AudioParameterFloat> ("arpeggioIntervalSliderValue", //ID
"Interval", //name
NormalisableRange<float> (0.001f, //min
0.3f, //max
0.001f, //step
0.5f), //skew
0.001f), //default
std::make_unique<AudioParameterFloat> ("portamentoTime", "Portamento Time", 0.0f, 1.0f, 0.0f),
//
// Bend
//
@ -118,6 +125,7 @@ Magical8bitPlug2AudioProcessor::Magical8bitPlug2AudioProcessor()
}
)
, settingRefs (&parameters)
, 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<float>& 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()

View File

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

View File

@ -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

View File

@ -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; i<NUMNOTEBUFFER; i++) { noteBuffer[i] = 0; }
currentNumRetireBuffer = 0;
for (int i=0; i<NUMNOTEBUFFER; i++) { retireBuffer[i] = 0; }
}
void TonalVoice::advanceControlFrame()
@ -61,7 +70,7 @@ void TonalVoice::calculateAngleDelta()
break;
}
}
double byWheel = settingRefs->vibratoIgnoresWheel() ? 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<currentNumNoteBuffer; i++) {
if (noteBuffer[i] > midiNoteNumber) break;
}
pushNoteBuffer(i, midiNoteNumber);
currentNumNoteBuffer++;
}
void TonalVoice::addArpeggioNoteDescending(int midiNoteNumber)
{
if (currentNumNoteBuffer >= NUMNOTEBUFFER) {
return;
}
int i;
for (i = 0; i<currentNumNoteBuffer; i++) {
if (noteBuffer[i] < midiNoteNumber) break;
}
pushNoteBuffer(i, midiNoteNumber);
currentNumNoteBuffer++;
}
/// Returns number of remaining arpeggio notes
int TonalVoice::removeArpeggioNote(int midiNoteNumber)
{
if (currentNumNoteBuffer==0) {
return 0;
}
// Just push the note number to Retire Buffer
// Actual retirement happens in advanceFrameBuffer
retireBuffer[currentNumRetireBuffer] = midiNoteNumber;
currentNumRetireBuffer++;
// Observed like current number of notes already decreased
// so the Synthsizer can determine if it should enter Release phase
return currentNumNoteBuffer - currentNumRetireBuffer;
}
void TonalVoice::pushNoteBuffer(int index, int value) {
for (int i=NUMNOTEBUFFER-1; i>index; i--) {
noteBuffer[i] = noteBuffer[i-1];
}
noteBuffer[index] = value;
}
void TonalVoice::shiftNoteBuffer(int index) {
for (int i=index; i<NUMNOTEBUFFER-1; i++) {
noteBuffer[i] = noteBuffer[i+1];
}
}
double TonalVoice::noteNoToHeltzDouble (double noteNoInDouble, const double frequencyOfA)
{
return frequencyOfA * std::pow (2.0, (noteNoInDouble - 69) / 12.0);
@ -133,4 +244,72 @@ void TonalVoice::onFrameAdvanced()
autoBendDelta = 0;
}
}
if (arpeggioFrameLength > 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<currentNumRetireBuffer; j++) {
int target = retireBuffer[j];
int i = 0;
// Find first match
for (i=0; i<currentNumNoteBuffer; i++) {
if (noteBuffer[i] == target) break;
}
if (i == currentNumNoteBuffer) { // not found
continue;
}
shiftNoteBuffer(i);
currentNumNoteBuffer--;
}
// Clear Retire Buffer
currentNumRetireBuffer = 0;
for (int i=0; i<NUMNOTEBUFFER; i++) { retireBuffer[i] = 0; }
} /* else {
Retirements should not happen in Release Phase
to keep the arpeggio playing during the release.
} */
if (noteBuffer[currentArpeggioFrame] == noteNumber || noteBuffer[currentArpeggioFrame] == 0) {
// Normally 'currentArpaggioFrame' will be updated every frame with
// 'noteBuffer[currentArpeggioFrame] == noteNumber' condition,
// but when the retirement happens currentArpeggioFrame may point at the slot already gone.
// to handle this situation the condition 'noteBuffer[currentArpeggioFrame] == 0' is added.
currentArpeggioFrame++;
if (currentArpeggioFrame >= 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;
}
}

View File

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