diff --git a/.gitignore b/.gitignore index 0f90feb2..0239b036 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ JuceLibraryCode/ Builds/ assets/ products/ + +.DS_Store diff --git a/Dexed.jucer b/Dexed.jucer index 790b744d..95cdea5f 100644 --- a/Dexed.jucer +++ b/Dexed.jucer @@ -79,6 +79,8 @@ + + @@ -140,7 +142,7 @@ + vst3Folder="libs/vst3sdk" extraCompilerFlags="-Wno-macro-redefined -Wno-deprecated-declarations -Wno-shorten-64-to-32"> diff --git a/Source/GlobalEditor.cpp b/Source/GlobalEditor.cpp index 9c132bd6..5964a653 100644 --- a/Source/GlobalEditor.cpp +++ b/Source/GlobalEditor.cpp @@ -644,8 +644,16 @@ void GlobalEditor::mouseDown(const MouseEvent &e) { if ( e.mods.isPopupMenu()) { PopupMenu popup; popup.addItem(1, "Send current program to DX7"); - if ( popup.show() == 1 ) + + auto p = popup.show(); + switch( p ) + { + case 1: processor->sendCurrentSysexProgram(); + break; + default: + break; + } } } //[/MiscUserCode] diff --git a/Source/ParamDialog.cpp b/Source/ParamDialog.cpp index 99ccb859..c8e926b6 100644 --- a/Source/ParamDialog.cpp +++ b/Source/ParamDialog.cpp @@ -7,12 +7,12 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 5.2.0 + Created with Projucer version: 5.4.5 ------------------------------------------------------------------------------ - The Projucer is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright (c) 2015 - ROLI Ltd. + The Projucer is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. ============================================================================== */ @@ -33,39 +33,55 @@ ParamDialog::ParamDialog () //[Constructor_pre] You can add your own custom stuff here.. //[/Constructor_pre] - addAndMakeVisible (pitchRange = new Slider ("pitchRange")); + pitchRange.reset (new Slider ("pitchRange")); + addAndMakeVisible (pitchRange.get()); pitchRange->setRange (0, 12, 1); pitchRange->setSliderStyle (Slider::RotaryVerticalDrag); pitchRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); pitchRange->addListener (this); - addAndMakeVisible (pitchStep = new Slider ("pitchStep")); + pitchRange->setBounds (264, 16, 72, 24); + + pitchStep.reset (new Slider ("pitchStep")); + addAndMakeVisible (pitchStep.get()); pitchStep->setRange (0, 12, 1); pitchStep->setSliderStyle (Slider::RotaryVerticalDrag); pitchStep->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); pitchStep->addListener (this); - addAndMakeVisible (sysexIn = new ComboBox ("sysexIn")); + pitchStep->setBounds (264, 56, 72, 24); + + sysexIn.reset (new ComboBox ("sysexIn")); + addAndMakeVisible (sysexIn.get()); sysexIn->setEditableText (false); sysexIn->setJustificationType (Justification::centredLeft); sysexIn->setTextWhenNothingSelected (String()); sysexIn->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); sysexIn->addListener (this); - addAndMakeVisible (sysexOut = new ComboBox ("sysexOut")); + sysexIn->setBounds (104, 224, 224, 24); + + sysexOut.reset (new ComboBox ("sysexOut")); + addAndMakeVisible (sysexOut.get()); sysexOut->setEditableText (false); sysexOut->setJustificationType (Justification::centredLeft); sysexOut->setTextWhenNothingSelected (String()); sysexOut->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); sysexOut->addListener (this); - addAndMakeVisible (sysexChl = new Slider ("sysexChl")); + sysexOut->setBounds (104, 264, 224, 24); + + sysexChl.reset (new Slider ("sysexChl")); + addAndMakeVisible (sysexChl.get()); sysexChl->setRange (1, 16, 1); sysexChl->setSliderStyle (Slider::RotaryVerticalDrag); sysexChl->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); sysexChl->addListener (this); - addAndMakeVisible (engineReso = new ComboBox ("new combo box")); + sysexChl->setBounds (264, 304, 72, 24); + + engineReso.reset (new ComboBox ("new combo box")); + addAndMakeVisible (engineReso.get()); engineReso->setEditableText (false); engineReso->setJustificationType (Justification::centredLeft); engineReso->setTextWhenNothingSelected (String()); @@ -75,81 +91,170 @@ ParamDialog::ParamDialog () engineReso->addItem (TRANS("OPL Series"), 3); engineReso->addListener (this); - addAndMakeVisible (showKeyboard = new ToggleButton ("showKeyboard")); + engineReso->setBounds (160, 156, 168, 24); + + showKeyboard.reset (new ToggleButton ("showKeyboard")); + addAndMakeVisible (showKeyboard.get()); showKeyboard->setButtonText (String()); + showKeyboard->addListener (this); - addAndMakeVisible (whlRange = new Slider ("whlRange")); + showKeyboard->setBounds (264, 96, 56, 24); + + whlRange.reset (new Slider ("whlRange")); + addAndMakeVisible (whlRange.get()); whlRange->setRange (0, 99, 1); whlRange->setSliderStyle (Slider::RotaryVerticalDrag); whlRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); whlRange->addListener (this); - addAndMakeVisible (ftRange = new Slider ("ftRange")); + whlRange->setBounds (448, 16, 72, 24); + + ftRange.reset (new Slider ("ftRange")); + addAndMakeVisible (ftRange.get()); ftRange->setRange (0, 99, 1); ftRange->setSliderStyle (Slider::RotaryVerticalDrag); ftRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); ftRange->addListener (this); - addAndMakeVisible (brRange = new Slider ("brRange")); + ftRange->setBounds (448, 56, 72, 24); + + brRange.reset (new Slider ("brRange")); + addAndMakeVisible (brRange.get()); brRange->setRange (0, 99, 1); brRange->setSliderStyle (Slider::RotaryVerticalDrag); brRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); brRange->addListener (this); - addAndMakeVisible (atRange = new Slider ("atRange")); + brRange->setBounds (448, 96, 72, 24); + + atRange.reset (new Slider ("atRange")); + addAndMakeVisible (atRange.get()); atRange->setRange (0, 99, 1); atRange->setSliderStyle (Slider::RotaryVerticalDrag); atRange->setTextBoxStyle (Slider::TextBoxLeft, false, 80, 20); atRange->addListener (this); - addAndMakeVisible (whlEg = new ToggleButton ("whlEg")); + atRange->setBounds (448, 136, 72, 24); + + whlEg.reset (new ToggleButton ("whlEg")); + addAndMakeVisible (whlEg.get()); whlEg->setButtonText (String()); whlEg->addListener (this); - addAndMakeVisible (ftEg = new ToggleButton ("ftEg")); + whlEg->setBounds (640, 16, 56, 24); + + ftEg.reset (new ToggleButton ("ftEg")); + addAndMakeVisible (ftEg.get()); ftEg->setButtonText (String()); ftEg->addListener (this); - addAndMakeVisible (brEg = new ToggleButton ("brEg")); + ftEg->setBounds (640, 56, 56, 24); + + brEg.reset (new ToggleButton ("brEg")); + addAndMakeVisible (brEg.get()); brEg->setButtonText (String()); brEg->addListener (this); - addAndMakeVisible (atEg = new ToggleButton ("atEg")); + brEg->setBounds (640, 96, 56, 24); + + atEg.reset (new ToggleButton ("atEg")); + addAndMakeVisible (atEg.get()); atEg->setButtonText (String()); atEg->addListener (this); - addAndMakeVisible (whlAmp = new ToggleButton ("whlAmp")); + atEg->setBounds (640, 136, 56, 24); + + whlAmp.reset (new ToggleButton ("whlAmp")); + addAndMakeVisible (whlAmp.get()); whlAmp->setButtonText (String()); whlAmp->addListener (this); - addAndMakeVisible (ftAmp = new ToggleButton ("ftAmp")); + whlAmp->setBounds (584, 16, 56, 24); + + ftAmp.reset (new ToggleButton ("ftAmp")); + addAndMakeVisible (ftAmp.get()); ftAmp->setButtonText (String()); ftAmp->addListener (this); - addAndMakeVisible (brAmp = new ToggleButton ("brAmp")); + ftAmp->setBounds (584, 56, 56, 24); + + brAmp.reset (new ToggleButton ("brAmp")); + addAndMakeVisible (brAmp.get()); brAmp->setButtonText (String()); brAmp->addListener (this); - addAndMakeVisible (atAmp = new ToggleButton ("atAmp")); + brAmp->setBounds (584, 96, 56, 24); + + atAmp.reset (new ToggleButton ("atAmp")); + addAndMakeVisible (atAmp.get()); atAmp->setButtonText (String()); atAmp->addListener (this); - addAndMakeVisible (whlPitch = new ToggleButton ("whlPitch")); + atAmp->setBounds (584, 136, 56, 24); + + whlPitch.reset (new ToggleButton ("whlPitch")); + addAndMakeVisible (whlPitch.get()); whlPitch->setButtonText (String()); whlPitch->addListener (this); - addAndMakeVisible (ftPitch = new ToggleButton ("ftPitch")); + whlPitch->setBounds (528, 16, 56, 24); + + ftPitch.reset (new ToggleButton ("ftPitch")); + addAndMakeVisible (ftPitch.get()); ftPitch->setButtonText (String()); ftPitch->addListener (this); - addAndMakeVisible (brPitch = new ToggleButton ("brPitch")); + ftPitch->setBounds (528, 56, 56, 24); + + brPitch.reset (new ToggleButton ("brPitch")); + addAndMakeVisible (brPitch.get()); brPitch->setButtonText (String()); brPitch->addListener (this); - addAndMakeVisible (atPitch = new ToggleButton ("atPitch")); + brPitch->setBounds (528, 96, 56, 24); + + atPitch.reset (new ToggleButton ("atPitch")); + addAndMakeVisible (atPitch.get()); atPitch->setButtonText (String()); atPitch->addListener (this); + atPitch->setBounds (528, 136, 56, 24); + + sclButton.reset (new TextButton ("scl button")); + addAndMakeVisible (sclButton.get()); + sclButton->setButtonText (TRANS("SCL")); + sclButton->addListener (this); + + sclButton->setBounds (448, 208, 56, 24); + + kbmButton.reset (new TextButton ("kbm button")); + addAndMakeVisible (kbmButton.get()); + kbmButton->setButtonText (TRANS("KBM")); + kbmButton->addListener (this); + + kbmButton->setBounds (512, 208, 56, 24); + + showTunButton.reset (new TextButton ("show tuning button")); + addAndMakeVisible (showTunButton.get()); + showTunButton->setButtonText (TRANS("Show")); + showTunButton->addListener (this); + + showTunButton->setBounds (576, 208, 48, 24); + + resetTuningButton.reset (new TextButton ("reset tuning button")); + addAndMakeVisible (resetTuningButton.get()); + resetTuningButton->setButtonText (TRANS("Reset")); + resetTuningButton->addListener (this); + + resetTuningButton->setBounds (632, 208, 48, 24); + + transposeScale.reset (new ToggleButton ("transposeScale")); + addAndMakeVisible (transposeScale.get()); + transposeScale->setButtonText (String()); + transposeScale->addListener (this); + + transposeScale->setBounds (576, 240, 56, 24); + //[UserPreSize] //[/UserPreSize] @@ -204,6 +309,11 @@ ParamDialog::~ParamDialog() ftPitch = nullptr; brPitch = nullptr; atPitch = nullptr; + sclButton = nullptr; + kbmButton = nullptr; + showTunButton = nullptr; + resetTuningButton = nullptr; + transposeScale = nullptr; //[Destructor]. You can add your own custom destruction code here.. @@ -319,7 +429,7 @@ void ParamDialog::paint (Graphics& g) { int x = 368, y = 96, width = 276, height = 23; - String text (TRANS("Foot")); + String text (TRANS("Breath")); Colour fillColour = Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -331,7 +441,7 @@ void ParamDialog::paint (Graphics& g) { int x = 368, y = 56, width = 276, height = 23; - String text (TRANS("Breath")); + String text (TRANS("Foot")); Colour fillColour = Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -389,6 +499,51 @@ void ParamDialog::paint (Graphics& g) Justification::centredLeft, true); } + { + int x = 371, y = 194, width = 325, height = 1; + Colour fillColour = Colours::black; + //[UserPaintCustomArguments] Customize the painting arguments here.. + //[/UserPaintCustomArguments] + g.setColour (fillColour); + g.fillRect (x, y, width, height); + } + + { + int x = 371, y = 210, width = 276, height = 25; + String text (TRANS("Tuning")); + Colour fillColour = Colours::white; + //[UserPaintCustomArguments] Customize the painting arguments here.. + //[/UserPaintCustomArguments] + g.setColour (fillColour); + g.setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular")); + g.drawText (text, x, y, width, height, + Justification::centredLeft, true); + } + + { + int x = 459, y = 242, width = 125, height = 25; + String text (TRANS("Transp 12 as Scale")); + Colour fillColour = Colours::white; + //[UserPaintCustomArguments] Customize the painting arguments here.. + //[/UserPaintCustomArguments] + g.setColour (fillColour); + g.setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular")); + g.drawText (text, x, y, width, height, + Justification::centredLeft, true); + } + + { + int x = 643, y = 242, width = 45, height = 25; + String text (TRANS("Value")); + Colour fillColour = Colours::white; + //[UserPaintCustomArguments] Customize the painting arguments here.. + //[/UserPaintCustomArguments] + g.setColour (fillColour); + g.setFont (Font (15.00f, Font::plain).withTypefaceStyle ("Regular")); + g.drawText (text, x, y, width, height, + Justification::centredLeft, true); + } + //[UserPaint] Add your own custom painting code here.. if ( ! JUCEApplication::isStandaloneApp() ) { g.setColour (Colours::white); @@ -411,29 +566,6 @@ void ParamDialog::resized() //[UserPreResize] Add your own custom resize code here.. //[/UserPreResize] - pitchRange->setBounds (264, 16, 72, 24); - pitchStep->setBounds (264, 56, 72, 24); - sysexIn->setBounds (104, 224, 224, 24); - sysexOut->setBounds (104, 264, 224, 24); - sysexChl->setBounds (264, 304, 72, 24); - engineReso->setBounds (160, 156, 168, 24); - showKeyboard->setBounds (264, 96, 56, 24); - whlRange->setBounds (448, 16, 72, 24); - ftRange->setBounds (448, 56, 72, 24); - brRange->setBounds (448, 96, 72, 24); - atRange->setBounds (448, 136, 72, 24); - whlEg->setBounds (640, 16, 56, 24); - ftEg->setBounds (640, 56, 56, 24); - brEg->setBounds (640, 96, 56, 24); - atEg->setBounds (640, 136, 56, 24); - whlAmp->setBounds (584, 16, 56, 24); - ftAmp->setBounds (584, 56, 56, 24); - brAmp->setBounds (584, 96, 56, 24); - atAmp->setBounds (584, 136, 56, 24); - whlPitch->setBounds (528, 16, 56, 24); - ftPitch->setBounds (528, 56, 56, 24); - brPitch->setBounds (528, 96, 56, 24); - atPitch->setBounds (528, 136, 56, 24); //[UserResized] Add your own custom resize handling here.. //[/UserResized] } @@ -441,141 +573,190 @@ void ParamDialog::resized() void ParamDialog::sliderValueChanged (Slider* sliderThatWasMoved) { //[UsersliderValueChanged_Pre] + bool handled = false; //[/UsersliderValueChanged_Pre] - if (sliderThatWasMoved == pitchRange) + if (sliderThatWasMoved == pitchRange.get()) { //[UserSliderCode_pitchRange] -- add your slider handling code here.. //[/UserSliderCode_pitchRange] } - else if (sliderThatWasMoved == pitchStep) + else if (sliderThatWasMoved == pitchStep.get()) { //[UserSliderCode_pitchStep] -- add your slider handling code here.. pitchRange->setEnabled(pitchStep->getValue() == 0); //[/UserSliderCode_pitchStep] } - else if (sliderThatWasMoved == sysexChl) + else if (sliderThatWasMoved == sysexChl.get()) { //[UserSliderCode_sysexChl] -- add your slider handling code here.. //[/UserSliderCode_sysexChl] } - else if (sliderThatWasMoved == whlRange) + else if (sliderThatWasMoved == whlRange.get()) { //[UserSliderCode_whlRange] -- add your slider handling code here.. //[/UserSliderCode_whlRange] } - else if (sliderThatWasMoved == ftRange) + else if (sliderThatWasMoved == ftRange.get()) { //[UserSliderCode_ftRange] -- add your slider handling code here.. //[/UserSliderCode_ftRange] } - else if (sliderThatWasMoved == brRange) + else if (sliderThatWasMoved == brRange.get()) { //[UserSliderCode_brRange] -- add your slider handling code here.. //[/UserSliderCode_brRange] } - else if (sliderThatWasMoved == atRange) + else if (sliderThatWasMoved == atRange.get()) { //[UserSliderCode_atRange] -- add your slider handling code here.. //[/UserSliderCode_atRange] } //[UsersliderValueChanged_Post] + if( ! handled ) + general_callback_(this); //[/UsersliderValueChanged_Post] } void ParamDialog::comboBoxChanged (ComboBox* comboBoxThatHasChanged) { //[UsercomboBoxChanged_Pre] + bool handled = false; //[/UsercomboBoxChanged_Pre] - if (comboBoxThatHasChanged == sysexIn) + if (comboBoxThatHasChanged == sysexIn.get()) { //[UserComboBoxCode_sysexIn] -- add your combo box handling code here.. //[/UserComboBoxCode_sysexIn] } - else if (comboBoxThatHasChanged == sysexOut) + else if (comboBoxThatHasChanged == sysexOut.get()) { //[UserComboBoxCode_sysexOut] -- add your combo box handling code here.. //[/UserComboBoxCode_sysexOut] } - else if (comboBoxThatHasChanged == engineReso) + else if (comboBoxThatHasChanged == engineReso.get()) { //[UserComboBoxCode_engineReso] -- add your combo box handling code here.. //[/UserComboBoxCode_engineReso] } //[UsercomboBoxChanged_Post] + if( ! handled ) + general_callback_(this); //[/UsercomboBoxChanged_Post] } void ParamDialog::buttonClicked (Button* buttonThatWasClicked) { //[UserbuttonClicked_Pre] + bool handled = false; //[/UserbuttonClicked_Pre] - if (buttonThatWasClicked == whlEg) + if (buttonThatWasClicked == showKeyboard.get()) + { + //[UserButtonCode_showKeyboard] -- add your button handler code here.. + //[/UserButtonCode_showKeyboard] + } + else if (buttonThatWasClicked == whlEg.get()) { //[UserButtonCode_whlEg] -- add your button handler code here.. //[/UserButtonCode_whlEg] } - else if (buttonThatWasClicked == ftEg) + else if (buttonThatWasClicked == ftEg.get()) { //[UserButtonCode_ftEg] -- add your button handler code here.. //[/UserButtonCode_ftEg] } - else if (buttonThatWasClicked == brEg) + else if (buttonThatWasClicked == brEg.get()) { //[UserButtonCode_brEg] -- add your button handler code here.. //[/UserButtonCode_brEg] } - else if (buttonThatWasClicked == atEg) + else if (buttonThatWasClicked == atEg.get()) { //[UserButtonCode_atEg] -- add your button handler code here.. //[/UserButtonCode_atEg] } - else if (buttonThatWasClicked == whlAmp) + else if (buttonThatWasClicked == whlAmp.get()) { //[UserButtonCode_whlAmp] -- add your button handler code here.. //[/UserButtonCode_whlAmp] } - else if (buttonThatWasClicked == ftAmp) + else if (buttonThatWasClicked == ftAmp.get()) { //[UserButtonCode_ftAmp] -- add your button handler code here.. //[/UserButtonCode_ftAmp] } - else if (buttonThatWasClicked == brAmp) + else if (buttonThatWasClicked == brAmp.get()) { //[UserButtonCode_brAmp] -- add your button handler code here.. //[/UserButtonCode_brAmp] } - else if (buttonThatWasClicked == atAmp) + else if (buttonThatWasClicked == atAmp.get()) { //[UserButtonCode_atAmp] -- add your button handler code here.. //[/UserButtonCode_atAmp] } - else if (buttonThatWasClicked == whlPitch) + else if (buttonThatWasClicked == whlPitch.get()) { //[UserButtonCode_whlPitch] -- add your button handler code here.. //[/UserButtonCode_whlPitch] } - else if (buttonThatWasClicked == ftPitch) + else if (buttonThatWasClicked == ftPitch.get()) { //[UserButtonCode_ftPitch] -- add your button handler code here.. //[/UserButtonCode_ftPitch] } - else if (buttonThatWasClicked == brPitch) + else if (buttonThatWasClicked == brPitch.get()) { //[UserButtonCode_brPitch] -- add your button handler code here.. //[/UserButtonCode_brPitch] } - else if (buttonThatWasClicked == atPitch) + else if (buttonThatWasClicked == atPitch.get()) { //[UserButtonCode_atPitch] -- add your button handler code here.. //[/UserButtonCode_atPitch] } + else if (buttonThatWasClicked == sclButton.get()) + { + //[UserButtonCode_sclButton] -- add your button handler code here.. + tuning_callback_(this, TuningAction::LOAD_SCL); + handled = true; + //[/UserButtonCode_sclButton] + } + else if (buttonThatWasClicked == kbmButton.get()) + { + //[UserButtonCode_kbmButton] -- add your button handler code here.. + tuning_callback_(this, TuningAction::LOAD_KBM); + handled = true; + //[/UserButtonCode_kbmButton] + } + else if (buttonThatWasClicked == showTunButton.get()) + { + //[UserButtonCode_showTunButton] -- add your button handler code here.. + tuning_callback_(this, TuningAction::SHOW_TUNING); + handled = true; + //[/UserButtonCode_showTunButton] + } + else if (buttonThatWasClicked == resetTuningButton.get()) + { + //[UserButtonCode_resetTuningButton] -- add your button handler code here.. + tuning_callback_(this, TuningAction::RESET_TUNING); + handled = true; + //[/UserButtonCode_resetTuningButton] + } + else if (buttonThatWasClicked == transposeScale.get()) + { + //[UserButtonCode_transposeScale] -- add your button handler code here.. + //[/UserButtonCode_transposeScale] + } //[UserbuttonClicked_Post] + if( ! handled ) + { + general_callback_(this); + } //[/UserbuttonClicked_Post] } @@ -609,6 +790,8 @@ void ParamDialog::setDialogValues(Controllers &c, SysexComm &mgr, int reso, bool atAmp->setToggleState(c.at.amp, dontSendNotification); atEg->setToggleState(c.at.eg, dontSendNotification); + transposeScale->setToggleState(c.transpose12AsScale ? 0 : 1, dontSendNotification ); + StringArray inputs = MidiInput::getDevices(); int idx = inputs.indexOf(mgr.getInput()); idx = idx == -1 ? 0 : idx + 1; @@ -649,6 +832,8 @@ bool ParamDialog::getDialogValues(Controllers &c, SysexComm &mgr, int *reso, boo c.at.amp = atAmp->getToggleState(); c.at.eg = atEg->getToggleState(); + c.transpose12AsScale = ! transposeScale->getToggleState(); + c.refresh(); if ( ! JUCEApplication::isStandaloneApp() ) { @@ -662,6 +847,15 @@ bool ParamDialog::getDialogValues(Controllers &c, SysexComm &mgr, int *reso, boo return ret; } +void ParamDialog::setIsStandardTuning( bool b ) +{ + is_standard_tuning_ = b; + if( showTunButton != nullptr ) + showTunButton->setEnabled( ! b ); + if( resetTuningButton != nullptr ) + resetTuningButton->setEnabled( ! b ); +} + //[/MiscUserCode] @@ -680,54 +874,64 @@ BEGIN_JUCER_METADATA fixedSize="1" initialWidth="710" initialHeight="350"> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> - - + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + + + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + fontname="Default font" fontsize="15.0" kerning="0.0" bold="0" + italic="0" justification="33"/> + + + + + explicitFocusOrder="0" pos="264 304 72 24" min="1.0" max="16.0" + int="1.0" style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" + textBoxEditable="1" textBoxWidth="80" textBoxHeight="20" skewFactor="1.0" + needsCallback="1"/> + connectedEdges="0" needsCallback="1" radioGroupId="0" state="0"/> + explicitFocusOrder="0" pos="448 16 72 24" min="0.0" max="99.0" + int="1.0" style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" + textBoxEditable="1" textBoxWidth="80" textBoxHeight="20" skewFactor="1.0" + needsCallback="1"/> + explicitFocusOrder="0" pos="448 56 72 24" min="0.0" max="99.0" + int="1.0" style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" + textBoxEditable="1" textBoxWidth="80" textBoxHeight="20" skewFactor="1.0" + needsCallback="1"/> + explicitFocusOrder="0" pos="448 96 72 24" min="0.0" max="99.0" + int="1.0" style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" + textBoxEditable="1" textBoxWidth="80" textBoxHeight="20" skewFactor="1.0" + needsCallback="1"/> + explicitFocusOrder="0" pos="448 136 72 24" min="0.0" max="99.0" + int="1.0" style="RotaryVerticalDrag" textBoxPos="TextBoxLeft" + textBoxEditable="1" textBoxWidth="80" textBoxHeight="20" skewFactor="1.0" + needsCallback="1"/> @@ -798,6 +1007,21 @@ BEGIN_JUCER_METADATA + + + + + END_JUCER_METADATA @@ -807,3 +1031,4 @@ END_JUCER_METADATA //[EndFile] You can add extra defines here... //[/EndFile] + diff --git a/Source/ParamDialog.h b/Source/ParamDialog.h index 5732c7b1..cc02cc5d 100644 --- a/Source/ParamDialog.h +++ b/Source/ParamDialog.h @@ -7,12 +7,12 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 5.2.0 + Created with Projucer version: 5.4.5 ------------------------------------------------------------------------------ - The Projucer is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright (c) 2015 - ROLI Ltd. + The Projucer is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. ============================================================================== */ @@ -23,6 +23,7 @@ #include "JuceHeader.h" #include "msfa/controllers.h" #include "SysexComm.h" +#include //[/Headers] @@ -49,6 +50,18 @@ class ParamDialog : public Component, //[UserMethods] -- You can add your own custom methods in this section. void setDialogValues(Controllers &c, SysexComm &mgr, int reso, bool showKeyboard); bool getDialogValues(Controllers &c, SysexComm &mgr, int *reso, bool *showKeyboard); + + typedef enum { + LOAD_SCL, + LOAD_KBM, + RESET_TUNING, + SHOW_TUNING + } TuningAction; + + void setTuningCallback(std::function tc ) { tuning_callback_ = tc; } + void setGeneralCallback(std::function gc ) { general_callback_ = gc; } + + void setIsStandardTuning(bool s); //[/UserMethods] void paint (Graphics& g) override; @@ -61,32 +74,40 @@ class ParamDialog : public Component, private: //[UserVariables] -- You can add your own custom variables in this section. + std::function tuning_callback_ = [](ParamDialog *, ParamDialog::TuningAction i) {}; + bool is_standard_tuning_; + std::function general_callback_ = [](ParamDialog *p) {}; //[/UserVariables] //============================================================================== - ScopedPointer pitchRange; - ScopedPointer pitchStep; - ScopedPointer sysexIn; - ScopedPointer sysexOut; - ScopedPointer sysexChl; - ScopedPointer engineReso; - ScopedPointer showKeyboard; - ScopedPointer whlRange; - ScopedPointer ftRange; - ScopedPointer brRange; - ScopedPointer atRange; - ScopedPointer whlEg; - ScopedPointer ftEg; - ScopedPointer brEg; - ScopedPointer atEg; - ScopedPointer whlAmp; - ScopedPointer ftAmp; - ScopedPointer brAmp; - ScopedPointer atAmp; - ScopedPointer whlPitch; - ScopedPointer ftPitch; - ScopedPointer brPitch; - ScopedPointer atPitch; + std::unique_ptr pitchRange; + std::unique_ptr pitchStep; + std::unique_ptr sysexIn; + std::unique_ptr sysexOut; + std::unique_ptr sysexChl; + std::unique_ptr engineReso; + std::unique_ptr showKeyboard; + std::unique_ptr whlRange; + std::unique_ptr ftRange; + std::unique_ptr brRange; + std::unique_ptr atRange; + std::unique_ptr whlEg; + std::unique_ptr ftEg; + std::unique_ptr brEg; + std::unique_ptr atEg; + std::unique_ptr whlAmp; + std::unique_ptr ftAmp; + std::unique_ptr brAmp; + std::unique_ptr atAmp; + std::unique_ptr whlPitch; + std::unique_ptr ftPitch; + std::unique_ptr brPitch; + std::unique_ptr atPitch; + std::unique_ptr sclButton; + std::unique_ptr kbmButton; + std::unique_ptr showTunButton; + std::unique_ptr resetTuningButton; + std::unique_ptr transposeScale; //============================================================================== @@ -95,3 +116,4 @@ class ParamDialog : public Component, //[EndFile] You can add extra defines here... //[/EndFile] + diff --git a/Source/PluginData.cpp b/Source/PluginData.cpp index 9cc9e168..79c86cca 100644 --- a/Source/PluginData.cpp +++ b/Source/PluginData.cpp @@ -320,6 +320,7 @@ void DexedAudioProcessor::getStateInformation(MemoryBlock& destData) { dexedState.setAttribute("masterTune", controllers.masterTune); //TRACE("saving opswitch %s", controllers.opSwitch); dexedState.setAttribute("opSwitch", controllers.opSwitch); + dexedState.setAttribute("transpose12AsScale", controllers.transpose12AsScale ? 1 : 0 ); char mod_cfg[15]; controllers.wheel.setConfig(mod_cfg); @@ -330,6 +331,15 @@ void DexedAudioProcessor::getStateInformation(MemoryBlock& destData) { dexedState.setAttribute("breathMod", mod_cfg); controllers.at.setConfig(mod_cfg); dexedState.setAttribute("aftertouchMod", mod_cfg); + + if( currentSCLData.size() > 1 || currentKBMData.size() > 1 ) + { + auto tuningx = dexedState.createNewChildElement("dexedTuning" ); + auto sclx = tuningx->createNewChildElement("scl"); + sclx->addTextElement(currentSCLData); + auto kbmx = tuningx->createNewChildElement("kbm"); + kbmx->addTextElement(currentKBMData); + } if ( activeFileCartridge.exists() ) dexedState.setAttribute("activeFileCartridge", activeFileCartridge.getFullPathName()); @@ -358,7 +368,7 @@ void DexedAudioProcessor::setStateInformation(const void* source, int sizeInByte // used to LOAD plugin state std::unique_ptr root(getXmlFromBinary(source, sizeInBytes)); - + if (root == nullptr) { TRACE("unknown state format"); return; @@ -387,10 +397,33 @@ void DexedAudioProcessor::setStateInformation(const void* source, int sizeInByte setEngineType(root->getIntAttribute("engineType", 1)); monoMode = root->getIntAttribute("monoMode", 0); controllers.masterTune = root->getIntAttribute("masterTune", 0); + controllers.transpose12AsScale = ( root->getIntAttribute("transpose12AsScale", 1) != 0 ); File possibleCartridge = File(root->getStringAttribute("activeFileCartridge")); if ( possibleCartridge.exists() ) activeFileCartridge = possibleCartridge; + + auto tuningParent = root->getChildByName( "dexedTuning" ); + if( tuningParent ) + { + auto sclx = tuningParent->getChildByName( "scl" ); + auto kbmx = tuningParent->getChildByName( "kbm" ); + std::string s = ""; + if( sclx && sclx->getFirstChildElement() && sclx->getFirstChildElement()->isTextElement() ) + { + s = sclx->getFirstChildElement()->getText().toStdString(); + if( s.size() > 1 ) + applySCLTuning(s); + } + + std::string k = ""; + if( kbmx && kbmx->getFirstChildElement() && kbmx->getFirstChildElement()->isTextElement() ) + { + k = kbmx->getFirstChildElement()->getText().toStdString(); + if( k.size() > 1 ) + applyKBMMapping(k); + } + } XmlElement *dexedBlob = root->getChildByName("dexedBlob"); if ( dexedBlob == NULL ) { diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index a5ff2e08..8968b487 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -153,30 +153,78 @@ void DexedAudioProcessorEditor::saveCart() { } } +void DexedAudioProcessorEditor::tuningShow() { + auto te = new TextEditor(); + te->setMultiLine(true); + te->setReadOnly(true); + + te->setText( processor->synthTuningState->display_tuning_str().c_str() ); + te->setSize( 500, 700 ); + + DialogWindow::LaunchOptions options; + options.content.setOwned(te); + options.dialogTitle = "Current Tuning"; + options.dialogBackgroundColour = Colour(0x32FFFFFF); + options.escapeKeyTriggersCloseButton = true; + options.useNativeTitleBar = false; + options.resizable = false; + + auto dialogwindow = options.launchAsync(); +} + void DexedAudioProcessorEditor::parmShow() { int tp = processor->getEngineType(); - AlertWindow window("","", AlertWindow::NoIcon, this); - ParamDialog param; - param.setColour(AlertWindow::backgroundColourId, Colour(0x32FFFFFF)); - param.setDialogValues(processor->controllers, processor->sysexComm, tp, processor->showKeyboard); - - window.addCustomComponent(¶m); - window.addButton("OK", 0); - window.addButton("Cancel" ,1); - if ( window.runModalLoop() != 0 ) - return; - - bool ret = param.getDialogValues(processor->controllers, processor->sysexComm, &tp, &processor->showKeyboard); - processor->setEngineType(tp); - processor->savePreference(); + DialogWindow::LaunchOptions options; - setSize(866, processor->showKeyboard ? 674 : 581); - midiKeyboard.repaint(); - - if ( ret == false ) { - AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Midi Interface", "Error opening midi ports"); - } + auto param = new ParamDialog(); + param->setColour(AlertWindow::backgroundColourId, Colour(0x32FFFFFF)); + param->setDialogValues(processor->controllers, processor->sysexComm, tp, processor->showKeyboard); + param->setIsStandardTuning(processor->synthTuningState->is_standard_tuning() ); + param->setTuningCallback([this](ParamDialog *p, ParamDialog::TuningAction which) { + switch(which) + { + case ParamDialog::LOAD_SCL: + this->processor->applySCLTuning(); + break; + case ParamDialog::LOAD_KBM: + this->processor->applyKBMMapping(); + break; + case ParamDialog::RESET_TUNING: + this->processor->retuneToStandard(); + break; + case ParamDialog::SHOW_TUNING: + // consider https://forum.juce.com/t/closing-a-modal-dialog-window/2961 + this->tuningShow(); + break; + } + p->setIsStandardTuning(this->processor->synthTuningState->is_standard_tuning() ); + } ); + + options.content.setOwned(param); + options.dialogTitle = "dexed Parameters"; + options.dialogBackgroundColour = Colour(0x32FFFFFF); + options.escapeKeyTriggersCloseButton = true; + options.useNativeTitleBar = false; + options.resizable = false; + + auto generalCallback = [this](ParamDialog *param) + { + int tpo; + bool ret = param->getDialogValues(this->processor->controllers, this->processor->sysexComm, &tpo, &this->processor->showKeyboard); + this->processor->setEngineType(tpo); + this->processor->savePreference(); + + this->setSize(866, this->processor->showKeyboard ? 674 : 581); + this->midiKeyboard.repaint(); + + if ( ret == false ) { + AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Midi Interface", "Error opening midi ports"); + } + }; + param->setGeneralCallback(generalCallback); + + auto dialogWindow = options.launchAsync(); } void DexedAudioProcessorEditor::initProgram() { @@ -369,3 +417,28 @@ void DexedAudioProcessorEditor::discoverMidiCC(Ctrl *ctrl) { ccListener.runModalLoop(); } +bool DexedAudioProcessorEditor::isInterestedInFileDrag (const StringArray &files) +{ + if( files.size() != 1 ) return false; + + for( auto i = files.begin(); i != files.end(); ++i ) + { + if( i->endsWithIgnoreCase( ".scl" ) || i->endsWithIgnoreCase( ".kbm" ) ) + return true; + } + return false; +} + +void DexedAudioProcessorEditor::filesDropped (const StringArray &files, int x, int y ) +{ + if( files.size() != 1 ) return; + auto fn = files[0]; + if( fn.endsWithIgnoreCase( ".scl" ) ) + { + processor->applySCLTuning( File( fn ) ); + } + if( fn.endsWithIgnoreCase( ".kbm" ) ) + { + processor->applyKBMMapping( File( fn ) ); + } +} diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 6d242a35..7f1aad44 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -31,7 +31,8 @@ //============================================================================== /** */ -class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox::Listener, public Timer { +class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox::Listener, public Timer, + public FileDragAndDropTarget { MidiKeyboardComponent midiKeyboard; OperatorEditor operators[6]; Colour background; @@ -44,10 +45,10 @@ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox: DexedAudioProcessorEditor (DexedAudioProcessor* ownerFilter); ~DexedAudioProcessorEditor(); - void timerCallback(); + virtual void timerCallback() override; - void paint (Graphics& g); - void comboBoxChanged (ComboBox* comboBoxThatHasChanged); + virtual void paint (Graphics& g) override; + virtual void comboBoxChanged (ComboBox* comboBoxThatHasChanged) override; void updateUI(); void rebuildProgramCombobox(); void loadCart(File file); @@ -56,7 +57,11 @@ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBox: void storeProgram(); void cartShow(); void parmShow(); + void tuningShow(); void discoverMidiCC(Ctrl *ctrl); + + virtual bool isInterestedInFileDrag (const StringArray &files) override; + virtual void filesDropped (const StringArray &files, int x, int y ) override; }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index db24218e..0852e6af 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -80,6 +80,8 @@ DexedAudioProcessor::DexedAudioProcessor() { Tanh::init(); Sin::init(); + synthTuningState = createStandardTuning(); + lastStateSave = 0; currentNote = -1; engineType = -1; @@ -132,7 +134,7 @@ void DexedAudioProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) fx.init(sampleRate); for (int note = 0; note < MAX_ACTIVE_NOTES; ++note) { - voices[note].dx7_note = new Dx7Note; + voices[note].dx7_note = new Dx7Note(synthTuningState); voices[note].keydown = false; voices[note].sustained = false; voices[note].live = false; @@ -395,7 +397,7 @@ void DexedAudioProcessor::keydown(uint8_t pitch, uint8_t velo) { return; } - pitch += data[144] - 24; + pitch += tuningTranspositionShift(); if ( normalizeDxVelocity ) { velo = ((float)velo) * 0.7874015; // 100/127 @@ -442,7 +444,7 @@ void DexedAudioProcessor::keydown(uint8_t pitch, uint8_t velo) { } void DexedAudioProcessor::keyup(uint8_t pitch) { - pitch += data[144] - 24; + pitch += tuningTranspositionShift(); int note; for (note=0; noteis_standard_tuning() || ! controllers.transpose12AsScale ) + return data[144] - 24; + else + { + int d144 = data[144]; + if( d144 % 12 == 0 ) + { + int oct = (d144 - 24) / 12; + int res = oct * synthTuningState->scale_length(); + return res; + } + else + return data[144] - 24; + } +} + void DexedAudioProcessor::panic() { for(int i=0;i t) +{ + synthTuningState = t; + for( int i=0; ituning_state_ = synthTuningState; +} + +void DexedAudioProcessor::retuneToStandard() +{ + currentSCLData = ""; + currentKBMData = ""; + resetTuning(createStandardTuning()); +} + +void DexedAudioProcessor::applySCLTuning() { + FileChooser fc( "Please select an SCL File", File(), "*.scl" ); + if( fc.browseForFileToOpen() ) + { + auto s = fc.getResult(); + applySCLTuning(s); + } +} + +void DexedAudioProcessor::applySCLTuning(File s) { + std::string sclcontents = s.loadFileAsString().toStdString(); + applySCLTuning(sclcontents); +} + +void DexedAudioProcessor::applySCLTuning(std::string sclcontents) { + currentSCLData = sclcontents; + + if( currentKBMData.size() < 1 ) + { + auto t = createTuningFromSCLData( sclcontents ); + resetTuning(t); + } + else + { + auto t = createTuningFromSCLAndKBMData( sclcontents, currentKBMData ); + resetTuning(t); + } +} + +void DexedAudioProcessor::applyKBMMapping() { + FileChooser fc( "Please select an KBM File", File(), "*.kbm" ); + if( fc.browseForFileToOpen() ) + { + auto s = fc.getResult(); + applyKBMMapping(s); + } +} + +void DexedAudioProcessor::applyKBMMapping( File s ) +{ + std::string kbmcontents = s.loadFileAsString().toStdString(); + applyKBMMapping(kbmcontents); +} + +void DexedAudioProcessor::applyKBMMapping(std::string kbmcontents) { + currentKBMData = kbmcontents; + + if( currentSCLData.size() < 1 ) + { + auto t = createTuningFromKBMData( currentKBMData ); + resetTuning(t); + } + else + { + auto t = createTuningFromSCLAndKBMData( currentSCLData, currentKBMData ); + resetTuning(t); + } +} diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 0246a059..8c221553 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -28,6 +28,7 @@ #include "msfa/lfo.h" #include "msfa/synth.h" #include "msfa/fm_core.h" +#include "msfa/tuning.h" #include "PluginParam.h" #include "PluginData.h" #include "PluginFx.h" @@ -236,6 +237,27 @@ public : static File dexedCartDir; Value lastCCUsed; + + std::shared_ptr synthTuningState; + // Prompt for a file + void applySCLTuning(); + void applyKBMMapping(); + + // Load a file + void applySCLTuning(File sclf); + void applyKBMMapping(File kbmf); + + // Load from text + void applySCLTuning(std::string scld); + void applyKBMMapping(std::string kbmd); + + void retuneToStandard(); + void resetTuning(std::shared_ptr t); + int tuningTranspositionShift(); + + std::string currentSCLData = ""; + std::string currentKBMData = ""; + private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DexedAudioProcessor) diff --git a/Source/msfa/controllers.h b/Source/msfa/controllers.h index 4a04931f..62a8482f 100644 --- a/Source/msfa/controllers.h +++ b/Source/msfa/controllers.h @@ -90,6 +90,8 @@ class Controllers { int modwheel_cc; int masterTune; + + bool transpose12AsScale = true; FmMod wheel; FmMod foot; diff --git a/Source/msfa/dx7note.cc b/Source/msfa/dx7note.cc index e2a99867..43ad33ff 100644 --- a/Source/msfa/dx7note.cc +++ b/Source/msfa/dx7note.cc @@ -22,15 +22,10 @@ #include "exp2.h" #include "controllers.h" #include "dx7note.h" +#include const int FEEDBACK_BITDEPTH = 8; -int32_t midinote_to_logfreq(int midinote) { - const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) - const int step = (1 << 24) / 12; - return base + step * midinote; -} - const int32_t coarsemul[] = { -16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600, 50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816, @@ -39,11 +34,11 @@ const int32_t coarsemul[] = { 81503396, 82323963, 83117622 }; -int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune) { +int32_t Dx7Note::osc_freq(int midinote, int mode, int coarse, int fine, int detune) { // TODO: pitch randomization int32_t logfreq; if (mode == 0) { - logfreq = midinote_to_logfreq(midinote); + logfreq = tuning_state_->midinote_to_logfreq(midinote); // could use more precision, closer enough for now. those numbers comes from my DX7 double detuneRatio = 0.0209 * exp(-0.396 * (((float)logfreq)/(1<<24))) / 7; @@ -137,7 +132,7 @@ static const uint32_t ampmodsenstab[] = { 0, 4342338, 7171437, 16777216 }; -Dx7Note::Dx7Note() { +Dx7Note::Dx7Note(std::shared_ptr ts) : tuning_state_(ts) { for(int op=0;op<6;op++) { params_[op].phase = 0; params_[op].gain_out = 0; diff --git a/Source/msfa/dx7note.h b/Source/msfa/dx7note.h index 07d80f11..7e030eab 100644 --- a/Source/msfa/dx7note.h +++ b/Source/msfa/dx7note.h @@ -27,6 +27,8 @@ #include "env.h" #include "pitchenv.h" #include "fm_core.h" +#include "tuning.h" +#include struct VoiceStatus { uint32_t amp[6]; @@ -36,7 +38,7 @@ struct VoiceStatus { class Dx7Note { public: - Dx7Note(); + Dx7Note(std::shared_ptr ts); void init(const uint8_t patch[156], int midinote, int velocity); // Note: this _adds_ to the buffer. Interesting question whether it's @@ -57,7 +59,11 @@ class Dx7Note { void transferState(Dx7Note& src); void transferSignal(Dx7Note &src); void oscSync(); - + + int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune); + + std::shared_ptr tuning_state_; + private: Env env_[6]; FmOpParams params_[6]; @@ -67,11 +73,12 @@ class Dx7Note { int32_t fb_shift_; int32_t ampmodsens_[6]; int32_t opMode[6]; - + int ampmoddepth_; int algorithm_; int pitchmoddepth_; int pitchmodsens_; + }; #endif // SYNTH_DX7NOTE_H_ diff --git a/Source/msfa/tuning.cc b/Source/msfa/tuning.cc new file mode 100644 index 00000000..3454ebc6 --- /dev/null +++ b/Source/msfa/tuning.cc @@ -0,0 +1,562 @@ +#include "tuning.h" + +#include +#include +#include +#include +#include +#include +#include + + +struct StandardTuning : public TuningState { + StandardTuning() { + const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) + const int step = (1 << 24) / 12; + for( int mn = 0; mn < 128; ++mn ) + { + auto res = base + step * mn; + current_logfreq_table_[mn] = res; + } + } + + virtual int32_t midinote_to_logfreq(int midinote) override { + return current_logfreq_table_[midinote]; + } + int current_logfreq_table_[128]; +}; + + +std::shared_ptr createStandardTuning() +{ + return std::make_shared(); +} + + +// This code is heavily based on the surge tuning implementation +struct Tone +{ + typedef enum Type + { + kToneCents, + kToneRatio + } Type; + + Type type; + float cents; + int ratio_d, ratio_n; + std::string stringRep; + float floatValue; + + Tone() : type(kToneRatio), cents(0), ratio_d(1), ratio_n(1), stringRep("1/1"), floatValue(1.0) + { + } +}; + +struct Scale +{ + std::string name; + std::string description; + std::string rawText; + int count; + std::vector tones; + + Scale() : name("empty scale"), description(""), rawText(""), count(0) + { + } + + bool isValid() const; + + static Scale evenTemperament12NoteScale(); +}; + +struct KeyboardMapping +{ + bool isValid; + bool isStandardMapping; + int count; + int firstMidi, lastMidi; + int middleNote; + int tuningConstantNote; + float tuningFrequency; + int octaveDegrees; + std::vector keys; // rather than an 'x' we use a '-1' for skipped keys + + std::string rawText; + std::string name; + + KeyboardMapping() : isValid(true), + isStandardMapping(true), + count(12), + firstMidi(0), + lastMidi(127), + middleNote(60), + tuningConstantNote(60), + tuningFrequency(8.175798915 * 32), + octaveDegrees(12), + rawText( "" ), + name( "" ) + { + for( int i=0; i<12; ++i ) + keys.push_back(i); + } + + static KeyboardMapping tuneA69To(double freq); +}; + + +Scale parseSCLData(const std::string &sclContents); +KeyboardMapping parseKBMData(const std::string &kbmContents); + + +Scale scaleFromStream(std::istream &inf) +{ + std::string line; + const int read_header = 0, read_count = 1, read_note = 2; + int state = read_header; + + Scale res; + std::ostringstream rawOSS; + while (std::getline(inf, line)) + { + rawOSS << line << "\n"; + if (line[0] == '!') + { + continue; + } + switch (state) + { + case read_header: + res.description = line; + state = read_count; + break; + case read_count: + res.count = atoi(line.c_str()); + state = read_note; + break; + case read_note: + Tone t; + t.stringRep = line; + if (line.find(".") != std::string::npos) + { + t.type = Tone::kToneCents; + t.cents = atof(line.c_str()); + } + else + { + t.type = Tone::kToneRatio; + auto slashPos = line.find("/"); + if (slashPos == std::string::npos) + { + t.ratio_n = atoi(line.c_str()); + t.ratio_d = 1; + } + else + { + t.ratio_n = atoi(line.substr(0, slashPos).c_str()); + t.ratio_d = atoi(line.substr(slashPos + 1).c_str()); + } + + // 2^(cents/1200) = n/d + // cents = 1200 * log(n/d) / log(2) + + t.cents = 1200 * log(1.0 * t.ratio_n/t.ratio_d) / log(2.0); + } + t.floatValue = t.cents / 1200.0 + 1.0; + res.tones.push_back(t); + + break; + } + } + + res.rawText = rawOSS.str(); + return res; +} + +Scale readSCLFile(std::string fname) +{ + std::ifstream inf; + inf.open(fname); + if (!inf.is_open()) + { + return Scale(); + } + + auto res = scaleFromStream(inf); + res.name = fname; + return res; +} + +Scale parseSCLData(const std::string &d) +{ + std::istringstream iss(d); + auto res = scaleFromStream(iss); + res.name = "Scale from Patch"; + return res; +} + +Scale Scale::evenTemperament12NoteScale() +{ + auto data = R"SCL(! even.scl +! +12 note even temprament + 12 +! + 100.0 + 200.0 + 300.0 + 400.0 + 500.0 + 600.0 + 700.0 + 800.0 + 900.0 + 1000.0 + 1100.0 + 2/1 +)SCL"; + return parseSCLData(data); +} + +KeyboardMapping keyboardMappingFromStream(std::istream &inf) +{ + std::string line; + + KeyboardMapping res; + std::ostringstream rawOSS; + res.isStandardMapping = false; + res.keys.clear(); + + enum parsePosition { + map_size = 0, + first_midi, + last_midi, + middle, + reference, + freq, + degree, + keys + }; + parsePosition state = map_size; + + while (std::getline(inf, line)) + { + rawOSS << line << "\n"; + if (line[0] == '!') + { + continue; + } + + if( line == "x" ) line = "-1"; + + int i = std::atoi(line.c_str()); + float v = std::atof(line.c_str()); + + switch (state) + { + case map_size: + res.count = i; + break; + case first_midi: + res.firstMidi = i; + break; + case last_midi: + res.lastMidi = i; + break; + case middle: + res.middleNote = i; + break; + case reference: + res.tuningConstantNote = i; + break; + case freq: + res.tuningFrequency = v; + break; + case degree: + res.octaveDegrees = i; + break; + case keys: + res.keys.push_back(i); + break; + } + if( state != keys ) state = (parsePosition)(state + 1); + } + + res.rawText = rawOSS.str(); + return res; +} + +KeyboardMapping readKBMFile(std::string fname) +{ + std::ifstream inf; + inf.open(fname); + if (!inf.is_open()) + { + return KeyboardMapping(); + } + + auto res = keyboardMappingFromStream(inf); + res.name = fname; + return res; +} + +KeyboardMapping parseKBMData(const std::string &d) +{ + std::istringstream iss(d); + auto res = keyboardMappingFromStream(iss); + res.name = "Mapping from Patch"; + return res; +} + +bool Scale::isValid() const +{ + if (count <= 0) + return false; + + // TODO check more things maybe... + return true; +} + +KeyboardMapping KeyboardMapping::tuneA69To(double freq) +{ + // There's a couple of ways to do this but since I want it to stream I will syntheitcally create + // a KBM file + std::ostringstream oss; + oss << R"KBM(! Dexed Synthetic Keyboard Tuning to Retune A69 +! +! Map Size +0 +! First note +0 +! Last note +127 +! First mapping +60 +! Reference Note +69 +! Reference Freqency +)KBM" << freq << R"KBM( +! Scale Degree +0 +! Mapping)KBM"; + return parseKBMData( oss.str() ); +} + +struct SCLAndKBMTuningState : public TuningState { + virtual bool is_standard_tuning() override { + return false; + } + + virtual int32_t midinote_to_logfreq(int midinote) override { + return current_logfreq_table_[midinote]; + } + + int current_logfreq_table_[128]; + float current_freq_table_[128]; + + bool retuneTo(const Scale& s) { + return retuneTo( s, KeyboardMapping() ); + } + + bool retuneTo(const KeyboardMapping &k) { + return retuneTo( Scale(), k ); + } + + // Again this is basically the modified surge implementation + bool retuneTo( const Scale &s, const KeyboardMapping &k ) { + currentScale = s; + currentMapping = k; + + if (!s.isValid()) + return false; + + float cp; + if( k.isStandardMapping ) + cp = 32; + else + cp = k.tuningFrequency / 8.175798915; + + float pitches[512]; + int posPitch0 = 256 + k.tuningConstantNote; + int posScale0 = 256 + k.middleNote; + float pitchMod = log(cp)/log(2) - 1; + + int scalePositionOfTuningNote = k.tuningConstantNote - k.middleNote; + if( k.count > 0 ) + scalePositionOfTuningNote = k.keys[scalePositionOfTuningNote]; + + float tuningCenterPitchOffset; + if( scalePositionOfTuningNote == 0 ) + tuningCenterPitchOffset = 0; + else + tuningCenterPitchOffset = s.tones[scalePositionOfTuningNote-1].floatValue - 1.0; + + pitches[posPitch0] = 1.0; + float table_pitch[512]; + + for (int i=0; i<512; ++i) + { + // TODO: ScaleCenter and PitchCenter are now two different notes. + int distanceFromPitch0 = i - posPitch0; + int distanceFromScale0 = i - posScale0; + + if( distanceFromPitch0 == 0 ) + { + table_pitch[i] = pow( 2.0, pitches[i] + pitchMod ); +#if DEBUG_SCALES + if( i > 296 && i < 340 ) + std::cout << "PITCH: i=" << i << " n=" << i - 256 + << " p=" << pitches[i] + << " tp=" << table_pitch[i] + << " fr=" << table_pitch[i] * 8.175798915 + << std::endl; +#endif + } + else + { + /* + We used to have this which assumed 1-12 + Now we have our note number, our distance from the + center note, and the key remapping + int rounds = (distanceFromScale0-1) / s.count; + int thisRound = (distanceFromScale0-1) % s.count; + */ + + int rounds; + int thisRound; + int disable = false; + if( k.isStandardMapping || ( k.count == 0 ) ) + { + rounds = (distanceFromScale0-1) / s.count; + thisRound = (distanceFromScale0-1) % s.count; + } + else + { + /* + ** Now we have this situation. We are at note i so we + ** are m away from the center note which is distanceFromScale0 + ** + ** If we mod that by the mapping size we know which note we are on + */ + int mappingKey = distanceFromScale0 % k.count; + if( mappingKey < 0 ) + mappingKey += k.count; + int cm = k.keys[mappingKey]; + int push = 0; + if( cm < 0 ) + { + disable = true; + } + else + { + push = mappingKey - cm; + } + rounds = (distanceFromScale0 - push - 1) / s.count; + thisRound = (distanceFromScale0 - push - 1) % s.count; +#ifdef DEBUG_SCALES + if( i > 296 && i < 340 ) + std::cout << "MAPPING n=" << i - 256 << " pushes ds0=" << distanceFromScale0 << " cmc=" << k.count << " tr=" << thisRound << " r=" << rounds << " mk=" << mappingKey << " cm=" << cm << " push=" << push << " dis=" << disable << " mk-p-1=" << mappingKey - push - 1 << std::endl; +#endif + + + } + + if( thisRound < 0 ) + { + thisRound += s.count; + rounds -= 1; + } +#if DEBUG_SCALES + float mul = pow( s.tones[s.count-1].floatValue, rounds); + float otp = table_pitch[i]; +#endif + if( disable ) + pitches[i] = 0; + else + pitches[i] = s.tones[thisRound].floatValue + rounds * (s.tones[s.count - 1].floatValue - 1.0) - tuningCenterPitchOffset; + + table_pitch[i] = pow( 2.0, pitches[i] + pitchMod ); + +#if DEBUG_SCALES + if( i > 296 && i < 340 ) + std::cout << "PITCH: i=" << i << " n=" << i - 256 + << " ds0=" << distanceFromScale0 + << " dp0=" << distanceFromPitch0 + << " r=" << rounds << " t=" << thisRound + << " p=" << pitches[i] + << " t=" << s.tones[thisRound].floatValue << " " << s.tones[thisRound ] + << " dis=" << disable + << " tp=" << table_pitch[i] + << " fr=" << table_pitch[i] * 8.175798915 + << " otp=" << otp + << " tcpo=" << tuningCenterPitchOffset + << " diff=" << table_pitch[i] - otp + + //<< " l2p=" << log(otp)/log(2.0) + //<< " l2p-p=" << log(otp)/log(2.0) - pitches[i] - rounds - 3 + << std::endl; +#endif + } + } + + // OK so now table_pitch is the pitch where 1 -> 8.17 and 32 -> 261 and it doubles. It is also offset by 256. So + for( int i=0; i<128; ++i ) + { + double tp = table_pitch[i + 256 ]; + double freq = tp * 8.175798915; + current_freq_table_[i] = freq; + double flog = log(freq) / log(2.0); + int res = (int)( ( 1 << 24 ) * flog ); + + current_logfreq_table_[i] = res; + } + + return true; + } + + Scale currentScale; + + virtual int scale_length() { return currentScale.count; } + + KeyboardMapping currentMapping; + + virtual std::string display_tuning_str() override { + std::ostringstream oss; + oss << "Current Tuning\n\n"; + oss << "SCL File:\n" << currentScale.rawText << "\n\n KBM File:\n" << currentMapping.rawText << "\n\n"; + oss << "Frequency by MidiNote\n"; + oss << "Note | Freq\n"; + for( int i=0; i<128; ++i ) + { + oss << i << " | " << current_freq_table_[i] << " Hz\n"; + } + return oss.str(); + } + + +}; + +std::shared_ptr createTuningFromSCLData( const std::string &scl ) +{ + auto k = parseSCLData(scl); + auto res = std::make_shared(); + res->retuneTo(k); + return res; +} + +std::shared_ptr createTuningFromKBMData( const std::string &kbm ) +{ + auto k = parseKBMData(kbm); + auto res = std::make_shared(); + res->retuneTo(Scale::evenTemperament12NoteScale(), k); + return res; +} + +std::shared_ptr createTuningFromSCLAndKBMData( const std::string &sclData, const std::string &kbmData ) +{ + auto s = parseSCLData(sclData); + auto k = parseKBMData(kbmData); + auto res = std::make_shared(); + res->retuneTo(s,k); + return res; +} diff --git a/Source/msfa/tuning.h b/Source/msfa/tuning.h new file mode 100644 index 00000000..d49b771a --- /dev/null +++ b/Source/msfa/tuning.h @@ -0,0 +1,24 @@ +#ifndef __SYNTH_TUNING_H +#define __SYNTH_TUNING_H + +#include "synth.h" +#include +#include + +class TuningState { +public: + virtual ~TuningState() { } + + virtual int32_t midinote_to_logfreq(int midinote) = 0; + virtual bool is_standard_tuning() { return true; } + virtual int scale_length() { return 12; } + virtual std::string display_tuning_str() { return "Standard Tuning"; } +}; + +std::shared_ptr createStandardTuning(); + +std::shared_ptr createTuningFromSCLData( const std::string &sclData ); +std::shared_ptr createTuningFromKBMData( const std::string &kbmData ); +std::shared_ptr createTuningFromSCLAndKBMData( const std::string &sclData, const std::string &kbmData ); + +#endif