gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 1a7066c10556f7c32f4706c1dffd89015e42821f
parent a8ea6a13094ec1e7e86f51df65cac5446e9806e4
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Tue, 13 Aug 2024 17:51:22 +0200

Merge branch 'priv/n2x' into oss/main

Diffstat:
MCMakeLists.txt | 2+-
Mbase.cmake | 18++++++++++++++++--
Mdoc/changelog.txt | 16++++++++++++++++
Mscripts/Jenkinsfile | 72+++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mscripts/JenkinsfileMulti | 24+++++++++++++++++++-----
Mscripts/synths.cmake | 3+++
Msource/CMakeLists.txt | 40+++++++++++++++++++++++-----------------
Asource/baseLib/CMakeLists.txt | 23+++++++++++++++++++++++
Asource/baseLib/binarystream.cpp | 24++++++++++++++++++++++++
Asource/baseLib/binarystream.h | 470+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/baseLib/configFile.cpp | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/baseLib/configFile.h | 20++++++++++++++++++++
Asource/baseLib/hybridcontainer.h | 30++++++++++++++++++++++++++++++
Asource/baseLib/md5.cpp | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/baseLib/md5.h | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/baseLib/semaphore.h | 37+++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/CMakeLists.txt | 25+++++++++++++++++++++++++
Asource/hardwareLib/am29f.cpp | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/am29f.h | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/dspBootCode.cpp | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/dspBootCode.h | 37+++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/haltDSP.cpp | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/haltDSP.h | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2c.cpp | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2c.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2cFlash.cpp | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/i2cFlash.h | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/lcd.cpp | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/lcd.h | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/lcdfonts.cpp | 2675+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/lcdfonts.h | 8++++++++
Asource/hardwareLib/sciMidi.cpp | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/sciMidi.h | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/hardwareLib/syncUCtoDSP.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/juce.cmake | 1+
Msource/jucePluginEditorLib/patchmanager/patchmanager.cpp | 14++++++++------
Msource/jucePluginEditorLib/patchmanager/patchmanager.h | 9++++++++-
Msource/jucePluginEditorLib/pluginEditor.cpp | 55+++++++++++++++++++++++++++++++++++++++++++------------
Msource/jucePluginEditorLib/pluginEditor.h | 15+++++++++++++--
Msource/jucePluginEditorLib/pluginEditorState.h | 2--
Msource/jucePluginEditorLib/pluginProcessor.cpp | 10+++++-----
Msource/jucePluginEditorLib/pluginProcessor.h | 4++--
Msource/jucePluginLib/CMakeLists.txt | 1+
Msource/jucePluginLib/clipboard.h | 4+++-
Msource/jucePluginLib/controller.cpp | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msource/jucePluginLib/controller.h | 38+++++++++++++++++++++++---------------
Asource/jucePluginLib/controllermap.cpp | 38++++++++++++++++++++++++++++++++++++++
Asource/jucePluginLib/controllermap.h | 24++++++++++++++++++++++++
Msource/jucePluginLib/midipacket.cpp | 30++++++++++++++++++++++--------
Msource/jucePluginLib/midipacket.h | 24++++++++++++++++--------
Msource/jucePluginLib/midiports.cpp | 9+++++----
Msource/jucePluginLib/midiports.h | 11++++++++---
Msource/jucePluginLib/parameter.cpp | 27++++++++++++++++++++-------
Msource/jucePluginLib/parameter.h | 18+++++-------------
Msource/jucePluginLib/parameterbinding.cpp | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msource/jucePluginLib/parameterbinding.h | 7++++++-
Msource/jucePluginLib/parameterdescriptions.cpp | 53+++++++++++++++++++----------------------------------
Msource/jucePluginLib/parameterdescriptions.h | 7++++---
Msource/jucePluginLib/parameterlinks.cpp | 8++++----
Msource/jucePluginLib/parameterlinks.h | 4++--
Msource/jucePluginLib/patchdb/datasource.cpp | 8++++----
Msource/jucePluginLib/patchdb/datasource.h | 6+++---
Msource/jucePluginLib/patchdb/db.cpp | 19++++++++++---------
Msource/jucePluginLib/patchdb/db.h | 5++++-
Msource/jucePluginLib/patchdb/patch.cpp | 8++++----
Msource/jucePluginLib/patchdb/patch.h | 6+++---
Msource/jucePluginLib/patchdb/patchmodifications.cpp | 8++++----
Msource/jucePluginLib/patchdb/patchmodifications.h | 4++--
Msource/jucePluginLib/patchdb/tags.cpp | 14+++++++-------
Msource/jucePluginLib/patchdb/tags.h | 10+++++-----
Msource/jucePluginLib/processor.cpp | 35+++++++++++++++++++----------------
Msource/jucePluginLib/processor.h | 16++++++++++------
Msource/jucePluginLib/types.h | 6++++--
Msource/juceUiLib/button.cpp | 1+
Msource/juceUiLib/button.h | 18++++++++++++++++++
Msource/juceUiLib/buttonStyle.cpp | 26++++++++++++++++++++++++++
Msource/juceUiLib/uiObject.cpp | 14++++++++++++--
Msource/juceUiLib/uiObjectStyle.cpp | 11++++++++---
Msource/juceUiLib/uiObjectStyle.h | 2++
Msource/mqJucePlugin/PluginEditorState.cpp | 101+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msource/mqJucePlugin/PluginEditorState.h | 26++++++++++++++------------
Msource/mqJucePlugin/PluginProcessor.cpp | 69+++++++++++++++++++++++++++++++++------------------------------------
Msource/mqJucePlugin/PluginProcessor.h | 28+++++++++++++---------------
Msource/mqJucePlugin/mqController.cpp | 913+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msource/mqJucePlugin/mqController.h | 208++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msource/mqJucePlugin/mqEditor.cpp | 2+-
Msource/mqJucePlugin/mqEditor.h | 5++---
Msource/mqJucePlugin/mqFrontPanel.h | 3+--
Msource/mqJucePlugin/mqLcd.cpp | 8+++-----
Msource/mqJucePlugin/mqPartButton.cpp | 25++++++++++++++-----------
Msource/mqJucePlugin/mqPartButton.h | 23+++++++++++------------
Msource/mqJucePlugin/mqPartSelect.cpp | 103+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msource/mqJucePlugin/mqPartSelect.h | 44+++++++++++++++++++++-----------------------
Msource/mqJucePlugin/mqPatchManager.cpp | 11-----------
Msource/mqJucePlugin/mqPatchManager.h | 4+---
Msource/mqLib/CMakeLists.txt | 1+
Msource/mqLib/lcd.cpp | 2+-
Msource/mqLib/lcd.h | 4++--
Msource/mqLib/microq.cpp | 10++++------
Msource/mqLib/mqdsp.cpp | 49+++++++++++++++++++------------------------------
Msource/mqLib/mqdsp.h | 6+++++-
Msource/mqLib/mqhardware.cpp | 10+++++-----
Msource/mqLib/mqhardware.h | 9++++-----
Msource/mqLib/mqmc.cpp | 2+-
Msource/mqLib/mqmc.h | 5+++--
Msource/mqLib/rom.cpp | 13+++++++++++++
Msource/mqLib/rom.h | 5++---
Asource/mqLib/romloader.cpp | 28++++++++++++++++++++++++++++
Asource/mqLib/romloader.h | 12++++++++++++
Msource/mqTestConsole/mqTestConsole.cpp | 6+++---
Asource/nord/CMakeLists.txt | 5+++++
Asource/nord/n2x/CMakeLists.txt | 8++++++++
Asource/nord/n2x/n2xJucePlugin/CMakeLists.txt | 34++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xArp.cpp | 23+++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xArp.h | 16++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xController.cpp | 424+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xController.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xEditor.cpp | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xEditor.h | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xFocusedParameter.cpp | 20++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xFocusedParameter.h | 18++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xLcd.cpp | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xLcd.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xLfo.cpp | 44++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xLfo.h | 29+++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xMasterVolume.cpp | 33+++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xMasterVolume.h | 31+++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xOctLed.cpp | 28++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xOctLed.h | 15+++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xParameterDrivenLed.cpp | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xParameterDrivenLed.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPart.cpp | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPart.h | 20++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPartLed.cpp | 33+++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPartLed.h | 19+++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xParts.cpp | 45+++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xParts.h | 27+++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPatchManager.cpp | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPatchManager.h | 33+++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPluginEditorState.cpp | 46++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPluginEditorState.h | 23+++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPluginProcessor.cpp | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xPluginProcessor.h | 20++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xVmMap.cpp | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/n2xVmMap.h | 35+++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/parameterDescriptions_n2x.json | 586+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/DSEG14Classic-BoldItalic.ttf | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/DSEG7Classic-BoldItalic.ttf | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/assets.cmake | 24++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/background.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_a.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_b.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_black.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_browser.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_c.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_d.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_extra.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_main.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_red.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_round.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/h_slider.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/knob_big.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/n2xTrancy.json | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main_arpeggiator.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main_lfo2.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_2_browser.png | 0
Asource/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_3_extra.png | 0
Asource/nord/n2x/n2xLib/CMakeLists.txt | 34++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xdevice.cpp | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xdevice.h | 41+++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xdsp.cpp | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xdsp.h | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xflash.cpp | 22++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xflash.h | 17+++++++++++++++++
Asource/nord/n2x/n2xLib/n2xfrontpanel.cpp | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xfrontpanel.h | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xhardware.cpp | 356+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xhardware.h | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xhdi08.cpp | 5+++++
Asource/nord/n2x/n2xLib/n2xhdi08.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xmc.cpp | 332+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xmc.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xmiditypes.h | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xrom.cpp | 28++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xrom.h | 16++++++++++++++++
Asource/nord/n2x/n2xLib/n2xromdata.cpp | 33+++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xromdata.h | 33+++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xromloader.cpp | 20++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xromloader.h | 14++++++++++++++
Asource/nord/n2x/n2xLib/n2xstate.cpp | 498+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xLib/n2xstate.h | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Csource/juceUiLib/button.cpp -> source/nord/n2x/n2xLib/n2xsync.cpp | 0
Asource/nord/n2x/n2xLib/n2xsync.h | 16++++++++++++++++
Asource/nord/n2x/n2xLib/n2xtypes.h | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/nord/n2x/n2xTestConsole/CMakeLists.txt | 15+++++++++++++++
Asource/nord/n2x/n2xTestConsole/n2xTestConsole.cpp | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/osTIrusJucePlugin/OsTIrusEditorState.cpp | 2+-
Msource/osTIrusJucePlugin/OsTIrusEditorState.h | 6++----
Msource/osTIrusJucePlugin/OsTIrusProcessor.h | 2+-
Msource/osirusJucePlugin/OsirusEditorState.cpp | 2+-
Msource/osirusJucePlugin/OsirusEditorState.h | 6++----
Msource/osirusJucePlugin/OsirusProcessor.h | 2+-
Msource/synthLib/CMakeLists.txt | 6+-----
Dsource/synthLib/binarystream.cpp | 24------------------------
Dsource/synthLib/binarystream.h | 470-------------------------------------------------------------------------------
Dsource/synthLib/configFile.cpp | 62--------------------------------------------------------------
Dsource/synthLib/configFile.h | 20--------------------
Msource/synthLib/device.h | 7+++++++
Msource/synthLib/dspMemoryPatch.cpp | 2+-
Msource/synthLib/dspMemoryPatch.h | 7++++---
Dsource/synthLib/hybridcontainer.h | 30------------------------------
Dsource/synthLib/md5.cpp | 145-------------------------------------------------------------------------------
Dsource/synthLib/md5.h | 88-------------------------------------------------------------------------------
Msource/synthLib/resamplerInOut.cpp | 32+++++++++++++++++++++-----------
Msource/virusJucePlugin/FxPage.cpp | 2+-
Msource/virusJucePlugin/Leds.cpp | 2+-
Msource/virusJucePlugin/Leds.h | 9++++++---
Msource/virusJucePlugin/ParameterNames.h | 2+-
Msource/virusJucePlugin/Parts.cpp | 8++++----
Msource/virusJucePlugin/PatchManager.cpp | 6+++---
Msource/virusJucePlugin/PatchManager.h | 4++--
Msource/virusJucePlugin/VirusController.cpp | 8++++----
Msource/virusJucePlugin/VirusController.h | 12+++++-------
Msource/virusJucePlugin/VirusEditor.cpp | 10+++++-----
Msource/virusJucePlugin/VirusEditor.h | 11+++++++----
Msource/virusJucePlugin/VirusEditorState.cpp | 176++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msource/virusJucePlugin/VirusEditorState.h | 22+++++++++++++---------
Msource/virusJucePlugin/VirusProcessor.cpp | 198++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msource/virusJucePlugin/VirusProcessor.h | 106++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msource/virusLib/dspMemoryPatches.cpp | 2+-
Msource/virusLib/dspMemoryPatches.h | 2+-
Msource/virusLib/romfile.h | 4++--
Msource/virusLib/romloader.cpp | 6++++--
Msource/wLib/CMakeLists.txt | 7+------
Dsource/wLib/am29f.cpp | 140-------------------------------------------------------------------------------
Dsource/wLib/am29f.h | 68--------------------------------------------------------------------
Dsource/wLib/dspBootCode.h | 74--------------------------------------------------------------------------
Dsource/wLib/lcd.cpp | 219-------------------------------------------------------------------------------
Dsource/wLib/lcd.h | 88-------------------------------------------------------------------------------
Dsource/wLib/lcdfonts.cpp | 2675-------------------------------------------------------------------------------
Dsource/wLib/lcdfonts.h | 8--------
Msource/wLib/wHardware.cpp | 20++++----------------
Msource/wLib/wHardware.h | 9++++++---
Dsource/wLib/wMidi.cpp | 101-------------------------------------------------------------------------------
Dsource/wLib/wMidi.h | 46----------------------------------------------
Msource/wLib/wRom.cpp | 5++++-
Msource/wLib/wRom.h | 6++++++
Msource/xtJucePlugin/PluginEditorState.cpp | 96+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msource/xtJucePlugin/PluginEditorState.h | 25+++++++++++++------------
Msource/xtJucePlugin/PluginProcessor.cpp | 63++++++++++++++++++++++++++++++---------------------------------
Msource/xtJucePlugin/PluginProcessor.h | 27+++++++++++++--------------
Msource/xtJucePlugin/weData.h | 4++--
Msource/xtJucePlugin/xtController.cpp | 1080+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msource/xtJucePlugin/xtController.h | 238++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msource/xtJucePlugin/xtEditor.cpp | 2+-
Msource/xtJucePlugin/xtEditor.h | 5++---
Msource/xtJucePlugin/xtLcd.cpp | 114+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msource/xtJucePlugin/xtLcd.h | 38+++++++++++++++++++++-----------------
Msource/xtJucePlugin/xtPatchManager.cpp | 11-----------
Msource/xtJucePlugin/xtPatchManager.h | 4+---
Msource/xtLib/xtDSP.cpp | 53+++++++++++++++++++++--------------------------------
Msource/xtLib/xtDSP.h | 5++++-
Msource/xtLib/xtHardware.cpp | 2+-
Msource/xtLib/xtHardware.h | 9++++-----
Msource/xtLib/xtUc.h | 4++--
Msource/xtTestConsole/xtTestConsole.cpp | 4++--
267 files changed, 13941 insertions(+), 6658 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -19,7 +19,7 @@ if(APPLE) message("CMAKE_OSX_DEPLOYMENT_TARGET: " ${CMAKE_OSX_DEPLOYMENT_TARGET}) endif() -project(gearmulator VERSION 1.3.18) +project(gearmulator VERSION 1.3.19) include(base.cmake) include(CTest) diff --git a/base.cmake b/base.cmake @@ -56,7 +56,7 @@ elseif(APPLE) "-framework OpenGL" "-framework QuartzCore" ) - string(APPEND CMAKE_C_FLAGS_RELEASE " -funroll-loops -Ofast -flto") + string(APPEND CMAKE_C_FLAGS_RELEASE " -funroll-loops -Ofast -flto -fno-stack-protector") string(APPEND CMAKE_CXX_FLAGS_RELEASE " -funroll-loops -Ofast -flto -fno-stack-protector") else() message("CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) @@ -64,10 +64,24 @@ else() if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES arm AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES aarch64) string(APPEND CMAKE_CXX_FLAGS " -msse") endif() - string(APPEND CMAKE_C_FLAGS_RELEASE " -Ofast") + string(APPEND CMAKE_C_FLAGS_RELEASE " -Ofast -fno-stack-protector") string(APPEND CMAKE_CXX_FLAGS_RELEASE " -Ofast -fno-stack-protector") string(APPEND CMAKE_CXX_FLAGS_DEBUG " -rdynamic") execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE) + + # Good atomics are important on aarch64, they exist on ARMv8.1a or higher + # Check some known common machines and tell compiler if present + execute_process(COMMAND uname -a COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_A) + if( + UNAME_A MATCHES rk3588 # Orange Pi 5 variants + OR + UNAME_A MATCHES rock-5b # Raxda Rock 5B + OR + UNAME_A MATCHES rpi-2712 # Raspberry Pi 5 + ) + string(APPEND CMAKE_CXX_FLAGS " -march=armv8.2-a") + string(APPEND CMAKE_C_FLAGS " -march=armv8.2-a") + endif() endif() message( STATUS "Architecture: ${ARCHITECTURE}" ) diff --git a/doc/changelog.txt b/doc/changelog.txt @@ -1,5 +1,11 @@ Release Notes +1.3.19 + +Framework: + +- [Fix] CLAP: Ranges of discrete parameters were not reported properly + 1.3.18 Framework: @@ -8,12 +14,22 @@ Framework: - [Fix] Physical MIDI ports were not opened before GUI was opened +NodalRed2x: + +- [Imp] Implement Morph parameters / VM Map button +- [Imp] Implement Master Volume +- [Imp] Implement Arp Toggle LED/Button +- [Imp] Implement Octave LED + +- [Fix] Do not attempt to boot a rom that is not for N2x + Vavra: - [Imp] Physical MIDI in/out can now be selected via global context menu Xenia: +- [Fix] Do not expose parameters for parts 9-16 as the machine only has 8 - [Fix] Physical MIDI in/out port selectors didn't work - [Fix] LFO 2 rate knob in sync mode adjusting LFO 1 instead of LFO 2 diff --git a/scripts/Jenkinsfile b/scripts/Jenkinsfile @@ -3,8 +3,6 @@ pipeline { environment { GIT_URL = credentials('dsp56300_gitUrl') - BUILD_JUCE = needsJuce() - BUILD_FX_PLUGIN = buildFxPlugins() CMAKE_BUILD_DIR = 'temp/cmake_jenkins' } parameters @@ -16,6 +14,11 @@ pipeline { booleanParam(name: 'IntegrationTests', defaultValue: true, description: '') booleanParam(name: 'Upload', defaultValue: false, description: '') string(name: 'UploadFolder', defaultValue: "", description: '') + booleanParam(name: 'SynthOsirus', defaultValue: true, description: '') + booleanParam(name: 'SynthOsTIrus', defaultValue: true, description: '') + booleanParam(name: 'SynthVavra', defaultValue: true, description: '') + booleanParam(name: 'SynthXenia', defaultValue: false, description: '') + booleanParam(name: 'SynthNodalRed2x', defaultValue: false, description: '') } stages { stage("Checkout") { @@ -23,6 +26,16 @@ pipeline { script { currentBuild.displayName += " - ${params.AgentLabel} - ${params.Branch}" currentBuild.description = "Integration Tests: ${params.IntegrationTests}\nDeploy: ${params.Deploy}\nUpload: ${params.Upload}" + + if(params.FXPlugins) currentBuild.displayName += " FX" + if(params.Deploy) currentBuild.displayName += " d" + if(params.IntegrationTests) currentBuild.displayName += " i" + if(params.Upload) currentBuild.displayName += " u" + if(params.SynthOsirus) currentBuild.displayName += " Sabc" + if(params.SynthOsTIrus) currentBuild.displayName += " Sti" + if(params.SynthVavra) currentBuild.displayName += " SmQ" + if(params.SynthXenia) currentBuild.displayName += " Sxt" + if(params.SynthNodalRed2x) currentBuild.displayName += " Sn2x" } doCheckout() } @@ -115,12 +128,11 @@ def deleteFile(name) def needsJuce() { - //return params.Deploy || params.Upload ? 'ON' : 'OFF' - return true + return "on" } def buildFxPlugins() { - return params.FXPlugins ? 'ON' : 'OFF' + return params.FXPlugins ? 'on' : 'off' } def cmakeBuildDir() { @@ -144,27 +156,40 @@ def doCheckout() ) } +def getSynths() +{ + def synths = + "-Dgearmulator_SYNTH_OSIRUS=" + (params.SynthOsirus ? "on" : "off") + + " -Dgearmulator_SYNTH_OSTIRUS=" + (params.SynthOsTIrus ? "on" : "off") + + " -Dgearmulator_SYNTH_VAVRA=" + (params.SynthVavra ? "on" : "off") + + " -Dgearmulator_SYNTH_XENIA=" + (params.SynthXenia ? "on" : "off") + + " -Dgearmulator_SYNTH_NODALRED2X=" + (params.SynthNodalRed2x ? "on" : "off") + + return synths +} + def cmakeBuild() { - def buildDir = cmakeBuildDir(); + def buildDir = cmakeBuildDir() + def synths = getSynths() + def juce = needsJuce() + def fx = buildFxPlugins() withCredentials([file(credentialsId: 'rclone_dsp56300_conf', variable: 'RCLONE_CONF')]) { - if(hasLabel("mac")) { - genericSh "cmake . -G Xcode -B ${buildDir} -Dgearmulator_BUILD_JUCEPLUGIN=${BUILD_JUCE} -Dgearmulator_BUILD_FX_PLUGIN=${BUILD_FX_PLUGIN} -DCMAKE_BUILD_TYPE=Release" - } else { - genericSh "cmake . -B ${buildDir} -Dgearmulator_BUILD_JUCEPLUGIN=${BUILD_JUCE} -Dgearmulator_BUILD_FX_PLUGIN=${BUILD_FX_PLUGIN} -DCMAKE_BUILD_TYPE=Release" - } + genericSh "cmake -Dgearmulator_SOURCE_DIR=../ -Dgearmulator_BINARY_DIR=../${buildDir} -Dgearmulator_BUILD_JUCEPLUGIN=${juce} -Dgearmulator_BUILD_FX_PLUGIN=${fx} -DCMAKE_BUILD_TYPE=Release ${synths} -P scripts/generate.cmake" + } - dir(cmakeBuildDir()) { - if(hasLabel("m2")) { - genericSh 'cmake --build . --config Release --parallel 6' - } else if(hasLabel("pi5")) { - genericSh 'cmake --build . --config Release --parallel 2' - } else { - genericSh 'cmake --build . --config Release --parallel 2' - } - } + def parallel = 2; + + if(hasLabel("win")) { + parallel = 12; + } else if(hasLabel("m2")) { + parallel = 6; + } else if(hasLabel("pi5")) { + paraellel = 2; } + + genericSh "cmake --build ${buildDir} --config Release --parallel ${parallel}" } def doIntegrationTests() @@ -185,10 +210,11 @@ def doPack() def copyArtefacts(local, remote) { + def buildDir = cmakeBuildDir() + def synths = getSynths() + withCredentials([file(credentialsId: 'rclone_dsp56300_conf', variable: 'RCLONE_CONF')]) { - dir(cmakeBuildDir()) { - genericSh "cmake -DUPLOAD_LOCAL=${local} -DUPLOAD_REMOTE=${remote} -DFOLDER=${params.Branch}/${params.UploadFolder} -P ../../scripts/deploy.cmake" - } + genericSh "cmake -Dgearmulator_BINARY_DIR=${buildDir} -DUPLOAD_LOCAL=${local} -DUPLOAD_REMOTE=${remote} -DFOLDER=${params.UploadFolder} ${synths} -P scripts/deployAll.cmake" } } diff --git a/scripts/JenkinsfileMulti b/scripts/JenkinsfileMulti @@ -6,12 +6,17 @@ pipeline { booleanParam(name: 'LinuxX86', defaultValue: true, description: '') booleanParam(name: 'Win', defaultValue: true, description: '') booleanParam(name: 'Mac', defaultValue: true, description: '') - choice(name: 'Branch', choices: ['osirus', 'ostirus', 'vavra'], description: '') + choice(name: 'Branch', choices: ['main'], description: '') booleanParam(name: 'FXPlugins', defaultValue: true, description: '') - booleanParam(name: 'IntegrationTests', defaultValue: false, description: '') + booleanParam(name: 'IntegrationTests', defaultValue: true, description: '') booleanParam(name: 'Deploy', defaultValue: false, description: '') booleanParam(name: 'Upload', defaultValue: false, description: '') - choice(name: 'UploadFolder', choices: ['/internal', '', '/alpha', '/beta', '/donators', ''], description: '') + choice(name: 'UploadFolder', choices: ['internal', '', 'alpha', 'beta', 'donators', ''], description: '') + booleanParam(name: 'SynthOsirus', defaultValue: true, description: '') + booleanParam(name: 'SynthOsTIrus', defaultValue: true, description: '') + booleanParam(name: 'SynthVavra', defaultValue: true, description: '') + booleanParam(name: 'SynthXenia', defaultValue: true, description: '') + booleanParam(name: 'SynthNodalRed2x', defaultValue: false, description: '') } stages { stage('Prepare') { @@ -27,6 +32,11 @@ pipeline { if(params.IntegrationTests) currentBuild.displayName += " i" if(params.Deploy) currentBuild.displayName += " d" if(params.Upload) currentBuild.displayName += " u" + if(params.SynthOsirus) currentBuild.displayName += " Sabc" + if(params.SynthOsTIrus) currentBuild.displayName += " Sti" + if(params.SynthVavra) currentBuild.displayName += " SmQ" + if(params.SynthXenia) currentBuild.displayName += " Sxt" + if(params.SynthNodalRed2x) currentBuild.displayName += " Sn2x" currentBuild.description = "Integration Tests: ${params.IntegrationTests}\nDeploy: ${params.Deploy}" } @@ -73,8 +83,7 @@ pipeline { def startBuildJob(label) { -// build job: 'dsp56300', parameters: [[$class: 'LabelParameterValue', name: 'Agent', label: "win" ]] - build job: 'dsp56300', parameters: + build job: 'dsp56300_main', parameters: [ [$class: 'StringParameterValue', name: 'AgentLabel', value: "${label}"], [$class: 'StringParameterValue', name: 'Branch', value: params.Branch], @@ -83,5 +92,10 @@ def startBuildJob(label) [$class: 'BooleanParameterValue', name: 'IntegrationTests', value: params.IntegrationTests], [$class: 'BooleanParameterValue', name: 'FXPlugins', value: params.FXPlugins], [$class: 'StringParameterValue', name: 'UploadFolder', value: params.UploadFolder], + [$class: 'BooleanParameterValue', name: 'SynthOsirus', value: params.SynthOsirus], + [$class: 'BooleanParameterValue', name: 'SynthOsTIrus', value: params.SynthOsTIrus], + [$class: 'BooleanParameterValue', name: 'SynthVavra', value: params.SynthVavra], + [$class: 'BooleanParameterValue', name: 'SynthXenia', value: params.SynthXenia], + [$class: 'BooleanParameterValue', name: 'SynthNodalRed2x', value: params.SynthNodalRed2x], ] } diff --git a/scripts/synths.cmake b/scripts/synths.cmake @@ -3,17 +3,20 @@ set(synths gearmulator_SYNTH_OSTIRUS gearmulator_SYNTH_VAVRA gearmulator_SYNTH_XENIA + gearmulator_SYNTH_NODALRED2X ) set(gearmulator_SYNTH_OSIRUS_name Osirus) set(gearmulator_SYNTH_OSTIRUS_name OsTIrus) set(gearmulator_SYNTH_VAVRA_name Vavra) set(gearmulator_SYNTH_XENIA_name Xenia) +set(gearmulator_SYNTH_NODALRED2X_name NodalRed2x) set(gearmulator_SYNTH_OSIRUS_folder osirus) set(gearmulator_SYNTH_OSTIRUS_folder ostirus) set(gearmulator_SYNTH_VAVRA_folder vavra) set(gearmulator_SYNTH_XENIA_folder xenia) +set(gearmulator_SYNTH_NODALRED2X_folder nodalred2x) macro(validateToggle NAME) if(NOT DEFINED ${NAME} OR (NOT ${${NAME}} STREQUAL "on" AND NOT ${${NAME}} STREQUAL "off")) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt @@ -4,6 +4,7 @@ option(${CMAKE_PROJECT_NAME}_SYNTH_OSIRUS "Build Osirus" on) option(${CMAKE_PROJECT_NAME}_SYNTH_OSTIRUS "Build OsTIrus" on) option(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA "Build Vavra" on) option(${CMAKE_PROJECT_NAME}_SYNTH_XENIA "Build Xenia" on) +option(${CMAKE_PROJECT_NAME}_SYNTH_NODALRED2X "Build NodalRed2x" on) # ----------------- DSP56300 emulator @@ -15,6 +16,7 @@ add_subdirectory(dsp56300/source) # ----------------- Common libraries used by all synths +add_subdirectory(baseLib) add_subdirectory(synthLib) add_subdirectory(libresample) @@ -31,29 +33,29 @@ if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN_CLAP) add_subdirectory(clap-juce-extensions) endif() - add_subdirectory(jucePluginLib) - add_subdirectory(juceUiLib) - add_subdirectory(jucePluginEditorLib) - add_subdirectory(jucePluginData) + add_subdirectory(jucePluginLib EXCLUDE_FROM_ALL) + add_subdirectory(juceUiLib EXCLUDE_FROM_ALL) + add_subdirectory(jucePluginEditorLib EXCLUDE_FROM_ALL) + add_subdirectory(jucePluginData EXCLUDE_FROM_ALL) include(juce.cmake) endif() # ----------------- dependencies -if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA OR ${CMAKE_PROJECT_NAME}_SYNTH_XENIA) - add_subdirectory(mc68k) - add_subdirectory(wLib) -endif() + +add_subdirectory(mc68k EXCLUDE_FROM_ALL) +add_subdirectory(hardwareLib EXCLUDE_FROM_ALL) +add_subdirectory(wLib EXCLUDE_FROM_ALL) # ----------------- Synths Osirus/OsTIrus if(${CMAKE_PROJECT_NAME}_SYNTH_OSIRUS OR ${CMAKE_PROJECT_NAME}_SYNTH_OSTIRUS) - add_subdirectory(virusLib) - add_subdirectory(virusConsoleLib) + add_subdirectory(virusLib EXCLUDE_FROM_ALL) + add_subdirectory(virusConsoleLib EXCLUDE_FROM_ALL) add_subdirectory(virusTestConsole) add_subdirectory(virusIntegrationTest) if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) - add_subdirectory(virusJucePlugin) + add_subdirectory(virusJucePlugin EXCLUDE_FROM_ALL) if(${CMAKE_PROJECT_NAME}_SYNTH_OSIRUS) add_subdirectory(osirusJucePlugin) endif() @@ -66,22 +68,22 @@ endif() # ----------------- Synth Vavra if(${CMAKE_PROJECT_NAME}_SYNTH_VAVRA) - add_subdirectory(mqLib) + add_subdirectory(mqLib EXCLUDE_FROM_ALL) # needed for test console set(CPPTERMINAL_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(CPPTERMINAL_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) set(CPPTERMINAL_ENABLE_TESING OFF CACHE BOOL "" FORCE) - add_subdirectory(cpp-terminal) + add_subdirectory(cpp-terminal EXCLUDE_FROM_ALL) if(NOT ANDROID) - add_subdirectory(portmidi) + add_subdirectory(portmidi EXCLUDE_FROM_ALL) endif() set(PA_USE_ASIO OFF CACHE BOOL "" FORCE) - add_subdirectory(portaudio) + add_subdirectory(portaudio EXCLUDE_FROM_ALL) - add_subdirectory(mqConsoleLib) + add_subdirectory(mqConsoleLib EXCLUDE_FROM_ALL) add_subdirectory(mqTestConsole) add_subdirectory(mqPerformanceTest) @@ -93,10 +95,14 @@ endif() # ----------------- Synth Xenia if(${CMAKE_PROJECT_NAME}_SYNTH_XENIA) - add_subdirectory(xtLib) + add_subdirectory(xtLib EXCLUDE_FROM_ALL) add_subdirectory(xtTestConsole) if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) add_subdirectory(xtJucePlugin) endif() endif() + +# ----------------- all nords + +add_subdirectory(nord) diff --git a/source/baseLib/CMakeLists.txt b/source/baseLib/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.10) +project(baseLib) + +add_library(baseLib STATIC) + +set(SOURCES + binarystream.cpp binarystream.h + configFile.cpp configFile.h + hybridcontainer.h + md5.cpp md5.h + semaphore.h +) + +target_sources(baseLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +if(NOT MSVC) + target_link_libraries(baseLib PUBLIC dl) +endif() + +set_property(TARGET baseLib PROPERTY FOLDER "Gearmulator") + +target_include_directories(baseLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/baseLib/binarystream.cpp b/source/baseLib/binarystream.cpp @@ -0,0 +1,24 @@ +#include "binarystream.h" + +namespace baseLib +{ + BinaryStream BinaryStream::readChunk() + { + Chunk chunk; + chunk.read(*this); + return std::move(chunk.data); + } + + BinaryStream BinaryStream::tryReadChunkInternal(const char* _4Cc, const uint32_t _version) + { + Chunk chunk; + chunk.read(*this); + if(chunk.version != _version) + return {}; + if(0 != strcmp(chunk.fourCC, _4Cc)) + return {}; + return std::move(chunk.data); + } + + template BinaryStream BinaryStream::tryReadChunk(char const(& _4Cc)[5], uint32_t _version); +} diff --git a/source/baseLib/binarystream.h b/source/baseLib/binarystream.h @@ -0,0 +1,470 @@ +#pragma once + +#include <cassert> +#include <cstdint> +#include <functional> +#include <sstream> +#include <vector> +#include <cstring> + +namespace baseLib +{ + class StreamBuffer + { + public: + StreamBuffer() = default; + explicit StreamBuffer(std::vector<uint8_t>&& _buffer) : m_vector(std::move(_buffer)) + { + } + explicit StreamBuffer(const size_t _capacity) + { + m_vector.reserve(_capacity); + } + StreamBuffer(uint8_t* _buffer, const size_t _size) : m_buffer(_buffer), m_size(_size), m_fixedSize(true) + { + } + StreamBuffer(StreamBuffer& _parent, const size_t _childSize) : m_buffer(&_parent.buffer()[_parent.tellg()]), m_size(_childSize), m_fixedSize(true) + { + // force eof if range is not valid + if(_parent.tellg() + _childSize > _parent.size()) + { + assert(false && "invalid range"); + m_readPos = _childSize; + } + + // seek parent forward + _parent.seekg(_parent.tellg() + _childSize); + } + StreamBuffer(StreamBuffer&& _source) noexcept + : m_buffer(_source.m_buffer) + , m_size(_source.m_size) + , m_fixedSize(_source.m_fixedSize) + , m_readPos(_source.m_readPos) + , m_writePos(_source.m_writePos) + , m_vector(std::move(_source.m_vector)) + , m_fail(_source.m_fail) + { + _source.destroy(); + } + + StreamBuffer& operator = (StreamBuffer&& _source) noexcept + { + m_buffer = _source.m_buffer; + m_size = _source.m_size; + m_fixedSize = _source.m_fixedSize; + m_readPos = _source.m_readPos; + m_writePos = _source.m_writePos; + m_vector = std::move(_source.m_vector); + + _source.destroy(); + + return *this; + } + + void seekg(const size_t _pos) { m_readPos = _pos; } + size_t tellg() const { return m_readPos; } + void seekp(const size_t _pos) { m_writePos = _pos; } + size_t tellp() const { return m_writePos; } + bool eof() const { return tellg() >= size(); } + bool fail() const { return m_fail; } + + bool read(uint8_t* _dst, size_t _size) + { + const auto remaining = size() - tellg(); + if(remaining < _size) + { + m_fail = true; + return false; + } + ::memcpy(_dst, &buffer()[m_readPos], _size); + m_readPos += _size; + return true; + } + bool write(const uint8_t* _src, size_t _size) + { + const auto remaining = size() - tellp(); + if(remaining < _size) + { + if(m_fixedSize) + { + m_fail = true; + return false; + } + m_vector.resize(tellp() + _size); + } + ::memcpy(&buffer()[m_writePos], _src, _size); + m_writePos += _size; + return true; + } + + explicit operator bool () const + { + return !eof(); + } + + private: + size_t size() const { return m_fixedSize ? m_size : m_vector.size(); } + + uint8_t* buffer() + { + if(m_fixedSize) + return m_buffer; + return m_vector.data(); + } + + void destroy() + { + m_buffer = nullptr; + m_size = 0; + m_fixedSize = false; + m_readPos = 0; + m_writePos = 0; + m_vector.clear(); + m_fail = false; + } + + uint8_t* m_buffer = nullptr; + size_t m_size = 0; + bool m_fixedSize = false; + size_t m_readPos = 0; + size_t m_writePos = 0; + std::vector<uint8_t> m_vector; + bool m_fail = false; + }; + + using StreamSizeType = uint32_t; + + class BinaryStream final : StreamBuffer + { + public: + using Base = StreamBuffer; + using SizeType = StreamSizeType; + + BinaryStream() = default; + + using StreamBuffer::operator bool; + + explicit BinaryStream(BinaryStream& _parent, SizeType _length) : StreamBuffer(_parent, _length) + { + } + + template<typename T> explicit BinaryStream(const std::vector<T>& _data) + { + Base::write(reinterpret_cast<const uint8_t*>(_data.data()), _data.size() * sizeof(T)); + seekg(0); + } + + // ___________________________________ + // tools + // + + void toVector(std::vector<uint8_t>& _buffer, const bool _append = false) + { + const auto size = tellp(); + if(size <= 0) + { + if(!_append) + _buffer.clear(); + return; + } + + seekg(0); + + if(_append) + { + const auto currentSize = _buffer.size(); + _buffer.resize(currentSize + size); + Base::read(&_buffer[currentSize], size); + } + else + { + _buffer.resize(size); + Base::read(_buffer.data(), size); + } + } + + bool checkString(const std::string& _str) + { + const auto pos = tellg(); + + const auto size = read<SizeType>(); + if (size != _str.size()) + { + seekg(pos); + return false; + } + std::string s; + s.resize(size); + Base::read(reinterpret_cast<uint8_t*>(s.data()), size); + const auto result = _str == s; + seekg(pos); + return result; + } + + uint32_t getWritePos() const { return static_cast<uint32_t>(tellp()); } + uint32_t getReadPos() const { return static_cast<uint32_t>(tellg()); } + bool endOfStream() const { return eof(); } + + void setWritePos(const uint32_t _pos) { seekp(_pos); } + void setReadPos(const uint32_t _pos) { seekg(_pos); } + + // ___________________________________ + // write + // + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const T& _value) + { + Base::write(reinterpret_cast<const uint8_t*>(&_value), sizeof(_value)); + } + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const std::vector<T>& _vector) + { + const auto size = static_cast<SizeType>(_vector.size()); + write(size); + if(size) + Base::write(reinterpret_cast<const uint8_t*>(_vector.data()), sizeof(T) * size); + } + + void write(const std::string& _string) + { + const auto s = static_cast<SizeType>(_string.size()); + write(s); + Base::write(reinterpret_cast<const uint8_t*>(_string.c_str()), s); + } + + void write(const char* const _value) + { + write(std::string(_value)); + } + + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + void write4CC(char const(&_str)[N]) + { + write(_str[0]); + write(_str[1]); + write(_str[2]); + write(_str[3]); + } + + + // ___________________________________ + // read + // + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> T read() + { + T v{}; + Base::read(reinterpret_cast<uint8_t*>(&v), sizeof(v)); + checkFail(); + return v; + } + + template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void read(std::vector<T>& _vector) + { + const auto size = read<SizeType>(); + checkFail(); + if (!size) + { + _vector.clear(); + return; + } + _vector.resize(size); + Base::read(reinterpret_cast<uint8_t*>(_vector.data()), sizeof(T) * size); + checkFail(); + } + + std::string readString() + { + const auto size = read<SizeType>(); + std::string s; + s.resize(size); + Base::read(reinterpret_cast<uint8_t*>(s.data()), size); + checkFail(); + return s; + } + + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + void read4CC(char const(&_str)[N]) + { + char res[5]; + read4CC(res); + + return strcmp(res, _str) == 0; + } + + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + void read4CC(char (&_str)[N]) + { + _str[0] = 'E'; + _str[1] = 'R'; + _str[2] = 'R'; + _str[3] = 'R'; + _str[4] = 0; + + _str[0] = read<char>(); + _str[1] = read<char>(); + _str[2] = read<char>(); + _str[3] = read<char>(); + } + + BinaryStream readChunk(); + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + BinaryStream tryReadChunk(char const(&_4Cc)[N], const uint32_t _version = 1) + { + return tryReadChunkInternal(_4Cc, _version); + } + + private: + BinaryStream tryReadChunkInternal(const char* _4Cc, uint32_t _version = 1); + + + // ___________________________________ + // helpers + // + + private: + void checkFail() const + { + if(fail()) + throw std::range_error("end-of-stream"); + } + }; + + struct Chunk + { + using SizeType = BinaryStream::SizeType; + + char fourCC[5]; + uint32_t version; + SizeType length; + BinaryStream data; + + bool read(BinaryStream& _parentStream) + { + _parentStream.read4CC(fourCC); + version = _parentStream.read<uint32_t>(); + length = _parentStream.read<SizeType>(); + data = BinaryStream(_parentStream, length); + return !data.endOfStream(); + } + }; + + class ChunkWriter + { + public: + using SizeType = BinaryStream::SizeType; + + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + ChunkWriter(BinaryStream& _stream, char const(&_4Cc)[N], const uint32_t _version = 1) : m_stream(_stream) + { + m_stream.write4CC(_4Cc); + m_stream.write(_version); + m_lengthWritePos = m_stream.getWritePos(); + m_stream.write<SizeType>(0); + } + + ChunkWriter() = delete; + ChunkWriter(ChunkWriter&&) = delete; + ChunkWriter(const ChunkWriter&) = delete; + ChunkWriter& operator = (ChunkWriter&&) = delete; + ChunkWriter& operator = (const ChunkWriter&) = delete; + + ~ChunkWriter() + { + const auto currentWritePos = m_stream.getWritePos(); + const SizeType chunkDataLength = currentWritePos - m_lengthWritePos - sizeof(SizeType); + m_stream.setWritePos(m_lengthWritePos); + m_stream.write(chunkDataLength); + m_stream.setWritePos(currentWritePos); + } + + private: + BinaryStream& m_stream; + SizeType m_lengthWritePos = 0; + }; + + class ChunkReader + { + public: + using SizeType = ChunkWriter::SizeType; + using ChunkCallback = std::function<void(BinaryStream&, uint32_t)>; // data, version + + struct ChunkCallbackData + { + char fourCC[5]; + uint32_t expectedVersion; + ChunkCallback callback; + }; + + explicit ChunkReader(BinaryStream& _stream) : m_stream(_stream) + { + } + + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + void add(char const(&_4Cc)[N], const uint32_t _version, const ChunkCallback& _callback) + { + ChunkCallbackData c; + strcpy(c.fourCC, _4Cc); + c.expectedVersion = _version; + c.callback = _callback; + supportedChunks.emplace_back(std::move(c)); + } + + void read(const uint32_t _count = 0) + { + uint32_t count = 0; + + while(!m_stream.endOfStream() && (!_count || ++count <= _count)) + { + Chunk chunk; + chunk.read(m_stream); + + ++m_numChunks; + + for (const auto& chunkData : supportedChunks) + { + if(0 != strcmp(chunkData.fourCC, chunk.fourCC)) + continue; + + if(chunk.version > chunkData.expectedVersion) + break; + + ++m_numRead; + chunkData.callback(chunk.data, chunk.version); + break; + } + } + } + + bool tryRead(const uint32_t _count = 0) + { + const auto pos = m_stream.getReadPos(); + try + { + read(_count); + return true; + } + catch(std::range_error&) + { + m_stream.setReadPos(pos); + return false; + } + } + + uint32_t numRead() const + { + return m_numRead; + } + + uint32_t numChunks() const + { + return m_numChunks; + } + + private: + BinaryStream& m_stream; + std::vector<ChunkCallbackData> supportedChunks; + uint32_t m_numRead = 0; + uint32_t m_numChunks = 0; + }; +} diff --git a/source/baseLib/configFile.cpp b/source/baseLib/configFile.cpp @@ -0,0 +1,59 @@ +#include "configFile.h" + +#include <fstream> + +namespace baseLib +{ + static bool needsTrim(char _c) + { + switch (_c) + { + case ' ': + case '\r': + case '\n': + case '\t': + return true; + default: + return false; + } + } + + static std::string trim(std::string _s) + { + while (!_s.empty() && needsTrim(_s.front())) + _s = _s.substr(1); + while (!_s.empty() && needsTrim(_s.back())) + _s = _s.substr(0, _s.size() - 1); + return _s; + } + + ConfigFile::ConfigFile(const char* _filename) + { + std::ifstream file(_filename, std::ios::in); + + if (!file.is_open()) + throw std::runtime_error("Failed to open config file " + std::string(_filename)); + + std::string line; + + while(std::getline(file, line)) + { + if(line.empty()) + continue; + + // // comment? + if (line.front() == '#' || line.front() == ';') + continue; + + const auto posEq = line.find('='); + + if (posEq == std::string::npos) + continue; + + const auto key = trim(line.substr(0, posEq)); + const auto val = trim(line.substr(posEq + 1)); + + m_values.emplace_back(key, val); + } + } +} +\ No newline at end of file diff --git a/source/baseLib/configFile.h b/source/baseLib/configFile.h @@ -0,0 +1,20 @@ +#pragma once + +#include <string> +#include <vector> + +namespace baseLib +{ + class ConfigFile + { + public: + explicit ConfigFile(const char* _filename); + + const std::vector<std::pair<std::string, std::string>>& getValues() const + { + return m_values; + } + private: + std::vector<std::pair<std::string, std::string>> m_values; + }; +} diff --git a/source/baseLib/hybridcontainer.h b/source/baseLib/hybridcontainer.h @@ -0,0 +1,29 @@ +#pragma once +/* +#include <vector> +#include <memory_resource> +#include <array> + +namespace baseLib +{ + template<typename T, size_t S> class BufferResource + { + public: + auto& getPool() { return m_pool; } + protected: + std::array<T, S> m_buffer; + std::pmr::monotonic_buffer_resource m_pool{ std::data(m_buffer), std::size(m_buffer) }; + }; + + template<typename T, size_t S> + class HybridVector final : public BufferResource<T,S>, public std::pmr::vector<T> + { + public: + using Base = std::pmr::vector<T>; + + HybridVector() : BufferResource<T, S>(), Base(&static_cast<BufferResource<T, S>&>(*this).getPool()) + { + } + }; +} +*/ +\ No newline at end of file diff --git a/source/baseLib/md5.cpp b/source/baseLib/md5.cpp @@ -0,0 +1,147 @@ +#include "md5.h" + +#include <cstring> // memcpy +#include <iomanip> +#include <sstream> + +#define HEXN(S, n) std::hex << std::setfill('0') << std::setw(n) << (uint32_t)S + +namespace baseLib +{ + // leftrotate function definition + uint32_t leftrotate(const uint32_t x, const uint32_t c) + { + return (((x) << (c)) | ((x) >> (32 - (c)))); + } + + // These vars will contain the hash: h0, h1, h2, h3 + void md5(uint32_t& _h0, uint32_t& _h1, uint32_t& _h2, uint32_t& _h3, const uint8_t *_initialMsg, const uint32_t _initialLen) + { + // Note: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating + + // r specifies the per-round shift amounts + + constexpr uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }; + + // Use binary integer part of the sines of integers (in radians) as constants// Initialize variables: + constexpr uint32_t k[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; + + _h0 = 0x67452301; + _h1 = 0xefcdab89; + _h2 = 0x98badcfe; + _h3 = 0x10325476; + + // Pre-processing: adding a single 1 bit + //append "1" bit to message + /* Notice: the input bytes are considered as bits strings, + where the first bit is the most significant bit of the byte.[37] */ + + // Pre-processing: padding with zeros + //append "0" bit until message length in bit ≡ 448 (mod 512) + //append length mod (2 pow 64) to message + + const uint32_t newLen = ((((_initialLen + 8) / 64) + 1) * 64) - 8; + + std::vector<uint8_t> buffer; + buffer.resize(newLen + 64, 0); + auto* msg = buffer.data(); + memcpy(msg, _initialMsg, _initialLen); + msg[_initialLen] = 128; // write the "1" bit + + const uint32_t bitsLen = 8 * _initialLen; // note, we append the len + memcpy(msg + newLen, &bitsLen, 4); // in bits at the end of the buffer + + // Process the message in successive 512-bit chunks: + //for each 512-bit chunk of message: + for (uint32_t offset = 0; offset < newLen; offset += (512 / 8)) + { + // break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15 + const auto* w = reinterpret_cast<uint32_t*>(msg + offset); + + // Initialize hash value for this chunk: + uint32_t a = _h0; + uint32_t b = _h1; + uint32_t c = _h2; + uint32_t d = _h3; + + // Main loop: + for (uint32_t i = 0; i < 64; i++) + { + uint32_t f, g; + + if (i < 16) { + f = (b & c) | ((~b) & d); + g = i; + } + else if (i < 32) { + f = (d & b) | ((~d) & c); + g = (5 * i + 1) % 16; + } + else if (i < 48) { + f = b ^ c ^ d; + g = (3 * i + 5) % 16; + } + else { + f = c ^ (b | (~d)); + g = (7 * i) % 16; + } + + const uint32_t temp = d; + d = c; + c = b; +// printf("rotateLeft(%x + %x + %x + %x, %d)\n", a, f, k[i], w[g], r[i]); + b = b + leftrotate((a + f + k[i] + w[g]), r[i]); + a = temp; + } + + // Add this chunk's hash to result so far: + + _h0 += a; + _h1 += b; + _h2 += c; + _h3 += d; + + } + + // cleanup + // free(msg); + } + + MD5::MD5(const std::vector<uint8_t>& _data) + { + md5(m_h[0], m_h[1], m_h[2], m_h[3], _data.data(), static_cast<uint32_t>(_data.size())); + } + + std::string MD5::toString() const + { + std::stringstream ss; + + for (const auto& e : m_h) + { + ss << HEXN((e & 0xff), 2); + ss << HEXN(((e>>8u) & 0xff), 2); + ss << HEXN(((e>>16u) & 0xff), 2); + ss << HEXN(((e>>24u) & 0xff), 2); + } + return ss.str(); + } +} diff --git a/source/baseLib/md5.h b/source/baseLib/md5.h @@ -0,0 +1,88 @@ +#pragma once + +#include <array> +#include <cassert> +#include <cstdint> +#include <string> +#include <vector> + +namespace baseLib +{ + class MD5 + { + public: + static constexpr uint32_t parse1(const char _b) + { + if (_b >= '0' && _b <= '9') + return _b - '0'; + if (_b >= 'A' && _b <= 'F') + return _b - 'A' + 10; + if (_b >= 'a' && _b <= 'f') + return _b - 'a' + 10; + assert(false); + return 0; + } + static constexpr uint32_t parse2(const char _b0, const char _b1) + { + return parse1(_b1) << 4 | parse1(_b0); + } + static constexpr uint32_t parse4(const char _b0, const char _b1, const char _b2, const char _b3) + { + return parse2(_b3, _b2) << 8 | parse2(_b1, _b0); + } + static constexpr uint32_t parse8(const char _b0, const char _b1, const char _b2, const char _b3, const char _b4, const char _b5, const char _b6, const char _b7) + { + return parse4(_b4, _b5, _b6, _b7) << 16 | parse4(_b0, _b1, _b2, _b3); + } + + template<size_t N, std::enable_if_t<N == 33, void*> = nullptr> constexpr MD5(char const(&_digest)[N]) + : m_h + { + parse8(_digest[ 0], _digest[ 1], _digest[ 2], _digest[ 3], _digest[ 4], _digest[ 5], _digest[ 6], _digest[ 7]), + parse8(_digest[ 8], _digest[ 9], _digest[10], _digest[11], _digest[12], _digest[13], _digest[14], _digest[15]), + parse8(_digest[16], _digest[17], _digest[18], _digest[19], _digest[20], _digest[21], _digest[22], _digest[23]), + parse8(_digest[24], _digest[25], _digest[26], _digest[27], _digest[28], _digest[29], _digest[30], _digest[31]) + } + { + } + + explicit MD5(const std::vector<uint8_t>& _data); + + MD5() : m_h({0,0,0,0}) {} + + MD5(const MD5& _src) = default; + MD5(MD5&& _src) = default; + + ~MD5() = default; + + MD5& operator = (const MD5&) = default; + MD5& operator = (MD5&&) = default; + + std::string toString() const; + + constexpr bool operator == (const MD5& _md5) const + { + return m_h[0] == _md5.m_h[0] && m_h[1] == _md5.m_h[1] && m_h[2] == _md5.m_h[2] && m_h[3] == _md5.m_h[3]; + } + + constexpr bool operator != (const MD5& _md5) const + { + return !(*this == _md5); + } + + constexpr bool operator < (const MD5& _md5) const + { + if(m_h[0] < _md5.m_h[0]) return true; + if(m_h[0] > _md5.m_h[0]) return false; + if(m_h[1] < _md5.m_h[1]) return true; + if(m_h[1] > _md5.m_h[1]) return false; + if(m_h[2] < _md5.m_h[2]) return true; + if(m_h[2] > _md5.m_h[2]) return false; + if(m_h[3] < _md5.m_h[3]) return true; + /*if(m_h[3] > _md5.m_h[3])*/ return false; + } + + private: + std::array<uint32_t, 4> m_h; + }; +} diff --git a/source/baseLib/semaphore.h b/source/baseLib/semaphore.h @@ -0,0 +1,37 @@ +#pragma once + +#include <mutex> +#include <condition_variable> +#include <cstdint> + +namespace baseLib +{ + class Semaphore + { + public: + explicit Semaphore(const uint32_t _count = 0) : m_count(_count) {} + + void wait() + { + std::unique_lock uLock(m_mutex); + + m_cv.wait(uLock, [&]{ return m_count > 0; }); + + --m_count; + } + + void notify() + { + { + std::lock_guard uLockHalt(m_mutex); + ++m_count; + } + m_cv.notify_one(); + } + + private: + std::condition_variable m_cv; + std::mutex m_mutex; + uint32_t m_count; + }; +} diff --git a/source/hardwareLib/CMakeLists.txt b/source/hardwareLib/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +project(hardwareLib) + +add_library(hardwareLib STATIC) + +set(SOURCES + am29f.cpp am29f.h + dspBootCode.cpp dspBootCode.h + haltDSP.cpp haltDSP.h + i2c.cpp i2c.h + i2cFlash.cpp i2cFlash.h + lcd.cpp lcd.h + lcdfonts.cpp lcdfonts.h + sciMidi.cpp sciMidi.h + syncUCtoDSP.h +) + +target_sources(hardwareLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(hardwareLib PUBLIC 68kEmu dsp56kEmu synthLib) + +set_property(TARGET hardwareLib PROPERTY FOLDER "Gearmulator") + +target_include_directories(hardwareLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/hardwareLib/am29f.cpp b/source/hardwareLib/am29f.cpp @@ -0,0 +1,138 @@ +#include "am29f.h" + +#include <cassert> + +#include "mc68k/logging.h" +#include "mc68k/mc68k.h" + +namespace hwLib +{ + Am29f::Am29f(uint8_t* _buffer, const size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr): m_buffer(_buffer), m_size(_size), m_useWriteEnable(_useWriteEnable), m_bitreverseCmdAddr(_bitreversedCmdAddr) + { + auto br = [&](uint16_t x) + { + return m_bitreverseCmdAddr ? static_cast<uint16_t>(bitreverse(x) >> 4) : x; + }; + + // Chip Erase + m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x10}}}); + + // Sector Erase + m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}}}); + + // Program + m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0xA0}}}); + } + + void Am29f::write(const uint32_t _addr, const uint16_t _data) + { + const auto reset = [this]() + { + m_currentBusCycle = 0; + m_currentCommand = -1; + }; + + if(!writeEnabled()) + { + reset(); + return; + } + + bool anyMatch = false; + + const auto d = _data & 0xff; + + for (size_t i=0; i<m_commands.size(); ++i) + { + auto& cycles = m_commands[i].cycles; + + if(m_currentBusCycle < cycles.size()) + { + const auto& c = cycles[m_currentBusCycle]; + + if(c.addr == _addr && c.data == d) + { + anyMatch = true; + + if(m_currentBusCycle == cycles.size() - 1) + m_currentCommand = static_cast<int32_t>(i); + } + } + } + + if(!anyMatch) + { + if(m_currentCommand >= 0) + { + const auto c = static_cast<CommandType>(m_currentCommand); + + execCommand(c, _addr, _data); + } + + reset(); + } + else + { + ++m_currentBusCycle; + } + } + + void Am29f::execCommand(const CommandType _command, uint32_t _addr, const uint16_t _data) + { + switch (_command) + { + case CommandType::ChipErase: + assert(false); + break; + case CommandType::SectorErase: + { + size_t sectorSizekB = 0; + switch (_addr) + { + case 0x00000: sectorSizekB = 16; break; + case 0x04000: + case 0x06000: sectorSizekB = 8; break; + case 0x08000: sectorSizekB = 32; break; + case 0x10000: + case 0x20000: + case 0x30000: + case 0x40000: + case 0x50000: + case 0x60000: + case 0x70000: sectorSizekB = 64; break; + case 0x78000: + case 0x7A000: + case 0x7C000: + // mq sends erase commands for a flash with top boot block even though a chip with bottom boot block is installed + _addr = 0x70000; + sectorSizekB = 64; + break; + default: + MCLOG("Unable to erase sector at " << MCHEX(_addr) << ", out of bounds!"); + return; + } + + MCLOG("Erasing Sector at " << MCHEX(_addr) << ", size " << MCHEX(1024 * sectorSizekB)); + for(size_t i = _addr; i< _addr + sectorSizekB * 1024; ++i) + m_buffer[i] = 0xff; + } + break; + case CommandType::Program: + { + if(_addr >= m_size) + return; + MCLOG("Programming word at " << MCHEX(_addr) << ", value " << MCHEXN(_data, 4)); + const auto old = mc68k::Mc68k::readW(m_buffer, _addr); + // "A bit cannot be programmed from a 0 back to a 1" + const auto v = _data & old; + mc68k::Mc68k::writeW(m_buffer, _addr, v); + // assert(v == _data); + break; + } + case CommandType::Invalid: + default: + assert(false); + break; + } + } +} +\ No newline at end of file diff --git a/source/hardwareLib/am29f.h b/source/hardwareLib/am29f.h @@ -0,0 +1,67 @@ +#pragma once + +#include <cstdint> +#include <cstddef> +#include <vector> + +namespace hwLib +{ + class Am29f + { + public: + struct BusCycle + { + uint16_t addr; + uint8_t data; + }; + + struct Command + { + std::vector<BusCycle> cycles; + }; + + enum class CommandType + { + Invalid = -1, + ChipErase, + SectorErase, + Program, + }; + + explicit Am29f(uint8_t* _buffer, size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr); + + void writeEnable(bool _writeEnable) + { + m_writeEnable = _writeEnable; + } + + void write(uint32_t _addr, uint16_t _data); + + private: + bool writeEnabled() const + { + return !m_useWriteEnable || m_writeEnable; + } + + static constexpr uint16_t bitreverse(uint16_t x) + { + x = ((x & 0xaaaau) >> 1) | static_cast<uint16_t>((x & 0x5555u) << 1); + x = ((x & 0xccccu) >> 2) | static_cast<uint16_t>((x & 0x3333u) << 2); + x = ((x & 0xf0f0u) >> 4) | static_cast<uint16_t>((x & 0x0f0fu) << 4); + + return ((x & 0xff00) >> 8) | static_cast<uint16_t>((x & 0x00ff) << 8); + } + + void execCommand(CommandType _command, uint32_t _addr, uint16_t _data); + + uint8_t* m_buffer; + const size_t m_size; + const bool m_useWriteEnable; + const bool m_bitreverseCmdAddr; + + std::vector<Command> m_commands; + bool m_writeEnable = false; + uint32_t m_currentBusCycle = 0; + int32_t m_currentCommand = -1; + }; +} +\ No newline at end of file diff --git a/source/hardwareLib/dspBootCode.cpp b/source/hardwareLib/dspBootCode.cpp @@ -0,0 +1,66 @@ +#include "dspBootCode.h" + +#include "dsp56kEmu/dsp.h" + +namespace hwLib +{ + DspBoot::DspBoot(dsp56k::DSP& _dsp) : m_dsp(_dsp) + { + // H(D)I08 interface needs to be enabled after boot, set it up now already + auto enableHdi08 = [](dsp56k::HDI08& _hdi08) + { + _hdi08.writePortControlRegister(1<<dsp56k::HDI08::HPCR_HEN); + }; + + if(auto* periph56362 = dynamic_cast<dsp56k::Peripherals56362*>(m_dsp.getPeriph(0))) + enableHdi08(periph56362->getHDI08()); + else if(auto* periph56303 = dynamic_cast<dsp56k::Peripherals56303*>(m_dsp.getPeriph(0))) + enableHdi08(periph56303->getHI08()); + } + + bool DspBoot::hdiWriteTX(const dsp56k::TWord& _val) + { + // DSP Boot Code emulation. The following bytes are sent to the DSP via HDI08: + // + // * Length = number of words to be received + // * Address = target address where words will be written to + // * 'Length' number of words + // + // After completion, DSP jumps to 'Address' to execute the code it just received + + switch (m_state) + { + case State::Length: + m_remaining = _val; + m_state = State::Address; + return false; + case State::Address: + m_address = _val; + + LOG("DSP Boot: " << m_remaining << " words, initial PC " << HEX(m_address)); + + // r0 is used as counter, r1 holds the initial PC + m_dsp.regs().r[0].var = static_cast<int32_t>(m_address + m_remaining); + m_dsp.regs().r[1].var = static_cast<int32_t>(m_address); + + m_dsp.regs().sr.var &= ~0xff; // ccr is cleared before jump to r1 + m_dsp.setPC(m_address); // jmp (r1) + + m_state = State::Data; + return false; + case State::Data: + m_dsp.memory().set(dsp56k::MemArea_P, m_address++, _val); + if(0 == --m_remaining) + { + LOG("DSP Boot: finished"); + m_state = State::Finished; + return true; + } + return false; + case State::Finished: + return true; + } + assert(false && "invalid state"); + return false; + } +} diff --git a/source/hardwareLib/dspBootCode.h b/source/hardwareLib/dspBootCode.h @@ -0,0 +1,37 @@ +#pragma once + +#include "dsp56kEmu/types.h" + +namespace dsp56k +{ + class DSP; +} + +namespace hwLib +{ + class DspBoot + { + public: + enum class State + { + Length, + Address, + Data, + Finished + }; + + explicit DspBoot(dsp56k::DSP& _dsp); + + bool hdiWriteTX(const dsp56k::TWord& _val); + + bool finished() const { return m_state == State::Finished; } + + private: + dsp56k::DSP& m_dsp; + + State m_state = State::Length; + + dsp56k::TWord m_remaining = 0; + dsp56k::TWord m_address = 0; + }; +} diff --git a/source/hardwareLib/haltDSP.cpp b/source/hardwareLib/haltDSP.cpp @@ -0,0 +1,96 @@ +#include "haltDSP.h" + +#include "dsp56kEmu/dsp.h" + +namespace hwLib +{ + HaltDSP::HaltDSP(dsp56k::DSP& _dsp) + : m_dsp(_dsp) + , m_irq(_dsp.registerInterruptFunc([this] { onInterrupt(); })) + { + } + + void HaltDSP::haltDSP(const bool _wait) + { + if(++m_halted == 1) + { + ++m_irqRequestCount; + + m_dsp.injectExternalInterrupt(m_irq); + + if(_wait) + { + const auto expectedCount = m_irqRequestCount; + std::unique_lock lock(m_mutex); + m_cvHalted.wait(lock, [this, expectedCount] + { + if(expectedCount != m_irqServedCount) + return false; + return true; + }); + } + } + } + + bool HaltDSP::resumeDSP() + { + if(!m_halted) + return false; + if(--m_halted == 0) + m_blockSem.notify(); + return true; + } + + void HaltDSP::wakeUp(std::function<void()>&& _func) + { + if(!m_halted) + { + _func(); + return; + } + + { + std::unique_lock lock(m_mutex); + m_wakeUps.emplace(m_wakeUpId++, std::move(_func)); + } + m_blockSem.notify(); + } + + void HaltDSP::onInterrupt() + { + // signal waiter that we reached halt state + { + std::unique_lock lock(m_mutex); + ++m_irqServedCount; + } + m_cvHalted.notify_one(); + + m_halting = true; + + // halt and wait for resume or a wakeup call + m_blockSem.wait(); + + while(true) + { + std::function<void()> func; + + // check if wakeup call + { + std::unique_lock lock(m_mutex); + const auto it = m_wakeUps.find(m_wakeUpCount); + if(it == m_wakeUps.end()) + break; + + func = std::move(it->second); + m_wakeUps.erase(it); + ++m_wakeUpCount; + } + + // execute wakeup and go back to halt state + func(); + m_blockSem.wait(); + } + + m_halting = false; + } +} diff --git a/source/hardwareLib/haltDSP.h b/source/hardwareLib/haltDSP.h @@ -0,0 +1,86 @@ +#pragma once + +#include <cstdint> +#include <functional> +#include <unordered_map> + +#include "baseLib/semaphore.h" + +namespace dsp56k +{ + class DSP; +} + +namespace hwLib +{ + class HaltDSP + { + public: + explicit HaltDSP(dsp56k::DSP& _dsp); + + void haltDSP(bool _wait = false); + bool resumeDSP(); + + // only returns true if the last halt request has been serviced + bool isHalting() const { return m_halting && m_irqRequestCount == m_irqServedCount; } + + dsp56k::DSP& getDSP() const { return m_dsp; } + + void wakeUp(std::function<void()>&& _func); + + private: + void onInterrupt(); + + dsp56k::DSP& m_dsp; + + uint32_t m_halted = 0; + uint32_t m_irq; + baseLib::Semaphore m_blockSem; + + std::mutex m_mutex; + std::condition_variable m_cvHalted; + + uint32_t m_irqServedCount = 0; + uint32_t m_irqRequestCount = 0; + + uint32_t m_wakeUpId = 0; + uint32_t m_wakeUpCount = 0; + + std::unordered_map<uint32_t, std::function<void()>> m_wakeUps; + + bool m_halting = false; + }; + + class ScopedResumeDSP + { + public: + explicit ScopedResumeDSP(HaltDSP& _haltDSP) : m_haltDSP(_haltDSP), m_resumed(_haltDSP.resumeDSP()) + { + } + + ~ScopedResumeDSP() + { + if(m_resumed) + m_haltDSP.haltDSP(); + } + private: + HaltDSP& m_haltDSP; + bool m_resumed; + }; + + class ScopedHaltDSP + { + public: + explicit ScopedHaltDSP(HaltDSP& _haltDSP) : m_haltDSP(_haltDSP) + { + _haltDSP.haltDSP(); + } + + ~ScopedHaltDSP() + { + m_haltDSP.resumeDSP(); + } + private: + HaltDSP& m_haltDSP; + }; +} diff --git a/source/hardwareLib/i2c.cpp b/source/hardwareLib/i2c.cpp @@ -0,0 +1,165 @@ +#include "i2c.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +namespace hwLib +{ + void I2c::masterWrite(const bool _sda, const bool _scl) + { + if(_sda != m_sda && _scl != m_scl) + { + assert(false && "only one pin should flip"); + return; + } + + if(_sda != m_sda) + { + m_sda = _sda; + sdaFlip(_sda); + } + else if(_scl != m_scl) + { + m_scl = _scl; + sclFlip(_scl); + } + } + + std::optional<bool> I2c::masterRead(const bool _scl) + { + if(_scl == m_scl) + return {}; + + if(m_state != State::Start) + return {}; + + m_scl = _scl; + + if(_scl) + { + if(m_nextBit == BitAck) + { + m_nextBit = Bit7; + return m_ackBit; // this was returned already in onAck() + } + + if(m_nextBit >= Bit0 && m_nextBit <= Bit7) + { + if(m_nextBit == Bit7) + m_byte = onReadByte(); + + auto res = m_byte & (1<<m_nextBit); + --m_nextBit; + return res; + } + } + + return {}; + } + + std::optional<bool> I2c::setSdaWrite(const bool _write) + { + if(m_sdaWrite == _write) + return {}; + + m_sdaWrite = _write; + + if(m_state != State::Start) + return {}; + + if(!m_sdaWrite) + { + if(m_nextBit == BitAck) + { + const auto ackBit = onAck(); + if(ackBit) + { + m_ackBit = *ackBit; + } + return ackBit; + } + } + return {}; + } + + void I2c::onStateChanged(const State _state) + { + LOG("state: " << (_state == State::Start ? "start" : "stop")); + + switch (_state) + { + case State::Stop: + m_nextBit = BitInvalid; + break; + case State::Start: + m_nextBit = Bit7; + m_byte = 0; + break; + } + } + + void I2c::onStartCondition() + { + setState(State::Start); + } + + void I2c::onStopCondition() + { + setState(State::Stop); + } + + void I2c::onByteWritten() + { + LOG("Got byte " << HEXN(byte(), 2)); + } + + void I2c::sdaFlip(const bool _sda) + { + if(m_scl) + { + if(!_sda) + onStartCondition(); + else + onStopCondition(); + } + } + + void I2c::sclFlip(const bool _scl) + { + if(_scl && m_state == State::Start) + { + if(m_nextBit >= Bit0 && m_nextBit <= Bit7) + { + LOG("next bit " << static_cast<int>(m_nextBit) << " = " << m_sda); + + if(m_nextBit == Bit7) + m_byte = 0; + + // data input + if(m_sda) + m_byte |= (1<<m_nextBit); + + --m_nextBit; + + if(m_nextBit < 0) + { + onByteWritten(); + } + } + else if(m_nextBit == BitAck) + { + LOG("ACK by master=" << m_sda); + m_nextBit = 7; + } + } + } + + void I2c::setState(const State _state) + { +/* if(m_state == _state) + return; +*/ m_state = _state; + onStateChanged(_state); + } +} diff --git a/source/hardwareLib/i2c.h b/source/hardwareLib/i2c.h @@ -0,0 +1,63 @@ +#pragma once + +#include <cstdint> +#include <optional> + +namespace hwLib +{ + class I2c + { + public: + enum class State + { + Stop, + Start, + }; + enum BitPos : int8_t + { + Bit7 = 7, + Bit6 = 6, + Bit5 = 5, + Bit4 = 4, + Bit3 = 3, + Bit2 = 2, + Bit1 = 1, + Bit0 = 0, + BitAck = -1, + BitInvalid = -2, + }; + + I2c() = default; + + virtual ~I2c() = default; + + void masterWrite(bool _sda, bool _scl); + virtual std::optional<bool> masterRead(bool _scl); + std::optional<bool> setSdaWrite(bool _write); + + bool sda() const { return m_sda; } + bool scl() const { return m_scl; } + uint8_t byte() const { return m_byte; } + + protected: + virtual void onStateChanged(State _state); + virtual void onStartCondition(); + virtual void onStopCondition(); + virtual void onByteWritten(); + virtual std::optional<bool> onAck() { return {}; } + virtual uint8_t onReadByte() { return 0; } + + private: + void sdaFlip(bool _sda); + void sclFlip(bool _scl); + void setState(State _state); + + bool m_sdaWrite = true; // true = write + bool m_sda = false; + bool m_scl = false; + State m_state = State::Stop; + int8_t m_nextBit = BitInvalid; + uint8_t m_byte; + bool m_ackBit = true; + }; +} diff --git a/source/hardwareLib/i2cFlash.cpp b/source/hardwareLib/i2cFlash.cpp @@ -0,0 +1,112 @@ +#include "i2cFlash.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +#include "synthLib/os.h" + +namespace hwLib +{ + void I2cFlash::saveAs(const std::string& _filename) const + { + synthLib::writeFile(_filename, m_data); + } + + bool I2cFlash::setData(std::vector<uint8_t>& _data) + { + if(_data.size() != Size) + return false; + + std::copy(_data.begin(), _data.end(), m_data.begin()); + return true; + } + + void I2cFlash::onStartCondition() + { + m_state = State::ReadDeviceSelect; + I2c::onStartCondition(); + } + + void I2cFlash::onStopCondition() + { + I2c::onStopCondition(); + } + + void I2cFlash::onByteWritten() + { + I2c::onByteWritten(); + + switch (m_state) + { + case State::ReadDeviceSelect: + m_deviceSelect = byte(); + m_state = State::AckDeviceSelect; + break; + case State::ReadAddressMSB: + m_address = byte() << 8; + m_state = State::AckAddressMSB; + break; + case State::ReadAddressLSB: + m_address |= byte(); + m_state = State::AckAddressLSB; + break; + case State::ReadWriteData: + writeByte(byte()); + break; + default: + assert(false && "invalid state"); + break; + } + } + + std::optional<bool> I2cFlash::onAck() + { + switch (m_state) + { + case State::AckDeviceSelect: + m_state = State::ReadAddressMSB; + return false; + case State::AckAddressMSB: + m_state = State::ReadAddressLSB; + return false; + case State::AckAddressLSB: + m_state = State::ReadWriteData; + return false; + case State::ReadWriteData: + return false; + default: + assert(false && "invalid state"); + return true; + } + } + + uint8_t I2cFlash::onReadByte() + { + assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); + assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Read); + const auto res = m_data[m_address]; + + LOG("I2C op read from " << HEXN(m_address,4) << ", res " << HEXN(res,2)); + ++m_address; + if(m_address >= m_data.size()) + m_address = static_cast<uint32_t>(m_data.size()) - 1; + return res; + } + + void I2cFlash::writeByte(uint8_t _byte) + { + assert((m_deviceSelect & DeviceSelectMask::Area) == DeviceSelectValues::AreaMemory); + assert((m_deviceSelect & DeviceSelectMask::Rw) == DeviceSelectValues::Write); + + m_data[m_address] = _byte; + + LOG("I2C op write to " << HEXN(m_address,4) << ", val " << HEXN(_byte,2)); + advanceAddress(); + } + + void I2cFlash::advanceAddress() + { + m_address = (m_address & 0xFF80) | ((m_address + 1) & 0x7F); + } +} diff --git a/source/hardwareLib/i2cFlash.h b/source/hardwareLib/i2cFlash.h @@ -0,0 +1,74 @@ +#pragma once + +#include <array> +#include <optional> +#include <string> +#include <vector> + +#include "i2c.h" + +namespace hwLib +{ + // M24512 64kx 8 i2c flash chip emulation + class I2cFlash : public I2c + { + public: + static constexpr uint32_t Size = 0x10000; + using Data = std::array<uint8_t, Size>; + + I2cFlash() + { + m_data.fill(0xff); + } + + explicit I2cFlash(const Data& _data) : m_data(_data) + { + } + + void saveAs(const std::string& _filename) const; + + bool setData(std::vector<uint8_t>& _data); + + const auto& getAddress() const { return m_address; } + + protected: + void onStartCondition() override; + void onStopCondition() override; + void onByteWritten() override; + std::optional<bool> onAck() override; + uint8_t onReadByte() override; + + private: + enum class State + { + ReadDeviceSelect, AckDeviceSelect, + ReadAddressMSB, AckAddressMSB, + ReadAddressLSB, AckAddressLSB, + ReadWriteData, + }; + + enum DeviceSelectMask + { + Area = 0b1111'000'0, + ChipEnable = 0b0000'111'0, + Rw = 0b0000'000'1, + }; + + enum DeviceSelectValues + { + AreaMemory = 0b1010'000'0, + AreaId = 0b1011'000'0, + Read = 0b0000'000'1, + Write = 0b0000'000'0, + }; + + void writeByte(uint8_t _byte); + void advanceAddress(); + + State m_state = State::ReadDeviceSelect; + uint8_t m_deviceSelect = 0; + uint32_t m_address = 0; + + Data m_data; + }; +} diff --git a/source/hardwareLib/lcd.cpp b/source/hardwareLib/lcd.cpp @@ -0,0 +1,218 @@ +#include "lcd.h" + +#include <cassert> + +#include "mc68k/logging.h" + +#define LOG MCLOG + +namespace hwLib +{ + LCD::LCD() = default; + + std::optional<uint8_t> LCD::exec(bool registerSelect, bool read, uint8_t g) + { + bool changed = false; + bool cgRamChanged = false; + + std::optional<uint8_t> result; + + if(!read) + { + if(!registerSelect) + { + if(g == 0x01) + { + LOG("LCD Clear Display"); + m_dramData.fill(' '); + changed = true; + } + else if(g == 0x02) + { + LOG("LCD Return Home"); + m_dramAddr = 0; + m_cursorPos = 0; + } + else if((g & 0xfc) == 0x04) + { + const int increment = (g >> 1) & 1; + const int shift = g & 1; + LOG("LCD Entry Mode Set, inc=" << increment << ", shift=" << shift); + + m_addrIncrement = increment ? 1 : -1; + } + else if((g & 0xf8) == 0x08) + { + const int displayOnOff = (g >> 2) & 1; + const int cursorOnOff = (g >> 1) & 1; + const int cursorBlinking = g & 1; + + LOG("LCD Display ON/OFF, display=" << displayOnOff << ", cursor=" << cursorOnOff << ", blinking=" << cursorBlinking); + + m_displayOn = displayOnOff != 0; + m_cursorOn = cursorOnOff != 0; + m_cursorBlinking = cursorBlinking != 0; + } + else if((g & 0xf3) == 0x10) + { + const int scrl = (g >> 2) & 3; + + LOG("LCD Cursor/Display Shift, scrl=" << scrl); + m_cursorShift = static_cast<CursorShiftMode>(scrl); + } + else if((g & 0xec) == 0x28) + { + const int dl = (g >> 4) & 1; + const int ft = g & 3; + + LOG("LCD Function Set, dl=" << dl << ", ft=" << ft); + m_dataLength = static_cast<DataLength>(dl); + m_fontTable = static_cast<FontTable>(ft); + } + else if(g & (1<<7)) + { + const int addr = g & 0x7f; +// LOG("LCD Set DDRAM address, addr=" << addr); + m_dramAddr = addr; + m_addressMode = AddressMode::DDRam; + } + else if(g & (1<<6)) + { + const int acg = g & 0x3f; + +// LOG("LCD Set CGRAM address, acg=" << acg); + m_cgramAddr = acg; + m_addressMode = AddressMode::CGRam; + } + else + { + LOG("LCD unknown command"); + assert(false); + } + } + else + { + if(m_addressMode == AddressMode::CGRam) + { +// changed = true; +// LOG("LCD write data to CGRAM addr " << m_cgramAddr << ", data=" << static_cast<int>(g)); + + if (m_cgramData[m_cgramAddr] != g) + { + m_cgramData[m_cgramAddr] = g; + cgRamChanged = true; + } + + m_cgramAddr += m_addrIncrement; + + /* + if((m_cgramAddr & 0x7) == 0) + { + std::stringstream ss; + ss << "CG RAM character " << (m_cgramAddr/8 - 1) << ':' << '\n'; + ss << "##################" << '\n'; + for(auto i = m_cgramAddr - 8; i < m_cgramAddr - 1; ++i) + { + ss << '#'; + for(int x=7; x >= 0; --x) + { + if(m_cgramData[i] & (1<<x)) + ss << "[]"; + else + ss << " "; + } + ss << '#' << '\n'; + } + ss << "##################" << '\n'; + const auto s(ss.str()); + LOG(s); + } + */ + } + else + { +// LOG("LCD write data to DDRAM addr " << m_dramAddr << ", data=" << static_cast<int>(g) << ", char=" << static_cast<char>(g)); + + const auto old = m_dramData; + + if(m_dramAddr >= 20 && m_dramAddr < 0x40) + { + for(size_t i=1; i<=20; ++i) + m_dramData[i-1] = m_dramData[i]; + m_dramData[19] = static_cast<char>(g); + } + else if(m_dramAddr > 0x53) + { + for(size_t i=21; i<=40; ++i) + m_dramData[i-1] = m_dramData[i]; + + m_dramData[39] = static_cast<char>(g); + } + else + { + if(m_dramAddr < 20) + m_dramData[m_dramAddr] = static_cast<char>(g); + else + m_dramData[m_dramAddr - 0x40 + 20] = static_cast<char>(g); + } + + if(m_dramAddr != 20 && m_dramAddr != 0x54) + m_dramAddr += m_addrIncrement; + + if(m_dramData != old) + changed = true; + } + } + } + else + { + if(registerSelect) + { + LOG("LCD read data from CGRAM or DDRAM"); + if(m_addressMode == AddressMode::CGRam) + result = m_cgramData[m_cgramAddr]; + else + result = m_dramData[m_dramAddr]; + } + else + { + LOG("LCD read busy flag & address"); + if(m_addressMode == AddressMode::CGRam) + { + result = static_cast<uint8_t>(m_cgramAddr); + } + else + { + auto a = m_dramAddr; + if(a > 0x53) + a = 0x53; + if(a == 20) + a = 19; + result = static_cast<uint8_t>(m_dramAddr); + } + } + } + + if(changed && m_changeCallback) + m_changeCallback(); + + if(cgRamChanged && m_cgRamChangeCallback) + m_cgRamChangeCallback(); + + return result; + } + + bool LCD::getCgData(std::array<uint8_t, 8>& _data, uint32_t _charIndex) const + { + const auto idx = _charIndex * 8; + if(idx + 8 >= getCgRam().size()) + return false; + + uint32_t j = 0; + + for(auto i = idx; i<idx+8; ++i) + _data[j++] = getCgRam()[i]; + + return true; + } +} diff --git a/source/hardwareLib/lcd.h b/source/hardwareLib/lcd.h @@ -0,0 +1,91 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <functional> +#include <optional> + +namespace hwLib +{ + // 2*20 characters display simulation (20*2) + // EW20290GLW / NHD-0220DZW-AB5 and compatibles + + class LCD + { + public: + using ChangeCallback = std::function<void()>; + + LCD(); + std::optional<uint8_t> exec(bool registerSelect, bool read, uint8_t g); + + const std::array<char, 40>& getDdRam() const { return m_dramData; } + const auto& getCgRam() const { return m_cgramData; } + bool getCgData(std::array<uint8_t, 8>& _data, uint32_t _charIndex) const; + + void setChangeCallback(const ChangeCallback& _callback) + { + m_changeCallback = _callback; + } + + void setCgRamChangeCallback(const ChangeCallback& _callback) + { + m_cgRamChangeCallback = _callback; + } + private: + enum class CursorShiftMode + { + CursorLeft, + CursorRight, + DisplayLeft, + DisplayRight + }; + enum class DisplayShiftMode + { + Right, + Left + }; + enum class FontTable + { + EnglishJapanese, + WesternEuropean1, + EnglishRussian, + WesternEuropean2, + }; + enum class DataLength + { + Bit8, + Bit4 + }; + + enum class AddressMode + { + DDRam, + CGRam, + }; + + uint32_t m_lastWriteCounter = 0xffffffff; + + uint32_t m_cursorPos = 0; + uint32_t m_dramAddr = 0; + uint32_t m_cgramAddr = 0; + + CursorShiftMode m_cursorShift = CursorShiftMode::CursorLeft; + DisplayShiftMode m_displayShift = DisplayShiftMode::Left; + FontTable m_fontTable = FontTable::EnglishJapanese; + DataLength m_dataLength = DataLength::Bit8; + AddressMode m_addressMode = AddressMode::DDRam; + + bool m_displayOn = true; + bool m_cursorOn = false; + bool m_cursorBlinking = false; + + int m_addrIncrement = 1; + + std::array<uint8_t, 0x40> m_cgramData{}; + std::array<char, 40> m_dramData{}; + uint32_t m_lastOpState = 0; + + ChangeCallback m_changeCallback; + ChangeCallback m_cgRamChangeCallback; + }; +} diff --git a/source/hardwareLib/lcdfonts.cpp b/source/hardwareLib/lcdfonts.cpp @@ -0,0 +1,2675 @@ +#include <cstdint> +#include <cstddef> + +#include <array> + +namespace hwLib +{ + constexpr uint8_t g_fontTable0[] = + { + // CGRam Data, initially empty + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 0 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 1 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 2 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 3 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 4 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 5 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 6 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 7 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 8 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 9 + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 a + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 b + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 c + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 d + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 e + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 f + + 0b00000, // 1 0 + 0b10001, + 0b01001, + 0b01110, + 0b10010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 1 + 0b11110, + 0b00010, + 0b00010, + 0b00010, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 2 + 0b00110, + 0b00010, + 0b00110, + 0b01010, + 0b01010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 3 + 0b11111, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 4 + 0b11111, + 0b00001, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 5 + 0b00110, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 6 + 0b01110, + 0b00100, + 0b01000, + 0b00100, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 7 + 0b11111, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 8 + 0b10111, + 0b10101, + 0b10101, + 0b10001, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 9 + 0b00110, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 a + 0b01111, + 0b00001, + 0b00001, + 0b00001, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 b + 0b11110, + 0b00001, + 0b00001, + 0b00001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b10000, // 1 c + 0b11111, + 0b00001, + 0b00001, + 0b00001, + 0b00110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 d + 0b11111, + 0b10001, + 0b10001, + 0b10001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 e + 0b10110, + 0b01001, + 0b10001, + 0b10001, + 0b10111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 1 f + 0b00110, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 0 + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 2 1 ! + 0b00100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b01010, // 2 2 " + 0b01010, + 0b01010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b01010, // 2 3 # + 0b01010, + 0b11111, + 0b01010, + 0b11111, + 0b01010, + 0b01010, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 2 4 $ + 0b01111, + 0b10100, + 0b01110, + 0b00101, + 0b11110, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b11000, // 2 5 % + 0b11001, + 0b00010, + 0b00100, + 0b01000, + 0b10011, + 0b00011, + 0b00000, + 0b00000, + 0b00000, + + 0b01100, // 2 6 & + 0b10010, + 0b10100, + 0b01000, + 0b10101, + 0b10010, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + + 0b01100, // 2 7 ' + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // 2 8 ( + 0b00100, + 0b01000, + 0b01000, + 0b01000, + 0b00100, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // 2 9 ) + 0b00100, + 0b00010, + 0b00010, + 0b00010, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 a * + 0b00100, + 0b10101, + 0b01110, + 0b10101, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 b + + 0b00100, + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 c , + 0b00000, + 0b00000, + 0b00000, + 0b01100, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 d - + 0b00000, + 0b00000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 e . + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b01100, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 2 f / + 0b00001, + 0b00010, + 0b00100, + 0b01000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 3 0 0 + 0b10001, + 0b10011, + 0b10101, + 0b11001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 3 1 1 + 0b01100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 3 2 2 + 0b10001, + 0b00001, + 0b00010, + 0b00100, + 0b01000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 3 3 3 + 0b00010, + 0b00100, + 0b00010, + 0b00001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // 3 4 4 + 0b00110, + 0b01010, + 0b10010, + 0b11111, + 0b00010, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 3 5 5 + 0b10000, + 0b11110, + 0b00001, + 0b00001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00110, // 3 6 6 + 0b01000, + 0b10000, + 0b11110, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 3 7 7 + 0b00001, + 0b00010, + 0b00100, + 0b01000, + 0b01000, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 3 8 8 + 0b10001, + 0b10001, + 0b01110, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 3 9 9 + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b00010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 3 a : + 0b01100, + 0b01100, + 0b00000, + 0b01100, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 3 b ; + 0b01100, + 0b01100, + 0b00000, + 0b01100, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // 3 c < + 0b00100, + 0b01000, + 0b10000, + 0b01000, + 0b00100, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 3 d = + 0b00000, + 0b11111, + 0b00000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // 3 e > + 0b00100, + 0b00010, + 0b00001, + 0b00010, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 3 f ? + 0b10001, + 0b00001, + 0b00010, + 0b00100, + 0b00000, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 4 0 @ + 0b10001, + 0b00001, + 0b01101, + 0b10101, + 0b10101, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 4 1 A + 0b10001, + 0b10001, + 0b10001, + 0b11111, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b11110, // 4 2 B + 0b10001, + 0b10001, + 0b11110, + 0b10001, + 0b10001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 4 3 C + 0b10001, + 0b10000, + 0b10000, + 0b10000, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b11100, // 4 4 D + 0b10010, + 0b10001, + 0b10001, + 0b10001, + 0b10010, + 0b11100, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 4 5 E + 0b10000, + 0b10000, + 0b11110, + 0b10000, + 0b10000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 4 6 F + 0b10000, + 0b10000, + 0b11110, + 0b10000, + 0b10000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 4 7 G + 0b10001, + 0b10000, + 0b10111, + 0b10001, + 0b10001, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 4 8 H + 0b10001, + 0b10001, + 0b11111, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 4 9 I + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00111, // 4 a J + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b10010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 4 b K + 0b10010, + 0b10100, + 0b11000, + 0b10100, + 0b10010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b10000, // 4 c L + 0b10000, + 0b10000, + 0b10000, + 0b10000, + 0b10000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 4 d M + 0b11011, + 0b10101, + 0b10101, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 4 e N + 0b10001, + 0b11001, + 0b10101, + 0b10011, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 4 f O + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b11110, // 5 0 P + 0b10001, + 0b10001, + 0b11110, + 0b10000, + 0b10000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 5 1 Q + 0b10001, + 0b10001, + 0b10001, + 0b10101, + 0b10010, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + + 0b11110, // 5 2 R + 0b10001, + 0b10001, + 0b11110, + 0b10100, + 0b10010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b01111, // 5 3 S + 0b10000, + 0b10000, + 0b01110, + 0b00001, + 0b00001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 5 4 T + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 5 5 U + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 5 6 V + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b01010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b10101, // 5 7 W + 0b10101, + 0b10101, + 0b10101, + 0b10101, + 0b10101, + 0b01010, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 5 8 X + 0b10001, + 0b01010, + 0b00100, + 0b01010, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 5 9 Y + 0b10001, + 0b10001, + 0b01010, + 0b00100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // 5 a Z + 0b00001, + 0b00010, + 0b00100, + 0b01000, + 0b10000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 5 b [ + 0b01000, + 0b01000, + 0b01000, + 0b01000, + 0b01000, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b10001, // 5 c + 0b01010, + 0b11111, + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // 5 d ] + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 5 e ^ + 0b01010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 5 f _ + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // 6 0 ` + 0b00100, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 1 a + 0b00000, + 0b01110, + 0b00001, + 0b01111, + 0b10001, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + + 0b10000, // 6 2 b + 0b10000, + 0b10110, + 0b11001, + 0b10001, + 0b10001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 3 c + 0b00000, + 0b01110, + 0b10000, + 0b10000, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00001, // 6 4 d + 0b00001, + 0b01101, + 0b10011, + 0b10001, + 0b10001, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 5 e + 0b00000, + 0b01110, + 0b10001, + 0b11111, + 0b10000, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00110, // 6 6 f + 0b01001, + 0b01000, + 0b11100, + 0b01000, + 0b01000, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 7 g + 0b01111, + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b10000, // 6 8 h + 0b10000, + 0b10110, + 0b11001, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 6 9 i + 0b00000, + 0b01100, + 0b00100, + 0b00100, + 0b00100, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // 6 a j + 0b00000, + 0b00110, + 0b00010, + 0b00010, + 0b10010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b10000, // 6 b k + 0b10000, + 0b10010, + 0b10100, + 0b11000, + 0b10100, + 0b10010, + 0b00000, + 0b00000, + 0b00000, + + 0b01100, // 6 c l + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 d m + 0b00000, + 0b11010, + 0b10101, + 0b10101, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 e n + 0b00000, + 0b10110, + 0b11001, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 6 f o + 0b00000, + 0b01110, + 0b10001, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 0 p + 0b00000, + 0b11110, + 0b10001, + 0b11110, + 0b10000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 1 q + 0b00000, + 0b01101, + 0b10011, + 0b01111, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 2 r + 0b00000, + 0b10110, + 0b11001, + 0b10000, + 0b10000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 3 s + 0b00000, + 0b01110, + 0b10000, + 0b01110, + 0b00001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // 7 4 t + 0b01000, + 0b11100, + 0b01000, + 0b01000, + 0b01001, + 0b00110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 5 u + 0b00000, + 0b10001, + 0b10001, + 0b10001, + 0b10011, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 6 v + 0b00000, + 0b10001, + 0b10001, + 0b10001, + 0b01010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 7 w + 0b00000, + 0b10001, + 0b10001, + 0b10001, + 0b10101, + 0b01010, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 8 x + 0b00000, + 0b10001, + 0b01010, + 0b00100, + 0b01010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 9 y + 0b00000, + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 a z + 0b00000, + 0b11111, + 0b00010, + 0b00100, + 0b01000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // 7 b { + 0b00100, + 0b00100, + 0b01000, + 0b00100, + 0b00100, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 7 c | + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // 7 d } + 0b00100, + 0b00100, + 0b00010, + 0b00100, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 e -> + 0b00100, + 0b00010, + 0b11111, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 7 f <- + 0b00100, + 0b01000, + 0b11111, + 0b01000, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 0 + 0b00110, + 0b00010, + 0b00010, + 0b00010, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 1 + 0b11111, + 0b10001, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 2 + 0b10001, + 0b10001, + 0b01001, + 0b00101, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 3 + 0b01111, + 0b01001, + 0b01101, + 0b00001, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 4 + 0b11111, + 0b01001, + 0b01101, + 0b00001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 5 + 0b01001, + 0b00110, + 0b00010, + 0b00001, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 6 + 0b10001, + 0b01010, + 0b00100, + 0b00010, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 7 + 0b11111, + 0b00001, + 0b10001, + 0b10110, + 0b10000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 8 + 0b11111, + 0b00001, + 0b00001, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 9 + 0b10101, + 0b10101, + 0b10101, + 0b10101, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 a + 0b01111, + 0b01001, + 0b01001, + 0b01001, + 0b11001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 8 b + 0b01001, + 0b11101, + 0b00001, + 0b10001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 c + 0b00010, + 0b00000, + 0b00010, + 0b00101, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 d + 0b00000, + 0b00000, + 0b00010, + 0b00101, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // 8 e + 0b00100, + 0b01110, + 0b00000, + 0b00010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 8 f + 0b00100, + 0b00000, + 0b00110, + 0b01000, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 9 0 + 0b00101, + 0b11100, + 0b00000, + 0b00100, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 1 + 0b00000, + 0b00100, + 0b01010, + 0b00001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 2 + 0b00010, + 0b00000, + 0b10011, + 0b10011, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 3 + 0b00000, + 0b00000, + 0b00111, + 0b00101, + 0b11011, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 4 + 0b01100, + 0b00010, + 0b01001, + 0b10101, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 9 5 + 0b00000, + 0b01111, + 0b01001, + 0b00110, + 0b11001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 9 6 + 0b01001, + 0b11101, + 0b00001, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 7 + 0b00101, + 0b00000, + 0b00010, + 0b00101, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 8 + 0b00000, + 0b00000, + 0b01100, + 0b01010, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 9 + 0b01010, + 0b00000, + 0b01100, + 0b01010, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 a + 0b00000, + 0b01111, + 0b01001, + 0b00110, + 0b11001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // 9 b + 0b00000, + 0b00100, + 0b01010, + 0b00001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 c + 0b00101, + 0b00000, + 0b10011, + 0b10011, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 d + 0b00001, + 0b00001, + 0b00001, + 0b00001, + 0b11110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // 9 e + 0b00000, + 0b00000, + 0b00100, + 0b01010, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00110, // 9 f + 0b11101, + 0b00001, + 0b00001, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 0 + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 1 + 0b00000, + 0b00000, + 0b00000, + 0b11100, + 0b10100, + 0b11100, + 0b00000, + 0b00000, + 0b00000, + + 0b00111, // a 2 + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 3 + 0b00000, + 0b00000, + 0b00100, + 0b00100, + 0b00100, + 0b11100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 4 + 0b00000, + 0b00000, + 0b00000, + 0b10000, + 0b01000, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 5 + 0b00000, + 0b00000, + 0b01100, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 6 + 0b11111, + 0b00001, + 0b11111, + 0b00001, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 7 + 0b00000, + 0b11111, + 0b00001, + 0b00110, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 8 + 0b00000, + 0b00010, + 0b00100, + 0b01100, + 0b10100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a 9 + 0b00000, + 0b00100, + 0b11111, + 0b10001, + 0b00001, + 0b00110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a a + 0b00000, + 0b00000, + 0b11111, + 0b00100, + 0b00100, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a b + 0b00000, + 0b00010, + 0b11111, + 0b00110, + 0b01010, + 0b10010, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a c + 0b00000, + 0b01000, + 0b11111, + 0b01001, + 0b01010, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a d + 0b00000, + 0b00000, + 0b01110, + 0b00010, + 0b00010, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // a e + 0b00000, + 0b00000, + 0b11110, + 0b00010, + 0b11110, + 0b00010, + 0b11110, + 0b00000, + 0b00000, + + 0b00000, // a f + 0b00000, + 0b00000, + 0b10101, + 0b10101, + 0b00001, + 0b00110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b 0 + 0b00000, + 0b00000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // b 1 + 0b00001, + 0b00101, + 0b00110, + 0b00100, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00001, // b 2 + 0b00010, + 0b00100, + 0b01100, + 0b10100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // b 3 + 0b11111, + 0b10001, + 0b10001, + 0b00001, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b 4 + 0b11111, + 0b00100, + 0b00100, + 0b00100, + 0b00100, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // b 5 + 0b11111, + 0b00010, + 0b00110, + 0b01010, + 0b10010, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // b 6 + 0b11111, + 0b01001, + 0b01001, + 0b01001, + 0b01001, + 0b10010, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // b 7 + 0b11111, + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b 8 + 0b01111, + 0b01001, + 0b10001, + 0b00001, + 0b00010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // b 9 + 0b01111, + 0b10010, + 0b00010, + 0b00010, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b a + 0b11111, + 0b00001, + 0b00001, + 0b00001, + 0b00001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b01010, // b b + 0b11111, + 0b01010, + 0b01010, + 0b00010, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b c + 0b11000, + 0b00001, + 0b11001, + 0b00001, + 0b00010, + 0b11100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b d + 0b11111, + 0b00001, + 0b00010, + 0b00100, + 0b01010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // b e + 0b11111, + 0b01001, + 0b01010, + 0b01000, + 0b01000, + 0b00111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // b f + 0b10001, + 0b10001, + 0b01001, + 0b00001, + 0b00010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c 0 + 0b01111, + 0b01001, + 0b10101, + 0b00011, + 0b00010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // c 1 + 0b11100, + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c 2 + 0b10101, + 0b10101, + 0b10101, + 0b00001, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // c 3 + 0b00000, + 0b11111, + 0b00100, + 0b00100, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // c 4 + 0b01000, + 0b01000, + 0b01100, + 0b01010, + 0b01000, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // c 5 + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b01000, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c 6 + 0b01110, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c 7 + 0b11111, + 0b00001, + 0b01010, + 0b00100, + 0b01010, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // c 8 + 0b11111, + 0b00010, + 0b00100, + 0b01110, + 0b10101, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // c 9 + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c a + 0b00100, + 0b00010, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b10000, // c b + 0b10000, + 0b11111, + 0b10000, + 0b10000, + 0b10000, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c c + 0b11111, + 0b00001, + 0b00001, + 0b00001, + 0b00010, + 0b01100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c d + 0b01000, + 0b10100, + 0b00010, + 0b00001, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // c e + 0b11111, + 0b00100, + 0b10101, + 0b10101, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // c f + 0b11111, + 0b00001, + 0b00001, + 0b01010, + 0b00100, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 0 + 0b01110, + 0b00000, + 0b01110, + 0b00000, + 0b01110, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 1 + 0b00100, + 0b01000, + 0b10000, + 0b10001, + 0b11111, + 0b00001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 2 + 0b00001, + 0b00001, + 0b01010, + 0b00100, + 0b01010, + 0b10000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 3 + 0b11111, + 0b01000, + 0b11111, + 0b01000, + 0b01000, + 0b00111, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // d 4 + 0b01000, + 0b11111, + 0b01001, + 0b01010, + 0b01000, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 5 + 0b01110, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 6 + 0b11111, + 0b00001, + 0b11111, + 0b00001, + 0b00001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // d 7 + 0b00000, + 0b11111, + 0b00001, + 0b00001, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b10010, // d 8 + 0b10010, + 0b10010, + 0b10010, + 0b00010, + 0b00100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d 9 + 0b00100, + 0b10100, + 0b10100, + 0b10100, + 0b10101, + 0b10110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d a + 0b10000, + 0b10000, + 0b10001, + 0b10010, + 0b10100, + 0b11000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d b + 0b11111, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d c + 0b11111, + 0b10001, + 0b10001, + 0b00001, + 0b00010, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // d d + 0b11000, + 0b00000, + 0b00001, + 0b00001, + 0b00010, + 0b11100, + 0b00000, + 0b00000, + 0b00000, + + 0b00100, // d e + 0b10010, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b11100, // d f + 0b10100, + 0b11100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // e 0 + 0b01001, + 0b10101, + 0b10010, + 0b10010, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b01010, // e 1 + 0b00000, + 0b01110, + 0b00001, + 0b01111, + 0b10001, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // e 2 + 0b00000, + 0b01110, + 0b10001, + 0b11110, + 0b10001, + 0b11110, + 0b10000, + 0b10000, + 0b10000, + + 0b00000, // e 3 + 0b00000, + 0b01110, + 0b10000, + 0b01100, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // e 4 + 0b00000, + 0b10001, + 0b10001, + 0b10001, + 0b10011, + 0b11101, + 0b10000, + 0b10000, + 0b10000, + + 0b00000, // e 5 + 0b00000, + 0b00000, + 0b01111, + 0b10100, + 0b10010, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + + 0b00000, // e 6 + 0b00000, + 0b00110, + 0b01001, + 0b10001, + 0b10001, + 0b11110, + 0b10000, + 0b10000, + 0b10000, + + 0b00000, // e 7 + 0b00000, + 0b01111, + 0b10001, + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b00001, + 0b01110, + + 0b00000, // e 8 + 0b00000, + 0b00111, + 0b00100, + 0b00100, + 0b10100, + 0b01000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // e 9 + 0b00010, + 0b11010, + 0b00010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00010, // e a + 0b00000, + 0b00110, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b00010, + 0b10010, + 0b01100, + + 0b00000, // e b + 0b10100, + 0b01000, + 0b10100, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // e c + 0b00100, + 0b01110, + 0b10100, + 0b10101, + 0b01110, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b01000, // e d + 0b01000, + 0b11100, + 0b01000, + 0b11100, + 0b01000, + 0b01111, + 0b00000, + 0b00000, + 0b00000, + + 0b01110, // e e + 0b00000, + 0b10110, + 0b11001, + 0b10001, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b01010, // e f + 0b00000, + 0b01110, + 0b10001, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f 0 + 0b00000, + 0b10110, + 0b11001, + 0b10001, + 0b10001, + 0b11110, + 0b10000, + 0b10000, + 0b10000, + + 0b00000, // f 1 + 0b00000, + 0b01101, + 0b10011, + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b00001, + 0b00001, + + 0b00000, // f 2 + 0b01110, + 0b10001, + 0b11111, + 0b10001, + 0b10001, + 0b01110, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f 3 + 0b00000, + 0b00000, + 0b01011, + 0b10101, + 0b11010, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f 4 + 0b00000, + 0b01110, + 0b10001, + 0b10001, + 0b01010, + 0b11011, + 0b00000, + 0b00000, + 0b00000, + + 0b01010, // f 5 + 0b00000, + 0b10001, + 0b10001, + 0b10001, + 0b10011, + 0b01101, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // f 6 + 0b10000, + 0b01000, + 0b00100, + 0b01000, + 0b10000, + 0b11111, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f 7 + 0b00000, + 0b11111, + 0b01010, + 0b01010, + 0b01010, + 0b10011, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // f 8 + 0b00000, + 0b10001, + 0b01010, + 0b00100, + 0b01010, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f 9 + 0b00000, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b00001, + 0b01110, + + 0b00000, // f a + 0b00001, + 0b11110, + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f b + 0b00000, + 0b11111, + 0b01000, + 0b01111, + 0b01001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f c + 0b00000, + 0b11111, + 0b10101, + 0b11111, + 0b10001, + 0b10001, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f d + 0b00000, + 0b00100, + 0b00000, + 0b11111, + 0b00000, + 0b00100, + 0b00000, + 0b00000, + 0b00000, + + 0b00000, // f e + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + 0b00000, + + 0b11111, // f f + 0b11111, + 0b11111, + 0b11111, + 0b11111, + 0b11111, + 0b11111, + 0b11111, + 0b11111, + 0b11111, + }; + + static_assert(std::size(g_fontTable0) == static_cast<size_t>(256 * 10)); + + const uint8_t* getCharacterData(const uint8_t _character) + { + return &g_fontTable0[static_cast<size_t>(_character) * 10]; + } +} diff --git a/source/hardwareLib/lcdfonts.h b/source/hardwareLib/lcdfonts.h @@ -0,0 +1,8 @@ +#pragma once + +#include <cstdint> + +namespace hwLib +{ + const uint8_t* getCharacterData(uint8_t _character); +} diff --git a/source/hardwareLib/sciMidi.cpp b/source/hardwareLib/sciMidi.cpp @@ -0,0 +1,126 @@ +#include "sciMidi.h" + +#include <deque> + +#include "mc68k/qsm.h" + +#include "synthLib/midiBufferParser.h" + +namespace hwLib +{ + // pause 0.1 seconds for a sysex size of 500, delay is calculated for other sysex sizes accordingly + static constexpr float g_sysexSendDelaySeconds = 0.1f; + static constexpr uint32_t g_sysexSendDelaySize = 500; + + SciMidi::SciMidi(mc68k::Qsm& _qsm, const float _samplerate) : m_qsm(_qsm), m_samplerate(_samplerate), m_sysexDelaySeconds(g_sysexSendDelaySeconds), m_sysexDelaySize(g_sysexSendDelaySize) + { + } + + void SciMidi::process(const uint32_t _numSamples) + { + std::unique_lock lock(m_mutex); + + if(m_readingSysex) + return; + + auto remainingSamples = _numSamples; + + while(!m_pendingSysexBuffers.empty()) + { + if(m_remainingSysexDelay > 0) + { + const auto sub = std::min(m_remainingSysexDelay, remainingSamples); + remainingSamples -= sub; + + m_remainingSysexDelay -= sub; + } + + if(m_remainingSysexDelay) + break; + + const auto& msg = m_pendingSysexBuffers.front(); + + for (const auto b : msg) + m_qsm.writeSciRX(b); + + m_remainingSysexDelay = static_cast<uint32_t>(static_cast<float>(msg.size()) * m_samplerate * m_sysexDelaySeconds / static_cast<float>(m_sysexDelaySize)); + + m_pendingSysexBuffers.pop_front(); + } + } + + void SciMidi::write(const uint8_t _byte) + { + std::unique_lock lock(m_mutex); + + if(_byte == 0xf0) + { + m_writingSysex = true; + } + + if(m_writingSysex) + { + m_pendingSysexMessage.push_back(_byte); + } + else + { + m_qsm.writeSciRX(_byte); + } + + if (_byte == 0xf7) + { + m_writingSysex = false; + + if (!m_pendingSysexMessage.empty()) + m_pendingSysexBuffers.push_back(std::move(m_pendingSysexMessage)); + + m_pendingSysexMessage.clear(); + } + } + + void SciMidi::write(const synthLib::SMidiEvent& _e) + { + if(!_e.sysex.empty()) + { + write(_e.sysex); + } + else + { + write(_e.a); + const auto len = synthLib::MidiBufferParser::lengthFromStatusByte(_e.a); + if (len > 1) + write(_e.b); + if (len > 2) + write(_e.c); + } + } + + void SciMidi::read(std::vector<uint8_t>& _result) + { + std::deque<uint16_t> midiData; + m_qsm.readSciTX(midiData); + if (midiData.empty()) + return; + + _result.clear(); + _result.reserve(midiData.size()); + + for (const auto data : midiData) + { + const uint8_t d = data & 0xff; + + if(d == 0xf0) + m_readingSysex = true; + else if(d == 0xf7) + m_readingSysex = false; + + _result.push_back(d); + } + } + + void SciMidi::setSysexDelay(const float _seconds, const uint32_t _size) + { + m_sysexDelaySeconds = _seconds; + m_sysexDelaySize = _size; + } +} diff --git a/source/hardwareLib/sciMidi.h b/source/hardwareLib/sciMidi.h @@ -0,0 +1,60 @@ +#pragma once + +#include <deque> +#include <vector> +#include <cstdint> +#include <mutex> + +namespace synthLib +{ + struct SMidiEvent; +} + +namespace mc68k +{ + class Qsm; +} + +namespace hwLib +{ + class SciMidi + { + public: + explicit SciMidi(mc68k::Qsm& _qsm, float _samplerate); + + void process(uint32_t _numSamples); + + void write(uint8_t _byte); + void write(const std::initializer_list<uint8_t>& _bytes) + { + for (const uint8_t byte : _bytes) + write(byte); + } + void write(const std::vector<uint8_t>& _bytes) + { + for (const uint8_t byte : _bytes) + write(byte); + } + void write(const synthLib::SMidiEvent& _e); + + + void read(std::vector<uint8_t>& _result); + + void setSysexDelay(const float _seconds, const uint32_t _size); + + private: + mc68k::Qsm& m_qsm; + + const float m_samplerate; + + bool m_readingSysex = false; + bool m_writingSysex = false; + uint32_t m_remainingSysexDelay = 0; + + std::deque< std::vector<uint8_t> > m_pendingSysexBuffers; + std::vector<uint8_t> m_pendingSysexMessage; + std::mutex m_mutex; + float m_sysexDelaySeconds; + uint32_t m_sysexDelaySize; + }; +} diff --git a/source/hardwareLib/syncUCtoDSP.h b/source/hardwareLib/syncUCtoDSP.h @@ -0,0 +1,99 @@ +#pragma once + +#include <cstdint> + +#include "haltDSP.h" + +namespace hwLib +{ + template<uint32_t UcFreqHz, uint32_t Samplerate, uint32_t SamplesThreshold> + class SyncUCtoDSP + { + public: + static constexpr double SamplerateInv = 1.0 / static_cast<double>(Samplerate); + static constexpr double UcCyclesPerSample = static_cast<double>(UcFreqHz) / Samplerate; + static constexpr double UcCyclesThreshold = UcCyclesPerSample * SamplesThreshold; + + explicit SyncUCtoDSP(dsp56k::DSP& _dsp) : m_haltDSP(_dsp) + { + } + + virtual ~SyncUCtoDSP() = default; + + void advanceDspSample() + { + m_targetUcCycles += UcCyclesPerSample; + ++m_totalSampleCount; + + if((m_totalSampleCount & (SamplesThreshold-1)) == 0) + m_cvSampleAdded.notify_one(); + } + + void advanceUcCycles(const uint32_t _cycles) + { + m_totalUcCycles += static_cast<double>(_cycles); + + evaluate(); + } + + auto& getHaltDSP() { return m_haltDSP; } + + protected: + void evaluate() + { + if(!m_totalSampleCount) + return; + + const auto diff = m_totalUcCycles - m_targetUcCycles; + + if(diff > UcCyclesThreshold) + { + // UC is too fast, slow down + resumeDSP(); + haltUC(); + } + else if(diff < -UcCyclesThreshold) + { + haltDSP(); + resumeUC(); + } + else + { + resumeDSP(); + resumeUC(); + } + } + + void haltUC() + { + const auto lastCount = m_totalSampleCount; + std::unique_lock lock(m_lockSampleAdded); + m_cvSampleAdded.wait(lock, [&] + { + return m_totalSampleCount > lastCount; + }); + } + + virtual void resumeUC() + { + } + + void haltDSP() + { + m_haltDSP.haltDSP(); + } + + void resumeDSP() + { + m_haltDSP.resumeDSP(); + } + + private: + HaltDSP m_haltDSP; + double m_targetUcCycles = 0.0; + double m_totalUcCycles = 0.0; + uint32_t m_totalSampleCount = 0; + std::condition_variable m_cvSampleAdded; + std::mutex m_lockSampleAdded; + }; +} diff --git a/source/juce.cmake b/source/juce.cmake @@ -92,6 +92,7 @@ macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProjec CLAP_FEATURES ${clapFeatures} CLAP_SUPPORT_URL "https://dsp56300.wordpress.com" CLAP_MANUAL_URL "https://dsp56300.wordpress.com" + CLAP_USE_JUCE_PARAMETER_RANGES "DISCRETE" ) set_property(TARGET ${targetName}_CLAP PROPERTY FOLDER ${targetName}) add_dependencies(${targetName}_All ${targetName}_CLAP) diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.cpp b/source/jucePluginEditorLib/patchmanager/patchmanager.cpp @@ -365,6 +365,8 @@ namespace jucePluginEditorLib::patchManager if(getCurrentPart() == _part) getListModel()->setSelectedPatches({_patch}); + onSelectedPatchChanged(_part, _patch); + return true; } @@ -382,7 +384,7 @@ namespace jucePluginEditorLib::patchManager }); } - uint32_t PatchManager::createSaveMenuEntries(juce::PopupMenu& _menu, uint32_t _part) + uint32_t PatchManager::createSaveMenuEntries(juce::PopupMenu& _menu, uint32_t _part, const std::string& _name/* = "patch"*/) { const auto& state = getState(); const auto key = state.getPatch(_part); @@ -401,7 +403,7 @@ namespace jucePluginEditorLib::patchManager if(*p == key) { ++countAdded; - _menu.addItem("Overwrite patch '" + p->getName() + "' in user bank '" + ds->name + "'", true, false, [this, p, _part] + _menu.addItem("Overwrite " + _name + " '" + p->getName() + "' in user bank '" + ds->name + "'", true, false, [this, p, _part] { const auto newPatch = requestPatchForPart(_part); if(newPatch) @@ -424,7 +426,7 @@ namespace jucePluginEditorLib::patchManager for (const auto& ds : existingLocalDS) { ++countAdded; - _menu.addItem("Add to user bank '" + ds->name + "'", true, false, [this, ds, _part] + _menu.addItem("Add " + _name + " to user bank '" + ds->name + "'", true, false, [this, ds, _part] { const auto newPatch = requestPatchForPart(_part); @@ -438,7 +440,7 @@ namespace jucePluginEditorLib::patchManager else { ++countAdded; - _menu.addItem("Create new user bank and add patch", true, false, [this, _part] + _menu.addItem("Create new user bank and add " + _name, true, false, [this, _part] { const auto newPatch = requestPatchForPart(_part); @@ -689,7 +691,7 @@ namespace jucePluginEditorLib::patchManager bool PatchManager::activatePatch(const std::string& _filename, const uint32_t _part) { - if(_part >= m_state.getPartCount()) + if(_part >= m_state.getPartCount() || _part > m_editor.getProcessor().getController().getPartCount()) return false; const auto patches = loadPatchesFromFiles(std::vector<std::string>{_filename}); @@ -741,7 +743,7 @@ namespace jucePluginEditorLib::patchManager { DB::onLoadFinished(); - for(uint32_t i=0; i<m_state.getPartCount(); ++i) + for(uint32_t i=0; i<std::min(m_editor.getProcessor().getController().getPartCount(), static_cast<uint8_t>(m_state.getPartCount())); ++i) { const auto p = m_state.getPatch(i); diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.h b/source/jucePluginEditorLib/patchmanager/patchmanager.h @@ -4,6 +4,7 @@ #include "state.h" #include "types.h" +#include "jucePluginLib/event.h" #include "jucePluginLib/patchdb/db.h" #include "juce_gui_basics/juce_gui_basics.h" @@ -40,6 +41,8 @@ namespace jucePluginEditorLib::patchManager Grid }; + pluginLib::Event<uint32_t, pluginLib::patchDB::PatchKey> onSelectedPatchChanged; + static constexpr std::initializer_list<GroupType> DefaultGroupTypes{GroupType::Favourites, GroupType::LocalStorage, GroupType::Factory, GroupType::DataSources}; explicit PatchManager(Editor& _editor, Component* _root, const juce::File& _dir, const std::initializer_list<GroupType>& _groupTypes = DefaultGroupTypes); @@ -89,7 +92,11 @@ namespace jucePluginEditorLib::patchManager void copyPatchesToLocalStorage(const pluginLib::patchDB::DataSourceNodePtr& _ds, const std::vector<pluginLib::patchDB::PatchPtr>& _patches, int _part); - uint32_t createSaveMenuEntries(juce::PopupMenu& _menu, uint32_t _part); + uint32_t createSaveMenuEntries(juce::PopupMenu& _menu, uint32_t _part, const std::string& _name = "patch"); + uint32_t createSaveMenuEntries(juce::PopupMenu& _menu, const std::string& _name = "patch") + { + return createSaveMenuEntries(_menu, getCurrentPart(), _name); + } std::string getTagTypeName(pluginLib::patchDB::TagType _type) const; void setTagTypeName(pluginLib::patchDB::TagType _type, const std::string& _name); diff --git a/source/jucePluginEditorLib/pluginEditor.cpp b/source/jucePluginEditorLib/pluginEditor.cpp @@ -138,31 +138,62 @@ namespace jucePluginEditorLib { m_patchManager.reset(_patchManager); - if(_patchManager && !m_instanceConfig.empty()) - m_patchManager->setPerInstanceConfig(m_instanceConfig); + if(_patchManager && !m_patchManagerConfig.empty()) + m_patchManager->setPerInstanceConfig(m_patchManagerConfig); } void Editor::setPerInstanceConfig(const std::vector<uint8_t>& _data) { - m_instanceConfig = _data; + { + // test if its an old version that didn't use chunks yet + pluginLib::PluginStream oldStream(_data); + const auto version = oldStream.read<uint32_t>(); - if(m_patchManager) - m_patchManager->setPerInstanceConfig(_data); + if(version == 1) + { + m_patchManagerConfig = _data; + if(m_patchManager) + m_patchManager->setPerInstanceConfig(_data); + return; + } + } + + baseLib::BinaryStream s(_data); + baseLib::ChunkReader cr(s); + loadChunkData(cr); + cr.read(); + } + + void Editor::loadChunkData(baseLib::ChunkReader& _cr) + { + _cr.add("pmDt", 2, [this](baseLib::BinaryStream& _s, uint32_t/* _version*/) + { + m_patchManagerConfig.clear(); + _s.read(m_patchManagerConfig); + if(m_patchManager) + m_patchManager->setPerInstanceConfig(m_patchManagerConfig); + }); } void Editor::getPerInstanceConfig(std::vector<uint8_t>& _data) { + baseLib::BinaryStream s; + saveChunkData(s); + s.toVector(_data); + } + + void Editor::saveChunkData(baseLib::BinaryStream& _s) + { if(m_patchManager) { - m_instanceConfig.clear(); - m_patchManager->getPerInstanceConfig(m_instanceConfig); + m_patchManagerConfig.clear(); + m_patchManager->getPerInstanceConfig(m_patchManagerConfig); } - - if(!m_instanceConfig.empty()) - _data.insert(_data.end(), m_instanceConfig.begin(), m_instanceConfig.end()); + baseLib::ChunkWriter cw(_s, "pmDt", 2); + _s.write(m_patchManagerConfig); } - void Editor::setCurrentPart(uint8_t _part) + void Editor::setCurrentPart(const uint8_t _part) { genericUI::Editor::setCurrentPart(_part); @@ -442,7 +473,7 @@ namespace jucePluginEditorLib return true; } - bool Editor::setParameters(const std::map<std::string, uint8_t>& _paramValues) const + bool Editor::setParameters(const std::map<std::string, pluginLib::ParamValue>& _paramValues) const { if(_paramValues.empty()) return false; diff --git a/source/jucePluginEditorLib/pluginEditor.h b/source/jucePluginEditorLib/pluginEditor.h @@ -9,6 +9,14 @@ #include "synthLib/buildconfig.h" #include "jucePluginLib/event.h" +#include "jucePluginLib/types.h" + +namespace baseLib +{ + class ChunkReader; + class BinaryStream; + class ChunkWriter; +} namespace pluginLib { @@ -62,6 +70,9 @@ namespace jucePluginEditorLib void setPerInstanceConfig(const std::vector<uint8_t>& _data) override; void getPerInstanceConfig(std::vector<uint8_t>& _data) override; + virtual void saveChunkData(baseLib::BinaryStream& _s); + virtual void loadChunkData(baseLib::ChunkReader& _cr); + void setCurrentPart(uint8_t _part) override; void showDisclaimer() const; @@ -76,7 +87,7 @@ namespace jucePluginEditorLib bool copyRegionToClipboard(const std::string& _regionId) const; bool copyParametersToClipboard(const std::vector<std::string>& _params, const std::string& _regionId = {}) const; - bool setParameters(const std::map<std::string, uint8_t>& _paramValues) const; + bool setParameters(const std::map<std::string, pluginLib::ParamValue>& _paramValues) const; auto& getImagePool() { return m_imagePool; } @@ -102,7 +113,7 @@ namespace jucePluginEditorLib std::unique_ptr<juce::FileChooser> m_fileChooser; std::unique_ptr<patchManager::PatchManager> m_patchManager; - std::vector<uint8_t> m_instanceConfig; + std::vector<uint8_t> m_patchManagerConfig; std::vector<std::shared_ptr<juce::TemporaryFile>> m_dragAndDropTempFiles; std::vector<juce::File> m_dragAndDropFiles; ImagePool m_imagePool; diff --git a/source/jucePluginEditorLib/pluginEditorState.h b/source/jucePluginEditorLib/pluginEditorState.h @@ -12,8 +12,6 @@ namespace juce class Component; } -class VirusProcessor; - namespace jucePluginEditorLib { class Editor; diff --git a/source/jucePluginEditorLib/pluginProcessor.cpp b/source/jucePluginEditorLib/pluginProcessor.cpp @@ -3,7 +3,7 @@ #include "pluginEditorState.h" #include "pluginEditorWindow.h" -#include "synthLib/binarystream.h" +#include "baseLib/binarystream.h" namespace jucePluginEditorLib { @@ -57,7 +57,7 @@ namespace jucePluginEditorLib m_editorState.reset(); } - void Processor::saveChunkData(synthLib::BinaryStream& s) + void Processor::saveChunkData(baseLib::BinaryStream& s) { pluginLib::Processor::saveChunkData(s); @@ -69,7 +69,7 @@ namespace jucePluginEditorLib if(!m_editorStateData.empty()) { - synthLib::ChunkWriter cw(s, "EDST", 1); + baseLib::ChunkWriter cw(s, "EDST", 1); s.write(m_editorStateData); } @@ -88,11 +88,11 @@ namespace jucePluginEditorLib return true; } - void Processor::loadChunkData(synthLib::ChunkReader& _cr) + void Processor::loadChunkData(baseLib::ChunkReader& _cr) { pluginLib::Processor::loadChunkData(_cr); - _cr.add("EDST", 1, [this](synthLib::BinaryStream& _binaryStream, unsigned _version) + _cr.add("EDST", 1, [this](baseLib::BinaryStream& _binaryStream, unsigned _version) { _binaryStream.read(m_editorStateData); }); diff --git a/source/jucePluginEditorLib/pluginProcessor.h b/source/jucePluginEditorLib/pluginProcessor.h @@ -23,9 +23,9 @@ namespace jucePluginEditorLib virtual PluginEditorState* createEditorState() = 0; void destroyEditorState(); - void saveChunkData(synthLib::BinaryStream& s) override; + void saveChunkData(baseLib::BinaryStream& s) override; bool loadCustomData(const std::vector<uint8_t>& _sourceBuffer) override; - void loadChunkData(synthLib::ChunkReader& _cr) override; + void loadChunkData(baseLib::ChunkReader& _cr) override; private: std::unique_ptr<PluginEditorState> m_editorState; diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt @@ -10,6 +10,7 @@ set(SOURCES createVersionDateTime.cmake clipboard.cpp clipboard.h controller.cpp controller.h + controllermap.cpp controllermap.h dummydevice.cpp dummydevice.h event.cpp event.h midipacket.cpp midipacket.h diff --git a/source/jucePluginLib/clipboard.h b/source/jucePluginLib/clipboard.h @@ -5,6 +5,8 @@ #include <string> #include <vector> +#include "types.h" + namespace pluginLib { class Processor; @@ -14,7 +16,7 @@ namespace pluginLib public: struct Data { - using ParameterValues = std::map<std::string,uint8_t>; + using ParameterValues = std::map<std::string, ParamValue>; std::string pluginName; std::string pluginVersionString; diff --git a/source/jucePluginLib/controller.cpp b/source/jucePluginLib/controller.cpp @@ -44,7 +44,7 @@ namespace pluginLib std::map<ParamIndex, int> knownParameterIndices; - for (uint8_t part = 0; part < 16; part++) + for (uint8_t part = 0; part < getPartCount(); part++) { m_paramsByParamType[part].reserve(m_descriptions.getDescriptions().size()); @@ -147,7 +147,7 @@ namespace pluginLib softKnobs.push_back(i); } - for(size_t part = 0; part<m_paramsByParamType.size(); ++part) + for(size_t part = 0; part<getPartCount(); ++part) { for (const auto& softKnobParam : softKnobs) { @@ -157,10 +157,10 @@ namespace pluginLib } } - void Controller::sendSysEx(const pluginLib::SysEx& msg) const + void Controller::sendSysEx(const pluginLib::SysEx& _msg) const { synthLib::SMidiEvent ev(synthLib::MidiEventSource::Editor); - ev.sysex = msg; + ev.sysex = _msg; sendMidiEvent(ev); } @@ -174,7 +174,7 @@ namespace pluginLib m_processor.addMidiEvent(synthLib::SMidiEvent(_source, _a, _b, _c, _offset)); } - bool Controller::combineParameterChange(uint8_t& _result, const std::string& _midiPacket, const Parameter& _parameter, uint8_t _value) const + bool Controller::combineParameterChange(uint8_t& _result, const std::string& _midiPacket, const Parameter& _parameter, ParamValue _value) const { const auto &desc = _parameter.getDescription(); @@ -214,7 +214,7 @@ namespace pluginLib if (definitions.size() == 1) { - _result = _value; + _result = static_cast<uint8_t>(_value); return true; } @@ -232,12 +232,26 @@ namespace pluginLib auto* p = getParameter(i, _parameter.getPart()); const auto v = p == &_parameter ? _value : getParameterValue(p); - _result |= it->getMaskedValue(v); + _result |= it->packValue(v); } return true; } + void Controller::applyPatchParameters(const MidiPacket::ParamValues& _params, const uint8_t _part) const + { + for (const auto& it : _params) + { + auto* p = getParameter(it.first.second, _part); + p->setValueFromSynth(it.second, pluginLib::Parameter::Origin::PresetChange); + + for (const auto& derivedParam : p->getDerivedParameters()) + derivedParam->setValueFromSynth(it.second, pluginLib::Parameter::Origin::PresetChange); + } + + getProcessor().updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); + } + void Controller::timerCallback() { processMidiMessages(); @@ -245,11 +259,10 @@ namespace pluginLib bool Controller::sendSysEx(const std::string& _packetName) const { - const std::map<pluginLib::MidiDataType, uint8_t> params; - return sendSysEx(_packetName, params); + return sendSysEx(_packetName, {}); } - bool Controller::sendSysEx(const std::string& _packetName, const std::map<pluginLib::MidiDataType, uint8_t>& _params) const + bool Controller::sendSysEx(const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _params) const { std::vector<uint8_t> sysex; @@ -332,7 +345,7 @@ namespace pluginLib return m_descriptions.getIndexByName(index, _name) ? index : InvalidParameterIndex; } - bool Controller::setParameters(const std::map<std::string, uint8_t>& _values, const uint8_t _part, const Parameter::Origin _changedBy) const + bool Controller::setParameters(const std::map<std::string, ParamValue>& _values, const uint8_t _part, const Parameter::Origin _changedBy) const { bool res = false; @@ -451,7 +464,7 @@ namespace pluginLib return _packet.parse(_data, _parameterValues, m_descriptions, _src); } - bool Controller::parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, const std::function<void(MidiPacket::ParamIndex, uint8_t)>& _parameterValues, const std::vector<uint8_t>& _src) const + bool Controller::parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, const std::function<void(MidiPacket::ParamIndex, ParamValue)>& _parameterValues, const std::vector<uint8_t>& _src) const { _data.clear(); return _packet.parse(_data, _parameterValues, m_descriptions, _src); @@ -481,6 +494,14 @@ namespace pluginLib return false; } + void Controller::setCurrentPart(const uint8_t _part) + { + if(_part == m_currentPart) + return; + m_currentPart = _part; + onCurrentPartChanged(m_currentPart); + } + bool Controller::parseMidiMessage(const synthLib::SMidiEvent& _e) { if(_e.sysex.empty()) @@ -497,14 +518,35 @@ namespace pluginLib m_midiMessages.insert(m_midiMessages.end(), _events.begin(), _events.end()); } - void Controller::loadChunkData(synthLib::ChunkReader& _cr) + void Controller::loadChunkData(baseLib::ChunkReader& _cr) { m_parameterLinks.loadChunkData(_cr); } - void Controller::saveChunkData(synthLib::BinaryStream& s) + void Controller::saveChunkData(baseLib::BinaryStream& _s) const + { + m_parameterLinks.saveChunkData(_s); + } + + Parameter::Origin Controller::midiEventSourceToParameterOrigin(const synthLib::MidiEventSource _source) { - m_parameterLinks.saveChunkData(s); + switch (_source) + { + case synthLib::MidiEventSource::Unknown: + return Parameter::Origin::Unknown; + case synthLib::MidiEventSource::Editor: + return Parameter::Origin::Ui; + case synthLib::MidiEventSource::Host: + return Parameter::Origin::HostAutomation; + case synthLib::MidiEventSource::PhysicalInput: + case synthLib::MidiEventSource::Plugin: + return Parameter::Origin::Midi; + case synthLib::MidiEventSource::Internal: + return Parameter::Origin::Unknown; + default: + assert(false && "implement new midi event source type"); + return Parameter::Origin::Unknown; + } } void Controller::getMidiMessages(std::vector<synthLib::SMidiEvent>& _events) diff --git a/source/jucePluginLib/controller.h b/source/jucePluginLib/controller.h @@ -11,6 +11,8 @@ #include "parameterlinks.h" +#include "event.h" + namespace juce { class AudioProcessor; @@ -27,10 +29,12 @@ namespace pluginLib public: static constexpr uint32_t InvalidParameterIndex = 0xffffffff; + Event<uint8_t> onCurrentPartChanged; + explicit Controller(Processor& _processor, const std::string& _parameterDescJson); ~Controller() override; - virtual void sendParameterChange(const Parameter& _parameter, uint8_t _value) = 0; + virtual void sendParameterChange(const Parameter& _parameter, ParamValue _value) = 0; void sendLockedParameters(uint8_t _part); juce::Value* getParamValueObject(uint32_t _index, uint8_t _part) const; @@ -40,27 +44,27 @@ namespace pluginLib uint32_t getParameterIndexByName(const std::string& _name) const; - bool setParameters(const std::map<std::string, uint8_t>& _values, uint8_t _part, Parameter::Origin _changedBy) const; + bool setParameters(const std::map<std::string, ParamValue>& _values, uint8_t _part, Parameter::Origin _changedBy) const; const MidiPacket* getMidiPacket(const std::string& _name) const; bool createNamedParamValues(MidiPacket::NamedParamValues& _params, const std::string& _packetName, uint8_t _part) const; bool createNamedParamValues(MidiPacket::NamedParamValues& _dest, const MidiPacket::AnyPartParamValues& _source) const; - bool createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _data, uint8_t _part) const; - bool createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _data, const MidiPacket::NamedParamValues& _values) const; - bool createMidiDataFromPacket(std::vector<uint8_t>& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _data, const MidiPacket::AnyPartParamValues& _values) const; + bool createMidiDataFromPacket(SysEx& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _data, uint8_t _part) const; + bool createMidiDataFromPacket(SysEx& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _data, const MidiPacket::NamedParamValues& _values) const; + bool createMidiDataFromPacket(SysEx& _sysex, const std::string& _packetName, const std::map<MidiDataType, uint8_t>& _data, const MidiPacket::AnyPartParamValues& _values) const; - bool parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const; - bool parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, MidiPacket::AnyPartParamValues& _parameterValues, const std::vector<uint8_t>& _src) const; - bool parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, const std::function<void(MidiPacket::ParamIndex, uint8_t)>& _parameterValues, const std::vector<uint8_t>& _src) const; - bool parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const; - bool parseMidiPacket(std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const std::vector<uint8_t>& _src) const; + bool parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const SysEx& _src) const; + bool parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, MidiPacket::AnyPartParamValues& _parameterValues, const SysEx& _src) const; + bool parseMidiPacket(const MidiPacket& _packet, MidiPacket::Data& _data, const std::function<void(MidiPacket::ParamIndex, ParamValue)>& _parameterValues, const SysEx& _src) const; + bool parseMidiPacket(const std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const SysEx& _src) const; + bool parseMidiPacket(std::string& _name, MidiPacket::Data& _data, MidiPacket::ParamValues& _parameterValues, const SysEx& _src) const; const auto& getExposedParameters() const { return m_synthParams; } uint8_t getCurrentPart() const { return m_currentPart; } - void setCurrentPart(const uint8_t _part) { m_currentPart = _part; } - virtual uint8_t getPartCount() { return 16; } + virtual void setCurrentPart(uint8_t _part); + virtual uint8_t getPartCount() const { return 16; } virtual bool parseSysexMessage(const SysEx&, synthLib::MidiEventSource) = 0; virtual bool parseControllerMessage(const synthLib::SMidiEvent&) = 0; @@ -72,8 +76,10 @@ namespace pluginLib // this is called by the plug-in on audio thread! void enqueueMidiMessages(const std::vector<synthLib::SMidiEvent>&); - void loadChunkData(synthLib::ChunkReader& _cr); - void saveChunkData(synthLib::BinaryStream& s); + void loadChunkData(baseLib::ChunkReader& _cr); + void saveChunkData(baseLib::BinaryStream& _s) const; + + static Parameter::Origin midiEventSourceToParameterOrigin(synthLib::MidiEventSource _source); private: void getMidiMessages(std::vector<synthLib::SMidiEvent>&); @@ -106,7 +112,9 @@ namespace pluginLib void sendMidiEvent(const synthLib::SMidiEvent& _ev) const; void sendMidiEvent(uint8_t _a, uint8_t _b, uint8_t _c, uint32_t _offset = 0, synthLib::MidiEventSource _source = synthLib::MidiEventSource::Editor) const; - bool combineParameterChange(uint8_t& _result, const std::string& _midiPacket, const Parameter& _parameter, uint8_t _value) const; + bool combineParameterChange(uint8_t& _result, const std::string& _midiPacket, const Parameter& _parameter, ParamValue _value) const; + + void applyPatchParameters(const MidiPacket::ParamValues& _params, uint8_t _part) const; virtual bool isDerivedParameter(Parameter& _derived, Parameter& _base) const { return true; } diff --git a/source/jucePluginLib/controllermap.cpp b/source/jucePluginLib/controllermap.cpp @@ -0,0 +1,38 @@ +#include "controllermap.h" + +namespace pluginLib +{ + void ControllerMap::add(synthLib::MidiStatusByte _midiStatusByte, uint8_t _cc, uint32_t _paramIndex) + { + m_ccToParamIndex[_midiStatusByte][_cc].push_back(_paramIndex); + m_paramIndexToCC[_midiStatusByte][_paramIndex].push_back(_cc); + } + + const std::vector<uint32_t>& ControllerMap::getControlledParameters(const synthLib::SMidiEvent& _ev) const + { + static std::vector<uint32_t> empty; + const uint8_t type = _ev.a & 0xf0; + + const auto itType = m_ccToParamIndex.find(type); + if(itType == m_ccToParamIndex.end()) + return empty; + + const auto itValue = itType->second.find(_ev.b); + if(itValue == itType->second.end()) + return empty; + + return itValue->second; + } + + std::vector<uint8_t> ControllerMap::getControlChanges(const synthLib::MidiStatusByte _midiStatusByte, const uint32_t _paramIndex) const + { + static std::vector<uint8_t> empty; + const auto itType = m_paramIndexToCC.find(_midiStatusByte); + if(itType == m_paramIndexToCC.end()) + return empty; + const auto itCCList = itType->second.find(_paramIndex); + if(itCCList == itType->second.end()) + return empty; + return itCCList->second; + } +} diff --git a/source/jucePluginLib/controllermap.h b/source/jucePluginLib/controllermap.h @@ -0,0 +1,24 @@ +#pragma once + +#include <cstdint> +#include <unordered_map> +#include <vector> + +#include "parameter.h" +#include "synthLib/midiTypes.h" + +namespace pluginLib +{ + class ControllerMap + { + public: + void add(synthLib::MidiStatusByte _midiStatusByte, uint8_t _cc, uint32_t _paramIndex); + + const std::vector<uint32_t>& getControlledParameters(const synthLib::SMidiEvent& _ev) const; + std::vector<uint8_t> getControlChanges(synthLib::MidiStatusByte _midiStatusByte, uint32_t _paramIndex) const; + + private: + std::unordered_map<uint8_t, std::unordered_map<uint8_t, std::vector<uint32_t>>> m_ccToParamIndex; // type (control change, poly pressure) => index (modwheel, main vol, ...) => parameter index + std::unordered_map<uint8_t, std::unordered_map<uint32_t, std::vector<uint8_t>>> m_paramIndexToCC; // type (control change, poly pressure) => parameter index => index (modwheel, main vol, ...) + }; +} diff --git a/source/jucePluginLib/midipacket.cpp b/source/jucePluginLib/midipacket.cpp @@ -17,6 +17,8 @@ namespace pluginLib std::set<uint32_t> usedParts; + std::string lastParam; + for(uint32_t i=0; i<m_definitions.size(); ++i) { const auto& d = m_definitions[i]; @@ -24,14 +26,22 @@ namespace pluginLib if(d.paramPart != AnyPart) usedParts.insert(d.paramPart); + bool isNewParam = true; + if(d.type == MidiDataType::Parameter) + { + isNewParam = d.paramName != lastParam; + lastParam = d.paramName; m_hasParameters = true; + } - const auto masked = static_cast<uint8_t>((0xff & d.paramMask) << d.paramShift); + const auto masked = d.packValue(0xff); - if(usedMask & masked) + if((usedMask & masked) || !isNewParam) { - // next byte starts if the current mask overlaps with an existing one + // next byte starts if the current mask overlaps with an existing one. + // next byte starts also if the parameter name is identical. In that case, multiple + // bytes form one parameter. usedMask = 0; ++byteIndex; } @@ -84,7 +94,7 @@ namespace pluginLib LOG("Failed to find value for parameter " << d.paramName << ", part " << d.paramPart); return false; } - _dst[i] |= d.getMaskedValue(it->second); + _dst[i] |= d.packValue(it->second); } break; case MidiDataType::Checksum: @@ -147,11 +157,15 @@ namespace pluginLib return parse(_data, [&](ParamIndex _paramIndex, uint8_t _value) { - _parameterValues.insert(std::make_pair(_paramIndex, _value)); + const auto itExisting = _parameterValues.find(_paramIndex); + if(itExisting != _parameterValues.end()) + itExisting->second |= _value; + else + _parameterValues.insert(std::make_pair(_paramIndex, _value)); }, _parameters, _src, _ignoreChecksumErrors); } - bool MidiPacket::parse(Data& _data, const std::function<void(ParamIndex, uint8_t)>& _addParamValueCallback, const ParameterDescriptions& _parameters, const Sysex& _src, bool _ignoreChecksumErrors) const + bool MidiPacket::parse(Data& _data, const std::function<void(ParamIndex, ParamValue)>& _addParamValueCallback, const ParameterDescriptions& _parameters, const Sysex& _src, bool _ignoreChecksumErrors) const { if(_src.size() != size()) return false; @@ -206,8 +220,8 @@ namespace pluginLib LOG("Failed to find named parameter " << d.paramName << " while parsing midi packet, midi byte " << i); return false; } - const auto sMasked = (s >> d.paramShift) & d.paramMask; - _addParamValueCallback(std::make_pair(d.paramPart, idx), static_cast<uint8_t>(sMasked)); + const auto sUnpacked = d.unpackValue(s); + _addParamValueCallback(std::make_pair(d.paramPart, idx), sUnpacked); } break; default: diff --git a/source/jucePluginLib/midipacket.h b/source/jucePluginLib/midipacket.h @@ -9,6 +9,8 @@ #include <unordered_map> #include <vector> +#include "types.h" + namespace pluginLib { class ParameterDescriptions; @@ -42,21 +44,27 @@ namespace pluginLib std::string paramName; uint8_t paramMask = 0xff; - uint8_t paramShift = 0; + uint8_t paramShiftRight = 0; // right shift for unpacking from midi, left for packing + uint8_t paramShiftLeft = 0; // left shift for unpacking from midi, right for packing uint8_t paramPart = AnyPart; uint32_t checksumFirstIndex = 0; uint32_t checksumLastIndex = 0; uint8_t checksumInitValue = 0; - uint8_t getMaskedValue(const uint8_t _unmasked) const + uint8_t packValue(const ParamValue _unmasked) const + { + return static_cast<uint8_t>(((_unmasked & paramMask) << paramShiftRight) >> paramShiftLeft); + } + + ParamValue unpackValue(const uint8_t _masked) const { - return static_cast<uint8_t>((_unmasked & paramMask) << paramShift); + return ((_masked << paramShiftLeft) >> paramShiftRight) & paramMask; } bool doMasksOverlap(const MidiDataDefinition& _d) const { - return (getMaskedValue(0xff) & _d.getMaskedValue(0xff)) != 0; + return (packValue(0xff) & _d.packValue(0xff)) != 0; } }; @@ -73,9 +81,9 @@ namespace pluginLib }; using ParamIndices = std::set<ParamIndex>; - using ParamValues = std::unordered_map<ParamIndex, uint8_t, ParamIndexHash>; // part, index => value - using AnyPartParamValues = std::vector<std::optional<uint8_t>>; // index => value - using NamedParamValues = std::map<std::pair<uint8_t,std::string>, uint8_t>; // part, name => value + using ParamValues = std::unordered_map<ParamIndex, ParamValue, ParamIndexHash>; // part, index => value + using AnyPartParamValues = std::vector<std::optional<ParamValue>>; // index => value + using NamedParamValues = std::map<std::pair<uint8_t,std::string>, ParamValue>; // part, name => value using Sysex = std::vector<uint8_t>; MidiPacket() = default; @@ -88,7 +96,7 @@ namespace pluginLib bool create(std::vector<uint8_t>& _dst, const Data& _data) const; bool parse(Data& _data, AnyPartParamValues& _parameterValues, const ParameterDescriptions& _parameters, const Sysex& _src, bool _ignoreChecksumErrors = true) const; bool parse(Data& _data, ParamValues& _parameterValues, const ParameterDescriptions& _parameters, const Sysex& _src, bool _ignoreChecksumErrors = true) const; - bool parse(Data& _data, const std::function<void(ParamIndex, uint8_t)>& _addParamValueCallback, const ParameterDescriptions& _parameters, const Sysex& _src, bool _ignoreChecksumErrors = true) const; + bool parse(Data& _data, const std::function<void(ParamIndex, ParamValue)>& _addParamValueCallback, const ParameterDescriptions& _parameters, const Sysex& _src, bool _ignoreChecksumErrors = true) const; bool getParameterIndices(ParamIndices& _indices, const ParameterDescriptions& _parameters) const; bool getDefinitionsForByteIndex(std::vector<const MidiDataDefinition*>& _result, uint32_t _byteIndex) const; bool getParameterIndicesForByteIndex(std::vector<ParamIndex>& _result, const ParameterDescriptions& _parameters, uint32_t _byteIndex) const; diff --git a/source/jucePluginLib/midiports.cpp b/source/jucePluginLib/midiports.cpp @@ -1,6 +1,7 @@ #include "midiports.h" #include "processor.h" + #include "juce_audio_devices/juce_audio_devices.h" namespace pluginLib @@ -37,9 +38,9 @@ namespace pluginLib return getMidiOutput() != nullptr ? getMidiOutput()->getIdentifier() : juce::String(); } - void MidiPorts::saveChunkData(synthLib::BinaryStream& _binaryStream) const + void MidiPorts::saveChunkData(baseLib::BinaryStream& _binaryStream) const { - synthLib::ChunkWriter cw(_binaryStream, "mpIO", 1); + baseLib::ChunkWriter cw(_binaryStream, "mpIO", 1); if(m_midiInput) _binaryStream.write(m_midiInput->getIdentifier().toStdString()); @@ -51,9 +52,9 @@ namespace pluginLib _binaryStream.write(std::string()); } - void MidiPorts::loadChunkData(synthLib::ChunkReader& _cr) + void MidiPorts::loadChunkData(baseLib::ChunkReader& _cr) { - _cr.add("mpIO", 1, [&](synthLib::BinaryStream& _data, uint32_t) + _cr.add("mpIO", 1, [&](baseLib::BinaryStream& _data, uint32_t) { const auto input = _data.readString(); const auto output = _data.readString(); diff --git a/source/jucePluginLib/midiports.h b/source/jucePluginLib/midiports.h @@ -3,7 +3,12 @@ #include <memory> #include "juce_audio_devices/juce_audio_devices.h" -#include "synthLib/binarystream.h" + +namespace baseLib +{ + class ChunkReader; + class BinaryStream; +} namespace juce { @@ -37,8 +42,8 @@ namespace pluginLib juce::String getInputId() const; juce::String getOutputId() const; - void saveChunkData(synthLib::BinaryStream& _binaryStream) const; - void loadChunkData(synthLib::ChunkReader& _cr); + void saveChunkData(baseLib::BinaryStream& _binaryStream) const; + void loadChunkData(baseLib::ChunkReader& _cr); private: void handleIncomingMidiMessage(juce::MidiInput* _source, const juce::MidiMessage& _message) override; diff --git a/source/jucePluginLib/parameter.cpp b/source/jucePluginLib/parameter.cpp @@ -44,7 +44,6 @@ namespace pluginLib const auto value = juce::roundToInt(floatValue); jassert(m_range.getRange().contains(floatValue) || m_range.end == floatValue); - jassert(value >= 0 && value <= 127); if (value == m_lastValue) return; @@ -54,12 +53,12 @@ namespace pluginLib { if(m_rateLimit) { - sendParameterChangeDelayed(static_cast<uint8_t>(value), ++m_uniqueDelayCallbackId); + sendParameterChangeDelayed(value, ++m_uniqueDelayCallbackId); } else { m_lastSendTime = milliseconds(); - m_controller.sendParameterChange(*this, static_cast<uint8_t>(value)); + m_controller.sendParameterChange(*this, value); } } @@ -72,7 +71,7 @@ namespace pluginLib return t.count(); } - void Parameter::sendParameterChangeDelayed(const uint8_t _value, uint32_t _uniqueId) + void Parameter::sendParameterChangeDelayed(const ParamValue _value, uint32_t _uniqueId) { if(_uniqueId != m_uniqueDelayCallbackId) return; @@ -227,13 +226,27 @@ namespace pluginLib return juce::String::formatted("%d_%d_%d", static_cast<int>(d.page), part, d.index); } - uint8_t Parameter::getDefault() const + float Parameter::getValueForText(const juce::String& _text) const { - if(m_desc.defaultValue != Description::NoDefaultValue) - return static_cast<uint8_t>(m_desc.defaultValue); + auto res = m_desc.valueList.textToValue(std::string(_text.getCharPointer())); + if(m_desc.range.getStart() < 0) + res += m_desc.range.getStart(); + return convertTo0to1(static_cast<float>(res)); + } + + ParamValue Parameter::getDefault() const + { + if(m_desc.defaultValue != Description::NoDefaultValue) + return m_desc.defaultValue; return 0; } + juce::String Parameter::getText(const float _normalisedValue, int _i) const + { + const auto v = convertFrom0to1(_normalisedValue); + return m_desc.valueList.valueToText(juce::roundToInt(v) - std::min(0, m_desc.range.getStart())); + } + void Parameter::setLocked(const bool _locked) { if(m_isLocked == _locked) diff --git a/source/jucePluginLib/parameter.h b/source/jucePluginLib/parameter.h @@ -58,24 +58,16 @@ namespace pluginLib bool isBoolean() const override { return m_desc.isBool; } bool isBipolar() const { return m_desc.isBipolar; } - float getValueForText(const juce::String &text) const override - { - const auto res = m_desc.valueList.textToValue(std::string(text.getCharPointer())); - return convertTo0to1(static_cast<float>(res)); - } + float getValueForText(const juce::String& _text) const override; float getDefaultValue() const override { - return convertTo0to1((float)getDefault()); + return convertTo0to1(static_cast<float>(getDefault())); } - virtual uint8_t getDefault() const; + virtual ParamValue getDefault() const; - juce::String getText(float normalisedValue, int /*maximumStringLength*/) const override - { - const auto v = convertFrom0to1(normalisedValue); - return m_desc.valueList.valueToText(juce::roundToInt(v)); - } + juce::String getText(float _normalisedValue, int /*maximumStringLength*/) const override; void setLocked(bool _locked); bool isLocked() const { return m_isLocked; } @@ -114,7 +106,7 @@ namespace pluginLib void setDerivedValue(const int _value); void sendToSynth(); static uint64_t milliseconds(); - void sendParameterChangeDelayed(uint8_t, uint32_t _uniqueId); + void sendParameterChangeDelayed(ParamValue _value, uint32_t _uniqueId); void forwardToDerived(const int _newValue); int clampValue(int _value) const; diff --git a/source/jucePluginLib/parameterbinding.cpp b/source/jucePluginLib/parameterbinding.cpp @@ -74,6 +74,9 @@ namespace pluginLib if (v->isBipolar()) _slider.getProperties().set("bipolar", true); + // Juce bug: If the range changes but the value doesn't, Juce doesn't issue a repaint. Do it manually + _slider.repaint(); + const BoundParameter p{v, &_slider, _param, _part}; addBinding(p); } @@ -166,7 +169,10 @@ namespace pluginLib { v->setUnnormalizedValueNotifyingHost(id - 1, Parameter::Origin::Ui); } - v->getValueObject().setValue(id - 1); + else + { + v->setUnnormalizedValue(id - 1, Parameter::Origin::Ui); + } }; const auto listenerId = v->onValueChanged.addListener([this, &_combo](pluginLib::Parameter* v) @@ -184,19 +190,74 @@ namespace pluginLib bind(_btn, _param, CurrentPart); } - void ParameterBinding::bind(juce::Button& _control, uint32_t _param, uint8_t _part) + void ParameterBinding::bind(juce::Button& _control, const uint32_t _param, const uint8_t _part) { - const auto v = m_controller.getParameter(_param, _part == CurrentPart ? m_controller.getCurrentPart() : _part); - if (!v) + const auto param = m_controller.getParameter(_param, _part == CurrentPart ? m_controller.getCurrentPart() : _part); + if (!param) { assert(false && "Failed to find parameter"); return; } - _control.getToggleStateValue().referTo(v->getValueObject()); - const BoundParameter p{v, &_control, _param, CurrentPart}; + + const bool hasCustomValue = _control.getProperties().contains("parametervalue"); + int paramValue = 1; + int paramValueOff = -1; + if(hasCustomValue) + paramValue = _control.getProperties()["parametervalue"]; + if(_control.getProperties().contains("parametervalueoff")) + paramValueOff = _control.getProperties()["parametervalueoff"]; + + _control.onClick = [&_control, param, paramValue, hasCustomValue, paramValueOff] + { + const auto on = _control.getToggleState(); + if(hasCustomValue) + { + if(on) + param->setUnnormalizedValueNotifyingHost(paramValue, Parameter::Origin::Ui); + else if(paramValueOff != -1) + param->setUnnormalizedValueNotifyingHost(paramValueOff, Parameter::Origin::Ui); + else + _control.setToggleState(true, juce::dontSendNotification); + } + else + { + param->setUnnormalizedValueNotifyingHost(on ? 1 : 0, Parameter::Origin::Ui); + } + }; + + _control.setToggleState(param->getUnnormalizedValue() == paramValue, juce::dontSendNotification); + + const auto listenerId = param->onValueChanged.addListener([this, &_control, paramValue](const Parameter* _p) + { + const auto value = _p->getUnnormalizedValue(); + _control.setToggleState(value == paramValue, juce::dontSendNotification); + }); + + const BoundParameter p{param, &_control, _param, CurrentPart, listenerId}; addBinding(p); } + bool ParameterBinding::bind(juce::Component& _component, const uint32_t _param, const uint8_t _part) + { + if(auto* slider = dynamic_cast<juce::Slider*>(&_component)) + { + bind(*slider, _param, _part); + return true; + } + if(auto* button = dynamic_cast<juce::DrawableButton*>(&_component)) + { + bind(*button, _param, _part); + return true; + } + if(auto* comboBox = dynamic_cast<juce::ComboBox*>(&_component)) + { + bind(*comboBox, _param, _part); + return true; + } + assert(false && "unknown component type"); + return false; + } + juce::Component* ParameterBinding::getBoundComponent(const Parameter* _parameter) const { const auto it = m_boundParameters.find(_parameter); @@ -213,6 +274,37 @@ namespace pluginLib return it->second; } + bool ParameterBinding::unbind(const Parameter* _param) + { + for (auto it= m_bindings.begin(); it != m_bindings.end(); ++it) + { + if(it->parameter != _param) + continue; + + m_bindings.erase(it); + + return true; + } + return false; + } + + bool ParameterBinding::unbind(const juce::Component* _component) + { + for (auto it= m_bindings.begin(); it != m_bindings.end(); ++it) + { + if(it->component != _component) + continue; + + disableBinding(*it); + + m_disabledBindings.push_back(*it); + m_bindings.erase(it); + + return true; + } + return false; + } + void ParameterBinding::removeMouseListener(juce::Slider& _slider) { const auto it = m_sliderMouseListeners.find(&_slider); @@ -228,27 +320,7 @@ namespace pluginLib void ParameterBinding::bind(const std::vector<BoundParameter>& _bindings, const bool _currentPartOnly) { for (const auto& b : _bindings) - { - auto* slider = dynamic_cast<juce::Slider*>(b.component); - if(slider) - { - bind(*slider, b.type, b.part); - continue; - } - auto* button = dynamic_cast<juce::DrawableButton*>(b.component); - if(button) - { - bind(*button, b.type, b.part); - continue; - } - auto* comboBox = dynamic_cast<juce::ComboBox*>(b.component); - if(comboBox) - { - bind(*comboBox, b.type, b.part); - continue; - } - assert(false && "unknown component type"); - } + bind(*b.component, b.paramIndex, b.part); } void ParameterBinding::addBinding(const BoundParameter& _boundParameter) @@ -285,7 +357,7 @@ namespace pluginLib auto* button = dynamic_cast<juce::Button*>(_b.component); if(button != nullptr) - button->getToggleStateValue().referTo(juce::Value()); + button->onClick = nullptr; if(_b.onChangeListenerId != ParameterListener::InvalidListenerId) { diff --git a/source/jucePluginLib/parameterbinding.h b/source/jucePluginLib/parameterbinding.h @@ -42,7 +42,7 @@ namespace pluginLib { Parameter* parameter = nullptr; juce::Component* component = nullptr; - uint32_t type = 0xffffffff; + uint32_t paramIndex = 0xffffffff; uint8_t part = CurrentPart; size_t onChangeListenerId = ParameterListener::InvalidListenerId; }; @@ -62,6 +62,8 @@ namespace pluginLib void bind(juce::Button &_control, uint32_t _param); void bind(juce::Button &_control, uint32_t _param, uint8_t _part); + bool bind(juce::Component& _component, uint32_t _param, uint8_t _part); + void clearBindings(); void clear(); void setPart(uint8_t _part); @@ -73,6 +75,9 @@ namespace pluginLib juce::Component* getBoundComponent(const pluginLib::Parameter* _parameter) const; pluginLib::Parameter* getBoundParameter(const juce::Component* _component) const; + bool unbind(const Parameter* _param); + bool unbind(const juce::Component* _component); + private: void removeMouseListener(juce::Slider& _slider); diff --git a/source/jucePluginLib/parameterdescriptions.cpp b/source/jucePluginLib/parameterdescriptions.cpp @@ -62,23 +62,6 @@ namespace pluginLib return &it->second; } - const std::vector<uint32_t>& ParameterDescriptions::getControlledParameters(const synthLib::SMidiEvent& _ev) - { - static std::vector<uint32_t> empty; - - const uint8_t type = _ev.a & 0xf0; - - const auto itType = m_controllerMap.find(type); - if(itType == m_controllerMap.end()) - return empty; - - const auto itValue = itType->second.find(_ev.b); - if(itValue == itType->second.end()) - return empty; - - return itValue->second; - } - std::string ParameterDescriptions::loadJson(const std::string& _jsonString) { // juce' JSON parser doesn't like JSON5-style comments @@ -205,16 +188,6 @@ namespace pluginLib const auto maxValue = readPropertyInt("max"); const auto defaultValue = readPropertyIntWithDefault("default", Description::NoDefaultValue); - if (minValue < 0 || minValue > 127) - { - errors << name << ": min value for parameter desc " << name << " must be in range 0-127 but min is set to " << minValue << std::endl; - continue; - } - if(maxValue < 0 || maxValue > 127) - { - errors << name << ": max value for parameter desc " << name << " must be in range 0-127 but max is set to " << maxValue << std::endl; - continue; - } if (maxValue < minValue) { errors << name << ": max value must be larger than min value but min is " << minValue << ", max is " << maxValue << std::endl; @@ -563,7 +536,8 @@ namespace pluginLib } const auto hasMask = entry.hasProperty("mask"); - const auto hasShift = entry.hasProperty("shift"); + const auto hasRightShift = entry.hasProperty("shift"); + const auto hasLeftShift = entry.hasProperty("shiftL"); const auto hasPart = entry.hasProperty("part"); if(hasMask) @@ -577,15 +551,26 @@ namespace pluginLib byte.paramMask = static_cast<uint8_t>(mask); } - if(hasShift) + if(hasRightShift) { const int shift = entry["shift"]; if(shift < 0 || shift > 7) { - _errors << "shift value needs to be between 0 and 7 but got " << shift << std::endl; + _errors << "right shift value needs to be between 0 and 7 but got " << shift << std::endl; + return; + } + byte.paramShiftRight = static_cast<uint8_t>(shift); + } + + if(hasLeftShift) + { + const int shift = entry["shiftL"]; + if(shift < 0 || shift > 7) + { + _errors << "left shift value needs to be between 0 and 7 but got " << shift << std::endl; return; } - byte.paramShift = static_cast<uint8_t>(shift); + byte.paramShiftLeft = static_cast<uint8_t>(shift); } if(hasPart) @@ -628,7 +613,7 @@ namespace pluginLib else if(type == "null") byte.type = MidiDataType::Null; else { - _errors << "Unknown midi packet data type " << type << ", midi packet " << _key << ", index " << i << std::endl; + _errors << "Unknown midi packet data type '" << type << "', midi packet " << _key << ", index " << i << std::endl; return; } @@ -842,9 +827,9 @@ namespace pluginLib } if(cc != Invalid) - m_controllerMap[synthLib::M_CONTROLCHANGE][cc].push_back(paramIndex); + m_controllerMap.add(synthLib::M_CONTROLCHANGE, cc, paramIndex); if(pp != Invalid) - m_controllerMap[synthLib::M_POLYPRESSURE][pp].push_back(paramIndex); + m_controllerMap.add(synthLib::M_POLYPRESSURE, pp, paramIndex); } } diff --git a/source/jucePluginLib/parameterdescriptions.h b/source/jucePluginLib/parameterdescriptions.h @@ -4,6 +4,7 @@ #include <string> #include <vector> +#include "controllermap.h" #include "midipacket.h" #include "parameterdescription.h" #include "parameterlink.h" @@ -44,11 +45,11 @@ namespace pluginLib const ValueList* getValueList(const std::string& _key) const; - const std::vector<uint32_t>& getControlledParameters(const synthLib::SMidiEvent& _ev); - const std::string& getErrors() const { return m_errors; } bool isValid() const { return getErrors().empty(); } + const auto& getControllerMap() const { return m_controllerMap; } + private: std::string loadJson(const std::string& _jsonString); @@ -69,7 +70,7 @@ namespace pluginLib std::unordered_map<std::string, MidiPacket> m_midiPackets; std::vector<ParameterLink> m_parameterLinks; std::unordered_map<std::string, ParameterRegion> m_regions; - std::unordered_map<uint8_t, std::unordered_map<uint8_t, std::vector<uint32_t>>> m_controllerMap; // type (control change, poly pressure) => index (modwheel, main vol, ...) => parameter index + ControllerMap m_controllerMap; std::string m_errors; }; diff --git a/source/jucePluginLib/parameterlinks.cpp b/source/jucePluginLib/parameterlinks.cpp @@ -130,12 +130,12 @@ namespace pluginLib return m_linkedRegions.find(link) != m_linkedRegions.end(); } - void ParameterLinks::saveChunkData(synthLib::BinaryStream& s) const + void ParameterLinks::saveChunkData(baseLib::BinaryStream& s) const { if(m_linkedRegions.empty()) return; - synthLib::ChunkWriter cw(s, "PLNK", 1); + baseLib::ChunkWriter cw(s, "PLNK", 1); s.write(static_cast<uint32_t>(m_linkedRegions.size())); @@ -147,9 +147,9 @@ namespace pluginLib } } - void ParameterLinks::loadChunkData(synthLib::ChunkReader& _cr) + void ParameterLinks::loadChunkData(baseLib::ChunkReader& _cr) { - _cr.add("PLNK", 1, [this](synthLib::BinaryStream& _binaryStream, unsigned _version) + _cr.add("PLNK", 1, [this](baseLib::BinaryStream& _binaryStream, unsigned _version) { const uint32_t count = _binaryStream.read<uint32_t>(); diff --git a/source/jucePluginLib/parameterlinks.h b/source/jucePluginLib/parameterlinks.h @@ -26,8 +26,8 @@ namespace pluginLib bool unlinkRegion(const std::string& _regionId, uint8_t _partSource, uint8_t _partDest); bool isRegionLinked(const std::string& _regionId, uint8_t _partSource, uint8_t _partDest) const; - void saveChunkData(synthLib::BinaryStream& s) const; - void loadChunkData(synthLib::ChunkReader& _cr); + void saveChunkData(baseLib::BinaryStream& s) const; + void loadChunkData(baseLib::ChunkReader& _cr); private: struct RegionLink diff --git a/source/jucePluginLib/patchdb/datasource.cpp b/source/jucePluginLib/patchdb/datasource.cpp @@ -7,7 +7,7 @@ #include "db.h" #include "patch.h" -#include "synthLib/binarystream.h" +#include "baseLib/binarystream.h" namespace pluginLib::patchDB { @@ -157,9 +157,9 @@ namespace pluginLib::patchDB return ss.str(); } - void DataSource::write(synthLib::BinaryStream& _outStream) const + void DataSource::write(baseLib::BinaryStream& _outStream) const { - synthLib::ChunkWriter cw(_outStream, chunks::g_datasource, 1); + baseLib::ChunkWriter cw(_outStream, chunks::g_datasource, 1); _outStream.write(static_cast<uint8_t>(type)); _outStream.write(static_cast<uint8_t>(origin)); @@ -172,7 +172,7 @@ namespace pluginLib::patchDB patch->write(_outStream); } - bool DataSource::read(synthLib::BinaryStream& _inStream) + bool DataSource::read(baseLib::BinaryStream& _inStream) { auto in = _inStream.tryReadChunk(chunks::g_datasource); if(!in) diff --git a/source/jucePluginLib/patchdb/datasource.h b/source/jucePluginLib/patchdb/datasource.h @@ -4,7 +4,7 @@ #include "patchdbtypes.h" -namespace synthLib +namespace baseLib { class BinaryStream; } @@ -115,8 +115,8 @@ namespace pluginLib::patchDB std::string toString() const; - void write(synthLib::BinaryStream& _outStream) const; - bool read(synthLib::BinaryStream& _inStream); + void write(baseLib::BinaryStream& _outStream) const; + bool read(baseLib::BinaryStream& _inStream); }; struct DataSourceNode final : DataSource, std::enable_shared_from_this<DataSourceNode> diff --git a/source/jucePluginLib/patchdb/db.cpp b/source/jucePluginLib/patchdb/db.cpp @@ -8,8 +8,9 @@ #include "synthLib/os.h" #include "synthLib/midiToSysex.h" -#include "synthLib/hybridcontainer.h" -#include "synthLib/binarystream.h" + +#include "baseLib/hybridcontainer.h" +#include "baseLib/binarystream.h" #include "dsp56kEmu/logging.h" @@ -1733,7 +1734,7 @@ namespace pluginLib::patchDB try { - synthLib::BinaryStream inStream(data); + baseLib::BinaryStream inStream(data); auto stream = inStream.tryReadChunk(chunks::g_patchManager, 2); @@ -1933,14 +1934,14 @@ namespace pluginLib::patchDB if(!m_cacheFileName.hasWriteAccess()) return; - synthLib::BinaryStream outStream; + baseLib::BinaryStream outStream; { std::shared_lock lockDS(m_dataSourcesMutex); std::shared_lock lockP(m_patchesMutex); - synthLib::ChunkWriter cw(outStream, chunks::g_patchManager, 2); + baseLib::ChunkWriter cw(outStream, chunks::g_patchManager, 2); { - synthLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerDataSources, 1); + baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerDataSources, 1); outStream.write<uint32_t>(static_cast<uint32_t>(m_dataSources.size())); @@ -1985,7 +1986,7 @@ namespace pluginLib::patchDB { // write tags - synthLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerTags, 1); + baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerTags, 1); outStream.write(static_cast<uint32_t>(m_tags.size())); @@ -2004,7 +2005,7 @@ namespace pluginLib::patchDB { // write tag colors - synthLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerTagColors, 1); + baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerTagColors, 1); outStream.write(static_cast<uint32_t>(m_tagColors.size())); @@ -2026,7 +2027,7 @@ namespace pluginLib::patchDB { // write patch modifications - synthLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerPatchModifications, 1); + baseLib::ChunkWriter cwDS(outStream, chunks::g_patchManagerPatchModifications, 1); outStream.write(static_cast<uint32_t>(m_patchModifications.size())); diff --git a/source/jucePluginLib/patchdb/db.h b/source/jucePluginLib/patchdb/db.h @@ -98,7 +98,10 @@ namespace pluginLib::patchDB virtual PatchPtr initializePatch(Data&& _sysex) = 0; virtual Data prepareSave(const PatchPtr& _patch) const = 0; virtual bool parseFileData(DataList& _results, const Data& _data); - virtual bool equals(const PatchPtr& _a, const PatchPtr& _b) const = 0; + virtual bool equals(const PatchPtr& _a, const PatchPtr& _b) const + { + return _a == _b || _a->hash == _b->hash; + } virtual void processDirty(const Dirty& _dirty) const = 0; protected: diff --git a/source/jucePluginLib/patchdb/patch.cpp b/source/jucePluginLib/patchdb/patch.cpp @@ -9,7 +9,7 @@ #include "juce_core/juce_core.h" -#include "synthLib/binarystream.h" +#include "baseLib/binarystream.h" namespace pluginLib::patchDB { @@ -43,9 +43,9 @@ namespace pluginLib::patchDB sysex = _patch.sysex; } - void Patch::write(synthLib::BinaryStream& _s) const + void Patch::write(baseLib::BinaryStream& _s) const { - synthLib::ChunkWriter chunkWriter(_s, chunks::g_patch, 2); + baseLib::ChunkWriter chunkWriter(_s, chunks::g_patch, 2); _s.write(name); _s.write(bank); @@ -66,7 +66,7 @@ namespace pluginLib::patchDB _s.write(sysex); } - bool Patch::read(synthLib::BinaryStream& _in) + bool Patch::read(baseLib::BinaryStream& _in) { auto in = _in.tryReadChunk(chunks::g_patch, 2); if(!in) diff --git a/source/jucePluginLib/patchdb/patch.h b/source/jucePluginLib/patchdb/patch.h @@ -9,7 +9,7 @@ #include "tags.h" #include "patchdbtypes.h" -namespace synthLib +namespace baseLib { class BinaryStream; } @@ -33,8 +33,8 @@ namespace pluginLib::patchDB void replaceData(const Patch& _patch); - void write(synthLib::BinaryStream& _s) const; - bool read(synthLib::BinaryStream& _in); + void write(baseLib::BinaryStream& _s) const; + bool read(baseLib::BinaryStream& _in); bool operator == (const PatchKey& _key) const; bool operator != (const PatchKey& _key) const diff --git a/source/jucePluginLib/patchdb/patchmodifications.cpp b/source/jucePluginLib/patchdb/patchmodifications.cpp @@ -4,7 +4,7 @@ #include "juce_core/juce_core.h" -#include "synthLib/binarystream.h" +#include "baseLib/binarystream.h" namespace pluginLib::patchDB { @@ -109,14 +109,14 @@ namespace pluginLib::patchDB return name.empty() && tags.empty(); } - void PatchModifications::write(synthLib::BinaryStream& _outStream) const + void PatchModifications::write(baseLib::BinaryStream& _outStream) const { - synthLib::ChunkWriter cw(_outStream, chunks::g_patchModification, 1); + baseLib::ChunkWriter cw(_outStream, chunks::g_patchModification, 1); _outStream.write(name); tags.write(_outStream); } - bool PatchModifications::read(synthLib::BinaryStream& _binaryStream) + bool PatchModifications::read(baseLib::BinaryStream& _binaryStream) { auto in = _binaryStream.tryReadChunk(chunks::g_patchModification, 1); if(!in) diff --git a/source/jucePluginLib/patchdb/patchmodifications.h b/source/jucePluginLib/patchdb/patchmodifications.h @@ -20,8 +20,8 @@ namespace pluginLib::patchDB bool empty() const; - void write(synthLib::BinaryStream& _outStream) const; - bool read(synthLib::BinaryStream& _binaryStream); + void write(baseLib::BinaryStream& _outStream) const; + bool read(baseLib::BinaryStream& _binaryStream); std::weak_ptr<Patch> patch; TypedTags tags; diff --git a/source/jucePluginLib/patchdb/tags.cpp b/source/jucePluginLib/patchdb/tags.cpp @@ -2,13 +2,13 @@ #include "juce_core/juce_core.h" -#include "synthLib/binarystream.h" +#include "baseLib/binarystream.h" namespace pluginLib::patchDB { - void Tags::write(synthLib::BinaryStream& _s) const + void Tags::write(baseLib::BinaryStream& _s) const { - synthLib::ChunkWriter cw(_s, chunks::g_tags, 1); + baseLib::ChunkWriter cw(_s, chunks::g_tags, 1); _s.write(m_added.size()); for (const auto& added : m_added) _s.write(added); @@ -17,7 +17,7 @@ namespace pluginLib::patchDB _s.write(removed); } - bool Tags::read(synthLib::BinaryStream& _stream) + bool Tags::read(baseLib::BinaryStream& _stream) { auto in = _stream.tryReadChunk(chunks::g_tags, 1); if(!in) @@ -253,9 +253,9 @@ namespace pluginLib::patchDB return true; } - void TypedTags::write(synthLib::BinaryStream& _s) const + void TypedTags::write(baseLib::BinaryStream& _s) const { - synthLib::ChunkWriter cw(_s, chunks::g_typedTags, 1); + baseLib::ChunkWriter cw(_s, chunks::g_typedTags, 1); _s.write(m_tags.size()); for (const auto& t : m_tags) @@ -265,7 +265,7 @@ namespace pluginLib::patchDB } } - bool TypedTags::read(synthLib::BinaryStream& _stream) + bool TypedTags::read(baseLib::BinaryStream& _stream) { auto in = _stream.tryReadChunk(chunks::g_typedTags, 1); diff --git a/source/jucePluginLib/patchdb/tags.h b/source/jucePluginLib/patchdb/tags.h @@ -6,7 +6,7 @@ #include "patchdbtypes.h" -namespace synthLib +namespace baseLib { class BinaryStream; } @@ -77,8 +77,8 @@ namespace pluginLib::patchDB return m_added.empty() && m_removed.empty(); } - void write(synthLib::BinaryStream& _s) const; - bool read(synthLib::BinaryStream& _stream); + void write(baseLib::BinaryStream& _s) const; + bool read(baseLib::BinaryStream& _stream); bool operator == (const Tags& _t) const; @@ -105,8 +105,8 @@ namespace pluginLib::patchDB void deserialize(juce::DynamicObject* _obj); bool operator == (const TypedTags& _tags) const; - void write(synthLib::BinaryStream& _s) const; - bool read(synthLib::BinaryStream& _stream); + void write(baseLib::BinaryStream& _s) const; + bool read(baseLib::BinaryStream& _stream); private: std::unordered_map<TagType, Tags> m_tags; diff --git a/source/jucePluginLib/processor.cpp b/source/jucePluginLib/processor.cpp @@ -2,9 +2,10 @@ #include "dummydevice.h" #include "types.h" +#include "baseLib/binarystream.h" + #include "synthLib/deviceException.h" #include "synthLib/os.h" -#include "synthLib/binarystream.h" #include "synthLib/midiBufferParser.h" #include "dsp56kEmu/fastmath.h" @@ -96,6 +97,8 @@ namespace pluginLib try { m_device.reset(createDevice()); + if(!m_device->isValid()) + throw synthLib::DeviceException(synthLib::DeviceError::Unknown, "Device initialization failed"); } catch(const synthLib::DeviceException& e) { @@ -160,22 +163,22 @@ namespace pluginLib void Processor::saveCustomData(std::vector<uint8_t>& _targetBuffer) { - synthLib::BinaryStream s; + baseLib::BinaryStream s; saveChunkData(s); s.toVector(_targetBuffer, true); } - void Processor::saveChunkData(synthLib::BinaryStream& s) + void Processor::saveChunkData(baseLib::BinaryStream& s) { { std::vector<uint8_t> buffer; getPlugin().getState(buffer, synthLib::StateTypeGlobal); - synthLib::ChunkWriter cw(s, "MIDI", 1); + baseLib::ChunkWriter cw(s, "MIDI", 1); s.write(buffer); } { - synthLib::ChunkWriter cw(s, "GAIN", 1); + baseLib::ChunkWriter cw(s, "GAIN", 1); s.write<uint32_t>(1); // version s.write(m_inputGain); s.write(m_outputGain); @@ -183,13 +186,13 @@ namespace pluginLib if(m_dspClockPercent != 100) { - synthLib::ChunkWriter cw(s, "DSPC", 1); + baseLib::ChunkWriter cw(s, "DSPC", 1); s.write(m_dspClockPercent); } if(m_preferredDeviceSamplerate > 0) { - synthLib::ChunkWriter cw(s, "DSSR", 1); + baseLib::ChunkWriter cw(s, "DSSR", 1); s.write(m_preferredDeviceSamplerate); } @@ -204,41 +207,41 @@ namespace pluginLib // In Vavra, the only data we had was the gain parameters if(_sourceBuffer.size() == sizeof(float) * 2 + sizeof(uint32_t)) { - synthLib::BinaryStream ss(_sourceBuffer); + baseLib::BinaryStream ss(_sourceBuffer); readGain(ss); return true; } - synthLib::BinaryStream s(_sourceBuffer); - synthLib::ChunkReader cr(s); + baseLib::BinaryStream s(_sourceBuffer); + baseLib::ChunkReader cr(s); loadChunkData(cr); return _sourceBuffer.empty() || (cr.tryRead() && cr.numRead() > 0); } - void Processor::loadChunkData(synthLib::ChunkReader& _cr) + void Processor::loadChunkData(baseLib::ChunkReader& _cr) { - _cr.add("MIDI", 1, [this](synthLib::BinaryStream& _binaryStream, uint32_t _version) + _cr.add("MIDI", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version) { std::vector<uint8_t> buffer; _binaryStream.read(buffer); getPlugin().setState(buffer); }); - _cr.add("GAIN", 1, [this](synthLib::BinaryStream& _binaryStream, uint32_t _version) + _cr.add("GAIN", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version) { readGain(_binaryStream); }); - _cr.add("DSPC", 1, [this](synthLib::BinaryStream& _binaryStream, uint32_t _version) + _cr.add("DSPC", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version) { auto p = _binaryStream.read<uint32_t>(); p = dsp56k::clamp<uint32_t>(p, 50, 200); setDspClockPercent(p); }); - _cr.add("DSSR", 1, [this](synthLib::BinaryStream& _binaryStream, uint32_t _version) + _cr.add("DSSR", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version) { const auto sr = _binaryStream.read<float>(); setPreferredDeviceSamplerate(sr); @@ -247,7 +250,7 @@ namespace pluginLib m_midiPorts.loadChunkData(_cr); } - void Processor::readGain(synthLib::BinaryStream& _s) + void Processor::readGain(baseLib::BinaryStream& _s) { const auto version = _s.read<uint32_t>(); if (version != 1) diff --git a/source/jucePluginLib/processor.h b/source/jucePluginLib/processor.h @@ -8,10 +8,14 @@ #include "synthLib/plugin.h" -namespace synthLib +namespace baseLib { class BinaryStream; class ChunkReader; +} + +namespace synthLib +{ class Plugin; struct SMidiEvent; } @@ -53,11 +57,11 @@ namespace pluginLib virtual void updateLatencySamples(); virtual void saveCustomData(std::vector<uint8_t>& _targetBuffer); - virtual void saveChunkData(synthLib::BinaryStream& s); + virtual void saveChunkData(baseLib::BinaryStream& s); virtual bool loadCustomData(const std::vector<uint8_t>& _sourceBuffer); - virtual void loadChunkData(synthLib::ChunkReader& _cr); + virtual void loadChunkData(baseLib::ChunkReader& _cr); - void readGain(synthLib::BinaryStream& _s); + void readGain(baseLib::BinaryStream& _s); template<size_t N> void applyOutputGain(std::array<float*, N>& _buffers, const size_t _numSamples) { @@ -76,7 +80,7 @@ namespace pluginLib { if (buf) { - for (int i = 0; i < _numSamples; ++i) + for (size_t i = 0; i < _numSamples; ++i) buf[i] *= _gain; } } @@ -98,7 +102,7 @@ namespace pluginLib const Properties& getProperties() const { return m_properties; } - virtual void processBpm(float _bpm) {}; + virtual void processBpm(float _bpm) {} bool rebootDevice(); diff --git a/source/jucePluginLib/types.h b/source/jucePluginLib/types.h @@ -1,10 +1,10 @@ #pragma once -#include "synthLib/binarystream.h" +#include "baseLib/binarystream.h" namespace pluginLib { - using PluginStream = synthLib::BinaryStream; + using PluginStream = baseLib::BinaryStream; enum ParameterLinkType : uint32_t { @@ -12,4 +12,6 @@ namespace pluginLib Source = 1, Target = 2 }; + + using ParamValue = int32_t; } diff --git a/source/juceUiLib/button.cpp b/source/juceUiLib/button.cpp @@ -0,0 +1 @@ +#include "button.h" diff --git a/source/juceUiLib/button.h b/source/juceUiLib/button.h @@ -63,10 +63,28 @@ namespace genericUI return m_allowRightClick; } + bool hitTest (const int _x, const int _y) override + { + if(!T::hitTest(_x,_y)) + return false; + + if(_x < m_hitAreaOffset.getX() || _y < m_hitAreaOffset.getY()) + return false; + if(_x > T::getWidth() - m_hitAreaOffset.getWidth() || _y > T::getHeight() - m_hitAreaOffset.getHeight()) + return false; + return true; + } + + void setHitAreaOffset(const juce::Rectangle<int>& _offset) + { + m_hitAreaOffset = _offset; + } + Callback onDown; Callback onUp; private: bool m_allowRightClick = false; + juce::Rectangle<int> m_hitAreaOffset{0,0,0,0}; }; } \ No newline at end of file diff --git a/source/juceUiLib/buttonStyle.cpp b/source/juceUiLib/buttonStyle.cpp @@ -1,5 +1,6 @@ #include "buttonStyle.h" +#include "button.h" #include "uiObject.h" namespace genericUI @@ -98,5 +99,30 @@ namespace genericUI _button.setRadioGroupId(m_radioGroupId); _button.setImages(m_normalImage, m_overImage, m_downImage, m_disabledImage, m_normalImageOn, m_overImageOn, m_downImageOn, m_disabledImageOn); + + auto* button = dynamic_cast<Button<juce::DrawableButton>*>(&_button); + + if(button) + { + auto hitRect = m_hitAreaOffset; + + if(hitRect.getX() < 0) hitRect.setX(0); + if(hitRect.getY() < 0) hitRect.setY(0); + if(hitRect.getWidth() > 0) hitRect.setWidth(0); + if(hitRect.getHeight() > 0) hitRect.setHeight(0); + + auto newW = button->getWidth(); + auto newH = button->getHeight(); + + if(m_hitAreaOffset.getWidth() > 0) newW += m_hitAreaOffset.getWidth(); + if(m_hitAreaOffset.getHeight() > 0) newH += m_hitAreaOffset.getHeight(); + + button->setSize(newW, newH); + + hitRect.setWidth(-hitRect.getWidth()); + hitRect.setHeight(-hitRect.getHeight()); + + button->setHitAreaOffset(hitRect); + } } } diff --git a/source/juceUiLib/uiObject.cpp b/source/juceUiLib/uiObject.cpp @@ -590,9 +590,19 @@ namespace genericUI if(index < 0) throw std::runtime_error("Parameter named " + param + " not found"); - _editor.getInterface().bindParameter(_target, index); - _target.getProperties().set("parameter", index); + + if constexpr(std::is_base_of_v<juce::Button, T>) + { + auto value = getPropertyInt("value", -1); + if(value != -1) + _target.getProperties().set("parametervalue", value); + auto valueOff = getPropertyInt("valueOff", -1); + if(valueOff != -1) + _target.getProperties().set("parametervalueoff", valueOff); + } + + _editor.getInterface().bindParameter(_target, index); } template <typename Target, typename Style> void UiObject::createStyle(Editor& _editor, Target& _target, Style* _style) diff --git a/source/juceUiLib/uiObjectStyle.cpp b/source/juceUiLib/uiObjectStyle.cpp @@ -57,9 +57,9 @@ namespace genericUI juce::Justification a = 0; switch (alignH[0]) { - case 'L': a = juce::Justification::left; break; + case 'L': a = juce::Justification::left; break; case 'C': a = juce::Justification::horizontallyCentred; break; - case 'R': a = juce::Justification::right; break; + case 'R': a = juce::Justification::right; break; } m_align = a; } @@ -71,7 +71,7 @@ namespace genericUI switch (alignV[0]) { case 'T': a = juce::Justification::top; break; - case 'C': a = juce::Justification::verticallyCentred; break; + case 'C': a = juce::Justification::verticallyCentred; break; case 'B': a = juce::Justification::bottom; break; } m_align = m_align.getFlags() | a.getFlags(); @@ -89,6 +89,11 @@ namespace genericUI m_offsetT = _object.getPropertyInt("offsetT", 1); m_offsetR = _object.getPropertyInt("offsetR", -30); m_offsetB = _object.getPropertyInt("offsetB", -2); + + m_hitAreaOffset.setX(_object.getPropertyInt("hitOffsetL", 0)); + m_hitAreaOffset.setY(_object.getPropertyInt("hitOffsetT", 0)); + m_hitAreaOffset.setWidth(_object.getPropertyInt("hitOffsetR", 0)); + m_hitAreaOffset.setHeight(_object.getPropertyInt("hitOffsetB", 0)); } std::optional<juce::Font> UiObjectStyle::getFont() const diff --git a/source/juceUiLib/uiObjectStyle.h b/source/juceUiLib/uiObjectStyle.h @@ -69,5 +69,7 @@ namespace genericUI int m_offsetB = 0; std::string m_url; + + juce::Rectangle<int> m_hitAreaOffset; // only supported for buttons atm }; } diff --git a/source/mqJucePlugin/PluginEditorState.cpp b/source/mqJucePlugin/PluginEditorState.cpp @@ -7,69 +7,71 @@ #include "synthLib/os.h" -const std::vector<PluginEditorState::Skin> g_includedSkins = +namespace mqJucePlugin { - {"Editor", "mqDefault.json", ""}, - {"Device", "mqFrontPanel.json", ""} -}; + const std::vector<PluginEditorState::Skin> g_includedSkins = + { + {"Editor", "mqDefault.json", ""}, + {"Device", "mqFrontPanel.json", ""} + }; -PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor) : jucePluginEditorLib::PluginEditorState(_processor, _processor.getController(), g_includedSkins) -{ - loadDefaultSkin(); -} + PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor) : jucePluginEditorLib::PluginEditorState(_processor, _processor.getController(), g_includedSkins) + { + loadDefaultSkin(); + } -void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) -{ - jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); + void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) + { + jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); - auto& p = m_processor; + auto& p = m_processor; - const auto gain = static_cast<int>(std::roundf(p.getOutputGain())); + const auto gain = static_cast<int>(std::roundf(p.getOutputGain())); - juce::PopupMenu gainMenu; + juce::PopupMenu gainMenu; - gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); - gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); - gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); + gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); + gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); + gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); - _menu.addSubMenu("Output Gain", gainMenu); + _menu.addSubMenu("Output Gain", gainMenu); - jucePluginEditorLib::MidiPorts::createMidiPortsMenu(_menu, p.getMidiPorts()); -} + jucePluginEditorLib::MidiPorts::createMidiPortsMenu(_menu, p.getMidiPorts()); + } -bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) -{ - jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); - - const auto percent = m_processor.getDspClockPercent(); - const auto hz = m_processor.getDspClockHz(); + bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) + { + jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); - juce::PopupMenu clockMenu; + const auto percent = m_processor.getDspClockPercent(); + const auto hz = m_processor.getDspClockHz(); - auto makeEntry = [&](const int _percent) - { - const auto mhz = hz * _percent / 100 / 1000000; - std::stringstream ss; - ss << _percent << "% (" << mhz << " MHz)"; - if(_percent == 100) - ss << " (Default)"; - clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); - }; + juce::PopupMenu clockMenu; - makeEntry(50); - makeEntry(75); - makeEntry(100); - makeEntry(125); - makeEntry(150); - makeEntry(200); + auto makeEntry = [&](const int _percent) + { + const auto mhz = hz * _percent / 100 / 1000000; + std::stringstream ss; + ss << _percent << "% (" << mhz << " MHz)"; + if(_percent == 100) + ss << " (Default)"; + clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); + }; - _menu.addSubMenu("DSP Clock", clockMenu); + makeEntry(50); + makeEntry(75); + makeEntry(100); + makeEntry(125); + makeEntry(150); + makeEntry(200); - return true; -} + _menu.addSubMenu("DSP Clock", clockMenu); + return true; + } -jucePluginEditorLib::Editor* PluginEditorState::createEditor(const Skin& _skin) -{ - return new mqJucePlugin::Editor(m_processor, m_parameterBinding, _skin.folder, _skin.jsonFilename); -} + jucePluginEditorLib::Editor* PluginEditorState::createEditor(const Skin& _skin) + { + return new mqJucePlugin::Editor(m_processor, m_parameterBinding, _skin.folder, _skin.jsonFilename); + } +} +\ No newline at end of file diff --git a/source/mqJucePlugin/PluginEditorState.h b/source/mqJucePlugin/PluginEditorState.h @@ -1,7 +1,5 @@ #pragma once -#include <functional> - #include "jucePluginEditorLib/pluginEditorState.h" namespace juce @@ -9,14 +7,17 @@ namespace juce class Component; } -class AudioPluginAudioProcessor; - -class PluginEditorState : public jucePluginEditorLib::PluginEditorState +namespace mqJucePlugin { -public: - explicit PluginEditorState(AudioPluginAudioProcessor& _processor); - void initContextMenu(juce::PopupMenu& _menu) override; - bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; -private: - jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; -}; + class AudioPluginAudioProcessor; + + class PluginEditorState : public jucePluginEditorLib::PluginEditorState + { + public: + explicit PluginEditorState(AudioPluginAudioProcessor& _processor); + void initContextMenu(juce::PopupMenu& _menu) override; + bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; + private: + jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; + }; +} +\ No newline at end of file diff --git a/source/mqJucePlugin/PluginProcessor.cpp b/source/mqJucePlugin/PluginProcessor.cpp @@ -1,16 +1,11 @@ #include "PluginProcessor.h" #include "PluginEditorState.h" -#include <juce_audio_processors/juce_audio_processors.h> -#include <juce_audio_devices/juce_audio_devices.h> - #include "mqController.h" #include "jucePluginLib/processor.h" #include "mqLib/device.h" -class Controller; - namespace { juce::PropertiesFile::Options getOptions() @@ -24,44 +19,46 @@ namespace } } -//============================================================================== -AudioPluginAudioProcessor::AudioPluginAudioProcessor() : - Processor(BusesProperties() - .withInput("Input", juce::AudioChannelSet::stereo(), true) - .withOutput("Output", juce::AudioChannelSet::stereo(), true) +namespace mqJucePlugin +{ + class Controller; + + AudioPluginAudioProcessor::AudioPluginAudioProcessor() : + Processor(BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true) #if JucePlugin_IsSynth - .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) - .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) + .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) + .withOutput("Out 3", juce::AudioChannelSet::stereo(), true) #endif - , getOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect}) -{ - getController(); - const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); - Processor::setLatencyBlocks(latencyBlocks); -} + , getOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect}) + { + getController(); + const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); + Processor::setLatencyBlocks(latencyBlocks); + } -AudioPluginAudioProcessor::~AudioPluginAudioProcessor() -{ - destroyEditorState(); -} + AudioPluginAudioProcessor::~AudioPluginAudioProcessor() + { + destroyEditorState(); + } -jucePluginEditorLib::PluginEditorState* AudioPluginAudioProcessor::createEditorState() -{ - return new PluginEditorState(*this); -} -synthLib::Device* AudioPluginAudioProcessor::createDevice() -{ - return new mqLib::Device(); -} + jucePluginEditorLib::PluginEditorState* AudioPluginAudioProcessor::createEditorState() + { + return new PluginEditorState(*this); + } + synthLib::Device* AudioPluginAudioProcessor::createDevice() + { + return new mqLib::Device(); + } -pluginLib::Controller* AudioPluginAudioProcessor::createController() -{ - return new Controller(*this); + pluginLib::Controller* AudioPluginAudioProcessor::createController() + { + return new Controller(*this); + } } -//============================================================================== -// This creates new instances of the plugin.. juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { - return new AudioPluginAudioProcessor(); + return new mqJucePlugin::AudioPluginAudioProcessor(); } diff --git a/source/mqJucePlugin/PluginProcessor.h b/source/mqJucePlugin/PluginProcessor.h @@ -2,22 +2,20 @@ #include "jucePluginEditorLib/pluginProcessor.h" -//============================================================================== -class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor +namespace mqJucePlugin { -public: - AudioPluginAudioProcessor(); - ~AudioPluginAudioProcessor() override; + class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor + { + public: + AudioPluginAudioProcessor(); + ~AudioPluginAudioProcessor() override; - jucePluginEditorLib::PluginEditorState* createEditorState() override; + jucePluginEditorLib::PluginEditorState* createEditorState() override; - // _____________ - // - synthLib::Device* createDevice() override; + synthLib::Device* createDevice() override; - pluginLib::Controller* createController() override; -private: - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) -}; + pluginLib::Controller* createController() override; + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) + }; +} diff --git a/source/mqJucePlugin/mqController.cpp b/source/mqJucePlugin/mqController.cpp @@ -12,569 +12,558 @@ #include "dsp56kEmu/logging.h" -constexpr const char* g_midiPacketNames[] = +namespace mqJucePlugin { - "requestsingle", - "requestmulti", - "requestdrum", - "requestsinglebank", - "requestmultibank", - "requestdrumbank", - "requestglobal", - "requestallsingles", - "singleparameterchange", - "multiparameterchange", - "globalparameterchange", - "singledump", - "singledump_Q", - "multidump", - "globaldump", - "emuRequestLcd", - "emuRequestLeds", - "emuSendButton", - "emuSendRotary" -}; - -static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count)); - -static const char* midiPacketName(Controller::MidiPacketType _type) -{ - return g_midiPacketNames[static_cast<uint32_t>(_type)]; -} + constexpr const char* g_midiPacketNames[] = + { + "requestsingle", + "requestmulti", + "requestdrum", + "requestsinglebank", + "requestmultibank", + "requestdrumbank", + "requestglobal", + "requestallsingles", + "singleparameterchange", + "multiparameterchange", + "globalparameterchange", + "singledump", + "singledump_Q", + "multidump", + "globaldump", + "emuRequestLcd", + "emuRequestLeds", + "emuSendButton", + "emuSendRotary" + }; + + static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count)); + + static const char* midiPacketName(Controller::MidiPacketType _type) + { + return g_midiPacketNames[static_cast<uint32_t>(_type)]; + } -Controller::Controller(AudioPluginAudioProcessor& p, unsigned char _deviceId) : pluginLib::Controller(p, loadParameterDescriptions()), m_deviceId(_deviceId) -{ - registerParams(p); + Controller::Controller(AudioPluginAudioProcessor& p, unsigned char _deviceId) : pluginLib::Controller(p, loadParameterDescriptions()), m_deviceId(_deviceId) + { + registerParams(p); -// sendSysEx(RequestAllSingles); - sendSysEx(RequestGlobal); -// sendGlobalParameterChange(mqLib::GlobalParameter::SingleMultiMode, 1); + // sendSysEx(RequestAllSingles); + sendSysEx(RequestGlobal); + // sendGlobalParameterChange(mqLib::GlobalParameter::SingleMultiMode, 1); - onPlayModeChanged.addListener(0, [this](bool multiMode) + onPlayModeChanged.addListener(0, [this](bool multiMode) + { + requestAllPatches(); + }); + } + + Controller::~Controller() = default; + + void Controller::setFrontPanel(mqJucePlugin::FrontPanel* _frontPanel) { - requestAllPatches(); - }); -} + m_frontPanel = _frontPanel; + } + + void Controller::sendSingle(const std::vector<uint8_t>& _sysex) + { + sendSingle(_sysex, getCurrentPart()); + } -Controller::~Controller() = default; + void Controller::sendSingle(const std::vector<uint8_t>& _sysex, const uint8_t _part) + { + auto data = _sysex; -void Controller::setFrontPanel(mqJucePlugin::FrontPanel* _frontPanel) -{ - m_frontPanel = _frontPanel; -} + data[wLib::IdxBuffer] = static_cast<uint8_t>(isMultiMode() ? mqLib::MidiBufferNum::SingleEditBufferMultiMode : mqLib::MidiBufferNum::SingleEditBufferSingleMode); + data[wLib::IdxLocation] = isMultiMode() ? _part : 0; + data[wLib::IdxDeviceId] = m_deviceId; -void Controller::sendSingle(const std::vector<uint8_t>& _sysex) -{ - sendSingle(_sysex, getCurrentPart()); -} + const auto* p = getMidiPacket(g_midiPacketNames[SingleDump]); -void Controller::sendSingle(const std::vector<uint8_t>& _sysex, const uint8_t _part) -{ - auto data = _sysex; + if (!p->updateChecksums(data)) + { + p = getMidiPacket(g_midiPacketNames[SingleDumpQ]); - data[wLib::IdxBuffer] = static_cast<uint8_t>(isMultiMode() ? mqLib::MidiBufferNum::SingleEditBufferMultiMode : mqLib::MidiBufferNum::SingleEditBufferSingleMode); - data[wLib::IdxLocation] = isMultiMode() ? _part : 0; - data[wLib::IdxDeviceId] = m_deviceId; + if(!p->updateChecksums(data)) + return; + } - const auto* p = getMidiPacket(g_midiPacketNames[SingleDump]); + pluginLib::Controller::sendSysEx(data); - if (!p->updateChecksums(data)) - { - p = getMidiPacket(g_midiPacketNames[SingleDumpQ]); + sendLockedParameters(_part); - if(!p->updateChecksums(data)) - return; + requestSingle(isMultiMode() ? mqLib::MidiBufferNum::SingleEditBufferMultiMode : mqLib::MidiBufferNum::SingleEditBufferSingleMode, + isMultiMode() ? mqLib::MidiSoundLocation::EditBufferFirstMultiSingle : mqLib::MidiSoundLocation::EditBufferCurrentSingle); } - pluginLib::Controller::sendSysEx(data); + std::string Controller::loadParameterDescriptions() + { + const auto name = "parameterDescriptions_mq.json"; + const auto path = synthLib::getModulePath() + name; - sendLockedParameters(_part); + const std::ifstream f(path.c_str(), std::ios::in); + if(f.is_open()) + { + std::stringstream buf; + buf << f.rdbuf(); + return buf.str(); + } - requestSingle(isMultiMode() ? mqLib::MidiBufferNum::SingleEditBufferMultiMode : mqLib::MidiBufferNum::SingleEditBufferSingleMode, - isMultiMode() ? mqLib::MidiSoundLocation::EditBufferFirstMultiSingle : mqLib::MidiSoundLocation::EditBufferCurrentSingle); -} + uint32_t size; + const auto res = mqJucePlugin::Editor::findEmbeddedResource(name, size); + if(res) + return {res, size}; + return {}; + } -std::string Controller::loadParameterDescriptions() -{ - const auto name = "parameterDescriptions_mq.json"; - const auto path = synthLib::getModulePath() + name; - - const std::ifstream f(path.c_str(), std::ios::in); - if(f.is_open()) - { - std::stringstream buf; - buf << f.rdbuf(); - return buf.str(); - } - - uint32_t size; - const auto res = mqJucePlugin::Editor::findEmbeddedResource(name, size); - if(res) - return {res, size}; - return {}; -} + void Controller::onStateLoaded() + { + } -void Controller::onStateLoaded() -{ -} + std::string Controller::getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const + { + std::string name; + for(uint32_t i=0; i<16; ++i) + { + char paramName[16]; + sprintf(paramName, "Name%02u", i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; -std::string Controller::getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const -{ - std::string name; - for(uint32_t i=0; i<16; ++i) - { - char paramName[16]; - sprintf(paramName, "Name%02u", i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); - if(it == _values.end()) - break; - - name += static_cast<char>(it->second); - } - return name; -} + const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); + if(it == _values.end()) + break; -std::string Controller::getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const -{ - return getString(_values, "Name", 16); -} + name += static_cast<char>(it->second); + } + return name; + } -std::string Controller::getCategory(const pluginLib::MidiPacket::AnyPartParamValues& _values) const -{ - return getString(_values, "Category", 4); -} + std::string Controller::getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const + { + return getString(_values, "Name", 16); + } -std::string Controller::getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, const size_t _len) const -{ - std::string name; - for(uint32_t i=0; i<_len; ++i) - { - char paramName[64]; - snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); - - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto it = _values[idx]; - if(!it) - break; - - name += static_cast<char>(*it); - } - return name; -} + std::string Controller::getCategory(const pluginLib::MidiPacket::AnyPartParamValues& _values) const + { + return getString(_values, "Category", 4); + } -bool Controller::setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const -{ - return setString(_values, "Name", 16, _value); -} + std::string Controller::getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, const size_t _len) const + { + std::string name; + for(uint32_t i=0; i<_len; ++i) + { + char paramName[64]; + snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); -bool Controller::setCategory(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const -{ - return setString(_values, "Category", 4, _value); -} + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; -void Controller::applyPatchParameters(const pluginLib::MidiPacket::ParamValues& _params, const uint8_t _part) const -{ - for (const auto& it : _params) - { - auto* p = getParameter(it.first.second, _part); - p->setValueFromSynth(it.second, pluginLib::Parameter::Origin::PresetChange); + const auto it = _values[idx]; + if(!it) + break; - for (const auto& derivedParam : p->getDerivedParameters()) - derivedParam->setValueFromSynth(it.second, pluginLib::Parameter::Origin::PresetChange); + name += static_cast<char>(*it); + } + return name; } - getProcessor().updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); -} + bool Controller::setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const + { + return setString(_values, "Name", 16, _value); + } -void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) -{ - Patch patch; - patch.data = _msg; - patch.name = getSingleName(_params); + bool Controller::setCategory(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const + { + return setString(_values, "Category", 4, _value); + } - const auto bank = _data.at(pluginLib::MidiDataType::Bank); - const auto prog = _data.at(pluginLib::MidiDataType::Program); + void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) + { + Patch patch; + patch.data = _msg; + patch.name = getSingleName(_params); - if(bank == static_cast<uint8_t>(mqLib::MidiBufferNum::SingleEditBufferSingleMode) && prog == static_cast<uint8_t>(mqLib::MidiSoundLocation::EditBufferCurrentSingle)) - { - m_singleEditBuffer = patch; + const auto bank = _data.at(pluginLib::MidiDataType::Bank); + const auto prog = _data.at(pluginLib::MidiDataType::Program); - if(!isMultiMode()) - applyPatchParameters(_params, 0); - } - else if(bank == static_cast<uint8_t>(mqLib::MidiBufferNum::SingleEditBufferMultiMode)) - { - m_singleEditBuffers[prog] = patch; - - if (isMultiMode()) - applyPatchParameters(_params, prog); - - // if we switched to multi, all singles have to be requested. However, we cannot send all requests at once (device will miss some) - // so we chain them one after the other - if(prog + 1 < m_singleEditBuffers.size()) - requestSingle(mqLib::MidiBufferNum::SingleEditBufferMultiMode, mqLib::MidiSoundLocation::EditBufferFirstMultiSingle, prog + 1); - } -} + if(bank == static_cast<uint8_t>(mqLib::MidiBufferNum::SingleEditBufferSingleMode) && prog == static_cast<uint8_t>(mqLib::MidiSoundLocation::EditBufferCurrentSingle)) + { + m_singleEditBuffer = patch; -void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) -{ - Patch patch; - patch.data = _msg; - patch.name = getSingleName(_params); + if(!isMultiMode()) + applyPatchParameters(_params, 0); + } + else if(bank == static_cast<uint8_t>(mqLib::MidiBufferNum::SingleEditBufferMultiMode)) + { + m_singleEditBuffers[prog] = patch; + + if (isMultiMode()) + applyPatchParameters(_params, prog); - const auto bank = _data.at(pluginLib::MidiDataType::Bank); -// const auto prog = _data.at(pluginLib::MidiDataType::Program); + // if we switched to multi, all singles have to be requested. However, we cannot send all requests at once (device will miss some) + // so we chain them one after the other + if(prog + 1 < m_singleEditBuffers.size()) + requestSingle(mqLib::MidiBufferNum::SingleEditBufferMultiMode, mqLib::MidiSoundLocation::EditBufferFirstMultiSingle, prog + 1); + } + } - if(bank == static_cast<uint8_t>(mqLib::MidiBufferNum::MultiEditBuffer)) + void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) { - applyPatchParameters(_params, 0); + Patch patch; + patch.data = _msg; + patch.name = getSingleName(_params); + + const auto bank = _data.at(pluginLib::MidiDataType::Bank); + // const auto prog = _data.at(pluginLib::MidiDataType::Program); + + if(bank == static_cast<uint8_t>(mqLib::MidiBufferNum::MultiEditBuffer)) + { + applyPatchParameters(_params, 0); + } } -} -bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource _source) -{ - if(_msg.size() >= 5) - { - const auto cmd = static_cast<mqLib::SysexCommand>(_msg[4]); - switch (cmd) - { - case mqLib::SysexCommand::EmuRotaries: - case mqLib::SysexCommand::EmuButtons: - case mqLib::SysexCommand::EmuLCD: - case mqLib::SysexCommand::EmuLCDCGRata: - case mqLib::SysexCommand::EmuLEDs: - if(m_frontPanel) - m_frontPanel->processSysex(_msg); - return true; - default: - break; - } - } - - LOG("Got sysex of size " << _msg.size()); - - std::string name; - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::ParamValues parameterValues; - - if(!pluginLib::Controller::parseMidiPacket(name, data, parameterValues, _msg)) - return false; - - if(name == midiPacketName(SingleDump)) - { - parseSingle(_msg, data, parameterValues); - } - else if (name == midiPacketName(MultiDump)) - { - parseMulti(_msg, data, parameterValues); - } - else if(name == midiPacketName(GlobalDump)) - { - const auto lastPlayMode = isMultiMode(); - memcpy(m_globalData.data(), &_msg[5], sizeof(m_globalData)); - const auto newPlayMode = isMultiMode(); - - if(lastPlayMode != newPlayMode) - onPlayModeChanged(newPlayMode); - else - requestAllPatches(); - } - else if(name == midiPacketName(SingleParameterChange)) - { - const auto page = data[pluginLib::MidiDataType::Page]; - const auto index = data[pluginLib::MidiDataType::ParameterIndex]; - const auto part = data[pluginLib::MidiDataType::Part]; - const auto value = data[pluginLib::MidiDataType::ParameterValue]; - - auto& params = findSynthParam(part, page, index); - - for (auto& param : params) - param->setValueFromSynth(value, pluginLib::Parameter::Origin::Midi); - - LOG("Single parameter " << static_cast<int>(index) << ", page " << static_cast<int>(page) << " for part " << static_cast<int>(part) << " changed to value " << static_cast<int>(value)); - } - else if(name == midiPacketName(GlobalParameterChange)) - { - const auto index = (static_cast<uint32_t>(data[pluginLib::MidiDataType::Page]) << 7) + static_cast<uint32_t>(data[pluginLib::MidiDataType::ParameterIndex]); - const auto value = data[pluginLib::MidiDataType::ParameterValue]; - - if(m_globalData[index] != value) + bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource _source) + { + if(_msg.size() >= 5) { - LOG("Global parameter " << index << " changed to value " << static_cast<int>(value)); - m_globalData[index] = value; + const auto cmd = static_cast<mqLib::SysexCommand>(_msg[4]); + switch (cmd) + { + case mqLib::SysexCommand::EmuRotaries: + case mqLib::SysexCommand::EmuButtons: + case mqLib::SysexCommand::EmuLCD: + case mqLib::SysexCommand::EmuLCDCGRata: + case mqLib::SysexCommand::EmuLEDs: + if(m_frontPanel) + m_frontPanel->processSysex(_msg); + return true; + default: + break; + } + } - if (index == static_cast<uint32_t>(mqLib::GlobalParameter::SingleMultiMode)) + LOG("Got sysex of size " << _msg.size()); + + std::string name; + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; + + if(!pluginLib::Controller::parseMidiPacket(name, data, parameterValues, _msg)) + return false; + + if(name == midiPacketName(SingleDump)) + { + parseSingle(_msg, data, parameterValues); + } + else if (name == midiPacketName(MultiDump)) + { + parseMulti(_msg, data, parameterValues); + } + else if(name == midiPacketName(GlobalDump)) + { + const auto lastPlayMode = isMultiMode(); + memcpy(m_globalData.data(), &_msg[5], sizeof(m_globalData)); + const auto newPlayMode = isMultiMode(); + + if(lastPlayMode != newPlayMode) + onPlayModeChanged(newPlayMode); + else requestAllPatches(); } - } - else - { - LOG("Received unknown sysex of size " << _msg.size()); - return false; - } - return true; -} + else if(name == midiPacketName(SingleParameterChange)) + { + const auto page = data[pluginLib::MidiDataType::Page]; + const auto index = data[pluginLib::MidiDataType::ParameterIndex]; + const auto part = data[pluginLib::MidiDataType::Part]; + const auto value = data[pluginLib::MidiDataType::ParameterValue]; -bool Controller::parseControllerMessage(const synthLib::SMidiEvent&) -{ - // TODO - return false; -} + auto& params = findSynthParam(part, page, index); -bool Controller::parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const -{ - const auto* p = getMidiPacket(g_midiPacketNames[_type]); - assert(p && "midi packet not found"); - return pluginLib::Controller::parseMidiPacket(*p, _data, _params, _sysex); -} + for (auto& param : params) + param->setValueFromSynth(value, pluginLib::Parameter::Origin::Midi); -bool Controller::sendSysEx(MidiPacketType _type) const -{ - std::map<pluginLib::MidiDataType, uint8_t> params; - return sendSysEx(_type, params); -} + LOG("Single parameter " << static_cast<int>(index) << ", page " << static_cast<int>(page) << " for part " << static_cast<int>(part) << " changed to value " << static_cast<int>(value)); + } + else if(name == midiPacketName(GlobalParameterChange)) + { + const auto index = (static_cast<uint32_t>(data[pluginLib::MidiDataType::Page]) << 7) + static_cast<uint32_t>(data[pluginLib::MidiDataType::ParameterIndex]); + const auto value = data[pluginLib::MidiDataType::ParameterValue]; -bool Controller::sendSysEx(const MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const -{ - _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); -} + if(m_globalData[index] != value) + { + LOG("Global parameter " << index << " changed to value " << static_cast<int>(value)); + m_globalData[index] = value; -bool Controller::isMultiMode() const -{ - return m_globalData[static_cast<uint32_t>(mqLib::GlobalParameter::SingleMultiMode)] != 0; -} + if (index == static_cast<uint32_t>(mqLib::GlobalParameter::SingleMultiMode)) + requestAllPatches(); + } + } + else + { + LOG("Received unknown sysex of size " << _msg.size()); + return false; + } + return true; + } -void Controller::setPlayMode(const bool _multiMode) -{ - const uint8_t playMode = _multiMode ? 1 : 0; + bool Controller::parseControllerMessage(const synthLib::SMidiEvent&) + { + // TODO + return false; + } - if(m_globalData[static_cast<uint32_t>(mqLib::GlobalParameter::SingleMultiMode)] == playMode) - return; + bool Controller::parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const + { + const auto* p = getMidiPacket(g_midiPacketNames[_type]); + assert(p && "midi packet not found"); + return pluginLib::Controller::parseMidiPacket(*p, _data, _params, _sysex); + } - sendGlobalParameterChange(mqLib::GlobalParameter::SingleMultiMode, playMode); + bool Controller::sendSysEx(MidiPacketType _type) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + return sendSysEx(_type, params); + } - onPlayModeChanged(_multiMode); -} + bool Controller::sendSysEx(const MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const + { + _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); + } -void Controller::selectNextPreset() -{ - selectPreset(+1); -} + bool Controller::isMultiMode() const + { + return m_globalData[static_cast<uint32_t>(mqLib::GlobalParameter::SingleMultiMode)] != 0; + } -void Controller::selectPrevPreset() -{ - selectPreset(-1); -} + void Controller::setPlayMode(const bool _multiMode) + { + const uint8_t playMode = _multiMode ? 1 : 0; -std::vector<uint8_t> Controller::createSingleDump(const mqLib::MidiBufferNum _buffer, const mqLib::MidiSoundLocation _location, const uint8_t _locationOffset, const uint8_t _part) const -{ - pluginLib::MidiPacket::Data data; + if(m_globalData[static_cast<uint32_t>(mqLib::GlobalParameter::SingleMultiMode)] == playMode) + return; - data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, static_cast<uint8_t>(_location) + _locationOffset)); + sendGlobalParameterChange(mqLib::GlobalParameter::SingleMultiMode, playMode); - std::vector<uint8_t> dst; + onPlayModeChanged(_multiMode); + } - if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _part)) - return {}; + void Controller::selectNextPreset() + { + selectPreset(+1); + } - return dst; -} + void Controller::selectPrevPreset() + { + selectPreset(-1); + } -std::vector<uint8_t> Controller::createSingleDump(mqLib::MidiBufferNum _buffer, mqLib::MidiSoundLocation _location, const uint8_t _locationOffset, const pluginLib::MidiPacket::AnyPartParamValues& _values) const -{ - pluginLib::MidiPacket::Data data; + std::vector<uint8_t> Controller::createSingleDump(const mqLib::MidiBufferNum _buffer, const mqLib::MidiSoundLocation _location, const uint8_t _locationOffset, const uint8_t _part) const + { + pluginLib::MidiPacket::Data data; - data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, static_cast<uint8_t>(_location) + _locationOffset)); + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, static_cast<uint8_t>(_location) + _locationOffset)); - std::vector<uint8_t> dst; + std::vector<uint8_t> dst; - if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _values)) - return {}; + if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _part)) + return {}; - return dst; -} + return dst; + } -bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const -{ - if(parseMidiPacket(SingleDump, _data, _paramValues, _sysex)) - return true; - return parseMidiPacket(SingleDumpQ, _data, _paramValues, _sysex); -} + std::vector<uint8_t> Controller::createSingleDump(mqLib::MidiBufferNum _buffer, mqLib::MidiSoundLocation _location, const uint8_t _locationOffset, const pluginLib::MidiPacket::AnyPartParamValues& _values) const + { + pluginLib::MidiPacket::Data data; -bool Controller::setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const -{ - for(uint32_t i=0; i<_len && i <_value.size(); ++i) - { - char paramName[64]; - snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); - - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - _values[idx] = static_cast<uint8_t>(_value[i]); - } - return true; -} + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, static_cast<uint8_t>(_location) + _locationOffset)); -void Controller::selectPreset(int _offset) -{ - auto& current = isMultiMode() ? m_currentSingles[getCurrentPart()] : m_currentSingle; - - int index = static_cast<int>(current) + _offset; - - if (index < 0) - index += 300; - - if (index >= 300) - index -= 300; - - current = static_cast<uint32_t>(index); - - const int single = index % 100; - const int bank = index / 100; - - if (isMultiMode()) - { - // TODO: modify multi - } - else - { - sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTMSB, m_deviceId); - sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTLSB, static_cast<uint8_t>(mqLib::MidiBufferNum::SingleBankA) + bank); - sendMidiEvent(synthLib::M_PROGRAMCHANGE, static_cast<uint8_t>(single), 0); -/* - sendGlobalParameterChange(mqLib::GlobalParameter::InstrumentABankNumber, static_cast<uint8_t>(bank)); - sendGlobalParameterChange(mqLib::GlobalParameter::InstrumentASingleNumber, static_cast<uint8_t>(single)); -*/ } -} + std::vector<uint8_t> dst; -void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, const uint8_t _value) -{ - const auto &desc = _parameter.getDescription(); + if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _values)) + return {}; - std::map<pluginLib::MidiDataType, uint8_t> data; + return dst; + } - if (desc.page >= 100) + bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const { - uint8_t v; + if(parseMidiPacket(SingleDump, _data, _paramValues, _sysex)) + return true; + return parseMidiPacket(SingleDumpQ, _data, _paramValues, _sysex); + } - if (!combineParameterChange(v, g_midiPacketNames[MultiDump], _parameter, _value)) - return; + bool Controller::setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const + { + for(uint32_t i=0; i<_len && i <_value.size(); ++i) + { + char paramName[64]; + snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); - const auto& dump = mqLib::State::Dumps[static_cast<int>(mqLib::State::DumpType::Multi)]; + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; - uint32_t idx = desc.index; + _values[idx] = static_cast<uint8_t>(_value[i]); + } + return true; + } - if(desc.page > 100) - idx += (static_cast<uint32_t>(mqLib::MultiParameter::Inst1) - static_cast<uint32_t>(mqLib::MultiParameter::Inst0)) * (desc.page - 101); + void Controller::selectPreset(int _offset) + { + auto& current = isMultiMode() ? m_currentSingles[getCurrentPart()] : m_currentSingle; - data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); - data.insert(std::make_pair(pluginLib::MidiDataType::Page, idx >> 7)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, idx & 0x7f)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); + int index = static_cast<int>(current) + _offset; + + if (index < 0) + index += 300; + + if (index >= 300) + index -= 300; + + current = static_cast<uint32_t>(index); + + const int single = index % 100; + const int bank = index / 100; - sendSysEx(MultiParameterChange, data); - return; + if (isMultiMode()) + { + // TODO: modify multi + } + else + { + sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTMSB, m_deviceId); + sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTLSB, static_cast<uint8_t>(mqLib::MidiBufferNum::SingleBankA) + bank); + sendMidiEvent(synthLib::M_PROGRAMCHANGE, static_cast<uint8_t>(single), 0); + /* + sendGlobalParameterChange(mqLib::GlobalParameter::InstrumentABankNumber, static_cast<uint8_t>(bank)); + sendGlobalParameterChange(mqLib::GlobalParameter::InstrumentASingleNumber, static_cast<uint8_t>(single)); + */ } } - uint8_t v; - if (!combineParameterChange(v, g_midiPacketNames[SingleDump], _parameter, _value)) - return; + void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, const pluginLib::ParamValue _value) + { + const auto &desc = _parameter.getDescription(); - data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); - data.insert(std::make_pair(pluginLib::MidiDataType::Page, desc.page)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, desc.index)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); + std::map<pluginLib::MidiDataType, uint8_t> data; - sendSysEx(SingleParameterChange, data); -} + if (desc.page >= 100) + { + uint8_t v; -bool Controller::sendGlobalParameterChange(mqLib::GlobalParameter _param, uint8_t _value) -{ - const auto index = static_cast<uint32_t>(_param); + if (!combineParameterChange(v, g_midiPacketNames[MultiDump], _parameter, _value)) + return; - if(m_globalData[index] == _value) - return true; + const auto& dump = mqLib::State::Dumps[static_cast<int>(mqLib::State::DumpType::Multi)]; - std::map<pluginLib::MidiDataType, uint8_t> data; + uint32_t idx = desc.index; - data.insert(std::make_pair(pluginLib::MidiDataType::Page, index >> 7 )); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, index & 0x7f )); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); + if(desc.page > 100) + idx += (static_cast<uint32_t>(mqLib::MultiParameter::Inst1) - static_cast<uint32_t>(mqLib::MultiParameter::Inst0)) * (desc.page - 101); - m_globalData[index] = _value; + data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); + data.insert(std::make_pair(pluginLib::MidiDataType::Page, idx >> 7)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, idx & 0x7f)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); - return sendSysEx(GlobalParameterChange, data); -} + sendSysEx(MultiParameterChange, data); + return; + } -void Controller::requestSingle(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset/* = 0*/) const -{ - std::map<pluginLib::MidiDataType, uint8_t> params; - params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); - params[pluginLib::MidiDataType::Program] = static_cast<uint8_t>(_location) + _locationOffset; - sendSysEx(RequestSingle, params); -} + uint8_t v; + if (!combineParameterChange(v, g_midiPacketNames[SingleDump], _parameter, _value)) + return; -void Controller::requestMulti(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset) const -{ - std::map<pluginLib::MidiDataType, uint8_t> params; - params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); - params[pluginLib::MidiDataType::Program] = static_cast<uint8_t>(_location) + _locationOffset; - sendSysEx(RequestMulti, params); -} + data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); + data.insert(std::make_pair(pluginLib::MidiDataType::Page, desc.page)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, desc.index)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); -uint8_t Controller::getGlobalParam(mqLib::GlobalParameter _type) const -{ - return m_globalData[static_cast<uint32_t>(_type)]; -} + sendSysEx(SingleParameterChange, data); + } -bool Controller::isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const -{ - if(_derived.getDescription().page >= 100) - return false; + bool Controller::sendGlobalParameterChange(mqLib::GlobalParameter _param, uint8_t _value) + { + const auto index = static_cast<uint32_t>(_param); + + if(m_globalData[index] == _value) + return true; + + std::map<pluginLib::MidiDataType, uint8_t> data; + + data.insert(std::make_pair(pluginLib::MidiDataType::Page, index >> 7 )); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, index & 0x7f )); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); - const auto& packetName = g_midiPacketNames[SingleDump]; - const auto* packet = getMidiPacket(packetName); + m_globalData[index] = _value; - if (!packet) + return sendSysEx(GlobalParameterChange, data); + } + + void Controller::requestSingle(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset/* = 0*/) const { - LOG("Failed to find midi packet " << packetName); - return true; + std::map<pluginLib::MidiDataType, uint8_t> params; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); + params[pluginLib::MidiDataType::Program] = static_cast<uint8_t>(_location) + _locationOffset; + sendSysEx(RequestSingle, params); } - - const auto* defA = packet->getDefinitionByParameterName(_derived.getDescription().name); - const auto* defB = packet->getDefinitionByParameterName(_base.getDescription().name); - if (!defA || !defB) - return true; + void Controller::requestMulti(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); + params[pluginLib::MidiDataType::Program] = static_cast<uint8_t>(_location) + _locationOffset; + sendSysEx(RequestMulti, params); + } - return defA->doMasksOverlap(*defB); -} + uint8_t Controller::getGlobalParam(mqLib::GlobalParameter _type) const + { + return m_globalData[static_cast<uint32_t>(_type)]; + } -void Controller::requestAllPatches() const -{ - if (isMultiMode()) + bool Controller::isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const { - requestMulti(mqLib::MidiBufferNum::MultiEditBuffer, mqLib::MidiSoundLocation::EditBufferFirstMultiSingle); + if(_derived.getDescription().page >= 100) + return false; + + const auto& packetName = g_midiPacketNames[SingleDump]; + const auto* packet = getMidiPacket(packetName); - // the other singles 1-15 are requested one after the other after a single has been received - requestSingle(mqLib::MidiBufferNum::SingleEditBufferMultiMode, mqLib::MidiSoundLocation::EditBufferFirstMultiSingle, 0); + if (!packet) + { + LOG("Failed to find midi packet " << packetName); + return true; + } + + const auto* defA = packet->getDefinitionByParameterName(_derived.getDescription().name); + const auto* defB = packet->getDefinitionByParameterName(_base.getDescription().name); + + if (!defA || !defB) + return true; + + return defA->doMasksOverlap(*defB); } - else + + void Controller::requestAllPatches() const { - requestSingle(mqLib::MidiBufferNum::SingleEditBufferSingleMode, mqLib::MidiSoundLocation::EditBufferCurrentSingle); + if (isMultiMode()) + { + requestMulti(mqLib::MidiBufferNum::MultiEditBuffer, mqLib::MidiSoundLocation::EditBufferFirstMultiSingle); + + // the other singles 1-15 are requested one after the other after a single has been received + requestSingle(mqLib::MidiBufferNum::SingleEditBufferMultiMode, mqLib::MidiSoundLocation::EditBufferFirstMultiSingle, 0); + } + else + { + requestSingle(mqLib::MidiBufferNum::SingleEditBufferSingleMode, mqLib::MidiSoundLocation::EditBufferCurrentSingle); + } } } diff --git a/source/mqJucePlugin/mqController.h b/source/mqJucePlugin/mqController.h @@ -8,108 +8,107 @@ namespace mqJucePlugin { class FrontPanel; -} -class AudioPluginAudioProcessor; - -class Controller : public pluginLib::Controller -{ -public: - enum MidiPacketType - { - RequestSingle, - RequestMulti, - RequestDrum, - RequestSingleBank, - RequestMultiBank, - RequestDrumBank, - RequestGlobal, - RequestAllSingles, - SingleParameterChange, - MultiParameterChange, - GlobalParameterChange, - SingleDump, - SingleDumpQ, - MultiDump, - GlobalDump, - EmuRequestLcd, - EmuRequestLeds, - EmuSendButton, - EmuSendRotary, - - Count - }; - - struct Patch - { - std::string name; - std::vector<uint8_t> data; - }; - - pluginLib::Event<bool> onPlayModeChanged; - - Controller(AudioPluginAudioProcessor &, unsigned char _deviceId = 0); - ~Controller() override; - - void setFrontPanel(mqJucePlugin::FrontPanel* _frontPanel); - void sendSingle(const std::vector<uint8_t>& _sysex); - void sendSingle(const std::vector<uint8_t>& _sysex, uint8_t _part); - - bool sendSysEx(MidiPacketType _type) const; - bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; - - bool isMultiMode() const; - void setPlayMode(bool _multiMode); - - void selectNextPreset(); - void selectPrevPreset(); - - std::vector<uint8_t> createSingleDump(mqLib::MidiBufferNum _buffer, mqLib::MidiSoundLocation _location, uint8_t _locationOffset, uint8_t _part) const; - std::vector<uint8_t> createSingleDump(mqLib::MidiBufferNum _buffer, mqLib::MidiSoundLocation _location, uint8_t _locationOffset, const pluginLib::MidiPacket - ::AnyPartParamValues& _values) const; - bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const; - - std::string getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const; - std::string getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; - std::string getCategory(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; - std::string getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len) const; - - bool setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; - bool setCategory(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; - bool setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const; - -private: - void selectPreset(int _offset); - - static std::string loadParameterDescriptions(); - - void onStateLoaded() override; - - void applyPatchParameters(const pluginLib::MidiPacket::ParamValues& _params, uint8_t _part) const; - void parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); - void parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); - bool parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const; - - bool parseSysexMessage(const pluginLib::SysEx&, synthLib::MidiEventSource _source) override; - bool parseControllerMessage(const synthLib::SMidiEvent&) override; - - void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; - bool sendGlobalParameterChange(mqLib::GlobalParameter _param, uint8_t _value); - void requestSingle(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset = 0) const; - void requestMulti(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset = 0) const; - - uint8_t getGlobalParam(mqLib::GlobalParameter _type) const; - - bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; - - void requestAllPatches() const; - - const uint8_t m_deviceId; - - Patch m_singleEditBuffer; - std::array<Patch,16> m_singleEditBuffers; - std::array<uint8_t, 200> m_globalData{}; - mqJucePlugin::FrontPanel* m_frontPanel = nullptr; - std::array<uint32_t, 16> m_currentSingles{0}; - uint32_t m_currentSingle = 0; -}; + class AudioPluginAudioProcessor; + + class Controller : public pluginLib::Controller + { + public: + enum MidiPacketType + { + RequestSingle, + RequestMulti, + RequestDrum, + RequestSingleBank, + RequestMultiBank, + RequestDrumBank, + RequestGlobal, + RequestAllSingles, + SingleParameterChange, + MultiParameterChange, + GlobalParameterChange, + SingleDump, + SingleDumpQ, + MultiDump, + GlobalDump, + EmuRequestLcd, + EmuRequestLeds, + EmuSendButton, + EmuSendRotary, + + Count + }; + + struct Patch + { + std::string name; + std::vector<uint8_t> data; + }; + + pluginLib::Event<bool> onPlayModeChanged; + + Controller(AudioPluginAudioProcessor &, unsigned char _deviceId = 0); + ~Controller() override; + + void setFrontPanel(mqJucePlugin::FrontPanel* _frontPanel); + void sendSingle(const std::vector<uint8_t>& _sysex); + void sendSingle(const std::vector<uint8_t>& _sysex, uint8_t _part); + + bool sendSysEx(MidiPacketType _type) const; + bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; + + bool isMultiMode() const; + void setPlayMode(bool _multiMode); + + void selectNextPreset(); + void selectPrevPreset(); + + std::vector<uint8_t> createSingleDump(mqLib::MidiBufferNum _buffer, mqLib::MidiSoundLocation _location, uint8_t _locationOffset, uint8_t _part) const; + std::vector<uint8_t> createSingleDump(mqLib::MidiBufferNum _buffer, mqLib::MidiSoundLocation _location, uint8_t _locationOffset, const pluginLib::MidiPacket + ::AnyPartParamValues& _values) const; + bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const; + + std::string getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; + std::string getCategory(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; + std::string getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len) const; + + bool setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; + bool setCategory(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; + bool setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const; + + private: + void selectPreset(int _offset); + + static std::string loadParameterDescriptions(); + + void onStateLoaded() override; + + void parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); + void parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); + bool parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const; + + bool parseSysexMessage(const pluginLib::SysEx&, synthLib::MidiEventSource _source) override; + bool parseControllerMessage(const synthLib::SMidiEvent&) override; + + void sendParameterChange(const pluginLib::Parameter& _parameter, pluginLib::ParamValue _value) override; + bool sendGlobalParameterChange(mqLib::GlobalParameter _param, uint8_t _value); + void requestSingle(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset = 0) const; + void requestMulti(mqLib::MidiBufferNum _buf, mqLib::MidiSoundLocation _location, uint8_t _locationOffset = 0) const; + + uint8_t getGlobalParam(mqLib::GlobalParameter _type) const; + + bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; + + void requestAllPatches() const; + + const uint8_t m_deviceId; + + Patch m_singleEditBuffer; + std::array<Patch,16> m_singleEditBuffers; + std::array<uint8_t, 200> m_globalData{}; + mqJucePlugin::FrontPanel* m_frontPanel = nullptr; + std::array<uint32_t, 16> m_currentSingles{0}; + uint32_t m_currentSingle = 0; + }; +} +\ No newline at end of file diff --git a/source/mqJucePlugin/mqEditor.cpp b/source/mqJucePlugin/mqEditor.cpp @@ -191,7 +191,7 @@ namespace mqJucePlugin { juce::PopupMenu menu; - const auto countAdded = getPatchManager()->createSaveMenuEntries(menu, getPatchManager()->getCurrentPart()); + const auto countAdded = getPatchManager()->createSaveMenuEntries(menu); if(countAdded) menu.addSeparator(); diff --git a/source/mqJucePlugin/mqEditor.h b/source/mqJucePlugin/mqEditor.h @@ -2,9 +2,6 @@ #include "jucePluginEditorLib/pluginEditor.h" -class mqPartSelect; -class Controller; - namespace jucePluginEditorLib { class FocusedParameter; @@ -18,6 +15,8 @@ namespace pluginLib namespace mqJucePlugin { + class mqPartSelect; + class Controller; class FrontPanel; class PatchManager; diff --git a/source/mqJucePlugin/mqFrontPanel.h b/source/mqJucePlugin/mqFrontPanel.h @@ -7,10 +7,9 @@ #include "juce_gui_basics/juce_gui_basics.h" -class Controller; - namespace mqJucePlugin { + class Controller; class Editor; class FrontPanel diff --git a/source/mqJucePlugin/mqLcd.cpp b/source/mqJucePlugin/mqLcd.cpp @@ -1,10 +1,8 @@ #include "mqLcd.h" -#include "jucePluginLib/pluginVersion.h" - -#include "wLib/lcdfonts.h" +#include "hardwareLib/lcdfonts.h" -// EW20290GLW display simulation (20*2) +#include "jucePluginLib/pluginVersion.h" MqLcd::MqLcd(Component& _parent) : Lcd(_parent, 20, 2) { @@ -40,5 +38,5 @@ bool MqLcd::getOverrideText(std::vector<std::vector<uint8_t>>& _lines) const uint8_t* MqLcd::getCharacterData(uint8_t _character) const { - return wLib::getCharacterData(_character); + return hwLib::getCharacterData(_character); } diff --git a/source/mqJucePlugin/mqPartButton.cpp b/source/mqJucePlugin/mqPartButton.cpp @@ -2,18 +2,21 @@ #include "mqEditor.h" #include "mqPartSelect.h" -mqPartButton::mqPartButton(mqJucePlugin::Editor& _editor, const std::string& _name, ButtonStyle _buttonStyle) -: PartButton(_editor, _name, _buttonStyle) -, m_mqEditor(_editor) +namespace mqJucePlugin { -} + mqPartButton::mqPartButton(mqJucePlugin::Editor& _editor, const std::string& _name, ButtonStyle _buttonStyle) + : PartButton(_editor, _name, _buttonStyle) + , m_mqEditor(_editor) + { + } -bool mqPartButton::isInterestedInDragSource(const SourceDetails& dragSourceDetails) -{ - return PartButton<DrawableButton>::isInterestedInDragSource(dragSourceDetails); -} + bool mqPartButton::isInterestedInDragSource(const SourceDetails& dragSourceDetails) + { + return PartButton<DrawableButton>::isInterestedInDragSource(dragSourceDetails); + } -void mqPartButton::onClick() -{ - m_mqEditor.getPartSelect()->selectPart(getPart()); + void mqPartButton::onClick() + { + m_mqEditor.getPartSelect()->selectPart(getPart()); + } } diff --git a/source/mqJucePlugin/mqPartButton.h b/source/mqJucePlugin/mqPartButton.h @@ -2,22 +2,21 @@ #include "jucePluginEditorLib/partbutton.h" -class mqPartSelect; - namespace mqJucePlugin { class Editor; -} + class mqPartSelect; -class mqPartButton : public jucePluginEditorLib::PartButton<juce::DrawableButton> -{ -public: - explicit mqPartButton(mqJucePlugin::Editor& _editor, const std::string& _name, ButtonStyle _buttonStyle); + class mqPartButton : public jucePluginEditorLib::PartButton<juce::DrawableButton> + { + public: + explicit mqPartButton(Editor& _editor, const std::string& _name, ButtonStyle _buttonStyle); - bool isInterestedInDragSource(const SourceDetails& dragSourceDetails) override; + bool isInterestedInDragSource(const SourceDetails& dragSourceDetails) override; - void onClick() override; + void onClick() override; -private: - mqJucePlugin::Editor& m_mqEditor; -}; + private: + Editor& m_mqEditor; + }; +} diff --git a/source/mqJucePlugin/mqPartSelect.cpp b/source/mqJucePlugin/mqPartSelect.cpp @@ -4,71 +4,74 @@ #include "mqEditor.h" #include "mqPartButton.h" -mqPartSelect::mqPartSelect(mqJucePlugin::Editor& _editor, Controller& _controller, pluginLib::ParameterBinding& _parameterBinding) - : m_editor(_editor) - , m_controller(_controller) - , m_parameterBinding(_parameterBinding) +namespace mqJucePlugin { - std::vector<mqPartButton*> buttons; - std::vector<juce::Button*> leds; - - _editor.findComponents(buttons, "partSelectButton", 16); - _editor.findComponents(leds, "partSelectLED", 16); - - for(size_t i=0; i<m_parts.size(); ++i) + mqPartSelect::mqPartSelect(Editor& _editor, Controller& _controller, pluginLib::ParameterBinding& _parameterBinding) + : m_editor(_editor) + , m_controller(_controller) + , m_parameterBinding(_parameterBinding) { - auto& part = m_parts[i]; + std::vector<mqPartButton*> buttons; + std::vector<juce::Button*> leds; - part.button = buttons[i]; - part.led = leds[i]; + _editor.findComponents(buttons, "partSelectButton", 16); + _editor.findComponents(leds, "partSelectLED", 16); - auto index = static_cast<uint8_t>(i); + for(size_t i=0; i<m_parts.size(); ++i) + { + auto& part = m_parts[i]; - part.button->initalize(static_cast<uint8_t>(i)); + part.button = buttons[i]; + part.led = leds[i]; - part.led->onClick = [this, index] { selectPart(index); }; - } + auto index = static_cast<uint8_t>(i); - updateUiState(); -} + part.button->initalize(static_cast<uint8_t>(i)); -void mqPartSelect::onPlayModeChanged() const -{ - if(m_controller.getCurrentPart() > 0) - selectPart(0); - else - updateUiState(); -} + part.led->onClick = [this, index] { selectPart(index); }; + } -void mqPartSelect::updateUiState() const -{ - const auto current = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(0); + updateUiState(); + } - for(size_t i=0; i<m_parts.size(); ++i) + void mqPartSelect::onPlayModeChanged() const { - const auto& part = m_parts[i]; + if(m_controller.getCurrentPart() > 0) + selectPart(0); + else + updateUiState(); + } - part.button->setToggleState(i == current, juce::dontSendNotification); - part.led->setToggleState(i == current, juce::dontSendNotification); + void mqPartSelect::updateUiState() const + { + const auto current = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(0); - if(i > 0) + for(size_t i=0; i<m_parts.size(); ++i) { - part.button->setVisible(m_controller.isMultiMode()); - part.led->setVisible(m_controller.isMultiMode()); - /* - part.button->setEnabled(m_controller.isMultiMode()); - part.led->setEnabled(m_controller.isMultiMode()); - - part.button->setAlpha(1.0f); - part.led->setAlpha(1.0f); - */ + const auto& part = m_parts[i]; + + part.button->setToggleState(i == current, juce::dontSendNotification); + part.led->setToggleState(i == current, juce::dontSendNotification); + + if(i > 0) + { + part.button->setVisible(m_controller.isMultiMode()); + part.led->setVisible(m_controller.isMultiMode()); + /* + part.button->setEnabled(m_controller.isMultiMode()); + part.led->setEnabled(m_controller.isMultiMode()); + + part.button->setAlpha(1.0f); + part.led->setAlpha(1.0f); + */ + } } } -} -void mqPartSelect::selectPart(const uint8_t _index) const -{ - m_parameterBinding.setPart(_index); - m_editor.setCurrentPart(_index); - updateUiState(); + void mqPartSelect::selectPart(const uint8_t _index) const + { + m_parameterBinding.setPart(_index); + m_editor.setCurrentPart(_index); + updateUiState(); + } } diff --git a/source/mqJucePlugin/mqPartSelect.h b/source/mqJucePlugin/mqPartSelect.h @@ -2,35 +2,33 @@ #include "jucePluginLib/parameterbinding.h" -class mqPartButton; -class Controller; - namespace mqJucePlugin { + class mqPartButton; + class Controller; class Editor; -} -class mqPartSelect -{ -public: - explicit mqPartSelect(mqJucePlugin::Editor& _editor, Controller& _controller, pluginLib::ParameterBinding& _parameterBinding); + class mqPartSelect + { + public: + explicit mqPartSelect(mqJucePlugin::Editor& _editor, Controller& _controller, pluginLib::ParameterBinding& _parameterBinding); - void onPlayModeChanged() const; + void onPlayModeChanged() const; - void selectPart(uint8_t _index) const; + void selectPart(uint8_t _index) const; -private: - void updateUiState() const; + private: + void updateUiState() const; - struct Part - { - mqPartButton* button = nullptr; - juce::Button* led = nullptr; - }; - - mqJucePlugin::Editor& m_editor; - Controller& m_controller; - pluginLib::ParameterBinding& m_parameterBinding; - std::array<Part, 16> m_parts{}; -}; + struct Part + { + mqPartButton* button = nullptr; + juce::Button* led = nullptr; + }; + mqJucePlugin::Editor& m_editor; + Controller& m_controller; + pluginLib::ParameterBinding& m_parameterBinding; + std::array<Part, 16> m_parts{}; + }; +} diff --git a/source/mqJucePlugin/mqPatchManager.cpp b/source/mqJucePlugin/mqPatchManager.cpp @@ -97,17 +97,6 @@ namespace mqJucePlugin static_cast<uint8_t>(program), parameterValues); } - bool PatchManager::equals(const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) const - { - if(_a == _b) - return true; - - if(_a->hash == _b->hash) - return true; - - return false; - } - uint32_t PatchManager::getCurrentPart() const { return m_editor.getProcessor().getController().getCurrentPart(); diff --git a/source/mqJucePlugin/mqPatchManager.h b/source/mqJucePlugin/mqPatchManager.h @@ -2,10 +2,9 @@ #include "jucePluginEditorLib/patchmanager/patchmanager.h" -class Controller; - namespace mqJucePlugin { + class Controller; class Editor; class PatchManager : public jucePluginEditorLib::patchManager::PatchManager @@ -19,7 +18,6 @@ namespace mqJucePlugin bool loadRomData(pluginLib::patchDB::DataList& _results, uint32_t _bank, uint32_t _program) override; pluginLib::patchDB::PatchPtr initializePatch(pluginLib::patchDB::Data&& _sysex) override; pluginLib::patchDB::Data prepareSave(const pluginLib::patchDB::PatchPtr& _patch) const override; - bool equals(const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) const override; uint32_t getCurrentPart() const override; bool activatePatch(const pluginLib::patchDB::PatchPtr& _patch) override; bool activatePatch(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _part) override; diff --git a/source/mqLib/CMakeLists.txt b/source/mqLib/CMakeLists.txt @@ -24,6 +24,7 @@ set(SOURCES lcd.cpp lcd.h leds.cpp leds.h rom.cpp rom.h + romloader.cpp romloader.h microq.cpp microq.h mqbuildconfig.h mqdsp.cpp mqdsp.h diff --git a/source/mqLib/lcd.cpp b/source/mqLib/lcd.cpp @@ -30,7 +30,7 @@ namespace mqLib if(!execute) return false; - const auto res = wLib::LCD::exec(registerSelect, read, g); + const auto res = hwLib::LCD::exec(registerSelect, read, g); if(res) _portGp.writeRX(*res); diff --git a/source/mqLib/lcd.h b/source/mqLib/lcd.h @@ -1,6 +1,6 @@ #pragma once -#include "wLib/lcd.h" +#include "hardwareLib/lcd.h" namespace mc68k { @@ -9,7 +9,7 @@ namespace mc68k namespace mqLib { - class LCD : public wLib::LCD + class LCD : public hwLib::LCD { public: bool exec(mc68k::Port& _portGp, const mc68k::Port& _portF); diff --git a/source/mqLib/microq.cpp b/source/mqLib/microq.cpp @@ -7,6 +7,7 @@ #include "dsp56kEmu/threadtools.h" #include "mqhardware.h" +#include "romloader.h" #include "mc68k/logging.h" @@ -14,14 +15,11 @@ namespace mqLib { MicroQ::MicroQ(BootMode _bootMode/* = BootMode::Default*/) { - // create hardware, will use in-memory ROM if no ROM provided -// auto romFile = synthLib::findROM(512 * 1024); // TODO: validate the found ROMs before use, check if it starts with "2.23". Otherwise, a Virus ROM might be found and booted -// if(romFile.empty()) - const auto romFile = synthLib::findFile(".mid", 300 * 1024, 400 * 1024); - if(romFile.empty()) + const auto romFile = RomLoader::findROM(); + if(!romFile.isValid()) throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "Failed to find device operating system file mq_2_23.mid."); - MCLOG("Boot using ROM " << romFile); + MCLOG("Boot using ROM " << romFile.getFilename()); m_hw.reset(new Hardware(romFile)); diff --git a/source/mqLib/mqdsp.cpp b/source/mqLib/mqdsp.cpp @@ -8,8 +8,6 @@ #include "mc68k/hdi08.h" -#include "wLib/dspBootCode.h" - #include "dsp56kEmu/aar.h" namespace mqLib @@ -24,6 +22,7 @@ namespace mqLib , m_periphX(nullptr) , m_memory(g_memoryValidator, g_pMemSize, g_xyMemSize, g_bridgedAddr, m_memoryBuffer) , m_dsp(m_memory, &m_periphX, &m_periphNop) + , m_boot(m_dsp) { if(!_hardware.isValid()) return; @@ -54,25 +53,6 @@ namespace mqLib m_dsp.getJit().notifyProgramMemWrite(i); } - // rewrite bootloader to work at address g_bootCodeBase instead of $ff0000 - for(uint32_t i=0; i<std::size(wLib::g_dspBootCode); ++i) - { - uint32_t code = wLib::g_dspBootCode[i]; - if((wLib::g_dspBootCode[i] & 0xffff00) == 0xff0000) - { - code = g_bootCodeBase | (wLib::g_dspBootCode[i] & 0xff); - } - - m_memory.set(dsp56k::MemArea_P, i + g_bootCodeBase, code); - m_dsp.getJit().notifyProgramMemWrite(i + g_bootCodeBase); - } - -// m_memory.saveAssembly("dspBootDisasm.asm", g_bootCodeBase, static_cast<uint32_t>(std::size(g_dspBootCode)), true, true, &m_periphX, nullptr); - - // set OMR pins so that bootcode wants program data via HDI08 RX - m_dsp.setPC(g_bootCodeBase); - m_dsp.regs().omr.var |= OMR_MA | OMR_MB | OMR_MC | OMR_MD; - getPeriph().disableTimers(true); // only used to test DSP load, we report 0 all the time for now m_periphX.getEsai().writeEmptyAudioIn(8); @@ -86,7 +66,8 @@ namespace mqLib }); m_hdiUC.setWriteTxCallback([&](const uint32_t _word) { - hdiTransferUCtoDSP(_word); + if(m_boot.hdiWriteTX(_word)) + onDspBootFinished(); }); m_hdiUC.setWriteIrqCallback([&](const uint8_t _irq) { @@ -96,14 +77,6 @@ namespace mqLib { return hdiUcReadIsr(_isr); }); - -#if DSP56300_DEBUGGER - m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp))); -#else - m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); -#endif - - m_thread->setLogToStdout(false); } void MqDsp::exec() @@ -139,6 +112,22 @@ namespace mqLib } } + void MqDsp::onDspBootFinished() + { + m_hdiUC.setWriteTxCallback([&](const uint32_t _word) + { + hdiTransferUCtoDSP(_word); + }); + +#if DSP56300_DEBUGGER + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp))); +#else + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); +#endif + + m_thread->setLogToStdout(false); + } + void MqDsp::onUCRxEmpty(bool _needMoreData) { hdi08().injectTXInterrupt(); diff --git a/source/mqLib/mqdsp.h b/source/mqLib/mqdsp.h @@ -4,6 +4,8 @@ #include "dsp56kEmu/dspthread.h" #include "dsp56kEmu/peripherals.h" +#include "hardwareLib/dspBootCode.h" + #include "wLib/wDsp.h" namespace mc68k @@ -21,7 +23,6 @@ namespace mqLib static constexpr dsp56k::TWord g_bridgedAddr = 0x080000; // start of external SRAM, mapped to X and Y static constexpr dsp56k::TWord g_xyMemSize = 0x800000; // due to weird AAR mapping we just allocate enough so that everything fits into it static constexpr dsp56k::TWord g_pMemSize = 0x2000; // only $0000 < $1400 for DSP, rest for us - static constexpr dsp56k::TWord g_bootCodeBase = 0x1500; MqDsp(Hardware& _hardware, mc68k::Hdi08& _hdiUC, uint32_t _index); void exec(); @@ -52,6 +53,7 @@ namespace mqLib bool haveSentTXToDSP() const { return m_haveSentTXtoDSP; } bool receivedMagicEsaiPacket() const { return m_receivedMagicEsaiPacket; } + void onDspBootFinished(); private: void onUCRxEmpty(bool _needMoreData); @@ -76,5 +78,7 @@ namespace mqLib std::unique_ptr<dsp56k::DSPThread> m_thread; bool m_receivedMagicEsaiPacket = false; + + hwLib::DspBoot m_boot; }; } diff --git a/source/mqLib/mqhardware.cpp b/source/mqLib/mqhardware.cpp @@ -7,16 +7,16 @@ namespace mqLib { - Hardware::Hardware(const std::string& _romFilename) + Hardware::Hardware(const ROM& _rom) : wLib::Hardware(44100) - , m_rom(_romFilename) + , m_rom(_rom) , m_uc(m_rom) #if MQ_VOICE_EXPANSION , m_dsps{MqDsp(*this, m_uc.getHdi08A().getHdi08(), 0), MqDsp(*this, m_uc.getHdi08B().getHdi08(), 1) , MqDsp(*this, m_uc.getHdi08C().getHdi08(), 2)} #else , m_dsps{MqDsp(*this, m_uc.getHdi08A().getHdi08(), 0)} #endif - , m_midi(m_uc.getQSM()) + , m_midi(m_uc.getQSM(), 44100) { if(!m_rom.isValid()) throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing); @@ -207,8 +207,8 @@ namespace mqLib void Hardware::setGlobalDefaultParameters() { - m_midi.writeMidi({0xf0,0x3e,0x10,0x7f,0x24,0x00,0x07,0x02,0xf7}); // Control Send = SysEx - m_midi.writeMidi({0xf0,0x3e,0x10,0x7f,0x24,0x00,0x08,0x01,0xf7}); // Control Receive = on + m_midi.write({0xf0,0x3e,0x10,0x7f,0x24,0x00,0x07,0x02,0xf7}); // Control Send = SysEx + m_midi.write({0xf0,0x3e,0x10,0x7f,0x24,0x00,0x08,0x01,0xf7}); // Control Receive = on m_bootCompleted = true; } diff --git a/source/mqLib/mqhardware.h b/source/mqLib/mqhardware.h @@ -10,9 +10,8 @@ #include "dsp56kEmu/dspthread.h" -#include "synthLib/midiTypes.h" +#include "hardwareLib/sciMidi.h" -#include "wLib/wMidi.h" #include "wLib/wHardware.h" namespace mqLib @@ -22,7 +21,7 @@ namespace mqLib static constexpr uint32_t g_dspCount = g_useVoiceExpansion ? 3 : 1; public: - explicit Hardware(const std::string& _romFilename); + explicit Hardware(const ROM& _rom); ~Hardware(); void process(); @@ -54,7 +53,7 @@ namespace mqLib void initVoiceExpansion(); - wLib::Midi& getMidi() override + hwLib::SciMidi& getMidi() override { return m_midi; } @@ -79,6 +78,6 @@ namespace mqLib TAudioOutputs m_audioOutputs; std::array<MqDsp,g_dspCount> m_dsps; - wLib::Midi m_midi; + hwLib::SciMidi m_midi; }; } diff --git a/source/mqLib/mqmc.cpp b/source/mqLib/mqmc.cpp @@ -33,7 +33,7 @@ namespace mqLib m_romRuntimeData.resize(ROM::size()); memcpy(m_romRuntimeData.data(), m_rom.getData(), ROM::size()); - m_flash.reset(new wLib::Am29f(m_romRuntimeData.data(), m_romRuntimeData.size(), false, true)); + m_flash.reset(new hwLib::Am29f(m_romRuntimeData.data(), m_romRuntimeData.size(), false, true)); m_memory.resize(g_memorySize, 0); diff --git a/source/mqLib/mqmc.h b/source/mqLib/mqmc.h @@ -6,7 +6,8 @@ #include "buttons.h" #include "lcd.h" #include "leds.h" -#include "wLib/am29f.h" + +#include "hardwareLib/am29f.h" #include "mc68k/mc68k.h" #include "mc68k/hdi08periph.h" @@ -66,7 +67,7 @@ namespace mqLib const ROM& m_rom; std::vector<uint8_t> m_romRuntimeData; - std::unique_ptr<wLib::Am29f> m_flash; + std::unique_ptr<hwLib::Am29f> m_flash; LCD m_lcd; Buttons m_buttons; Leds m_leds; diff --git a/source/mqLib/rom.cpp b/source/mqLib/rom.cpp @@ -1,5 +1,18 @@ #include "rom.h" +#include <cstring> + namespace mqLib { + ROM::ROM(const std::string& _filename) : wLib::ROM(_filename, g_romSize) + { + if(getSize() < 5) + return; + + auto* d = reinterpret_cast<const char*>(getData()); + + // OS version is right at the start of the ROM, as zero-terminated ASCII + if(strstr(d, "2.23") != d) + clear(); + } } diff --git a/source/mqLib/rom.h b/source/mqLib/rom.h @@ -9,9 +9,8 @@ namespace mqLib public: static constexpr uint32_t g_romSize = 524288; - explicit ROM(const std::string& _filename) : wLib::ROM(_filename, g_romSize) - { - } + ROM() = default; + explicit ROM(const std::string& _filename); static constexpr uint32_t size() { return g_romSize; } diff --git a/source/mqLib/romloader.cpp b/source/mqLib/romloader.cpp @@ -0,0 +1,28 @@ +#include "romloader.h" + +#include "synthLib/os.h" + +namespace mqLib +{ + ROM RomLoader::findROM() + { + const auto midiFiles = findFiles(".mid", 300 * 1024, 400 * 1024); + + for (const auto& midiFile : midiFiles) + { + ROM rom(midiFile); + if(rom.isValid()) + return rom; + } + + const auto binFiles = findFiles(".bin", 512 * 1024, 512 * 1024); + + for (const auto& binFile : binFiles) + { + ROM rom(binFile); + if(rom.isValid()) + return rom; + } + return {}; + } +} diff --git a/source/mqLib/romloader.h b/source/mqLib/romloader.h @@ -0,0 +1,12 @@ +#pragma once +#include "rom.h" +#include "synthLib/romLoader.h" + +namespace mqLib +{ + class RomLoader : synthLib::RomLoader + { + public: + static ROM findROM(); + }; +} diff --git a/source/mqTestConsole/mqTestConsole.cpp b/source/mqTestConsole/mqTestConsole.cpp @@ -2,14 +2,14 @@ #include <iostream> #include <memory> +#include "baseLib/configFile.h" + #include "synthLib/os.h" #include "synthLib/wavWriter.h" #include "synthLib/midiTypes.h" -#include "synthLib/configFile.h" #include "mqLib/microq.h" #include "mqLib/mqhardware.h" -#include "mqLib/rom.h" #include "dsp56kEmu/threadtools.h" @@ -78,7 +78,7 @@ int main(int _argc, char* _argv[]) try { - synthLib::ConfigFile cfg((synthLib::getModulePath() + "config.cfg").c_str()); + baseLib::ConfigFile cfg((synthLib::getModulePath() + "config.cfg").c_str()); for (const auto& v : cfg.getValues()) { if(v.first == "MidiIn") diff --git a/source/nord/CMakeLists.txt b/source/nord/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.15) + +if(${CMAKE_PROJECT_NAME}_SYNTH_NODALRED2X) + add_subdirectory(n2x) +endif() diff --git a/source/nord/n2x/CMakeLists.txt b/source/nord/n2x/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.15) + +add_subdirectory(n2xLib) +add_subdirectory(n2xTestConsole) + +if(${CMAKE_PROJECT_NAME}_BUILD_JUCEPLUGIN) + add_subdirectory(n2xJucePlugin) +endif() diff --git a/source/nord/n2x/n2xJucePlugin/CMakeLists.txt b/source/nord/n2x/n2xJucePlugin/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.15) + +project(n2xJucePlugin VERSION ${CMAKE_PROJECT_VERSION}) + +set(SOURCES + n2xArp.cpp n2xArp.h + n2xController.cpp n2xController.h + n2xEditor.cpp n2xEditor.h + n2xFocusedParameter.cpp n2xFocusedParameter.h + n2xMasterVolume.cpp n2xMasterVolume.h + n2xOctLed.cpp n2xOctLed.h + n2xParameterDrivenLed.cpp n2xParameterDrivenLed.h + n2xLcd.cpp n2xLcd.h + n2xLfo.cpp n2xLfo.h + n2xPart.cpp n2xPart.h + n2xPartLed.cpp n2xPartLed.h + n2xParts.cpp n2xParts.h + n2xPatchManager.cpp n2xPatchManager.h + n2xPluginEditorState.cpp n2xPluginEditorState.h + n2xPluginProcessor.cpp n2xPluginProcessor.h + n2xVmMap.cpp n2xVmMap.h + parameterDescriptions_n2x.json + skins/n2xTrancy/n2xTrancy.json +) + +# https://forum.juce.com/t/help-needed-using-binarydata-with-cmake-juce-6/40486 +# "This might be because the BinaryData files are generated during the build, so the IDE may not be able to find them until the build has been run once (and even then, some IDEs might need a bit of a nudge to re-index the binary directory…)" +SET(ASSETS "parameterDescriptions_n2x.json") + +include(skins/n2xTrancy/assets.cmake) + +juce_add_binary_data(n2xJucePlugin_BinaryData SOURCES ${ASSETS} ${ASSETS_n2xTrancy}) + +createJucePlugin(n2xJucePlugin "NodalRed2x" TRUE "Tn2x" n2xJucePlugin_BinaryData n2xLib) diff --git a/source/nord/n2x/n2xJucePlugin/n2xArp.cpp b/source/nord/n2x/n2xJucePlugin/n2xArp.cpp @@ -0,0 +1,23 @@ +#include "n2xArp.h" + +#include "n2xController.h" + +namespace n2xJucePlugin +{ + Arp::Arp(Editor& _editor) : ParameterDrivenLed(_editor, "ArpEnabled", "Lfo2Dest") + { + bind(); + } + + bool Arp::updateToggleState(const pluginLib::Parameter* _parameter) const + { + const auto v = _parameter->getUnnormalizedValue(); + const bool arpActive = v != 3 && v != 4 && v != 7; + return arpActive; + } + + void Arp::onClick(pluginLib::Parameter* _targetParameter, const bool _toggleState) + { + _targetParameter->setUnnormalizedValueNotifyingHost(_toggleState ? 0 : 3, pluginLib::Parameter::Origin::Ui); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xArp.h b/source/nord/n2x/n2xJucePlugin/n2xArp.h @@ -0,0 +1,16 @@ +#pragma once + +#include "n2xParameterDrivenLed.h" + +namespace n2xJucePlugin +{ + class Arp : ParameterDrivenLed + { + public: + explicit Arp(Editor& _editor); + + protected: + bool updateToggleState(const pluginLib::Parameter* _parameter) const override; + void onClick(pluginLib::Parameter* _targetParameter, bool _toggleState) override; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.cpp b/source/nord/n2x/n2xJucePlugin/n2xController.cpp @@ -0,0 +1,424 @@ +#include "n2xController.h" + +#include <fstream> + +#include "BinaryData.h" +#include "n2xPatchManager.h" +#include "n2xPluginProcessor.h" + +#include "synthLib/os.h" + +#include "dsp56kEmu/logging.h" +#include "n2xLib/n2xmiditypes.h" + +namespace +{ + constexpr const char* g_midiPacketNames[] = + { + "requestdump", + "singledump", + "multidump" + }; + + static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(n2xJucePlugin::Controller::MidiPacketType::Count)); + + const char* midiPacketName(n2xJucePlugin::Controller::MidiPacketType _type) + { + return g_midiPacketNames[static_cast<uint32_t>(_type)]; + } + + constexpr uint32_t g_multiPage = 10; +} + +namespace n2xJucePlugin +{ + Controller::Controller(AudioPluginAudioProcessor& _p) : pluginLib::Controller(_p, loadParameterDescriptions()), m_state(nullptr) + { + registerParams(_p); + + requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 0); // single edit buffers A-D + requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 1); + requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 2); + requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 3); + + requestDump(n2x::SysexByte::MultiRequestBankEditBuffer, 0); // performance edit buffer + + requestDump(n2x::SysexByte::EmuGetPotsPosition, 0); + + m_currentPartChanged.set(onCurrentPartChanged, [this](const uint8_t& _part) + { + setMultiParameter(n2x::SelectedChannel, _part); + }); + } + + Controller::~Controller() = default; + + const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size) + { + for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + { + if (BinaryData::originalFilenames[i] != _filename) + continue; + + int size = 0; + const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); + _size = static_cast<uint32_t>(size); + return res; + } + return nullptr; + } + + std::string Controller::loadParameterDescriptions() + { + const auto name = "parameterDescriptions_n2x.json"; + const auto path = synthLib::getModulePath() + name; + + const std::ifstream f(path.c_str(), std::ios::in); + if(f.is_open()) + { + std::stringstream buf; + buf << f.rdbuf(); + return buf.str(); + } + + uint32_t size; + const auto res = findEmbeddedResource(name, size); + if(res) + return {res, size}; + return {}; + } + + void Controller::onStateLoaded() + { + requestDump(n2x::SysexByte::EmuGetPotsPosition, 0); + } + + bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource _source) + { + if(_msg.size() == n2x::g_singleDumpSize) + { + return parseSingleDump(_msg); + } + if(_msg.size() == n2x::g_multiDumpSize) + { + return parseMultiDump(_msg); + } + + n2x::KnobType knobType; + uint8_t knobValue; + + if(n2x::State::parseKnobSysex(knobType, knobValue, _msg)) + { + if(m_state.receive(_msg, _source)) + { + onKnobChanged(knobType, knobValue); + return true; + } + } + return false; + } + + bool Controller::parseSingleDump(const pluginLib::SysEx& _msg) + { + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues params; + + if(!parseMidiPacket(midiPacketName(MidiPacketType::SingleDump), data, params, _msg)) + return false; + + const auto bank = data[pluginLib::MidiDataType::Bank]; + const auto program = data[pluginLib::MidiDataType::Program]; + + if(bank == n2x::SysexByte::SingleDumpBankEditBuffer && program < getPartCount()) + { + m_state.receive(_msg, synthLib::MidiEventSource::Plugin); + applyPatchParameters(params, program); + onProgramChanged(); + return true; + } + + assert(false && "receiving a single for a non-edit-buffer is unexpected"); + return false; + } + + bool Controller::parseMultiDump(const pluginLib::SysEx& _msg) + { + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues params; + + if(!parseMidiPacket(midiPacketName(MidiPacketType::MultiDump), data, params, _msg)) + return false; + + const auto bank = data[pluginLib::MidiDataType::Bank]; + + if(bank != n2x::SysexByte::MultiDumpBankEditBuffer) + return false; + + m_state.receive(_msg, synthLib::MidiEventSource::Plugin); + + applyPatchParameters(params, 0); + + onProgramChanged(); + + const auto part = m_state.getMultiParam(n2x::SelectedChannel, 0); + if(part < getPartCount()) // if have seen dumps that have invalid stuff in here + { + // we ignore this for now, is annoying if the selected part changes whenever we load a multi +// setCurrentPart(part); + } + + return true; + } + + bool Controller::parseControllerMessage(const synthLib::SMidiEvent& _e) + { + const auto& cm = getParameterDescriptions().getControllerMap(); + const auto paramIndices = cm.getControlledParameters(_e); + + if(paramIndices.empty()) + return false; + + const auto origin = midiEventSourceToParameterOrigin(_e.source); + + m_state.receive(_e); + + const auto parts = m_state.getPartsForMidiChannel(_e); + + for (const uint8_t part : parts) + { + if(_e.b == n2x::ControlChange::CCSync) + { + // this controls both Sync and RingMod + // Sync = bit 0 + // RingMod = bit 1 + auto* paramSync = getParameter("Sync", part); + auto* paramRingMod = getParameter("RingMod", part); + paramSync->setValueFromSynth(_e.c & 1, origin); + paramRingMod->setValueFromSynth((_e.c>>1) & 1, origin); + } + else + { + for (const auto paramIndex : paramIndices) + { + auto* param = getParameter(paramIndex, part); + assert(param && "parameter not found for control change"); + param->setValueFromSynth(_e.c, origin); + } + } + } + + return true; + } + + void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, pluginLib::ParamValue _value) + { + if(_parameter.getDescription().page >= g_multiPage) + { + sendMultiParameter(_parameter, static_cast<uint8_t>(_value)); + return; + } + + constexpr uint32_t sysexRateLimitMs = 150; + + pluginLib::Parameter& nonConstParam = const_cast<pluginLib::Parameter&>(_parameter); + + const auto singleParam = static_cast<n2x::SingleParam>(_parameter.getDescription().index); + const uint8_t part = _parameter.getPart(); + + const auto& controllerMap = getParameterDescriptions().getControllerMap(); + + uint32_t descIndex; + if(!getParameterDescriptions().getIndexByName(descIndex, _parameter.getDescription().name)) + assert(false && "parameter not found"); + + const auto& ccs = controllerMap.getControlChanges(synthLib::M_CONTROLCHANGE, descIndex); + if(ccs.empty()) + { + nonConstParam.setRateLimitMilliseconds(sysexRateLimitMs); + setSingleParameter(part, singleParam, _value); + return; + } + + const auto cc = ccs.front(); + + if(cc == n2x::ControlChange::CCSync) + { + // sync and ringmod have the same CC, combine them + const auto* paramSync = getParameter("Sync", part); + const auto* paramRingMod = getParameter("RingMod", part); + + _value = static_cast<uint8_t>(paramSync->getUnnormalizedValue() | (paramRingMod->getUnnormalizedValue() << 1)); + } + + const auto ch = m_state.getPartMidiChannel(part); + + const auto parts = m_state.getPartsForMidiChannel(ch); + + const auto ev = synthLib::SMidiEvent{synthLib::MidiEventSource::Editor, static_cast<uint8_t>(synthLib::M_CONTROLCHANGE + ch), cc, static_cast<uint8_t>(_value)}; + + if(parts.size() > 1) + { + // this is problematic. We want to edit one part only but two parts receive on the same channel. We have to send a full dump + nonConstParam.setRateLimitMilliseconds(sysexRateLimitMs); + setSingleParameter(part, singleParam, static_cast<uint8_t>(_value)); + } + else + { + nonConstParam.setRateLimitMilliseconds(0); + m_state.receive(ev); + sendMidiEvent(ev); + } + } + + void Controller::setSingleParameter(uint8_t _part, n2x::SingleParam _sp, uint8_t _value) + { + if(!m_state.changeSingleParameter(_part, _sp, _value)) + return; + + const auto& single = m_state.getSingle(_part); + pluginLib::Controller::sendSysEx(pluginLib::SysEx{single.begin(), single.end()}); + } + + void Controller::setMultiParameter(n2x::MultiParam _mp, uint8_t _value) + { + if(!m_state.changeMultiParameter(_mp, _value)) + return; + const auto& multi = m_state.updateAndGetMulti(); + pluginLib::Controller::sendSysEx(pluginLib::SysEx{multi.begin(), multi.end()}); + } + + uint8_t Controller::getMultiParameter(const n2x::MultiParam _param) const + { + return m_state.getMultiParam(_param, 0); + } + + void Controller::sendMultiParameter(const pluginLib::Parameter& _parameter, const uint8_t _value) + { + const auto& desc = _parameter.getDescription(); + + const auto mp = static_cast<n2x::MultiParam>(desc.index + (desc.page - g_multiPage) * 128); + + setMultiParameter(mp, _value); + } + + bool Controller::sendSysEx(MidiPacketType _packet, const std::map<pluginLib::MidiDataType, uint8_t>& _params) const + { + return pluginLib::Controller::sendSysEx(midiPacketName(_packet), _params); + } + + void Controller::requestDump(const uint8_t _bank, const uint8_t _patch) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + + params[pluginLib::MidiDataType::DeviceId] = n2x::SysexByte::DefaultDeviceId; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_bank); + params[pluginLib::MidiDataType::Program] = _patch; + + sendSysEx(MidiPacketType::RequestDump, params); + } + + std::vector<uint8_t> Controller::createSingleDump(uint8_t _bank, uint8_t _program, uint8_t _part) const + { + pluginLib::MidiPacket::Data data; + + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, n2x::SysexByte::DefaultDeviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); + + std::vector<uint8_t> dst; + + if (!createMidiDataFromPacket(dst, midiPacketName(MidiPacketType::SingleDump), data, _part)) + return {}; + + return dst; + } + + std::vector<uint8_t> Controller::createMultiDump(const n2x::SysexByte _bank, const uint8_t _program) + { + const auto multi = m_state.updateAndGetMulti(); + + std::vector<uint8_t> result(multi.begin(), multi.end()); + + result[n2x::SysexIndex::IdxMsgType] = _bank; + result[n2x::SysexIndex::IdxMsgSpec] = _program; + + return result; + } + + bool Controller::activatePatch(const std::vector<uint8_t>& _sysex, const uint32_t _part) const + { + if(_part >= getPartCount()) + return false; + + const auto isSingle =_sysex.size() == n2x::g_singleDumpSize; + const auto isMulti = _sysex.size() == n2x::g_multiDumpSize; + + if(!isSingle && !isMulti) + return false; + + auto d = _sysex; + + d[n2x::SysexIndex::IdxMsgType] = isSingle ? n2x::SysexByte::SingleDumpBankEditBuffer : n2x::SysexByte::MultiDumpBankEditBuffer; + d[n2x::SysexIndex::IdxMsgSpec] = static_cast<uint8_t>(isMulti ? 0 : _part); + + pluginLib::Controller::sendSysEx(d); + + if(isSingle) + requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, static_cast<uint8_t>(_part)); + else + { + requestDump(n2x::SysexByte::MultiRequestBankEditBuffer, 0); + for(uint8_t i=0; i<getPartCount(); ++i) + requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, i); + } + + return true; + } + + bool Controller::isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const + { + if(_base.getDescription().isNonPartSensitive() || _derived.getDescription().isNonPartSensitive()) + return false; + + if(_derived.getParameterIndex() != _base.getParameterIndex()) + return false; + + const auto& packetName = midiPacketName(MidiPacketType::SingleDump); + const auto* packet = getMidiPacket(packetName); + + if (!packet) + { + LOG("Failed to find midi packet " << packetName); + return true; + } + + const auto* defA = packet->getDefinitionByParameterName(_derived.getDescription().name); + const auto* defB = packet->getDefinitionByParameterName(_base.getDescription().name); + + if (!defA || !defB) + return true; + + return defA->doMasksOverlap(*defB); + } + + std::string Controller::getSingleName(const uint8_t _part) const + { + const auto& single = m_state.getSingle(_part); + return PatchManager::getPatchName({single.begin(), single.end()}); + } + + std::string Controller::getPatchName(const uint8_t _part) const + { + const auto& multi = m_state.getMulti(); + + const auto bank = multi[n2x::SysexIndex::IdxMsgType]; + if(bank >= n2x::SysexByte::MultiDumpBankA) + return PatchManager::getPatchName({multi.begin(), multi.end()}); + return getSingleName(_part); + } + + bool Controller::getKnobState(uint8_t& _result, const n2x::KnobType _type) const + { + return m_state.getKnobState(_result, _type); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xController.h b/source/nord/n2x/n2xJucePlugin/n2xController.h @@ -0,0 +1,71 @@ +#pragma once + +#include "jucePluginLib/controller.h" +#include "n2xLib/n2xstate.h" + +namespace n2xJucePlugin +{ + class AudioPluginAudioProcessor; + + class Controller : public pluginLib::Controller + { + public: + enum class MidiPacketType + { + RequestDump, + SingleDump, + MultiDump, + + Count + }; + + pluginLib::Event<> onProgramChanged; + pluginLib::Event<n2x::KnobType, uint8_t> onKnobChanged; + + Controller(AudioPluginAudioProcessor&); + ~Controller() override; + + static std::string loadParameterDescriptions(); + + void onStateLoaded() override; + + uint8_t getPartCount() const override + { + return 4; + } + + bool parseSysexMessage(const pluginLib::SysEx&, synthLib::MidiEventSource) override; + bool parseSingleDump(const pluginLib::SysEx& _msg); + bool parseMultiDump(const pluginLib::SysEx& _msg); + bool parseControllerMessage(const synthLib::SMidiEvent&) override; + + void sendParameterChange(const pluginLib::Parameter& _parameter, pluginLib::ParamValue _value) override; + + void setSingleParameter(uint8_t _part, n2x::SingleParam _sp, uint8_t _value); + void setMultiParameter(n2x::MultiParam _mp, uint8_t _value); + uint8_t getMultiParameter(n2x::MultiParam _param) const; + + void sendMultiParameter(const pluginLib::Parameter& _parameter, uint8_t _value); + bool sendSysEx(MidiPacketType _packet, const std::map<pluginLib::MidiDataType, uint8_t>& _params) const; + + void requestDump(uint8_t _bank, uint8_t _patch) const; + + std::vector<uint8_t> createSingleDump(uint8_t _bank, uint8_t _program, uint8_t _part) const; + std::vector<uint8_t> createMultiDump(n2x::SysexByte _bank, uint8_t _program); + + bool activatePatch(const std::vector<uint8_t>& _sysex, uint32_t _part) const; + + bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; + + std::string getSingleName(uint8_t _part) const; + std::string getPatchName(uint8_t _part) const; + + using pluginLib::Controller::sendSysEx; + + bool getKnobState(uint8_t& _result, n2x::KnobType _type) const; + + private: + n2x::State m_state; + pluginLib::EventListener<uint8_t> m_currentPartChanged; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xEditor.cpp b/source/nord/n2x/n2xJucePlugin/n2xEditor.cpp @@ -0,0 +1,254 @@ +#include "n2xEditor.h" + +#include "BinaryData.h" + +#include "n2xArp.h" +#include "n2xController.h" +#include "n2xFocusedParameter.h" +#include "n2xLcd.h" +#include "n2xLfo.h" +#include "n2xMasterVolume.h" +#include "n2xOctLed.h" +#include "n2xPart.h" +#include "n2xParts.h" +#include "n2xPatchManager.h" +#include "n2xPluginProcessor.h" +#include "n2xVmMap.h" + +#include "jucePluginLib/parameterbinding.h" +#include "jucePluginLib/pluginVersion.h" + +#include "n2xLib/n2xdevice.h" + +namespace n2xJucePlugin +{ + Editor::Editor(jucePluginEditorLib::Processor& _processor, pluginLib::ParameterBinding& _binding, std::string _skinFolder, const std::string& _jsonFilename) + : jucePluginEditorLib::Editor(_processor, _binding, std::move(_skinFolder)) + , m_controller(dynamic_cast<Controller&>(_processor.getController())) + , m_parameterBinding(_binding) + { + create(_jsonFilename); + + addMouseListener(this, true); + { + // Init Patch Manager + const auto container = findComponent("ContainerPatchManager"); + constexpr auto scale = 0.8f; + const float x = static_cast<float>(container->getX()); + const float y = static_cast<float>(container->getY()); + const float w = static_cast<float>(container->getWidth()); + const float h = static_cast<float>(container->getHeight()); + container->setTransform(juce::AffineTransform::scale(scale, scale)); + container->setSize(static_cast<int>(w / scale),static_cast<int>(h / scale)); + container->setTopLeftPosition(static_cast<int>(x / scale),static_cast<int>(y / scale)); + + const auto configOptions = getProcessor().getConfigOptions(); + const auto dir = configOptions.getDefaultFile().getParentDirectory(); + + setPatchManager(new PatchManager(*this, container, dir)); + } + + if(auto* versionNumber = findComponentT<juce::Label>("VersionNumber", false)) + { + versionNumber->setText(pluginLib::Version::getVersionString(), juce::dontSendNotification); + } + + if(auto* romSelector = findComponentT<juce::ComboBox>("RomSelector")) + { + const auto* dev = dynamic_cast<const n2x::Device*>(getProcessor().getPlugin().getDevice()); + + if(dev != nullptr && getProcessor().isPluginValid()) + { + constexpr int id = 1; + + const auto name = juce::File(dev->getRomFilename()).getFileName(); + romSelector->addItem(name, id); + } + else + { + romSelector->addItem("<No ROM found>", 1); + } + romSelector->setSelectedId(1); + romSelector->setInterceptsMouseClicks(false, false); + } + + m_arp.reset(new Arp(*this)); + m_focusedParameter.reset(new FocusedParameter(*this)); + m_lcd.reset(new Lcd(*this)); + for(uint8_t i=0; i<m_lfos.size(); ++i) + m_lfos[i].reset(new Lfo(*this, i)); + m_masterVolume.reset(new MasterVolume(*this)); + m_octLed.reset(new OctLed(*this)); + m_parts.reset(new Parts(*this)); + m_vmMap.reset(new VmMap(*this, m_parameterBinding)); + + onPartChanged.set(m_controller.onCurrentPartChanged, [this](const uint8_t& _part) + { + setCurrentPart(_part); + m_parameterBinding.setPart(_part); + }); + + if(auto* bt = findComponentT<juce::Button>("button_store")) + { + bt->onClick = [this] + { + onBtSave(); + }; + } + + if(auto* bt = findComponentT<juce::Button>("PresetPrev")) + { + bt->onClick = [this] + { + onBtPrev(); + }; + } + + if(auto* bt = findComponentT<juce::Button>("PresetNext")) + { + bt->onClick = [this] + { + onBtNext(); + }; + } + + m_onSelectedPatchChanged.set(getPatchManager()->onSelectedPatchChanged, [this](const uint32_t& _part, const pluginLib::patchDB::PatchKey& _patchKey) + { + onSelectedPatchChanged(static_cast<uint8_t>(_part), _patchKey); + }); + } + + Editor::~Editor() + { + m_arp.reset(); + m_focusedParameter.reset(); + m_lcd.reset(); + for (auto& lfo : m_lfos) + lfo.reset(); + m_masterVolume.reset(); + m_octLed.reset(); + m_parts.reset(); + m_vmMap.reset(); + } + + const char* Editor::findEmbeddedResource(const std::string& _filename, uint32_t& _size) + { + for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + { + if (BinaryData::originalFilenames[i] != _filename) + continue; + + int size = 0; + const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); + _size = static_cast<uint32_t>(size); + return res; + } + return nullptr; + } + + const char* Editor::findResourceByFilename(const std::string& _filename, uint32_t& _size) + { + return findEmbeddedResource(_filename, _size); + } + + std::pair<std::string, std::string> Editor::getDemoRestrictionText() const + { + return {}; + } + + genericUI::Button<juce::DrawableButton>* Editor::createJuceComponent(genericUI::Button<juce::DrawableButton>* _button, genericUI::UiObject& _object, const std::string& _name, const juce::DrawableButton::ButtonStyle _buttonStyle) + { + if( _name == "PerfSlotActiveA" || + _name == "PerfSlotActiveB" || + _name == "PerfSlotActiveC" || + _name == "PerfSlotActiveD") + return new Part(*this, _name, _buttonStyle); + + return jucePluginEditorLib::Editor::createJuceComponent(_button, _object, _name, _buttonStyle); + } + + std::string Editor::getCurrentPatchName() const + { + const auto part = m_controller.getCurrentPart(); + if(!m_activePatchNames[part].empty()) + return m_activePatchNames[part]; + return m_controller.getPatchName(part); + } + + void Editor::onPatchActivated(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _part) + { + const auto isMulti = _patch->sysex.size() == n2x::g_multiDumpSize; + + const auto name = _patch->getName(); + + setCurrentPatchName(_part, name); + + if(isMulti) + { + for (uint8_t p=0; p<m_activePatchNames.size(); ++p) + { + if(p == _part) + continue; + + // set patch name for all parts if the source is a multi + setCurrentPatchName(p, name); + + // set patch manager selection so that all parts have the multi selected + getPatchManager()->setSelectedPatch(p, _patch); + } + } + + m_lcd->updatePatchName(); + } + + void Editor::mouseEnter(const juce::MouseEvent& _ev) + { + m_focusedParameter->onMouseEnter(_ev); + } + + void Editor::onBtSave() const + { + juce::PopupMenu menu; + + if(getPatchManager()->createSaveMenuEntries(menu, "Program")) + menu.addSeparator(); + + getPatchManager()->createSaveMenuEntries(menu, m_controller.getPartCount(), "Performance"); + + menu.showMenuAsync({}); + } + + void Editor::onBtPrev() const + { + getPatchManager()->selectPrevPreset(m_controller.getCurrentPart()); + } + + void Editor::onBtNext() const + { + getPatchManager()->selectNextPreset(m_controller.getCurrentPart()); + } + + void Editor::setCurrentPatchName(uint8_t _part, const std::string& _name) + { + if(m_activePatchNames[_part] == _name) + return; + + m_activePatchNames[_part] = _name; + + if(m_controller.getCurrentPart() == _part) + m_lcd->updatePatchName(); + } + + void Editor::onSelectedPatchChanged(uint8_t _part, const pluginLib::patchDB::PatchKey& _patchKey) + { + auto source = _patchKey.source; + if(!source) + return; + + if(source->patches.empty()) + source = getPatchManager()->getDataSource(*source); + + if(const auto patch = source->getPatch(_patchKey)) + setCurrentPatchName(_part, patch->getName()); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xEditor.h b/source/nord/n2x/n2xJucePlugin/n2xEditor.h @@ -0,0 +1,88 @@ +#pragma once + +#include "jucePluginEditorLib/pluginEditor.h" +#include "jucePluginLib/patchdb/patch.h" + +namespace jucePluginEditorLib +{ + class FocusedParameter; + class Processor; +} + +namespace pluginLib +{ + class ParameterBinding; +} + +namespace n2xJucePlugin +{ + class Lfo; + class FocusedParameter; + class PatchManager; + class Controller; + + class Arp; + class Lcd; + class MasterVolume; + class OctLed; + class Parts; + class VmMap; + + class Editor final : public jucePluginEditorLib::Editor + { + public: + Editor(jucePluginEditorLib::Processor& _processor, pluginLib::ParameterBinding& _binding, std::string _skinFolder, const std::string& _jsonFilename); + ~Editor() override; + + Editor(Editor&&) = delete; + Editor(const Editor&) = delete; + Editor& operator = (Editor&&) = delete; + Editor& operator = (const Editor&) = delete; + + static const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size); + const char* findResourceByFilename(const std::string& _filename, uint32_t& _size) override; + std::pair<std::string, std::string> getDemoRestrictionText() const override; + + Controller& getN2xController() const { return m_controller; } + + genericUI::Button<juce::DrawableButton>* createJuceComponent(genericUI::Button<juce::DrawableButton>*, genericUI::UiObject& _object, const std::string& _name, juce::DrawableButton::ButtonStyle) override; + + std::string getCurrentPatchName() const; + + void onPatchActivated(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _part); + + pluginLib::ParameterBinding& getParameterBinding() const { return m_parameterBinding; } + + Lcd& getLCD() const + { + assert(m_lcd); + return *m_lcd.get(); + } + + private: + void mouseEnter(const juce::MouseEvent& _ev) override; + void onBtSave() const; + void onBtPrev() const; + void onBtNext() const; + void setCurrentPatchName(uint8_t _part, const std::string& _name); + void onSelectedPatchChanged(uint8_t _part, const pluginLib::patchDB::PatchKey& _patchKey); + + Controller& m_controller; + pluginLib::ParameterBinding& m_parameterBinding; + + std::unique_ptr<Arp> m_arp; + std::unique_ptr<FocusedParameter> m_focusedParameter; + std::unique_ptr<Lcd> m_lcd; + std::array<std::unique_ptr<Lfo>, 2> m_lfos; + std::unique_ptr<MasterVolume> m_masterVolume; + std::unique_ptr<OctLed> m_octLed; + std::unique_ptr<Parts> m_parts; + std::unique_ptr<VmMap> m_vmMap; + + pluginLib::EventListener<uint8_t> onPartChanged; + + std::array<std::string, 4> m_activePatchNames; + + pluginLib::EventListener<uint32_t, pluginLib::patchDB::PatchKey> m_onSelectedPatchChanged; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xFocusedParameter.cpp b/source/nord/n2x/n2xJucePlugin/n2xFocusedParameter.cpp @@ -0,0 +1,20 @@ +#include "n2xFocusedParameter.h" + +#include "n2xEditor.h" +#include "n2xController.h" + +namespace n2xJucePlugin +{ + FocusedParameter::FocusedParameter(const Editor& _editor) + : jucePluginEditorLib::FocusedParameter(_editor.getN2xController(), _editor.getParameterBinding(), _editor) + , m_editor(_editor) + { + } + + void FocusedParameter::updateParameter(const std::string& _name, const std::string& _value) + { + // we only have 3 digits but some parameters have values <= -100, doesn't look nice at all +// m_editor.getLCD().setOverrideText(_value); + jucePluginEditorLib::FocusedParameter::updateParameter(_name, _value); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xFocusedParameter.h b/source/nord/n2x/n2xJucePlugin/n2xFocusedParameter.h @@ -0,0 +1,18 @@ +#pragma once + +#include "jucePluginEditorLib/focusedParameter.h" + +namespace n2xJucePlugin +{ + class Editor; + + class FocusedParameter : public jucePluginEditorLib::FocusedParameter + { + public: + explicit FocusedParameter(const Editor& _editor); + void updateParameter(const std::string& _name, const std::string& _value) override; + + private: + const Editor& m_editor; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xLcd.cpp b/source/nord/n2x/n2xJucePlugin/n2xLcd.cpp @@ -0,0 +1,171 @@ +#include "n2xLcd.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +namespace n2xJucePlugin +{ + constexpr char space = 0x21; // unit separator, i.e. full width space + + constexpr uint32_t g_animDelayStart = 1000; + constexpr uint32_t g_animDelayScroll = 500; + constexpr uint32_t g_animDelayEnd = 1000; + + Lcd::Lcd(Editor& _editor) : m_editor(_editor) + { + m_label = _editor.findComponentT<juce::Label>("PatchName"); + setText("---"); + + m_onProgramChanged.set(_editor.getN2xController().onProgramChanged, [this] + { + onProgramChanged(); + }); + + m_onPartChanged.set(_editor.getN2xController().onCurrentPartChanged, [this](const uint8_t&) + { + onProgramChanged(); + }); + } + + void Lcd::setText(const std::string& _text) + { + if(m_text == _text) + return; + m_text = _text; + + if(m_overrideText.empty()) + onTextChanged(); + } + + void Lcd::timerCallback() + { + stopTimer(); + + switch(m_animState) + { + case AnimState::Start: + if(updateClippedText(getCurrentText(), ++m_currentOffset)) + { + m_animState = AnimState::Scroll; + startTimer(g_animDelayScroll); + } + break; + case AnimState::Scroll: + if(!updateClippedText(getCurrentText(), ++m_currentOffset)) + { + m_animState = AnimState::End; + startTimer(g_animDelayEnd); + } + else + { + startTimer(g_animDelayScroll); + } + break; + case AnimState::End: + m_animState = AnimState::Start; + m_currentOffset = 0; + updateClippedText(getCurrentText(), m_currentOffset); + startTimer(g_animDelayStart); + break; + } + } + + void Lcd::updatePatchName() + { + onProgramChanged(); + } + + void Lcd::setOverrideText(const std::string& _text) + { + std::string t = _text; + if(!t.empty()) + { + while(t.size() < 3) + t = ' ' + t; + } + + if(t == m_overrideText) + return; + m_overrideText = t; + onTextChanged(); + } + + void Lcd::setClippedText(const std::string& _text) + { + if(m_clippedText == _text) + return; + m_clippedText = _text; + + auto t = _text; + + for (char& c : t) + { + if(c == ' ') + c = space; + } + + m_label->setText(t, juce::dontSendNotification); + } + + std::string Lcd::substring(const std::string& _text, uint32_t _offset, uint32_t _len) + { + auto findIndex = [&_text](const uint32_t _off) + { + uint32_t o = _off; + + for(uint32_t i=0; i<_text.size(); ++i) + { + if(_text[i] == '.' && i < o) + ++o; + } + return o; + }; + + const auto start = findIndex(_offset); + const auto end = findIndex(_offset + _len); + + return _text.substr(start, end - start); + } + + bool Lcd::updateClippedText(const std::string& _text, const uint32_t _offset) + { + const auto str = substring(_text, _offset, 3); + if(str.size() < 3) + return false; + setClippedText(str); + return true; + } + + void Lcd::startAnim() + { + m_currentOffset = 0; + m_animState = AnimState::Start; + startTimer(g_animDelayStart); + } + + void Lcd::onTextChanged() + { + updateClippedText(getCurrentText(), 0); + + if(m_clippedText != getCurrentText()) + { + startAnim(); + } + else + { + stopTimer(); + } + } + + void Lcd::onProgramChanged() + { + setText(m_editor.getCurrentPatchName()); + } + + const std::string& Lcd::getCurrentText() const + { + if(m_overrideText.empty()) + return m_text; + return m_overrideText; + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xLcd.h b/source/nord/n2x/n2xJucePlugin/n2xLcd.h @@ -0,0 +1,59 @@ +#pragma once + +#include <string> + +#include "jucePluginLib/event.h" + +#include "juce_events/juce_events.h" + +namespace juce +{ + class Label; +} + +namespace n2xJucePlugin +{ + class Editor; + + class Lcd : juce::Timer + { + public: + enum class AnimState + { + Start, + Scroll, + End + }; + + explicit Lcd(Editor& _editor); + + void setText(const std::string& _text); + + void timerCallback() override; + + void updatePatchName(); + + void setOverrideText(const std::string& _text); + + private: + void setClippedText(const std::string& _text); + static std::string substring(const std::string& _text, uint32_t _offset, uint32_t _len); + bool updateClippedText(const std::string& _text, uint32_t _offset); + void startAnim(); + void onTextChanged(); + void onProgramChanged(); + const std::string& getCurrentText() const; + + Editor& m_editor; + juce::Label* m_label; + std::string m_text; + std::string m_clippedText; + uint32_t m_currentOffset = 0; + AnimState m_animState = AnimState::Start; + + std::string m_overrideText; + + pluginLib::EventListener<> m_onProgramChanged; + pluginLib::EventListener<uint8_t> m_onPartChanged; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xLfo.cpp b/source/nord/n2x/n2xJucePlugin/n2xLfo.cpp @@ -0,0 +1,44 @@ +#include "n2xLfo.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +namespace n2xJucePlugin +{ + Lfo::Lfo(Editor& _editor, const uint8_t _index) + : m_editor(_editor) + , m_index(_index) + , m_slider(_editor.findComponentT<juce::Slider>(_index ? "PerfLfo2SyncA" : "PerfLfo1SyncA")) + { + m_onCurrentPartChanged.set(_editor.getN2xController().onCurrentPartChanged, [this](const uint8_t&) + { + bind(); + }); + + bind(); + } + + std::string Lfo::getSyncMultiParamName(const uint8_t _part, const uint8_t _lfoIndex) + { + return std::string("PerfLfo") + std::to_string(_lfoIndex+1) + "Sync" + static_cast<char>('A' + _part); + } + + void Lfo::bind() const + { + const auto& controller = m_editor.getN2xController(); + + const auto paramName = getSyncMultiParamName(controller.getCurrentPart(), m_index); + + const auto paramIdx = controller.getParameterIndexByName(paramName); + + auto& binding = m_editor.getParameterBinding(); + + binding.unbind(m_slider); + binding.bind(*m_slider, paramIdx, 0); + } + + void Lfo::updateState(const pluginLib::Parameter* _param) const + { + m_slider->setValue(_param->getUnnormalizedValue()); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xLfo.h b/source/nord/n2x/n2xJucePlugin/n2xLfo.h @@ -0,0 +1,29 @@ +#pragma once + +#include "n2xParameterDrivenLed.h" + +namespace juce +{ + class Slider; +} + +namespace n2xJucePlugin +{ + class Lfo + { + public: + Lfo(Editor& _editor, uint8_t _index); + + static std::string getSyncMultiParamName(uint8_t _part, uint8_t _lfoIndex); + + private: + void bind() const; + void updateState(const pluginLib::Parameter* _param) const; + + Editor& m_editor; + const uint8_t m_index; + juce::Slider* m_slider; + + pluginLib::EventListener<uint8_t> m_onCurrentPartChanged; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xMasterVolume.cpp b/source/nord/n2x/n2xJucePlugin/n2xMasterVolume.cpp @@ -0,0 +1,33 @@ +#include "n2xMasterVolume.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +namespace n2xJucePlugin +{ + MasterVolume::MasterVolume(const Editor& _editor) : m_editor(_editor), m_volume(_editor.findComponentT<juce::Slider>("MasterVolume")) + { + m_volume->setRange(0.0f, 255.0f); + + uint8_t currentValue; + + if(_editor.getN2xController().getKnobState(currentValue, n2x::KnobType::MasterVol)) + m_volume->setValue(currentValue, juce::dontSendNotification); + else + m_volume->setValue(255.0f, juce::dontSendNotification); + + m_volume->onValueChange = [this] + { + const auto sysex = n2x::State::createKnobSysex(n2x::KnobType::MasterVol, static_cast<uint8_t>(m_volume->getValue())); + + m_editor.getN2xController().sendSysEx(sysex); + }; + + m_onKnobChanged.set(_editor.getN2xController().onKnobChanged, [this](const n2x::KnobType& _type, const unsigned char& _value) + { + if(_type != n2x::KnobType::MasterVol) + return; + m_volume->setValue(_value, juce::dontSendNotification); + }); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xMasterVolume.h b/source/nord/n2x/n2xJucePlugin/n2xMasterVolume.h @@ -0,0 +1,31 @@ +#pragma once + +#include "jucePluginLib/event.h" + +namespace n2x +{ + enum class KnobType; +} + +namespace juce +{ + class Slider; +} + +namespace n2xJucePlugin +{ + class Editor; + + class MasterVolume + { + public: + explicit MasterVolume(const Editor& _editor); + + private: + const Editor& m_editor; + + juce::Slider* m_volume; + + pluginLib::EventListener<n2x::KnobType, uint8_t> m_onKnobChanged; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xOctLed.cpp b/source/nord/n2x/n2xJucePlugin/n2xOctLed.cpp @@ -0,0 +1,28 @@ +#include "n2xOctLed.h" + +#include "n2xController.h" + +namespace n2xJucePlugin +{ + OctLed::OctLed(Editor& _editor) : ParameterDrivenLed(_editor, "O2Pitch_LED", "O2Pitch") + { + bind(); + } + + bool OctLed::updateToggleState(const pluginLib::Parameter* _parameter) const + { + const auto v = _parameter->getUnnormalizedValue(); + const bool active = v / 12 * 12 == v; + return active; + } + + void OctLed::onClick(pluginLib::Parameter* _targetParameter, bool _toggleState) + { + const auto v = _targetParameter->getUnnormalizedValue(); + auto newV = v + 6; + newV /= 12; + newV *= 12; + if(newV != v) + _targetParameter->setUnnormalizedValueNotifyingHost(newV, pluginLib::Parameter::Origin::Ui); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xOctLed.h b/source/nord/n2x/n2xJucePlugin/n2xOctLed.h @@ -0,0 +1,15 @@ +#pragma once + +#include "n2xParameterDrivenLed.h" + +namespace n2xJucePlugin +{ + class OctLed : ParameterDrivenLed + { + public: + explicit OctLed(Editor& _editor); + protected: + bool updateToggleState(const pluginLib::Parameter* _parameter) const override; + void onClick(pluginLib::Parameter* _targetParameter, bool _toggleState) override; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xParameterDrivenLed.cpp b/source/nord/n2x/n2xJucePlugin/n2xParameterDrivenLed.cpp @@ -0,0 +1,59 @@ +#include "n2xParameterDrivenLed.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +#include "juce_gui_basics/juce_gui_basics.h" + +namespace n2xJucePlugin +{ + ParameterDrivenLed::ParameterDrivenLed(Editor& _editor, const std::string& _component, std::string _parameter, uint8_t _part/* = CurrentPart*/) + : m_editor(_editor) + , m_parameterName(std::move(_parameter)) + , m_led(_editor.findComponentT<juce::Button>(_component)) + , m_part(_part) + { + auto& c = _editor.getN2xController(); + + m_onCurrentPartChanged.set(c.onCurrentPartChanged, [this](const uint8_t&) + { + bind(); + }); + + m_led->onClick = [this] + { + if(!m_param) + return; + onClick(m_param, m_led->getToggleState()); + }; + } + + void ParameterDrivenLed::updateState(juce::Button& _target, const pluginLib::Parameter* _source) const + { + _target.setToggleState(updateToggleState(_source), juce::dontSendNotification); + } + + void ParameterDrivenLed::bind() + { + const auto& c = m_editor.getN2xController(); + + m_param = c.getParameter(m_parameterName, m_part == CurrentPart ? c.getCurrentPart() : m_part); + + m_onParamChanged.set(m_param, [this](const pluginLib::Parameter* _parameter) + { + updateStateFromParameter(_parameter); + }); + + updateStateFromParameter(m_param); + } + + void ParameterDrivenLed::disableClick() const + { + m_led->setInterceptsMouseClicks(false, false); + } + + void ParameterDrivenLed::updateStateFromParameter(const pluginLib::Parameter* _parameter) const + { + updateState(*m_led, _parameter); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xParameterDrivenLed.h b/source/nord/n2x/n2xJucePlugin/n2xParameterDrivenLed.h @@ -0,0 +1,44 @@ +#pragma once + +#include "jucePluginLib/parameterlistener.h" + +#include <string> + +namespace juce +{ + class Button; +} + +namespace n2xJucePlugin +{ + class Editor; + + class ParameterDrivenLed + { + public: + static constexpr uint8_t CurrentPart = 0xff; + + explicit ParameterDrivenLed(Editor& _editor, const std::string& _component, std::string _parameter, uint8_t _part = CurrentPart); + virtual ~ParameterDrivenLed() = default; + + protected: + virtual void updateState(juce::Button& _target, const pluginLib::Parameter* _source) const; + virtual bool updateToggleState(const pluginLib::Parameter* _parameter) const = 0; + virtual void onClick(pluginLib::Parameter* _targetParameter, bool _toggleState) {} + + void bind(); + void disableClick() const; + + private: + void updateStateFromParameter(const pluginLib::Parameter* _parameter) const; + + Editor& m_editor; + const std::string m_parameterName; + juce::Button* m_led; + const uint8_t m_part; + + pluginLib::ParameterListener m_onParamChanged; + pluginLib::EventListener<uint8_t> m_onCurrentPartChanged; + pluginLib::Parameter* m_param = nullptr; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPart.cpp b/source/nord/n2x/n2xJucePlugin/n2xPart.cpp @@ -0,0 +1,139 @@ +#include "n2xPart.h" + +#include "n2xController.h" +#include "n2xEditor.h" +#include "n2xLfo.h" + +namespace n2xJucePlugin +{ + Part::Part(Editor& _editor, const std::string& _name, const ButtonStyle _buttonStyle) : PartButton(_editor, _name, _buttonStyle), m_editor(_editor) + { + } + + void Part::onClick() + { + m_editor.getN2xController().setCurrentPart(getPart()); + } + + void Part::mouseDown(const juce::MouseEvent& _e) + { + if(!_e.mods.isPopupMenu()) + { + PartButton<DrawableButton>::mouseDown(_e); + return; + } + + juce::PopupMenu menu; + + auto& controller = m_editor.getN2xController(); + + // Midi Channel + { + juce::PopupMenu menuChannel; + + const auto mp = static_cast<n2x::MultiParam>(n2x::MultiParam::SlotAMidiChannel + getPart()); + const auto name = std::string("PerfMidiChannel") + static_cast<char>('A' + getPart()); + auto* param = controller.getParameter(name, 0); + const auto ch = param->getUnnormalizedValue(); + + for(uint8_t c=0; c<16; ++c) + { + menuChannel.addItem((std::string("Channel ") + std::to_string(c+1)).c_str(), true, c == ch, [param, c] + { + param->setUnnormalizedValueNotifyingHost(c, pluginLib::Parameter::Origin::Ui); + }); + } + + menuChannel.addSeparator(); + menuChannel.addItem("Off", true, ch >= 16, [param] + { + param->setUnnormalizedValueNotifyingHost(16, pluginLib::Parameter::Origin::Ui); + }); + + menu.addSubMenu("Midi Channel", menuChannel); + } + + // Output Mode + { + juce::PopupMenu menuOut; + + constexpr auto mp = n2x::MultiParam::OutModeABCD; + auto o = controller.getMultiParameter(mp); + auto out = o; + + if(getPart() >= 2) + out >>= 4; + else + out &= 0xf; + + auto setMode = [this, &controller, mp, o](uint8_t _mode) + { + auto newO = o; + if(getPart() < 2) + { + newO &= 0xf0; + newO |= _mode; + } + else + { + newO &= 0x0f; + newO |= _mode << 4u; + } + controller.setMultiParameter(mp, newO); + }; + + if(getPart() < 2) + { + menuOut.addItem("A & B Mono/Stereo", true, out == 0, [setMode, mp] { setMode(0); }); + menuOut.addItem("A & B Mono" , true, out == 1, [setMode, mp] { setMode(1); }); + menuOut.addItem("A & B Alternating", true, out == 2, [setMode, mp] { setMode(2); }); + menuOut.addItem("A to A / B to B" , true, out == 3, [setMode, mp] { setMode(3); }); + } + else + { + menuOut.addItem("Same as A & B " , true, out == 0, [setMode, mp] { setMode(0); }); + menuOut.addItem("C & D Mono/Stereo" , true, out == 1, [setMode, mp] { setMode(1); }); + menuOut.addItem("C & D Mono" , true, out == 2, [setMode, mp] { setMode(2); }); + menuOut.addItem("C & D Alternating" , true, out == 3, [setMode, mp] { setMode(3); }); + menuOut.addItem("C to C / D to D" , true, out == 4, [setMode, mp] { setMode(4); }); + } + + menu.addSubMenu("Output Mode", menuOut); + } + + // LFO Sync + { + juce::PopupMenu lfoA; + juce::PopupMenu lfoB; + + auto createSyncMenu = [this](juce::PopupMenu& _menu, uint8_t _lfoIndex) + { + const auto paramName = Lfo::getSyncMultiParamName(getPart(), _lfoIndex); + auto* param = m_editor.getN2xController().getParameter(paramName, 0); + const auto v = param->getUnnormalizedValue(); + + auto createEntry = [&_menu, param, v](const char* _name, const uint8_t _v) + { + _menu.addItem(_name, true, _v == v, [param, _v] + { + param->setUnnormalizedValueNotifyingHost(_v, pluginLib::Parameter::Origin::Ui); + }); + }; + + const auto& desc = param->getDescription(); + const auto& range = desc.range; + + for(auto i=range.getStart(); i <= range.getEnd(); ++i) + createEntry(desc.valueList.valueToText(i).c_str(), i); + }; + + createSyncMenu(lfoA, 0); + createSyncMenu(lfoB, 1); + + menu.addSubMenu("LFO 1 Sync", lfoA); + menu.addSubMenu("LFO 2 Sync", lfoB); + } + + menu.showMenuAsync({}); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPart.h b/source/nord/n2x/n2xJucePlugin/n2xPart.h @@ -0,0 +1,20 @@ +#pragma once + +#include "jucePluginEditorLib/partbutton.h" + +namespace n2xJucePlugin +{ + class Editor; + + class Part : public jucePluginEditorLib::PartButton<juce::DrawableButton> + { + public: + Part(Editor& _editor, const std::string& _name, ButtonStyle _buttonStyle); + + void onClick() override; + + void mouseDown(const juce::MouseEvent& _e) override; + private: + Editor& m_editor; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPartLed.cpp b/source/nord/n2x/n2xJucePlugin/n2xPartLed.cpp @@ -0,0 +1,33 @@ +#include "n2xPartLed.h" + +#include "jucePluginLib/parameter.h" + +namespace n2xJucePlugin +{ + namespace + { + std::string getName(const uint8_t _slot) + { + return std::string("PerfMidiChannel") + static_cast<char>('A' + _slot); + } + } + + PartLed::PartLed(Editor& _editor, const uint8_t _slot) : ParameterDrivenLed(_editor, getName(_slot), getName(_slot), 0) + , m_slot(_slot) + { + bind(); + disableClick(); + } + + void PartLed::updateState(juce::Button& _target, const pluginLib::Parameter* _source) const + { + ParameterDrivenLed::updateState(_target, _source); + const auto ch = _source->getUnnormalizedValue(); + _target.setAlpha(ch > 0 && ch <= 15 ? 0.5f : 1.0f); + } + + bool PartLed::updateToggleState(const pluginLib::Parameter* _parameter) const + { + return _parameter->getUnnormalizedValue() <= 15; + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPartLed.h b/source/nord/n2x/n2xJucePlugin/n2xPartLed.h @@ -0,0 +1,19 @@ +#pragma once + +#include "n2xParameterDrivenLed.h" + +namespace n2xJucePlugin +{ + class PartLed : ParameterDrivenLed + { + public: + PartLed(Editor& _editor, uint8_t _slot); + + protected: + void updateState(juce::Button& _target, const pluginLib::Parameter* _source) const override; + bool updateToggleState(const pluginLib::Parameter* _parameter) const override; + + private: + const uint8_t m_slot; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xParts.cpp b/source/nord/n2x/n2xJucePlugin/n2xParts.cpp @@ -0,0 +1,45 @@ +#include "n2xParts.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +namespace n2xJucePlugin +{ + Parts::Parts(Editor& _editor): m_editor(_editor) + { + m_parts[0] = _editor.findComponentT<Part>("PerfSlotActiveA"); + m_parts[1] = _editor.findComponentT<Part>("PerfSlotActiveB"); + m_parts[2] = _editor.findComponentT<Part>("PerfSlotActiveC"); + m_parts[3] = _editor.findComponentT<Part>("PerfSlotActiveD"); + + m_partLeds[0].reset(new PartLed(_editor, 0)); + m_partLeds[1].reset(new PartLed(_editor, 1)); + m_partLeds[2].reset(new PartLed(_editor, 2)); + m_partLeds[3].reset(new PartLed(_editor, 3)); + + for(uint8_t p=0; p<static_cast<uint8_t>(m_parts.size()); ++p) + { + auto* part = m_parts[p]; + part->initalize(p); + } + + onCurrentPartChanged.set(_editor.getN2xController().onCurrentPartChanged, [this](const uint8_t& _part) + { + setCurrentPart(_part); + }); + + setCurrentPart(0); + } + + void Parts::setCurrentPart(const uint8_t _part) const + { + juce::MessageManager::callAsync([this, _part] + { + for(uint8_t p=0; p<static_cast<uint8_t>(m_parts.size()); ++p) + { + auto* part = m_parts[p]; + part->setToggleState(_part == p, juce::dontSendNotification); + } + }); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xParts.h b/source/nord/n2x/n2xJucePlugin/n2xParts.h @@ -0,0 +1,27 @@ +#pragma once + +#include "n2xPart.h" +#include "n2xPartLed.h" + +#include "jucePluginLib/event.h" + +namespace n2xJucePlugin +{ + class Editor; + + class Parts + { + public: + explicit Parts(Editor& _editor); + + private: + void setCurrentPart(uint8_t _part) const; + + Editor& m_editor; + + std::array<Part*,4> m_parts; + std::array<std::unique_ptr<PartLed>,4> m_partLeds; + pluginLib::EventListener<uint8_t> onCurrentPartChanged; + + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPatchManager.cpp b/source/nord/n2x/n2xJucePlugin/n2xPatchManager.cpp @@ -0,0 +1,195 @@ +#include "n2xPatchManager.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +#include "juce_cryptography/hashing/juce_MD5.h" + +#include "n2xLib/n2xmiditypes.h" + +namespace n2xJucePlugin +{ + constexpr char g_performancePrefixes[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'L' }; + + static constexpr std::initializer_list<jucePluginEditorLib::patchManager::GroupType> g_groupTypes = + { + jucePluginEditorLib::patchManager::GroupType::Favourites, + jucePluginEditorLib::patchManager::GroupType::LocalStorage, + jucePluginEditorLib::patchManager::GroupType::DataSources, + }; + + PatchManager::PatchManager(Editor& _editor, Component* _root, const juce::File& _dir) + : jucePluginEditorLib::patchManager::PatchManager(_editor, _root, _dir, g_groupTypes) + , m_editor(_editor) + , m_controller(_editor.getN2xController()) + { + setTagTypeName(pluginLib::patchDB::TagType::CustomA, "Patch Type"); + startLoaderThread(); + addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomA); + } + + PatchManager::~PatchManager() + { + stopLoaderThread(); + } + + bool PatchManager::requestPatchForPart(pluginLib::patchDB::Data& _data, const uint32_t _part) + { + if(_part < m_controller.getPartCount()) + _data = m_controller.createSingleDump(n2x::SysexByte::SingleDumpBankA, 0, static_cast<uint8_t>(_part)); + else + _data = m_controller.createMultiDump(n2x::SysexByte::MultiDumpBankA, 0); + return !_data.empty(); + } + + bool PatchManager::loadRomData(pluginLib::patchDB::DataList& _results, uint32_t _bank, uint32_t _program) + { + return false; + } + + pluginLib::patchDB::PatchPtr PatchManager::initializePatch(pluginLib::patchDB::Data&& _sysex) + { + if(!isValidPatchDump(_sysex)) + return {}; + + const auto bank = _sysex[n2x::SysexIndex::IdxMsgType]; + const auto program = _sysex[n2x::SysexIndex::IdxMsgSpec]; + const auto isSingle = _sysex.size() == n2x::g_singleDumpSize; + + auto p = std::make_shared<pluginLib::patchDB::Patch>(); + + p->tags.add(pluginLib::patchDB::TagType::CustomA, isSingle ? "Program" : "Performance"); + + if(isSingle) + { + const auto distRmSync = n2x::State::getSingleParam(_sysex, n2x::SingleParam::Distortion, 0); + if(distRmSync & 1) + p->tags.add(pluginLib::patchDB::TagType::Tag, "Sync"); + if(distRmSync & 2) + p->tags.add(pluginLib::patchDB::TagType::Tag, "RingMod"); + if(distRmSync & (1<<4)) + p->tags.add(pluginLib::patchDB::TagType::Tag, "Distortion"); + + if(n2x::State::getSingleParam(_sysex, n2x::SingleParam::Unison, 0)) + p->tags.add(pluginLib::patchDB::TagType::Tag, "Unison"); + + const auto voiceMode = n2x::State::getSingleParam(_sysex, n2x::SingleParam::VoiceMode, 0); + if(voiceMode == 2) + p->tags.add(pluginLib::patchDB::TagType::Tag, "Poly"); + else if(voiceMode == 1) + p->tags.add(pluginLib::patchDB::TagType::Tag, "Legato"); + else + p->tags.add(pluginLib::patchDB::TagType::Tag, "Mono"); + } + + p->name = getPatchName(_sysex); + p->sysex = std::move(_sysex); + p->program = program; + p->bank = bank; + + const juce::MD5 md5(p->sysex.data() + n2x::g_sysexHeaderSize, p->sysex.size() - n2x::g_sysexContainerSize); + static_assert(sizeof(juce::MD5) >= sizeof(pluginLib::patchDB::PatchHash)); + memcpy(p->hash.data(), md5.getChecksumDataArray(), std::size(p->hash)); + + return p; + } + + pluginLib::patchDB::Data PatchManager::prepareSave(const pluginLib::patchDB::PatchPtr& _patch) const + { + auto d = _patch->sysex; + + d[n2x::SysexIndex::IdxMsgType] = static_cast<uint8_t>(_patch->bank); + d[n2x::SysexIndex::IdxMsgSpec] = static_cast<uint8_t>(_patch->program); + + return d; + } + + uint32_t PatchManager::getCurrentPart() const + { + return m_controller.getCurrentPart(); + } + + bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch) + { + return activatePatch(_patch, getCurrentPart()); + } + + bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _part) + { + if(!m_controller.activatePatch(_patch->sysex, _part)) + return false; + + m_editor.onPatchActivated(_patch, _part); + return true; + } + + bool PatchManager::parseFileData(pluginLib::patchDB::DataList& _results, const pluginLib::patchDB::Data& _data) + { + return jucePluginEditorLib::patchManager::PatchManager::parseFileData(_results, _data); + } + + std::string PatchManager::getPatchName(const pluginLib::patchDB::Data& _sysex) + { + if(!isValidPatchDump(_sysex)) + return {}; + + const auto isSingle = _sysex.size() == n2x::g_singleDumpSize; + + const auto bank = _sysex[n2x::SysexIndex::IdxMsgType]; + const auto program = _sysex[n2x::SysexIndex::IdxMsgSpec]; + + char name[128]{0}; + + auto getBankChar = [&]() -> char + { + if(isSingle) + { + if(bank == n2x::SingleDumpBankEditBuffer) + return 'e'; + return static_cast<char>('0' + bank - n2x::SysexByte::SingleDumpBankA); + } + if(bank == n2x::MultiDumpBankEditBuffer) + return 'e'; + + return static_cast<char>('0' + bank - n2x::SysexByte::MultiDumpBankA); + }; + + if(isSingle) + { + (void)snprintf(name, sizeof(name), "%c.%02d", getBankChar(), program); + } + else + { + (void)snprintf(name, sizeof(name), "%c.%c%01d", getBankChar(), g_performancePrefixes[(program/10)%std::size(g_performancePrefixes)], program % 10); + } + + return name; + } + + bool PatchManager::isValidPatchDump(const pluginLib::patchDB::Data& _sysex) + { + const auto isSingle = _sysex.size() == n2x::g_singleDumpSize; + const auto isMulti = _sysex.size() == n2x::g_multiDumpSize; + + if(!isSingle && !isMulti) + return false; + + const auto deviceId = _sysex[n2x::SysexIndex::IdxDevice]; + const auto bank = _sysex[n2x::SysexIndex::IdxMsgType]; + const auto program = _sysex[n2x::SysexIndex::IdxMsgSpec]; + + if(deviceId > 15) + return false; + + if(program > n2x::g_programsPerBank) + return false; + + if(isSingle && (bank < n2x::SysexByte::SingleDumpBankEditBuffer || bank > (n2x::SysexByte::SingleDumpBankEditBuffer + n2x::g_singleBankCount))) + return false; + + if(isMulti && (bank < n2x::SysexByte::MultiDumpBankEditBuffer || bank > (n2x::SysexByte::MultiDumpBankEditBuffer + n2x::g_multiBankCount))) + return false; + + return true; + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPatchManager.h b/source/nord/n2x/n2xJucePlugin/n2xPatchManager.h @@ -0,0 +1,33 @@ +#pragma once + +#include "jucePluginEditorLib/patchmanager/patchmanager.h" + +namespace n2xJucePlugin +{ + class Editor; + class Controller; + + class PatchManager : public jucePluginEditorLib::patchManager::PatchManager + { + public: + PatchManager(Editor& _editor, Component* _root, const juce::File& _dir); + ~PatchManager() override; + + // PatchManager overrides + bool requestPatchForPart(pluginLib::patchDB::Data& _data, uint32_t _part) override; + bool loadRomData(pluginLib::patchDB::DataList& _results, uint32_t _bank, uint32_t _program) override; + pluginLib::patchDB::PatchPtr initializePatch(pluginLib::patchDB::Data&& _sysex) override; + pluginLib::patchDB::Data prepareSave(const pluginLib::patchDB::PatchPtr& _patch) const override; + uint32_t getCurrentPart() const override; + bool activatePatch(const pluginLib::patchDB::PatchPtr& _patch) override; + bool activatePatch(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _part) override; + bool parseFileData(pluginLib::patchDB::DataList& _results, const pluginLib::patchDB::Data& _data) override; + + static std::string getPatchName(const pluginLib::patchDB::Data& _sysex); + static bool isValidPatchDump(const pluginLib::patchDB::Data& _sysex); + + private: + Editor& m_editor; + Controller& m_controller; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPluginEditorState.cpp b/source/nord/n2x/n2xJucePlugin/n2xPluginEditorState.cpp @@ -0,0 +1,46 @@ +#include "n2xPluginEditorState.h" + +#include "n2xEditor.h" +#include "n2xPluginProcessor.h" + +#include "synthLib/os.h" + +namespace n2xJucePlugin +{ + const std::vector<PluginEditorState::Skin> g_includedSkins = + { + {"N2x", "n2xTrancy.json", ""}, + }; + + PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor) : jucePluginEditorLib::PluginEditorState(_processor, _processor.getController(), g_includedSkins) + { + loadDefaultSkin(); + } + + void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) + { + jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); + + auto& p = m_processor; + + const auto gain = static_cast<int>(std::roundf(p.getOutputGain())); + + juce::PopupMenu gainMenu; + + gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); + gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); + gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); + + _menu.addSubMenu("Output Gain", gainMenu); + } + + bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, const bool _enabled) + { + return jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); + } + + jucePluginEditorLib::Editor* PluginEditorState::createEditor(const Skin& _skin) + { + return new n2xJucePlugin::Editor(m_processor, m_parameterBinding, _skin.folder, _skin.jsonFilename); + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPluginEditorState.h b/source/nord/n2x/n2xJucePlugin/n2xPluginEditorState.h @@ -0,0 +1,23 @@ +#pragma once + +#include "jucePluginEditorLib/pluginEditorState.h" + +namespace juce +{ + class Component; +} + +namespace n2xJucePlugin +{ + class AudioPluginAudioProcessor; + + class PluginEditorState : public jucePluginEditorLib::PluginEditorState + { + public: + explicit PluginEditorState(AudioPluginAudioProcessor& _processor); + void initContextMenu(juce::PopupMenu& _menu) override; + bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; + private: + jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.cpp b/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.cpp @@ -0,0 +1,67 @@ +#include "n2xPluginProcessor.h" + +#include "n2xController.h" +#include "n2xPluginEditorState.h" + +#include "jucePluginLib/processor.h" + +#include "n2xLib/n2xdevice.h" + +#include "synthLib/deviceException.h" + +namespace +{ + juce::PropertiesFile::Options getOptions() + { + juce::PropertiesFile::Options opts; + opts.applicationName = "DSP56300EmulatorNodalRed"; + opts.filenameSuffix = ".settings"; + opts.folderName = "DSP56300EmulatorNodalRed"; + opts.osxLibrarySubFolder = "Application Support/DSP56300EmulatorNodalRed"; + return opts; + } +} + +namespace n2xJucePlugin +{ + class Controller; + + AudioPluginAudioProcessor::AudioPluginAudioProcessor() : + Processor(BusesProperties() + .withOutput("Out AB", juce::AudioChannelSet::stereo(), true) + .withOutput("Out CD", juce::AudioChannelSet::stereo(), true) + , getOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect}) + { + getController(); + const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); + Processor::setLatencyBlocks(latencyBlocks); + } + + AudioPluginAudioProcessor::~AudioPluginAudioProcessor() + { + destroyEditorState(); + } + + jucePluginEditorLib::PluginEditorState* AudioPluginAudioProcessor::createEditorState() + { + return new PluginEditorState(*this); + } + + synthLib::Device* AudioPluginAudioProcessor::createDevice() + { + auto* d = new n2x::Device(); + if(!d->isValid()) + throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A firmware rom (512k .bin) is required, but was not found."); + return d; + } + + pluginLib::Controller* AudioPluginAudioProcessor::createController() + { + return new n2xJucePlugin::Controller(*this); + } +} + +juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() +{ + return new n2xJucePlugin::AudioPluginAudioProcessor(); +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.h b/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.h @@ -0,0 +1,20 @@ +#pragma once + +#include "jucePluginEditorLib/pluginProcessor.h" + +namespace n2xJucePlugin +{ + class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor + { + public: + AudioPluginAudioProcessor(); + ~AudioPluginAudioProcessor() override; + + jucePluginEditorLib::PluginEditorState* createEditorState() override; + synthLib::Device* createDevice() override; + pluginLib::Controller* createController() override; + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xVmMap.cpp b/source/nord/n2x/n2xJucePlugin/n2xVmMap.cpp @@ -0,0 +1,75 @@ +#include "n2xVmMap.h" + +#include "n2xController.h" +#include "n2xEditor.h" + +namespace n2xJucePlugin +{ + constexpr const char* g_postfix = "Sens"; + constexpr float g_enabledAlpha = 0.5f; + + VmMap::VmMap(Editor& _editor, pluginLib::ParameterBinding& _binding) + : m_editor(_editor) + , m_binding(_binding) + , m_btVmMap(_editor.findComponentT<juce::Button>("VMMAP")) + { + const auto& c = _editor.getN2xController(); + const auto& descs = c.getParameterDescriptions().getDescriptions(); + + for (const auto& desc : descs) + { + uint32_t idx; + + if(c.getParameterDescriptions().getIndexByName(idx, desc.name + g_postfix)) + m_paramNames.insert(desc.name); + } + + m_btVmMap->onClick = [this] + { + toggleVmMap(m_btVmMap->getToggleState()); + }; + } + + void VmMap::toggleVmMap(const bool _enabled) + { + if(m_enabled == _enabled) + return; + + m_enabled = _enabled; + + const auto& controller = m_editor.getN2xController(); + + const auto part = controller.getCurrentPart(); + + for (const auto& paramName : m_paramNames) + { + const auto paramIdxDefault = controller.getParameterIndexByName(paramName); + const auto paramIdxVm = controller.getParameterIndexByName(paramName + g_postfix); + + const auto* paramDefault = controller.getParameter(paramName, part); + const auto* paramVm = controller.getParameter(paramName + g_postfix, part); + + auto* comp = m_binding.getBoundComponent(paramDefault); + + if(comp == nullptr) + comp = m_binding.getBoundComponent(paramVm); + + if(comp != nullptr) + { + m_binding.unbind(comp); + } + else + { + assert(false && "bound component not found"); + return; + } + + m_binding.unbind(paramDefault); + m_binding.unbind(paramVm); + + m_binding.bind(*comp, _enabled ? paramIdxVm : paramIdxDefault, pluginLib::ParameterBinding::CurrentPart); + + comp->setAlpha(_enabled ? g_enabledAlpha : 1.0f); + } + } +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xVmMap.h b/source/nord/n2x/n2xJucePlugin/n2xVmMap.h @@ -0,0 +1,35 @@ +#pragma once + +#include <string> +#include <set> + +namespace pluginLib +{ + class ParameterBinding; +} + +namespace juce +{ + class Button; +} + +namespace n2xJucePlugin +{ + class Editor; + + class VmMap + { + public: + explicit VmMap(Editor& _editor, pluginLib::ParameterBinding& _binding); + + private: + void toggleVmMap(bool _enabled); + + Editor& m_editor; + pluginLib::ParameterBinding& m_binding; + + std::set<std::string> m_paramNames; + juce::Button* m_btVmMap; + bool m_enabled = false; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/parameterDescriptions_n2x.json b/source/nord/n2x/n2xJucePlugin/parameterDescriptions_n2x.json @@ -0,0 +1,586 @@ +{ + "parameterdescriptiondefaults": + { + "isPublic":true, + "isBipolar":false, + "toText":"unsignedZero", + "name":"", + "class":"", + "min":0, + "max":127, + "isBool":false, + "isDiscrete":false, + "page":0, + "step":0 + }, + "parameterdescriptions": + [ + {"index":0 , "name":"O2Pitch", "min":0, "max":120, "default":60, "isBipolar":true, "toText":"oscPitch"}, + {"index":1 , "name":"O2PitchFine", "default":64, "toText":"signed"}, + {"index":2 , "name":"Mix", "default":64, "toText":"signed"}, + {"index":3 , "name":"Cutoff", "default":127}, + {"index":4 , "name":"Resonance"}, + {"index":5 , "name":"FilterEnvAmount"}, + {"index":6 , "name":"PW"}, + {"index":7 , "name":"FmDepth"}, + {"index":8 , "name":"FilterEnvA"}, + {"index":9 , "name":"FilterEnvD"}, + {"index":10, "name":"FilterEnvS", "default":127}, + {"index":11, "name":"FilterEnvR"}, + {"index":12, "name":"AmpEnvA"}, + {"index":13, "name":"AmpEnvD"}, + {"index":14, "name":"AmpEnvS", "default":127}, + {"index":15, "name":"AmpEnvR"}, + {"index":16, "name":"Portamento"}, + {"index":17, "name":"Gain", "default":127}, + {"index":18, "name":"ModEnvA"}, + {"index":19, "name":"ModEnvD"}, + {"index":20, "name":"ModEnvLevel", "default":64, "toText":"signed"}, + {"index":21, "name":"Lfo1Rate"}, + {"index":22, "name":"Lfo1Level"}, + {"index":23, "name":"Lfo2Rate"}, + {"index":24, "name":"ArpRange"}, + + // TODO these should have a min value of -128 + {"index":25, "name":"O2PitchSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":26, "name":"O2PitchFineSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":27, "name":"MixSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":28, "name":"CutoffSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":29, "name":"ResonanceSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":30, "name":"FilterEnvAmountSens", "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":31, "name":"PWSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":32, "name":"FmDepthSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":33, "name":"FilterEnvASens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":34, "name":"FilterEnvDSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":35, "name":"FilterEnvSSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":36, "name":"FilterEnvRSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":37, "name":"AmpEnvASens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":38, "name":"AmpEnvDSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":39, "name":"AmpEnvSSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":40, "name":"AmpEnvRSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":41, "name":"PortamentoSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":42, "name":"GainSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":43, "name":"ModEnvASens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":44, "name":"ModEnvDSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":45, "name":"ModEnvLevelSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":46, "name":"Lfo1RateSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":47, "name":"Lfo1LevelSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":48, "name":"Lfo2RateSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + {"index":49, "name":"ArpRangeSens" , "min":-128, "max":127, "toText":"signed256", "isBipolar":true}, + + {"index":50, "name":"O1Waveform", "min":0, "max":3, "isDiscrete":true}, + {"index":51, "name":"O2Waveform", "min":0, "max":3, "isDiscrete":true}, + {"index":52, "name":"Sync", "min":0, "max":1, "isBool":true}, + {"index":52, "name":"RingMod", "min":0, "max":1, "isBool":true}, + {"index":100, "name":"Distortion", "min":0, "max":1, "isBool":true}, + {"index":53, "name":"FilterType", "min":0, "max":4, "isDiscrete":true}, + {"index":54, "name":"O2Keytrack", "min":0, "max":1, "isBool":true}, + {"index":55, "name":"FilterKeytrack", "min":0, "max":3, "isBool":true}, + {"index":56, "name":"Lfo1Waveform", "min":0, "max":4, "isDiscrete":true}, + {"index":57, "name":"Lfo1Dest", "min":0, "max":4, "isDiscrete":true}, + {"index":58, "name":"VoiceMode", "min":0, "max":2, "isDiscrete":true}, + {"index":59, "name":"ModWheelDest", "min":0, "max":4, "isDiscrete":true}, + {"index":60, "name":"Unison", "min":0, "max":1, "isBool":true}, + {"index":61, "name":"ModEnvDest", "min":0, "max":4, "isDiscrete":true}, + {"index":62, "name":"Auto", "min":0, "max":4, "isDiscrete":true}, + {"index":63, "name":"FilterVelocity", "min":0, "max":1, "isDiscrete":true}, + {"index":64, "name":"OctaveShift", "min":0, "max":4, "default":2, "isDiscrete":true}, + {"index":65, "name":"Lfo2Dest", "min":0, "max":8, "isDiscrete":true}, + + // MULTI aka Performance + {"class":"NonPartSensitive", "page":10, "index":264, "name":"PerfMidiChannelA", "min":0, "max":16, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":265, "name":"PerfMidiChannelB", "min":0, "max":16, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":266, "name":"PerfMidiChannelC", "min":0, "max":16, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":267, "name":"PerfMidiChannelD", "min":0, "max":16, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":268, "name":"PerfLfo1SyncA", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":269, "name":"PerfLfo1SyncB", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":270, "name":"PerfLfo1SyncC", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":271, "name":"PerfLfo1SyncD", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":272, "name":"PerfLfo2SyncA", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":273, "name":"PerfLfo2SyncB", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":274, "name":"PerfLfo2SyncC", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":275, "name":"PerfLfo2SyncD", "min":0, "max":7, "isDiscrete":true, "toText":"lfoSync"}, + {"class":"NonPartSensitive", "page":10, "index":276, "name":"PerfFilterEnvTriggerA", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":277, "name":"PerfFilterEnvTriggerB", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":278, "name":"PerfFilterEnvTriggerC", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":279, "name":"PerfFilterEnvTriggerD", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":280, "name":"PerfFilterEnvTriggerMidiChannelA", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":281, "name":"PerfFilterEnvTriggerMidiChannelB", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":282, "name":"PerfFilterEnvTriggerMidiChannelC", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":283, "name":"PerfFilterEnvTriggerMidiChannelD", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":284, "name":"PerfFilterEnvTriggerNoteNumberA", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":285, "name":"PerfFilterEnvTriggerNoteNumberB", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":286, "name":"PerfFilterEnvTriggerNoteNumberC", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":287, "name":"PerfFilterEnvTriggerNoteNumberD", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":288, "name":"PerfAmpEnvTriggerA", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":289, "name":"PerfAmpEnvTriggerB", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":290, "name":"PerfAmpEnvTriggerC", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":291, "name":"PerfAmpEnvTriggerD", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":292, "name":"PerfAmpEnvTriggerMidiChannelA", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":293, "name":"PerfAmpEnvTriggerMidiChannelB", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":294, "name":"PerfAmpEnvTriggerMidiChannelC", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":295, "name":"PerfAmpEnvTriggerMidiChannelD", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":296, "name":"PerfAmpEnvTriggerNoteNumberA", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":297, "name":"PerfAmpEnvTriggerNoteNumberB", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":298, "name":"PerfAmpEnvTriggerNoteNumberC", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":299, "name":"PerfAmpEnvTriggerNoteNumberD", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":300, "name":"PerfMorfTriggerA", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":301, "name":"PerfMorfTriggerB", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":302, "name":"PerfMorfTriggerC", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":303, "name":"PerfMorfTriggerD", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":304, "name":"PerfMorfTriggerMidiChannelA", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":305, "name":"PerfMorfTriggerMidiChannelB", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":306, "name":"PerfMorfTriggerMidiChannelC", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":307, "name":"PerfMorfTriggerMidiChannelD", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":308, "name":"PerfMorfTriggerNoteNumberA", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":309, "name":"PerfMorfTriggerNoteNumberB", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":310, "name":"PerfMorfTriggerNoteNumberC", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":311, "name":"PerfMorfTriggerNoteNumberD", "min":23, "max":127, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":312, "name":"PerfBendRange", "min":0, "max":8, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":313, "name":"PerfUnisonDetune", "min":0, "max":8, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":314, "name":"PerfOutModeABCD", "min":0, "max":8, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":315, "name":"PerfGlobalMidiChannel", "min":0, "max":15, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":316, "name":"PerfProgramChange", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":317, "name":"PerfMidiControl", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":318, "name":"PerfMasterTune", "min":-99, "max":99, "toText":"masterTune"}, + {"class":"NonPartSensitive", "page":10, "index":319, "name":"PerfPedalType", "min":0, "max":2, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":320, "name":"PerfLocalControl", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":321, "name":"PerfKeyboardOctaveShift", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":322, "name":"PerfSelectedSlot", "min":0, "max":3, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":323, "name":"PerfArpMidiOut", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":324, "name":"PerfSlotActiveA", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":325, "name":"PerfSlotActiveB", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":326, "name":"PerfSlotActiveC", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":327, "name":"PerfSlotActiveD", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":328, "name":"PerfProgramSelectA", "min":0, "max":98, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":329, "name":"PerfProgramSelectB", "min":0, "max":98, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":330, "name":"PerfProgramSelectC", "min":0, "max":98, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":331, "name":"PerfProgramSelectD", "min":0, "max":98, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":332, "name":"PerfBankSelectA", "min":0, "max":3, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":333, "name":"PerfBankSelectB", "min":0, "max":3, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":334, "name":"PerfBankSelectC", "min":0, "max":3, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":335, "name":"PerfBankSelectD", "min":0, "max":3, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":336, "name":"PerfChannelPressureAmountA", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":337, "name":"PerfChannelPressureAmountB", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":338, "name":"PerfChannelPressureAmountC", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":339, "name":"PerfChannelPressureAmountD", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":340, "name":"PerfChannelPressureDestA", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":341, "name":"PerfChannelPressureDestB", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":342, "name":"PerfChannelPressureDestC", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":343, "name":"PerfChannelPressureDestD", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":344, "name":"PerfExpressionPedalAmountA", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":345, "name":"PerfExpressionPedalAmountB", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":346, "name":"PerfExpressionPedalAmountC", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":347, "name":"PerfExpressionPedalAmountD", "min":0, "max":7, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":348, "name":"PerfExpressionPedalDestA", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":349, "name":"PerfExpressionPedalDestB", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":350, "name":"PerfExpressionPedalDestC", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":351, "name":"PerfExpressionPedalDestD", "min":0, "max":4, "isDiscrete":true}, + {"class":"NonPartSensitive", "page":10, "index":352, "name":"PerfKeyboardSplit", "min":0, "max":1, "isBool":true}, + {"class":"NonPartSensitive", "page":10, "index":352, "name":"PerfSplitPoint", "min":0, "max":127, "isDiscrete":true} + ], + "regions": + [ + { "id":"oscA", "name": "Oscillator 1", + "parameters":["O1Waveform", "FmDepth"] + }, + { "id":"oscB", "name": "Oscillator 2", + "parameters":["O2Pitch", "O2PitchFine", "O2Waveform", "O2Keytrack"] + }, + { "id":"oscCommon", "name": "Oscillator Common", + "parameters":["PW", "Sync", "RingMod"] + }, + { "id":"filter", "name": "Filter", + "parameters":["Cutoff", "Resonance", "FilterEnvAmount", "FilterType", "FilterKeytrack", "FilterVelocity", "Distortion"] + }, + { "id":"filterEnv", "name": "Filter Env", + "parameters":["FilterEnvA", "FilterEnvD", "FilterEnvS", "FilterEnvR"] + }, + { "id":"ampEnv", "name": "Amp Env", + "parameters":["AmpEnvA", "AmpEnvD", "AmpEnvS", "AmpEnvR"] + }, + { "id":"modEnv", "name": "Mod Env", + "parameters":["ModEnvA", "ModEnvD", "ModEnvLevel"] + }, + { "id":"lfoA", "name": "LFO 1", + "parameters":["Lfo1Rate", "Lfo1Level", "Lfo1Dest", "Lfo1Waveform"] + }, + { "id":"lfoB", "name": "LFO 2", + "parameters":["Lfo2Rate", "Lfo2Dest", "ArpRange"] + }, + { "id":"mod", "name": "Modulation", + "parameters":["ModWheelDest", "ModEnvDest"] + }, + { "id":"amp", "name": "Amplifier", + "parameters":["Gain", "Mix"] + }, + { "id":"patch", "name": "Patch Common", + "parameters":["Portamento", "VoiceMode", "Unison", "Auto", "OctaveShift"] + } + ], + "valuelists": + { + "unsignedZero": + [ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", + "120", "121", "122", "123", "124", "125", "126", "127" + ], + "signed": + [ + "-64", "-63", "-62", "-61", "-60", "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", "-49", "-48", + "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", "-29", "-28", + "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", "-9", "-8", + "-7", "-6", "-5", "-4", "-3", "-2", "-1", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63" + ], + "oscPitch": + [ + "-60", "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", "-49", "-48", + "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", "-29", "-28", + "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", "-9", "-8", + "-7", "-6", "-5", "-4", "-3", "-2", "-1", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60" + ], + "signed256": + [ + "-128", "-127", "-126", "-125", "-124", "-123", "-122", "-121", "-120", "-119", "-118", "-117", "-116", "-115", "-114", "-113", "-112", "-111", "-110", "-109", "-108", + "-107", "-106", "-105", "-104", "-103", "-102", "-101", "-100", "-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", "-89", "-88", + "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", "-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", "-69", "-68", + "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", "-49", "-48", + "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", "-29", "-28", + "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", "-9", "-8", + "-7", "-6", "-5", "-4", "-3", "-2", "-1", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", + "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", + "120", "121", "122", "123", "124", "125", "126", "127" + ], + "masterTune": + [ + "-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", "-89", "-88", + "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", "-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", "-69", "-68", + "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", "-49", "-48", + "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", "-29", "-28", + "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", "-9", "-8", + "-7", "-6", "-5", "-4", "-3", "-2", "-1", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99" + ], + "lfoSync": + [ + "Off", "2/1", "1/1", "1/2", "1/4", "1/8", "1/8.", "1/16" + ] + }, + "midipackets": + { + "requestdump": [ + {"type": "byte", "value": "f0"}, + {"type": "byte", "value": "33"}, + {"type": "deviceid"}, + {"type": "byte", "value": "04"}, + {"type": "bank"}, + {"type": "program"}, + {"type": "byte", "value": "f7"} + ], + "singledump": [ + {"type": "byte", "value": "f0"}, + {"type": "byte", "value": "33"}, + {"type": "deviceid"}, + {"type": "byte", "value": "04"}, + {"type": "bank"}, + {"type": "program"}, + + {"type": "param", "name":"O2Pitch" , "mask":"f"}, {"type": "param", "name":"O2Pitch" , "shiftL":4}, + {"type": "param", "name":"O2PitchFine" , "mask":"f"}, {"type": "param", "name":"O2PitchFine" , "shiftL":4}, + {"type": "param", "name":"Mix" , "mask":"f"}, {"type": "param", "name":"Mix" , "shiftL":4}, + {"type": "param", "name":"Cutoff" , "mask":"f"}, {"type": "param", "name":"Cutoff" , "shiftL":4}, + {"type": "param", "name":"Resonance" , "mask":"f"}, {"type": "param", "name":"Resonance" , "shiftL":4}, + {"type": "param", "name":"FilterEnvAmount" , "mask":"f"}, {"type": "param", "name":"FilterEnvAmount" , "shiftL":4}, + {"type": "param", "name":"PW" , "mask":"f"}, {"type": "param", "name":"PW" , "shiftL":4}, + {"type": "param", "name":"FmDepth" , "mask":"f"}, {"type": "param", "name":"FmDepth" , "shiftL":4}, + {"type": "param", "name":"FilterEnvA" , "mask":"f"}, {"type": "param", "name":"FilterEnvA" , "shiftL":4}, + {"type": "param", "name":"FilterEnvD" , "mask":"f"}, {"type": "param", "name":"FilterEnvD" , "shiftL":4}, + {"type": "param", "name":"FilterEnvS" , "mask":"f"}, {"type": "param", "name":"FilterEnvS" , "shiftL":4}, + {"type": "param", "name":"FilterEnvR" , "mask":"f"}, {"type": "param", "name":"FilterEnvR" , "shiftL":4}, + {"type": "param", "name":"AmpEnvA" , "mask":"f"}, {"type": "param", "name":"AmpEnvA" , "shiftL":4}, + {"type": "param", "name":"AmpEnvD" , "mask":"f"}, {"type": "param", "name":"AmpEnvD" , "shiftL":4}, + {"type": "param", "name":"AmpEnvS" , "mask":"f"}, {"type": "param", "name":"AmpEnvS" , "shiftL":4}, + {"type": "param", "name":"AmpEnvR" , "mask":"f"}, {"type": "param", "name":"AmpEnvR" , "shiftL":4}, + {"type": "param", "name":"Portamento" , "mask":"f"}, {"type": "param", "name":"Portamento" , "shiftL":4}, + {"type": "param", "name":"Gain" , "mask":"f"}, {"type": "param", "name":"Gain" , "shiftL":4}, + {"type": "param", "name":"ModEnvA" , "mask":"f"}, {"type": "param", "name":"ModEnvA" , "shiftL":4}, + {"type": "param", "name":"ModEnvD" , "mask":"f"}, {"type": "param", "name":"ModEnvD" , "shiftL":4}, + {"type": "param", "name":"ModEnvLevel" , "mask":"f"}, {"type": "param", "name":"ModEnvLevel" , "shiftL":4}, + {"type": "param", "name":"Lfo1Rate" , "mask":"f"}, {"type": "param", "name":"Lfo1Rate" , "shiftL":4}, + {"type": "param", "name":"Lfo1Level" , "mask":"f"}, {"type": "param", "name":"Lfo1Level" , "shiftL":4}, + {"type": "param", "name":"Lfo2Rate" , "mask":"f"}, {"type": "param", "name":"Lfo2Rate" , "shiftL":4}, + {"type": "param", "name":"ArpRange" , "mask":"f"}, {"type": "param", "name":"ArpRange" , "shiftL":4}, + + {"type": "param", "name":"O2PitchSens" , "mask":"f"}, {"type": "param", "name":"O2PitchSens" , "shiftL":4}, + {"type": "param", "name":"O2PitchFineSens" , "mask":"f"}, {"type": "param", "name":"O2PitchFineSens" , "shiftL":4}, + {"type": "param", "name":"MixSens" , "mask":"f"}, {"type": "param", "name":"MixSens" , "shiftL":4}, + {"type": "param", "name":"CutoffSens" , "mask":"f"}, {"type": "param", "name":"CutoffSens" , "shiftL":4}, + {"type": "param", "name":"ResonanceSens" , "mask":"f"}, {"type": "param", "name":"ResonanceSens" , "shiftL":4}, + {"type": "param", "name":"FilterEnvAmountSens", "mask":"f"}, {"type": "param", "name":"FilterEnvAmountSens", "shiftL":4}, + {"type": "param", "name":"PWSens" , "mask":"f"}, {"type": "param", "name":"PWSens" , "shiftL":4}, + {"type": "param", "name":"FmDepthSens" , "mask":"f"}, {"type": "param", "name":"FmDepthSens" , "shiftL":4}, + {"type": "param", "name":"FilterEnvASens" , "mask":"f"}, {"type": "param", "name":"FilterEnvASens" , "shiftL":4}, + {"type": "param", "name":"FilterEnvDSens" , "mask":"f"}, {"type": "param", "name":"FilterEnvDSens" , "shiftL":4}, + {"type": "param", "name":"FilterEnvSSens" , "mask":"f"}, {"type": "param", "name":"FilterEnvSSens" , "shiftL":4}, + {"type": "param", "name":"FilterEnvRSens" , "mask":"f"}, {"type": "param", "name":"FilterEnvRSens" , "shiftL":4}, + {"type": "param", "name":"AmpEnvASens" , "mask":"f"}, {"type": "param", "name":"AmpEnvASens" , "shiftL":4}, + {"type": "param", "name":"AmpEnvDSens" , "mask":"f"}, {"type": "param", "name":"AmpEnvDSens" , "shiftL":4}, + {"type": "param", "name":"AmpEnvSSens" , "mask":"f"}, {"type": "param", "name":"AmpEnvSSens" , "shiftL":4}, + {"type": "param", "name":"AmpEnvRSens" , "mask":"f"}, {"type": "param", "name":"AmpEnvRSens" , "shiftL":4}, + {"type": "param", "name":"PortamentoSens" , "mask":"f"}, {"type": "param", "name":"PortamentoSens" , "shiftL":4}, + {"type": "param", "name":"GainSens" , "mask":"f"}, {"type": "param", "name":"GainSens" , "shiftL":4}, + {"type": "param", "name":"ModEnvASens" , "mask":"f"}, {"type": "param", "name":"ModEnvASens" , "shiftL":4}, + {"type": "param", "name":"ModEnvDSens" , "mask":"f"}, {"type": "param", "name":"ModEnvDSens" , "shiftL":4}, + {"type": "param", "name":"ModEnvLevelSens" , "mask":"f"}, {"type": "param", "name":"ModEnvLevelSens" , "shiftL":4}, + {"type": "param", "name":"Lfo1RateSens" , "mask":"f"}, {"type": "param", "name":"Lfo1RateSens" , "shiftL":4}, + {"type": "param", "name":"Lfo1LevelSens" , "mask":"f"}, {"type": "param", "name":"Lfo1LevelSens" , "shiftL":4}, + {"type": "param", "name":"Lfo2RateSens" , "mask":"f"}, {"type": "param", "name":"Lfo2RateSens" , "shiftL":4}, + {"type": "param", "name":"ArpRangeSens" , "mask":"f"}, {"type": "param", "name":"ArpRangeSens" , "shiftL":4}, + + {"type": "param", "name":"O1Waveform" , "mask":"f"}, {"type": "param", "name":"O1Waveform" , "shiftL":4}, + {"type": "param", "name":"O2Waveform" , "mask":"f"}, {"type": "param", "name":"O2Waveform" , "shiftL":4}, + + {"type": "param", "name":"Sync" , "mask":1, "shift":0}, + {"type": "param", "name":"RingMod" , "mask":1, "shift":1}, + {"type": "param", "name":"Distortion" , "mask":1, "shift":0}, + + {"type": "param", "name":"FilterType" , "mask":"f"}, {"type": "param", "name":"FilterType" , "shiftL":4}, + {"type": "param", "name":"O2Keytrack" , "mask":"f"}, {"type": "param", "name":"O2Keytrack" , "shiftL":4}, + {"type": "param", "name":"FilterKeytrack" , "mask":"f"}, {"type": "param", "name":"FilterKeytrack" , "shiftL":4}, + {"type": "param", "name":"Lfo1Waveform" , "mask":"f"}, {"type": "param", "name":"Lfo1Waveform" , "shiftL":4}, + {"type": "param", "name":"Lfo1Dest" , "mask":"f"}, {"type": "param", "name":"Lfo1Dest" , "shiftL":4}, + {"type": "param", "name":"VoiceMode" , "mask":"f"}, {"type": "param", "name":"VoiceMode" , "shiftL":4}, + {"type": "param", "name":"ModWheelDest" , "mask":"f"}, {"type": "param", "name":"ModWheelDest" , "shiftL":4}, + {"type": "param", "name":"Unison" , "mask":"f"}, {"type": "param", "name":"Unison" , "shiftL":4}, + {"type": "param", "name":"ModEnvDest" , "mask":"f"}, {"type": "param", "name":"ModEnvDest" , "shiftL":4}, + {"type": "param", "name":"Auto" , "mask":"f"}, {"type": "param", "name":"Auto" , "shiftL":4}, + {"type": "param", "name":"FilterVelocity" , "mask":"f"}, {"type": "param", "name":"FilterVelocity" , "shiftL":4}, + {"type": "param", "name":"OctaveShift" , "mask":"f"}, {"type": "param", "name":"OctaveShift" , "shiftL":4}, + {"type": "param", "name":"Lfo2Dest" , "mask":"f"}, {"type": "param", "name":"Lfo2Dest" , "shiftL":4}, + + {"type":"byte", "value":"f7"} + ], + + "multidump": [ + {"type": "byte", "value": "f0"}, + {"type": "byte", "value": "33"}, + {"type": "deviceid"}, + {"type": "byte", "value": "04"}, + {"type": "bank"}, + {"type": "program"}, + + // 4 * single dump without sysex container = 4 * 66 * 2 parameters + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + {"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"},{"type": "null"}, + + {"type":"param", "name":"PerfMidiChannelA"}, {"type":"param", "name":"PerfMidiChannelA", "shiftL":4}, + {"type":"param", "name":"PerfMidiChannelB"}, {"type":"param", "name":"PerfMidiChannelB", "shiftL":4}, + {"type":"param", "name":"PerfMidiChannelC"}, {"type":"param", "name":"PerfMidiChannelC", "shiftL":4}, + {"type":"param", "name":"PerfMidiChannelD"}, {"type":"param", "name":"PerfMidiChannelD", "shiftL":4}, + {"type":"param", "name":"PerfLfo1SyncA"}, {"type":"param", "name":"PerfLfo1SyncA", "shiftL":4}, + {"type":"param", "name":"PerfLfo1SyncB"}, {"type":"param", "name":"PerfLfo1SyncB", "shiftL":4}, + {"type":"param", "name":"PerfLfo1SyncC"}, {"type":"param", "name":"PerfLfo1SyncC", "shiftL":4}, + {"type":"param", "name":"PerfLfo1SyncD"}, {"type":"param", "name":"PerfLfo1SyncD", "shiftL":4}, + {"type":"param", "name":"PerfLfo2SyncA"}, {"type":"param", "name":"PerfLfo2SyncA", "shiftL":4}, + {"type":"param", "name":"PerfLfo2SyncB"}, {"type":"param", "name":"PerfLfo2SyncB", "shiftL":4}, + {"type":"param", "name":"PerfLfo2SyncC"}, {"type":"param", "name":"PerfLfo2SyncC", "shiftL":4}, + {"type":"param", "name":"PerfLfo2SyncD"}, {"type":"param", "name":"PerfLfo2SyncD", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerA"}, {"type":"param", "name":"PerfFilterEnvTriggerA", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerB"}, {"type":"param", "name":"PerfFilterEnvTriggerB", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerC"}, {"type":"param", "name":"PerfFilterEnvTriggerC", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerD"}, {"type":"param", "name":"PerfFilterEnvTriggerD", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelA"}, {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelA", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelB"}, {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelB", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelC"}, {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelC", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelD"}, {"type":"param", "name":"PerfFilterEnvTriggerMidiChannelD", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberA"}, {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberA", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberB"}, {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberB", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberC"}, {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberC", "shiftL":4}, + {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberD"}, {"type":"param", "name":"PerfFilterEnvTriggerNoteNumberD", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerA"}, {"type":"param", "name":"PerfAmpEnvTriggerA", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerB"}, {"type":"param", "name":"PerfAmpEnvTriggerB", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerC"}, {"type":"param", "name":"PerfAmpEnvTriggerC", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerD"}, {"type":"param", "name":"PerfAmpEnvTriggerD", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelA"}, {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelA", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelB"}, {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelB", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelC"}, {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelC", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelD"}, {"type":"param", "name":"PerfAmpEnvTriggerMidiChannelD", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberA"}, {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberA", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberB"}, {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberB", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberC"}, {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberC", "shiftL":4}, + {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberD"}, {"type":"param", "name":"PerfAmpEnvTriggerNoteNumberD", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerA"}, {"type":"param", "name":"PerfMorfTriggerA", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerB"}, {"type":"param", "name":"PerfMorfTriggerB", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerC"}, {"type":"param", "name":"PerfMorfTriggerC", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerD"}, {"type":"param", "name":"PerfMorfTriggerD", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerMidiChannelA"}, {"type":"param", "name":"PerfMorfTriggerMidiChannelA", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerMidiChannelB"}, {"type":"param", "name":"PerfMorfTriggerMidiChannelB", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerMidiChannelC"}, {"type":"param", "name":"PerfMorfTriggerMidiChannelC", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerMidiChannelD"}, {"type":"param", "name":"PerfMorfTriggerMidiChannelD", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerNoteNumberA"}, {"type":"param", "name":"PerfMorfTriggerNoteNumberA", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerNoteNumberB"}, {"type":"param", "name":"PerfMorfTriggerNoteNumberB", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerNoteNumberC"}, {"type":"param", "name":"PerfMorfTriggerNoteNumberC", "shiftL":4}, + {"type":"param", "name":"PerfMorfTriggerNoteNumberD"}, {"type":"param", "name":"PerfMorfTriggerNoteNumberD", "shiftL":4}, + {"type":"param", "name":"PerfBendRange"}, {"type":"param", "name":"PerfBendRange", "shiftL":4}, + {"type":"param", "name":"PerfUnisonDetune"}, {"type":"param", "name":"PerfUnisonDetune", "shiftL":4}, + {"type":"param", "name":"PerfOutModeABCD"}, {"type":"param", "name":"PerfOutModeABCD", "shiftL":4}, + {"type":"param", "name":"PerfGlobalMidiChannel"}, {"type":"param", "name":"PerfGlobalMidiChannel", "shiftL":4}, + {"type":"param", "name":"PerfProgramChange"}, {"type":"param", "name":"PerfProgramChange", "shiftL":4}, + {"type":"param", "name":"PerfMidiControl"}, {"type":"param", "name":"PerfMidiControl", "shiftL":4}, + {"type":"param", "name":"PerfMasterTune"}, {"type":"param", "name":"PerfMasterTune", "shiftL":4}, + {"type":"param", "name":"PerfPedalType"}, {"type":"param", "name":"PerfPedalType", "shiftL":4}, + {"type":"param", "name":"PerfLocalControl"}, {"type":"param", "name":"PerfLocalControl", "shiftL":4}, + {"type":"param", "name":"PerfKeyboardOctaveShift"}, {"type":"param", "name":"PerfKeyboardOctaveShift", "shiftL":4}, + {"type":"param", "name":"PerfSelectedSlot"}, {"type":"param", "name":"PerfSelectedSlot", "shiftL":4}, + {"type":"param", "name":"PerfArpMidiOut"}, {"type":"param", "name":"PerfArpMidiOut", "shiftL":4}, + {"type":"param", "name":"PerfSlotActiveA"}, {"type":"param", "name":"PerfSlotActiveA", "shiftL":4}, + {"type":"param", "name":"PerfSlotActiveB"}, {"type":"param", "name":"PerfSlotActiveB", "shiftL":4}, + {"type":"param", "name":"PerfSlotActiveC"}, {"type":"param", "name":"PerfSlotActiveC", "shiftL":4}, + {"type":"param", "name":"PerfSlotActiveD"}, {"type":"param", "name":"PerfSlotActiveD", "shiftL":4}, + {"type":"param", "name":"PerfProgramSelectA"}, {"type":"param", "name":"PerfProgramSelectA", "shiftL":4}, + {"type":"param", "name":"PerfProgramSelectB"}, {"type":"param", "name":"PerfProgramSelectB", "shiftL":4}, + {"type":"param", "name":"PerfProgramSelectC"}, {"type":"param", "name":"PerfProgramSelectC", "shiftL":4}, + {"type":"param", "name":"PerfProgramSelectD"}, {"type":"param", "name":"PerfProgramSelectD", "shiftL":4}, + {"type":"param", "name":"PerfBankSelectA"}, {"type":"param", "name":"PerfBankSelectA", "shiftL":4}, + {"type":"param", "name":"PerfBankSelectB"}, {"type":"param", "name":"PerfBankSelectB", "shiftL":4}, + {"type":"param", "name":"PerfBankSelectC"}, {"type":"param", "name":"PerfBankSelectC", "shiftL":4}, + {"type":"param", "name":"PerfBankSelectD"}, {"type":"param", "name":"PerfBankSelectD", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureAmountA"}, {"type":"param", "name":"PerfChannelPressureAmountA", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureAmountB"}, {"type":"param", "name":"PerfChannelPressureAmountB", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureAmountC"}, {"type":"param", "name":"PerfChannelPressureAmountC", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureAmountD"}, {"type":"param", "name":"PerfChannelPressureAmountD", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureDestA"}, {"type":"param", "name":"PerfChannelPressureDestA", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureDestB"}, {"type":"param", "name":"PerfChannelPressureDestB", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureDestC"}, {"type":"param", "name":"PerfChannelPressureDestC", "shiftL":4}, + {"type":"param", "name":"PerfChannelPressureDestD"}, {"type":"param", "name":"PerfChannelPressureDestD", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalAmountA"}, {"type":"param", "name":"PerfExpressionPedalAmountA", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalAmountB"}, {"type":"param", "name":"PerfExpressionPedalAmountB", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalAmountC"}, {"type":"param", "name":"PerfExpressionPedalAmountC", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalAmountD"}, {"type":"param", "name":"PerfExpressionPedalAmountD", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalDestA"}, {"type":"param", "name":"PerfExpressionPedalDestA", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalDestB"}, {"type":"param", "name":"PerfExpressionPedalDestB", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalDestC"}, {"type":"param", "name":"PerfExpressionPedalDestC", "shiftL":4}, + {"type":"param", "name":"PerfExpressionPedalDestD"}, {"type":"param", "name":"PerfExpressionPedalDestD", "shiftL":4}, + {"type":"param", "name":"PerfKeyboardSplit"}, {"type":"param", "name":"PerfKeyboardSplit", "shiftL":4}, + {"type":"param", "name":"PerfSplitPoint"}, {"type":"param", "name":"PerfSplitPoint", "shiftL":4}, + + {"type": "byte", "value": "f7"} + ] + }, + "controllerMap": [ + {"cc": 7, "param": "Gain"}, + {"cc":17, "param": "OctaveShift"}, + {"cc":18, "param": "ModWheelDest"}, + {"cc":16, "param": "Unison"}, + {"cc":15, "param": "VoiceMode"}, + {"cc":65, "param": "Auto"}, + {"cc": 5, "param": "Portamento"}, + {"cc":19, "param": "Lfo1Rate"}, + {"cc":20, "param": "Lfo1Waveform"}, + {"cc":21, "param": "Lfo1Dest"}, + {"cc":22, "param": "Lfo1Level"}, + {"cc":23, "param": "Lfo2Rate"}, + {"cc":24, "param": "Lfo2Dest"}, + {"cc":25, "param": "ArpRange"}, + {"cc":26, "param": "ModEnvA"}, + {"cc":27, "param": "ModEnvD"}, + {"cc":28, "param": "ModEnvDest"}, + {"cc":29, "param": "ModEnvLevel"}, + {"cc":30, "param": "O1Waveform"}, + {"cc":31, "param": "O2Waveform"}, + {"cc":78, "param": "O2Pitch"}, + {"cc":33, "param": "O2PitchFine"}, + {"cc":70, "param": "FmDepth"}, + {"cc":34, "param": "O2Keytrack"}, + {"cc":79, "param": "PW"}, + {"cc":35, "param": "Sync"}, + {"cc":35, "param": "RingMod"}, + {"cc": 8, "param": "Mix"}, + {"cc":73, "param": "AmpEnvA"}, + {"cc":36, "param": "AmpEnvD"}, + {"cc":37, "param": "AmpEnvS"}, + {"cc":72, "param": "AmpEnvR"}, + {"cc":38, "param": "FilterEnvA"}, + {"cc":39, "param": "FilterEnvD"}, + {"cc":40, "param": "FilterEnvS"}, + {"cc":41, "param": "FilterEnvR"}, + {"cc":44, "param": "FilterType"}, + {"cc":74, "param": "Cutoff"}, + {"cc":42, "param": "Resonance"}, + {"cc":43, "param": "FilterEnvAmount"}, + {"cc":45, "param": "FilterVelocity"}, + {"cc":46, "param": "FilterKeytrack"}, + {"cc":80, "param": "Distortion"} + ] +} diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/DSEG14Classic-BoldItalic.ttf b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/DSEG14Classic-BoldItalic.ttf Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/DSEG7Classic-BoldItalic.ttf b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/DSEG7Classic-BoldItalic.ttf Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/assets.cmake b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/assets.cmake @@ -0,0 +1,24 @@ +set(ASSETS_n2xTrancy + ${CMAKE_CURRENT_LIST_DIR}/background.png + ${CMAKE_CURRENT_LIST_DIR}/button_a.png + ${CMAKE_CURRENT_LIST_DIR}/button_b.png + ${CMAKE_CURRENT_LIST_DIR}/button_black.png + ${CMAKE_CURRENT_LIST_DIR}/button_browser.png + ${CMAKE_CURRENT_LIST_DIR}/button_c.png + ${CMAKE_CURRENT_LIST_DIR}/button_d.png +# ${CMAKE_CURRENT_LIST_DIR}/button_extra.png + ${CMAKE_CURRENT_LIST_DIR}/button_main.png + ${CMAKE_CURRENT_LIST_DIR}/button_red.png + ${CMAKE_CURRENT_LIST_DIR}/button_round.png + ${CMAKE_CURRENT_LIST_DIR}/DSEG7Classic-BoldItalic.ttf + ${CMAKE_CURRENT_LIST_DIR}/DSEG14Classic-BoldItalic.ttf + ${CMAKE_CURRENT_LIST_DIR}/h_slider.png + ${CMAKE_CURRENT_LIST_DIR}/knob_big.png + ${CMAKE_CURRENT_LIST_DIR}/panel_1_main.png + ${CMAKE_CURRENT_LIST_DIR}/panel_1_main_arpeggiator.png + ${CMAKE_CURRENT_LIST_DIR}/panel_1_main_lfo2.png + ${CMAKE_CURRENT_LIST_DIR}/panel_2_browser.png +# ${CMAKE_CURRENT_LIST_DIR}/panel_3_extra.png + + ${CMAKE_CURRENT_LIST_DIR}/n2xTrancy.json +) diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/background.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/background.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_a.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_a.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_b.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_b.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_black.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_black.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_browser.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_browser.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_c.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_c.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_d.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_d.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_extra.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_extra.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_main.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_main.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_red.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_red.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_round.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/button_round.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/h_slider.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/h_slider.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/knob_big.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/knob_big.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/n2xTrancy.json b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/n2xTrancy.json @@ -0,0 +1,200 @@ +{ + "name" : "Root", + "root" : { "x" : "0", "y" : "0", "width" : "2275", "height" : "1197", "scale" : "0.5"}, + "tabgroup": { + "name": "pages", + "buttons": ["button_1_main", "button_2_browser"], + "pages": ["page_1_main", "page_2_browser"] + }, + + "children" : + [ + {"name":"bg","image":{"x":"0","y":"0","width":"2275","height":"1197","texture":"background"}}, + + {"name":"button_1_main", "button":{"isToggle":"1","radioGroupId":"42","normalImage":"0","overImage":"1","downImage":"1","normalImageOn":"1","overImageOn":"1","downImageOn":"1","x":"1847","y":"897","width":"184","height":"67.5","texture":"button_main","tileSizeX":"184","tileSizeY":"67.5",}}, + {"name":"button_2_browser", "button":{"isToggle":"1","radioGroupId":"42","normalImage":"0","overImage":"1","downImage":"1","normalImageOn":"1","overImageOn":"1","downImageOn":"1","x":"2026","y":"897","width":"184","height":"67.5","texture":"button_browser","tileSizeX":"184","tileSizeY":"67.5",}}, + + { "name" : "MasterVolume", "rotary" : { }, "spritesheet" : { "x" : "74", "y" : "961", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "PerfUnisonDetune", "parameterAttachment" : { "parameter" : "PerfUnisonDetune" }, "rotary" : { }, "spritesheet" : { "x" : "892", "y" : "960", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "Portamento", "parameterAttachment" : { "parameter" : "Portamento" }, "rotary" : { }, "spritesheet" : { "x" : "715", "y" : "960", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "Auto_LED", "parameterAttachment" : { "parameter" : "Auto"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "685", "y" : "1071", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + { "name" : "VMMAP", "parameterAttachment" : { }, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "41", "y" : "1071", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + + { "name" : "Unison", "parameterAttachment" : { "parameter" : "Unison"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "857", "y" : "881", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + { "name" : "VoiceMode_1", "parameterAttachment" : { "parameter" : "VoiceMode", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "518", "y" : "927", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + { "name" : "VoiceMode_2", "parameterAttachment" : { "parameter" : "VoiceMode", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "518", "y" : "964", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + { "name" : "VoiceMode_3", "parameterAttachment" : { "parameter" : "VoiceMode", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "518", "y" : "1001", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + + { "name" : "PerfMidiChannelA", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "203", "y" : "1063", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" } }, + { "name" : "PerfMidiChannelB", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "276", "y" : "1063", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" } }, + { "name" : "PerfMidiChannelC", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "349", "y" : "1063", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" } }, + { "name" : "PerfMidiChannelD", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "422", "y" : "1063", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" } }, + + { "name" : "PerfSlotActiveA", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "218", "y" : "1023", "width" : "74", "height" : "56", "texture" : "button_a", "tileSizeX" : "74", "tileSizeY" : "56" } }, + { "name" : "PerfSlotActiveB", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "291", "y" : "1023", "width" : "74", "height" : "56", "texture" : "button_b", "tileSizeX" : "74", "tileSizeY" : "56" } }, + { "name" : "PerfSlotActiveC", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "364", "y" : "1023", "width" : "74", "height" : "56", "texture" : "button_c", "tileSizeX" : "74", "tileSizeY" : "56" } }, + { "name" : "PerfSlotActiveD", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "437", "y" : "1023", "width" : "74", "height" : "56", "texture" : "button_d", "tileSizeX" : "74", "tileSizeY" : "56" } }, + + { "name" : "OctaveShift_1", "parameterAttachment" : { "parameter" : "OctaveShift", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1053", "y" : "938", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "OctaveShift_2", "parameterAttachment" : { "parameter" : "OctaveShift", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1053", "y" : "975", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "OctaveShift_3", "parameterAttachment" : { "parameter" : "OctaveShift", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1053", "y" : "1012", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "OctaveShift_4", "parameterAttachment" : { "parameter" : "OctaveShift", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1053", "y" : "1049", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "OctaveShift_5", "parameterAttachment" : { "parameter" : "OctaveShift", "value":4}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1053", "y" : "1087", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + + { "name" : "ModWheelDest_1", "parameterAttachment" : { "parameter" : "ModWheelDest", "value":4}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1222", "y" : "938", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "ModWheelDest_2", "parameterAttachment" : { "parameter" : "ModWheelDest", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1222", "y" : "975", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "ModWheelDest_3", "parameterAttachment" : { "parameter" : "ModWheelDest", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1222", "y" : "1012", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "ModWheelDest_4", "parameterAttachment" : { "parameter" : "ModWheelDest", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1222", "y" : "1049", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + { "name" : "ModWheelDest_5", "parameterAttachment" : { "parameter" : "ModWheelDest", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1222", "y" : "1087", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150 } }, + + {"name":"button_store", "button":{"isToggle":"0","radioGroupId":"1","normalImage":"0","overImage":"0","downImage":"1","normalImageOn":"1","overImageOn":"1","downImageOn":"1","x":"560","y":"1065","width":"99","height":"47","texture":"button_red","tileSizeX":"99","tileSizeY":"47",}}, + + {"name":"PresetNext", "button":{"normalImage":"0","overImage":"0","downImage":"1","disabledImage":"0","normalImageOn":"0","overImageOn":"0","downImageOn":"0","disabledImageOn":"0","x":"225","y":"910","width":"99","height":"47","texture":"button_black","tileSizeX":"99","tileSizeY":"47"}}, + {"name":"PresetPrev", "button":{"normalImage":"0","overImage":"0","downImage":"1","disabledImage":"0","normalImageOn":"0","overImageOn":"0","downImageOn":"0","disabledImageOn":"0","x":"225","y":"957","width":"99","height":"47","texture":"button_black","tileSizeX":"99","tileSizeY":"47"}}, + + {"name":"PatchName", "label":{"text":"666", "color" : "FF0000FF", "textHeight":"42","alignH":"L","alignV":"C","fontFile":"DSEG7Classic-BoldItalic","fontName":"Register","x":"380","y":"925","width":"150","height":"62"}}, + + {"name":"FocusedParameterValue", "label":{"text":"-42", "color" : "FF0000FF","textHeight":"23","alignH":"R","alignV":"C","fontFile":"DSEG14Classic-BoldItalic","fontName":"Register","x":"260","y":"1145","width":"256.7","height":"52"}}, + {"name":"FocusedParameterName", "label":{"text":"Osc", "color" : "FF0000FF","textHeight":"23","alignH":"L","alignV":"C","fontFile":"DSEG14Classic-BoldItalic","fontName":"Register","x":"40","y":"1145","width":"600","height":"52"}}, + + { + "name" : "page_1_main", + "image" : { "x" : "60", "y" : "60", "width" : "2153", "height" : "803", "texture" : "panel_1_main" }, + "children" : [ + { "name" : "Gain", "parameterAttachment" : { "parameter" : "Gain" }, "rotary" : { }, "spritesheet" : { "x" : "1987", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "Attack", "parameterAttachment" : { "parameter" : "AmpEnvA" }, "rotary" : { }, "spritesheet" : { "x" : "1344", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "AmpEnvD", "parameterAttachment" : { "parameter" : "AmpEnvD" }, "rotary" : { }, "spritesheet" : { "x" : "1504", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "AmpEnvS", "parameterAttachment" : { "parameter" : "AmpEnvS" }, "rotary" : { }, "spritesheet" : { "x" : "1664", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "AmpEnvR", "parameterAttachment" : { "parameter" : "AmpEnvR" }, "rotary" : { }, "spritesheet" : { "x" : "1824", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + + { "name" : "FmDepth", "parameterAttachment" : { "parameter" : "FmDepth" }, "rotary" : { }, "spritesheet" : { "x" : "719", "y" : "377", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "O1Waveform_1", "parameterAttachment" : { "parameter" : "O1Waveform", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "684", "y" : "79", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O1Waveform_2", "parameterAttachment" : { "parameter" : "O1Waveform", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "684", "y" : "116", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O1Waveform_3", "parameterAttachment" : { "parameter" : "O1Waveform", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "684", "y" : "153", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O1Waveform_4", "parameterAttachment" : { "parameter" : "O1Waveform", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "684", "y" : "190", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + + { "name" : "O2PitchFine", "parameterAttachment" : { "parameter" : "O2PitchFine" }, "rotary" : { }, "spritesheet" : { "x" : "923", "y" : "377", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "O2Pitch", "parameterAttachment" : { "parameter" : "O2Pitch" }, "rotary" : { }, "spritesheet" : { "x" : "921", "y" : "107", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "O2Pitch_LED", "button" : { "isToggle" : "0", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "942", "y" : "27", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" } }, + + { "name" : "O2Waveform_1", "parameterAttachment" : { "parameter" : "O2Waveform", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1101", "y" : "79", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O2Waveform_2", "parameterAttachment" : { "parameter" : "O2Waveform", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1101", "y" : "116", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O2Waveform_3", "parameterAttachment" : { "parameter" : "O2Waveform", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1101", "y" : "153", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O2Waveform_4", "parameterAttachment" : { "parameter" : "O2Waveform", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1101", "y" : "190", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "O2Keytrack", "parameterAttachment" : { "parameter" : "O2Keytrack"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1082", "y" : "398", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":120 } }, + + { "name" : "Mix", "parameterAttachment" : { "parameter" : "Mix" }, "rotary" : { }, "spritesheet" : { "x" : "1102", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "PW", "parameterAttachment" : { "parameter" : "PW" }, "rotary" : { }, "spritesheet" : { "x" : "719", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "Sync", "parameterAttachment" : { "parameter" : "Sync"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "888", "y" : "602", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + { "name" : "RingMod", "parameterAttachment" : { "parameter" : "RingMod"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "888", "y" : "565", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + + { "name" : "Cutoff", "parameterAttachment" : { "parameter" : "Cutoff" }, "rotary" : { }, "spritesheet" : { "x" : "1344", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "Resonance", "parameterAttachment" : { "parameter" : "Resonance" }, "rotary" : { }, "spritesheet" : { "x" : "1504", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "FilterEnvAmount", "parameterAttachment" : { "parameter" : "FilterEnvAmount" }, "rotary" : { }, "spritesheet" : { "x" : "1664", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "FilterEnvA", "parameterAttachment" : { "parameter" : "FilterEnvA" }, "rotary" : { }, "spritesheet" : { "x" : "1344", "y" : "377", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "FilterEnvD", "parameterAttachment" : { "parameter" : "FilterEnvD" }, "rotary" : { }, "spritesheet" : { "x" : "1505", "y" : "377", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "FilterEnvS", "parameterAttachment" : { "parameter" : "FilterEnvS" }, "rotary" : { }, "spritesheet" : { "x" : "1666", "y" : "377", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "FilterEnvR", "parameterAttachment" : { "parameter" : "FilterEnvR" }, "rotary" : { }, "spritesheet" : { "x" : "1826", "y" : "377", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "FilterVelocity", "parameterAttachment" : { "parameter" : "FilterVelocity"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1627", "y" : "548", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + + { "name" : "FilterKeytrack_1", "parameterAttachment" : { "parameter" : "FilterKeytrack", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1791", "y" : "592", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + { "name" : "FilterKeytrack_2", "parameterAttachment" : { "parameter" : "FilterKeytrack", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1791", "y" : "629", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + { "name" : "FilterKeytrack_3", "parameterAttachment" : { "parameter" : "FilterKeytrack", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1791", "y" : "666", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + { "name" : "FilterKeytrack_4", "parameterAttachment" : { "parameter" : "FilterKeytrack", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1791", "y" : "705", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + + { "name" : "FilterType_1", "parameterAttachment" : { "parameter" : "FilterType", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1954", "y" : "369", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "FilterType_2", "parameterAttachment" : { "parameter" : "FilterType", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1954", "y" : "406", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "FilterType_3", "parameterAttachment" : { "parameter" : "FilterType", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1954", "y" : "443", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "FilterType_4", "parameterAttachment" : { "parameter" : "FilterType", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1954", "y" : "480", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "FilterType_5", "parameterAttachment" : { "parameter" : "FilterType", "value":4}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1954", "y" : "517", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + + { "name" : "Distortion", "parameterAttachment" : { "parameter" : "Distortion"}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "1954", "y" : "705", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":150} }, + + { "name" : "PerfLfo1SyncA", "parameterAttachment" : { "parameter" : "PerfLfo1SyncA" }, "rotary" : { "style" : "LinearHorizontal"}, "spritesheet" : { "x" : "500", "y" : "2", "width" : "140", "height" : "40", "texture" : "h_slider", "tileSizeX" : "300", "tileSizeY" : "70" } }, + + { "name" : "Lfo1Rate", "parameterAttachment" : { "parameter" : "Lfo1Rate" }, "rotary" : { }, "spritesheet" : { "x" : "36", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "Lfo1Level", "parameterAttachment" : { "parameter" : "Lfo1Level" }, "rotary" : { }, "spritesheet" : { "x" : "508", "y" : "88", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + + { "name" : "Lfo1Waveform_1", "parameterAttachment" : { "parameter" : "Lfo1Waveform", "value":4}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "50", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Waveform_2", "parameterAttachment" : { "parameter" : "Lfo1Waveform", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "87", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Waveform_3", "parameterAttachment" : { "parameter" : "Lfo1Waveform", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "124", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Waveform_4", "parameterAttachment" : { "parameter" : "Lfo1Waveform", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "161", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Waveform_5", "parameterAttachment" : { "parameter" : "Lfo1Waveform", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "198", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + + { "name" : "Lfo1Dest_1", "parameterAttachment" : { "parameter" : "Lfo1Dest", "value":4}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "313", "y" : "50", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Dest_2", "parameterAttachment" : { "parameter" : "Lfo1Dest", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "313", "y" : "87", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Dest_3", "parameterAttachment" : { "parameter" : "Lfo1Dest", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "313", "y" : "124", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Dest_4", "parameterAttachment" : { "parameter" : "Lfo1Dest", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "313", "y" : "161", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo1Dest_5", "parameterAttachment" : { "parameter" : "Lfo1Dest", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "313", "y" : "198", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + + { + "name" : "LFO2", + "image" : { "x" : "0", "y" : "267", "width" : "661", "height" : "268", "texture" : "panel_1_main_lfo2" }, + "condition": { "enableOnParameter": "Lfo2Dest", "enableOnValues": "3,4,7" }, + "children" : [ + { "name" : "Lfo2Dest_LFO2_OSC1+2", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":4}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "45", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo2Dest_LFO2_AMP", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":3}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "82", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + { "name" : "Lfo2Dest_LFO2_FILTER", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":7}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "119", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":100 } }, + ] + }, + + { + "name" : "ARP", + "image" : { "x" : "0", "y" : "267", "width" : "661", "height" : "268", "texture" : "panel_1_main_arpeggiator" }, + "condition": { "enableOnParameter": "Lfo2Dest", "enableOnValues": "0,1,2,5,6,8" }, + "children" : [ + { "name" : "Lfo2Dest_LFO2_ECHO", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":6}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "45", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "Lfo2Dest_LFO2_UP", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "82", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "Lfo2Dest_LFO2_DOWN", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "119", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "Lfo2Dest_LFO2_UP_DOWN", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "156", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "Lfo2Dest_LFO2_RANDOM", "parameterAttachment" : { "parameter" : "Lfo2Dest", "value":5}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "162", "y" : "193", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + ] + }, + + { "name" : "PerfLfo2SyncA", "parameterAttachment" : { "parameter" : "PerfLfo2SyncA" }, "rotary" : { "style" : "LinearHorizontal"}, "spritesheet" : { "x" : "352", "y" : "269", "width" : "140", "height" : "40", "texture" : "h_slider", "tileSizeX" : "300", "tileSizeY" : "70" } }, + { "name" : "ArpEnabled", "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "485", "y" : "254", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70" , "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":90} }, + + { "name" : "Lfo2Rate", "parameterAttachment" : { "parameter" : "Lfo2Rate" }, "rotary" : { }, "spritesheet" : { "x" : "36", "y" : "350", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "ArpRange", "parameterAttachment" : { "parameter" : "ArpRange" }, "rotary" : { }, "spritesheet" : { "x" : "508", "y" : "350", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + + { "name" : "ModEnvA", "parameterAttachment" : { "parameter" : "ModEnvA" }, "rotary" : { }, "spritesheet" : { "x" : "36", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "ModEnvD", "parameterAttachment" : { "parameter" : "ModEnvD" }, "rotary" : { }, "spritesheet" : { "x" : "196", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + { "name" : "ModEnvLevel", "parameterAttachment" : { "parameter" : "ModEnvLevel" }, "rotary" : { }, "spritesheet" : { "x" : "508", "y" : "619", "width" : "113", "height" : "113", "texture" : "knob_big", "tileSizeX" : "113", "tileSizeY" : "113" } }, + + { "name" : "ModEnvDest_1", "parameterAttachment" : { "parameter" : "ModEnvDest", "value":1}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "323", "y" : "611", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "ModEnvDest_2", "parameterAttachment" : { "parameter" : "ModEnvDest", "value":2}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "323", "y" : "648", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + { "name" : "ModEnvDest_3", "parameterAttachment" : { "parameter" : "ModEnvDest", "value":0}, "button" : { "isToggle" : "1", "normalImage" : "0", "overImage" : "0", "downImage" : "0", "normalImageOn" : "1", "overImageOn" : "1", "downImageOn" : "1", "x" : "323", "y" : "685", "width" : "70", "height" : "70", "texture" : "button_round", "tileSizeX" : "70", "tileSizeY" : "70", "hitOffsetT":20, "hitOffsetB":-20, "hitOffsetR":130 } }, + ] + }, + { + "name" : "page_2_browser", + "image" : { "x" : "60", "y" : "60", "width" : "2153", "height" : "803", "texture" : "panel_2_browser" }, + "children" : [ + { + "name" : "ContainerPatchManager", + "component" : { + "x" : "15", + "y" : "45", + "width" : "2128", + "height" : "745" + } + }, + ] + }, + + { "name" : "DonateInfo", "hyperlinkbutton" : {"url" : "https://paypal.me/dsp56300", "text" : "Donate", "textHeight" : "28", "color" : "FFFFFFFF", "x" : "1630", "y" : "1150", "width" : "160", "height" : "40"} }, + { "name" : "VersionNumber", "label" : { "text" : "1.3.2.13", "textHeight" : "28", "color" : "FFFFFFFF", "alignH" : "L", "alignV" : "C", "x" : "725", "y" : "1155", "width" : "200", "height" : "32" }}, + { "name" : "RomSelector", "combobox" : { "text" : "rom.bin", "textHeight" : "28", "color" : "FFFFFFFF", "alignH" : "L", "alignV" : "C", "x" : "995", "y" : "1154", "width" : "400", "height" : "32", "offsetR" : "-60", "tooltip" : "Copy a valid ROM file with file extension .bin next to this plugin" }}, + + { + "name" : "FocusedParameterTooltip", + "label" : { + "text" : "", "textHeight" : "30", "fontFile":"DSEG14Classic-BoldItalic","fontName":"Register", + "color" : "FF0000FF", "backgroundColor" : "220303ff", + "alignH" : "C", "alignV" : "C", + "x" : "1385", "y" : "1225", "width" : "80", "height" : "45" + }, + "componentProperties" : {"offsetY" : "25"} + } + ] +} diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main_arpeggiator.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main_arpeggiator.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main_lfo2.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_1_main_lfo2.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_2_browser.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_2_browser.png Binary files differ. diff --git a/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_3_extra.png b/source/nord/n2x/n2xJucePlugin/skins/n2xTrancy/panel_3_extra.png Binary files differ. diff --git a/source/nord/n2x/n2xLib/CMakeLists.txt b/source/nord/n2x/n2xLib/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.10) + +project(n2xLib) + +add_library(n2xLib STATIC) + +set(SOURCES + n2xdevice.cpp n2xdevice.h + n2xdsp.cpp n2xdsp.h + n2xflash.cpp n2xflash.h + n2xfrontpanel.cpp n2xfrontpanel.h + n2xhardware.cpp n2xhardware.h + n2xhdi08.cpp n2xhdi08.h + n2xmc.cpp n2xmc.h + n2xmiditypes.h + n2xrom.cpp n2xrom.h + n2xromdata.cpp n2xromdata.h + n2xromloader.cpp n2xromloader.h + n2xstate.cpp n2xstate.h + n2xtypes.h +) + +target_sources(n2xLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(n2xLib PUBLIC synthLib 68kEmu hardwareLib) + +if(DSP56300_DEBUGGER) + target_link_libraries(n2xLib PUBLIC dsp56kDebugger) +endif() + +set_property(TARGET n2xLib PROPERTY FOLDER "N2x") + +target_include_directories(n2xLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/nord/n2x/n2xLib/n2xdevice.cpp b/source/nord/n2x/n2xLib/n2xdevice.cpp @@ -0,0 +1,107 @@ +#include "n2xdevice.h" + +#include "n2xhardware.h" +#include "n2xtypes.h" + +namespace n2x +{ + Device::Device() : m_state(&m_hardware) + { + } + + const std::string& Device::getRomFilename() const + { + return m_hardware.getRomFilename(); + } + + float Device::getSamplerate() const + { + return g_samplerate; + } + + bool Device::isValid() const + { + return m_hardware.isValid(); + } + + bool Device::getState(std::vector<uint8_t>& _state, synthLib::StateType _type) + { + return m_state.getState(_state); + } + + bool Device::setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) + { + return m_state.setState(_state); + } + + uint32_t Device::getChannelCountIn() + { + return 0; + } + + uint32_t Device::getChannelCountOut() + { + return 4; + } + + bool Device::setDspClockPercent(const uint32_t _percent) + { + bool res = m_hardware.getDSPA().getPeriph().getEsaiClock().setSpeedPercent(_percent); + res &= m_hardware.getDSPB().getPeriph().getEsaiClock().setSpeedPercent(_percent); + return res; + } + + uint32_t Device::getDspClockPercent() const + { + return const_cast<Hardware&>(m_hardware).getDSPA().getPeriph().getEsaiClock().getSpeedPercent(); + } + + uint64_t Device::getDspClockHz() const + { + return const_cast<Hardware&>(m_hardware).getDSPA().getPeriph().getEsaiClock().getSpeedInHz(); + } + + void Device::readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) + { + m_hardware.getMidi().read(m_midiOutBuffer); + m_midiParser.write(m_midiOutBuffer); + m_midiOutBuffer.clear(); + m_midiParser.getEvents(_midiOut); + + for (const auto& midiOut : _midiOut) + { + if(midiOut.sysex.empty()) + LOG("TX " << HEXN(midiOut.a,2) << ' ' << HEXN(midiOut.b,2) << ' ' << HEXN(midiOut.c, 2)); + else + LOG("TX Sysex of size " << midiOut.sysex.size()); + m_state.receive(midiOut); + } + } + + void Device::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) + { + m_hardware.processAudio(_outputs, static_cast<uint32_t>(_samples), getExtraLatencySamples()); + m_numSamplesProcessed += static_cast<uint32_t>(_samples); + +// m_hardware.setButtonState(ButtonType::OscSync, (m_numSamplesProcessed & 65535) > 2048); + } + + bool Device::sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) + { + if(_ev.sysex.empty()) + { + m_state.receive(_response, _ev); + auto e = _ev; + e.offset += m_numSamplesProcessed + getExtraLatencySamples(); + m_hardware.sendMidi(e); + } + else + { + if(m_state.receive(_response, _ev)) + return true; + + m_hardware.sendMidi(_ev); + } + return true; + } +} diff --git a/source/nord/n2x/n2xLib/n2xdevice.h b/source/nord/n2x/n2xLib/n2xdevice.h @@ -0,0 +1,41 @@ +#pragma once + +#include "n2xhardware.h" +#include "n2xstate.h" + +#include "wLib/wDevice.h" + +namespace n2x +{ + class Hardware; + + class Device : public synthLib::Device + { + public: + Device(); + + const std::string& getRomFilename() const; + + float getSamplerate() const override; + bool isValid() const override; + bool getState(std::vector<uint8_t>& _state, synthLib::StateType _type) override; + bool setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) override; + uint32_t getChannelCountIn() override; + uint32_t getChannelCountOut() override; + bool setDspClockPercent(uint32_t _percent) override; + uint32_t getDspClockPercent() const override; + uint64_t getDspClockHz() const override; + + protected: + void readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) override; + void processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) override; + bool sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) override; + + private: + Hardware m_hardware; + State m_state; + std::vector<uint8_t> m_midiOutBuffer; + synthLib::MidiBufferParser m_midiParser; + uint32_t m_numSamplesProcessed = 0; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xdsp.cpp b/source/nord/n2x/n2xLib/n2xdsp.cpp @@ -0,0 +1,223 @@ +#include "n2xdsp.h" + +#include "n2xhardware.h" + +#include "dsp56kDebugger/debugger.h" + +#include "dsp56kEmu/dspthread.h" + +#include "mc68k/hdi08.h" + +namespace n2x +{ + static constexpr dsp56k::TWord g_xyMemSize = 0x010000; + static constexpr dsp56k::TWord g_externalMemAddr = 0x008000; + static constexpr dsp56k::TWord g_pMemSize = 0x004000; + + namespace + { + dsp56k::DefaultMemoryValidator g_memValidator; + } + DSP::DSP(Hardware& _hw, mc68k::Hdi08& _hdiUc, const uint32_t _index) + : m_hardware(_hw) + , m_hdiUC(_hdiUc) + , m_index(_index) + , m_name(_index ? "DSP B" : "DSP A") + , m_memory(g_memValidator, g_pMemSize, g_xyMemSize, g_externalMemAddr) + , m_dsp(m_memory, &m_periphX, &m_periphNop) + , m_haltDSP(m_dsp) + , m_boot(m_dsp) + { + if(!_hw.isValid()) + return; + + { + auto& clock = m_periphX.getEsaiClock(); + auto& esai = m_periphX.getEsai(); + + clock.setExternalClockFrequency(3'333'333); // schematic claims 1 MHz but we measured 10/3 Mhz + + constexpr auto samplerate = g_samplerate; + constexpr auto clockMultiplier = 2; + + clock.setSamplerate(samplerate * clockMultiplier); + + clock.setClockSource(dsp56k::EsaiClock::ClockSource::Cycles); + + if(m_index == 0) + { + // DSP A = chip U2 = left on the schematic + // Sends its audio to DSP B at twice the sample rate, it sends four words per frame + clock.setEsaiDivider(&esai, 0); + } + else + { + // DSP B = chip U3 = right on the schematic + // receives audio from DSP A at twice the sample rate + // sends its audio to the DACs at regular sample rate + clock.setEsaiDivider(&esai, 1, 0); + clock.setEsaiCounter(&esai, -1, 0); + } + } + + auto config = m_dsp.getJit().getConfig(); + + config.aguSupportBitreverse = true; + config.linkJitBlocks = true; + config.dynamicPeripheralAddressing = false; +#ifdef _DEBUG + config.debugDynamicPeripheralAddressing = true; +#endif + config.maxInstructionsPerBlock = 0; + config.support16BitSCMode = true; + config.dynamicFastInterrupts = true; + + m_dsp.getJit().setConfig(config); + + // fill P memory with something that reminds us if we jump to garbage + for(dsp56k::TWord i=0; i<m_memory.sizeP(); ++i) + { + m_memory.set(dsp56k::MemArea_P, i, 0x000200); // debug instruction + m_dsp.getJit().notifyProgramMemWrite(i); + } + + hdi08().setRXRateLimit(0); + + m_periphX.getEsai().writeEmptyAudioIn(2); + + m_hdiUC.setRxEmptyCallback([&](const bool _needMoreData) + { + onUCRxEmpty(_needMoreData); + }); + + m_hdiUC.setWriteTxCallback([this](const uint32_t _word) + { + if(m_boot.hdiWriteTX(_word)) + onDspBootFinished(); + }); + m_hdiUC.setWriteIrqCallback([&](const uint8_t _irq) + { + hdiSendIrqToDSP(_irq); + }); + m_hdiUC.setReadIsrCallback([&](const uint8_t _isr) + { + return hdiUcReadIsr(_isr); + }); + m_hdiUC.setInitHdi08Callback([&] + { + // clear init flag again immediately, code is waiting for it to happen + m_hdiUC.icr(m_hdiUC.icr() & 0x7f); + m_hdiUC.isr(m_hdiUC.isr() | mc68k::Hdi08::IsrBits::Txde | mc68k::Hdi08::IsrBits::Trdy); + }); + + m_irqInterruptDone = dsp().registerInterruptFunc([this] + { + m_triggerInterruptDone.notify(); + }); + } + + void DSP::terminate() + { + for(uint32_t i=0; i<32768; ++i) + m_triggerInterruptDone.notify(); + + m_thread->terminate(); + } + + void DSP::join() const + { + m_thread->join(); + } + + void DSP::onDspBootFinished() + { + m_hdiUC.setWriteTxCallback([&](const uint32_t _word) + { + hdiTransferUCtoDSP(_word); + }); + +#if DSP56300_DEBUGGER + if(!m_index) + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp))); + else +#endif + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); + + m_thread->setLogToStdout(false); + } + + void DSP::onUCRxEmpty(const bool _needMoreData) + { + if(_needMoreData) + { + hwLib::ScopedResumeDSP rA(m_hardware.getDSPA().getHaltDSP()); + hwLib::ScopedResumeDSP rB(m_hardware.getDSPB().getHaltDSP()); + + while(dsp().hasPendingInterrupts()) + std::this_thread::yield(); + } + hdiTransferDSPtoUC(); + } + + void DSP::hdiTransferUCtoDSP(const uint32_t _word) + { +// LOG('[' << m_name << "] toDSP writeRX=" << HEX(_word) << ", ucPC=" << HEX(m_hardware.getUC().getPrevPC())); + hdi08().writeRX(&_word, 1); + } + + void DSP::hdiSendIrqToDSP(const uint8_t _irq) + { + if(m_hardware.requestingHaltDSPs() && getHaltDSP().isHalting()) + { + // this is a very hacky way to execute a DSP interrupt even though the DSP is halted. This case happens if the DSPs run too fast + // and are halted by the sync code, but the UC wants to inject an interrupt, which needs to be executed immediately. + // In this case, we execute the interrupt without altering the DSP state + + const auto numOps = dsp().getInstructionCounter(); + const auto numCycles = dsp().getCycles(); + + const auto pc = dsp().getPC(); + dsp().getJit().exec(_irq); + dsp().setPC(pc); + + const_cast<uint32_t&>(dsp().getInstructionCounter()) = numOps; + const_cast<uint32_t&>(dsp().getCycles()) = numCycles; + } + else + { + dsp().injectExternalInterrupt(_irq); + dsp().injectExternalInterrupt(m_irqInterruptDone); + + hwLib::ScopedResumeDSP rA(m_hardware.getDSPA().getHaltDSP()); + hwLib::ScopedResumeDSP rB(m_hardware.getDSPB().getHaltDSP()); + m_triggerInterruptDone.wait(); + } + + hdiTransferDSPtoUC(); + } + + uint8_t DSP::hdiUcReadIsr(uint8_t _isr) + { + hdiTransferDSPtoUC(); + + // transfer DSP host flags HF2&3 to uc + const auto hf23 = hdi08().readControlRegister() & 0x18; + _isr &= ~0x18; + _isr |= hf23; + // always ready to receive more data + _isr |= mc68k::Hdi08::IsrBits::Trdy; + return _isr; + } + + bool DSP::hdiTransferDSPtoUC() + { + if (m_hdiUC.canReceiveData() && hdi08().hasTX()) + { + const auto v = hdi08().readTX(); +// LOG('[' << m_name << "] HDI dsp2UC=" << HEX(v)); + m_hdiUC.writeRx(v); + return true; + } + return false; + } +} diff --git a/source/nord/n2x/n2xLib/n2xdsp.h b/source/nord/n2x/n2xLib/n2xdsp.h @@ -0,0 +1,77 @@ +#pragma once + +#include <memory> +#include <string> + +#include "dsp56kEmu/dsp.h" +#include "dsp56kEmu/dspthread.h" + +#include "baseLib/semaphore.h" + +#include "hardwareLib/dspBootCode.h" +#include "hardwareLib/haltDSP.h" + +namespace mc68k +{ + class Hdi08; +} + +namespace n2x +{ + class Hardware; + + class DSP + { + public: + DSP(Hardware& _hw, mc68k::Hdi08& _hdiUc, uint32_t _index); + + dsp56k::HDI08& hdi08() + { + return m_periphX.getHDI08(); + } + + dsp56k::DSP& dsp() + { + return m_dsp; + } + + dsp56k::Peripherals56362& getPeriph() + { + return m_periphX; + } + + dsp56k::DSPThread& getDSPThread() const { return *m_thread; } + auto& getHaltDSP() { return m_haltDSP; } + + void terminate(); + void join() const; + void onDspBootFinished(); + + private: + void onUCRxEmpty(bool _needMoreData); + void hdiTransferUCtoDSP(uint32_t _word); + void hdiSendIrqToDSP(uint8_t _irq); + uint8_t hdiUcReadIsr(uint8_t _isr); + bool hdiTransferDSPtoUC(); + + Hardware& m_hardware; + mc68k::Hdi08& m_hdiUC; + + const uint32_t m_index; + const std::string m_name; + + dsp56k::PeripheralsNop m_periphNop; + dsp56k::Peripherals56362 m_periphX; + dsp56k::Memory m_memory; + dsp56k::DSP m_dsp; + + std::unique_ptr<dsp56k::DSPThread> m_thread; + + baseLib::Semaphore m_triggerInterruptDone; + uint32_t m_irqInterruptDone = 0; + + hwLib::HaltDSP m_haltDSP; + + hwLib::DspBoot m_boot; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xflash.cpp b/source/nord/n2x/n2xLib/n2xflash.cpp @@ -0,0 +1,22 @@ +#include "n2xflash.h" + +#include "n2xhardware.h" +#include "n2xtypes.h" +#include "synthLib/os.h" + +namespace n2x +{ + Flash::Flash(Hardware& _hardware) : m_hardware(_hardware) + { + const auto filename = synthLib::findROM(g_flashSize, g_flashSize); + + if(!filename.empty()) + { + std::vector<uint8_t> d; + if(synthLib::readFile(d, filename)) + { + setData(d); + } + } + } +} diff --git a/source/nord/n2x/n2xLib/n2xflash.h b/source/nord/n2x/n2xLib/n2xflash.h @@ -0,0 +1,17 @@ +#pragma once + +#include "hardwareLib/i2cFlash.h" + +namespace n2x +{ + class Hardware; + + class Flash : public hwLib::I2cFlash + { + public: + Flash(Hardware& _hardware); + + private: + Hardware& m_hardware; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xfrontpanel.cpp b/source/nord/n2x/n2xLib/n2xfrontpanel.cpp @@ -0,0 +1,243 @@ +#include "n2xfrontpanel.h" + +#include <cassert> +#include <cstring> // memcpy + +#include "n2xhardware.h" + +#include "dsp56kEmu/logging.h" + +namespace n2x +{ + template class FrontPanelCS<g_frontPanelAddressCS4>; + template class FrontPanelCS<g_frontPanelAddressCS6>; + + template <uint32_t Base> FrontPanelCS<Base>::FrontPanelCS(FrontPanel& _fp): m_panel(_fp) + { + } + + FrontPanelCS4::FrontPanelCS4(FrontPanel& _fp) : FrontPanelCS(_fp) + { + setKnobPosition(KnobType::PitchBend, 0); // pretend we're a rack unit + setKnobPosition(KnobType::ModWheel, 0); // pretend we're a rack unit + + setKnobPosition(KnobType::MasterVol, 0xff); + setKnobPosition(KnobType::AmpGain, 0xff); + setKnobPosition(KnobType::Osc1Fm, 0); + setKnobPosition(KnobType::Porta, 0); + setKnobPosition(KnobType::Lfo2Rate, 0x0); + setKnobPosition(KnobType::Lfo1Rate, 0x0); + setKnobPosition(KnobType::ModEnvAmt, 0); + setKnobPosition(KnobType::ModEnvD, 0); + setKnobPosition(KnobType::ModEnvA, 0); + setKnobPosition(KnobType::AmpEnvD, 0); + setKnobPosition(KnobType::FilterFreq, 0xff); + setKnobPosition(KnobType::FilterEnvA, 0); + setKnobPosition(KnobType::AmpEnvA, 0); + setKnobPosition(KnobType::OscMix, 0x7f); + setKnobPosition(KnobType::Osc2Fine, 0x7f); + setKnobPosition(KnobType::Lfo1Amount, 0x0f); + setKnobPosition(KnobType::OscPW, 0x40); + setKnobPosition(KnobType::FilterEnvR, 0x30); + setKnobPosition(KnobType::AmpEnvR, 0x90); + setKnobPosition(KnobType::FilterEnvAmt, 0); + setKnobPosition(KnobType::FilterEnvS, 0x7f); + setKnobPosition(KnobType::AmpEnvS, 0x7f); + setKnobPosition(KnobType::FilterReso, 0x10); + setKnobPosition(KnobType::FilterEnvD, 0); + setKnobPosition(KnobType::ExpPedal, 0x0); + setKnobPosition(KnobType::Lfo2Amount, 0); + setKnobPosition(KnobType::Osc2Semi, 0x7f); + } + + uint8_t FrontPanelCS4::read8(mc68k::PeriphAddress _addr) + { + const auto knobType = m_panel.cs6().getKnobType(); + + if(knobType == KnobType::Invalid) + return 0; + + return getKnobPosition(knobType); + } + + uint8_t FrontPanelCS4::getKnobPosition(KnobType _knob) const + { + const auto i = static_cast<uint32_t>(_knob) - static_cast<uint32_t>(KnobType::First); + return m_knobPositions[i]; + } + + void FrontPanelCS4::setKnobPosition(KnobType _knob, const uint8_t _value) + { + const auto i = static_cast<uint32_t>(_knob) - static_cast<uint32_t>(KnobType::First); + m_knobPositions[i] = _value; + } + + FrontPanelCS6::FrontPanelCS6(FrontPanel& _fp) : FrontPanelCS(_fp), m_buttonStates({}) + { + m_buttonStates.fill(0xff); + } + + void FrontPanelCS6::write8(const mc68k::PeriphAddress _addr, const uint8_t _val) + { +// LOG("Write CS6 " << HEXN(_addr - base(),2) << " = " << HEXN(_val,2)); + + switch (static_cast<uint32_t>(_addr)) + { + case g_frontPanelAddressCS6 + 0x8: + m_ledLatch8 = _val; + break; + case g_frontPanelAddressCS6 + 0xa: + m_ledLatch10 = _val; + +// LOG("Read pot " << HEXN((_val & 0x7f), 2)); + m_selectedKnob = static_cast<KnobType>(_val & 0x7f); + + if(m_ledLatch10 & (1<<7)) + { + m_lcds[2] = m_ledLatch8; + onLCDChanged(); + } + break; + case g_frontPanelAddressCS6 + 0xc: + { + bool gotLCDs = false; + + m_ledLatch12 = _val; + + if(m_ledLatch12 & (1<<6)) + { + m_lcds[0] = m_ledLatch8; + gotLCDs = true; + } + if(m_ledLatch12 & (1<<7)) + { + m_lcds[1] = m_ledLatch8; + gotLCDs = true; + } + + if(gotLCDs) + onLCDChanged(); + } + break; + } + FrontPanelCS::write8(_addr, _val); + } + + uint8_t FrontPanelCS6::read8(mc68k::PeriphAddress _addr) + { + const auto a = static_cast<uint32_t>(_addr); + switch (a) + { + case g_frontPanelAddressCS6: return m_buttonStates[0]; + case g_frontPanelAddressCS6 + 2: return m_buttonStates[1]; + case g_frontPanelAddressCS6 + 4: return m_buttonStates[2]; + case g_frontPanelAddressCS6 + 6: return m_buttonStates[3]; + } + return FrontPanelCS::read8(_addr); + } + + void FrontPanelCS6::setButtonState(ButtonType _button, const bool _pressed) + { + const auto id = static_cast<uint32_t>(_button); + const auto index = id>>9; + const auto mask = id & 0xff; + if(_pressed) + m_buttonStates[index] |= mask; + else + m_buttonStates[index] &= ~mask; + } + + bool FrontPanelCS6::getButtonState(ButtonType _button) const + { + const auto id = static_cast<uint32_t>(_button); + const auto index = id>>9; + const auto mask = id & 0xff; + return m_buttonStates[index] & mask ? false : true; + } + + void FrontPanelCS6::printLCD() const + { + static uint32_t count = 0; + ++count; + if(count & 0xff) + return; + /* + -- -- -- + | | | | | | + -- -- -- + | | | | | | + -- -- -- + */ + + using Line = std::array<char, 16>; + std::array<Line,5> buf; + + for (auto& b : buf) + { + b.fill(' '); + } + + int off = 0; + + auto drawH = [&](int _x, const int _y, const bool _set) + { + _x += off; + buf[_y][_x ] = _set ? '-' : ' '; + buf[_y][_x+1] = _set ? '-' : ' '; + }; + + auto drawV = [&](int _x, const int _y, const bool _set) + { + _x += off; + buf[_y][_x] = _set ? '|' : ' '; + }; + + for(auto i=0; i<3; ++i) + { + auto bt = [&](const uint32_t _bit) + { + return !(m_lcds[i] & (1<<_bit)); + }; + + drawH(1, 0, bt(7)); + drawH(1, 2, bt(1)); + drawH(1, 4, bt(4)); + + drawV(0, 1, bt(2)); + drawV(3, 1, bt(6)); + drawV(0, 3, bt(3)); + drawV(3, 3, bt(5)); + + off += 6; + } + + buf[4][4] = (m_lcds[0] & (1<<0)) ? ' ' : '*'; + + char message[(sizeof(Line) + 1) * buf.size() + 1]; + size_t i=0; + for (const auto& line : buf) + { + memcpy(&message[i], line.data(), line.size()); + i += line.size(); + message[i++] = '\n'; + } + assert(i == std::size(message)-1); + message[i] = 0; + + LOG("LCD:\n" << message); + } + + void FrontPanelCS6::onLCDChanged() + { + // Check if the LCD displays " 1", used as indicator that device has finished booting + if(m_lcds[0] == 255 && m_lcds[1] >= 254 && m_lcds[2] == 159) + { + m_panel.getHardware().notifyBootFinished(); + } + printLCD(); + } + + FrontPanel::FrontPanel(Hardware& _hardware) : m_hardware(_hardware), m_cs4(*this), m_cs6(*this) + { + } +} diff --git a/source/nord/n2x/n2xLib/n2xfrontpanel.h b/source/nord/n2x/n2xLib/n2xfrontpanel.h @@ -0,0 +1,99 @@ +#pragma once + +#include "n2xtypes.h" +#include "mc68k/peripheralBase.h" + +namespace n2x +{ + class Hardware; + class FrontPanel; + + template<uint32_t Base> + class FrontPanelCS : public mc68k::PeripheralBase<Base, g_frontPanelSize> + { + public: + explicit FrontPanelCS(FrontPanel& _fp); + + protected: + FrontPanel& m_panel; + }; + + class FrontPanelCS4 : public FrontPanelCS<g_frontPanelAddressCS4> + { + public: + explicit FrontPanelCS4(FrontPanel& _fp); + + uint8_t read8(mc68k::PeriphAddress _addr) override; + uint8_t getKnobPosition(KnobType _knob) const; + void setKnobPosition(KnobType _knob, uint8_t _value); + + std::array<uint8_t, static_cast<uint32_t>(KnobType::Last) - static_cast<uint32_t>(KnobType::First) + 1> m_knobPositions; + }; + + class FrontPanelCS6 : public FrontPanelCS<g_frontPanelAddressCS6> + { + public: + explicit FrontPanelCS6(FrontPanel& _fp); + + void write8(mc68k::PeriphAddress _addr, uint8_t _val) override; + uint8_t read8(mc68k::PeriphAddress _addr) override; + + auto getKnobType() const { return m_selectedKnob; } + + void setButtonState(ButtonType _button, bool _pressed); + bool getButtonState(ButtonType _button) const; + + private: + void printLCD() const; + void onLCDChanged(); + + uint8_t m_ledLatch8 = 0; + uint8_t m_ledLatch10 = 0; + uint8_t m_ledLatch12 = 0; + std::array<uint8_t,3> m_lcds{0,0,0}; + std::array<uint8_t,3> m_lcdsPrev{0,0,0}; + KnobType m_selectedKnob = KnobType::PitchBend; + + std::array<uint8_t, 4> m_buttonStates; + }; + + class FrontPanel + { + public: + FrontPanel(Hardware& _hardware); + + auto& cs4() { return m_cs4; } + auto& cs6() { return m_cs6; } + + bool isInRange(const mc68k::PeriphAddress _pa) const + { + return m_cs4.isInRange(_pa) || m_cs6.isInRange(_pa); + } + + bool getButtonState(ButtonType _type) const + { + return m_cs6.getButtonState(_type); + } + + void setButtonState(ButtonType _type, bool _pressed) + { + m_cs6.setButtonState(_type, _pressed); + } + + Hardware& getHardware() const { return m_hardware; } + + uint8_t getKnobPosition(KnobType _knob) const + { + return m_cs4.getKnobPosition(_knob); + } + void setKnobPosition(KnobType _knob, uint8_t _value) + { + m_cs4.setKnobPosition(_knob, _value); + } + + private: + Hardware& m_hardware; + FrontPanelCS4 m_cs4; + FrontPanelCS6 m_cs6; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xhardware.cpp b/source/nord/n2x/n2xLib/n2xhardware.cpp @@ -0,0 +1,356 @@ +#include "n2xhardware.h" + +#include "n2xromloader.h" +#include "dsp56kEmu/threadtools.h" + +namespace n2x +{ + constexpr uint32_t g_syncEsaiFrameRate = 16; + constexpr uint32_t g_syncHaltDspEsaiThreshold = 32; + + static_assert((g_syncEsaiFrameRate & (g_syncEsaiFrameRate - 1)) == 0, "esai frame sync rate must be power of two"); + static_assert(g_syncHaltDspEsaiThreshold >= g_syncEsaiFrameRate * 2, "esai DSP halt threshold must be greater than two times the sync rate"); + + Hardware::Hardware() + : m_rom(RomLoader::findROM()) + , m_uc(*this, m_rom) + , m_dspA(*this, m_uc.getHdi08A(), 0) + , m_dspB(*this, m_uc.getHdi08B(), 1) + , m_samplerateInv(1.0 / g_samplerate) + , m_semDspAtoB(2) + { + if(!m_rom.isValid()) + return; + + m_dspA.getPeriph().getEsai().setCallback([this](dsp56k::Audio*){ onEsaiCallbackA(); }, 0); + m_dspB.getPeriph().getEsai().setCallback([this](dsp56k::Audio*){ onEsaiCallbackB(); }, 0); + + m_ucThread.reset(new std::thread([this] + { + ucThreadFunc(); + })); + + while(!m_bootFinished) + processAudio(8,8); + m_midiOffsetCounter = 0; + } + + Hardware::~Hardware() + { + m_destroy = true; + + while(m_destroy) + processAudio(8,64); + + m_dspA.terminate(); + m_dspB.terminate(); + + m_esaiFrameIndex = 0; + m_maxEsaiCallbacks = std::numeric_limits<uint32_t>::max(); + m_esaiLatency = 0; + + while(!m_dspA.getDSPThread().runThread() || !m_dspB.getDSPThread().runThread()) + { + // DSP A waits for space to push to DSP B + m_semDspAtoB.notify(); + + if(m_dspB.getPeriph().getEsai().getAudioInputs().full()) + m_dspB.getPeriph().getEsai().getAudioInputs().pop_front(); + + // DSP B waits for ESAI rate limiting and for DSP A to provide audio data + m_haltDSPcv.notify_all(); + if(m_dspA.getPeriph().getEsai().getAudioOutputs().empty()) + m_dspA.getPeriph().getEsai().getAudioOutputs().push_back({}); + } + + m_ucThread->join(); + } + + bool Hardware::isValid() const + { + return m_rom.isValid(); + } + + void Hardware::processUC() + { + syncUCtoDSP(); + + const auto deltaCycles = m_uc.exec(); + + if(m_esaiFrameIndex > 0) + m_remainingUcCycles -= static_cast<int64_t>(deltaCycles); + } + + void Hardware::processAudio(uint32_t _frames, const uint32_t _latency) + { + getMidi().process(_frames); + + ensureBufferSize(_frames); + + dsp56k::TWord* outputs[12]{nullptr}; + outputs[0] = &m_audioOutputs[0].front(); + outputs[1] = &m_audioOutputs[1].front(); + outputs[2] = &m_audioOutputs[2].front(); + outputs[3] = &m_audioOutputs[3].front(); + outputs[4] = m_dummyOutput.data(); + outputs[5] = m_dummyOutput.data(); + outputs[6] = m_dummyOutput.data(); + outputs[7] = m_dummyOutput.data(); + outputs[8] = m_dummyOutput.data(); + outputs[9] = m_dummyOutput.data(); + outputs[10] = m_dummyOutput.data(); + outputs[11] = m_dummyOutput.data(); + + auto& esaiB = m_dspB.getPeriph().getEsai(); + +// LOG("B out " << esaiB.getAudioOutputs().size() << ", A out " << esaiA.getAudioOutputs().size() << ", B in " << esaiB.getAudioInputs().size()); + + while (_frames) + { + const auto processCount = std::min(_frames, static_cast<uint32_t>(64)); + _frames -= processCount; + + advanceSamples(processCount, _latency); + + const auto requiredSize = processCount > 8 ? processCount - 8 : 0; + + if(esaiB.getAudioOutputs().size() < requiredSize) + { + // reduce thread contention by waiting for output buffer to be full enough to let us grab the data without entering the read mutex too often + + std::unique_lock uLock(m_requestedFramesAvailableMutex); + m_requestedFrames = requiredSize; + m_requestedFramesAvailableCv.wait(uLock, [&]() + { + if(esaiB.getAudioOutputs().size() < requiredSize) + return false; + m_requestedFrames = 0; + return true; + }); + } + + // read output of DSP B to regular audio output + esaiB.processAudioOutputInterleaved(outputs, processCount); + + outputs[0] += processCount; + outputs[1] += processCount; + outputs[2] += processCount; + outputs[3] += processCount; + } + } + + void Hardware::processAudio(const synthLib::TAudioOutputs& _outputs, const uint32_t _frames, const uint32_t _latency) + { + processAudio(_frames, _latency); + + for(size_t i=0; i<_frames; ++i) + { + _outputs[0][i] = dsp56k::dsp2sample<float>(m_audioOutputs[0][i]); + _outputs[1][i] = dsp56k::dsp2sample<float>(m_audioOutputs[1][i]); + _outputs[2][i] = dsp56k::dsp2sample<float>(m_audioOutputs[2][i]); + _outputs[3][i] = dsp56k::dsp2sample<float>(m_audioOutputs[3][i]); + } + } + + bool Hardware::sendMidi(const synthLib::SMidiEvent& _ev) + { + m_midiIn.push_back(_ev); + return true; + } + + void Hardware::notifyBootFinished() + { + m_bootFinished = true; + } + + void Hardware::ensureBufferSize(const uint32_t _frames) + { + if(m_dummyInput.size() >= _frames) + return; + + m_dummyInput.resize(_frames, 0); + m_dummyOutput.resize(_frames, 0); + + for (auto& audioOutput : m_audioOutputs) + audioOutput.resize(_frames, 0); + + m_dspAtoBBuffer.resize(_frames * 4); + } + + void Hardware::onEsaiCallbackA() + { + // forward DSP A output to DSP B input + const auto out = m_dspA.getPeriph().getEsai().getAudioOutputs().pop_front(); + + dsp56k::Audio::RxFrame in; + in.resize(out.size()); + + in[0] = dsp56k::Audio::RxSlot{out[0][0]}; + in[1] = dsp56k::Audio::RxSlot{out[1][0]}; + in[2] = dsp56k::Audio::RxSlot{out[2][0]}; + in[3] = dsp56k::Audio::RxSlot{out[3][0]}; + + m_dspB.getPeriph().getEsai().getAudioInputs().push_back(in); + + m_semDspAtoB.wait(); + } + + void Hardware::processMidiInput() + { + ++m_midiOffsetCounter; + + while(!m_midiIn.empty()) + { + const auto& e = m_midiIn.front(); + + if(e.offset > m_midiOffsetCounter) + break; + + getMidi().write(e); + m_midiIn.pop_front(); + } + } + + void Hardware::onEsaiCallbackB() + { + m_semDspAtoB.notify(); + + ++m_esaiFrameIndex; + + processMidiInput(); + + if((m_esaiFrameIndex & (g_syncEsaiFrameRate-1)) == 0) + m_esaiFrameAddedCv.notify_one(); + + m_requestedFramesAvailableMutex.lock(); + + if(m_requestedFrames && m_dspB.getPeriph().getEsai().getAudioOutputs().size() >= m_requestedFrames) + { + m_requestedFramesAvailableMutex.unlock(); + m_requestedFramesAvailableCv.notify_one(); + } + else + { + m_requestedFramesAvailableMutex.unlock(); + } + + if(m_esaiFrameIndex >= m_maxEsaiCallbacks + m_esaiLatency) + { + std::unique_lock uLock(m_haltDSPmutex); + m_haltDSPcv.wait(uLock, [&] + { + return (m_maxEsaiCallbacks + m_esaiLatency) > m_esaiFrameIndex; + }); + } + } + + void Hardware::syncUCtoDSP() + { + if(m_remainingUcCycles > 0) + return; + + // we can only use ESAI to clock the uc once it has been enabled + if(m_esaiFrameIndex <= 0) + return; + + if(m_esaiFrameIndex == m_lastEsaiFrameIndex) + { + resumeDSPs(); + std::unique_lock uLock(m_esaiFrameAddedMutex); + m_esaiFrameAddedCv.wait(uLock, [this]{return m_esaiFrameIndex > m_lastEsaiFrameIndex;}); + } + + const auto esaiFrameIndex = m_esaiFrameIndex; + const auto esaiDelta = esaiFrameIndex - m_lastEsaiFrameIndex; + + const auto ucClock = m_uc.getSim().getSystemClockHz(); + const double ucCyclesPerFrame = static_cast<double>(ucClock) * m_samplerateInv; + + // if the UC consumed more cycles than it was allowed to, remove them from remaining cycles + m_remainingUcCyclesD += static_cast<double>(m_remainingUcCycles); + + // add cycles for the ESAI time that has passed + m_remainingUcCyclesD += ucCyclesPerFrame * static_cast<double>(esaiDelta); + + // set new remaining cycle count + m_remainingUcCycles = static_cast<int64_t>(m_remainingUcCyclesD); + + // and consume them + m_remainingUcCyclesD -= static_cast<double>(m_remainingUcCycles); + + if(esaiDelta > g_syncHaltDspEsaiThreshold) + haltDSPs(); + + m_lastEsaiFrameIndex = esaiFrameIndex; + } + + void Hardware::ucThreadFunc() + { + dsp56k::ThreadTools::setCurrentThreadName("MC68331"); + dsp56k::ThreadTools::setCurrentThreadPriority(dsp56k::ThreadPriority::Highest); + + while(!m_destroy) + { + processUC(); + processUC(); + processUC(); + processUC(); + processUC(); + processUC(); + processUC(); + processUC(); + } + resumeDSPs(); + m_destroy = false; + } + + void Hardware::advanceSamples(const uint32_t _samples, const uint32_t _latency) + { + { + std::lock_guard uLockHalt(m_haltDSPmutex); + m_maxEsaiCallbacks += _samples; + m_esaiLatency = _latency; + } + m_haltDSPcv.notify_one(); + } + + void Hardware::haltDSPs() + { + if(m_dspHalted) + return; + m_dspHalted = true; +// LOG("Halt"); + m_dspA.getHaltDSP().haltDSP(); + m_dspB.getHaltDSP().haltDSP(); + } + + void Hardware::resumeDSPs() + { + if(!m_dspHalted) + return; + m_dspHalted = false; +// LOG("Resume"); + m_dspA.getHaltDSP().resumeDSP(); + m_dspB.getHaltDSP().resumeDSP(); + } + + bool Hardware::getButtonState(const ButtonType _type) const + { + return m_uc.getFrontPanel().getButtonState(_type); + } + + void Hardware::setButtonState(const ButtonType _type, const bool _pressed) + { + m_uc.getFrontPanel().setButtonState(_type, _pressed); + } + + uint8_t Hardware::getKnobPosition(KnobType _knob) const + { + return m_uc.getFrontPanel().getKnobPosition(_knob); + } + + void Hardware::setKnobPosition(KnobType _knob, uint8_t _value) + { + return m_uc.getFrontPanel().setKnobPosition(_knob, _value); + } +} diff --git a/source/nord/n2x/n2xLib/n2xhardware.h b/source/nord/n2x/n2xLib/n2xhardware.h @@ -0,0 +1,101 @@ +#pragma once + +#include "n2xdsp.h" +#include "n2xmc.h" +#include "n2xrom.h" + +#include "synthLib/audioTypes.h" +#include "synthLib/midiTypes.h" + +namespace n2x +{ + class Hardware + { + public: + using AudioOutputs = std::array<std::vector<dsp56k::TWord>, 4>; + Hardware(); + ~Hardware(); + + bool isValid() const; + + void processUC(); + + Microcontroller& getUC() {return m_uc; } + + const auto& getAudioOutputs() const { return m_audioOutputs; } + + void processAudio(uint32_t _frames, uint32_t _latency); + + auto& getDSPA() { return m_dspA; } + auto& getDSPB() { return m_dspB; } + + auto& getMidi() { return m_uc.getMidi(); } + + void haltDSPs(); + void resumeDSPs(); + bool requestingHaltDSPs() const { return m_dspHalted; } + + bool getButtonState(ButtonType _type) const; + void setButtonState(ButtonType _type, bool _pressed); + + uint8_t getKnobPosition(KnobType _knob) const; + void setKnobPosition(KnobType _knob, uint8_t _value); + + void processAudio(const synthLib::TAudioOutputs& _outputs, uint32_t _frames, uint32_t _latency); + bool sendMidi(const synthLib::SMidiEvent& _ev); + void notifyBootFinished(); + + const std::string& getRomFilename() const { return m_rom.getFilename(); } + + private: + void ensureBufferSize(uint32_t _frames); + void onEsaiCallbackA(); + void processMidiInput(); + void onEsaiCallbackB(); + void syncUCtoDSP(); + void ucThreadFunc(); + void advanceSamples(uint32_t _samples, uint32_t _latency); + + Rom m_rom; + Microcontroller m_uc; + DSP m_dspA; + DSP m_dspB; + + std::vector<dsp56k::TWord> m_dummyInput; + std::vector<dsp56k::TWord> m_dummyOutput; + std::vector<dsp56k::TWord> m_dspAtoBBuffer; + + AudioOutputs m_audioOutputs; + + // timing + const double m_samplerateInv; + uint32_t m_esaiFrameIndex = 0; + uint32_t m_lastEsaiFrameIndex = 0; + int64_t m_remainingUcCycles = 0; + double m_remainingUcCyclesD = 0; + std::mutex m_esaiFrameAddedMutex; + std::condition_variable m_esaiFrameAddedCv; + std::mutex m_requestedFramesAvailableMutex; + std::condition_variable m_requestedFramesAvailableCv; + size_t m_requestedFrames = 0; + bool m_dspHalted = false; + dsp56k::SpscSemaphore m_semDspAtoB; + + dsp56k::RingBuffer<dsp56k::Audio::RxFrame, 4, true> m_dspAtoBbuf; + + std::unique_ptr<std::thread> m_ucThread; + bool m_destroy = false; + + // Midi + dsp56k::RingBuffer<synthLib::SMidiEvent, 16384, true> m_midiIn; + uint32_t m_midiOffsetCounter = 0; + + // DSP slowdown + uint32_t m_maxEsaiCallbacks = 0; + uint32_t m_esaiLatency = 0; + std::mutex m_haltDSPmutex; + std::condition_variable m_haltDSPcv; + + bool m_bootFinished = false; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xhdi08.cpp b/source/nord/n2x/n2xLib/n2xhdi08.cpp @@ -0,0 +1,5 @@ +#include "n2xhdi08.h" + +namespace n2x +{ +} diff --git a/source/nord/n2x/n2xLib/n2xhdi08.h b/source/nord/n2x/n2xLib/n2xhdi08.h @@ -0,0 +1,66 @@ +#pragma once + +#include <cassert> + +#include "n2xtypes.h" + +#include "mc68k/hdi08periph.h" + +namespace n2x +{ + using Hdi08DspA = mc68k::Hdi08Periph<g_dspAAddress>; + using Hdi08DspB = mc68k::Hdi08Periph<g_dspBAddress>; + + class Hdi08DspBoth : public mc68k::PeripheralBase<g_dspBothAddress, g_dspAAddress - g_dspBothAddress> + { + public: + static constexpr uint32_t OffsetA = g_dspAAddress - base(); + static constexpr uint32_t OffsetB = g_dspBAddress - base(); + + Hdi08DspBoth(Hdi08DspA& _a, Hdi08DspB& _b) : m_a(_a), m_b(_b) + { + } + + void write8(const mc68k::PeriphAddress _addr, const uint8_t _val) override + { + m_a.write8(offA(_addr), _val); + m_b.write8(offB(_addr), _val); + } + + void write16(const mc68k::PeriphAddress _addr, const uint16_t _val) override + { + m_a.write16(offA(_addr), _val); + m_b.write16(offB(_addr), _val); + } + + uint8_t read8(const mc68k::PeriphAddress _addr) override + { + assert(false && "not readable"); + return m_a.read8(_addr); + } + + uint16_t read16(const mc68k::PeriphAddress _addr) override + { + assert(false && "not readable"); + return m_a.read16(_addr); + } + + static mc68k::PeriphAddress offA(const mc68k::PeriphAddress _addr) + { + return off<OffsetA>(_addr); + } + + static mc68k::PeriphAddress offB(const mc68k::PeriphAddress _addr) + { + return off<OffsetB>(_addr); + } + + template<uint32_t Off> static mc68k::PeriphAddress off(mc68k::PeriphAddress _addr) + { + return static_cast<mc68k::PeriphAddress>(static_cast<uint32_t>(_addr) + Off); + } + private: + Hdi08DspA& m_a; + Hdi08DspB& m_b; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xmc.cpp b/source/nord/n2x/n2xLib/n2xmc.cpp @@ -0,0 +1,332 @@ +#include "n2xmc.h" + +#include <cassert> + +#include "n2xdsp.h" +#include "n2xrom.h" + +#include "synthLib/os.h" + +namespace n2x +{ + // OC2 = PGP4 = SDA + // OC3 = PGP5 = SCL + // OC5 = PGP7 = sustain pedal + + static constexpr uint32_t g_bitResetDSP = 3; + static constexpr uint32_t g_bitSDA = 4; + static constexpr uint32_t g_bitSCL = 5; + static constexpr uint32_t g_bitResetDAC = 6; + + static constexpr uint32_t g_maskResetDSP = 1 << g_bitResetDSP; + static constexpr uint32_t g_maskResetDAC = 1 << g_bitResetDAC; + + Microcontroller::Microcontroller(Hardware& _hardware, const Rom& _rom) + : m_flash(_hardware) + , m_hdi08(m_hdi08A, m_hdi08B) + , m_panel(_hardware) + , m_midi(getQSM(), g_samplerate) + { + if(!_rom.isValid()) + return; + + m_romRam.fill(0); + std::copy(std::begin(_rom.data()), std::end(_rom.data()), std::begin(m_romRam)); + + m_midi.setSysexDelay(0.0f, 1); + +// dumpAssembly("n2x_68k.asm", g_romAddress, g_romSize); + + reset(); + + setPC(g_pcInitial); + + // keyboard irqs, we do not really care but as the UC spinlooped once while waiting for it to become high we set it to high all the time + getPortF().writeRX(0xff); + + getPortGP().setWriteTXCallback([this](const mc68k::Port& _port) + { + const auto v = _port.read(); + const auto d = _port.getDirection(); + + const auto sdaV = (v >> g_bitSDA) & 1; + const auto sclV = (v >> g_bitSCL) & 1; + + const auto sdaD = (d >> g_bitSDA) & 1; + const auto sclD = (d >> g_bitSCL) & 1; + +// LOG("PortGP write SDA=" << sdaV << " SCL=" << sclV); + + if(d & g_maskResetDAC) + { + if(v & g_maskResetDAC) + { + } + else + { + LOG("Reset DAC"); + } + } + if((d & g_maskResetDSP)) + { + if(v & g_maskResetDSP) + { + } + else + { + LOG("Reset DSP"); + } + } + + if(sdaD && sclD) + m_flash.masterWrite(sdaV, sclV); + else if(!sdaD && sclD) + { + if(const auto res = m_flash.masterRead(sclV)) + { + auto r = v; + r &= ~(1 << g_bitSDA); + r |= *res ? (1<<g_bitSDA) : 0; + + getPortGP().writeRX(r); + LOG("PortGP return SDA=" << *res); + } + else + { +// LOG("PortGP return SDA={}, wrong SCL"); + } + } + }); + getPortGP().setDirectionChangeCallback([this](const mc68k::Port& _port) + { + // OC2 = PGP4 = SDA + // OC3 = PGP5 = SCL + const auto p = _port.getDirection(); + const auto sda = (p >> g_bitSDA) & 1; + const auto scl = (p >> g_bitSCL) & 1; + LOG("PortGP dir SDA=" << (sda?"w":"r") << " SCL=" << (scl?"w":"r")); + if(scl) + { + const auto ack = m_flash.setSdaWrite(sda); + if(ack) + { + LOG("Write ACK " << (*ack)); + getPortGP().writeRX(*ack ? (1<<g_bitSDA) : 0); + } + } + }); + } + + uint32_t Microcontroller::read32(uint32_t _addr) + { + return Mc68k::read32(_addr); + } + + uint16_t Microcontroller::readImm16(const uint32_t _addr) + { + return readW(m_romRam.data(), _addr & (m_romRam.size()-1)); + } + + uint16_t Microcontroller::read16(const uint32_t _addr) + { + if(_addr < m_romRam.size()) + { + const auto r = readW(m_romRam.data(), _addr); + return r; + } + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(pa)) return m_hdi08A.read16(pa); + if(m_hdi08B.isInRange(pa)) return m_hdi08B.read16(pa); + if(m_hdi08.isInRange(pa)) return m_hdi08.read16(pa); + + if(m_panel.cs4().isInRange(pa)) + { + LOG("Read Frontpanel CS4 " << HEX(_addr)); + return m_panel.cs4().read16(pa); + } + + if(m_panel.cs6().isInRange(pa)) + { + LOG("Read Frontpanel CS6 " << HEX(_addr)); + return m_panel.cs6().read16(pa); + } + +#ifdef _DEBUG + if(_addr >= g_keyboardAddress && _addr < g_keyboardAddress + g_keyboardSize) + { + assert(false && "keyboard access is unexpected"); + LOG("Read Keyboard " << HEX(_addr)); + return 0; + } +#endif + return Mc68k::read16(_addr); + } + + uint8_t Microcontroller::read8(const uint32_t _addr) + { + if(_addr < m_romRam.size()) + { + const auto r = m_romRam[_addr]; +// LOG("read " << HEX(_addr) << "=" << HEXN(r,2)); + return r; + } + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(pa)) return m_hdi08A.read8(pa); + if(m_hdi08B.isInRange(pa)) return m_hdi08B.read8(pa); + if(m_hdi08.isInRange(pa)) return m_hdi08.read8(pa); + + if(m_panel.cs4().isInRange(pa)) + { +// LOG("Read Frontpanel CS4 " << HEX(_addr)); + return m_panel.cs4().read8(pa); + } + + if(m_panel.cs6().isInRange(pa)) + { +// LOG("Read Frontpanel CS6 " << HEX(_addr)); + return m_panel.cs6().read8(pa); + } + +#ifdef _DEBUG + if(_addr >= g_keyboardAddress && _addr < g_keyboardAddress + g_keyboardSize) + { + assert(false && "keyboard access is unexpected"); + LOG("Read Keyboard " << HEX(_addr)); + return 0; + } +#endif + return Mc68k::read8(_addr); + } + + void Microcontroller::write16(const uint32_t _addr, const uint16_t _val) + { + if(_addr < m_romRam.size()) + { + assert(_addr >= g_ramAddress); + writeW(m_romRam.data(), _addr, _val); + return; + } + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(pa)) + { + m_hdi08A.write16(pa, _val); + return; + } + + if(m_hdi08B.isInRange(pa)) + { + m_hdi08B.write16(pa, _val); + return; + } + + if(m_hdi08.isInRange(pa)) + { + m_hdi08.write16(pa, _val); + return; + } + + if(m_panel.cs4().isInRange(pa)) + { +// LOG("Write Frontpanel CS4 " << HEX(_addr) << "=" << HEXN(_val, 4)); + m_panel.cs4().write16(pa, _val); + return; + } + + if(m_panel.cs6().isInRange(pa)) + { +// LOG("Write Frontpanel CS6 " << HEX(_addr) << "=" << HEXN(_val, 4)); + m_panel.cs6().write16(pa, _val); + return; + } + + Mc68k::write16(_addr, _val); + } + + void Microcontroller::write8(const uint32_t _addr, const uint8_t _val) + { + if(_addr < m_romRam.size()) + { + assert(_addr >= g_ramAddress); + m_romRam[_addr] = _val; + return; + } + + const auto pa = static_cast<mc68k::PeriphAddress>(_addr); + + if(m_hdi08A.isInRange(pa)) + { + m_hdi08A.write8(pa, _val); + return; + } + + if(m_hdi08B.isInRange(pa)) + { + m_hdi08B.write8(pa, _val); + return; + } + + if(m_hdi08.isInRange(pa)) + { + m_hdi08.write8(pa, _val); + return; + } + + if(m_panel.cs4().isInRange(pa)) + { + LOG("Write Frontpanel CS4 " << HEX(_addr) << "=" << HEXN(_val, 2)); + m_panel.cs4().write8(pa, _val); + return; + } + + if(m_panel.cs6().isInRange(pa)) + { +// LOG("Write Frontpanel CS6 " << HEX(_addr) << "=" << HEXN(_val, 2)); + m_panel.cs6().write8(pa, _val); + return; + } + + Mc68k::write8(_addr, _val); + } + + uint32_t Microcontroller::exec() + { +#ifdef _DEBUG + const auto pc = getPC(); + m_prevPC = pc; + +// LOG("uc PC=" << HEX(pc)); + if(pc >= g_ramAddress) + { +// if(pc == 0x1000c8) +// dumpAssembly("nl2x_68k_ram.asm", g_ramAddress, g_ramSize); + } + + static volatile bool writeFlash = false; + static volatile bool writeRam = false; + + if(writeFlash) + { + writeFlash = false; + m_flash.saveAs("flash_runtime.bin"); + } + + if(writeRam) + { + writeRam = false; + synthLib::writeFile("romRam_runtime.bin", m_romRam); + } +#endif + const auto cycles = Mc68k::exec(); + +// m_hdi08A.exec(cycles); +// m_hdi08B.exec(cycles); + + return cycles; + } +} diff --git a/source/nord/n2x/n2xLib/n2xmc.h b/source/nord/n2x/n2xLib/n2xmc.h @@ -0,0 +1,55 @@ +#pragma once + +#include "n2xfrontpanel.h" +#include "n2xhdi08.h" +#include "n2xflash.h" +#include "n2xtypes.h" + +#include "mc68k/mc68k.h" + +#include "hardwareLib/sciMidi.h" + +namespace n2x +{ + class Rom; + + class Microcontroller : public mc68k::Mc68k + { + public: + explicit Microcontroller(Hardware& _hardware, const Rom& _rom); + + auto& getHdi08A() { return m_hdi08A.getHdi08(); } + auto& getHdi08B() { return m_hdi08B.getHdi08(); } + auto& getMidi() { return m_midi; } + + uint32_t exec() override; + + auto getPrevPC() const { return m_prevPC; } + + auto& getFrontPanel() { return m_panel; } + const auto& getFrontPanel() const { return m_panel; } + + private: + uint32_t read32(uint32_t _addr) override; + uint16_t readImm16(uint32_t _addr) override; + uint16_t read16(uint32_t _addr) override; + uint8_t read8(uint32_t _addr) override; + + void write16(uint32_t _addr, uint16_t _val) override; + void write8(uint32_t _addr, uint8_t _val) override; + + // rom at $000000, ram at $100000, dsps at $200000, we use one buffer for both rom and ram and a power of two sized buffer helps with faster access + std::array<uint8_t, g_dspBothAddress> m_romRam; + + Flash m_flash; + + Hdi08DspA m_hdi08A; + Hdi08DspB m_hdi08B; + Hdi08DspBoth m_hdi08; + + FrontPanel m_panel; + + uint32_t m_prevPC; + hwLib::SciMidi m_midi; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xmiditypes.h b/source/nord/n2x/n2xLib/n2xmiditypes.h @@ -0,0 +1,175 @@ +#pragma once + +#include <cstdint> + +namespace n2x +{ + enum SysexByte : uint8_t + { + IdClavia = 0x33, IdN2X = 0x04, DefaultDeviceId = 0xf, + + SingleDumpBankEditBuffer = 0x00, SingleDumpBankA = 0x01, SingleDumpBankB = 0x02, SingleDumpBankC = 0x03, SingleDumpBankD = 0x04, + SingleRequestBankEditBuffer = 0x0e, SingleRequestBankA = 0x0f, SingleRequestBankB = 0x10, SingleRequestBankC = 0x11, SingleRequestBankD = 0x12, + + MultiDumpBankEditBuffer = 30, MultiDumpBankA, + MultiRequestBankEditBuffer = 40, + + EmuSetPotPosition = 90, // total dump is: f0, IdClavia, IdDevice, IdN2x, EmuSetPotPosition, KnobType / nibble high, nibble low / f7 + EmuGetPotsPosition = 91, + }; + + enum SysexIndex + { + IdxClavia = 1, + IdxDevice, + IdxN2x, + IdxMsgType, + IdxMsgSpec, + IdxKnobPosH, + IdxKnobPosL + }; + + enum SingleParam + { + Gain = 17, + OctaveShift = 64, + ModWheelDest = 59, + Unison = 60, + VoiceMode = 58, + Auto = 62, + Portamento = 16, + Lfo1Rate = 21, + Lfo1Waveform = 56, + Lfo1Dest = 57, + Lfo1Level = 22, + Lfo2Rate = 23, + Lfo2Dest = 65, + ArpRange = 24, + ModEnvA = 18, + ModEnvD = 19, + ModEnvDest = 61, + ModEnvLevel = 20, + O1Waveform = 50, + O2Waveform = 51, + O2Pitch = 0, + O2PitchFine = 1, + FmDepth = 7, + O2Keytrack = 54, + PW = 6, + Sync = 52, + Mix = 2, + AmpEnvA = 12, + AmpEnvD = 13, + AmpEnvS = 14, + AmpEnvR = 15, + FilterEnvA = 8, + FilterEnvD = 9, + FilterEnvS = 10, + FilterEnvR = 11, + FilterType = 53, + Cutoff = 3, + Resonance = 4, + FilterEnvAmount = 5, + FilterVelocity = 63, + FilterKeytrack = 55, + Distortion = 52 + }; + + enum ControlChange + { + CCGain = 7, + CCOctaveShift = 17, + CCModWheelDest = 18, + CCUnison = 16, + CCVoiceMode = 15, + CCAuto = 65, + CCPortamento = 5, + CCLfo1Rate = 19, + CCLfo1Waveform = 20, + CCLfo1Dest = 21, + CCLfo1Level = 22, + CCLfo2Rate = 23, + CCLfo2Dest = 24, + CCArpRange = 25, + CCModEnvA = 26, + CCModEnvD = 27, + CCModEnvDest = 28, + CCModEnvLevel = 29, + CCO1Waveform = 30, + CCO2Waveform = 31, + CCO2Pitch = 78, + CCO2PitchFine = 33, + CCFmDepth = 70, + CCO2Keytrack = 34, + CCPW = 79, + CCSync = 35, + CCMix = 8, + CCAmpEnvA = 73, + CCAmpEnvD = 36, + CCAmpEnvS = 37, + CCAmpEnvR = 72, + CCFilterEnvA = 38, + CCFilterEnvD = 39, + CCFilterEnvS = 40, + CCFilterEnvR = 41, + CCFilterType = 44, + CCCutoff = 74, + CCResonance = 42, + CCFilterEnvAmount = 43, + CCFilterVelocity = 45, + CCFilterKeytrack = 46, + CCDistortion = 80, + }; + + enum MultiParam + { + SlotAMidiChannel = 264, + SlotALfo1Sync = 268, + SlotALfo2Sync = 272, + SlotAFilterEnvTrigger = 276, + SlotAFilterEnvTrigMidiChannel = 280, + SlotAFilterEnvTrigMidiNoteNumber = 284, + SlotAAmpEnvTrigger = 288, + SlotAAmpEnvTrigMidiChannel = 292, + SlotAAmpEnvTrigMidiNoteNumber = 296, + SlotAMorphTrigger = 300, + SlotAMorphTrigMidiChannel = 304, + SlotAMorphTrigMidiNoteNumber = 308, + BendRange = 312, + UnisonDetune, + OutModeABCD, + GlobalMidiChannel, + MidiProgramChange, + MidiControl, + MasterTune, + PedalType, + LocalControl, + KeyboardOctaveShift, + SelectedChannel, + ArpMidiOut, + SlotAChannelActive, + SlotAProgramSelect = 328, + SlotABankSelect = 332, + SlotAChannelPressureAmount = 336, + SlotAChannelPressureDest = 340, + SlotAExpressionPedalAmount = 344, + SlotAExpressionPedalDest = 348, + KeyboardSplitActive = 352, + KeyboardSplitPointPoint + }; + + static constexpr uint32_t g_sysexHeaderSize = 6; // F0, IdClavia, IdDevice, IdN2x, MsgType, MsgSpec + static constexpr uint32_t g_sysexFooterSize = 1; // F7 + static constexpr uint32_t g_sysexContainerSize = g_sysexHeaderSize + g_sysexFooterSize; + + static constexpr uint32_t g_singleDataSize = 66 * 2; // 66 parameters in two nibbles + static constexpr uint32_t g_multiDataSize = 4 * g_singleDataSize + 90 * 2; // 4 singles and 90 params in two nibbles + + static constexpr uint32_t g_singleDumpSize = g_singleDataSize + g_sysexContainerSize; + static constexpr uint32_t g_multiDumpSize = g_multiDataSize + g_sysexContainerSize; + static constexpr uint32_t g_patchRequestSize = g_sysexContainerSize; + + static constexpr uint32_t g_singleBankCount = 10; + static constexpr uint32_t g_multiBankCount = 4; + static constexpr uint32_t g_programsPerBank = 99; +} diff --git a/source/nord/n2x/n2xLib/n2xrom.cpp b/source/nord/n2x/n2xLib/n2xrom.cpp @@ -0,0 +1,28 @@ +#include "n2xrom.h" + +#include <algorithm> + +#include "synthLib/os.h" + +namespace n2x +{ + Rom::Rom() : RomData() + { + if(!isValidRom(data())) + invalidate(); + } + + Rom::Rom(const std::string& _filename) : RomData(_filename) + { + if(!isValidRom(data())) + invalidate(); + } + + bool Rom::isValidRom(std::array<unsigned char, 524288>& _data) + { + constexpr uint8_t key[] = {'n', 'r', '2', 0, 'n', 'L', '2', 0}; + + const auto it = std::search(_data.begin(), _data.end(), std::begin(key), std::end(key)); + return it != _data.end(); + } +} diff --git a/source/nord/n2x/n2xLib/n2xrom.h b/source/nord/n2x/n2xLib/n2xrom.h @@ -0,0 +1,16 @@ +#pragma once + +#include "n2xromdata.h" +#include "n2xtypes.h" + +namespace n2x +{ + class Rom : public RomData<g_romSize> + { + public: + Rom(); + Rom(const std::string& _filename); + + static bool isValidRom(std::array<uint8_t, g_romSize>& _data); + }; +} diff --git a/source/nord/n2x/n2xLib/n2xromdata.cpp b/source/nord/n2x/n2xLib/n2xromdata.cpp @@ -0,0 +1,33 @@ +#include "n2xromdata.h" + +#include "n2xtypes.h" + +#include "synthLib/os.h" + +namespace n2x +{ + template <uint32_t Size> RomData<Size>::RomData() : RomData(synthLib::findROM(MySize, MySize)) + { + } + + template <uint32_t Size> RomData<Size>::RomData(const std::string& _filename) + { + if(_filename.empty()) + return; + std::vector<uint8_t> data; + if(!synthLib::readFile(data, _filename)) + return; + if(data.size() != m_data.size()) + return; + std::copy(data.begin(), data.end(), m_data.begin()); + m_filename = _filename; + } + + template <uint32_t Size> void RomData<Size>::saveAs(const std::string& _filename) + { + synthLib::writeFile(_filename, m_data); + } + + template class RomData<g_flashSize>; + template class RomData<g_romSize>; +} diff --git a/source/nord/n2x/n2xLib/n2xromdata.h b/source/nord/n2x/n2xLib/n2xromdata.h @@ -0,0 +1,33 @@ +#pragma once + +#include <array> +#include <cstdint> +#include <string> + +namespace n2x +{ + template<uint32_t Size> + class RomData + { + public: + static constexpr uint32_t MySize = Size; + RomData(); + RomData(const std::string& _filename); + + bool isValid() const { return !m_filename.empty(); } + const auto& data() const { return m_data; } + auto& data() { return m_data; } + + void saveAs(const std::string& _filename); + + const auto& getFilename() const { return m_filename; } + + void invalidate() + { + m_filename.clear(); + } + private: + std::array<uint8_t, Size> m_data; + std::string m_filename; + }; +} diff --git a/source/nord/n2x/n2xLib/n2xromloader.cpp b/source/nord/n2x/n2xLib/n2xromloader.cpp @@ -0,0 +1,20 @@ +#include "n2xromloader.h" + +namespace n2x +{ + Rom RomLoader::findROM() + { + const auto files = findFiles(".bin", g_romSize, g_romSize); + + if(files.empty()) + return {}; + + for (const auto& file : files) + { + auto rom = Rom(file); + if(rom.isValid()) + return rom; + } + return {}; + } +} diff --git a/source/nord/n2x/n2xLib/n2xromloader.h b/source/nord/n2x/n2xLib/n2xromloader.h @@ -0,0 +1,14 @@ +#pragma once + +#include "n2xrom.h" + +#include "synthLib/romLoader.h" + +namespace n2x +{ + class RomLoader : synthLib::RomLoader + { + public: + static Rom findROM(); + }; +} diff --git a/source/nord/n2x/n2xLib/n2xstate.cpp b/source/nord/n2x/n2xLib/n2xstate.cpp @@ -0,0 +1,498 @@ +#include "n2xstate.h" + +#include <cassert> + +#include "n2xhardware.h" +#include "synthLib/midiToSysex.h" +#include "synthLib/midiTypes.h" + +namespace n2x +{ + static constexpr uint8_t g_singleDefault[] = + { + 72, // O2Pitch + 67, // O2PitchFine + 64, // Mix + 100, // Cutoff + 10, // Resonance + 0, // FilterEnvAmount + 0, // PW + 0, // FmDepth + 0, // FilterEnvA + 10, // FilterEnvD + 100, // FilterEnvS + 10, // FilterEnvR + 0, // AmpEnvA + 10, // AmpEnvD + 100, // AmpEnvS + 0, // AmpEnvR + 0, // Portamento + 127, // Gain + 0, // ModEnvA + 0, // ModEnvD + 0, // ModEnvLevel + 10, // Lfo1Rate + 0, // Lfo1Level + 10, // Lfo2Rate + 0, // ArpRange + 0, // O2PitchSens + 0, // O2PitchFineSens + 0, // MixSens + 0, // CutoffSens + 0, // ResonanceSens + 0, // FilterEnvAmountSens + 0, // PWSens + 0, // FmDepthSens + 0, // FilterEnvASens + 0, // FilterEnvDSens + 0, // FilterEnvSSens + 0, // FilterEnvRSens + 0, // AmpEnvASens + 0, // AmpEnvDSens + 0, // AmpEnvSSens + 0, // AmpEnvRSens + 0, // PortamentoSens + 0, // GainSens + 0, // ModEnvASens + 0, // ModEnvDSens + 0, // ModEnvLevelSens + 0, // Lfo1RateSens + 0, // Lfo1LevelSens + 0, // Lfo2RateSens + 0, // ArpRangeSens + 0, // O1Waveform + 0, // O2Waveform + 0, // Sync/RM/Dist + 0, // FilterType + 1, // O2Keytrack + 0, // FilterKeytrack + 0, // Lfo1Waveform + 0, // Lfo1Dest + 0, // VoiceMode + 0, // ModWheelDest + 0, // Unison + 0, // ModEnvDest + 0, // Auto + 0, // FilterVelocity + 2, // OctaveShift + 0, // Lfo2Dest + }; + + static_assert(std::size(g_singleDefault) == g_singleDataSize/2); + + using MultiDefaultData = std::array<uint8_t, ((g_multiDataSize - g_singleDataSize * 4)>>1)>; + + const std::unordered_map<ControlChange, SingleParam> g_controllerMap = + { + {ControlChange::CCGain , SingleParam::Gain }, + {ControlChange::CCOctaveShift , SingleParam::OctaveShift }, + {ControlChange::CCModWheelDest , SingleParam::ModWheelDest }, + {ControlChange::CCUnison , SingleParam::Unison }, + {ControlChange::CCVoiceMode , SingleParam::VoiceMode }, + {ControlChange::CCAuto , SingleParam::Auto }, + {ControlChange::CCPortamento , SingleParam::Portamento }, + {ControlChange::CCLfo1Rate , SingleParam::Lfo1Rate }, + {ControlChange::CCLfo1Waveform , SingleParam::Lfo1Waveform }, + {ControlChange::CCLfo1Dest , SingleParam::Lfo1Dest }, + {ControlChange::CCLfo1Level , SingleParam::Lfo1Level }, + {ControlChange::CCLfo2Rate , SingleParam::Lfo2Rate }, + {ControlChange::CCLfo2Dest , SingleParam::Lfo2Dest }, + {ControlChange::CCArpRange , SingleParam::ArpRange }, + {ControlChange::CCModEnvA , SingleParam::ModEnvA }, + {ControlChange::CCModEnvD , SingleParam::ModEnvD }, + {ControlChange::CCModEnvDest , SingleParam::ModEnvDest }, + {ControlChange::CCModEnvLevel , SingleParam::ModEnvLevel }, + {ControlChange::CCO1Waveform , SingleParam::O1Waveform }, + {ControlChange::CCO2Waveform , SingleParam::O2Waveform }, + {ControlChange::CCO2Pitch , SingleParam::O2Pitch }, + {ControlChange::CCO2PitchFine , SingleParam::O2PitchFine }, + {ControlChange::CCFmDepth , SingleParam::FmDepth }, + {ControlChange::CCO2Keytrack , SingleParam::O2Keytrack }, + {ControlChange::CCPW , SingleParam::PW }, + {ControlChange::CCSync , SingleParam::Sync }, + {ControlChange::CCMix , SingleParam::Mix }, + {ControlChange::CCAmpEnvA , SingleParam::AmpEnvA }, + {ControlChange::CCAmpEnvD , SingleParam::AmpEnvD }, + {ControlChange::CCAmpEnvS , SingleParam::AmpEnvS }, + {ControlChange::CCAmpEnvR , SingleParam::AmpEnvR }, + {ControlChange::CCFilterEnvA , SingleParam::FilterEnvA }, + {ControlChange::CCFilterEnvD , SingleParam::FilterEnvD }, + {ControlChange::CCFilterEnvS , SingleParam::FilterEnvS }, + {ControlChange::CCFilterEnvR , SingleParam::FilterEnvR }, + {ControlChange::CCFilterType , SingleParam::FilterType }, + {ControlChange::CCCutoff , SingleParam::Cutoff }, + {ControlChange::CCResonance , SingleParam::Resonance }, + {ControlChange::CCFilterEnvAmount , SingleParam::FilterEnvAmount }, + {ControlChange::CCFilterVelocity , SingleParam::FilterVelocity }, + {ControlChange::CCFilterKeytrack , SingleParam::FilterKeytrack }, + {ControlChange::CCDistortion , SingleParam::Distortion } + }; + + MultiDefaultData createMultiDefaultData() + { + uint32_t i=0; + + MultiDefaultData multi{}; + + auto set4 = [&](const uint8_t _a, const uint8_t _b, const uint8_t _c, const uint8_t _d) + { + multi[i++] = _a; multi[i++] = _b; multi[i++] = _c; multi[i++] = _d; + }; + + set4( 0, 1, 2, 3); // MIDI channel + set4( 0, 0, 0, 0); // LFO 1 Sync + set4( 0, 0, 0, 0); // LFO 2 Sync + set4( 0, 0, 0, 0); // Filter Env Trigger + set4( 0, 1, 2, 3); // Filter Env Trigger Midi Channel + set4(23,23,23,23); // Filter Env Trigger Note Number + set4( 0, 0, 0, 0); // Amp Env Trigger + set4( 0, 1, 2, 3); // Amp Env Trigger Midi Channel + set4(23,23,23,23); // Amp Env Trigger Note Number + set4( 0, 0, 0, 0); // Morph Trigger + set4( 0, 1, 2, 3); // Morph Trigger Midi Channel + set4(23,23,23,23); // Morph Trigger Note Number + multi[i++] = 2; // Bend Range + multi[i++] = 2; // Unison Detune + multi[i++] = 0; // Out Mode A&B (lower nibble) and C&D (upper nibble) + multi[i++] = 15; // Global Midi Channel + multi[i++] = 0; // Program Change Enable + multi[i++] = 1; // Midi Control + multi[i++] = 0; // Master Tune + multi[i++] = 0; // Pedal Type + multi[i++] = 1; // Local Control + multi[i++] = 2; // Keyboard Octave Shift + multi[i++] = 0; // Selected Channel + multi[i++] = 0; // Arp Midi Out + set4(1,0,0,0); // Channel Active + set4(0,0,0,0); // Program Select + set4(0,0,0,0); // Bank Select + set4(7,7,7,7); // Channel Pressure Amount + set4(0,0,0,0); // Channel Pressure Dest + set4(7,7,7,7); // Expression Pedal Amount + set4(0,0,0,0); // Expression Pedal Dest + multi[i++] = 0; // Keyboard Split + multi[i++] = 64; // Split Point + + assert(i == multi.size()); + + return multi; + } + + static const MultiDefaultData g_multiDefault = createMultiDefaultData(); + + State::State(Hardware* _hardware) : m_hardware(_hardware) + { + for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) + createDefaultSingle(m_singles[i], i); + createDefaultMulti(m_multi); + + receive(m_multi); + for (const auto& single : m_singles) + receive(single); + } + + bool State::getState(std::vector<uint8_t>& _state) + { + updateMultiFromSingles(); + _state.insert(_state.end(), m_multi.begin(), m_multi.end()); + for (const auto it : m_knobStates) + { + auto knobSysex = createKnobSysex(it.first, it.second); + _state.insert(_state.end(), knobSysex.begin(), knobSysex.end()); + } + return true; + } + + bool State::setState(const std::vector<uint8_t>& _state) + { + std::vector<std::vector<uint8_t>> msgs; + synthLib::MidiToSysex::splitMultipleSysex(msgs, _state); + + for (auto& msg : msgs) + receive(msg, synthLib::MidiEventSource::Host); + + return false; + } + + bool State::receive(std::vector<synthLib::SMidiEvent>& _responses, const synthLib::SMidiEvent& _ev) + { + if(_ev.sysex.empty()) + { + return receiveNonSysex(_ev); + } + + auto& sysex = _ev.sysex; + + if(sysex.size() <= SysexIndex::IdxMsgSpec) + return false; + + const auto bank = sysex[SysexIndex::IdxMsgType]; + const auto prog = sysex[SysexIndex::IdxMsgSpec]; + + if(sysex.size() == g_singleDumpSize) + { + if(bank != SysexByte::SingleDumpBankEditBuffer) + return false; + if(prog > m_singles.size()) + return false; + std::copy(sysex.begin(), sysex.end(), m_singles[prog].begin()); + send(_ev); + return true; + } + + if(sysex.size() == g_multiDumpSize) + { + if(bank != SysexByte::MultiDumpBankEditBuffer) + return false; + if(prog != 0) + return false; + std::copy(sysex.begin(), sysex.end(), m_multi.begin()); + send(_ev); + return true; + } + + if(bank == n2x::SysexByte::EmuSetPotPosition) + { + KnobType knobType; + uint8_t knobValue; + + if(parseKnobSysex(knobType, knobValue, sysex)) + { + if(m_hardware) + m_hardware->setKnobPosition(knobType, knobValue); + m_knobStates[knobType] = knobValue; + return true; + } + } + else if(bank == SysexByte::EmuGetPotsPosition) + { + for (const auto it : m_knobStates) + { + _responses.emplace_back(synthLib::MidiEventSource::Internal); + _responses.back().sysex = createKnobSysex(it.first, it.second); + } + return true; + } + + return false; + } + + bool State::receive(const std::vector<uint8_t>& _data, synthLib::MidiEventSource _source) + { + synthLib::SMidiEvent e; + e.sysex = _data; + e.source = _source; + return receive(e); + } + + bool State::receiveNonSysex(const synthLib::SMidiEvent& _ev) + { + switch (_ev.a & 0xf0) + { + case synthLib::M_CONTROLCHANGE: + { + const auto parts = getPartsForMidiChannel(_ev); + if(parts.empty()) + return false; + + const auto cc = static_cast<ControlChange>(_ev.b); + const auto it = g_controllerMap.find(cc); + if(it == g_controllerMap.end()) + return false; + const SingleParam param = it->second; + const auto offset = getOffsetInSingleDump(param); + switch (param) + { + case SingleParam::Sync: + // this can either be sync or distortion, they end up in the same midi byte + switch(cc) + { + case ControlChange::CCSync: + for (const auto part : parts) + { + auto v = unpackNibbles(m_singles[part], offset); + v &= ~0x3; + v |= _ev.c & 0x3; + packNibbles(m_singles[part], offset, v); + } + break; + case ControlChange::CCDistortion: + for (const auto part : parts) + { + auto v = unpackNibbles(m_singles[part], offset); + v &= ~(1<<4); + v |= _ev.c << 4; + packNibbles(m_singles[part], offset, v); + } + break; + default: + assert(false && "unexpected control change type"); + return false; + } + break; + default: + for (const auto part : parts) + packNibbles(m_singles[part], offset, _ev.c); + return true; + } + } + return false; + default: + return false; + } + } + + bool State::changeSingleParameter(const uint8_t _part, const SingleParam _parameter, const uint8_t _value) + { + if(_part >= m_singles.size()) + return false; + return changeDumpParameter(m_singles[_part], getOffsetInSingleDump(_parameter), _value); + } + + bool State::changeMultiParameter(const MultiParam _parameter, const uint8_t _value) + { + return changeDumpParameter(m_multi, getOffsetInMultiDump(_parameter), _value); + } + + void State::updateMultiFromSingles() + { + for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) + copySingleToMulti(m_multi, m_singles[i], i); + } + + void State::createDefaultSingle(SingleDump& _single, const uint8_t _program, const uint8_t _bank/* = n2x::SingleDumpBankEditBuffer*/) + { + createHeader(_single, _bank, _program); + + uint32_t o = IdxMsgSpec + 1; + + for (const auto b : g_singleDefault) + { + _single[o++] = b & 0xf; + _single[o++] = (b>>4) & 0xf; + } + } + + // ReSharper disable once CppParameterMayBeConstPtrOrRef + void State::copySingleToMulti(MultiDump& _multi, const SingleDump& _single, const uint8_t _index) + { + uint32_t i = SysexIndex::IdxMsgSpec + 1; + i += g_singleDataSize * _index; + std::copy_n(_single.begin() + g_sysexHeaderSize, g_singleDataSize, _multi.begin() + i); + } + + void State::extractSingleFromMulti(SingleDump& _single, const MultiDump& _multi, uint8_t _index) + { + uint32_t i = SysexIndex::IdxMsgSpec + 1; + i += g_singleDataSize * _index; + std::copy_n(_multi.begin() + i, g_singleDataSize, _single.begin() + g_sysexHeaderSize); + } + + void State::createDefaultMulti(MultiDump& _multi, const uint8_t _bank/* = SysexByte::MultiDumpBankEditBuffer*/) + { + createHeader(_multi, _bank, 0); + + SingleDump single; + createDefaultSingle(single, 0); + + copySingleToMulti(_multi, single, 0); + copySingleToMulti(_multi, single, 1); + copySingleToMulti(_multi, single, 2); + copySingleToMulti(_multi, single, 3); + + uint32_t i = SysexIndex::IdxMsgSpec + 1 + 4 * g_singleDataSize; + assert(i == 264 * 2 + g_sysexHeaderSize); + + for (const auto b : g_multiDefault) + { + _multi[i++] = b & 0xf; + _multi[i++] = (b>>4) & 0xf; + } + assert(i + g_sysexFooterSize == g_multiDumpSize); + } + + uint32_t State::getOffsetInSingleDump(const SingleParam _param) + { + return g_sysexHeaderSize + (_param<<1); + } + + uint32_t State::getOffsetInMultiDump(const MultiParam _param) + { + return g_sysexHeaderSize + (_param<<1); + } + + std::vector<uint8_t> State::getPartsForMidiChannel(const synthLib::SMidiEvent& _ev) const + { + const auto ch = _ev.a & 0xf; + return getPartsForMidiChannel(ch); + } + + std::vector<uint8_t> State::getPartsForMidiChannel(const uint8_t _channel) const + { + std::vector<uint8_t> res; + + for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i) + { + if(getPartMidiChannel(i) == _channel) + res.push_back(i); + } + return res; + } + + std::vector<uint8_t> State::createKnobSysex(KnobType _type, uint8_t _value) + { + return {0xf0, IdClavia, DefaultDeviceId, IdN2X, + EmuSetPotPosition, + static_cast<uint8_t>(_type), + static_cast<uint8_t>(_value >> 4), + static_cast<uint8_t>(_value & 0x0f), + 0xf7 + }; + } + + bool State::parseKnobSysex(KnobType& _type, uint8_t& _value, const std::vector<uint8_t>& _sysex) + { + if(_sysex.size() <= SysexIndex::IdxKnobPosL) + return false; + if(_sysex[SysexIndex::IdxMsgType] != SysexByte::EmuSetPotPosition) + return false; + + _type = static_cast<KnobType>(_sysex[SysexIndex::IdxMsgSpec]); + _value = static_cast<uint8_t>((_sysex[SysexIndex::IdxKnobPosH] << 4) | _sysex[SysexIndex::IdxKnobPosL]); + + return true; + } + + bool State::getKnobState(uint8_t& _result, const KnobType _type) const + { + const auto it = m_knobStates.find(_type); + if(it == m_knobStates.end()) + return false; + _result = it->second; + return true; + } + + void State::send(const synthLib::SMidiEvent& _e) const + { + if(_e.source == synthLib::MidiEventSource::Plugin) + return; + if(m_hardware) + m_hardware->sendMidi(_e); + } + + template<size_t Size> + void State::createHeader(std::array<uint8_t, Size>& _buffer, uint8_t _msgType, uint8_t _msgSpec) + { + _buffer.fill(0); + + _buffer[0] = 0xf0; + + _buffer[SysexIndex::IdxClavia] = SysexByte::IdClavia; + _buffer[SysexIndex::IdxDevice] = SysexByte::DefaultDeviceId; + _buffer[SysexIndex::IdxN2x] = SysexByte::IdN2X; + _buffer[SysexIndex::IdxMsgType] = _msgType; + _buffer[SysexIndex::IdxMsgSpec] = _msgSpec; + + _buffer.back() = 0xf7; + } +} diff --git a/source/nord/n2x/n2xLib/n2xstate.h b/source/nord/n2x/n2xLib/n2xstate.h @@ -0,0 +1,137 @@ +#pragma once + +#include <array> +#include <vector> +#include <cstddef> +#include <unordered_map> + +#include "n2xmiditypes.h" +#include "n2xtypes.h" + +#include "synthLib/midiTypes.h" + +namespace n2x +{ + class Hardware; + + class State + { + public: + using SingleDump = std::array<uint8_t, g_singleDumpSize>; + using MultiDump = std::array<uint8_t, g_multiDumpSize>; + + explicit State(Hardware* _hardware); + + bool getState(std::vector<uint8_t>& _state); + bool setState(const std::vector<uint8_t>& _state); + + bool receive(const synthLib::SMidiEvent& _ev) + { + std::vector<synthLib::SMidiEvent> responses; + return receive(responses, _ev); + } + bool receive(std::vector<synthLib::SMidiEvent>& _responses, const synthLib::SMidiEvent& _ev); + bool receive(const std::vector<uint8_t>& _data, synthLib::MidiEventSource _source); + + bool receiveNonSysex(const synthLib::SMidiEvent& _ev); + + bool changeSingleParameter(uint8_t _part, SingleParam _parameter, uint8_t _value); + bool changeMultiParameter(MultiParam _parameter, uint8_t _value); + + template<typename TDump> + bool changeDumpParameter(TDump& _dump, uint32_t _offset, uint8_t _value) + { + const auto current = unpackNibbles(_dump, _offset); + if(current == _value) + return false; + packNibbles(_dump, _offset, _value); + return true; + } + + void updateMultiFromSingles(); + + const auto& getMulti() const { return m_multi; } + const auto& getSingle(uint8_t _part) const { return m_singles[_part]; } + + const auto& updateAndGetMulti() + { + updateMultiFromSingles(); + return getMulti(); + } + + static void createDefaultSingle(SingleDump& _single, uint8_t _program, uint8_t _bank = n2x::SingleDumpBankEditBuffer); + static void copySingleToMulti(MultiDump& _multi, const SingleDump& _single, uint8_t _index); + static void extractSingleFromMulti(SingleDump& _single, const MultiDump& _multi, uint8_t _index); + static void createDefaultMulti(MultiDump& _multi, uint8_t _bank = SysexByte::MultiDumpBankEditBuffer); + + template<size_t Size> + static void createHeader(std::array<uint8_t, Size>& _buffer, uint8_t _msgType, uint8_t _msgSpec); + + static uint32_t getOffsetInSingleDump(SingleParam _param); + static uint32_t getOffsetInMultiDump(MultiParam _param); + + uint8_t getPartMidiChannel(const uint8_t _part) const + { + return getPartMidiChannel(m_multi, _part); + } + + std::vector<uint8_t> getPartsForMidiChannel(const synthLib::SMidiEvent& _ev) const; + std::vector<uint8_t> getPartsForMidiChannel(uint8_t _channel) const; + + template<typename TDump> static uint8_t getPartMidiChannel(const TDump& _dump, const uint8_t _part) + { + return getMultiParam(_dump, static_cast<MultiParam>(SlotAMidiChannel + _part), 0); + } + + uint8_t getMultiParam(const MultiParam _param, const uint8_t _part) const + { + return getMultiParam(m_multi, _param, _part); + } + + template<typename TDump> static uint8_t getMultiParam(const TDump& _dump, const MultiParam _param, const uint8_t _part) + { + const auto off = getOffsetInMultiDump(_param) + (_part << 2); + + return unpackNibbles<TDump>(_dump, off); + } + + template<typename TDump> static uint8_t getSingleParam(const TDump& _dump, const SingleParam _param, const uint8_t _part) + { + const auto off = getOffsetInSingleDump(_param) + (_part << 2); + + return unpackNibbles<TDump>(_dump, off); + } + + template<typename TDump> static uint8_t unpackNibbles(const TDump& _dump, uint32_t _off) + { + return static_cast<uint8_t>((_dump[_off] & 0xf) | (_dump[_off + 1] << 4)); + } + + template<typename TDump> static void packNibbles(TDump& _dump, uint32_t _off, const uint8_t _value) + { + _dump[_off ] = _value & 0x0f; + _dump[_off+1] = _value >> 4; + } + + static std::vector<uint8_t> createKnobSysex(KnobType _type, uint8_t _value); + static bool parseKnobSysex(KnobType& _type, uint8_t& _value, const std::vector<uint8_t>& _sysex); + + bool getKnobState(uint8_t& _result, KnobType _type) const; + + private: + template<size_t Size> bool receive(const std::array<uint8_t, Size>& _data) + { + synthLib::SMidiEvent e; + e.sysex.insert(e.sysex.begin(), _data.begin(), _data.end()); + e.source = synthLib::MidiEventSource::Host; + return receive(e); + } + + void send(const synthLib::SMidiEvent& _e) const; + + Hardware* m_hardware; + std::array<SingleDump, 4> m_singles; + MultiDump m_multi; + std::unordered_map<KnobType, uint8_t> m_knobStates; + }; +} diff --git a/source/juceUiLib/button.cpp b/source/nord/n2x/n2xLib/n2xsync.cpp diff --git a/source/nord/n2x/n2xLib/n2xsync.h b/source/nord/n2x/n2xLib/n2xsync.h @@ -0,0 +1,16 @@ +#pragma once + +#include "n2xtypes.h" + +#include "hardwareLib/syncUCtoDSP.h" + +namespace n2x +{ + class Sync : public hwLib::SyncUCtoDSP<g_ucFreqHz, g_samplerate, 16> + { + public: + Sync(dsp56k::DSP& _dsp) : SyncUCtoDSP(_dsp) + { + } + }; +} diff --git a/source/nord/n2x/n2xLib/n2xtypes.h b/source/nord/n2x/n2xLib/n2xtypes.h @@ -0,0 +1,98 @@ +#pragma once + +#include <cstdint> + +namespace n2x +{ + /* CHIP SELECTS START SIZE + CS0 = DSPs (both) $200000 $800 + CS1 = nc + CS2 = nc + CS3 = nc + CS4 = front panel $202800 $800 + CS5 = keyboard via 74HC374 TSSOP (both) $203000 $800 + CS6 = front panel $202000 $800 + CS7 = nc + CS8 = RAM A $100000 $40000 + CS9 = RAM B $100000 $40000 + CS10 = RAMs (both) $100000 $40000 + CSBOOT = BootROM $?????? $????? + */ + + static constexpr uint32_t g_romSize = 1024 * 512; + static constexpr uint32_t g_ramSize = 1024 * 256; + static constexpr uint32_t g_flashSize = 1024 * 64; + + static constexpr uint32_t g_pcInitial = 0xc2; + + static constexpr uint32_t g_romAddress = 0; + static constexpr uint32_t g_ramAddress = 0x100000; + + static constexpr uint32_t g_dspBothAddress = 0x200000; + static constexpr uint32_t g_dspAAddress = 0x200008; + static constexpr uint32_t g_dspBAddress = 0x200010; + + static constexpr uint32_t g_frontPanelAddressCS4 = 0x202800; + static constexpr uint32_t g_frontPanelAddressCS6 = 0x202000; + static constexpr uint32_t g_keyboardAddress = 0x203000; + + static constexpr uint32_t g_frontPanelSize = 0x800; + static constexpr uint32_t g_keyboardSize = 0x800; + + static constexpr uint32_t g_samplerate = 98200; + + enum class ButtonType + { + // id: MSB = address / LSB = bit mask + + Trigger = 0x00'01, + OctPlus = 0x00'02, + OctMinus = 0x00'04, + Unused0008 = 0x00'08, + Up = 0x00'10, + Down = 0x00'20, + Unused0040 = 0x00'40, + Unused0080 = 0x00'80, + + Assign = 0x02'01, + Perf = 0x02'02, + Osc2Keytrack = 0x02'04, + OscSync = 0x02'08, + FilterType = 0x02'10, + FilterVelo = 0x02'20, + FilterDist = 0x02'40, + Osc2Shape = 0x02'80, + + Distortion = 0x04'01, + Arp = 0x04'02, + Store = 0x04'04, + ModEnvDest = 0x04'08, + Lfo1Shape = 0x04'10, + Lfo1Dest = 0x04'20, + Lfo2Shape = 0x04'40, + Osc1Shape = 0x04'80, + + SlotA = 0x06'01, + SlotB = 0x06'02, + SlotC = 0x06'04, + SlotD = 0x06'08, + Poly = 0x06'10, + Unison = 0x06'20, + Portamento = 0x06'40, + ModwheelDest = 0x06'80, + + Shift = ModwheelDest + }; + + enum class KnobType + { + Invalid = 0, + Osc1Fm = 0x38, Porta, Lfo2Rate, Lfo1Rate, MasterVol, ModEnvAmt, ModEnvD, ModEnvA, + AmpEnvD = 0x58, FilterFreq, FilterEnvA, AmpEnvA, OscMix, Osc2Fine, Lfo1Amount, OscPW, + AmpGain = 0x68, FilterEnvR, AmpEnvR, FilterEnvAmt, FilterEnvS, AmpEnvS, FilterReso, FilterEnvD, + PitchBend = 0x70, ModWheel, ExpPedal, Lfo2Amount, Osc2Semi, + + First = Osc1Fm, + Last = Osc2Semi + }; +} diff --git a/source/nord/n2x/n2xTestConsole/CMakeLists.txt b/source/nord/n2x/n2xTestConsole/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.10) + +project(n2xTestConsole) + +add_executable(n2xTestConsole) + +set(SOURCES + n2xTestConsole.cpp +) + +target_sources(n2xTestConsole PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_link_libraries(n2xTestConsole PUBLIC n2xLib) +set_property(TARGET n2xTestConsole PROPERTY FOLDER "N2x") diff --git a/source/nord/n2x/n2xTestConsole/n2xTestConsole.cpp b/source/nord/n2x/n2xTestConsole/n2xTestConsole.cpp @@ -0,0 +1,77 @@ +#include <iostream> + +#include "n2xLib/n2xhardware.h" + +#include "synthLib/wavWriter.h" + +static constexpr bool g_factoryDemo = true; + +namespace n2x +{ + class Hardware; +} + +int main() +{ + std::unique_ptr<n2x::Hardware> hw; + hw.reset(new n2x::Hardware()); + + if(!hw->isValid()) + { + std::cout << "Failed to load rom file\n"; + return -1; + } + + hw->getDSPA().getDSPThread().setLogToStdout(true); + hw->getDSPB().getDSPThread().setLogToStdout(true); + + constexpr uint32_t blockSize = 64; + + std::vector<dsp56k::TWord> stereoOutput; + stereoOutput.resize(blockSize<<1); + + synthLib::AsyncWriter writer("n2xEmu_out.wav", n2x::g_samplerate, false); + + uint32_t totalSamples = 0; + + while(true) + { + hw->processAudio(blockSize, blockSize); + + totalSamples += blockSize; + + auto seconds = [&](uint32_t _seconds) + { + return _seconds * (n2x::g_samplerate / blockSize) * blockSize; + }; + + if constexpr (g_factoryDemo) + { + // Run factory demo, press shift + osc sync + if(totalSamples == seconds(4)) + { + hw->setButtonState(n2x::ButtonType::Shift, true); + hw->setButtonState(n2x::ButtonType::OscSync, true); + } + + else if(totalSamples == seconds(5)) + { + hw->setButtonState(n2x::ButtonType::Shift, false); + hw->setButtonState(n2x::ButtonType::OscSync, false); + } + } + + auto& outs = hw->getAudioOutputs(); + + for(size_t i=0; i<blockSize; ++i) + { + stereoOutput[(i<<1) ] = outs[0][i] + outs[2][i]; + stereoOutput[(i<<1)+1] = outs[1][i] + outs[3][i]; + } + + writer.append([&](std::vector<dsp56k::TWord>& _wavOut) + { + _wavOut.insert(_wavOut.end(), stereoOutput.begin(), stereoOutput.end()); + }); + } +} diff --git a/source/osTIrusJucePlugin/OsTIrusEditorState.cpp b/source/osTIrusJucePlugin/OsTIrusEditorState.cpp @@ -6,6 +6,6 @@ const std::vector<jucePluginEditorLib::PluginEditorState::Skin> g_includedSkins {"TI Trancy", "VirusTI_Trancy.json", ""} }; -OsTIrusEditorState::OsTIrusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller) : VirusEditorState(_processor, _controller, g_includedSkins) +OsTIrusEditorState::OsTIrusEditorState(virus::VirusProcessor& _processor, pluginLib::Controller& _controller) : VirusEditorState(_processor, _controller, g_includedSkins) { } diff --git a/source/osTIrusJucePlugin/OsTIrusEditorState.h b/source/osTIrusJucePlugin/OsTIrusEditorState.h @@ -2,10 +2,8 @@ #include "virusJucePlugin/VirusEditorState.h" -class VirusProcessor; - -class OsTIrusEditorState : public VirusEditorState +class OsTIrusEditorState : public virus::VirusEditorState { public: - explicit OsTIrusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller); + explicit OsTIrusEditorState(virus::VirusProcessor& _processor, pluginLib::Controller& _controller); }; diff --git a/source/osTIrusJucePlugin/OsTIrusProcessor.h b/source/osTIrusJucePlugin/OsTIrusProcessor.h @@ -2,7 +2,7 @@ #include "virusJucePlugin/VirusProcessor.h" -class OsTIrusProcessor : public VirusProcessor +class OsTIrusProcessor : public virus::VirusProcessor { public: OsTIrusProcessor(); diff --git a/source/osirusJucePlugin/OsirusEditorState.cpp b/source/osirusJucePlugin/OsirusEditorState.cpp @@ -8,6 +8,6 @@ const std::vector<jucePluginEditorLib::PluginEditorState::Skin> g_includedSkins {"Galaxpel", "VirusC_Galaxpel.json", ""} }; -OsirusEditorState::OsirusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller) : VirusEditorState(_processor, _controller, g_includedSkins) +OsirusEditorState::OsirusEditorState(virus::VirusProcessor& _processor, pluginLib::Controller& _controller) : VirusEditorState(_processor, _controller, g_includedSkins) { } diff --git a/source/osirusJucePlugin/OsirusEditorState.h b/source/osirusJucePlugin/OsirusEditorState.h @@ -2,10 +2,8 @@ #include "virusJucePlugin/VirusEditorState.h" -class VirusProcessor; - -class OsirusEditorState : public VirusEditorState +class OsirusEditorState : public virus::VirusEditorState { public: - explicit OsirusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller); + explicit OsirusEditorState(virus::VirusProcessor& _processor, pluginLib::Controller& _controller); }; diff --git a/source/osirusJucePlugin/OsirusProcessor.h b/source/osirusJucePlugin/OsirusProcessor.h @@ -2,7 +2,7 @@ #include "virusJucePlugin/VirusProcessor.h" -class OsirusProcessor : public VirusProcessor +class OsirusProcessor : public virus::VirusProcessor { public: OsirusProcessor(); diff --git a/source/synthLib/CMakeLists.txt b/source/synthLib/CMakeLists.txt @@ -10,15 +10,11 @@ add_library(synthLib STATIC) set(SOURCES audiobuffer.cpp audiobuffer.h audioTypes.h - binarystream.cpp binarystream.h buildconfig.h buildconfig.h.in - configFile.cpp configFile.h device.cpp device.h deviceException.cpp deviceException.h deviceTypes.h dspMemoryPatch.cpp dspMemoryPatch.h - hybridcontainer.h - md5.cpp md5.h midiBufferParser.cpp midiBufferParser.h midiToSysex.cpp midiToSysex.h midiTypes.h @@ -36,7 +32,7 @@ set(SOURCES target_sources(synthLib PRIVATE ${SOURCES}) source_group("source" FILES ${SOURCES}) -target_link_libraries(synthLib PUBLIC resample dsp56kEmu) +target_link_libraries(synthLib PUBLIC resample dsp56kEmu baseLib) if(NOT MSVC) target_link_libraries(synthLib PUBLIC dl) diff --git a/source/synthLib/binarystream.cpp b/source/synthLib/binarystream.cpp @@ -1,24 +0,0 @@ -#include "binarystream.h" - -namespace synthLib -{ - BinaryStream BinaryStream::readChunk() - { - Chunk chunk; - chunk.read(*this); - return std::move(chunk.data); - } - - BinaryStream BinaryStream::tryReadChunkInternal(const char* _4Cc, const uint32_t _version) - { - Chunk chunk; - chunk.read(*this); - if(chunk.version != _version) - return {}; - if(0 != strcmp(chunk.fourCC, _4Cc)) - return {}; - return std::move(chunk.data); - } - - template BinaryStream BinaryStream::tryReadChunk(char const(& _4Cc)[5], uint32_t _version); -} diff --git a/source/synthLib/binarystream.h b/source/synthLib/binarystream.h @@ -1,470 +0,0 @@ -#pragma once - -#include <cassert> -#include <cstdint> -#include <functional> -#include <sstream> -#include <vector> -#include <cstring> - -namespace synthLib -{ - class StreamBuffer - { - public: - StreamBuffer() = default; - explicit StreamBuffer(std::vector<uint8_t>&& _buffer) : m_vector(std::move(_buffer)) - { - } - explicit StreamBuffer(const size_t _capacity) - { - m_vector.reserve(_capacity); - } - StreamBuffer(uint8_t* _buffer, const size_t _size) : m_buffer(_buffer), m_size(_size), m_fixedSize(true) - { - } - StreamBuffer(StreamBuffer& _parent, const size_t _childSize) : m_buffer(&_parent.buffer()[_parent.tellg()]), m_size(_childSize), m_fixedSize(true) - { - // force eof if range is not valid - if(_parent.tellg() + _childSize > _parent.size()) - { - assert(false && "invalid range"); - m_readPos = _childSize; - } - - // seek parent forward - _parent.seekg(_parent.tellg() + _childSize); - } - StreamBuffer(StreamBuffer&& _source) noexcept - : m_buffer(_source.m_buffer) - , m_size(_source.m_size) - , m_fixedSize(_source.m_fixedSize) - , m_readPos(_source.m_readPos) - , m_writePos(_source.m_writePos) - , m_vector(std::move(_source.m_vector)) - , m_fail(_source.m_fail) - { - _source.destroy(); - } - - StreamBuffer& operator = (StreamBuffer&& _source) noexcept - { - m_buffer = _source.m_buffer; - m_size = _source.m_size; - m_fixedSize = _source.m_fixedSize; - m_readPos = _source.m_readPos; - m_writePos = _source.m_writePos; - m_vector = std::move(_source.m_vector); - - _source.destroy(); - - return *this; - } - - void seekg(const size_t _pos) { m_readPos = _pos; } - size_t tellg() const { return m_readPos; } - void seekp(const size_t _pos) { m_writePos = _pos; } - size_t tellp() const { return m_writePos; } - bool eof() const { return tellg() >= size(); } - bool fail() const { return m_fail; } - - bool read(uint8_t* _dst, size_t _size) - { - const auto remaining = size() - tellg(); - if(remaining < _size) - { - m_fail = true; - return false; - } - ::memcpy(_dst, &buffer()[m_readPos], _size); - m_readPos += _size; - return true; - } - bool write(const uint8_t* _src, size_t _size) - { - const auto remaining = size() - tellp(); - if(remaining < _size) - { - if(m_fixedSize) - { - m_fail = true; - return false; - } - m_vector.resize(tellp() + _size); - } - ::memcpy(&buffer()[m_writePos], _src, _size); - m_writePos += _size; - return true; - } - - explicit operator bool () const - { - return !eof(); - } - - private: - size_t size() const { return m_fixedSize ? m_size : m_vector.size(); } - - uint8_t* buffer() - { - if(m_fixedSize) - return m_buffer; - return m_vector.data(); - } - - void destroy() - { - m_buffer = nullptr; - m_size = 0; - m_fixedSize = false; - m_readPos = 0; - m_writePos = 0; - m_vector.clear(); - m_fail = false; - } - - uint8_t* m_buffer = nullptr; - size_t m_size = 0; - bool m_fixedSize = false; - size_t m_readPos = 0; - size_t m_writePos = 0; - std::vector<uint8_t> m_vector; - bool m_fail = false; - }; - - using StreamSizeType = uint32_t; - - class BinaryStream final : StreamBuffer - { - public: - using Base = StreamBuffer; - using SizeType = StreamSizeType; - - BinaryStream() = default; - - using StreamBuffer::operator bool; - - explicit BinaryStream(BinaryStream& _parent, SizeType _length) : StreamBuffer(_parent, _length) - { - } - - template<typename T> explicit BinaryStream(const std::vector<T>& _data) - { - Base::write(reinterpret_cast<const uint8_t*>(_data.data()), _data.size() * sizeof(T)); - seekg(0); - } - - // ___________________________________ - // tools - // - - void toVector(std::vector<uint8_t>& _buffer, const bool _append = false) - { - const auto size = tellp(); - if(size <= 0) - { - if(!_append) - _buffer.clear(); - return; - } - - seekg(0); - - if(_append) - { - const auto currentSize = _buffer.size(); - _buffer.resize(currentSize + size); - Base::read(&_buffer[currentSize], size); - } - else - { - _buffer.resize(size); - Base::read(_buffer.data(), size); - } - } - - bool checkString(const std::string& _str) - { - const auto pos = tellg(); - - const auto size = read<SizeType>(); - if (size != _str.size()) - { - seekg(pos); - return false; - } - std::string s; - s.resize(size); - Base::read(reinterpret_cast<uint8_t*>(s.data()), size); - const auto result = _str == s; - seekg(pos); - return result; - } - - uint32_t getWritePos() const { return static_cast<uint32_t>(tellp()); } - uint32_t getReadPos() const { return static_cast<uint32_t>(tellg()); } - bool endOfStream() const { return eof(); } - - void setWritePos(const uint32_t _pos) { seekp(_pos); } - void setReadPos(const uint32_t _pos) { seekg(_pos); } - - // ___________________________________ - // write - // - - template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const T& _value) - { - Base::write(reinterpret_cast<const uint8_t*>(&_value), sizeof(_value)); - } - - template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void write(const std::vector<T>& _vector) - { - const auto size = static_cast<SizeType>(_vector.size()); - write(size); - if(size) - Base::write(reinterpret_cast<const uint8_t*>(_vector.data()), sizeof(T) * size); - } - - void write(const std::string& _string) - { - const auto s = static_cast<SizeType>(_string.size()); - write(s); - Base::write(reinterpret_cast<const uint8_t*>(_string.c_str()), s); - } - - void write(const char* const _value) - { - write(std::string(_value)); - } - - template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> - void write4CC(char const(&_str)[N]) - { - write(_str[0]); - write(_str[1]); - write(_str[2]); - write(_str[3]); - } - - - // ___________________________________ - // read - // - - template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> T read() - { - T v{}; - Base::read(reinterpret_cast<uint8_t*>(&v), sizeof(v)); - checkFail(); - return v; - } - - template<typename T, typename = std::enable_if_t<std::is_trivially_copyable_v<T>>> void read(std::vector<T>& _vector) - { - const auto size = read<SizeType>(); - checkFail(); - if (!size) - { - _vector.clear(); - return; - } - _vector.resize(size); - Base::read(reinterpret_cast<uint8_t*>(_vector.data()), sizeof(T) * size); - checkFail(); - } - - std::string readString() - { - const auto size = read<SizeType>(); - std::string s; - s.resize(size); - Base::read(reinterpret_cast<uint8_t*>(s.data()), size); - checkFail(); - return s; - } - - template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> - void read4CC(char const(&_str)[N]) - { - char res[5]; - read4CC(res); - - return strcmp(res, _str) == 0; - } - - template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> - void read4CC(char (&_str)[N]) - { - _str[0] = 'E'; - _str[1] = 'R'; - _str[2] = 'R'; - _str[3] = 'R'; - _str[4] = 0; - - _str[0] = read<char>(); - _str[1] = read<char>(); - _str[2] = read<char>(); - _str[3] = read<char>(); - } - - BinaryStream readChunk(); - template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> - BinaryStream tryReadChunk(char const(&_4Cc)[N], const uint32_t _version = 1) - { - return tryReadChunkInternal(_4Cc, _version); - } - - private: - BinaryStream tryReadChunkInternal(const char* _4Cc, uint32_t _version = 1); - - - // ___________________________________ - // helpers - // - - private: - void checkFail() const - { - if(fail()) - throw std::range_error("end-of-stream"); - } - }; - - struct Chunk - { - using SizeType = BinaryStream::SizeType; - - char fourCC[5]; - uint32_t version; - SizeType length; - BinaryStream data; - - bool read(BinaryStream& _parentStream) - { - _parentStream.read4CC(fourCC); - version = _parentStream.read<uint32_t>(); - length = _parentStream.read<SizeType>(); - data = BinaryStream(_parentStream, length); - return !data.endOfStream(); - } - }; - - class ChunkWriter - { - public: - using SizeType = BinaryStream::SizeType; - - template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> - ChunkWriter(BinaryStream& _stream, char const(&_4Cc)[N], const uint32_t _version = 1) : m_stream(_stream) - { - m_stream.write4CC(_4Cc); - m_stream.write(_version); - m_lengthWritePos = m_stream.getWritePos(); - m_stream.write<SizeType>(0); - } - - ChunkWriter() = delete; - ChunkWriter(ChunkWriter&&) = delete; - ChunkWriter(const ChunkWriter&) = delete; - ChunkWriter& operator = (ChunkWriter&&) = delete; - ChunkWriter& operator = (const ChunkWriter&) = delete; - - ~ChunkWriter() - { - const auto currentWritePos = m_stream.getWritePos(); - const SizeType chunkDataLength = currentWritePos - m_lengthWritePos - sizeof(SizeType); - m_stream.setWritePos(m_lengthWritePos); - m_stream.write(chunkDataLength); - m_stream.setWritePos(currentWritePos); - } - - private: - BinaryStream& m_stream; - SizeType m_lengthWritePos = 0; - }; - - class ChunkReader - { - public: - using SizeType = ChunkWriter::SizeType; - using ChunkCallback = std::function<void(BinaryStream&, uint32_t)>; // data, version - - struct ChunkCallbackData - { - char fourCC[5]; - uint32_t expectedVersion; - ChunkCallback callback; - }; - - explicit ChunkReader(BinaryStream& _stream) : m_stream(_stream) - { - } - - template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> - void add(char const(&_4Cc)[N], const uint32_t _version, const ChunkCallback& _callback) - { - ChunkCallbackData c; - strcpy(c.fourCC, _4Cc); - c.expectedVersion = _version; - c.callback = _callback; - supportedChunks.emplace_back(std::move(c)); - } - - void read(const uint32_t _count = 0) - { - uint32_t count = 0; - - while(!m_stream.endOfStream() && (!_count || ++count <= _count)) - { - Chunk chunk; - chunk.read(m_stream); - - ++m_numChunks; - - for (const auto& chunkData : supportedChunks) - { - if(0 != strcmp(chunkData.fourCC, chunk.fourCC)) - continue; - - if(chunk.version > chunkData.expectedVersion) - break; - - ++m_numRead; - chunkData.callback(chunk.data, chunk.version); - break; - } - } - } - - bool tryRead(const uint32_t _count = 0) - { - const auto pos = m_stream.getReadPos(); - try - { - read(_count); - return true; - } - catch(std::range_error&) - { - m_stream.setReadPos(pos); - return false; - } - } - - uint32_t numRead() const - { - return m_numRead; - } - - uint32_t numChunks() const - { - return m_numChunks; - } - - private: - BinaryStream& m_stream; - std::vector<ChunkCallbackData> supportedChunks; - uint32_t m_numRead = 0; - uint32_t m_numChunks = 0; - }; -} diff --git a/source/synthLib/configFile.cpp b/source/synthLib/configFile.cpp @@ -1,61 +0,0 @@ -#include "configFile.h" - -#include <fstream> - -#include "dsp56kEmu/logging.h" - -namespace synthLib -{ - static bool needsTrim(char _c) - { - switch (_c) - { - case ' ': - case '\r': - case '\n': - case '\t': - return true; - default: - return false; - } - } - - static std::string trim(std::string _s) - { - while (!_s.empty() && needsTrim(_s.front())) - _s = _s.substr(1); - while (!_s.empty() && needsTrim(_s.back())) - _s = _s.substr(0, _s.size() - 1); - return _s; - } - - ConfigFile::ConfigFile(const char* _filename) - { - std::ifstream file(_filename, std::ios::in); - - if (!file.is_open()) - throw std::runtime_error("Failed to open config file " + std::string(_filename)); - - std::string line; - - while(std::getline(file, line)) - { - if(line.empty()) - continue; - - // // comment? - if (line.front() == '#' || line.front() == ';') - continue; - - const auto posEq = line.find('='); - - if (posEq == std::string::npos) - continue; - - const auto key = trim(line.substr(0, posEq)); - const auto val = trim(line.substr(posEq + 1)); - - m_values.emplace_back(key, val); - } - } -} -\ No newline at end of file diff --git a/source/synthLib/configFile.h b/source/synthLib/configFile.h @@ -1,20 +0,0 @@ -#pragma once - -#include <string> -#include <vector> - -namespace synthLib -{ - class ConfigFile - { - public: - explicit ConfigFile(const char* _filename); - - const std::vector<std::pair<std::string, std::string>>& getValues() const - { - return m_values; - } - private: - std::vector<std::pair<std::string, std::string>> m_values; - }; -} diff --git a/source/synthLib/device.h b/source/synthLib/device.h @@ -15,7 +15,14 @@ namespace synthLib { public: Device(); + Device(const Device&) = delete; + Device(Device&&) = delete; + virtual ~Device(); + + Device& operator = (const Device&) = delete; + Device& operator = (Device&&) = delete; + virtual void process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut); void setExtraLatencySamples(uint32_t _size); diff --git a/source/synthLib/dspMemoryPatch.cpp b/source/synthLib/dspMemoryPatch.cpp @@ -65,7 +65,7 @@ namespace synthLib return true; } - bool DspMemoryPatches::apply(dsp56k::DSP& _dsp, const MD5& _md5) const + bool DspMemoryPatches::apply(dsp56k::DSP& _dsp, const baseLib::MD5& _md5) const { for (const auto& e : allowedTargets) { diff --git a/source/synthLib/dspMemoryPatch.h b/source/synthLib/dspMemoryPatch.h @@ -1,6 +1,7 @@ #pragma once -#include "md5.h" +#include "baseLib/md5.h" + #include "dsp56kEmu/types.h" namespace dsp56k @@ -50,10 +51,10 @@ namespace synthLib struct DspMemoryPatches { - std::initializer_list<MD5> allowedTargets; + std::initializer_list<baseLib::MD5> allowedTargets; std::initializer_list<DspMemoryPatch> patches; - bool apply(dsp56k::DSP& _dsp, const MD5& _md5) const; + bool apply(dsp56k::DSP& _dsp, const baseLib::MD5& _md5) const; private: static bool apply(dsp56k::DSP& _dsp, const std::initializer_list<DspMemoryPatch>& _patches); diff --git a/source/synthLib/hybridcontainer.h b/source/synthLib/hybridcontainer.h @@ -1,29 +0,0 @@ -#pragma once -/* -#include <vector> -#include <memory_resource> -#include <array> - -namespace synthLib -{ - template<typename T, size_t S> class BufferResource - { - public: - auto& getPool() { return m_pool; } - protected: - std::array<T, S> m_buffer; - std::pmr::monotonic_buffer_resource m_pool{ std::data(m_buffer), std::size(m_buffer) }; - }; - - template<typename T, size_t S> - class HybridVector final : public BufferResource<T,S>, public std::pmr::vector<T> - { - public: - using Base = std::pmr::vector<T>; - - HybridVector() : BufferResource<T, S>(), Base(&static_cast<BufferResource<T, S>&>(*this).getPool()) - { - } - }; -} -*/ -\ No newline at end of file diff --git a/source/synthLib/md5.cpp b/source/synthLib/md5.cpp @@ -1,145 +0,0 @@ -#include "md5.h" - -#include "dsp56kEmu/logging.h" - -#include <cstring> // memcpy - -namespace synthLib -{ - // leftrotate function definition - uint32_t leftrotate(const uint32_t x, const uint32_t c) - { - return (((x) << (c)) | ((x) >> (32 - (c)))); - } - - // These vars will contain the hash: h0, h1, h2, h3 - void md5(uint32_t& _h0, uint32_t& _h1, uint32_t& _h2, uint32_t& _h3, const uint8_t *_initialMsg, const uint32_t _initialLen) - { - // Note: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating - - // r specifies the per-round shift amounts - - constexpr uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 }; - - // Use binary integer part of the sines of integers (in radians) as constants// Initialize variables: - constexpr uint32_t k[] = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; - - _h0 = 0x67452301; - _h1 = 0xefcdab89; - _h2 = 0x98badcfe; - _h3 = 0x10325476; - - // Pre-processing: adding a single 1 bit - //append "1" bit to message - /* Notice: the input bytes are considered as bits strings, - where the first bit is the most significant bit of the byte.[37] */ - - // Pre-processing: padding with zeros - //append "0" bit until message length in bit ≡ 448 (mod 512) - //append length mod (2 pow 64) to message - - const uint32_t newLen = ((((_initialLen + 8) / 64) + 1) * 64) - 8; - - std::vector<uint8_t> buffer; - buffer.resize(newLen + 64, 0); - auto* msg = buffer.data(); - memcpy(msg, _initialMsg, _initialLen); - msg[_initialLen] = 128; // write the "1" bit - - const uint32_t bitsLen = 8 * _initialLen; // note, we append the len - memcpy(msg + newLen, &bitsLen, 4); // in bits at the end of the buffer - - // Process the message in successive 512-bit chunks: - //for each 512-bit chunk of message: - for (uint32_t offset = 0; offset < newLen; offset += (512 / 8)) - { - // break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15 - const auto* w = reinterpret_cast<uint32_t*>(msg + offset); - - // Initialize hash value for this chunk: - uint32_t a = _h0; - uint32_t b = _h1; - uint32_t c = _h2; - uint32_t d = _h3; - - // Main loop: - for (uint32_t i = 0; i < 64; i++) - { - uint32_t f, g; - - if (i < 16) { - f = (b & c) | ((~b) & d); - g = i; - } - else if (i < 32) { - f = (d & b) | ((~d) & c); - g = (5 * i + 1) % 16; - } - else if (i < 48) { - f = b ^ c ^ d; - g = (3 * i + 5) % 16; - } - else { - f = c ^ (b | (~d)); - g = (7 * i) % 16; - } - - const uint32_t temp = d; - d = c; - c = b; -// printf("rotateLeft(%x + %x + %x + %x, %d)\n", a, f, k[i], w[g], r[i]); - b = b + leftrotate((a + f + k[i] + w[g]), r[i]); - a = temp; - } - - // Add this chunk's hash to result so far: - - _h0 += a; - _h1 += b; - _h2 += c; - _h3 += d; - - } - - // cleanup - // free(msg); - } - - MD5::MD5(const std::vector<uint8_t>& _data) - { - md5(m_h[0], m_h[1], m_h[2], m_h[3], _data.data(), static_cast<uint32_t>(_data.size())); - } - - std::string MD5::toString() const - { - std::stringstream ss; - - for (const auto& e : m_h) - { - ss << HEXN((e & 0xff), 2); - ss << HEXN(((e>>8u) & 0xff), 2); - ss << HEXN(((e>>16u) & 0xff), 2); - ss << HEXN(((e>>24u) & 0xff), 2); - } - return ss.str(); - } -} diff --git a/source/synthLib/md5.h b/source/synthLib/md5.h @@ -1,88 +0,0 @@ -#pragma once - -#include <array> -#include <cassert> -#include <cstdint> -#include <string> -#include <vector> - -namespace synthLib -{ - class MD5 - { - public: - static constexpr uint32_t parse1(const char _b) - { - if (_b >= '0' && _b <= '9') - return _b - '0'; - if (_b >= 'A' && _b <= 'F') - return _b - 'A' + 10; - if (_b >= 'a' && _b <= 'f') - return _b - 'a' + 10; - assert(false); - return 0; - } - static constexpr uint32_t parse2(const char _b0, const char _b1) - { - return parse1(_b1) << 4 | parse1(_b0); - } - static constexpr uint32_t parse4(const char _b0, const char _b1, const char _b2, const char _b3) - { - return parse2(_b3, _b2) << 8 | parse2(_b1, _b0); - } - static constexpr uint32_t parse8(const char _b0, const char _b1, const char _b2, const char _b3, const char _b4, const char _b5, const char _b6, const char _b7) - { - return parse4(_b4, _b5, _b6, _b7) << 16 | parse4(_b0, _b1, _b2, _b3); - } - - template<size_t N, std::enable_if_t<N == 33, void*> = nullptr> constexpr MD5(char const(&_digest)[N]) - : m_h - { - parse8(_digest[ 0], _digest[ 1], _digest[ 2], _digest[ 3], _digest[ 4], _digest[ 5], _digest[ 6], _digest[ 7]), - parse8(_digest[ 8], _digest[ 9], _digest[10], _digest[11], _digest[12], _digest[13], _digest[14], _digest[15]), - parse8(_digest[16], _digest[17], _digest[18], _digest[19], _digest[20], _digest[21], _digest[22], _digest[23]), - parse8(_digest[24], _digest[25], _digest[26], _digest[27], _digest[28], _digest[29], _digest[30], _digest[31]) - } - { - } - - explicit MD5(const std::vector<uint8_t>& _data); - - MD5() : m_h({0,0,0,0}) {} - - MD5(const MD5& _src) = default; - MD5(MD5&& _src) = default; - - ~MD5() = default; - - MD5& operator = (const MD5&) = default; - MD5& operator = (MD5&&) = default; - - std::string toString() const; - - constexpr bool operator == (const MD5& _md5) const - { - return m_h[0] == _md5.m_h[0] && m_h[1] == _md5.m_h[1] && m_h[2] == _md5.m_h[2] && m_h[3] == _md5.m_h[3]; - } - - constexpr bool operator != (const MD5& _md5) const - { - return !(*this == _md5); - } - - constexpr bool operator < (const MD5& _md5) const - { - if(m_h[0] < _md5.m_h[0]) return true; - if(m_h[0] > _md5.m_h[0]) return false; - if(m_h[1] < _md5.m_h[1]) return true; - if(m_h[1] > _md5.m_h[1]) return false; - if(m_h[2] < _md5.m_h[2]) return true; - if(m_h[2] > _md5.m_h[2]) return false; - if(m_h[3] < _md5.m_h[3]) return true; - /*if(m_h[3] > _md5.m_h[3])*/ return false; - } - - private: - std::array<uint32_t, 4> m_h; - }; -} diff --git a/source/synthLib/resamplerInOut.cpp b/source/synthLib/resamplerInOut.cpp @@ -171,25 +171,35 @@ namespace synthLib auto feedOutput = [&](const TAudioOutputs& _outs, const uint32_t _numProcessedSamples) { - m_scaledInputSize += m_in->process(m_scaledInput, m_scaledInputSize, m_channelCountIn, _numProcessedSamples, false, feedInput); + if(m_channelCountIn) + m_scaledInputSize += m_in->process(m_scaledInput, m_scaledInputSize, m_channelCountIn, _numProcessedSamples, false, feedInput); clampMidiEvents(m_processedMidiIn, m_midiIn, 0, _numProcessedSamples-1); m_midiIn.clear(); TAudioInputs inputs; - if(_numProcessedSamples > m_scaledInputSize) + + if(m_channelCountIn) { - // resampler prewarming, wants more data than we have - const auto diff = _numProcessedSamples - m_scaledInputSize; - m_scaledInput.insertZeroes(diff); - m_scaledInputSize += diff; - m_outputLatency += static_cast<uint32_t>(diff); - LOG("Resampler output latency " << m_outputLatency << " samples"); + if(_numProcessedSamples > m_scaledInputSize) + { + // resampler prewarming, wants more data than we have + const auto diff = _numProcessedSamples - m_scaledInputSize; + m_scaledInput.insertZeroes(diff); + m_scaledInputSize += diff; + m_outputLatency += static_cast<uint32_t>(diff); + LOG("Resampler output latency " << m_outputLatency << " samples"); + } + m_scaledInput.fillPointers(inputs); } - m_scaledInput.fillPointers(inputs); + _processFunc(inputs, _outs, _numProcessedSamples, m_processedMidiIn, m_midiOut); - m_scaledInput.remove(_numProcessedSamples); - m_scaledInputSize -= _numProcessedSamples; + + if(m_channelCountIn) + { + m_scaledInput.remove(_numProcessedSamples); + m_scaledInputSize -= _numProcessedSamples; + } }; const auto outputSize = m_out->process(_outputs, m_channelCountOut, _numSamples, false, feedOutput); diff --git a/source/virusJucePlugin/FxPage.cpp b/source/virusJucePlugin/FxPage.cpp @@ -9,7 +9,7 @@ namespace genericVirusUI { FxPage::FxPage(const VirusEditor& _editor) { - const auto delayReverbMode = _editor.getController().getParameterIndexByName(Virus::g_paramDelayReverbMode); + const auto delayReverbMode = _editor.getController().getParameterIndexByName(virus::g_paramDelayReverbMode); const auto p = _editor.getController().getParamValueObject(delayReverbMode, 0); if(!p) diff --git a/source/virusJucePlugin/Leds.cpp b/source/virusJucePlugin/Leds.cpp @@ -10,7 +10,7 @@ namespace genericVirusUI constexpr const char* g_lfoNames[3] = {"Lfo1LedOn", "Lfo2LedOn", "Lfo3LedOn"}; - Leds::Leds(const genericUI::Editor& _editor, VirusProcessor& _processor) : m_processor(_processor), m_logoAnimationEnabled(_processor.getConfig().getBoolValue(g_logoAnimKey, true)) + Leds::Leds(const genericUI::Editor& _editor, virus::VirusProcessor& _processor) : m_processor(_processor), m_logoAnimationEnabled(_processor.getConfig().getBoolValue(g_logoAnimKey, true)) { for(size_t i=0; i<m_lfos.size(); ++i) { diff --git a/source/virusJucePlugin/Leds.h b/source/virusJucePlugin/Leds.h @@ -12,7 +12,10 @@ namespace juce class MouseListener; } -class VirusProcessor; +namespace virus +{ + class VirusProcessor; +} namespace genericUI { @@ -41,7 +44,7 @@ namespace genericVirusUI Leds& m_leds; }; - Leds(const genericUI::Editor& _editor, VirusProcessor& _processor); + Leds(const genericUI::Editor& _editor, virus::VirusProcessor& _processor); ~Leds(); void toggleLogoAnimation(); @@ -50,7 +53,7 @@ namespace genericVirusUI bool isLogoAnimationEnabled() const { return m_logoAnimationEnabled; } private: - VirusProcessor& m_processor; + virus::VirusProcessor& m_processor; bool m_logoAnimationEnabled = true; std::array<std::unique_ptr<jucePluginEditorLib::Led>, 3> m_lfos; diff --git a/source/virusJucePlugin/ParameterNames.h b/source/virusJucePlugin/ParameterNames.h @@ -1,6 +1,6 @@ #pragma once -namespace Virus +namespace virus { static constexpr char g_paramDelayReverbMode[] = "Delay/Reverb Mode"; static constexpr char g_paramPartVolume[] = "Part Volume"; diff --git a/source/virusJucePlugin/Parts.cpp b/source/virusJucePlugin/Parts.cpp @@ -46,8 +46,8 @@ namespace genericVirusUI m_presetName[i]->initalize(static_cast<uint8_t>(i)); - const auto partVolume = _editor.getController().getParameterIndexByName(Virus::g_paramPartVolume); - const auto partPanorama = _editor.getController().getParameterIndexByName(Virus::g_paramPartPanorama); + const auto partVolume = _editor.getController().getParameterIndexByName(virus::g_paramPartVolume); + const auto partPanorama = _editor.getController().getParameterIndexByName(virus::g_paramPartPanorama); _editor.getParameterBinding().bind(*m_partVolume[i], partVolume, static_cast<uint8_t>(i)); _editor.getParameterBinding().bind(*m_partPan[i], partPanorama, static_cast<uint8_t>(i)); @@ -170,7 +170,7 @@ namespace genericVirusUI { const auto multiMode = m_editor.getController().isMultiMode(); - const auto partCount = multiMode ? static_cast<VirusProcessor&>(m_editor.getProcessor()).getPartCount() : 1; + const auto partCount = multiMode ? static_cast<virus::VirusProcessor&>(m_editor.getProcessor()).getPartCount() : 1; for(size_t i=0; i<m_partSelect.size(); ++i) { @@ -189,7 +189,7 @@ namespace genericVirusUI m_presetName[i]->setVisible(visible); } - const auto volumeParam = m_editor.getController().getParameterIndexByName(multiMode ? Virus::g_paramPartVolume : Virus::g_paramPatchVolume); + const auto volumeParam = m_editor.getController().getParameterIndexByName(multiMode ? virus::g_paramPartVolume : virus::g_paramPatchVolume); m_editor.getParameterBinding().bind(*m_partVolume[0], volumeParam, 0); m_partVolume[0]->getProperties().set("parameter", static_cast<int>(volumeParam)); } diff --git a/source/virusJucePlugin/PatchManager.cpp b/source/virusJucePlugin/PatchManager.cpp @@ -15,7 +15,7 @@ #include "juce_cryptography/hashing/juce_MD5.h" -namespace Virus +namespace virus { class Controller; } @@ -98,7 +98,7 @@ namespace genericVirusUI if (_sysex.size() < 267) return nullptr; - const auto& c = static_cast<const Virus::Controller&>(m_controller); + const auto& c = static_cast<const virus::Controller&>(m_controller); pluginLib::MidiPacket::Data data; pluginLib::MidiPacket::AnyPartParamValues parameterValues; @@ -196,7 +196,7 @@ namespace genericVirusUI pluginLib::MidiPacket::Data data; pluginLib::MidiPacket::AnyPartParamValues parameterValues; - Virus::Controller::MidiPacketType usedPacketType; + virus::Controller::MidiPacketType usedPacketType; if (!m_controller.parseSingle(data, parameterValues, _patch->sysex, usedPacketType)) return _patch->sysex; diff --git a/source/virusJucePlugin/PatchManager.h b/source/virusJucePlugin/PatchManager.h @@ -5,7 +5,7 @@ #include "virusLib/microcontrollerTypes.h" -namespace Virus +namespace virus { class Controller; } @@ -37,6 +37,6 @@ namespace genericVirusUI void addRomPatches(); pluginLib::patchDB::DataSource createRomDataSource(uint32_t _bank) const; - Virus::Controller& m_controller; + virus::Controller& m_controller; }; } diff --git a/source/virusJucePlugin/VirusController.cpp b/source/virusJucePlugin/VirusController.cpp @@ -12,7 +12,7 @@ using MessageType = virusLib::SysexMessageType; -namespace Virus +namespace virus { constexpr const char* g_midiPacketNames[] = { @@ -570,7 +570,7 @@ namespace Virus requestArrangement(); } - uint8_t Controller::getPartCount() + uint8_t Controller::getPartCount() const { return m_processor.getModel() == virusLib::DeviceModel::Snow ? 4 : 16; } @@ -651,11 +651,11 @@ namespace Virus return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); } - void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) + void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, const pluginLib::ParamValue _value) { const auto& desc = _parameter.getDescription(); - sendParameterChange(desc.page, _parameter.getPart(), desc.index, _value); + sendParameterChange(desc.page, _parameter.getPart(), desc.index, static_cast<uint8_t>(_value)); } bool Controller::sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const diff --git a/source/virusJucePlugin/VirusController.h b/source/virusJucePlugin/VirusController.h @@ -7,12 +7,10 @@ #include "virusLib/microcontrollerTypes.h" #include "virusLib/romfile.h" -#include "synthLib/plugin.h" - -class VirusProcessor; - -namespace Virus +namespace virus { + class VirusProcessor; + class Controller : public pluginLib::Controller { public: @@ -128,7 +126,7 @@ namespace Virus bool parseControllerMessage(const synthLib::SMidiEvent&) override; void onStateLoaded() override; - uint8_t getPartCount() override; + uint8_t getPartCount() const override; std::function<void(int)> onProgramChange = {}; std::function<void()> onMsgDone = {}; @@ -144,7 +142,7 @@ namespace Virus bool requestArrangement() const; void requestRomBanks(); - void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; + void sendParameterChange(const pluginLib::Parameter& _parameter, pluginLib::ParamValue _value) override; bool sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const; using pluginLib::Controller::sendSysEx; diff --git a/source/virusJucePlugin/VirusEditor.cpp b/source/virusJucePlugin/VirusEditor.cpp @@ -16,7 +16,7 @@ namespace genericVirusUI { - VirusEditor::VirusEditor(pluginLib::ParameterBinding& _binding, VirusProcessor& _processorRef, const std::string& _jsonFilename, std::string _skinFolder) : + VirusEditor::VirusEditor(pluginLib::ParameterBinding& _binding, virus::VirusProcessor& _processorRef, const std::string& _jsonFilename, std::string _skinFolder) : Editor(_processorRef, _binding, std::move(_skinFolder)), m_processor(_processorRef), m_parameterBinding(_binding), @@ -180,9 +180,9 @@ namespace genericVirusUI getController().onProgramChange = nullptr; } - Virus::Controller& VirusEditor::getController() const + virus::Controller& VirusEditor::getController() const { - return static_cast<Virus::Controller&>(m_processor.getController()); + return static_cast<virus::Controller&>(m_processor.getController()); } const char* VirusEditor::findEmbeddedResource(const std::string& _filename, uint32_t& _size) const @@ -313,7 +313,7 @@ namespace genericVirusUI { juce::PopupMenu menu; - const auto countAdded = getPatchManager()->createSaveMenuEntries(menu, getPatchManager()->getCurrentPart()); + const auto countAdded = getPatchManager()->createSaveMenuEntries(menu); if(countAdded) menu.addSeparator(); @@ -393,7 +393,7 @@ namespace genericVirusUI void VirusEditor::setPlayMode(uint8_t _playMode) { - const auto playMode = getController().getParameterIndexByName(Virus::g_paramPlayMode); + const auto playMode = getController().getParameterIndexByName(virus::g_paramPlayMode); auto* param = getController().getParameter(playMode); param->setUnnormalizedValueNotifyingHost(_playMode, pluginLib::Parameter::Origin::Ui); diff --git a/source/virusJucePlugin/VirusEditor.h b/source/virusJucePlugin/VirusEditor.h @@ -29,7 +29,10 @@ namespace pluginLib class ParameterBinding; } -class VirusProcessor; +namespace virus +{ + class VirusProcessor; +} namespace genericVirusUI { @@ -45,14 +48,14 @@ namespace genericVirusUI Arrangement }; - VirusEditor(pluginLib::ParameterBinding& _binding, VirusProcessor& _processorRef, const std::string& _jsonFilename, std::string _skinFolder); + VirusEditor(pluginLib::ParameterBinding& _binding, virus::VirusProcessor& _processorRef, const std::string& _jsonFilename, std::string _skinFolder); ~VirusEditor() override; void setPart(size_t _part); pluginLib::ParameterBinding& getParameterBinding() const { return m_parameterBinding; } - Virus::Controller& getController() const; + virus::Controller& getController() const; const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size) const; const char* findResourceByFilename(const std::string& _filename, uint32_t& _size) override; @@ -84,7 +87,7 @@ namespace genericVirusUI void savePresets(SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber = 0); bool savePresets(const std::string& _pathName, SaveType _saveType, jucePluginEditorLib::FileType _fileType, uint8_t _bankNumber = 0) const; - VirusProcessor& m_processor; + virus::VirusProcessor& m_processor; pluginLib::ParameterBinding& m_parameterBinding; std::unique_ptr<Parts> m_parts; diff --git a/source/virusJucePlugin/VirusEditorState.cpp b/source/virusJucePlugin/VirusEditorState.cpp @@ -6,119 +6,122 @@ #include "synthLib/os.h" -VirusEditorState::VirusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller, const std::vector<VirusEditorState::Skin>& _includedSkins) - : jucePluginEditorLib::PluginEditorState(_processor, _controller, _includedSkins) +namespace virus { - loadDefaultSkin(); -} - -jucePluginEditorLib::Editor* VirusEditorState::createEditor(const Skin& _skin) -{ - return new genericVirusUI::VirusEditor(m_parameterBinding, static_cast<VirusProcessor&>(m_processor), _skin.jsonFilename, _skin.folder); -} + VirusEditorState::VirusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller, const std::vector<VirusEditorState::Skin>& _includedSkins) + : jucePluginEditorLib::PluginEditorState(_processor, _controller, _includedSkins) + { + loadDefaultSkin(); + } -void VirusEditorState::initContextMenu(juce::PopupMenu& _menu) -{ - jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); - auto& p = m_processor; + jucePluginEditorLib::Editor* VirusEditorState::createEditor(const Skin& _skin) + { + return new genericVirusUI::VirusEditor(m_parameterBinding, static_cast<VirusProcessor&>(m_processor), _skin.jsonFilename, _skin.folder); + } + void VirusEditorState::initContextMenu(juce::PopupMenu& _menu) { - juce::PopupMenu gainMenu; + jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); + auto& p = m_processor; - const auto gain = m_processor.getOutputGain(); + { + juce::PopupMenu gainMenu; - gainMenu.addItem("-12 dB", true, gain == 0.25f, [&p] { p.setOutputGain(0.25f); }); - gainMenu.addItem("-6 dB", true, gain == 0.5f, [&p] { p.setOutputGain(0.5f); }); - gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); - gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); - gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); + const auto gain = m_processor.getOutputGain(); - _menu.addSubMenu("Output Gain", gainMenu); - } + gainMenu.addItem("-12 dB", true, gain == 0.25f, [&p] { p.setOutputGain(0.25f); }); + gainMenu.addItem("-6 dB", true, gain == 0.5f, [&p] { p.setOutputGain(0.5f); }); + gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); + gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); + gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); - if(const auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(getEditor())) - { - const auto& leds = editor->getLeds(); + _menu.addSubMenu("Output Gain", gainMenu); + } - if(leds && leds->supportsLogoAnimation()) + if(const auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(getEditor())) { - _menu.addItem("Enable Logo Animation", true, leds->isLogoAnimationEnabled(), [this] + const auto& leds = editor->getLeds(); + + if(leds && leds->supportsLogoAnimation()) { - const auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(getEditor()); - if(editor) + _menu.addItem("Enable Logo Animation", true, leds->isLogoAnimationEnabled(), [this] { - const auto& leds = editor->getLeds(); - leds->toggleLogoAnimation(); - } - }); + const auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(getEditor()); + if(editor) + { + const auto& leds = editor->getLeds(); + leds->toggleLogoAnimation(); + } + }); + } } } -} -bool VirusEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) -{ - jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); + bool VirusEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) + { + jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); - const auto percent = m_processor.getDspClockPercent(); - const auto hz = m_processor.getDspClockHz(); + const auto percent = m_processor.getDspClockPercent(); + const auto hz = m_processor.getDspClockHz(); - juce::PopupMenu clockMenu; + juce::PopupMenu clockMenu; - auto makeEntry = [&](const int _percent) - { - const auto mhz = hz * _percent / 100 / 1000000; - std::stringstream ss; - ss << _percent << "% (" << mhz << " MHz)"; - if(_percent == 100) - ss << " (Default)"; - clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); - }; - - makeEntry(50); - makeEntry(75); - makeEntry(100); - makeEntry(125); - makeEntry(150); - makeEntry(200); - - _menu.addSubMenu("DSP Clock", clockMenu); - - const auto samplerates = m_processor.getDeviceSupportedSamplerates(); - - if(samplerates.size() > 1) - { - juce::PopupMenu srMenu; + auto makeEntry = [&](const int _percent) + { + const auto mhz = hz * _percent / 100 / 1000000; + std::stringstream ss; + ss << _percent << "% (" << mhz << " MHz)"; + if(_percent == 100) + ss << " (Default)"; + clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); + }; - const auto current = m_processor.getPreferredDeviceSamplerate(); + makeEntry(50); + makeEntry(75); + makeEntry(100); + makeEntry(125); + makeEntry(150); + makeEntry(200); - const auto preferred = m_processor.getDevicePreferredSamplerates(); + _menu.addSubMenu("DSP Clock", clockMenu); - srMenu.addItem("Automatic (Match with host)", true, current == 0.0f, [this] { m_processor.setPreferredDeviceSamplerate(0.0f); }); - srMenu.addSeparator(); - srMenu.addSectionHeader("Official, used automatically"); + const auto samplerates = m_processor.getDeviceSupportedSamplerates(); - auto addSRs = [&](bool _usePreferred) + if(samplerates.size() > 1) { - for (const float samplerate : samplerates) + juce::PopupMenu srMenu; + + const auto current = m_processor.getPreferredDeviceSamplerate(); + + const auto preferred = m_processor.getDevicePreferredSamplerates(); + + srMenu.addItem("Automatic (Match with host)", true, current == 0.0f, [this] { m_processor.setPreferredDeviceSamplerate(0.0f); }); + srMenu.addSeparator(); + srMenu.addSectionHeader("Official, used automatically"); + + auto addSRs = [&](bool _usePreferred) { - const auto isPreferred = std::find(preferred.begin(), preferred.end(), samplerate) != preferred.end(); + for (const float samplerate : samplerates) + { + const auto isPreferred = std::find(preferred.begin(), preferred.end(), samplerate) != preferred.end(); - if(isPreferred != _usePreferred) - continue; + if(isPreferred != _usePreferred) + continue; - const auto title = std::to_string(static_cast<int>(std::floor(samplerate + 0.5f))) + " Hz"; + const auto title = std::to_string(static_cast<int>(std::floor(samplerate + 0.5f))) + " Hz"; - srMenu.addItem(title, _enabled, std::fabs(samplerate - current) < 1.0f, [this, samplerate] { m_processor.setPreferredDeviceSamplerate(samplerate); }); - } - }; + srMenu.addItem(title, _enabled, std::fabs(samplerate - current) < 1.0f, [this, samplerate] { m_processor.setPreferredDeviceSamplerate(samplerate); }); + } + }; - addSRs(true); - srMenu.addSeparator(); - srMenu.addSectionHeader("Undocumented, use with care"); - addSRs(false); + addSRs(true); + srMenu.addSeparator(); + srMenu.addSectionHeader("Undocumented, use with care"); + addSRs(false); - _menu.addSubMenu("Device Samplerate", srMenu); - } + _menu.addSubMenu("Device Samplerate", srMenu); + } - return true; -} + return true; + } +} +\ No newline at end of file diff --git a/source/virusJucePlugin/VirusEditorState.h b/source/virusJucePlugin/VirusEditorState.h @@ -2,15 +2,18 @@ #include "jucePluginEditorLib/pluginEditorState.h" -class VirusProcessor; - -class VirusEditorState : public jucePluginEditorLib::PluginEditorState +namespace virus { -public: - explicit VirusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller, const std::vector<VirusEditorState::Skin>& _includedSkins); + class VirusProcessor; + + class VirusEditorState : public jucePluginEditorLib::PluginEditorState + { + public: + explicit VirusEditorState(VirusProcessor& _processor, pluginLib::Controller& _controller, const std::vector<VirusEditorState::Skin>& _includedSkins); - jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; + jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; - void initContextMenu(juce::PopupMenu& _menu) override; - bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; -}; + void initContextMenu(juce::PopupMenu& _menu) override; + bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; + }; +} +\ No newline at end of file diff --git a/source/virusJucePlugin/VirusProcessor.cpp b/source/virusJucePlugin/VirusProcessor.cpp @@ -4,131 +4,134 @@ #include "virusLib/romloader.h" +#include "baseLib/binarystream.h" + #include "synthLib/deviceException.h" -#include "synthLib/binarystream.h" #include "synthLib/os.h" -//============================================================================== -VirusProcessor::VirusProcessor(const BusesProperties& _busesProperties, const juce::PropertiesFile::Options& _configOptions, const pluginLib::Processor::Properties& _properties, const std::vector<virusLib::ROMFile>& _roms, const virusLib::DeviceModel _defaultModel) - : Processor(_busesProperties, _configOptions, _properties) - , m_roms(_roms) - , m_defaultModel(_defaultModel) -{ -} - -VirusProcessor::~VirusProcessor() +namespace virus { - destroyController(); - destroyEditorState(); -} + VirusProcessor::VirusProcessor(const BusesProperties& _busesProperties, const juce::PropertiesFile::Options& _configOptions, const pluginLib::Processor::Properties& _properties, const std::vector<virusLib::ROMFile>& _roms, const virusLib::DeviceModel _defaultModel) + : Processor(_busesProperties, _configOptions, _properties) + , m_roms(_roms) + , m_defaultModel(_defaultModel) + { + } -//============================================================================== + VirusProcessor::~VirusProcessor() + { + destroyController(); + destroyEditorState(); + } -void VirusProcessor::processBpm(const float _bpm) -{ - // clamp to virus range, 63-190 - const auto bpmValue = juce::jmin(127, juce::jmax(0, static_cast<int>(_bpm)-63)); - const auto clockParam = getController().getParameter(m_clockTempoParam, 0); + //============================================================================== - if (clockParam == nullptr || clockParam->getUnnormalizedValue() == bpmValue) - return; + void VirusProcessor::processBpm(const float _bpm) + { + // clamp to virus range, 63-190 + const auto bpmValue = juce::jmin(127, juce::jmax(0, static_cast<int>(_bpm)-63)); + const auto clockParam = getController().getParameter(m_clockTempoParam, 0); - clockParam->setUnnormalizedValue(bpmValue, pluginLib::Parameter::Origin::HostAutomation); -} + if (clockParam == nullptr || clockParam->getUnnormalizedValue() == bpmValue) + return; -bool VirusProcessor::setSelectedRom(const uint32_t _index) -{ - if(_index >= m_roms.size()) - return false; - if(_index == m_selectedRom) - return true; - m_selectedRom = _index; + clockParam->setUnnormalizedValue(bpmValue, pluginLib::Parameter::Origin::HostAutomation); + } - try + bool VirusProcessor::setSelectedRom(const uint32_t _index) { - synthLib::Device* device = createDevice(); - getPlugin().setDevice(device); - (void)m_device.release(); - m_device.reset(device); + if(_index >= m_roms.size()) + return false; + if(_index == m_selectedRom) + return true; + m_selectedRom = _index; - evRomChanged.retain(getSelectedRom()); + try + { + synthLib::Device* device = createDevice(); + getPlugin().setDevice(device); + (void)m_device.release(); + m_device.reset(device); - return true; - } - catch(const synthLib::DeviceException& e) - { - juce::NativeMessageBox::showMessageBox(juce::MessageBoxIconType::WarningIcon, - "Device creation failed:", - std::string("Failed to create device:\n\n") + - e.what() + "\n\n" - "Will continue using old ROM"); - return false; - } -} + evRomChanged.retain(getSelectedRom()); -void VirusProcessor::postConstruct() -{ - evRomChanged.retain(getSelectedRom()); + return true; + } + catch(const synthLib::DeviceException& e) + { + juce::NativeMessageBox::showMessageBox(juce::MessageBoxIconType::WarningIcon, + "Device creation failed:", + std::string("Failed to create device:\n\n") + + e.what() + "\n\n" + "Will continue using old ROM"); + return false; + } + } - m_clockTempoParam = getController().getParameterIndexByName(Virus::g_paramClockTempo); + void VirusProcessor::postConstruct() + { + evRomChanged.retain(getSelectedRom()); - const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); - Processor::setLatencyBlocks(latencyBlocks); -} + m_clockTempoParam = getController().getParameterIndexByName(virus::g_paramClockTempo); -synthLib::Device* VirusProcessor::createDevice() -{ - const auto* rom = getSelectedRom(); + const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); + Processor::setLatencyBlocks(latencyBlocks); + } - if(!rom || !rom->isValid()) + synthLib::Device* VirusProcessor::createDevice() { - if(isTIFamily(m_defaultModel)) - throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A Virus TI firmware (.bin) is required, but was not found."); - throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A Virus A/B/C operating system (.bin or .mid) is required, but was not found."); + const auto* rom = getSelectedRom(); + + if(!rom || !rom->isValid()) + { + if(isTIFamily(m_defaultModel)) + throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A Virus TI firmware (.bin) is required, but was not found."); + throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A Virus A/B/C operating system (.bin or .mid) is required, but was not found."); + } + return new virusLib::Device(*rom, getPreferredDeviceSamplerate(), getHostSamplerate(), true); } - return new virusLib::Device(*rom, getPreferredDeviceSamplerate(), getHostSamplerate(), true); -} -pluginLib::Controller* VirusProcessor::createController() -{ - // force creation of device as the controller decides how to initialize based on the used ROM - getPlugin(); + pluginLib::Controller* VirusProcessor::createController() + { + // force creation of device as the controller decides how to initialize based on the used ROM + getPlugin(); - return new Virus::Controller(*this, m_defaultModel); -} + return new virus::Controller(*this, m_defaultModel); + } -void VirusProcessor::saveChunkData(synthLib::BinaryStream& s) -{ - auto* rom = getSelectedRom(); - if(rom) + void VirusProcessor::saveChunkData(baseLib::BinaryStream& s) { - synthLib::ChunkWriter cw(s, "ROM ", 2); - const auto romName = synthLib::getFilenameWithoutPath(rom->getFilename()); - s.write<uint8_t>(static_cast<uint8_t>(rom->getModel())); - s.write(romName); + auto* rom = getSelectedRom(); + if(rom) + { + baseLib::ChunkWriter cw(s, "ROM ", 2); + const auto romName = synthLib::getFilenameWithoutPath(rom->getFilename()); + s.write<uint8_t>(static_cast<uint8_t>(rom->getModel())); + s.write(romName); + } + Processor::saveChunkData(s); } - Processor::saveChunkData(s); -} -void VirusProcessor::loadChunkData(synthLib::ChunkReader& _cr) -{ - _cr.add("ROM ", 2, [this](synthLib::BinaryStream& _binaryStream, unsigned _version) + void VirusProcessor::loadChunkData(baseLib::ChunkReader& _cr) { - auto model = virusLib::DeviceModel::ABC; + _cr.add("ROM ", 2, [this](baseLib::BinaryStream& _binaryStream, unsigned _version) + { + auto model = virusLib::DeviceModel::ABC; - if(_version > 1) - model = static_cast<virusLib::DeviceModel>(_binaryStream.read<uint8_t>()); + if(_version > 1) + model = static_cast<virusLib::DeviceModel>(_binaryStream.read<uint8_t>()); - const auto romName = _binaryStream.readString(); + const auto romName = _binaryStream.readString(); - const auto& roms = getRoms(); - for(uint32_t i=0; i<static_cast<uint32_t>(roms.size()); ++i) - { - const auto& rom = roms[i]; - if(rom.getModel() == model && synthLib::getFilenameWithoutPath(rom.getFilename()) == romName) - setSelectedRom(i); - } - }); + const auto& roms = getRoms(); + for(uint32_t i=0; i<static_cast<uint32_t>(roms.size()); ++i) + { + const auto& rom = roms[i]; + if(rom.getModel() == model && synthLib::getFilenameWithoutPath(rom.getFilename()) == romName) + setSelectedRom(i); + } + }); - Processor::loadChunkData(_cr); -} + Processor::loadChunkData(_cr); + } +} +\ No newline at end of file diff --git a/source/virusJucePlugin/VirusProcessor.h b/source/virusJucePlugin/VirusProcessor.h @@ -9,73 +9,75 @@ #include "jucePluginEditorLib/pluginProcessor.h" -//============================================================================== -class VirusProcessor : public jucePluginEditorLib::Processor +namespace virus { -public: - VirusProcessor(const BusesProperties& _busesProperties, const juce::PropertiesFile::Options& _configOptions, const pluginLib::Processor::Properties& _properties, const std::vector<virusLib::ROMFile>& _roms, virusLib::DeviceModel _defaultModel); - ~VirusProcessor() override; + class VirusProcessor : public jucePluginEditorLib::Processor + { + public: + VirusProcessor(const BusesProperties& _busesProperties, const juce::PropertiesFile::Options& _configOptions, const pluginLib::Processor::Properties& _properties, const std::vector<virusLib::ROMFile>& _roms, virusLib::DeviceModel _defaultModel); + ~VirusProcessor() override; - void processBpm(float _bpm) override; + void processBpm(float _bpm) override; - // _____________ - // + // _____________ + // - std::string getRomName() const - { - const auto* rom = getSelectedRom(); - if(!rom) - return "<invalid>"; - return juce::File(juce::String(rom->getFilename())).getFileNameWithoutExtension().toStdString(); - } + std::string getRomName() const + { + const auto* rom = getSelectedRom(); + if(!rom) + return "<invalid>"; + return juce::File(juce::String(rom->getFilename())).getFileNameWithoutExtension().toStdString(); + } - const virusLib::ROMFile* getSelectedRom() const - { - if(m_selectedRom >= m_roms.size()) - return {}; - return &m_roms[m_selectedRom]; - } + const virusLib::ROMFile* getSelectedRom() const + { + if(m_selectedRom >= m_roms.size()) + return {}; + return &m_roms[m_selectedRom]; + } - virusLib::DeviceModel getModel() const - { - auto* rom = getSelectedRom(); - return rom ? rom->getModel() : virusLib::DeviceModel::Invalid; - } + virusLib::DeviceModel getModel() const + { + auto* rom = getSelectedRom(); + return rom ? rom->getModel() : virusLib::DeviceModel::Invalid; + } - const auto& getRoms() const { return m_roms; } + const auto& getRoms() const { return m_roms; } - bool setSelectedRom(uint32_t _index); - uint32_t getSelectedRomIndex() const { return m_selectedRom; } + bool setSelectedRom(uint32_t _index); + uint32_t getSelectedRomIndex() const { return m_selectedRom; } - uint32_t getPartCount() const - { - return getModel() == virusLib::DeviceModel::Snow ? 4 : 16; - } + uint32_t getPartCount() const + { + return getModel() == virusLib::DeviceModel::Snow ? 4 : 16; + } - virtual const char* findEmbeddedResource(const char* _name, uint32_t& _size) const = 0; + virtual const char* findEmbeddedResource(const char* _name, uint32_t& _size) const = 0; -protected: - void postConstruct(); + protected: + void postConstruct(); - // _____________ - // -private: - synthLib::Device* createDevice() override; + // _____________ + // + private: + synthLib::Device* createDevice() override; - pluginLib::Controller* createController() override; + pluginLib::Controller* createController() override; - void saveChunkData(synthLib::BinaryStream& s) override; - void loadChunkData(synthLib::ChunkReader& _cr) override; + void saveChunkData(baseLib::BinaryStream& s) override; + void loadChunkData(baseLib::ChunkReader& _cr) override; - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VirusProcessor) + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VirusProcessor) - std::vector<virusLib::ROMFile> m_roms; - const virusLib::DeviceModel m_defaultModel; - uint32_t m_selectedRom = 0; + std::vector<virusLib::ROMFile> m_roms; + const virusLib::DeviceModel m_defaultModel; + uint32_t m_selectedRom = 0; - uint32_t m_clockTempoParam = 0xffffffff; + uint32_t m_clockTempoParam = 0xffffffff; -public: - pluginLib::Event<const virusLib::ROMFile*> evRomChanged; -}; + public: + pluginLib::Event<const virusLib::ROMFile*> evRomChanged; + }; +} diff --git a/source/virusLib/dspMemoryPatches.cpp b/source/virusLib/dspMemoryPatches.cpp @@ -8,7 +8,7 @@ namespace virusLib { }; - void DspMemoryPatches::apply(const DspSingle* _dsp, const synthLib::MD5& _romChecksum) + void DspMemoryPatches::apply(const DspSingle* _dsp, const baseLib::MD5& _romChecksum) { if(!_dsp) return; diff --git a/source/virusLib/dspMemoryPatches.h b/source/virusLib/dspMemoryPatches.h @@ -9,6 +9,6 @@ namespace virusLib class DspMemoryPatches { public: - static void apply(const DspSingle* _dsp, const synthLib::MD5& _romChecksum); + static void apply(const DspSingle* _dsp, const baseLib::MD5& _romChecksum); }; } diff --git a/source/virusLib/romfile.h b/source/virusLib/romfile.h @@ -6,7 +6,7 @@ #include "dsp56kEmu/types.h" -#include "synthLib/md5.h" +#include "baseLib/md5.h" #include "deviceModel.h" @@ -137,7 +137,7 @@ private: std::string m_romFileName; std::vector<uint8_t> m_romFileData; - synthLib::MD5 m_romDataHash; + baseLib::MD5 m_romDataHash; }; } diff --git a/source/virusLib/romloader.cpp b/source/virusLib/romloader.cpp @@ -216,9 +216,11 @@ namespace virusLib if(model == DeviceModel::Invalid) { - assert(false && "retry model detection for debugging purposes below"); + // disable load if model is not detected, because we now have other synths that have roms of the same size +// assert(false && "retry model detection for debugging purposes below"); detectModel(fd.data); - model = _model; // Must be based on DSP 56362 or hell breaks loose + continue; +// model = _model; // Must be based on DSP 56362 or hell breaks loose } } diff --git a/source/wLib/CMakeLists.txt b/source/wLib/CMakeLists.txt @@ -5,14 +5,9 @@ project(wLib) add_library(wLib STATIC) set(SOURCES - am29f.cpp am29f.h - dspBootCode.h - lcd.cpp lcd.h - lcdfonts.cpp lcdfonts.h wDevice.cpp wDevice.h wDsp.cpp wDsp.h wHardware.cpp wHardware.h - wMidi.cpp wMidi.h wMidiTypes.h wPlugin.cpp wPlugin.h wRom.cpp wRom.h @@ -22,6 +17,6 @@ set(SOURCES target_sources(wLib PRIVATE ${SOURCES}) source_group("source" FILES ${SOURCES}) -target_link_libraries(wLib PUBLIC synthLib 68kEmu) +target_link_libraries(wLib PUBLIC synthLib 68kEmu hardwareLib) target_include_directories(wLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/source/wLib/am29f.cpp b/source/wLib/am29f.cpp @@ -1,139 +0,0 @@ -#include "am29f.h" - -#include <cassert> - -#include "mc68k/logging.h" -#include "mc68k/mc68k.h" - -namespace wLib -{ - Am29f::Am29f(uint8_t* _buffer, const size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr): m_buffer(_buffer), m_size(_size), m_useWriteEnable(_useWriteEnable), m_bitreverseCmdAddr(_bitreversedCmdAddr) - { - auto br = [&](uint16_t x) - { - return m_bitreverseCmdAddr ? static_cast<uint16_t>(bitreverse(x) >> 4) : x; - }; - - // Chip Erase - m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x10}}}); - - // Sector Erase - m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0x80}, {br(0x555),0xAA}, {br(0x2AA),0x55}}}); - - // Program - m_commands.push_back({{{br(0x555),0xAA}, {br(0x2AA),0x55}, {br(0x555),0xA0}}}); - } - - void Am29f::write(const uint32_t _addr, const uint16_t _data) - { - const auto reset = [this]() - { - m_currentBusCycle = 0; - m_currentCommand = -1; - }; - - if(!writeEnabled()) - { - reset(); - return; - } - - bool anyMatch = false; - - const auto d = _data & 0xff; - - for (size_t i=0; i<m_commands.size(); ++i) - { - auto& cycles = m_commands[i].cycles; - - if(m_currentBusCycle < cycles.size()) - { - const auto& c = cycles[m_currentBusCycle]; - - if(c.addr == _addr && c.data == d) - { - anyMatch = true; - - if(m_currentBusCycle == cycles.size() - 1) - m_currentCommand = static_cast<int32_t>(i); - } - } - } - - if(!anyMatch) - { - if(m_currentCommand >= 0) - { - const auto c = static_cast<CommandType>(m_currentCommand); - - execCommand(c, _addr, _data); - } - - reset(); - } - else - { - ++m_currentBusCycle; - } - } - - void Am29f::execCommand(const CommandType _command, uint32_t _addr, const uint16_t _data) - { - switch (_command) - { - case CommandType::ChipErase: - assert(false); - break; - case CommandType::SectorErase: - { - size_t sectorSizekB = 0; - switch (_addr) - { - case 0x00000: sectorSizekB = 16; break; - case 0x04000: - case 0x06000: sectorSizekB = 8; break; - case 0x08000: sectorSizekB = 32; break; - case 0x10000: - case 0x20000: - case 0x30000: - case 0x40000: - case 0x50000: - case 0x60000: - case 0x70000: sectorSizekB = 64; break; - case 0x78000: - case 0x7A000: - case 0x7C000: - // mq sends erase commands for a flash with top boot block even though a chip with bottom boot block is installed - _addr = 0x70000; - sectorSizekB = 64; - break; - default: - MCLOG("Unable to erase sector at " << MCHEX(_addr) << ", out of bounds!"); - return; - } - - MCLOG("Erasing Sector at " << MCHEX(_addr) << ", size " << MCHEX(1024 * sectorSizekB)); - for(size_t i = _addr; i< _addr + sectorSizekB * 1024; ++i) - m_buffer[i] = 0xff; - } - break; - case CommandType::Program: - { - if(_addr >= m_size) - return; - MCLOG("Programming word at " << MCHEX(_addr) << ", value " << MCHEXN(_data, 4)); - const auto old = mc68k::Mc68k::readW(m_buffer, _addr); - // "A bit cannot be programmed from a 0 back to a 1" - const auto v = _data & old; - mc68k::Mc68k::writeW(m_buffer, _addr, v); - // assert(v == _data); - break; - } - case CommandType::Invalid: - default: - assert(false); - break; - } - } - -} -\ No newline at end of file diff --git a/source/wLib/am29f.h b/source/wLib/am29f.h @@ -1,67 +0,0 @@ -#pragma once - -#include <cstdint> -#include <cstddef> -#include <vector> - -namespace wLib -{ - class Am29f - { - public: - struct BusCycle - { - uint16_t addr; - uint8_t data; - }; - - struct Command - { - std::vector<BusCycle> cycles; - }; - - enum class CommandType - { - Invalid = -1, - ChipErase, - SectorErase, - Program, - }; - - explicit Am29f(uint8_t* _buffer, size_t _size, bool _useWriteEnable, bool _bitreversedCmdAddr); - - void writeEnable(bool _writeEnable) - { - m_writeEnable = _writeEnable; - } - - void write(uint32_t _addr, uint16_t _data); - - private: - bool writeEnabled() const - { - return !m_useWriteEnable || m_writeEnable; - } - - static constexpr uint16_t bitreverse(uint16_t x) - { - x = ((x & 0xaaaau) >> 1) | static_cast<uint16_t>((x & 0x5555u) << 1); - x = ((x & 0xccccu) >> 2) | static_cast<uint16_t>((x & 0x3333u) << 2); - x = ((x & 0xf0f0u) >> 4) | static_cast<uint16_t>((x & 0x0f0fu) << 4); - - return ((x & 0xff00) >> 8) | static_cast<uint16_t>((x & 0x00ff) << 8); - } - - void execCommand(CommandType _command, uint32_t _addr, uint16_t _data); - - uint8_t* m_buffer; - const size_t m_size; - const bool m_useWriteEnable; - const bool m_bitreverseCmdAddr; - - std::vector<Command> m_commands; - bool m_writeEnable = false; - uint32_t m_currentBusCycle = 0; - int32_t m_currentCommand = -1; - }; -} -\ No newline at end of file diff --git a/source/wLib/dspBootCode.h b/source/wLib/dspBootCode.h @@ -1,74 +0,0 @@ -#pragma once - -#include <cstdint> - -namespace wLib -{ - static constexpr uint32_t g_dspBootCode[] = - { - 0x350013, 0x0afa23, 0xff0035, 0x0afa22, - 0xff000e, 0x0afa01, 0xff0022, 0x0afa20, - 0xff005e, 0x61f400, 0xff1000, 0x050c8f, - 0x0afa00, 0xff0021, 0x31a900, 0x0afa01, - 0xff0012, 0x0ad161, 0x04d191, 0x019191, - 0xff0013, 0x044894, 0x019191, 0xff0016, - 0x045094, 0x221100, 0x06c800, 0xff001f, - 0x019191, 0xff001c, 0x009814, 0x000000, - 0x050c5a, 0x050c5d, 0x62f400, 0xd00000, - 0x08f4b8, 0xd00409, 0x060680, 0xff0029, - 0x07da8a, 0x0c1c10, 0x219000, 0x219100, - 0x06c800, 0xff0033, 0x060380, 0xff0031, - 0x07da8a, 0x0c1c10, 0x07588c, 0x000000, - 0x050c46, 0x0afa02, 0xff005c, 0x0afa01, - 0xff003e, 0x0afa00, 0xff0046, 0x08f484, - 0x000038, 0x050c0b, 0x0afa20, 0xff0043, - 0x08f484, 0x005018, 0x050c06, 0x08f484, - 0x000218, 0x050c03, 0x08f484, 0x001c1e, - 0x0a8426, 0x0a8380, 0xff0049, 0x084806, - 0x0a8380, 0xff004c, 0x085006, 0x221100, - 0x06c800, 0xff0059, 0x0a83a0, 0xff0058, - 0x0a8383, 0xff0052, 0x00008c, 0x050c03, - 0x085846, 0x000000, 0x0000b9, 0x0ae180, - 0x0afa01, 0xff005f, 0x050c00, 0x66f41b, - 0xff0090, 0x0503a6, 0x04cfdd, 0x013f03, - 0x013e23, 0x045517, 0x060980, 0xff008b, - 0x07de85, 0x07de84, 0x07de86, 0x300013, - 0x70f400, 0x001600, 0x06d820, 0x4258a2, - 0x320013, 0x72f400, 0x000c00, 0x06da20, - 0x075a86, 0x300013, 0x06d800, 0xff007d, - 0x54e000, 0x200063, 0x200018, 0x5cd800, - 0x200043, 0x200018, 0x320013, 0x06da00, - 0xff0083, 0x07da8c, 0x200053, 0x200018, - 0x022d07, 0x08d73c, 0x0d104a, 0x000005, - 0x013d03, 0x00008c, 0x050c02, 0x017d03, - 0x000200, 0x000086 - }; - - static constexpr uint32_t g_dspBootCode56303[] = - { - 0x240a13, 0x0afa02, 0xff0028, 0x0afa01, - 0xff0009, 0x0afa00, 0xff0011, 0x0af080, - 0xff0014, 0x0afa20, 0xff000e, 0x08f484, - 0x005018, 0x050c09, 0x08f484, 0x000218, - 0x050c06, 0x08f484, 0x001c1e, 0x050c03, - 0x08f484, 0x000038, 0x0a8426, 0x0a8380, - 0xff0017, 0x084806, 0x0a8380, 0xff001a, - 0x085006, 0x221100, 0x06c800, 0xff0026, - 0x0a83a0, 0xff0026, 0x0a8383, 0xff0020, - 0x00008c, 0x050c02, 0x085846, 0x050c52, - 0x0afa01, 0xff0048, 0x07f41c, 0x000302, - 0x07f41b, 0x00c000, 0x07f41f, 0x000007, - 0x060680, 0xff0038, 0x019382, 0xff0032, - 0x044a98, 0x019381, 0xff0035, 0x04ca95, - 0x0c1c10, 0x219000, 0x219100, 0x06c800, - 0xff0046, 0x060380, 0xff0045, 0x019382, - 0xff003f, 0x044a98, 0x019381, 0xff0042, - 0x04ca95, 0x0c1c10, 0x07588c, 0x050c12, - 0x62f400, 0xd00000, 0x08f4b8, 0xd00409, - 0x060680, 0xff004f, 0x07da8a, 0x0c1c10, - 0x219000, 0x219100, 0x06c800, 0xff0058, - 0x060380, 0xff0057, 0x07da8a, 0x0c1c10, - 0x060380, 0xff0057, 0x07da8a, 0x0c1c10, - 0x07588c, 0x0000b9, 0x0ae180 - }; -} diff --git a/source/wLib/lcd.cpp b/source/wLib/lcd.cpp @@ -1,219 +0,0 @@ -#include "lcd.h" - -#include <cassert> - -#include "mc68k/port.h" -#include "mc68k/logging.h" - -#define LOG MCLOG - -namespace wLib -{ - LCD::LCD() = default; - - std::optional<uint8_t> LCD::exec(bool registerSelect, bool read, uint8_t g) - { - bool changed = false; - bool cgRamChanged = false; - - std::optional<uint8_t> result; - - if(!read) - { - if(!registerSelect) - { - if(g == 0x01) - { - LOG("LCD Clear Display"); - m_dramData.fill(' '); - changed = true; - } - else if(g == 0x02) - { - LOG("LCD Return Home"); - m_dramAddr = 0; - m_cursorPos = 0; - } - else if((g & 0xfc) == 0x04) - { - const int increment = (g >> 1) & 1; - const int shift = g & 1; - LOG("LCD Entry Mode Set, inc=" << increment << ", shift=" << shift); - - m_addrIncrement = increment ? 1 : -1; - } - else if((g & 0xf8) == 0x08) - { - const int displayOnOff = (g >> 2) & 1; - const int cursorOnOff = (g >> 1) & 1; - const int cursorBlinking = g & 1; - - LOG("LCD Display ON/OFF, display=" << displayOnOff << ", cursor=" << cursorOnOff << ", blinking=" << cursorBlinking); - - m_displayOn = displayOnOff != 0; - m_cursorOn = cursorOnOff != 0; - m_cursorBlinking = cursorBlinking != 0; - } - else if((g & 0xf3) == 0x10) - { - const int scrl = (g >> 2) & 3; - - LOG("LCD Cursor/Display Shift, scrl=" << scrl); - m_cursorShift = static_cast<CursorShiftMode>(scrl); - } - else if((g & 0xec) == 0x28) - { - const int dl = (g >> 4) & 1; - const int ft = g & 3; - - LOG("LCD Function Set, dl=" << dl << ", ft=" << ft); - m_dataLength = static_cast<DataLength>(dl); - m_fontTable = static_cast<FontTable>(ft); - } - else if(g & (1<<7)) - { - const int addr = g & 0x7f; -// LOG("LCD Set DDRAM address, addr=" << addr); - m_dramAddr = addr; - m_addressMode = AddressMode::DDRam; - } - else if(g & (1<<6)) - { - const int acg = g & 0x3f; - -// LOG("LCD Set CGRAM address, acg=" << acg); - m_cgramAddr = acg; - m_addressMode = AddressMode::CGRam; - } - else - { - LOG("LCD unknown command"); - assert(false); - } - } - else - { - if(m_addressMode == AddressMode::CGRam) - { -// changed = true; -// LOG("LCD write data to CGRAM addr " << m_cgramAddr << ", data=" << static_cast<int>(g)); - - if (m_cgramData[m_cgramAddr] != g) - { - m_cgramData[m_cgramAddr] = g; - cgRamChanged = true; - } - - m_cgramAddr += m_addrIncrement; - - /* - if((m_cgramAddr & 0x7) == 0) - { - std::stringstream ss; - ss << "CG RAM character " << (m_cgramAddr/8 - 1) << ':' << '\n'; - ss << "##################" << '\n'; - for(auto i = m_cgramAddr - 8; i < m_cgramAddr - 1; ++i) - { - ss << '#'; - for(int x=7; x >= 0; --x) - { - if(m_cgramData[i] & (1<<x)) - ss << "[]"; - else - ss << " "; - } - ss << '#' << '\n'; - } - ss << "##################" << '\n'; - const auto s(ss.str()); - LOG(s); - } - */ - } - else - { -// LOG("LCD write data to DDRAM addr " << m_dramAddr << ", data=" << static_cast<int>(g) << ", char=" << static_cast<char>(g)); - - const auto old = m_dramData; - - if(m_dramAddr >= 20 && m_dramAddr < 0x40) - { - for(size_t i=1; i<=20; ++i) - m_dramData[i-1] = m_dramData[i]; - m_dramData[19] = static_cast<char>(g); - } - else if(m_dramAddr > 0x53) - { - for(size_t i=21; i<=40; ++i) - m_dramData[i-1] = m_dramData[i]; - - m_dramData[39] = static_cast<char>(g); - } - else - { - if(m_dramAddr < 20) - m_dramData[m_dramAddr] = static_cast<char>(g); - else - m_dramData[m_dramAddr - 0x40 + 20] = static_cast<char>(g); - } - - if(m_dramAddr != 20 && m_dramAddr != 0x54) - m_dramAddr += m_addrIncrement; - - if(m_dramData != old) - changed = true; - } - } - } - else - { - if(registerSelect) - { - LOG("LCD read data from CGRAM or DDRAM"); - if(m_addressMode == AddressMode::CGRam) - result = m_cgramData[m_cgramAddr]; - else - result = m_dramData[m_dramAddr]; - } - else - { - LOG("LCD read busy flag & address"); - if(m_addressMode == AddressMode::CGRam) - { - result = static_cast<uint8_t>(m_cgramAddr); - } - else - { - auto a = m_dramAddr; - if(a > 0x53) - a = 0x53; - if(a == 20) - a = 19; - result = static_cast<uint8_t>(m_dramAddr); - } - } - } - - if(changed && m_changeCallback) - m_changeCallback(); - - if(cgRamChanged && m_cgRamChangeCallback) - m_cgRamChangeCallback(); - - return result; - } - - bool LCD::getCgData(std::array<uint8_t, 8>& _data, uint32_t _charIndex) const - { - const auto idx = _charIndex * 8; - if(idx + 8 >= getCgRam().size()) - return false; - - uint32_t j = 0; - - for(auto i = idx; i<idx+8; ++i) - _data[j++] = getCgRam()[i]; - - return true; - } -} diff --git a/source/wLib/lcd.h b/source/wLib/lcd.h @@ -1,88 +0,0 @@ -#pragma once - -#include <array> -#include <cstdint> -#include <functional> -#include <optional> - -namespace wLib -{ - class LCD - { - public: - using ChangeCallback = std::function<void()>; - - LCD(); - std::optional<uint8_t> exec(bool registerSelect, bool read, uint8_t g); - - const std::array<char, 40>& getDdRam() const { return m_dramData; } - const auto& getCgRam() const { return m_cgramData; } - bool getCgData(std::array<uint8_t, 8>& _data, uint32_t _charIndex) const; - - void setChangeCallback(const ChangeCallback& _callback) - { - m_changeCallback = _callback; - } - - void setCgRamChangeCallback(const ChangeCallback& _callback) - { - m_cgRamChangeCallback = _callback; - } - private: - enum class CursorShiftMode - { - CursorLeft, - CursorRight, - DisplayLeft, - DisplayRight - }; - enum class DisplayShiftMode - { - Right, - Left - }; - enum class FontTable - { - EnglishJapanese, - WesternEuropean1, - EnglishRussian, - WesternEuropean2, - }; - enum class DataLength - { - Bit8, - Bit4 - }; - - enum class AddressMode - { - DDRam, - CGRam, - }; - - uint32_t m_lastWriteCounter = 0xffffffff; - - uint32_t m_cursorPos = 0; - uint32_t m_dramAddr = 0; - uint32_t m_cgramAddr = 0; - - CursorShiftMode m_cursorShift = CursorShiftMode::CursorLeft; - DisplayShiftMode m_displayShift = DisplayShiftMode::Left; - FontTable m_fontTable = FontTable::EnglishJapanese; - DataLength m_dataLength = DataLength::Bit8; - AddressMode m_addressMode = AddressMode::DDRam; - - bool m_displayOn = true; - bool m_cursorOn = false; - bool m_cursorBlinking = false; - - int m_addrIncrement = 1; - - std::array<uint8_t, 0x40> m_cgramData{}; - std::array<char, 40> m_dramData{}; - uint32_t m_lastOpState = 0; - - ChangeCallback m_changeCallback; - ChangeCallback m_cgRamChangeCallback; - }; -} diff --git a/source/wLib/lcdfonts.cpp b/source/wLib/lcdfonts.cpp @@ -1,2675 +0,0 @@ -#include <cstdint> -#include <cstddef> - -#include <array> - -namespace wLib -{ - constexpr uint8_t g_fontTable0[] = - { - // CGRam Data, initially empty - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 0 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 1 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 2 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 3 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 4 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 5 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 6 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 7 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 8 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 9 - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 a - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 b - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 c - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 d - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 e - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, // 0 f - - 0b00000, // 1 0 - 0b10001, - 0b01001, - 0b01110, - 0b10010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 1 - 0b11110, - 0b00010, - 0b00010, - 0b00010, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 2 - 0b00110, - 0b00010, - 0b00110, - 0b01010, - 0b01010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 3 - 0b11111, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 4 - 0b11111, - 0b00001, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 5 - 0b00110, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 6 - 0b01110, - 0b00100, - 0b01000, - 0b00100, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 7 - 0b11111, - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 8 - 0b10111, - 0b10101, - 0b10101, - 0b10001, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 9 - 0b00110, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 a - 0b01111, - 0b00001, - 0b00001, - 0b00001, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 b - 0b11110, - 0b00001, - 0b00001, - 0b00001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b10000, // 1 c - 0b11111, - 0b00001, - 0b00001, - 0b00001, - 0b00110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 d - 0b11111, - 0b10001, - 0b10001, - 0b10001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 e - 0b10110, - 0b01001, - 0b10001, - 0b10001, - 0b10111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 1 f - 0b00110, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 0 - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 2 1 ! - 0b00100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b01010, // 2 2 " - 0b01010, - 0b01010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b01010, // 2 3 # - 0b01010, - 0b11111, - 0b01010, - 0b11111, - 0b01010, - 0b01010, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 2 4 $ - 0b01111, - 0b10100, - 0b01110, - 0b00101, - 0b11110, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b11000, // 2 5 % - 0b11001, - 0b00010, - 0b00100, - 0b01000, - 0b10011, - 0b00011, - 0b00000, - 0b00000, - 0b00000, - - 0b01100, // 2 6 & - 0b10010, - 0b10100, - 0b01000, - 0b10101, - 0b10010, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - - 0b01100, // 2 7 ' - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // 2 8 ( - 0b00100, - 0b01000, - 0b01000, - 0b01000, - 0b00100, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // 2 9 ) - 0b00100, - 0b00010, - 0b00010, - 0b00010, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 a * - 0b00100, - 0b10101, - 0b01110, - 0b10101, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 b + - 0b00100, - 0b00100, - 0b11111, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 c , - 0b00000, - 0b00000, - 0b00000, - 0b01100, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 d - - 0b00000, - 0b00000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 e . - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b01100, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 2 f / - 0b00001, - 0b00010, - 0b00100, - 0b01000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 3 0 0 - 0b10001, - 0b10011, - 0b10101, - 0b11001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 3 1 1 - 0b01100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 3 2 2 - 0b10001, - 0b00001, - 0b00010, - 0b00100, - 0b01000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 3 3 3 - 0b00010, - 0b00100, - 0b00010, - 0b00001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // 3 4 4 - 0b00110, - 0b01010, - 0b10010, - 0b11111, - 0b00010, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 3 5 5 - 0b10000, - 0b11110, - 0b00001, - 0b00001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00110, // 3 6 6 - 0b01000, - 0b10000, - 0b11110, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 3 7 7 - 0b00001, - 0b00010, - 0b00100, - 0b01000, - 0b01000, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 3 8 8 - 0b10001, - 0b10001, - 0b01110, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 3 9 9 - 0b10001, - 0b10001, - 0b01111, - 0b00001, - 0b00010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 3 a : - 0b01100, - 0b01100, - 0b00000, - 0b01100, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 3 b ; - 0b01100, - 0b01100, - 0b00000, - 0b01100, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // 3 c < - 0b00100, - 0b01000, - 0b10000, - 0b01000, - 0b00100, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 3 d = - 0b00000, - 0b11111, - 0b00000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // 3 e > - 0b00100, - 0b00010, - 0b00001, - 0b00010, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 3 f ? - 0b10001, - 0b00001, - 0b00010, - 0b00100, - 0b00000, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 4 0 @ - 0b10001, - 0b00001, - 0b01101, - 0b10101, - 0b10101, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 4 1 A - 0b10001, - 0b10001, - 0b10001, - 0b11111, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b11110, // 4 2 B - 0b10001, - 0b10001, - 0b11110, - 0b10001, - 0b10001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 4 3 C - 0b10001, - 0b10000, - 0b10000, - 0b10000, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b11100, // 4 4 D - 0b10010, - 0b10001, - 0b10001, - 0b10001, - 0b10010, - 0b11100, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 4 5 E - 0b10000, - 0b10000, - 0b11110, - 0b10000, - 0b10000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 4 6 F - 0b10000, - 0b10000, - 0b11110, - 0b10000, - 0b10000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 4 7 G - 0b10001, - 0b10000, - 0b10111, - 0b10001, - 0b10001, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 4 8 H - 0b10001, - 0b10001, - 0b11111, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 4 9 I - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00111, // 4 a J - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b10010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 4 b K - 0b10010, - 0b10100, - 0b11000, - 0b10100, - 0b10010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b10000, // 4 c L - 0b10000, - 0b10000, - 0b10000, - 0b10000, - 0b10000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 4 d M - 0b11011, - 0b10101, - 0b10101, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 4 e N - 0b10001, - 0b11001, - 0b10101, - 0b10011, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 4 f O - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b11110, // 5 0 P - 0b10001, - 0b10001, - 0b11110, - 0b10000, - 0b10000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 5 1 Q - 0b10001, - 0b10001, - 0b10001, - 0b10101, - 0b10010, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - - 0b11110, // 5 2 R - 0b10001, - 0b10001, - 0b11110, - 0b10100, - 0b10010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b01111, // 5 3 S - 0b10000, - 0b10000, - 0b01110, - 0b00001, - 0b00001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 5 4 T - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 5 5 U - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 5 6 V - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b01010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b10101, // 5 7 W - 0b10101, - 0b10101, - 0b10101, - 0b10101, - 0b10101, - 0b01010, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 5 8 X - 0b10001, - 0b01010, - 0b00100, - 0b01010, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 5 9 Y - 0b10001, - 0b10001, - 0b01010, - 0b00100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // 5 a Z - 0b00001, - 0b00010, - 0b00100, - 0b01000, - 0b10000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 5 b [ - 0b01000, - 0b01000, - 0b01000, - 0b01000, - 0b01000, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b10001, // 5 c - 0b01010, - 0b11111, - 0b00100, - 0b11111, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // 5 d ] - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 5 e ^ - 0b01010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 5 f _ - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // 6 0 ` - 0b00100, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 1 a - 0b00000, - 0b01110, - 0b00001, - 0b01111, - 0b10001, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - - 0b10000, // 6 2 b - 0b10000, - 0b10110, - 0b11001, - 0b10001, - 0b10001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 3 c - 0b00000, - 0b01110, - 0b10000, - 0b10000, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00001, // 6 4 d - 0b00001, - 0b01101, - 0b10011, - 0b10001, - 0b10001, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 5 e - 0b00000, - 0b01110, - 0b10001, - 0b11111, - 0b10000, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00110, // 6 6 f - 0b01001, - 0b01000, - 0b11100, - 0b01000, - 0b01000, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 7 g - 0b01111, - 0b10001, - 0b10001, - 0b01111, - 0b00001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b10000, // 6 8 h - 0b10000, - 0b10110, - 0b11001, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 6 9 i - 0b00000, - 0b01100, - 0b00100, - 0b00100, - 0b00100, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // 6 a j - 0b00000, - 0b00110, - 0b00010, - 0b00010, - 0b10010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b10000, // 6 b k - 0b10000, - 0b10010, - 0b10100, - 0b11000, - 0b10100, - 0b10010, - 0b00000, - 0b00000, - 0b00000, - - 0b01100, // 6 c l - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 d m - 0b00000, - 0b11010, - 0b10101, - 0b10101, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 e n - 0b00000, - 0b10110, - 0b11001, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 6 f o - 0b00000, - 0b01110, - 0b10001, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 0 p - 0b00000, - 0b11110, - 0b10001, - 0b11110, - 0b10000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 1 q - 0b00000, - 0b01101, - 0b10011, - 0b01111, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 2 r - 0b00000, - 0b10110, - 0b11001, - 0b10000, - 0b10000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 3 s - 0b00000, - 0b01110, - 0b10000, - 0b01110, - 0b00001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // 7 4 t - 0b01000, - 0b11100, - 0b01000, - 0b01000, - 0b01001, - 0b00110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 5 u - 0b00000, - 0b10001, - 0b10001, - 0b10001, - 0b10011, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 6 v - 0b00000, - 0b10001, - 0b10001, - 0b10001, - 0b01010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 7 w - 0b00000, - 0b10001, - 0b10001, - 0b10001, - 0b10101, - 0b01010, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 8 x - 0b00000, - 0b10001, - 0b01010, - 0b00100, - 0b01010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 9 y - 0b00000, - 0b10001, - 0b10001, - 0b01111, - 0b00001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 a z - 0b00000, - 0b11111, - 0b00010, - 0b00100, - 0b01000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // 7 b { - 0b00100, - 0b00100, - 0b01000, - 0b00100, - 0b00100, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 7 c | - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // 7 d } - 0b00100, - 0b00100, - 0b00010, - 0b00100, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 e -> - 0b00100, - 0b00010, - 0b11111, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 7 f <- - 0b00100, - 0b01000, - 0b11111, - 0b01000, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 0 - 0b00110, - 0b00010, - 0b00010, - 0b00010, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 1 - 0b11111, - 0b10001, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 2 - 0b10001, - 0b10001, - 0b01001, - 0b00101, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 3 - 0b01111, - 0b01001, - 0b01101, - 0b00001, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 4 - 0b11111, - 0b01001, - 0b01101, - 0b00001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 5 - 0b01001, - 0b00110, - 0b00010, - 0b00001, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 6 - 0b10001, - 0b01010, - 0b00100, - 0b00010, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 7 - 0b11111, - 0b00001, - 0b10001, - 0b10110, - 0b10000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 8 - 0b11111, - 0b00001, - 0b00001, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 9 - 0b10101, - 0b10101, - 0b10101, - 0b10101, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 a - 0b01111, - 0b01001, - 0b01001, - 0b01001, - 0b11001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 8 b - 0b01001, - 0b11101, - 0b00001, - 0b10001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 c - 0b00010, - 0b00000, - 0b00010, - 0b00101, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 d - 0b00000, - 0b00000, - 0b00010, - 0b00101, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // 8 e - 0b00100, - 0b01110, - 0b00000, - 0b00010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 8 f - 0b00100, - 0b00000, - 0b00110, - 0b01000, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 9 0 - 0b00101, - 0b11100, - 0b00000, - 0b00100, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 1 - 0b00000, - 0b00100, - 0b01010, - 0b00001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 2 - 0b00010, - 0b00000, - 0b10011, - 0b10011, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 3 - 0b00000, - 0b00000, - 0b00111, - 0b00101, - 0b11011, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 4 - 0b01100, - 0b00010, - 0b01001, - 0b10101, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 9 5 - 0b00000, - 0b01111, - 0b01001, - 0b00110, - 0b11001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 9 6 - 0b01001, - 0b11101, - 0b00001, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 7 - 0b00101, - 0b00000, - 0b00010, - 0b00101, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 8 - 0b00000, - 0b00000, - 0b01100, - 0b01010, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 9 - 0b01010, - 0b00000, - 0b01100, - 0b01010, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 a - 0b00000, - 0b01111, - 0b01001, - 0b00110, - 0b11001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // 9 b - 0b00000, - 0b00100, - 0b01010, - 0b00001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 c - 0b00101, - 0b00000, - 0b10011, - 0b10011, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 d - 0b00001, - 0b00001, - 0b00001, - 0b00001, - 0b11110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // 9 e - 0b00000, - 0b00000, - 0b00100, - 0b01010, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00110, // 9 f - 0b11101, - 0b00001, - 0b00001, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 0 - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 1 - 0b00000, - 0b00000, - 0b00000, - 0b11100, - 0b10100, - 0b11100, - 0b00000, - 0b00000, - 0b00000, - - 0b00111, // a 2 - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 3 - 0b00000, - 0b00000, - 0b00100, - 0b00100, - 0b00100, - 0b11100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 4 - 0b00000, - 0b00000, - 0b00000, - 0b10000, - 0b01000, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 5 - 0b00000, - 0b00000, - 0b01100, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 6 - 0b11111, - 0b00001, - 0b11111, - 0b00001, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 7 - 0b00000, - 0b11111, - 0b00001, - 0b00110, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 8 - 0b00000, - 0b00010, - 0b00100, - 0b01100, - 0b10100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a 9 - 0b00000, - 0b00100, - 0b11111, - 0b10001, - 0b00001, - 0b00110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a a - 0b00000, - 0b00000, - 0b11111, - 0b00100, - 0b00100, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a b - 0b00000, - 0b00010, - 0b11111, - 0b00110, - 0b01010, - 0b10010, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a c - 0b00000, - 0b01000, - 0b11111, - 0b01001, - 0b01010, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a d - 0b00000, - 0b00000, - 0b01110, - 0b00010, - 0b00010, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // a e - 0b00000, - 0b00000, - 0b11110, - 0b00010, - 0b11110, - 0b00010, - 0b11110, - 0b00000, - 0b00000, - - 0b00000, // a f - 0b00000, - 0b00000, - 0b10101, - 0b10101, - 0b00001, - 0b00110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b 0 - 0b00000, - 0b00000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // b 1 - 0b00001, - 0b00101, - 0b00110, - 0b00100, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00001, // b 2 - 0b00010, - 0b00100, - 0b01100, - 0b10100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // b 3 - 0b11111, - 0b10001, - 0b10001, - 0b00001, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b 4 - 0b11111, - 0b00100, - 0b00100, - 0b00100, - 0b00100, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // b 5 - 0b11111, - 0b00010, - 0b00110, - 0b01010, - 0b10010, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // b 6 - 0b11111, - 0b01001, - 0b01001, - 0b01001, - 0b01001, - 0b10010, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // b 7 - 0b11111, - 0b00100, - 0b11111, - 0b00100, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b 8 - 0b01111, - 0b01001, - 0b10001, - 0b00001, - 0b00010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // b 9 - 0b01111, - 0b10010, - 0b00010, - 0b00010, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b a - 0b11111, - 0b00001, - 0b00001, - 0b00001, - 0b00001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b01010, // b b - 0b11111, - 0b01010, - 0b01010, - 0b00010, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b c - 0b11000, - 0b00001, - 0b11001, - 0b00001, - 0b00010, - 0b11100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b d - 0b11111, - 0b00001, - 0b00010, - 0b00100, - 0b01010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // b e - 0b11111, - 0b01001, - 0b01010, - 0b01000, - 0b01000, - 0b00111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // b f - 0b10001, - 0b10001, - 0b01001, - 0b00001, - 0b00010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c 0 - 0b01111, - 0b01001, - 0b10101, - 0b00011, - 0b00010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // c 1 - 0b11100, - 0b00100, - 0b11111, - 0b00100, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c 2 - 0b10101, - 0b10101, - 0b10101, - 0b00001, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // c 3 - 0b00000, - 0b11111, - 0b00100, - 0b00100, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // c 4 - 0b01000, - 0b01000, - 0b01100, - 0b01010, - 0b01000, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // c 5 - 0b00100, - 0b11111, - 0b00100, - 0b00100, - 0b01000, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c 6 - 0b01110, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c 7 - 0b11111, - 0b00001, - 0b01010, - 0b00100, - 0b01010, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // c 8 - 0b11111, - 0b00010, - 0b00100, - 0b01110, - 0b10101, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // c 9 - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c a - 0b00100, - 0b00010, - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b10000, // c b - 0b10000, - 0b11111, - 0b10000, - 0b10000, - 0b10000, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c c - 0b11111, - 0b00001, - 0b00001, - 0b00001, - 0b00010, - 0b01100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c d - 0b01000, - 0b10100, - 0b00010, - 0b00001, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // c e - 0b11111, - 0b00100, - 0b10101, - 0b10101, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // c f - 0b11111, - 0b00001, - 0b00001, - 0b01010, - 0b00100, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 0 - 0b01110, - 0b00000, - 0b01110, - 0b00000, - 0b01110, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 1 - 0b00100, - 0b01000, - 0b10000, - 0b10001, - 0b11111, - 0b00001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 2 - 0b00001, - 0b00001, - 0b01010, - 0b00100, - 0b01010, - 0b10000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 3 - 0b11111, - 0b01000, - 0b11111, - 0b01000, - 0b01000, - 0b00111, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // d 4 - 0b01000, - 0b11111, - 0b01001, - 0b01010, - 0b01000, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 5 - 0b01110, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 6 - 0b11111, - 0b00001, - 0b11111, - 0b00001, - 0b00001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // d 7 - 0b00000, - 0b11111, - 0b00001, - 0b00001, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b10010, // d 8 - 0b10010, - 0b10010, - 0b10010, - 0b00010, - 0b00100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d 9 - 0b00100, - 0b10100, - 0b10100, - 0b10100, - 0b10101, - 0b10110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d a - 0b10000, - 0b10000, - 0b10001, - 0b10010, - 0b10100, - 0b11000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d b - 0b11111, - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d c - 0b11111, - 0b10001, - 0b10001, - 0b00001, - 0b00010, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // d d - 0b11000, - 0b00000, - 0b00001, - 0b00001, - 0b00010, - 0b11100, - 0b00000, - 0b00000, - 0b00000, - - 0b00100, // d e - 0b10010, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b11100, // d f - 0b10100, - 0b11100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // e 0 - 0b01001, - 0b10101, - 0b10010, - 0b10010, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b01010, // e 1 - 0b00000, - 0b01110, - 0b00001, - 0b01111, - 0b10001, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // e 2 - 0b00000, - 0b01110, - 0b10001, - 0b11110, - 0b10001, - 0b11110, - 0b10000, - 0b10000, - 0b10000, - - 0b00000, // e 3 - 0b00000, - 0b01110, - 0b10000, - 0b01100, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // e 4 - 0b00000, - 0b10001, - 0b10001, - 0b10001, - 0b10011, - 0b11101, - 0b10000, - 0b10000, - 0b10000, - - 0b00000, // e 5 - 0b00000, - 0b00000, - 0b01111, - 0b10100, - 0b10010, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - - 0b00000, // e 6 - 0b00000, - 0b00110, - 0b01001, - 0b10001, - 0b10001, - 0b11110, - 0b10000, - 0b10000, - 0b10000, - - 0b00000, // e 7 - 0b00000, - 0b01111, - 0b10001, - 0b10001, - 0b10001, - 0b01111, - 0b00001, - 0b00001, - 0b01110, - - 0b00000, // e 8 - 0b00000, - 0b00111, - 0b00100, - 0b00100, - 0b10100, - 0b01000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // e 9 - 0b00010, - 0b11010, - 0b00010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00010, // e a - 0b00000, - 0b00110, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b00010, - 0b10010, - 0b01100, - - 0b00000, // e b - 0b10100, - 0b01000, - 0b10100, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // e c - 0b00100, - 0b01110, - 0b10100, - 0b10101, - 0b01110, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b01000, // e d - 0b01000, - 0b11100, - 0b01000, - 0b11100, - 0b01000, - 0b01111, - 0b00000, - 0b00000, - 0b00000, - - 0b01110, // e e - 0b00000, - 0b10110, - 0b11001, - 0b10001, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b01010, // e f - 0b00000, - 0b01110, - 0b10001, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f 0 - 0b00000, - 0b10110, - 0b11001, - 0b10001, - 0b10001, - 0b11110, - 0b10000, - 0b10000, - 0b10000, - - 0b00000, // f 1 - 0b00000, - 0b01101, - 0b10011, - 0b10001, - 0b10001, - 0b01111, - 0b00001, - 0b00001, - 0b00001, - - 0b00000, // f 2 - 0b01110, - 0b10001, - 0b11111, - 0b10001, - 0b10001, - 0b01110, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f 3 - 0b00000, - 0b00000, - 0b01011, - 0b10101, - 0b11010, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f 4 - 0b00000, - 0b01110, - 0b10001, - 0b10001, - 0b01010, - 0b11011, - 0b00000, - 0b00000, - 0b00000, - - 0b01010, // f 5 - 0b00000, - 0b10001, - 0b10001, - 0b10001, - 0b10011, - 0b01101, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // f 6 - 0b10000, - 0b01000, - 0b00100, - 0b01000, - 0b10000, - 0b11111, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f 7 - 0b00000, - 0b11111, - 0b01010, - 0b01010, - 0b01010, - 0b10011, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // f 8 - 0b00000, - 0b10001, - 0b01010, - 0b00100, - 0b01010, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f 9 - 0b00000, - 0b10001, - 0b10001, - 0b10001, - 0b10001, - 0b01111, - 0b00001, - 0b00001, - 0b01110, - - 0b00000, // f a - 0b00001, - 0b11110, - 0b00100, - 0b11111, - 0b00100, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f b - 0b00000, - 0b11111, - 0b01000, - 0b01111, - 0b01001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f c - 0b00000, - 0b11111, - 0b10101, - 0b11111, - 0b10001, - 0b10001, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f d - 0b00000, - 0b00100, - 0b00000, - 0b11111, - 0b00000, - 0b00100, - 0b00000, - 0b00000, - 0b00000, - - 0b00000, // f e - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - 0b00000, - - 0b11111, // f f - 0b11111, - 0b11111, - 0b11111, - 0b11111, - 0b11111, - 0b11111, - 0b11111, - 0b11111, - 0b11111, - }; - - static_assert(std::size(g_fontTable0) == static_cast<size_t>(256 * 10)); - - const uint8_t* getCharacterData(const uint8_t _character) - { - return &g_fontTable0[static_cast<size_t>(_character) * 10]; - } -} diff --git a/source/wLib/lcdfonts.h b/source/wLib/lcdfonts.h @@ -1,8 +0,0 @@ -#pragma once - -#include <cstdint> - -namespace wLib -{ - const uint8_t* getCharacterData(uint8_t _character); -} diff --git a/source/wLib/wHardware.cpp b/source/wLib/wHardware.cpp @@ -1,10 +1,11 @@ #include "wHardware.h" -#include "wMidi.h" #include "dsp56kEmu/audio.h" #include "synthLib/midiBufferParser.h" +#include "hardwareLib/sciMidi.h" + #include "mc68k/mc68k.h" namespace wLib @@ -70,7 +71,7 @@ namespace wLib void Hardware::receiveMidi(std::vector<uint8_t>& _data) { - getMidi().readTransmitBuffer(_data); + getMidi().read(_data); } void Hardware::onEsaiCallback(dsp56k::Audio& _audio) @@ -157,20 +158,7 @@ namespace wLib if(e.offset > m_midiOffsetCounter) break; - if(!e.sysex.empty()) - { - getMidi().writeMidi(e.sysex); - } - else - { - getMidi().writeMidi(e.a); - const auto len = synthLib::MidiBufferParser::lengthFromStatusByte(e.a); - if (len > 1) - getMidi().writeMidi(e.b); - if (len > 2) - getMidi().writeMidi(e.c); - } - + getMidi().write(e); m_midiIn.pop_front(); } } diff --git a/source/wLib/wHardware.h b/source/wLib/wHardware.h @@ -8,6 +8,11 @@ #include "synthLib/midiTypes.h" +namespace hwLib +{ + class SciMidi; +} + namespace mc68k { class Mc68k; @@ -20,15 +25,13 @@ namespace dsp56k namespace wLib { - class Midi; - class Hardware { public: Hardware(const double& _samplerate); virtual ~Hardware() = default; - virtual Midi& getMidi() = 0; + virtual hwLib::SciMidi& getMidi() = 0; virtual mc68k::Mc68k& getUc() = 0; void haltDSP(); diff --git a/source/wLib/wMidi.cpp b/source/wLib/wMidi.cpp @@ -1,101 +0,0 @@ -#include "wMidi.h" - -#include <deque> - -#include "mc68k/qsm.h" - -namespace wLib -{ - // pause 0.1 seconds for a sysex size of 500, delay is calculated for other sysex sizes accordingly - static constexpr float g_sysexSendDelaySeconds = 0.1f; - static constexpr uint32_t g_sysexSendDelaySize = 500; - - Midi::Midi(mc68k::Qsm& _qsm) : m_qsm(_qsm) - { - } - - void Midi::process(const uint32_t _numSamples) - { - std::unique_lock lock(m_mutex); - - if(m_readingSysex) - return; - - auto remainingSamples = _numSamples; - - while(!m_pendingSysexBuffers.empty()) - { - if(m_remainingSysexDelay > 0) - { - const auto sub = std::min(m_remainingSysexDelay, remainingSamples); - remainingSamples -= sub; - - m_remainingSysexDelay -= sub; - } - - if(m_remainingSysexDelay) - break; - - const auto& msg = m_pendingSysexBuffers.front(); - - for (const auto b : msg) - m_qsm.writeSciRX(b); - - m_remainingSysexDelay = static_cast<uint32_t>(static_cast<float>(msg.size()) * 44100.0f * g_sysexSendDelaySeconds / static_cast<float>(g_sysexSendDelaySize)); - - m_pendingSysexBuffers.pop_front(); - } - } - - void Midi::writeMidi(const uint8_t _byte) - { - std::unique_lock lock(m_mutex); - - if(_byte == 0xf0) - { - m_writingSysex = true; - } - - if(m_writingSysex) - { - m_pendingSysexMessage.push_back(_byte); - } - else - { - m_qsm.writeSciRX(_byte); - } - - if (_byte == 0xf7) - { - m_writingSysex = false; - - if (!m_pendingSysexMessage.empty()) - m_pendingSysexBuffers.push_back(std::move(m_pendingSysexMessage)); - - m_pendingSysexMessage.clear(); - } - } - - void Midi::readTransmitBuffer(std::vector<uint8_t>& _result) - { - std::deque<uint16_t> midiData; - m_qsm.readSciTX(midiData); - if (midiData.empty()) - return; - - _result.clear(); - _result.reserve(midiData.size()); - - for (const auto data : midiData) - { - const uint8_t d = data & 0xff; - - if(d == 0xf0) - m_readingSysex = true; - else if(d == 0xf7) - m_readingSysex = false; - - _result.push_back(d); - } - } -} diff --git a/source/wLib/wMidi.h b/source/wLib/wMidi.h @@ -1,46 +0,0 @@ -#pragma once - -#include <deque> -#include <vector> -#include <cstdint> -#include <mutex> - -namespace mc68k -{ - class Qsm; -} - -namespace wLib -{ - class Midi - { - public: - explicit Midi(mc68k::Qsm& _qsm); - - void process(uint32_t _numSamples); - - void writeMidi(uint8_t _byte); - void writeMidi(const std::initializer_list<uint8_t>& _bytes) - { - for (const uint8_t byte : _bytes) - writeMidi(byte); - } - void writeMidi(const std::vector<uint8_t>& _bytes) - { - for (const uint8_t byte : _bytes) - writeMidi(byte); - } - void readTransmitBuffer(std::vector<uint8_t>& _result); - - private: - mc68k::Qsm& m_qsm; - - bool m_readingSysex = false; - bool m_writingSysex = false; - uint32_t m_remainingSysexDelay = 0; - - std::deque< std::vector<uint8_t> > m_pendingSysexBuffers; - std::vector<uint8_t> m_pendingSysexMessage; - std::mutex m_mutex; - }; -} diff --git a/source/wLib/wRom.cpp b/source/wLib/wRom.cpp @@ -27,7 +27,10 @@ namespace wLib m_buffer.resize(_expectedSize, 0xff); } - return m_buffer.size() == _expectedSize; + if(m_buffer.size() != _expectedSize) + return false; + m_filename = _filename; + return true; } bool ROM::loadFromMidi(std::vector<unsigned char>& _buffer, const std::string& _filename) diff --git a/source/wLib/wRom.h b/source/wLib/wRom.h @@ -9,6 +9,7 @@ namespace wLib class ROM { public: + ROM() = default; explicit ROM(const std::string& _filename, const uint32_t _expectedSize, std::vector<uint8_t> _data = {}) : m_buffer(std::move(_data)) { if (m_buffer.size() != _expectedSize) @@ -20,6 +21,10 @@ namespace wLib bool isValid() const { return !m_buffer.empty(); } virtual uint32_t getSize() const = 0; + void clear() { m_buffer.clear(); } + + const auto& getFilename() const { return m_filename; } + static bool loadFromMidi(std::vector<uint8_t>& _buffer, const std::string& _filename); static bool loadFromMidiData(std::vector<uint8_t>& _buffer, const std::vector<uint8_t>& _midiData); static bool loadFromSysExFile(std::vector<uint8_t>& _buffer, const std::string& _filename); @@ -29,5 +34,6 @@ namespace wLib bool loadFromFile(const std::string& _filename, uint32_t _expectedSize); std::vector<uint8_t> m_buffer; + std::string m_filename; }; } diff --git a/source/xtJucePlugin/PluginEditorState.cpp b/source/xtJucePlugin/PluginEditorState.cpp @@ -5,66 +5,69 @@ #include "synthLib/os.h" -const std::vector<PluginEditorState::Skin> g_includedSkins = +namespace xtJucePlugin { - {"XT", "xtDefault.json", ""}, -}; + const std::vector<PluginEditorState::Skin> g_includedSkins = + { + {"XT", "xtDefault.json", ""}, + }; -PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor) : jucePluginEditorLib::PluginEditorState(_processor, _processor.getController(), g_includedSkins) -{ - loadDefaultSkin(); -} + PluginEditorState::PluginEditorState(AudioPluginAudioProcessor& _processor) : jucePluginEditorLib::PluginEditorState(_processor, _processor.getController(), g_includedSkins) + { + loadDefaultSkin(); + } -void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) -{ - jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); + void PluginEditorState::initContextMenu(juce::PopupMenu& _menu) + { + jucePluginEditorLib::PluginEditorState::initContextMenu(_menu); - auto& p = m_processor; + auto& p = m_processor; - const auto gain = static_cast<int>(std::roundf(p.getOutputGain())); + const auto gain = static_cast<int>(std::roundf(p.getOutputGain())); - juce::PopupMenu gainMenu; + juce::PopupMenu gainMenu; - gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); - gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); - gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); + gainMenu.addItem("0 dB (default)", true, gain == 1, [&p] { p.setOutputGain(1); }); + gainMenu.addItem("+6 dB", true, gain == 2, [&p] { p.setOutputGain(2); }); + gainMenu.addItem("+12 dB", true, gain == 4, [&p] { p.setOutputGain(4); }); - _menu.addSubMenu("Output Gain", gainMenu); -} + _menu.addSubMenu("Output Gain", gainMenu); + } -bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) -{ - jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); + bool PluginEditorState::initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) + { + jucePluginEditorLib::PluginEditorState::initAdvancedContextMenu(_menu, _enabled); - const auto percent = m_processor.getDspClockPercent(); - const auto hz = m_processor.getDspClockHz(); + const auto percent = m_processor.getDspClockPercent(); + const auto hz = m_processor.getDspClockHz(); - juce::PopupMenu clockMenu; + juce::PopupMenu clockMenu; - auto makeEntry = [&](const int _percent) - { - const auto mhz = hz * _percent / 100 / 1000000; - std::stringstream ss; - ss << _percent << "% (" << mhz << " MHz)"; - if(_percent == 100) - ss << " (Default)"; - clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); - }; + auto makeEntry = [&](const int _percent) + { + const auto mhz = hz * _percent / 100 / 1000000; + std::stringstream ss; + ss << _percent << "% (" << mhz << " MHz)"; + if(_percent == 100) + ss << " (Default)"; + clockMenu.addItem(ss.str(), _enabled, percent == _percent, [this, _percent] { m_processor.setDspClockPercent(_percent); }); + }; - makeEntry(50); - makeEntry(75); - makeEntry(100); - makeEntry(125); - makeEntry(150); - makeEntry(200); + makeEntry(50); + makeEntry(75); + makeEntry(100); + makeEntry(125); + makeEntry(150); + makeEntry(200); - _menu.addSubMenu("DSP Clock", clockMenu); + _menu.addSubMenu("DSP Clock", clockMenu); - return true; -} + return true; + } -jucePluginEditorLib::Editor* PluginEditorState::createEditor(const Skin& _skin) -{ - return new xtJucePlugin::Editor(m_processor, m_parameterBinding, _skin.folder, _skin.jsonFilename); -} + jucePluginEditorLib::Editor* PluginEditorState::createEditor(const Skin& _skin) + { + return new xtJucePlugin::Editor(m_processor, m_parameterBinding, _skin.folder, _skin.jsonFilename); + } +} +\ No newline at end of file diff --git a/source/xtJucePlugin/PluginEditorState.h b/source/xtJucePlugin/PluginEditorState.h @@ -1,7 +1,5 @@ #pragma once -#include <functional> - #include "jucePluginEditorLib/pluginEditorState.h" namespace juce @@ -9,14 +7,17 @@ namespace juce class Component; } -class AudioPluginAudioProcessor; - -class PluginEditorState : public jucePluginEditorLib::PluginEditorState +namespace xtJucePlugin { -public: - explicit PluginEditorState(AudioPluginAudioProcessor& _processor); - void initContextMenu(juce::PopupMenu& _menu) override; - bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; -private: - jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; -}; + class AudioPluginAudioProcessor; + + class PluginEditorState : public jucePluginEditorLib::PluginEditorState + { + public: + explicit PluginEditorState(AudioPluginAudioProcessor& _processor); + void initContextMenu(juce::PopupMenu& _menu) override; + bool initAdvancedContextMenu(juce::PopupMenu& _menu, bool _enabled) override; + private: + jucePluginEditorLib::Editor* createEditor(const Skin& _skin) override; + }; +} diff --git a/source/xtJucePlugin/PluginProcessor.cpp b/source/xtJucePlugin/PluginProcessor.cpp @@ -1,8 +1,5 @@ #include "PluginProcessor.h" -#include <juce_audio_processors/juce_audio_processors.h> -#include <juce_audio_devices/juce_audio_devices.h> - #include "PluginEditorState.h" #include "xtController.h" @@ -24,44 +21,44 @@ namespace } } -//============================================================================== -AudioPluginAudioProcessor::AudioPluginAudioProcessor() : - Processor(BusesProperties() - .withInput("Input", juce::AudioChannelSet::stereo(), true) - .withOutput("Output", juce::AudioChannelSet::stereo(), true) +namespace xtJucePlugin +{ + AudioPluginAudioProcessor::AudioPluginAudioProcessor() : + Processor(BusesProperties() + .withInput("Input", juce::AudioChannelSet::stereo(), true) + .withOutput("Output", juce::AudioChannelSet::stereo(), true) #if JucePlugin_IsSynth - .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) + .withOutput("Out 2", juce::AudioChannelSet::stereo(), true) #endif - , getOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect}) -{ - getController(); - const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); - Processor::setLatencyBlocks(latencyBlocks); -} + , getOptions(), pluginLib::Processor::Properties{JucePlugin_Name, JucePlugin_IsSynth, JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect}) + { + getController(); + const auto latencyBlocks = getConfig().getIntValue("latencyBlocks", static_cast<int>(getPlugin().getLatencyBlocks())); + Processor::setLatencyBlocks(latencyBlocks); + } -AudioPluginAudioProcessor::~AudioPluginAudioProcessor() -{ - destroyEditorState(); -} + AudioPluginAudioProcessor::~AudioPluginAudioProcessor() + { + destroyEditorState(); + } -jucePluginEditorLib::PluginEditorState* AudioPluginAudioProcessor::createEditorState() -{ - return new PluginEditorState(*this); -} + jucePluginEditorLib::PluginEditorState* AudioPluginAudioProcessor::createEditorState() + { + return new PluginEditorState(*this); + } -synthLib::Device* AudioPluginAudioProcessor::createDevice() -{ - return new xt::Device(); -} + synthLib::Device* AudioPluginAudioProcessor::createDevice() + { + return new xt::Device(); + } -pluginLib::Controller* AudioPluginAudioProcessor::createController() -{ - return new Controller(*this); + pluginLib::Controller* AudioPluginAudioProcessor::createController() + { + return new Controller(*this); + } } -//============================================================================== -// This creates new instances of the plugin.. juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { - return new AudioPluginAudioProcessor(); + return new xtJucePlugin::AudioPluginAudioProcessor(); } diff --git a/source/xtJucePlugin/PluginProcessor.h b/source/xtJucePlugin/PluginProcessor.h @@ -2,22 +2,21 @@ #include "jucePluginEditorLib/pluginProcessor.h" -//============================================================================== -class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor +namespace xtJucePlugin { -public: - AudioPluginAudioProcessor(); - ~AudioPluginAudioProcessor() override; + class AudioPluginAudioProcessor : public jucePluginEditorLib::Processor + { + public: + AudioPluginAudioProcessor(); + ~AudioPluginAudioProcessor() override; - jucePluginEditorLib::PluginEditorState* createEditorState() override; - // _____________ - // - synthLib::Device* createDevice() override; + jucePluginEditorLib::PluginEditorState* createEditorState() override; - pluginLib::Controller* createController() override; + synthLib::Device* createDevice() override; -private: + pluginLib::Controller* createController() override; - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) -}; + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessor) + }; +} diff --git a/source/xtJucePlugin/weData.h b/source/xtJucePlugin/weData.h @@ -12,10 +12,10 @@ #include "jucePluginLib/midipacket.h" #include "jucePluginLib/event.h" -class Controller; - namespace xtJucePlugin { + class Controller; + class WaveEditorData { public: diff --git a/source/xtJucePlugin/xtController.cpp b/source/xtJucePlugin/xtController.cpp @@ -42,9 +42,9 @@ namespace "tableDump" }; - static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count)); + static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(xtJucePlugin::Controller::MidiPacketType::Count)); - const char* midiPacketName(Controller::MidiPacketType _type) + const char* midiPacketName(xtJucePlugin::Controller::MidiPacketType _type) { return g_midiPacketNames[static_cast<uint32_t>(_type)]; } @@ -63,671 +63,660 @@ namespace constexpr uint32_t g_pageControllers = 40; } -Controller::Controller(AudioPluginAudioProcessor& p, unsigned char _deviceId) : pluginLib::Controller(p, loadParameterDescriptions()), m_deviceId(_deviceId) +namespace xtJucePlugin { - registerParams(p); + Controller::Controller(AudioPluginAudioProcessor& p, const unsigned char _deviceId) : pluginLib::Controller(p, loadParameterDescriptions()), m_deviceId(_deviceId) + { + registerParams(p); - sendSysEx(RequestGlobal); - sendSysEx(RequestMode); - requestMulti(xt::LocationH::MultiDumpMultiEditBuffer, 0); + sendSysEx(RequestGlobal); + sendSysEx(RequestMode); + requestMulti(xt::LocationH::MultiDumpMultiEditBuffer, 0); - onPlayModeChanged.addListener([this](bool multiMode) - { - requestAllPatches(); - }); + onPlayModeChanged.addListener([this](bool multiMode) + { + requestAllPatches(); + }); - // slow down edits of the wavetable, device gets overloaded quickly if we send too many changes - uint32_t idx; - getParameterDescriptions().getIndexByName(idx, "Wave"); - for(uint8_t i=0; i<m_singleEditBuffers.size(); ++i) - { - auto* p = getParameter(idx, i); - p->setRateLimitMilliseconds(250); + // slow down edits of the wavetable, device gets overloaded quickly if we send too many changes + uint32_t idx; + getParameterDescriptions().getIndexByName(idx, "Wave"); + for(uint8_t i=0; i<m_singleEditBuffers.size(); ++i) + { + auto* p = getParameter(idx, i); + p->setRateLimitMilliseconds(250); + } } -} -Controller::~Controller() = default; + Controller::~Controller() = default; -bool Controller::sendSingle(const std::vector<uint8_t>& _sysex) -{ - return sendSingle(_sysex, getCurrentPart()); -} + bool Controller::sendSingle(const std::vector<uint8_t>& _sysex) + { + return sendSingle(_sysex, getCurrentPart()); + } -bool Controller::sendSingle(const std::vector<uint8_t>& _sysex, const uint8_t _part) -{ - if(_sysex.size() == xt::Mw1::g_singleDumpLength) + bool Controller::sendSingle(const std::vector<uint8_t>& _sysex, const uint8_t _part) { - // No program/bank bytes are part of the dump, send as-is and request the result + if(_sysex.size() == xt::Mw1::g_singleDumpLength) + { + // No program/bank bytes are part of the dump, send as-is and request the result - if(_part > 0) - return false; // we cannot support this as the hardware loads a MW1 to the "current" instrument, which is always the first one + if(_part > 0) + return false; // we cannot support this as the hardware loads a MW1 to the "current" instrument, which is always the first one - pluginLib::Controller::sendSysEx(_sysex); - requestSingle(isMultiMode() ? xt::LocationH::SingleEditBufferMultiMode : xt::LocationH::SingleEditBufferSingleMode, 0); - return true; - } + pluginLib::Controller::sendSysEx(_sysex); + requestSingle(isMultiMode() ? xt::LocationH::SingleEditBufferMultiMode : xt::LocationH::SingleEditBufferSingleMode, 0); + return true; + } - auto data = _sysex; + auto data = _sysex; - data[wLib::IdxBuffer] = static_cast<uint8_t>(isMultiMode() ? xt::LocationH::SingleEditBufferMultiMode : xt::LocationH::SingleEditBufferSingleMode); - data[wLib::IdxLocation] = isMultiMode() ? _part : 0; - data[wLib::IdxDeviceId] = m_deviceId; + data[wLib::IdxBuffer] = static_cast<uint8_t>(isMultiMode() ? xt::LocationH::SingleEditBufferMultiMode : xt::LocationH::SingleEditBufferSingleMode); + data[wLib::IdxLocation] = isMultiMode() ? _part : 0; + data[wLib::IdxDeviceId] = m_deviceId; - const auto* p = getMidiPacket(g_midiPacketNames[SingleDump]); + const auto* p = getMidiPacket(g_midiPacketNames[SingleDump]); - if (!p->updateChecksums(data)) - return false; + if (!p->updateChecksums(data)) + return false; - pluginLib::Controller::sendSysEx(data); + pluginLib::Controller::sendSysEx(data); - sendLockedParameters(_part); + sendLockedParameters(_part); - requestSingle(isMultiMode() ? xt::LocationH::SingleEditBufferMultiMode : xt::LocationH::SingleEditBufferSingleMode, 0); + requestSingle(isMultiMode() ? xt::LocationH::SingleEditBufferMultiMode : xt::LocationH::SingleEditBufferSingleMode, 0); - return true; -} + return true; + } -const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size) -{ - for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + const char* findEmbeddedResource(const std::string& _filename, uint32_t& _size) { - if (BinaryData::originalFilenames[i] != _filename) - continue; + for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + { + if (BinaryData::originalFilenames[i] != _filename) + continue; - int size = 0; - const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); - _size = static_cast<uint32_t>(size); - return res; + int size = 0; + const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); + _size = static_cast<uint32_t>(size); + return res; + } + return nullptr; } - return nullptr; -} -std::string Controller::loadParameterDescriptions() -{ - const auto name = "parameterDescriptions_xt.json"; - const auto path = synthLib::getModulePath() + name; - - const std::ifstream f(path.c_str(), std::ios::in); - if(f.is_open()) - { - std::stringstream buf; - buf << f.rdbuf(); - return buf.str(); - } - - uint32_t size; - const auto res = findEmbeddedResource(name, size); - if(res) - return {res, size}; - return {}; -} + std::string Controller::loadParameterDescriptions() + { + const auto name = "parameterDescriptions_xt.json"; + const auto path = synthLib::getModulePath() + name; -void Controller::onStateLoaded() -{ -} + const std::ifstream f(path.c_str(), std::ios::in); + if(f.is_open()) + { + std::stringstream buf; + buf << f.rdbuf(); + return buf.str(); + } + + uint32_t size; + const auto res = findEmbeddedResource(name, size); + if(res) + return {res, size}; + return {}; + } -uint8_t Controller::getPartCount() -{ - return 8; -} + void Controller::onStateLoaded() + { + } -std::string Controller::getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const -{ - std::string name; - for(uint32_t i=0; i<16; ++i) - { - char paramName[16]; - (void)snprintf(paramName, sizeof(paramName), "Name%02u", i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); - if(it == _values.end()) - break; - - name += static_cast<char>(it->second); - } - return name; -} + uint8_t Controller::getPartCount() const + { + return 8; + } -std::string Controller::getSingleName(const uint8_t _part) const -{ - std::string name; - for(uint32_t i=0; i<16; ++i) - { - char paramName[16]; - (void)snprintf(paramName, sizeof(paramName), "Name%02u", i); - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto* p = getParameter(idx, _part); - if(!p) - break; - - name += static_cast<char>(p->getUnnormalizedValue()); - } - return name; -} + std::string Controller::getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const + { + std::string name; + for(uint32_t i=0; i<16; ++i) + { + char paramName[16]; + (void)snprintf(paramName, sizeof(paramName), "Name%02u", i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; -std::string Controller::getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const -{ - return getString(_values, "Name", 16); -} + const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx)); + if(it == _values.end()) + break; -std::string Controller::getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, const size_t _len) const -{ - std::string name; - for(uint32_t i=0; i<_len; ++i) - { - char paramName[64]; - (void)snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); - - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - const auto it = _values[idx]; - if(!it) - break; - - name += static_cast<char>(*it); - } - return name; -} + name += static_cast<char>(it->second); + } + return name; + } -bool Controller::setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const -{ - return setString(_values, "Name", 16, _value); -} + std::string Controller::getSingleName(const uint8_t _part) const + { + std::string name; + for(uint32_t i=0; i<16; ++i) + { + char paramName[16]; + (void)snprintf(paramName, sizeof(paramName), "Name%02u", i); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; -void Controller::applyPatchParameters(const pluginLib::MidiPacket::ParamValues& _params, const uint8_t _part) const -{ - for (const auto& it : _params) + const auto* p = getParameter(idx, _part); + if(!p) + break; + + name += static_cast<char>(p->getUnnormalizedValue()); + } + return name; + } + + std::string Controller::getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const { - auto* p = getParameter(it.first.second, _part); - p->setValueFromSynth(it.second, pluginLib::Parameter::Origin::PresetChange); + return getString(_values, "Name", 16); + } + + std::string Controller::getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, const size_t _len) const + { + std::string name; + for(uint32_t i=0; i<_len; ++i) + { + char paramName[64]; + (void)snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); - for (const auto& derivedParam : p->getDerivedParameters()) - derivedParam->setValueFromSynth(it.second, pluginLib::Parameter::Origin::PresetChange); + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; + + const auto it = _values[idx]; + if(!it) + break; + + name += static_cast<char>(*it); + } + return name; } - getProcessor().updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true)); -} + bool Controller::setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const + { + return setString(_values, "Name", 16, _value); + } -void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) -{ - Patch patch; - patch.data = _msg; - patch.name = getSingleName(_params); + void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) + { + Patch patch; + patch.data = _msg; + patch.name = getSingleName(_params); - const auto bank = _data.at(pluginLib::MidiDataType::Bank); - const auto prog = _data.at(pluginLib::MidiDataType::Program); + const auto bank = _data.at(pluginLib::MidiDataType::Bank); + const auto prog = _data.at(pluginLib::MidiDataType::Program); - if(bank == static_cast<uint8_t>(xt::LocationH::SingleEditBufferSingleMode) && prog == 0) - { - m_singleEditBuffer = patch; + if(bank == static_cast<uint8_t>(xt::LocationH::SingleEditBufferSingleMode) && prog == 0) + { + m_singleEditBuffer = patch; - if(!isMultiMode()) - applyPatchParameters(_params, 0); - } - else if(bank == static_cast<uint8_t>(xt::LocationH::SingleEditBufferMultiMode)) - { - m_singleEditBuffers[prog] = patch; + if(!isMultiMode()) + applyPatchParameters(_params, 0); + } + else if(bank == static_cast<uint8_t>(xt::LocationH::SingleEditBufferMultiMode)) + { + m_singleEditBuffers[prog] = patch; - if (isMultiMode()) - applyPatchParameters(_params, prog); + if (isMultiMode()) + applyPatchParameters(_params, prog); - // if we switched to multi, all singles have to be requested. However, we cannot send all requests at once (device will miss some) - // so we chain them one after the other - if(prog + 1 < m_singleEditBuffers.size()) - requestSingle(xt::LocationH::SingleEditBufferMultiMode, prog + 1); - } + // if we switched to multi, all singles have to be requested. However, we cannot send all requests at once (device will miss some) + // so we chain them one after the other + if(prog + 1 < m_singleEditBuffers.size()) + requestSingle(xt::LocationH::SingleEditBufferMultiMode, prog + 1); + } - onProgramChanged(prog); -} + onProgramChanged(prog); + } -void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) const -{ - Patch patch; - patch.data = _msg; - patch.name = getSingleName(_params); + void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) const + { + Patch patch; + patch.data = _msg; + patch.name = getSingleName(_params); - const auto bank = _data.at(pluginLib::MidiDataType::Bank); -// const auto prog = _data.at(pluginLib::MidiDataType::Program); + const auto bank = _data.at(pluginLib::MidiDataType::Bank); + // const auto prog = _data.at(pluginLib::MidiDataType::Program); - if(bank == static_cast<uint8_t>(xt::LocationH::MultiDumpMultiEditBuffer)) + if(bank == static_cast<uint8_t>(xt::LocationH::MultiDumpMultiEditBuffer)) + { + applyPatchParameters(_params, 0); + } + } + + void Controller::parseGlobal(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) { + memcpy(m_globalData.data(), &_msg[xt::IdxGlobalParamFirst], sizeof(m_globalData)); + applyPatchParameters(_params, 0); } -} -void Controller::parseGlobal(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) -{ - memcpy(m_globalData.data(), &_msg[xt::IdxGlobalParamFirst], sizeof(m_globalData)); + bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource) + { + if(_msg.size() >= 5) + { + switch (const auto cmd = static_cast<xt::SysexCommand>(_msg[4])) + { + case xt::SysexCommand::EmuRotaries: + case xt::SysexCommand::EmuButtons: + case xt::SysexCommand::EmuLCD: + case xt::SysexCommand::EmuLEDs: + if(m_frontPanel) + m_frontPanel->processSysex(_msg); + return true; + default: + break; + } + } - applyPatchParameters(_params, 0); -} + LOG("Got sysex of size " << _msg.size()); -bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource) -{ - if(_msg.size() >= 5) - { - switch (const auto cmd = static_cast<xt::SysexCommand>(_msg[4])) - { - case xt::SysexCommand::EmuRotaries: - case xt::SysexCommand::EmuButtons: - case xt::SysexCommand::EmuLCD: - case xt::SysexCommand::EmuLEDs: - if(m_frontPanel) - m_frontPanel->processSysex(_msg); - return true; - default: - break; - } - } - - LOG("Got sysex of size " << _msg.size()); - - std::string name; - pluginLib::MidiPacket::Data data; - pluginLib::MidiPacket::ParamValues parameterValues; - - if(!pluginLib::Controller::parseMidiPacket(name, data, parameterValues, _msg)) - return false; + std::string name; + pluginLib::MidiPacket::Data data; + pluginLib::MidiPacket::ParamValues parameterValues; + + if(!pluginLib::Controller::parseMidiPacket(name, data, parameterValues, _msg)) + return false; + + if(name == midiPacketName(SingleDump)) + { + parseSingle(_msg, data, parameterValues); + } + else if (name == midiPacketName(MultiDump)) + { + parseMulti(_msg, data, parameterValues); + } + else if(name == midiPacketName(GlobalDump)) + { + parseGlobal(_msg, data, parameterValues); + } + else if(name == midiPacketName(ModeDump)) + { + const auto lastPlayMode = isMultiMode(); + memcpy(m_modeData.data(), &_msg[xt::IdxModeParamFirst], sizeof(m_modeData)); + const auto newPlayMode = isMultiMode(); + + if(lastPlayMode != newPlayMode) + onPlayModeChanged(newPlayMode); + else + requestAllPatches(); + } + else if(name == midiPacketName(SingleParameterChange)) + { + const auto page = data[pluginLib::MidiDataType::Page]; + const auto index = data[pluginLib::MidiDataType::ParameterIndex]; + const auto part = data[pluginLib::MidiDataType::Part]; + const auto value = data[pluginLib::MidiDataType::ParameterValue]; + + auto& params = findSynthParam(part, page, index); - if(name == midiPacketName(SingleDump)) - { - parseSingle(_msg, data, parameterValues); - } - else if (name == midiPacketName(MultiDump)) - { - parseMulti(_msg, data, parameterValues); - } - else if(name == midiPacketName(GlobalDump)) - { - parseGlobal(_msg, data, parameterValues); - } - else if(name == midiPacketName(ModeDump)) - { - const auto lastPlayMode = isMultiMode(); - memcpy(m_modeData.data(), &_msg[xt::IdxModeParamFirst], sizeof(m_modeData)); - const auto newPlayMode = isMultiMode(); - - if(lastPlayMode != newPlayMode) - onPlayModeChanged(newPlayMode); + for (auto& param : params) + param->setValueFromSynth(value, pluginLib::Parameter::Origin::Midi); + + LOG("Single parameter " << static_cast<int>(index) << ", page " << static_cast<int>(page) << " for part " << static_cast<int>(part) << " changed to value " << static_cast<int>(value)); + } + else if(name == midiPacketName(GlobalParameterChange)) + { + const auto index = (static_cast<uint32_t>(data[pluginLib::MidiDataType::Page]) << 7) + static_cast<uint32_t>(data[pluginLib::MidiDataType::ParameterIndex]); + const auto value = data[pluginLib::MidiDataType::ParameterValue]; + + if(m_globalData[index] != value) + { + LOG("Global parameter " << index << " changed to value " << static_cast<int>(value)); + m_globalData[index] = value; + } + } + else if(name == midiPacketName(WaveDump)) + { + if(m_waveEditor) + m_waveEditor->onReceiveWave(data, _msg); + } + else if(name == midiPacketName(TableDump)) + { + if(m_waveEditor) + m_waveEditor->onReceiveTable(data, _msg); + } else - requestAllPatches(); - } - else if(name == midiPacketName(SingleParameterChange)) - { - const auto page = data[pluginLib::MidiDataType::Page]; - const auto index = data[pluginLib::MidiDataType::ParameterIndex]; - const auto part = data[pluginLib::MidiDataType::Part]; - const auto value = data[pluginLib::MidiDataType::ParameterValue]; - - auto& params = findSynthParam(part, page, index); - - for (auto& param : params) - param->setValueFromSynth(value, pluginLib::Parameter::Origin::Midi); - - LOG("Single parameter " << static_cast<int>(index) << ", page " << static_cast<int>(page) << " for part " << static_cast<int>(part) << " changed to value " << static_cast<int>(value)); - } - else if(name == midiPacketName(GlobalParameterChange)) - { - const auto index = (static_cast<uint32_t>(data[pluginLib::MidiDataType::Page]) << 7) + static_cast<uint32_t>(data[pluginLib::MidiDataType::ParameterIndex]); - const auto value = data[pluginLib::MidiDataType::ParameterValue]; - - if(m_globalData[index] != value) { - LOG("Global parameter " << index << " changed to value " << static_cast<int>(value)); - m_globalData[index] = value; + LOG("Received unknown sysex of size " << _msg.size()); + return false; } - } - else if(name == midiPacketName(WaveDump)) + return true; + } + + bool Controller::parseControllerMessage(const synthLib::SMidiEvent&) { - if(m_waveEditor) - m_waveEditor->onReceiveWave(data, _msg); + // TODO + return false; } - else if(name == midiPacketName(TableDump)) + + bool Controller::parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const { - if(m_waveEditor) - m_waveEditor->onReceiveTable(data, _msg); + const auto* p = getMidiPacket(g_midiPacketNames[_type]); + assert(p && "midi packet not found"); + return pluginLib::Controller::parseMidiPacket(*p, _data, _params, _sysex); } - else - { - LOG("Received unknown sysex of size " << _msg.size()); - return false; - } - return true; -} -bool Controller::parseControllerMessage(const synthLib::SMidiEvent&) -{ - // TODO - return false; -} + bool Controller::sendSysEx(MidiPacketType _type) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + return sendSysEx(_type, params); + } -bool Controller::parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const -{ - const auto* p = getMidiPacket(g_midiPacketNames[_type]); - assert(p && "midi packet not found"); - return pluginLib::Controller::parseMidiPacket(*p, _data, _params, _sysex); -} + bool Controller::sendSysEx(const MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const + { + _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); + } -bool Controller::sendSysEx(MidiPacketType _type) const -{ - std::map<pluginLib::MidiDataType, uint8_t> params; - return sendSysEx(_type, params); -} + bool Controller::isMultiMode() const + { + return m_modeData.front() != 0; + } -bool Controller::sendSysEx(const MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const -{ - _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params); -} + void Controller::setPlayMode(const bool _multiMode) + { + if(isMultiMode() == _multiMode) + return; -bool Controller::isMultiMode() const -{ - return m_modeData.front() != 0; -} + m_modeData[0] = _multiMode ? 1 : 0; -void Controller::setPlayMode(const bool _multiMode) -{ - if(isMultiMode() == _multiMode) - return; + sendModeDump(); - m_modeData[0] = _multiMode ? 1 : 0; + onPlayModeChanged(_multiMode); + } - sendModeDump(); + void Controller::selectNextPreset() + { + selectPreset(+1); + } - onPlayModeChanged(_multiMode); -} + void Controller::selectPrevPreset() + { + selectPreset(-1); + } -void Controller::selectNextPreset() -{ - selectPreset(+1); -} + std::vector<uint8_t> Controller::createSingleDump(const xt::LocationH _buffer, const uint8_t _location, const uint8_t _part) const + { + pluginLib::MidiPacket::Data data; -void Controller::selectPrevPreset() -{ - selectPreset(-1); -} + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _location)); -std::vector<uint8_t> Controller::createSingleDump(const xt::LocationH _buffer, const uint8_t _location, const uint8_t _part) const -{ - pluginLib::MidiPacket::Data data; + std::vector<uint8_t> dst; - data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, _location)); + if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _part)) + return {}; - std::vector<uint8_t> dst; + return dst; + } - if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _part)) - return {}; + std::vector<uint8_t> Controller::createSingleDump(xt::LocationH _buffer, const uint8_t _location, const pluginLib::MidiPacket::AnyPartParamValues& _values) const + { + pluginLib::MidiPacket::Data data; - return dst; -} + data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); + data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); + data.insert(std::make_pair(pluginLib::MidiDataType::Program, _location)); -std::vector<uint8_t> Controller::createSingleDump(xt::LocationH _buffer, const uint8_t _location, const pluginLib::MidiPacket::AnyPartParamValues& _values) const -{ - pluginLib::MidiPacket::Data data; + std::vector<uint8_t> dst; - data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId)); - data.insert(std::make_pair(pluginLib::MidiDataType::Bank, static_cast<uint8_t>(_buffer))); - data.insert(std::make_pair(pluginLib::MidiDataType::Program, _location)); + if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _values)) + return {}; - std::vector<uint8_t> dst; + return dst; + } - if (!createMidiDataFromPacket(dst, midiPacketName(SingleDump), data, _values)) - return {}; + bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const + { + return parseMidiPacket(SingleDump, _data, _paramValues, _sysex); + } - return dst; -} + bool Controller::setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const + { + for(uint32_t i=0; i<_len && i <_value.size(); ++i) + { + char paramName[64]; + (void)snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); -bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const -{ - return parseMidiPacket(SingleDump, _data, _paramValues, _sysex); -} + const auto idx = getParameterIndexByName(paramName); + if(idx == InvalidParameterIndex) + break; -bool Controller::setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const -{ - for(uint32_t i=0; i<_len && i <_value.size(); ++i) - { - char paramName[64]; - (void)snprintf(paramName, sizeof(paramName), "%s%02u", _prefix.c_str(), i); - - const auto idx = getParameterIndexByName(paramName); - if(idx == InvalidParameterIndex) - break; - - _values[idx] = static_cast<uint8_t>(_value[i]); - } - return true; -} + _values[idx] = static_cast<uint8_t>(_value[i]); + } + return true; + } -void Controller::setFrontPanel(xtJucePlugin::FrontPanel* _frontPanel) -{ - m_frontPanel = _frontPanel; -} + void Controller::setFrontPanel(xtJucePlugin::FrontPanel* _frontPanel) + { + m_frontPanel = _frontPanel; + } -void Controller::setWaveEditor(xtJucePlugin::WaveEditor* _waveEditor) -{ - m_waveEditor = _waveEditor; -} + void Controller::setWaveEditor(xtJucePlugin::WaveEditor* _waveEditor) + { + m_waveEditor = _waveEditor; + } -void Controller::selectPreset(const int _offset) -{ - auto& current = isMultiMode() ? m_currentSingles[getCurrentPart()] : m_currentSingle; - - int index = static_cast<int>(current) + _offset; - - if (index < 0) - index += 300; - - if (index >= 300) - index -= 300; - - current = static_cast<uint32_t>(index); - - const int single = index % 100; - const int bank = index / 100; - - if (isMultiMode()) - { - // TODO: modify multi - } - else - { - sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTMSB, m_deviceId); - sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTLSB, static_cast<uint8_t>(xt::LocationH::SingleBankA) + bank); - sendMidiEvent(synthLib::M_PROGRAMCHANGE, static_cast<uint8_t>(single), 0); -/* - sendGlobalParameterChange(xt::GlobalParameter::InstrumentABankNumber, static_cast<uint8_t>(bank)); - sendGlobalParameterChange(xt::GlobalParameter::InstrumentASingleNumber, static_cast<uint8_t>(single)); -*/ } -} + void Controller::selectPreset(const int _offset) + { + auto& current = isMultiMode() ? m_currentSingles[getCurrentPart()] : m_currentSingle; -void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, const uint8_t _value) -{ - const auto &desc = _parameter.getDescription(); + int index = static_cast<int>(current) + _offset; - std::map<pluginLib::MidiDataType, uint8_t> data; + if (index < 0) + index += 300; - switch (desc.page) - { - case g_pageGlobal: - { - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, _parameter.getDescription().index & 0x7f)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); + if (index >= 300) + index -= 300; - sendSysEx(GlobalParameterChange, data); - } - return; - case g_pageMulti: - case g_pageMultiInst0: - case g_pageMultiInst1: - case g_pageMultiInst2: - case g_pageMultiInst3: - case g_pageMultiInst4: - case g_pageMultiInst5: - case g_pageMultiInst6: - case g_pageMultiInst7: - { - uint8_t v; + current = static_cast<uint32_t>(index); - if (!combineParameterChange(v, g_midiPacketNames[MultiDump], _parameter, _value)) - return; + const int single = index % 100; + const int bank = index / 100; - uint8_t page; + if (isMultiMode()) + { + // TODO: modify multi + } + else + { + sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTMSB, m_deviceId); + sendMidiEvent(synthLib::M_CONTROLCHANGE, synthLib::MC_BANKSELECTLSB, static_cast<uint8_t>(xt::LocationH::SingleBankA) + bank); + sendMidiEvent(synthLib::M_PROGRAMCHANGE, static_cast<uint8_t>(single), 0); + /* + sendGlobalParameterChange(xt::GlobalParameter::InstrumentABankNumber, static_cast<uint8_t>(bank)); + sendGlobalParameterChange(xt::GlobalParameter::InstrumentASingleNumber, static_cast<uint8_t>(single)); + */ } + } - if(desc.page > g_pageMulti) - page = desc.page - g_pageMultiInst0; - else - page = static_cast<uint8_t>(xt::LocationH::MultiDumpMultiEditBuffer); + void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, const pluginLib::ParamValue _value) + { + const auto &desc = _parameter.getDescription(); - data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); - data.insert(std::make_pair(pluginLib::MidiDataType::Page, page)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, desc.index)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); + std::map<pluginLib::MidiDataType, uint8_t> data; - sendSysEx(MultiParameterChange, data); - } - return; - case g_pageSoftKnobs: - break; - case g_pageControllers: - break; - default: + switch (desc.page) { - uint8_t v; - if (!combineParameterChange(v, g_midiPacketNames[SingleDump], _parameter, _value)) - return; - - data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); - data.insert(std::make_pair(pluginLib::MidiDataType::Page, desc.page)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, desc.index)); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); - - sendSysEx(SingleParameterChange, data); + case g_pageGlobal: + { + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, _parameter.getDescription().index & 0x7f)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); + + sendSysEx(GlobalParameterChange, data); + } + return; + case g_pageMulti: + case g_pageMultiInst0: + case g_pageMultiInst1: + case g_pageMultiInst2: + case g_pageMultiInst3: + case g_pageMultiInst4: + case g_pageMultiInst5: + case g_pageMultiInst6: + case g_pageMultiInst7: + { + uint8_t v; + + if (!combineParameterChange(v, g_midiPacketNames[MultiDump], _parameter, _value)) + return; + + uint8_t page; + + if(desc.page > g_pageMulti) + page = desc.page - g_pageMultiInst0; + else + page = static_cast<uint8_t>(xt::LocationH::MultiDumpMultiEditBuffer); + + data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); + data.insert(std::make_pair(pluginLib::MidiDataType::Page, page)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, desc.index)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); + + sendSysEx(MultiParameterChange, data); + } + return; + case g_pageSoftKnobs: + break; + case g_pageControllers: + break; + default: + { + uint8_t v; + if (!combineParameterChange(v, g_midiPacketNames[SingleDump], _parameter, _value)) + return; + + data.insert(std::make_pair(pluginLib::MidiDataType::Part, _parameter.getPart())); + data.insert(std::make_pair(pluginLib::MidiDataType::Page, desc.page)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, desc.index)); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, v)); + + sendSysEx(SingleParameterChange, data); + } + break; } - break; } -} - -bool Controller::sendGlobalParameterChange(xt::GlobalParameter _param, uint8_t _value) -{ - const auto index = static_cast<uint32_t>(_param); - if(m_globalData[index] == _value) - return true; + bool Controller::sendGlobalParameterChange(xt::GlobalParameter _param, uint8_t _value) + { + const auto index = static_cast<uint32_t>(_param); - std::map<pluginLib::MidiDataType, uint8_t> data; + if(m_globalData[index] == _value) + return true; - data.insert(std::make_pair(pluginLib::MidiDataType::Page, index >> 7 )); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, index & 0x7f )); - data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); + std::map<pluginLib::MidiDataType, uint8_t> data; - m_globalData[index] = _value; + data.insert(std::make_pair(pluginLib::MidiDataType::Page, index >> 7 )); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, index & 0x7f )); + data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value)); - return sendSysEx(GlobalParameterChange, data); -} + m_globalData[index] = _value; -bool Controller::sendModeDump() const -{ - std::vector<uint8_t> sysex; - std::map<pluginLib::MidiDataType, uint8_t> data; - data.insert({pluginLib::MidiDataType::DeviceId, m_deviceId}); - if(!createMidiDataFromPacket(sysex, midiPacketName(ModeDump), data, 0)) - return false; - sysex[xt::IdxModeParamFirst] = m_modeData.front(); - pluginLib::Controller::sendSysEx(sysex); - return true; -} - -void Controller::requestSingle(xt::LocationH _buf, uint8_t _location) const -{ - std::map<pluginLib::MidiDataType, uint8_t> params; - params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); - params[pluginLib::MidiDataType::Program] = _location; - sendSysEx(RequestSingle, params); -} - -void Controller::requestMulti(xt::LocationH _buf, uint8_t _location) const -{ - std::map<pluginLib::MidiDataType, uint8_t> params; - params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); - params[pluginLib::MidiDataType::Program] = _location; - sendSysEx(RequestMulti, params); -} + return sendSysEx(GlobalParameterChange, data); + } -bool Controller::requestWave(const uint32_t _number) const -{ - if(!xt::Wave::isValidWaveIndex(_number)) - return false; + bool Controller::sendModeDump() const + { + std::vector<uint8_t> sysex; + std::map<pluginLib::MidiDataType, uint8_t> data; + data.insert({pluginLib::MidiDataType::DeviceId, m_deviceId}); + if(!createMidiDataFromPacket(sysex, midiPacketName(ModeDump), data, 0)) + return false; + sysex[xt::IdxModeParamFirst] = m_modeData.front(); + pluginLib::Controller::sendSysEx(sysex); + return true; + } - std::map<pluginLib::MidiDataType, uint8_t> params; + void Controller::requestSingle(xt::LocationH _buf, uint8_t _location) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); + params[pluginLib::MidiDataType::Program] = _location; + sendSysEx(RequestSingle, params); + } - params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_number >> 7); - params[pluginLib::MidiDataType::Program] = _number & 0x7f; + void Controller::requestMulti(xt::LocationH _buf, uint8_t _location) const + { + std::map<pluginLib::MidiDataType, uint8_t> params; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_buf); + params[pluginLib::MidiDataType::Program] = _location; + sendSysEx(RequestMulti, params); + } - return sendSysEx(RequestWave, params); -} + bool Controller::requestWave(const uint32_t _number) const + { + if(!xt::Wave::isValidWaveIndex(_number)) + return false; -bool Controller::requestTable(const uint32_t _number) const -{ - if(!xt::Wave::isValidTableIndex(_number)) - return false; + std::map<pluginLib::MidiDataType, uint8_t> params; - std::map<pluginLib::MidiDataType, uint8_t> params; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_number >> 7); + params[pluginLib::MidiDataType::Program] = _number & 0x7f; - params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_number >> 7); - params[pluginLib::MidiDataType::Program] = _number & 0x7f; + return sendSysEx(RequestWave, params); + } - return sendSysEx(RequestTable, params); -} + bool Controller::requestTable(const uint32_t _number) const + { + if(!xt::Wave::isValidTableIndex(_number)) + return false; -uint8_t Controller::getGlobalParam(xt::GlobalParameter _type) const -{ - return m_globalData[static_cast<uint32_t>(_type)]; -} + std::map<pluginLib::MidiDataType, uint8_t> params; -bool Controller::isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const -{ - if(_derived.getDescription().page >= 100) - return false; + params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_number >> 7); + params[pluginLib::MidiDataType::Program] = _number & 0x7f; - const auto& packetName = g_midiPacketNames[SingleDump]; - const auto* packet = getMidiPacket(packetName); + return sendSysEx(RequestTable, params); + } - if (!packet) + uint8_t Controller::getGlobalParam(xt::GlobalParameter _type) const { - LOG("Failed to find midi packet " << packetName); - return true; + return m_globalData[static_cast<uint32_t>(_type)]; } - - const auto* defA = packet->getDefinitionByParameterName(_derived.getDescription().name); - const auto* defB = packet->getDefinitionByParameterName(_base.getDescription().name); - if (!defA || !defB) - return true; + bool Controller::isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const + { + if(_derived.getDescription().page >= 100) + return false; - return defA->doMasksOverlap(*defB); -} + const auto& packetName = g_midiPacketNames[SingleDump]; + const auto* packet = getMidiPacket(packetName); -void Controller::requestAllPatches() const -{ - if (isMultiMode()) - { - requestMulti(xt::LocationH::MultiDumpMultiEditBuffer, 0); + if (!packet) + { + LOG("Failed to find midi packet " << packetName); + return true; + } + + const auto* defA = packet->getDefinitionByParameterName(_derived.getDescription().name); + const auto* defB = packet->getDefinitionByParameterName(_base.getDescription().name); + + if (!defA || !defB) + return true; - // the other singles 1-7 are requested one after the other after a single has been received - requestSingle(xt::LocationH::SingleEditBufferMultiMode, 0); + return defA->doMasksOverlap(*defB); } - else + + void Controller::requestAllPatches() const { - requestSingle(xt::LocationH::SingleEditBufferSingleMode, 0); + if (isMultiMode()) + { + requestMulti(xt::LocationH::MultiDumpMultiEditBuffer, 0); + + // the other singles 1-7 are requested one after the other after a single has been received + requestSingle(xt::LocationH::SingleEditBufferMultiMode, 0); + } + else + { + requestSingle(xt::LocationH::SingleEditBufferSingleMode, 0); + } } -} +} +\ No newline at end of file diff --git a/source/xtJucePlugin/xtController.h b/source/xtJucePlugin/xtController.h @@ -4,132 +4,131 @@ #include "jucePluginLib/event.h" -namespace xtJucePlugin -{ - class WaveEditor; - class FrontPanel; -} - namespace xt { enum class GlobalParameter; enum class LocationH : uint8_t; } -class AudioPluginAudioProcessor; - -class Controller : public pluginLib::Controller +namespace xtJucePlugin { -public: - enum MidiPacketType - { - RequestSingle, - RequestMulti, - RequestSingleBank, - RequestMultiBank, - RequestGlobal, - RequestMode, - RequestAllSingles, - SingleParameterChange, - MultiParameterChange, - GlobalParameterChange, - SingleDump, - MultiDump, - GlobalDump, - ModeDump, - EmuRequestLcd, - EmuRequestLeds, - EmuSendButton, - EmuSendRotary, - RequestWave, - WaveDump, - RequestTable, - TableDump, - - Count - }; + class WaveEditor; + class FrontPanel; - struct Patch + class AudioPluginAudioProcessor; + + class Controller : public pluginLib::Controller { - std::string name; - std::vector<uint8_t> data; + public: + enum MidiPacketType + { + RequestSingle, + RequestMulti, + RequestSingleBank, + RequestMultiBank, + RequestGlobal, + RequestMode, + RequestAllSingles, + SingleParameterChange, + MultiParameterChange, + GlobalParameterChange, + SingleDump, + MultiDump, + GlobalDump, + ModeDump, + EmuRequestLcd, + EmuRequestLeds, + EmuSendButton, + EmuSendRotary, + RequestWave, + WaveDump, + RequestTable, + TableDump, + + Count + }; + + struct Patch + { + std::string name; + std::vector<uint8_t> data; + }; + + pluginLib::Event<bool> onPlayModeChanged; + pluginLib::Event<uint8_t> onProgramChanged; + + Controller(AudioPluginAudioProcessor &, unsigned char _deviceId = 0); + ~Controller() override; + + bool sendSingle(const std::vector<uint8_t>& _sysex); + bool sendSingle(const std::vector<uint8_t>& _sysex, uint8_t _part); + + bool sendSysEx(MidiPacketType _type) const; + bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; + + bool isMultiMode() const; + void setPlayMode(bool _multiMode); + + void selectNextPreset(); + void selectPrevPreset(); + + std::vector<uint8_t> createSingleDump(xt::LocationH _buffer, uint8_t _location, uint8_t _part) const; + std::vector<uint8_t> createSingleDump(xt::LocationH _buffer, uint8_t _location, const pluginLib::MidiPacket::AnyPartParamValues& _values) const; + bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const; + + std::string getSingleName(uint8_t _part) const; + std::string getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const; + std::string getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; + std::string getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len) const; + + bool setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; + bool setCategory(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; + bool setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const; + + void setFrontPanel(xtJucePlugin::FrontPanel* _frontPanel); + void setWaveEditor(xtJucePlugin::WaveEditor* _waveEditor); + + bool requestWave(uint32_t _number) const; + bool requestTable(uint32_t _number) const; + + private: + void selectPreset(int _offset); + + static std::string loadParameterDescriptions(); + + void onStateLoaded() override; + uint8_t getPartCount() const override; + + void parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); + void parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) const; + void parseGlobal(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); + + bool parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const; + + bool parseSysexMessage(const pluginLib::SysEx&, synthLib::MidiEventSource) override; + bool parseControllerMessage(const synthLib::SMidiEvent&) override; + + void sendParameterChange(const pluginLib::Parameter& _parameter, pluginLib::ParamValue _value) override; + bool sendGlobalParameterChange(xt::GlobalParameter _param, uint8_t _value); + bool sendModeDump() const; + void requestSingle(xt::LocationH _buf, uint8_t _location) const; + void requestMulti(xt::LocationH _buf, uint8_t _location) const; + + uint8_t getGlobalParam(xt::GlobalParameter _type) const; + + bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; + + void requestAllPatches() const; + + const uint8_t m_deviceId; + + Patch m_singleEditBuffer; + std::array<Patch,8> m_singleEditBuffers; + std::array<uint8_t, 39> m_globalData{}; + std::array<uint8_t, 1> m_modeData{}; + std::array<uint32_t, 8> m_currentSingles{0}; + uint32_t m_currentSingle = 0; + xtJucePlugin::FrontPanel* m_frontPanel = nullptr; + xtJucePlugin::WaveEditor* m_waveEditor = nullptr; }; - - pluginLib::Event<bool> onPlayModeChanged; - pluginLib::Event<uint8_t> onProgramChanged; - - Controller(AudioPluginAudioProcessor &, unsigned char _deviceId = 0); - ~Controller() override; - - bool sendSingle(const std::vector<uint8_t>& _sysex); - bool sendSingle(const std::vector<uint8_t>& _sysex, uint8_t _part); - - bool sendSysEx(MidiPacketType _type) const; - bool sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const; - - bool isMultiMode() const; - void setPlayMode(bool _multiMode); - - void selectNextPreset(); - void selectPrevPreset(); - - std::vector<uint8_t> createSingleDump(xt::LocationH _buffer, uint8_t _location, uint8_t _part) const; - std::vector<uint8_t> createSingleDump(xt::LocationH _buffer, uint8_t _location, const pluginLib::MidiPacket::AnyPartParamValues& _values) const; - bool parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _paramValues, const std::vector<uint8_t>& _sysex) const; - - std::string getSingleName(uint8_t _part) const; - std::string getSingleName(const pluginLib::MidiPacket::ParamValues& _values) const; - std::string getSingleName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const; - std::string getString(const pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len) const; - - bool setSingleName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; - bool setCategory(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _value) const; - bool setString(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _prefix, size_t _len, const std::string& _value) const; - - void setFrontPanel(xtJucePlugin::FrontPanel* _frontPanel); - void setWaveEditor(xtJucePlugin::WaveEditor* _waveEditor); - - bool requestWave(uint32_t _number) const; - bool requestTable(uint32_t _number) const; - -private: - void selectPreset(int _offset); - - static std::string loadParameterDescriptions(); - - void onStateLoaded() override; - uint8_t getPartCount() override; - - void applyPatchParameters(const pluginLib::MidiPacket::ParamValues& _params, uint8_t _part) const; - void parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); - void parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params) const; - void parseGlobal(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _params); - - bool parseMidiPacket(MidiPacketType _type, pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _params, const pluginLib::SysEx& _sysex) const; - - bool parseSysexMessage(const pluginLib::SysEx&, synthLib::MidiEventSource) override; - bool parseControllerMessage(const synthLib::SMidiEvent&) override; - - void sendParameterChange(const pluginLib::Parameter& _parameter, uint8_t _value) override; - bool sendGlobalParameterChange(xt::GlobalParameter _param, uint8_t _value); - bool sendModeDump() const; - void requestSingle(xt::LocationH _buf, uint8_t _location) const; - void requestMulti(xt::LocationH _buf, uint8_t _location) const; - - uint8_t getGlobalParam(xt::GlobalParameter _type) const; - - bool isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const override; - - void requestAllPatches() const; - - const uint8_t m_deviceId; - - Patch m_singleEditBuffer; - std::array<Patch,8> m_singleEditBuffers; - std::array<uint8_t, 39> m_globalData{}; - std::array<uint8_t, 1> m_modeData{}; - std::array<uint32_t, 8> m_currentSingles{0}; - uint32_t m_currentSingle = 0; - xtJucePlugin::FrontPanel* m_frontPanel = nullptr; - xtJucePlugin::WaveEditor* m_waveEditor = nullptr; -}; +} +\ No newline at end of file diff --git a/source/xtJucePlugin/xtEditor.cpp b/source/xtJucePlugin/xtEditor.cpp @@ -80,7 +80,7 @@ namespace xtJucePlugin { juce::PopupMenu menu; - const auto countAdded = getPatchManager()->createSaveMenuEntries(menu, getPatchManager()->getCurrentPart()); + const auto countAdded = getPatchManager()->createSaveMenuEntries(menu); if(countAdded) menu.showMenuAsync(juce::PopupMenu::Options()); diff --git a/source/xtJucePlugin/xtEditor.h b/source/xtJucePlugin/xtEditor.h @@ -6,9 +6,6 @@ #include "jucePluginLib/event.h" -class XtLcd; -class Controller; - namespace jucePluginEditorLib { class MidiPorts; @@ -27,6 +24,8 @@ namespace xtJucePlugin class FocusedParameter; class FrontPanel; class PatchManager; + class XtLcd; + class Controller; class Editor final : public jucePluginEditorLib::Editor { diff --git a/source/xtJucePlugin/xtLcd.cpp b/source/xtJucePlugin/xtLcd.cpp @@ -4,7 +4,7 @@ #include "jucePluginLib/pluginVersion.h" -#include "wLib/lcdfonts.h" +#include "hardwareLib/lcdfonts.h" namespace { @@ -20,77 +20,80 @@ namespace static_assert(std::size(g_defaultTextMulti) == 80 + 1); } -// 40*2 LCD simulation - -XtLcd::XtLcd(Component& _parent, Controller& _controller) : Lcd(_parent, 40, 2), m_controller(_controller) +namespace xtJucePlugin { - postConstruct(); + // 40*2 LCD simulation - m_currentText.fill(' '); -} + XtLcd::XtLcd(Component& _parent, Controller& _controller) : Lcd(_parent, 40, 2), m_controller(_controller) + { + postConstruct(); -XtLcd::~XtLcd() = default; + m_currentText.fill(' '); + } -void XtLcd::refresh() -{ - setText(m_currentText); -} + XtLcd::~XtLcd() = default; -void XtLcd::setText(const std::array<uint8_t, 80>& _text) -{ - m_currentText = _text; + void XtLcd::refresh() + { + setText(m_currentText); + } + + void XtLcd::setText(const std::array<uint8_t, 80>& _text) + { + m_currentText = _text; - std::vector<uint8_t> text{_text.begin(), _text.end()}; + std::vector<uint8_t> text{_text.begin(), _text.end()}; - if(m_controller.isMultiMode()) - text.back() = '1' + m_controller.getCurrentPart(); + if(m_controller.isMultiMode()) + text.back() = '1' + m_controller.getCurrentPart(); - if(!m_paramName.empty() && !m_paramValue.empty()) - { - const auto param = '[' + m_paramName + " = " + m_paramValue + ']'; - if(param.size() <= 40) + if(!m_paramName.empty() && !m_paramValue.empty()) { - memcpy(text.data() + 40 - param.size(), param.c_str(), param.size()); + const auto param = '[' + m_paramName + " = " + m_paramValue + ']'; + if(param.size() <= 40) + { + memcpy(text.data() + 40 - param.size(), param.c_str(), param.size()); + } } - } - Lcd::setText(text); -} + Lcd::setText(text); + } -bool XtLcd::getOverrideText(std::vector<std::vector<uint8_t>>& _lines) -{ - std::string lineA(std::string("Xenia v") + pluginLib::Version::getVersionString()); - std::string lineB = pluginLib::Version::getVersionDateTime(); + bool XtLcd::getOverrideText(std::vector<std::vector<uint8_t>>& _lines) + { + std::string lineA(std::string("Xenia v") + pluginLib::Version::getVersionString()); + std::string lineB = pluginLib::Version::getVersionDateTime(); - constexpr char lineAright[] = "From TUS"; - constexpr char lineBright[] = "with <3"; + constexpr char lineAright[] = "From TUS"; + constexpr char lineBright[] = "with <3"; - while(lineA.size() < 40 - std::size(lineAright) + 1) - lineA.push_back(' '); - lineA += lineAright; + while(lineA.size() < 40 - std::size(lineAright) + 1) + lineA.push_back(' '); + lineA += lineAright; - while(lineB.size() < 40 - std::size(lineBright) + 1) - lineB.push_back(' '); - lineB += lineBright; + while(lineB.size() < 40 - std::size(lineBright) + 1) + lineB.push_back(' '); + lineB += lineBright; - _lines = - { - std::vector<uint8_t>(lineA.begin(), lineA.end()), - std::vector<uint8_t>(lineB.begin(), lineB.end()) - }; + _lines = + { + std::vector<uint8_t>(lineA.begin(), lineA.end()), + std::vector<uint8_t>(lineB.begin(), lineB.end()) + }; - return true; -} + return true; + } -const uint8_t* XtLcd::getCharacterData(const uint8_t _character) const -{ - return wLib::getCharacterData(_character); -} + const uint8_t* XtLcd::getCharacterData(const uint8_t _character) const + { + return hwLib::getCharacterData(_character); + } -void XtLcd::setCurrentParameter(const std::string& _name, const std::string& _value) -{ - m_paramName = _name; - m_paramValue = _value; + void XtLcd::setCurrentParameter(const std::string& _name, const std::string& _value) + { + m_paramName = _name; + m_paramValue = _value; - setText(m_currentText); -} + setText(m_currentText); + } +} +\ No newline at end of file diff --git a/source/xtJucePlugin/xtLcd.h b/source/xtJucePlugin/xtLcd.h @@ -7,25 +7,28 @@ #include "jucePluginEditorLib/lcd.h" -class Controller; - -class XtLcd final : public jucePluginEditorLib::Lcd +namespace xtJucePlugin { -public: - explicit XtLcd(Component& _parent, Controller& _controller); - ~XtLcd() override; + class Controller; + + class XtLcd final : public jucePluginEditorLib::Lcd + { + public: + explicit XtLcd(Component& _parent, Controller& _controller); + ~XtLcd() override; - void refresh(); - void setText(const std::array<uint8_t, 80> &_text); + void refresh(); + void setText(const std::array<uint8_t, 80> &_text); - bool getOverrideText(std::vector<std::vector<uint8_t>>& _lines) override; - const uint8_t* getCharacterData(uint8_t _character) const override; + bool getOverrideText(std::vector<std::vector<uint8_t>>& _lines) override; + const uint8_t* getCharacterData(uint8_t _character) const override; - void setCurrentParameter(const std::string& _name, const std::string& _value); + void setCurrentParameter(const std::string& _name, const std::string& _value); -private: - std::array<uint8_t, 80> m_currentText; - Controller& m_controller; - std::string m_paramName; - std::string m_paramValue; -}; + private: + std::array<uint8_t, 80> m_currentText; + Controller& m_controller; + std::string m_paramName; + std::string m_paramValue; + }; +} +\ No newline at end of file diff --git a/source/xtJucePlugin/xtPatchManager.cpp b/source/xtJucePlugin/xtPatchManager.cpp @@ -103,17 +103,6 @@ namespace xtJucePlugin return m_controller.createSingleDump(static_cast<xt::LocationH>(static_cast<uint8_t>(xt::LocationH::SingleBankA) + bank), static_cast<uint8_t>(program), parameterValues); } - bool PatchManager::equals(const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) const - { - if(_a == _b) - return true; - - if(_a->hash == _b->hash) - return true; - - return false; - } - uint32_t PatchManager::getCurrentPart() const { return m_editor.getProcessor().getController().getCurrentPart(); diff --git a/source/xtJucePlugin/xtPatchManager.h b/source/xtJucePlugin/xtPatchManager.h @@ -2,11 +2,10 @@ #include "jucePluginEditorLib/patchmanager/patchmanager.h" -class Controller; - namespace xtJucePlugin { class Editor; + class Controller; class PatchManager : public jucePluginEditorLib::patchManager::PatchManager { @@ -19,7 +18,6 @@ namespace xtJucePlugin bool loadRomData(pluginLib::patchDB::DataList& _results, uint32_t _bank, uint32_t _program) override; pluginLib::patchDB::PatchPtr initializePatch(pluginLib::patchDB::Data&& _sysex) override; pluginLib::patchDB::Data prepareSave(const pluginLib::patchDB::PatchPtr& _patch) const override; - bool equals(const pluginLib::patchDB::PatchPtr& _a, const pluginLib::patchDB::PatchPtr& _b) const override; uint32_t getCurrentPart() const override; bool activatePatch(const pluginLib::patchDB::PatchPtr& _patch) override; bool activatePatch(const pluginLib::patchDB::PatchPtr& _patch, uint32_t _part) override; diff --git a/source/xtLib/xtDSP.cpp b/source/xtLib/xtDSP.cpp @@ -1,6 +1,5 @@ #include "xtDSP.h" -#include "wLib/dspBootCode.h" #include "xtHardware.h" #if DSP56300_DEBUGGER @@ -8,6 +7,7 @@ #endif #include "mc68k/hdi08.h" + #include "dsp56kEmu/aar.h" #include "dsp56kEmu/types.h" @@ -22,6 +22,7 @@ namespace xt , m_periphX() , m_memory(g_memoryValidator, g_pMemSize, g_xyMemSize, g_bridgedAddr, m_memoryBuffer) , m_dsp(m_memory, &m_periphX, &m_periphNop) + , m_boot(m_dsp) { if(!_hardware.isValid()) return; @@ -51,28 +52,7 @@ namespace xt m_dsp.getJit().notifyProgramMemWrite(i); } - const auto& bootCode = wLib::g_dspBootCode56303; - - // rewrite bootloader to work at address g_bootCodeBase instead of $ff0000 - for(uint32_t i=0; i<std::size(bootCode); ++i) - { - uint32_t code = bootCode[i]; - if((code & 0xffff00) == 0xff0000) - { - code = g_bootCodeBase | (bootCode[i] & 0xff); - } - - m_memory.set(dsp56k::MemArea_P, i + g_bootCodeBase, code); - m_dsp.getJit().notifyProgramMemWrite(i + g_bootCodeBase); - } - -// m_memory.saveAssembly("dspBootDisasm.asm", g_bootCodeBase, static_cast<uint32_t>(std::size(bootCode)), true, true, &m_periphX, nullptr); - - // set OMR pins so that bootcode wants program data via HDI08 RX - m_dsp.setPC(g_bootCodeBase); - m_dsp.regs().omr.var |= OMR_MA | OMR_MB | OMR_MC | OMR_MD; - -// getPeriph().disableTimers(true); // only used to test DSP load, we report 0 all the time for now +// getPeriph().disableTimers(true); m_periphX.getEssi0().writeEmptyAudioIn(8); @@ -85,7 +65,8 @@ namespace xt }); m_hdiUC.setWriteTxCallback([&](const uint32_t _word) { - hdiTransferUCtoDSP(_word); + if(m_boot.hdiWriteTX(_word)) + onDspBooted(); }); m_hdiUC.setWriteIrqCallback([&](const uint8_t _irq) { @@ -95,14 +76,6 @@ namespace xt { return hdiUcReadIsr(_isr); }); - -#if DSP56300_DEBUGGER - m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp))); -#else - m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); -#endif - - m_thread->setLogToStdout(false); } void DSP::exec() @@ -138,6 +111,22 @@ namespace xt } } + void DSP::onDspBooted() + { + m_hdiUC.setWriteTxCallback([&](const uint32_t _word) + { + hdiTransferUCtoDSP(_word); + }); + +#if DSP56300_DEBUGGER + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str(), std::make_shared<dsp56kDebugger::Debugger>(m_dsp))); +#else + m_thread.reset(new dsp56k::DSPThread(dsp(), m_name.c_str())); +#endif + + m_thread->setLogToStdout(false); + } + void DSP::onUCRxEmpty(bool _needMoreData) { hdi08().injectTXInterrupt(); diff --git a/source/xtLib/xtDSP.h b/source/xtLib/xtDSP.h @@ -4,6 +4,8 @@ #include "dsp56kEmu/dspthread.h" #include "dsp56kEmu/peripherals.h" +#include "hardwareLib/dspBootCode.h" + #include "wLib/wDsp.h" namespace mc68k @@ -21,7 +23,6 @@ namespace xt static constexpr dsp56k::TWord g_bridgedAddr = 0x020000; // start of external SRAM, mapped to X and Y static constexpr dsp56k::TWord g_xyMemSize = 0x800000; // due to weird AAR mapping we just allocate enough so that everything fits into it static constexpr dsp56k::TWord g_pMemSize = 0x020000; // DSP code does not use all of it, gives space for our boot code - static constexpr dsp56k::TWord g_bootCodeBase = 0x010000; DSP(Hardware& _hardware, mc68k::Hdi08& _hdiUC, uint32_t _index); void exec(); @@ -50,6 +51,7 @@ namespace xt dsp56k::DSPThread& thread() { return *m_thread; } bool haveSentTXToDSP() const { return m_haveSentTXtoDSP; } + void onDspBooted(); private: void onUCRxEmpty(bool _needMoreData); @@ -72,5 +74,6 @@ namespace xt uint32_t m_hdiHF01 = 0; // uc => DSP std::unique_ptr<dsp56k::DSPThread> m_thread; + hwLib::DspBoot m_boot; }; } diff --git a/source/xtLib/xtHardware.cpp b/source/xtLib/xtHardware.cpp @@ -14,7 +14,7 @@ namespace xt , m_rom(RomLoader::findROM()) , m_uc(m_rom) , m_dsps{DSP(*this, m_uc.getHdi08A().getHdi08(), 0)} - , m_midi(m_uc.getQSM()) + , m_midi(m_uc.getQSM(), 40000) { if(!m_rom.isValid()) throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing); diff --git a/source/xtLib/xtHardware.h b/source/xtLib/xtHardware.h @@ -1,14 +1,13 @@ #pragma once -#include <string> - #include "xtDSP.h" #include "xtRom.h" #include "xtUc.h" #include "dsp56kEmu/dspthread.h" -#include "wLib/wMidi.h" +#include "hardwareLib/sciMidi.h" + #include "wLib/wHardware.h" namespace xt @@ -50,7 +49,7 @@ namespace xt void initVoiceExpansion(); - wLib::Midi& getMidi() override + hwLib::SciMidi& getMidi() override { return m_midi; } @@ -70,6 +69,6 @@ namespace xt TAudioInputs m_audioInputs; TAudioOutputs m_audioOutputs; std::array<DSP,g_dspCount> m_dsps; - wLib::Midi m_midi; + hwLib::SciMidi m_midi; }; } diff --git a/source/xtLib/xtUc.h b/source/xtLib/xtUc.h @@ -9,7 +9,7 @@ #include "mc68k/mc68k.h" #include "mc68k/hdi08periph.h" -#include "wLib/am29f.h" +#include "hardwareLib/am29f.h" namespace xt { @@ -51,7 +51,7 @@ namespace xt std::array<uint8_t, g_romSize> m_romRuntimeData; xtHdi08A m_hdiA; - wLib::Am29f m_flash; + hwLib::Am29f m_flash; Pic m_pic; Lcd m_lcd; diff --git a/source/xtTestConsole/xtTestConsole.cpp b/source/xtTestConsole/xtTestConsole.cpp @@ -14,8 +14,6 @@ int main() const std::unique_ptr uc(std::make_unique<xt::Xt>()); - uc->getHardware()->getDSP().thread().setLogToStdout(true); - constexpr uint32_t blockSize = 64; std::vector<dsp56k::TWord> stereoOutput; stereoOutput.resize(blockSize<<1); @@ -27,6 +25,8 @@ int main() while(!uc->isBootCompleted()) uc->process(blockSize); + uc->getHardware()->getDSP().thread().setLogToStdout(true); + auto processLength = [&](const uint32_t _length) { size_t total = 0;