mirror of
https://github.com/yokemura/Magical8bitPlug2.git
synced 2025-05-24 23:00:21 -04:00
Merge pull request #19 from yokemura/feature/monoMode
Feature/mono mode
This commit is contained in:
commit
098b268001
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -25,6 +25,8 @@ struct BaseVoice : public SynthesiserVoice
|
||||
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() {};
|
||||
virtual void advanceControlFrame();
|
||||
|
@ -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& p, Magical8bitPlug2AudioProcessorEditor& e"
|
||||
parentClasses="public Component, public ComboBox::Listener" constructorParams="Magical8bitPlug2AudioProcessor& p, Magical8bitPlug2AudioProcessorEditor& 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, "gain", "Gain""/>
|
||||
<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, "isAdvancedPanelOpen_raw", "Show Advanced Options""/>
|
||||
virtualName="" explicitFocusOrder="0" pos="0Rr 4 180 28" class="CheckBoxComponent"
|
||||
params="p, "isAdvancedPanelOpen_raw", "Advanced Options""/>
|
||||
<GENERICCOMPONENT name="color selector" id="21d73ddc37680dd7" memberName="colorSchemeChoice"
|
||||
virtualName="" explicitFocusOrder="0" pos="4Rr 32 185 28" class="ChoiceComponent"
|
||||
params="p, "colorScheme", "Color""/>
|
||||
<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
|
||||
|
@ -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;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
|
@ -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
147
Source/CustomSynth.cpp
Normal 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
29
Source/CustomSynth.h
Normal 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;
|
||||
};
|
200
Source/MonophonicComponent.cpp
Normal file
200
Source/MonophonicComponent.cpp
Normal 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& 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, "monophonicBehavior_raw", "Behavior""/>
|
||||
<GENERICCOMPONENT name="interval selector" id="21d73ddc37680dd7" memberName="intervalChoice"
|
||||
virtualName="" explicitFocusOrder="0" pos="228 28 185 28" class="ChoiceComponent"
|
||||
params="p, "arpeggioIntervalType_raw", "Interval""/>
|
||||
<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, "portamentoTime", "Portamento""/>
|
||||
</JUCER_COMPONENT>
|
||||
|
||||
END_JUCER_METADATA
|
||||
*/
|
||||
#endif
|
||||
|
||||
|
||||
//[EndFile] You can add extra defines here...
|
||||
//[/EndFile]
|
||||
|
78
Source/MonophonicComponent.h
Normal file
78
Source/MonophonicComponent.h
Normal 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]
|
||||
|
@ -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,11 +26,16 @@ 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;
|
||||
|
||||
@ -205,26 +209,37 @@ void Magical8bitPlug2AudioProcessorEditor::resized()
|
||||
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);
|
||||
|
||||
@ -261,6 +276,8 @@ void Magical8bitPlug2AudioProcessorEditor::resized()
|
||||
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)
|
||||
|
@ -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;
|
||||
@ -52,5 +54,7 @@ private:
|
||||
std::unique_ptr<SweepParamsComponent> sweepCompo;
|
||||
std::unique_ptr<VibratoParamsComponent> vibCompo;
|
||||
|
||||
bool isComponentsReady;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Magical8bitPlug2AudioProcessorEditor)
|
||||
};
|
||||
|
@ -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 (¶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<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()
|
||||
|
@ -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)
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -36,6 +37,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;
|
||||
void advanceControlFrame() 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();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user