diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index f98357cd..d184a9a1 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -27,6 +27,7 @@ juce_add_plugin("${BaseTargetName}" FORMATS ${DEXED_JUCE_FORMATS} PRODUCT_NAME "Dexed" DESCRIPTION "Dexed FM Synth" + BUNDLE_ID "com.digitalsuburban.Dexed" # To avoid CMake Warning at libs/JUCE/extras/Build/CMake/JUCEUtils.cmake ) clap_juce_extensions_plugin(TARGET ${BaseTargetName} diff --git a/Source/CartManager.cpp b/Source/CartManager.cpp index 3bd0b1bc..fede0229 100644 --- a/Source/CartManager.cpp +++ b/Source/CartManager.cpp @@ -302,6 +302,7 @@ void CartManager::programRightClicked(ProgramListBox *source, int pos) { if ( mainWindow->processor->sysexComm.isOutputActive() ) { uint8_t msg[163]; exportSysexPgm(msg, unpackPgm); + msg[2] |= mainWindow->processor->sysexComm.getChl(); mainWindow->processor->sysexComm.send(MidiMessage(msg, 163)); } break; diff --git a/Source/GlobalEditor.cpp b/Source/GlobalEditor.cpp index e97e5a23..57d5ba34 100644 --- a/Source/GlobalEditor.cpp +++ b/Source/GlobalEditor.cpp @@ -63,26 +63,34 @@ class MidiMonitor : public Component { class AboutBox : public DialogWindow { public: Image logo_png; - HyperlinkButton dexed; - HyperlinkButton surge; + std::unique_ptr dexed; // changed to std::unique_ptr from juce::ScopedPointer + std::unique_ptr surge; // changed to std__unique_ptr from juce::ScopedPointer AboutBox(Component *parent) : DialogWindow("About", Colour(0xFF000000), true), - dexed("https://asb2m10.github.io/dexed/", URL("https://asb2m10.github.io/dexed/")), - surge("https://surge-synthesizer.github.io/", URL("https://surge-synthesizer.github.io/")) { + dexed(std::make_unique("https://asb2m10.github.io/dexed/", URL("https://asb2m10.github.io/dexed/"))), + surge(std::make_unique("https://surge-synthesizer.github.io/", URL("https://surge-synthesizer.github.io/"))) + { setUsingNativeTitleBar(false); setAlwaysOnTop(true); logo_png = ImageCache::getFromMemory(BinaryData::dexedlogo_png, BinaryData::dexedlogo_pngSize); - setSize(logo_png.getWidth()+ 8, 500); + setSize(logo_png.getWidth() + 8, 500); centreAroundComponent(parent, getWidth(), getHeight()); - dexed.setColour(HyperlinkButton::ColourIds::textColourId, Colour(0xFF4ea097)); - dexed.setJustificationType(Justification::left); - dexed.setBounds(18, 433, getWidth() - 36, 30); - addAndMakeVisible(&dexed); - surge.setColour(HyperlinkButton::ColourIds::textColourId, Colour(0xFF4ea097)); - surge.setJustificationType(Justification::left); - surge.setBounds(18, 458, getWidth() - 36, 30); - addAndMakeVisible(&surge); + dexed->setColour(HyperlinkButton::ColourIds::textColourId, Colour(0xFF4ea097)); + dexed->setJustificationType(Justification::left); + dexed->setBounds(18, 433, getWidth() - 36, 30); + + surge->setColour(HyperlinkButton::ColourIds::textColourId, Colour(0xFF4ea097)); + surge->setJustificationType(Justification::left); + surge->setBounds(18, 458, getWidth() - 36, 30); + + // create a new Component to hold ''dexed'' and ''surge'' as subcomponents + // and set this holder Component as the content component of the DialogWindow + Component* holder = new Component(); + holder->setSize(getWidth(), getHeight()); + holder->addAndMakeVisible((juce::Component*)dexed.get()); + holder->addAndMakeVisible((juce::Component*)surge.get()); + setContentOwned(holder, true); // TODO: ''setContentComponent(holder, true, true);'' also worked; which is the better? } void closeButtonPressed() { @@ -324,28 +332,28 @@ GlobalEditor::GlobalEditor () initButton.reset (new juce::TextButton ("initButton")); addAndMakeVisible (initButton.get()); - initButton->setButtonText (TRANS("INIT")); + initButton->setButtonText (translate("INIT")); initButton->addListener (this); initButton->setBounds (100, 111, 50, 30); parmButton.reset (new juce::TextButton ("parmButton")); addAndMakeVisible (parmButton.get()); - parmButton->setButtonText (TRANS("PARM")); + parmButton->setButtonText (translate("PARM")); parmButton->addListener (this); parmButton->setBounds (52, 111, 50, 30); cartButton.reset (new juce::TextButton ("cartButton")); addAndMakeVisible (cartButton.get()); - cartButton->setButtonText (TRANS("CART")); + cartButton->setButtonText (translate("CART")); cartButton->addListener (this); cartButton->setBounds (3, 111, 50, 30); storeButton.reset (new juce::TextButton ("storeButton")); addAndMakeVisible (storeButton.get()); - storeButton->setButtonText (TRANS("STORE")); + storeButton->setButtonText (translate("STORE")); storeButton->addListener (this); storeButton->setBounds (270, 109, 50, 30); diff --git a/Source/OperatorEditor.cpp b/Source/OperatorEditor.cpp index b55d6372..517af590 100644 --- a/Source/OperatorEditor.cpp +++ b/Source/OperatorEditor.cpp @@ -150,7 +150,7 @@ OperatorEditor::OperatorEditor () opCoarse->setBounds (43, 24, 34, 34); khzDisplay.reset (new juce::Label ("khz", - TRANS("1,000 kHz"))); + translate("1,000 kHz"))); addAndMakeVisible (khzDisplay.get()); khzDisplay->setFont (juce::Font (12.60f, juce::Font::plain).withTypefaceStyle ("Regular")); khzDisplay->setJustificationType (juce::Justification::centred); @@ -180,7 +180,7 @@ OperatorEditor::OperatorEditor () sclLeftLevel.reset (new juce::Slider ("sclLeftLevel")); addAndMakeVisible (sclLeftLevel.get()); - sclLeftLevel->setTooltip (TRANS("Keyboard Scale Level Left Depth ")); + sclLeftLevel->setTooltip (translate("Keyboard Scale Level Left Depth ")); sclLeftLevel->setRange (0, 99, 1); sclLeftLevel->setSliderStyle (juce::Slider::RotaryVerticalDrag); sclLeftLevel->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -190,7 +190,7 @@ OperatorEditor::OperatorEditor () sclRightLevel.reset (new juce::Slider ("sclRightLevel")); addAndMakeVisible (sclRightLevel.get()); - sclRightLevel->setTooltip (TRANS("Keyboard Scale Level Right Depth ")); + sclRightLevel->setTooltip (translate("Keyboard Scale Level Right Depth ")); sclRightLevel->setRange (0, 99, 1); sclRightLevel->setSliderStyle (juce::Slider::RotaryVerticalDrag); sclRightLevel->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -200,7 +200,7 @@ OperatorEditor::OperatorEditor () sclLvlBrkPt.reset (new juce::Slider ("sclLvlBrkPt")); addAndMakeVisible (sclLvlBrkPt.get()); - sclLvlBrkPt->setTooltip (TRANS("Scale Level Breakpoint")); + sclLvlBrkPt->setTooltip (translate("Scale Level Breakpoint")); sclLvlBrkPt->setRange (0, 99, 1); sclLvlBrkPt->setSliderStyle (juce::Slider::LinearHorizontal); sclLvlBrkPt->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); @@ -210,7 +210,7 @@ OperatorEditor::OperatorEditor () sclRateScaling.reset (new juce::Slider ("sclRateScaling")); addAndMakeVisible (sclRateScaling.get()); - sclRateScaling->setTooltip (TRANS("Keyboard Rate Scaling")); + sclRateScaling->setTooltip (translate("Keyboard Rate Scaling")); sclRateScaling->setRange (0, 7, 1); sclRateScaling->setSliderStyle (juce::Slider::RotaryVerticalDrag); sclRateScaling->setTextBoxStyle (juce::Slider::NoTextBox, false, 80, 20); diff --git a/Source/ParamDialog.cpp b/Source/ParamDialog.cpp index 842c49a0..326243c6 100644 --- a/Source/ParamDialog.cpp +++ b/Source/ParamDialog.cpp @@ -56,7 +56,7 @@ ParamDialog::ParamDialog () sysexIn->setEditableText (false); sysexIn->setJustificationType (juce::Justification::centredLeft); sysexIn->setTextWhenNothingSelected (juce::String()); - sysexIn->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); + sysexIn->setTextWhenNoChoicesAvailable (translate("(no choices)")); sysexIn->addListener (this); sysexIn->setBounds (104, 244, 224, 24); @@ -66,7 +66,7 @@ ParamDialog::ParamDialog () sysexOut->setEditableText (false); sysexOut->setJustificationType (juce::Justification::centredLeft); sysexOut->setTextWhenNothingSelected (juce::String()); - sysexOut->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); + sysexOut->setTextWhenNoChoicesAvailable (translate("(no choices)")); sysexOut->addListener (this); sysexOut->setBounds (104, 280, 224, 24); @@ -85,10 +85,10 @@ ParamDialog::ParamDialog () engineReso->setEditableText (false); engineReso->setJustificationType (juce::Justification::centredLeft); engineReso->setTextWhenNothingSelected (juce::String()); - engineReso->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); - engineReso->addItem (TRANS("Modern (24-bit)"), 1); - engineReso->addItem (TRANS("Mark I"), 2); - engineReso->addItem (TRANS("OPL Series"), 3); + engineReso->setTextWhenNoChoicesAvailable (translate("(no choices)")); + engineReso->addItem (translate("Modern (24-bit)"), 1); + engineReso->addItem (translate("Mark I"), 2); + engineReso->addItem (translate("OPL Series"), 3); engineReso->addListener (this); engineReso->setBounds (160, 188, 168, 24); @@ -222,28 +222,28 @@ ParamDialog::ParamDialog () sclButton.reset (new juce::TextButton ("scl button")); addAndMakeVisible (sclButton.get()); - sclButton->setButtonText (TRANS("SCL")); + sclButton->setButtonText (translate("SCL")); sclButton->addListener (this); sclButton->setBounds (448, 205, 56, 30); kbmButton.reset (new juce::TextButton ("kbm button")); addAndMakeVisible (kbmButton.get()); - kbmButton->setButtonText (TRANS("KBM")); + kbmButton->setButtonText (translate("KBM")); kbmButton->addListener (this); kbmButton->setBounds (512, 205, 56, 30); showTunButton.reset (new juce::TextButton ("show tuning button")); addAndMakeVisible (showTunButton.get()); - showTunButton->setButtonText (TRANS("Show")); + showTunButton->setButtonText (translate("Show")); showTunButton->addListener (this); showTunButton->setBounds (576, 205, 48, 30); resetTuningButton.reset (new juce::TextButton ("reset tuning button")); addAndMakeVisible (resetTuningButton.get()); - resetTuningButton->setButtonText (TRANS("Reset")); + resetTuningButton->setButtonText (translate("Reset")); resetTuningButton->addListener (this); resetTuningButton->setBounds (632, 205, 48, 30); @@ -296,10 +296,10 @@ ParamDialog::ParamDialog () scalingFactor->setEditableText (false); scalingFactor->setJustificationType (juce::Justification::centredLeft); scalingFactor->setTextWhenNothingSelected (juce::String()); - scalingFactor->setTextWhenNoChoicesAvailable (TRANS("(no choices)")); - scalingFactor->addItem (TRANS("100 %"), 1); - scalingFactor->addItem (TRANS("125 %"), 2); - scalingFactor->addItem (TRANS("150 %"), 3); + scalingFactor->setTextWhenNoChoicesAvailable (translate("(no choices)")); + scalingFactor->addItem (translate("100 %"), 1); + scalingFactor->addItem (translate("125 %"), 2); + scalingFactor->addItem (translate("150 %"), 3); scalingFactor->addSeparator(); scalingFactor->addListener (this); @@ -386,7 +386,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 20, y = 16, width = 276, height = 23; - juce::String text (TRANS("Pitch Bend Range")); + juce::String text (translate("Pitch Bend Range")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -398,7 +398,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 20, y = 56, width = 276, height = 23; - juce::String text (TRANS("Pitch Bend Step")); + juce::String text (translate("Pitch Bend Step")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -410,7 +410,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 20, y = 318, width = 245, height = 23; - juce::String text (TRANS("DX7 Channel")); + juce::String text (translate("DX7 Channel")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -422,7 +422,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 20, y = 190, width = 276, height = 23; - juce::String text (TRANS("Engine Resolution")); + juce::String text (translate("Engine Resolution")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -452,7 +452,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 20, y = 96, width = 276, height = 23; - juce::String text (TRANS("Show Keyboard")); + juce::String text (translate("Show Keyboard")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -473,7 +473,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 368, y = 16, width = 276, height = 23; - juce::String text (TRANS("Wheel")); + juce::String text (translate("Wheel")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -485,7 +485,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 368, y = 96, width = 276, height = 23; - juce::String text (TRANS("Breath")); + juce::String text (translate("Breath")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -497,7 +497,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 368, y = 56, width = 276, height = 23; - juce::String text (TRANS("Foot")); + juce::String text (translate("Foot")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -509,7 +509,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 368, y = 136, width = 276, height = 23; - juce::String text (TRANS("After Touch")); + juce::String text (translate("After Touch")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -521,7 +521,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 528, y = 163, width = 48, height = 23; - juce::String text (TRANS("PITCH")); + juce::String text (translate("PITCH")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -533,7 +533,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 584, y = 163, width = 48, height = 23; - juce::String text (TRANS("AMP")); + juce::String text (translate("AMP")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -545,7 +545,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 640, y = 163, width = 48, height = 23; - juce::String text (TRANS("EG BIAS")); + juce::String text (translate("EG BIAS")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -566,7 +566,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 371, y = 208, width = 276, height = 25; - juce::String text (TRANS("Tuning")); + juce::String text (translate("Tuning")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -578,7 +578,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 371, y = 242, width = 157, height = 25; - juce::String text (TRANS("Transposition 12 as:")); + juce::String text (translate("Transposition 12 as:")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -599,7 +599,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 371, y = 290, width = 276, height = 27; - juce::String text (TRANS("MPE")); + juce::String text (translate("MPE")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -611,7 +611,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 528, y = 290, width = 119, height = 27; - juce::String text (TRANS("Bend Range")); + juce::String text (translate("Bend Range")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -623,7 +623,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 555, y = 242, width = 37, height = 25; - juce::String text (TRANS("12")); + juce::String text (translate("12")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -635,7 +635,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 659, y = 242, width = 45, height = 25; - juce::String text (TRANS("SCL")); + juce::String text (translate("SCL")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -647,7 +647,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 147, y = 16, width = 20, height = 23; - juce::String text (TRANS("up")); + juce::String text (translate("up")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -659,7 +659,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 240, y = 16, width = 20, height = 23; - juce::String text (TRANS("dn")); + juce::String text (translate("dn")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -671,7 +671,7 @@ void ParamDialog::paint (juce::Graphics& g) { int x = 20, y = 136, width = 276, height = 23; - juce::String text (TRANS("UI Scaling")); + juce::String text (translate("UI Scaling")); juce::Colour fillColour = juce::Colours::white; //[UserPaintCustomArguments] Customize the painting arguments here.. //[/UserPaintCustomArguments] @@ -685,14 +685,14 @@ void ParamDialog::paint (juce::Graphics& g) if ( ! JUCEApplication::isStandaloneApp() ) { g.setColour (Colours::white); g.setFont (Font (15.00f, Font::plain)); - g.drawText (TRANS("DX7 In"), + g.drawText (translate("DX7 In"), 20, 245, 131, 23, Justification::centredLeft, true); } g.setColour (Colours::white); g.setFont (Font (15.00f, Font::plain)); - g.drawText (TRANS("DX7 Out"), + g.drawText (translate("DX7 Out"), 20, 280, 131, 23, Justification::centredLeft, true); //[/UserPaint] @@ -1068,8 +1068,14 @@ bool ParamDialog::getDialogValues(Controllers &c, SysexComm &mgr, int *reso, boo void ParamDialog::setIsStandardTuning( bool b ) { is_standard_tuning_ = b; + + // Allow to see the actual tuning always. +/* + // Enable to ''showTunButton'' only if the tuning + // is not the standard one. if( showTunButton != nullptr ) showTunButton->setEnabled( ! b ); +*/ if( resetTuningButton != nullptr ) resetTuningButton->setEnabled( ! b ); } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 4f7d324e..9a0bbc12 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -432,12 +432,67 @@ void DexedAudioProcessorEditor::filesDropped (const StringArray &files, int x, i { if( files.size() != 1 ) return; auto fn = files[0]; - if( fn.endsWithIgnoreCase( ".scl" ) ) - { - processor->applySCLTuning( File( fn ) ); + try { + std::ifstream in(fn.toStdString(), std::ifstream::ate | std::ifstream::binary); + std::ifstream::pos_type filesize = in.tellg(); + if (fn.endsWithIgnoreCase(".scl")) + { + if (filesize == 0) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "File size error!", + "File \'" + fn.toStdString() + "\' is empty." + ); + } + else if (filesize > MAX_SCL_KBM_FILE_SIZE) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "File size error!", + "File \'" + fn.toStdString() + "\' has " + std::to_string(filesize) + " bytes, exceeding the maximum limit ("+std::to_string(MAX_SCL_KBM_FILE_SIZE)+")." + ); + } + else { + processor->applySCLTuning(File(fn)); + } + } + if (fn.endsWithIgnoreCase(".kbm")) + { + if (filesize == 0) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "File size error!", + "File \'" + fn.toStdString() + "\' is empty." + ); + } + else if (filesize > MAX_SCL_KBM_FILE_SIZE) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "File size error!", + "File \'" + fn.toStdString() + "\' has " + std::to_string(filesize) + " bytes, exceeding the maximum limit (" + std::to_string(MAX_SCL_KBM_FILE_SIZE) + ")." + ); + } + else { + processor->applyKBMMapping(File(fn)); + } + } } - if( fn.endsWithIgnoreCase( ".kbm" ) ) - { - processor->applyKBMMapping( File( fn ) ); + catch (const std::ios_base::failure& ex) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "I/O error!", + "Related to file \'" + fn.toStdString() + "\', an exception (std::ios_base::failure) occured: " + ex.what() + ); + } + catch (std::bad_alloc& ex) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "I/O error!", + "Related to file \'" + fn.toStdString() + "\', an exception (std::bad_alloc) occured: " + ex.what()); } + catch (...) { + AlertWindow::showMessageBox( + AlertWindow::WarningIcon, + "I/O error!", + "Related to file \'"+fn.toStdString()+"\', an unknown exception occured."); + }; } diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index f2c38177..81257c4e 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -82,6 +82,7 @@ DexedAudioProcessor::DexedAudioProcessor() Sin::init(); synthTuningState = createStandardTuning(); + synthTuningStateLast = createStandardTuning(); lastStateSave = 0; currentNote = -1; @@ -844,6 +845,7 @@ void dexed_trace(const char *source, const char *fmt, ...) { void DexedAudioProcessor::resetTuning(std::shared_ptr t) { synthTuningState = t; + synthTuningStateLast = t; for( int i=0; ituning_state_ = synthTuningState; @@ -857,11 +859,47 @@ void DexedAudioProcessor::retuneToStandard() } void DexedAudioProcessor::applySCLTuning() { - FileChooser fc( "Please select an SCL File", File(), "*.scl" ); - if( fc.browseForFileToOpen() ) - { - auto s = fc.getResult(); + FileChooser fc( "Please select a scale (.scl) file.", File(), "*.scl" ); + File s; + + // loop to enforce the proper selection + for (;;) { + // open file chooser dialog + if (!fc.browseForFileToOpen()) + // User cancelled + return; + s = fc.getResult(); + + // enforce file extenstion ''.scl''. + // (reason: the extension ''.scl'' is mandatory according to + // ''https://www.huygens-fokker.org/scala/scl_format.html'' + if (s.getFileExtension() != ".scl") { + AlertWindow::showMessageBox(AlertWindow::WarningIcon, "Invalid file type!", "Only files with the \".scl\" extension (in lowercase!) are allowed."); + continue; + } + + // enforce to select file below the max limit16KB sized files + if (s.getSize() > MAX_SCL_KBM_FILE_SIZE) { + std::string msg; + msg = "File size exceeded the maximum limit of " + std::to_string(MAX_SCL_KBM_FILE_SIZE) + " bytes."; + AlertWindow::showMessageBox(AlertWindow::WarningIcon, "File size error!", msg); + continue; + } + + // enforce to select non-empty file + // TODO: check, whether zero sized files may occur indeed here; if not, delete this if-statement, please + if (s.getSize() == 0) { + std::string msg; + msg = "File is empty."; + AlertWindow::showMessageBox(AlertWindow::WarningIcon, "File size error!", msg); + continue; + } + + // try to apply the SCL file applySCLTuning(s); + + // exit the loop + break; } } @@ -871,26 +909,73 @@ void DexedAudioProcessor::applySCLTuning(File s) { } void DexedAudioProcessor::applySCLTuning(std::string sclcontents) { - currentSCLData = sclcontents; - if( currentKBMData.size() < 1 ) { auto t = createTuningFromSCLData( sclcontents ); - resetTuning(t); + if (t != nullptr) { + resetTuning(t); // update tuning + currentSCLData = sclcontents; // remember this SCL data + synthTuningStateLast = t; // remember this whole state as a "last good working state" + } + else { + resetTuning(synthTuningStateLast); // revert to the "last good working state" + } } else { auto t = createTuningFromSCLAndKBMData( sclcontents, currentKBMData ); - resetTuning(t); + if (t != nullptr) { + resetTuning(t); // update tuning + currentSCLData = sclcontents; // remember this SCL data + synthTuningStateLast = t; // remember this whole state as a "last good working state" + } + else { + resetTuning(synthTuningStateLast); // revert to the "last good working state" + } } } void DexedAudioProcessor::applyKBMMapping() { - FileChooser fc( "Please select an KBM File", File(), "*.kbm" ); - if( fc.browseForFileToOpen() ) - { - auto s = fc.getResult(); + FileChooser fc( "Please select a keyboard map (.kbm) file.", File(), "*.kbm" ); + File s; + + // loop to enforce the proper selection + for (;;) { + // invoke file chooser dialog + if (!fc.browseForFileToOpen()) + return; // User cancelled + s = fc.getResult(); + + // enforce file extenstion ''.kbm''. + // (reason: the extension ''.kbm'' is mandatory according to + // ''https://www.huygens-fokker.org/scala/scl_format.html'' + if (s.getFileExtension() != ".kbm") { + AlertWindow::showMessageBox(AlertWindow::WarningIcon, "Invalid file type!", "Only files with the \".kbm\" extension (in lowercase!) are allowed."); + continue; + } + + // enforce to select file below the max limit16KB sized files + if (s.getSize() > MAX_SCL_KBM_FILE_SIZE) { + std::string msg; + msg = "File size exceeded the maximum limit of " + std::to_string(MAX_SCL_KBM_FILE_SIZE) + " bytes."; + AlertWindow::showMessageBox(AlertWindow::WarningIcon, "File size error!", msg); + continue; + } + + // enforce to select non-empty file + // TODO: check, whether zero sized files may occur indeed here; if not, delete this if-statement, please + if (s.getSize() == 0) { + std::string msg; + msg = "File is empty."; + AlertWindow::showMessageBox(AlertWindow::WarningIcon, "File size error!", msg); + continue; + } + + // try to apply KBM mapping applyKBMMapping(s); + + // exit the loop + break; } } @@ -900,17 +985,24 @@ void DexedAudioProcessor::applyKBMMapping( File s ) 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); +void DexedAudioProcessor::applyKBMMapping(std::string kbmcontents) { + if( currentSCLData.size() < 1 ) { + auto t = createTuningFromKBMData(kbmcontents); + if (t != nullptr) { + resetTuning(t); // update tuning + currentKBMData = kbmcontents; // remember this KBM data + synthTuningStateLast = t; // remember this whole state as a "last good working state" + } else { + resetTuning(synthTuningStateLast); // revert to the "last good working state" + } + } else { + auto t = createTuningFromSCLAndKBMData( currentSCLData, kbmcontents ); + if (t != nullptr) { + resetTuning(t); // update tuning + currentKBMData = kbmcontents; // remember this KBM data + synthTuningStateLast = t; // remember this whole state as a "last good working state" + } else { + resetTuning(synthTuningStateLast); // revert to "last good working state" + } } } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a95b170c..cee88122 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -59,6 +59,13 @@ enum DexedEngineResolution { DEXED_ENGINE_OPL }; +/// Maximum allowed size for SCL and KBM files. +/// (COMMENT: Since none of the 5175 .scl files in the Scala archive +/// at ‘https://www.huygens-fokker.org/scala/downloads.html#scales’ +/// exceed 6 KB (in 25th Mar 2024), a maximum size of 16 KB appears +/// to be a practical choice.) +const int MAX_SCL_KBM_FILE_SIZE = 16384; + //============================================================================== /** */ @@ -252,6 +259,12 @@ public : MTSClient *mtsClient; std::shared_ptr synthTuningState; + + // holds the previous working tuning state; + // used to restore tuning state when there was a problem + // with loading/applying a new .SCL and/or .KBM file + std::shared_ptr synthTuningStateLast; + // Prompt for a file void applySCLTuning(); void applyKBMMapping(); diff --git a/Source/TuningShow.cpp b/Source/TuningShow.cpp index 9d32b660..2b68df84 100644 --- a/Source/TuningShow.cpp +++ b/Source/TuningShow.cpp @@ -35,9 +35,9 @@ TuningShow::TuningShow () tabbedComponent.reset (new TabbedComponent (TabbedButtonBar::TabsAtTop)); addAndMakeVisible (tabbedComponent.get()); tabbedComponent->setTabBarDepth (30); - tabbedComponent->addTab (TRANS("Tuning"), Colours::lightgrey, new TableListBox(), true); - tabbedComponent->addTab (TRANS("SCL"), Colours::lightgrey, new TextEditor(), true); - tabbedComponent->addTab (TRANS("KBM"), Colours::lightgrey, new TextEditor(), true); + tabbedComponent->addTab (translate("Tuning"), Colours::lightgrey, new TableListBox(), true, 0); + tabbedComponent->addTab (translate("SCL"), Colours::lightgrey, new TextEditor(), true, 1); + tabbedComponent->addTab (translate("KBM"), Colours::lightgrey, new TextEditor(), true, 2); tabbedComponent->setCurrentTabIndex (0); tabbedComponent->setBounds (0, 0, 500, 400); @@ -55,6 +55,11 @@ TuningShow::TuningShow () sclt->setReadOnly(true); kbmt->setReadOnly(true); + sclt->setMultiLine(true); + sclt->setScrollbarsShown(true); + kbmt->setMultiLine(true); + kbmt->setScrollbarsShown(true); + table = dynamic_cast( tabbedComponent->getTabContentComponent(0) ); //[/Constructor] diff --git a/Source/msfa/tuning.cc b/Source/msfa/tuning.cc index 3f7620fe..d90606b1 100644 --- a/Source/msfa/tuning.cc +++ b/Source/msfa/tuning.cc @@ -8,6 +8,7 @@ #include #include +#include struct StandardTuning : public TuningState { StandardTuning() { @@ -55,25 +56,91 @@ std::shared_ptr createStandardTuning() std::shared_ptr createTuningFromSCLData( const std::string &scl ) { - auto s = Tunings::parseSCLData(scl); + Tunings::Scale s; + try { + s = Tunings::parseSCLData(scl); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error parsing SCL data for SCL tuning", + e.what(), + "OK"); + return nullptr; + } + auto res = std::make_shared(); - res->tuning = Tunings::Tuning( s ); + try { + res->tuning = Tunings::Tuning(s); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error creating tuning for SCL tuning", + e.what(), + "OK"); + res = nullptr; + } + return res; } std::shared_ptr createTuningFromKBMData( const std::string &kbm ) { - auto k = Tunings::parseKBMData(kbm); + Tunings::KeyboardMapping k; + try { + k = Tunings::parseKBMData(kbm); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error parsing KBM data for KBM tuning", + e.what(), + "OK"); + return nullptr; + } + auto res = std::make_shared(); - res->tuning = Tunings::Tuning( k ); + try { + res->tuning = Tunings::Tuning(k); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error creating tuning for KBM tuning", + e.what(), + "OK"); + res = nullptr; + } + return res; } std::shared_ptr createTuningFromSCLAndKBMData( const std::string &sclData, const std::string &kbmData ) { - auto s = Tunings::parseSCLData(sclData); - auto k = Tunings::parseKBMData(kbmData); + Tunings::Scale s; + try { + s = Tunings::parseSCLData(sclData); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error parsing SCL data for SCL/KBM tuning", + e.what(), + "OK"); + return nullptr; + } + + Tunings::KeyboardMapping k; + try { + k = Tunings::parseKBMData(kbmData); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error parsing KBM data for SCL/KBM tuning", + e.what(), + "OK"); + return nullptr; + } + auto res = std::make_shared(); - res->tuning = Tunings::Tuning( s, k ); + try { + res->tuning = Tunings::Tuning(s, k); + } catch (const std::exception& e) { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::AlertIconType::WarningIcon, + "Error creating tuning for SCL/KBM tuning", + e.what(), + "OK"); + res = nullptr; + } return res; } diff --git a/libs/JUCE b/libs/JUCE index ae514483..22df0d22 160000 --- a/libs/JUCE +++ b/libs/JUCE @@ -1 +1 @@ -Subproject commit ae5144833e852815d61642af87c69b9db44984f7 +Subproject commit 22df0d2266007bccb25d6ed52b9907f60d04e971 diff --git a/libs/clap-juce-extensions b/libs/clap-juce-extensions index 0a3e7a64..2c23b918 160000 --- a/libs/clap-juce-extensions +++ b/libs/clap-juce-extensions @@ -1 +1 @@ -Subproject commit 0a3e7a6494788ea525e95e12a8b6a51640dcdc3e +Subproject commit 2c23b918828ba5fbc5fcb4c95d3a046fbf7e9285 diff --git a/libs/surgesynthteam_tuningui b/libs/surgesynthteam_tuningui index 54f9a74c..d6b009ec 160000 --- a/libs/surgesynthteam_tuningui +++ b/libs/surgesynthteam_tuningui @@ -1 +1 @@ -Subproject commit 54f9a74cd55cdb33fb4d32d706067626857cfc75 +Subproject commit d6b009ec2b0e27232f4d5ddb6fbe0638bf948edd diff --git a/libs/tuning-library b/libs/tuning-library index 3bbe9514..b7842351 160000 --- a/libs/tuning-library +++ b/libs/tuning-library @@ -1 +1 @@ -Subproject commit 3bbe9514816e1ae674c207b09e9f20eea4df372a +Subproject commit b7842351c7a95ea007750661cf8dd9d6c3fa6916