diff --git a/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate b/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate index efbe52d..1de9aea 100644 Binary files a/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate and b/Builds/MacOSX/Dexed.xcodeproj/project.xcworkspace/xcuserdata/asb2m10.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/README.md b/README.md index 998af4d..429a8b6 100644 --- a/README.md +++ b/README.md @@ -13,25 +13,7 @@ the original machine. Dexed is licensed on the GPL v3. The msfa component (acronym for music synthesizer for android, see msfa in the source folder) stays on the Apache 2.0 license to able to collaborate between projects. -Features --------- -* Multi platform (OS X, Windows or Linux) and multi format (VST, AU); by using JUCE -* The sound engine [music-synthesizer-for-android](https://code.google.com/p/music-synthesizer-for-android) is closely modeled on the original DX7 characteristics -* 144 DAW automatable DX7 parameters available from one single panel -* Fully supports DX7 input and output Sysex messages; including controller change. This means that you can use this with a native DX7/TX7 as a patch editor and sysex manager -* Each operator have a realtime VU meter to know which one is active -* Can load/save any DX7/TX7 sysex programs. It is also possible to save a single program into a different sysex file. - -Binary downloads ----------------- -Dexed is not a finished product but it is stable enough to be used in a DAW environment: -in normal operation it shouldn't crash and the VST state saving works. If you don't see the -new version here but you see it in the change log, it's because this version is in development -(current sprint). Only officials (tested) builds are listed here. - -* Version 0.8.0 [vst win32/x64](http://le-son666.com/software/dexed/dexed-0.8.0-win.zip) - [vst os x](http://le-son666.com/software/dexed/dexed-0.8.0-osx.vst.zip) -* Version 0.7.0 [vst win32/x64](http://le-son666.com/software/dexed/dexed-0.7.0-win.zip) - [vst os x](http://le-son666.com/software/dexed/dexed-0.7.0-osx.vst.zip) -* Version 0.6.1 [vst win32/x64](http://le-son666.com/software/dexed/dexed-0.6.1-win.zip) - [vst os x](http://le-son666.com/software/dexed/dexed-0.6.1-osx.vst.zip) +[Dexed User Website](http://asb2m10.github.io/dexed) Changelog --------- @@ -70,71 +52,6 @@ Changelog * Knobs now works with vertical mouse drags * User DX7 zip cartridges -DX7 cartridges --------------- -You can now put user cartridges in the "Cartridges" directory once Dexed is started. You will be able to browse them once you use the [CART] button. To get the exact location of this directory, simply click on [FILE MGR], it should open Finder or File Explorer in the "Cartridges" directory. - -NOTE: Dexed_Cart.zip is no longer used. - -Engine Type ------------ -Dexed can be configured to try to use the original math limitation of a DX synthesizer. This does not -only apply to the DAC, it also involves the bit resolution of the sine waves and the way that the -amplitude is applied to each operator. Since all of this is experimental, multiple engines will be -available to be able to compare them easily. - -Dexed comes with 3 engine types : -* Modern : this is the original 24-bit music-synthesizer-for-android implementation. -* Mark I : It is the music-synthesizer-for-android implementation reduced to 8-bit. It is used to be able to see the difference between the OPL series that also uses 8-bit but with sums of logs to avoid multiplications. It will be upgraded to 10-bit/12-bit later. -* OPL Series : this is an experimental implementation of the [reversed engineered OPL family chips](http://sourceforge.net/projects/peepeeplayer). 8-bit. Keep in mind that the envelopes stills needs tuning. - -Using as a DX7 editor ---------------------- -You can use this plugin to edit your real DX7 patchs. Since midi sysex send/receive are quirky for the -majority of VST hosts, any sysex messages (editor messages) must be send or received with the external Dexed -midi interface. This is configurable in the "PARM" panel. By setting a DX7 in / DX7 out midi interface, -Dexed will listen to specific program/cartridge changes from your DX7 and send controller/program/cartridge -you edit. - -### Prerequisite -* Before you use this interface, your DX7 must be configured to send or receive sysex messages. Do this by hitting [FUNCTION] and [8] button -* Check the midi channel. By reading some of DX7 literature, most DX7 are supposed to support only 1 channel. Unless you are running a cluster of DX7, you should keep this to 1. -* Press [8] again and you should see: "SYS INFO AVAIL" or "SYS INFO UNAVAIL". Keep it to "SYS INFO AVAIL" to be able to receive sysex messages. This step is not required on a TX7. -* You need to remove MEMORY PROTECT on the internal or cartridge memory to be able to receive a Dexed 32 voice bulk dump. This is done by using the [MEMORY PROTECT] button and then by pushing [NO]. - -### To send to your DX7 -* If the midi out port is configured, any parameter change will be sent to your DX7. -* If the midi out port is configured, you should see the [SEND] button, if not this button won't be available. -* You can use this [SEND] button on Dexed to send program or cartridge changes to your DX7. Be sure you have set [MEMORY PROTECT] is set to NO so the cartridge (internal or external one) on the DX7 can be overridden. - -### To receive from your DX7 -* The midi in port must be configured. -* Receive a program by simply using [MEMORY SELECT] then the program you want to transmit ([1] to [32]). -* Receive a cartridge by pressing [FUNCTION] then 3 times on [8] you should see " MIDI TRANSMIT ? ". If you hit yes, Dexed should receive the complete cartridge. - -### Troubleshooting -* If you play on your DX7 keyboard, the |DX7 In| light should be flashing. Use this to test the midi in communication. -* If the data sent is corrupted (wrong checksum, DX7 crash), it might be the midi interface implementation. Default Windows USB midi driver are known to send corrupt sysex data. If it is the case, use a third party device (like the midiman uno) that have his own USB driver. -* If you are unable to open the interface (error message after the using the [PARM] dialog), it might be because the midi driver doesn't support multiple clients (common on Windows). Be sure that there are no other applications that are using the same midi interface. - -Randomized programs -------------------- -Dexed doesn't check the sysex checksum so you can load any type of files. If the checksum doesn't -match, it will tell you but load it anyway. This feature act as a kind of a randomization -for DX programs. - -Warning: loading corrupt/random data can crash the plugin and ultimatly your DAW. I'm doing my -best to filter out values that can crash the DX engine; but I keep it to the minimum to get the -digital circuit bending feel. - -Saving those corrupt/random sysex data will regenerate a 32 bulk program sysex dump with a -valid checksum for your DX7 keyboard. I'm in now way responsible if this breaks your DX7 keyboard. - -FAQ (possibly) --------------- -* Some programs can generate distortion : This is because the voice summing still needs some tuning. You can simply lower the volume on those programs. -* Some sysex files seems to be corrupted : Even if the sysex checksum doesn't match, Dexed will try to load it (this is a kind of randomize feature). Right now Dexed supports only original DX7 sysex, other DX family sysex (like the DX21) is considered as random data. - Credits & thanks ---------------- * DX Synth engine : Raph Levien and the [msfa](https://code.google.com/p/music-synthesizer-for-android) team diff --git a/Source/CartManager.cpp b/Source/CartManager.cpp index 90433d3..58ac6fd 100644 --- a/Source/CartManager.cpp +++ b/Source/CartManager.cpp @@ -45,12 +45,12 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : TopLevelWindow("Ca cartDir = DexedAudioProcessor::dexedCartDir; addAndMakeVisible(activeCart = new ProgramListBox("activepgm", 8)); - activeCart->setBounds(8, 430, 843, 100); + activeCart->setBounds(28, 441, 800, 96); activeCart->addListener(this); memset(browserSysex, 0, 4096); addAndMakeVisible(browserCart = new ProgramListBox("browserpgm", 2)); - browserCart->setBounds(635, 10, 210, 400); + browserCart->setBounds(635, 18, 200, 384); browserCart->addListener(this); // ------------------------- @@ -60,34 +60,46 @@ CartManager::CartManager(DexedAudioProcessorEditor *editor) : TopLevelWindow("Ca timeSliceThread.startThread(); cartBrowser = new FileTreeComponent(*cartBrowserList); addAndMakeVisible(cartBrowser); - cartBrowser->setBounds(5, 10, 620, 400); + cartBrowser->setBounds(23, 18, 590, 384); cartBrowser->setDragAndDropDescription("Sysex Browser"); cartBrowser->addListener(this); - - /*addAndMakeVisible(newButton = new TextButton("NEW")); - newButton->setBounds(400, 540, 50, 30);*/ addAndMakeVisible(closeButton = new TextButton("CLOSE")); - closeButton->setBounds(10, 540, 50, 30); + closeButton->setBounds(4, 545, 50, 30); + closeButton->addListener(this); + addAndMakeVisible(loadButton = new TextButton("LOAD")); - loadButton->setBounds(58, 540, 50, 30); + loadButton->setBounds(52, 545, 50, 30); loadButton->addListener(this); + addAndMakeVisible(saveButton = new TextButton("SAVE")); - saveButton->setBounds(106, 540, 50, 30); + saveButton->setBounds(100, 545, 50, 30); saveButton->addListener(this); - closeButton->addListener(this); addAndMakeVisible(fileMgrButton = new TextButton("SHOW DIR")); - fileMgrButton->setBounds(154, 540, 70, 30); - fileMgrButton->addListener(this); + fileMgrButton->setBounds(148, 545, 70, 30); + fileMgrButton->addListener(this); + + addAndMakeVisible(getDXPgmButton = new TextButton("GET DX7 PGM")); + getDXPgmButton->setBounds(668, 545, 95, 30); + getDXPgmButton->addListener(this); + + addAndMakeVisible(getDXCartButton = new TextButton("GET DX7 CART")); + getDXCartButton->setBounds(761, 545, 95, 30); + getDXCartButton->addListener(this); + } -// 856, 571 +CartManager::~CartManager() { + timeSliceThread.stopThread(500); +} void CartManager::paint(Graphics &g) { g.fillAll(DXLookNFeel::lightBackground); - //g.setColour(Colours::black); - //g.fillRect(0, 5, 859, 410); + g.setColour(DXLookNFeel::roundBackground); + g.fillRoundedRectangle(8, 418, 843, 126, 15); + g.setColour(Colours::whitesmoke); + g.drawText("currently loaded cartridge", 38, 410, 150, 40, Justification::left); } void CartManager::programSelected(ProgramListBox *source, int pos) { @@ -104,7 +116,7 @@ void CartManager::programSelected(ProgramListBox *source, int pos) { browserCart->setSelected(pos); repaint(); mainWindow->processor->updateProgramFromSysex((uint8_t *) unpackPgm); - mainWindow->processor->updateHostDisplay(); + mainWindow->processor->updateHostDisplay(); } } @@ -135,14 +147,49 @@ void CartManager::buttonClicked(juce::Button *buttonThatWasClicked) { void CartManager::fileDoubleClicked(const File& file) { if ( file.isDirectory() ) return; - mainWindow->loadCart(file); activeCart->setCartridge(mainWindow->processor->sysex); } void CartManager::fileClicked(const File& file, const MouseEvent& e) { - if ( ! e.mods.isLeftButtonDown() ) + if ( e.mods.isRightButtonDown() ) { + PopupMenu menu; + + menu.addItem(1000, "Open location"); + if ( ! file.isDirectory() ) { + menu.addItem(1010, "Send sysex cartridge to DX7"); + } + menu.addSeparator(); + menu.addItem(1020, "Refresh"); + + switch(menu.show()) { + case 1000: + file.revealToUser(); + break; + case 1010 : + break; + case 1020: + cartBrowser->refresh(); + break; + } return; + } +} + +void CartManager::setActiveProgram(int idx, String activeName) { + if ( activeCart->programNames[idx] == activeName ) { + activeCart->setSelected(idx); + browserCart->setSelected(-1); + } + activeCart->repaint(); +} + +void CartManager::resetActiveSysex() { + activeCart->setCartridge(mainWindow->processor->sysex); +} + +void CartManager::selectionChanged() { + File file = cartBrowser->getSelectedFile(); if ( file.isDirectory() ) return; @@ -152,36 +199,44 @@ void CartManager::fileClicked(const File& file, const MouseEvent& e) { ifstream fp_in(f.toRawUTF8(), ios::binary); if (fp_in.fail()) { AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", "Unable to open: " + f); - return; + return; } fp_in.read((char *)syx_data, 4104); fp_in.close(); memcpy(browserSysex, syx_data+6, 4096); int checksum = sysexChecksum(((char *) &browserSysex), 4096); - + if ( checksum != syx_data[4102] ) { String message = "Sysex import checksum doesnt match "; - message << checksum << " != " << syx_data[4102]; + message << ((int)checksum) << " != " << ((int)syx_data[4102]); - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Error", message); - return; + AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, "Warning", message); } - + browserCart->setSelected(-1); browserCart->setCartridge(browserSysex); } -void CartManager::setActiveProgram(int idx) { - activeCart->setSelected(idx); - browserCart->setSelected(-1); - activeCart->repaint(); -} +void CartManager::programRightClicked(ProgramListBox *source, int pos) { + PopupMenu menu; + + menu.addItem(1000, "Send program '" + source->programNames[pos] + "' to DX7"); + + if ( source == activeCart ) + menu.addItem(1010, "Send current sysex cartridge to DX7"); + + switch(menu.show()) { + case 1000: + break; + + case 1010: + mainWindow->processor->sendCurrentSysexCartridge(); + break; + } -void CartManager::resetActiveSysex() { - activeCart->setCartridge(mainWindow->processor->sysex); } // unused stuff from FileBrowserListener void CartManager::browserRootChanged (const File& newRoot) {} -void CartManager::selectionChanged() {} + diff --git a/Source/CartManager.h b/Source/CartManager.h index 2b077d2..4f8373f 100644 --- a/Source/CartManager.h +++ b/Source/CartManager.h @@ -32,6 +32,9 @@ class CartManager : public TopLevelWindow, public ButtonListener, public DragAn ScopedPointer saveButton; ScopedPointer closeButton; ScopedPointer fileMgrButton; + ScopedPointer getDXPgmButton; + ScopedPointer getDXCartButton; + ScopedPointer activeCart; ScopedPointer browserCart; @@ -40,7 +43,6 @@ class CartManager : public TopLevelWindow, public ButtonListener, public DragAn ScopedPointer syxFileFilter; TimeSliceThread timeSliceThread; ScopedPointer cartBrowserList; - File cartDir; DexedAudioProcessorEditor *mainWindow; @@ -48,6 +50,7 @@ class CartManager : public TopLevelWindow, public ButtonListener, public DragAn char browserSysex[4096]; public: CartManager(DexedAudioProcessorEditor *editor); + virtual ~CartManager(); void paint(Graphics& g); void buttonClicked (Button* buttonThatWasClicked); @@ -56,10 +59,11 @@ public: void fileDoubleClicked (const File& file); void browserRootChanged (const File& newRoot); - void setActiveProgram(int idx); + void setActiveProgram(int idx, String activeName); void resetActiveSysex(); virtual void programSelected(ProgramListBox *source, int pos) override; + virtual void programRightClicked(ProgramListBox *source, int pos) override; }; diff --git a/Source/DXComponents.cpp b/Source/DXComponents.cpp index 9a39453..fcb5f35 100644 --- a/Source/DXComponents.cpp +++ b/Source/DXComponents.cpp @@ -402,14 +402,5 @@ void ComboBoxImage::setImage(Image image, int pos[]) { itemPos[i] = pos[i]; } -void ProgramSelector::setInit() { - isInInit = true; -} - void ProgramSelector::paint(Graphics &g) { - g.setColour(Colours::white); - g.setFont(Font(15.00f, Font::plain)); - //g.drawText (, getLocalBounds(), - // Justification::centred, true); // draw some placeholder text - } diff --git a/Source/DXComponents.h b/Source/DXComponents.h index e67d60d..1db8cc2 100644 --- a/Source/DXComponents.h +++ b/Source/DXComponents.h @@ -71,9 +71,7 @@ public: }; class ProgramSelector : public ComboBox { - bool isInInit; public: - void setInit(); virtual void paint(Graphics &g) override; }; diff --git a/Source/DXLookNFeel.cpp b/Source/DXLookNFeel.cpp index 8715ea6..0dbc831 100644 --- a/Source/DXLookNFeel.cpp +++ b/Source/DXLookNFeel.cpp @@ -227,7 +227,7 @@ void DXLookNFeel::positionComboBoxText(ComboBox& box, Label& label) { // I'm not proud of this one, but really... it must be another way to do this.... ComboBoxImage* img = dynamic_cast(src); - if(img != 0) { + if( img != 0 ) { return; } @@ -239,6 +239,7 @@ DXLookNFeel * DXLookNFeel::ins = NULL; Colour DXLookNFeel::fillColour = Colour(77,159,151); Colour DXLookNFeel::lightBackground = Colour(78,72,63); Colour DXLookNFeel::background = Colour(60,50,47); +Colour DXLookNFeel::roundBackground = Colour(58,52,48); DXLookNFeel *DXLookNFeel::getLookAndFeel() { const ScopedLock locker(lock); diff --git a/Source/DXLookNFeel.h b/Source/DXLookNFeel.h index f1cdce5..f35efd3 100644 --- a/Source/DXLookNFeel.h +++ b/Source/DXLookNFeel.h @@ -56,6 +56,7 @@ public: static Colour fillColour; static Colour lightBackground; static Colour background; + static Colour roundBackground; }; #endif // DXLOOKNFEEL_H_INCLUDED diff --git a/Source/OperatorEditor.cpp b/Source/OperatorEditor.cpp index 0ce46b7..94c4dcb 100644 --- a/Source/OperatorEditor.cpp +++ b/Source/OperatorEditor.cpp @@ -489,6 +489,8 @@ void OperatorEditor::mouseDown(const MouseEvent &event) { popup.addItem(1, "Copy Operator Values"); popup.addItem(2, "Paste Envelope Values", processor->hasClipboardContent()); popup.addItem(3, "Paste Operator Values", processor->hasClipboardContent()); + popup.addSeparator(); + popup.addItem(4, "Send current program to DX7"); switch(popup.show()) { case 1: @@ -502,6 +504,10 @@ void OperatorEditor::mouseDown(const MouseEvent &event) { case 3: processor->pasteOpFromClipboard(internalOp); break; + + case 4: + processor->sendCurrentSysexProgram(); + break; } } diff --git a/Source/PluginData.cpp b/Source/PluginData.cpp index 4cf80c2..8aac694 100644 --- a/Source/PluginData.cpp +++ b/Source/PluginData.cpp @@ -297,6 +297,24 @@ void DexedAudioProcessor::pasteEnvFromClipboard(int destOp) { triggerAsyncUpdate(); } +void DexedAudioProcessor::sendCurrentSysexProgram() { + uint8_t raw[167]; + + exportSysexPgm((char *) raw, data, sysexComm.getChl()); + if ( sysexComm.isOutputActive() ) { + sysexComm.send(MidiMessage(raw, 163)); + } +} + +void DexedAudioProcessor::sendCurrentSysexCartridge() { + uint8_t raw[4104]; + + exportSysexCart((char *) raw, (char *) &sysex, sysexComm.getChl()); + if ( sysexComm.isOutputActive() ) { + sysexComm.send(MidiMessage(raw, 4104)); + } +} + bool DexedAudioProcessor::hasClipboardContent() { return clipboardContent != -1; } @@ -318,6 +336,9 @@ void DexedAudioProcessor::getStateInformation(MemoryBlock& destData) { dexedState.setAttribute("currentProgram", currentProgram); dexedState.setAttribute("monoMode", monoMode); dexedState.setAttribute("engineType", (int) engineType); + + if ( activeFileCartridge.exists() ) + dexedState.setAttribute("activeFileCartridge", activeFileCartridge.getFullPathName()); char sysex_blob[4104]; exportSysexCart((char *) &sysex_blob, (char *) sysex, 0); @@ -350,6 +371,10 @@ void DexedAudioProcessor::setStateInformation(const void* source, int sizeInByte setEngineType(root->getIntAttribute("engineType", 0)); monoMode = root->getIntAttribute("monoMode", 0); + File possibleCartridge = File(root->getStringAttribute("activeFileCartridge")); + if ( possibleCartridge.exists() ) + activeFileCartridge = possibleCartridge; + XmlElement *dexedBlob = root->getChildByName("dexedBlob"); if ( dexedBlob == NULL ) { TRACE("dexedBlob element not found"); @@ -383,7 +408,7 @@ void DexedAudioProcessor::resolvAppDir() { #if JUCE_MAC || JUCE_IOS dexedAppDir = File("~/Library/Application Support/DigitalSuburban/Dexed"); #elif JUCE_WINDOWS - dexedAppDir = File(File::userApplicationDataDirectory).getChildFile("DigitalSuburban").getChildFile("Dexed")); + dexedAppDir = File::getSpecialLocation(File::userApplicationDataDirectory).getChildFile("DigitalSuburban").getChildFile("Dexed")); #else // char xdgHomeDefault[] = ; char *xdgHome = getenv("XDG_DATA_HOME"); diff --git a/Source/PluginData.h b/Source/PluginData.h index 2c08c2d..166a810 100644 --- a/Source/PluginData.h +++ b/Source/PluginData.h @@ -24,6 +24,8 @@ #include "../JuceLibraryCode/JuceHeader.h" #define SYSEX_SIZE 4104 +#include + enum UnpackedOffset { egRate, egLevel = 4, diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 0a4bf53..dde92fc 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -40,7 +40,7 @@ public: setUsingNativeTitleBar(false); setAlwaysOnTop(true); about_png = ImageCache::getFromMemory(BinaryData::about_png, BinaryData::about_pngSize); - setSize( about_png.getWidth(), about_png.getHeight()); + setSize(about_png.getWidth(), about_png.getHeight()); centreAroundComponent (parent, getWidth(), getHeight()); } @@ -108,9 +108,6 @@ DexedAudioProcessorEditor::DexedAudioProcessorEditor (DexedAudioProcessor* owner global.setBounds(2,436,864,144); global.bind(this); - sendPopup.addItem(1, "Send program to DX7"); - sendPopup.addItem(2, "Send cartridge to DX7"); - global.setMonoState(processor->isMonoMode()); rebuildProgramCombobox(); @@ -159,10 +156,12 @@ void DexedAudioProcessorEditor::loadCart(File file) { rebuildProgramCombobox(); global.programs->setSelectedId(processor->getCurrentProgram()+1, dontSendNotification); processor->updateHostDisplay(); + + processor->activeFileCartridge = file; } void DexedAudioProcessorEditor::saveCart() { - FileChooser fc ("Export DX sysex...", File::nonexistent, "*.syx", 1); + FileChooser fc ("Export DX sysex...", processor->dexedCartDir, "*.syx", 1); if ( fc.browseForFileToSave(true) ) { String f = fc.getResults().getReference(0).getFullPathName(); char syx_data[4104]; @@ -181,31 +180,6 @@ void DexedAudioProcessorEditor::saveCart() { } } -void DexedAudioProcessorEditor::sendToDx7() { - int result = sendPopup.show(); - - if ( result == 1 ) { - uint8_t raw[167]; - - exportSysexPgm((char *) raw, processor->data, processor->sysexComm.getChl()); - if ( processor->sysexComm.isOutputActive() ) { - processor->sysexComm.send(MidiMessage(raw, 163)); - } - global.setSystemMessage(String("Done sending program")); - return; - } - - if ( result == 2 ) { - uint8_t raw[4104]; - - exportSysexCart((char *) raw, (char *) &processor->sysex, processor->sysexComm.getChl()); - if ( processor->sysexComm.isOutputActive() ) { - processor->sysexComm.send(MidiMessage(raw, 4104)); - } - global.setSystemMessage(String("Done sending cartridge")); - } -} - void DexedAudioProcessorEditor::parmShow() { int tp = processor->getEngineType(); @@ -271,54 +245,70 @@ void DexedAudioProcessorEditor::updateUI() { void DexedAudioProcessorEditor::rebuildProgramCombobox() { global.programs->clear(dontSendNotification); + for(int i=0;igetNumPrograms();i++) { String id; id << (i+1) << ". " << processor->getProgramName(i); global.programs->addItem(id, i+1); } + global.programs->setSelectedId(processor->getCurrentProgram()+1, dontSendNotification); - cartManager.setActiveProgram(processor->getCurrentProgram()); + + String name = normalizeSysexName((const char *) processor->data+145); + cartManager.setActiveProgram(processor->getCurrentProgram(), name); + if ( name != processor->getProgramName(processor->getCurrentProgram()) ) + global.programs->setText("**. " + name, dontSendNotification); + cartManager.resetActiveSysex(); } void DexedAudioProcessorEditor::storeProgram() { - String currentName(processor->getProgramName(processor->getCurrentProgram())); + String currentName = normalizeSysexName((const char *) processor->data+145); char destSysex[4096]; File *externalFile = NULL; - + memcpy(&destSysex, processor->sysex, 4096); + bool activeCartridgeFound = processor->activeFileCartridge.exists(); + while (true) { String msg; + if ( externalFile == NULL ) { - msg = "Store program to current cartridge"; + if ( activeCartridgeFound ) + msg = "Store program to current (" + processor->activeFileCartridge.getFileName() + ") / new cartridge"; + else + msg = "Store program to current / new cartridge"; } else { msg = "Store program to " + externalFile->getFileName(); } - AlertWindow dialog(String("Store Program"), msg, AlertWindow::NoIcon, this); - dialog.addTextEditor(String("Name"), currentName, String("Name"), false); + AlertWindow dialog("Store Program", msg, AlertWindow::NoIcon, this); + dialog.addTextEditor("Name", currentName, String("Name"), false); // TODO: fix the name length to 10 StringArray programs; extractProgramNames((char *) &destSysex, programs); + dialog.addComboBox("Dest", programs, "Program Destination"); - dialog.addComboBox(String("Dest"), programs); - ScopedPointer saveToDisk = nullptr; if ( externalFile == NULL ) { - saveToDisk = new ToggleButton("Save Changes to disk"); - saveToDisk->setSize(300, 30); - dialog.addCustomComponent(saveToDisk); - } + StringArray saveAction; + saveAction.add("Store program to DAW plugin state"); + saveAction.add("Store program and create a new copy of the .syx cartridge"); + if ( activeCartridgeFound ) + saveAction.add("Store program and overwrite current .syx cartridge"); + dialog.addComboBox("SaveAction", saveAction, "Store Action"); + } + dialog.addButton("OK", 0, KeyPress(KeyPress::returnKey)); dialog.addButton("CANCEL", 1, KeyPress(KeyPress::escapeKey)); dialog.addButton("EXTERNAL FILE", 2, KeyPress()); int response = dialog.runModalLoop(); if ( response == 2 ) { - FileChooser fc("Destination Sysex", File::nonexistent, "*.syx;*.SYX;*.*", 1); + FileChooser fc("Destination Sysex", processor->dexedCartDir, "*.syx;*.SYX;*.*", 1); if ( fc.browseForFileToOpen() ) { if ( externalFile != NULL ) @@ -335,8 +325,8 @@ void DexedAudioProcessorEditor::storeProgram() { } if ( response == 0 ) { - TextEditor *name = dialog.getTextEditor(String("Name")); - ComboBox *dest = dialog.getComboBoxComponent(String("Dest")); + TextEditor *name = dialog.getTextEditor("Name"); + ComboBox *dest = dialog.getComboBoxComponent("Dest"); int programNum = dest->getSelectedItemIndex(); String programName(name->getText()); @@ -351,6 +341,23 @@ void DexedAudioProcessorEditor::storeProgram() { rebuildProgramCombobox(); processor->setCurrentProgram(programNum); processor->updateHostDisplay(); + + int action = dialog.getComboBoxComponent("SaveAction")->getSelectedItemIndex(); + if ( action > 0 ) { + File destination = processor->activeFileCartridge; + if ( ! destination.exists() ) { + FileChooser fc("Destination Sysex", processor->dexedCartDir, "*.syx", 1); + if ( ! fc.browseForFileToSave(true) ) + break; + destination = fc.getResult(); + } + char sysexFile[4104]; + exportSysexCart((char *) &sysexFile, (char *) &processor->sysex, 0); + if ( ! destination.replaceWithData(sysexFile, 4104) ) { + AlertWindow::showMessageBoxAsync(AlertWindow::WarningIcon, "Write error", "Unable to write file"); + } + processor->activeFileCartridge = destination; + } } else { packProgram((uint8_t *) &destSysex, (uint8_t *) processor->data, programNum, programName); char sysexFile[4104]; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 942fc1d..703d03d 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -20,7 +20,6 @@ #ifndef PLUGINEDITOR_H_INCLUDED #define PLUGINEDITOR_H_INCLUDED - #include "../JuceLibraryCode/JuceHeader.h" #include "PluginProcessor.h" #include "OperatorEditor.h" @@ -36,12 +35,12 @@ class DexedAudioProcessorEditor : public AudioProcessorEditor, public ComboBoxListener, public Timer { PopupMenu cartPopup; - PopupMenu sendPopup; MidiKeyboardComponent midiKeyboard; OperatorEditor operators[6]; Colour background; CartManager cartManager; + public: DexedAudioProcessor *processor; GlobalEditor global; @@ -58,7 +57,6 @@ public: void saveCart(); void initProgram(); void storeProgram(); - void sendToDx7(); void cartShow(); void parmShow(); }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 2bf2136..89245ca 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -439,7 +439,7 @@ void DexedAudioProcessor::handleIncomingMidiMessage(MidiInput* source, const Mid TRACE("program update sysex"); updateProgramFromSysex(buf+6); - String name = normalizeSysexName((const char *) buf+151); + String name = normalizeSysexName((const char *) buf+145); packProgram((uint8_t *) sysex, (uint8_t *) data, currentProgram, name); programNames.set(currentProgram, name); } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 43fe5bc..89cf7a9 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -49,8 +49,6 @@ enum DexedEngineResolution { DEXED_ENGINE_OPL }; -//extern File dexedWorkdir; - //============================================================================== /** */ @@ -117,8 +115,6 @@ class DexedAudioProcessor : public AudioProcessor, public AsyncUpdater, public char clipboardContent; void resolvAppDir(); - File activeCartridge; - public : // in MIDI units (0x4000 is neutral) Controllers controllers; @@ -129,6 +125,7 @@ public : //CartridgeManager cartManager; SysexComm sysexComm; VoiceStatus voiceStatus; + File activeFileCartridge; bool forceRefreshUI; float vuSignal; @@ -177,6 +174,8 @@ public : void copyToClipboard(int srcOp); void pasteOpFromClipboard(int destOp); void pasteEnvFromClipboard(int destOp); + void sendCurrentSysexProgram(); + void sendCurrentSysexCartridge(); bool hasClipboardContent(); //============================================================================== diff --git a/Source/ProgramListBox.cpp b/Source/ProgramListBox.cpp index d835148..7cf1d78 100644 --- a/Source/ProgramListBox.cpp +++ b/Source/ProgramListBox.cpp @@ -33,20 +33,40 @@ ProgramListBox::ProgramListBox(const String name, int numCols) : Component(name) void ProgramListBox::paint(Graphics &g) { int pgm = 0; + g.setColour(Colour(20,18,18)); + g.fillRect(0,0,getWidth(), getHeight()); + g.setColour(Colour(0,0,0)); + g.drawLine(0,0,getWidth(), 0, 2); + g.setColour(Colour(3,3,1)); + g.drawLine(0,0,0,getHeight(),2); + g.setColour(Colour(34,32,32)); + g.drawLine(getWidth(), 3, getWidth(), getHeight(), 2); + g.setColour(Colour(75,73,73)); + g.drawLine(0,getHeight(),getWidth(),getHeight(), 2); + + + const float dashLength[] = { 4, 4 }; + + g.setColour(Colour(83,76,69)); + for(int i=1;i line(cellWidth*i,0,cellWidth*i,getHeight()); + g.drawDashedLine(line, dashLength, 2); + } + for(int i=1;i line(2, cellHeight*i,getWidth(),cellHeight*i); + g.drawDashedLine(line, dashLength, 2); + } + for(int i=0;iprogramSelected(this, pos); - } - repaint(); } void ProgramListBox::mouseDown(const MouseEvent &event) { + if ( ! hasContent ) + return; + if ( ! event.mods.isRightButtonDown() ) + return; + int pos = programPosition(event); + if ( listener != nullptr ) + listener->programRightClicked(this, pos); } void ProgramListBox::setSelected(int idx) { diff --git a/Source/ProgramListBox.h b/Source/ProgramListBox.h index a5bc4ec..9131263 100644 --- a/Source/ProgramListBox.h +++ b/Source/ProgramListBox.h @@ -28,6 +28,7 @@ class ProgramListBoxListener { public: virtual ~ProgramListBoxListener() {} virtual void programSelected(ProgramListBox *source, int pos) = 0; + virtual void programRightClicked(ProgramListBox *source, int pos) = 0; }; class ProgramListBox : public Component { @@ -36,12 +37,12 @@ class ProgramListBox : public Component { bool showPgmNumber; int cols, rows; int cellWidth, cellHeight; - - StringArray programNames; int programPosition(const MouseEvent &event); int selectedPgm; public: + StringArray programNames; + ProgramListBox(const String name, int numCols); void addListener(ProgramListBoxListener *listener); void paint(Graphics &g);