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 820bafbb0c89dbf1289c976fa2584a97b925ab90
parent 5e8ed88c4315fd55ca3b1a8d89585be326c3aa66
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sat,  2 Apr 2022 02:31:44 +0200

Merge branch 'genericUI' into dsp56300

Diffstat:
M.gitattributes | 2++
MCMakeLists.txt | 169++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mdoc/changelog.txt | 37+++++++++++++++++++++++++++++++++++++
Msource/jucePlugin/CMakeLists.txt | 338+++++++++++++++++++++++++++++++-------------------------------------------------
Msource/jucePlugin/PluginEditor.cpp | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msource/jucePlugin/PluginEditor.h | 32+++++++++++++++++++++++++-------
Msource/jucePlugin/VirusController.cpp | 2+-
Msource/jucePlugin/VirusController.h | 53+++++++++++++++++++++++++++++++++--------------------
Msource/jucePlugin/VirusParameterBinding.cpp | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msource/jucePlugin/VirusParameterBinding.h | 19+++++++++++++++----
Asource/jucePlugin/skins/Hoverland/ARP_2034x1238_x2.png | 0
Asource/jucePlugin/skins/Hoverland/ARP_282x52_x2.png | 0
Asource/jucePlugin/skins/Hoverland/FX_2034x1238_x2_D.png | 0
Asource/jucePlugin/skins/Hoverland/FX_282x52_x2.png | 0
Asource/jucePlugin/skins/Hoverland/FX_reverbOverlay.png | 0
Asource/jucePlugin/skins/Hoverland/GenPol_140x140_x2.png | 0
Asource/jucePlugin/skins/Hoverland/GenRed_140x140_x2.png | 0
Asource/jucePlugin/skins/Hoverland/Gen_140x140_x2.png | 0
Asource/jucePlugin/skins/Hoverland/LFO_2034x1238_x2.png | 0
Asource/jucePlugin/skins/Hoverland/LFO_282x52_x2.png | 0
Asource/jucePlugin/skins/Hoverland/Mult_36x36_x2i.png | 0
Asource/jucePlugin/skins/Hoverland/OSC_2034x1238_x2.png | 0
Asource/jucePlugin/skins/Hoverland/OSC_282x52_x2.png | 0
Asource/jucePlugin/skins/Hoverland/Pres_2034x1238_x2.png | 0
Asource/jucePlugin/skins/Hoverland/VC_x2_N.png | 0
Asource/jucePlugin/skins/Hoverland/VirusC_Hoverland.json | 5740+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/skins/Hoverland/arp_72x37_X2.png | 0
Asource/jucePlugin/skins/Hoverland/arpall_104x432_X2.png | 0
Asource/jucePlugin/skins/Hoverland/assets.cmake | 29+++++++++++++++++++++++++++++
Asource/jucePlugin/skins/Hoverland/env_pol_100x68_x2.png | 0
Asource/jucePlugin/skins/Hoverland/handle_72x94_x2.png | 0
Asource/jucePlugin/skins/Hoverland/lfo_btn_46x76_x2.png | 0
Asource/jucePlugin/skins/Hoverland/link_horizon_48x72_x2.png | 0
Asource/jucePlugin/skins/Hoverland/link_vert_72x48_x2.png | 0
Asource/jucePlugin/skins/Hoverland/multi_btn_select_72x72_x2.png | 0
Asource/jucePlugin/skins/Hoverland/presest_btn_select_86x60_x2.png | 0
Asource/jucePlugin/skins/Hoverland/sync2_108x100_x2.png | 0
Asource/jucePlugin/skins/Trancy/Digital.ttf | 0
Asource/jucePlugin/skins/Trancy/VirusC_Trancy.json | 5517+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/skins/Trancy/assets.cmake | 26++++++++++++++++++++++++++
Asource/jucePlugin/skins/Trancy/btn_1.png | 0
Asource/jucePlugin/skins/Trancy/btn_2.png | 0
Asource/jucePlugin/skins/Trancy/btn_3.png | 0
Asource/jucePlugin/skins/Trancy/btn_4.png | 0
Asource/jucePlugin/skins/Trancy/btn_left.png | 0
Asource/jucePlugin/skins/Trancy/btn_main_1.png | 0
Asource/jucePlugin/skins/Trancy/btn_main_2.png | 0
Asource/jucePlugin/skins/Trancy/btn_main_3.png | 0
Asource/jucePlugin/skins/Trancy/btn_main_4.png | 0
Asource/jucePlugin/skins/Trancy/btn_main_5.png | 0
Asource/jucePlugin/skins/Trancy/btn_menu.png | 0
Asource/jucePlugin/skins/Trancy/btn_right.png | 0
Asource/jucePlugin/skins/Trancy/btn_save_preset.png | 0
Asource/jucePlugin/skins/Trancy/knob_1_128.png | 0
Asource/jucePlugin/skins/Trancy/knob_2_128.png | 0
Asource/jucePlugin/skins/Trancy/main_background.png | 0
Asource/jucePlugin/skins/Trancy/panel_1.png | 0
Asource/jucePlugin/skins/Trancy/panel_2.png | 0
Asource/jucePlugin/skins/Trancy/panel_3.png | 0
Asource/jucePlugin/skins/Trancy/panel_4.png | 0
Asource/jucePlugin/skins/Trancy/panel_5.png | 0
Dsource/jucePlugin/ui/Virus_PatchBrowser.cpp | 388-------------------------------------------------------------------------------
Dsource/jucePlugin/ui/Virus_PatchBrowser.h | 81-------------------------------------------------------------------------------
Asource/jucePlugin/ui3/FxPage.cpp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/FxPage.h | 28++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/MidiPorts.cpp | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/MidiPorts.h | 33+++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/Parts.cpp | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/Parts.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/PatchBrowser.cpp | 462+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/PatchBrowser.h | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/Tabs.cpp | 38++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/Tabs.h | 27+++++++++++++++++++++++++++
Asource/jucePlugin/ui3/VirusEditor.cpp | 484+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/jucePlugin/ui3/VirusEditor.h | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/CMakeLists.txt | 24++++++++++++++++++++++++
Asource/juceUiLib/buttonStyle.cpp | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/buttonStyle.h | 30++++++++++++++++++++++++++++++
Asource/juceUiLib/comboboxStyle.cpp | 13+++++++++++++
Asource/juceUiLib/comboboxStyle.h | 18++++++++++++++++++
Asource/juceUiLib/editor.cpp | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/editor.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/editorInterface.h | 18++++++++++++++++++
Asource/juceUiLib/hyperlinkbuttonStyle.cpp | 12++++++++++++
Asource/juceUiLib/hyperlinkbuttonStyle.h | 19+++++++++++++++++++
Asource/juceUiLib/image.cpp | 5+++++
Asource/juceUiLib/image.h | 10++++++++++
Asource/juceUiLib/labelStyle.cpp | 13+++++++++++++
Asource/juceUiLib/labelStyle.h | 14++++++++++++++
Asource/juceUiLib/rotaryStyle.cpp | 23+++++++++++++++++++++++
Asource/juceUiLib/rotaryStyle.h | 17+++++++++++++++++
Asource/juceUiLib/textbuttonStyle.cpp | 15+++++++++++++++
Asource/juceUiLib/textbuttonStyle.h | 17+++++++++++++++++
Asource/juceUiLib/uiObject.cpp | 332+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/uiObject.h | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/uiObjectStyle.cpp | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/juceUiLib/uiObjectStyle.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Msource/synthLib/midiToSysex.cpp | 23+++++++++++++++++++++++
Msource/synthLib/midiToSysex.h | 1+
Msource/synthLib/midiTypes.h | 7++++---
Msource/synthLib/os.cpp | 175++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msource/synthLib/os.h | 11+++++++++++
Msource/virusLib/CMakeLists.txt | 2++
Asource/virusLib/demopacketvalidator.cpp | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusLib/demopacketvalidator.h | 33+++++++++++++++++++++++++++++++++
Asource/virusLib/demoplayback.cpp | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/virusLib/demoplayback.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msource/virusLib/microcontroller.h | 3+++
Msource/virusTestConsole/virusTestConsole.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++----------------
Mtemp/cmake_win64/gearmulator.sln.DotSettings | 1+
110 files changed, 15442 insertions(+), 961 deletions(-)

diff --git a/.gitattributes b/.gitattributes @@ -1,2 +1,4 @@ * text=auto *.sh text eol=lf +*.txt text eol=lf +*.cmake text eol=lf diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -1,84 +1,85 @@ -cmake_minimum_required(VERSION 3.15) - -# build a fat binary that runs on both intel and the new Apple M1 chip -set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "OS X Architectures") - -set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") - -project(gearmulator VERSION 1.2.12) - -include(base.cmake) - -set(ASMJIT_STATIC TRUE) - -option(${PROJECT_NAME}_BUILD_JUCEPLUGIN "Build Juce plugin" on) - -add_subdirectory(source/dsp56300/source) -add_subdirectory(source/synthLib) -add_subdirectory(source/virusLib) -add_subdirectory(source/libresample) - -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/source/vstsdk2.4.2/public.sdk/source/vst2.x/audioeffect.h) - set(JUCE_GLOBAL_VST2_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/source/vstsdk2.4.2/) -endif() - -# ----------------- Juce based audio plugin - -set_property(GLOBAL PROPERTY USE_FOLDERS YES) -if(${PROJECT_NAME}_BUILD_JUCEPLUGIN) - set(JUCE_ENABLE_MODULE_SOURCE_GROUPS ON CACHE BOOL "" FORCE) - add_subdirectory(source/JUCE) - add_subdirectory(source/jucePlugin) -endif() - -# ----------------- Test Console - -add_executable(virusTestConsole) -target_sources(virusTestConsole PRIVATE source/virusTestConsole/virusTestConsole.cpp) -target_link_libraries(virusTestConsole PUBLIC virusLib) - -if(UNIX AND NOT APPLE) - target_link_libraries(virusTestConsole PUBLIC -static-libgcc -static-libstdc++) -endif() - -install(TARGETS virusTestConsole DESTINATION . COMPONENT testConsole) - -if(MSVC) - install(DIRECTORY deploy/win/ DESTINATION . COMPONENT testConsole) -else() - install(DIRECTORY deploy/linux/ DESTINATION . COMPONENT testConsole) -endif() - -# ----------------- CPack - -get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) -list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") - -message("CMAKE_SYSTEM_NAME: " ${CMAKE_SYSTEM_NAME}) -message("CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) - -if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - set(CPACK_SYSTEM_NAME ${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR}) -elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") - set(CPACK_SYSTEM_NAME "MacOS") -endif() - -set(CPACK_COMPONENTS_GROUPING IGNORE) -set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) - -set(CPACK_PACKAGE_CONTACT "The Usual Suspects") -set(CPACK_PACKAGE_VENDOR "The Usual Suspects") -set(CPACK_PACKAGE_NAME "DSP56300Emu") -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "DSP 56300 family emulator audio plugin") - -set(CPACK_DEB_COMPONENT_INSTALL ON) -set(CPACK_DEBIAN_PACKAGE_MAINTAINER "The Usual Suspects") -set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://dsp56300.wordpress.com") -set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) - -set(CPACK_RPM_COMPONENT_INSTALL ON) -set(CPACK_RPM_PACKAGE_AUTOREQ "yes") -set(CPACK_RPM_PACKAGE_URL ${CPACK_DEBIAN_PACKAGE_HOMEPAGE}) -set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}) - -include(CPack) +cmake_minimum_required(VERSION 3.15) + +# build a fat binary that runs on both intel and the new Apple M1 chip +set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "OS X Architectures") + +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum OS X deployment version") + +project(gearmulator VERSION 1.2.15) + +include(base.cmake) + +set(ASMJIT_STATIC TRUE) + +option(${PROJECT_NAME}_BUILD_JUCEPLUGIN "Build Juce plugin" on) + +add_subdirectory(source/dsp56300/source) +add_subdirectory(source/synthLib) +add_subdirectory(source/virusLib) +add_subdirectory(source/libresample) + +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/source/vstsdk2.4.2/public.sdk/source/vst2.x/audioeffect.h) + set(JUCE_GLOBAL_VST2_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/source/vstsdk2.4.2/) +endif() + +# ----------------- Juce based audio plugin + +set_property(GLOBAL PROPERTY USE_FOLDERS YES) +if(${PROJECT_NAME}_BUILD_JUCEPLUGIN) + set(JUCE_ENABLE_MODULE_SOURCE_GROUPS ON CACHE BOOL "" FORCE) + add_subdirectory(source/JUCE) + add_subdirectory(source/juceUiLib) + add_subdirectory(source/jucePlugin) +endif() + +# ----------------- Test Console + +add_executable(virusTestConsole) +target_sources(virusTestConsole PRIVATE source/virusTestConsole/virusTestConsole.cpp) +target_link_libraries(virusTestConsole PUBLIC virusLib) + +if(UNIX AND NOT APPLE) + target_link_libraries(virusTestConsole PUBLIC -static-libgcc -static-libstdc++) +endif() + +install(TARGETS virusTestConsole DESTINATION . COMPONENT testConsole) + +if(MSVC) + install(DIRECTORY deploy/win/ DESTINATION . COMPONENT testConsole) +else() + install(DIRECTORY deploy/linux/ DESTINATION . COMPONENT testConsole) +endif() + +# ----------------- CPack + +get_cmake_property(CPACK_COMPONENTS_ALL COMPONENTS) +list(REMOVE_ITEM CPACK_COMPONENTS_ALL "Unspecified") + +message("CMAKE_SYSTEM_NAME: " ${CMAKE_SYSTEM_NAME}) +message("CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(CPACK_SYSTEM_NAME ${CMAKE_SYSTEM_NAME}_${CMAKE_SYSTEM_PROCESSOR}) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CPACK_SYSTEM_NAME "MacOS") +endif() + +set(CPACK_COMPONENTS_GROUPING IGNORE) +set(CPACK_ARCHIVE_COMPONENT_INSTALL ON) + +set(CPACK_PACKAGE_CONTACT "The Usual Suspects") +set(CPACK_PACKAGE_VENDOR "The Usual Suspects") +set(CPACK_PACKAGE_NAME "DSP56300Emu") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "DSP 56300 family emulator audio plugin") + +set(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "The Usual Suspects") +set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://dsp56300.wordpress.com") +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + +set(CPACK_RPM_COMPONENT_INSTALL ON) +set(CPACK_RPM_PACKAGE_AUTOREQ "yes") +set(CPACK_RPM_PACKAGE_URL ${CPACK_DEBIAN_PACKAGE_HOMEPAGE}) +set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION_SUMMARY}) + +include(CPack) diff --git a/doc/changelog.txt b/doc/changelog.txt @@ -1,5 +1,42 @@ Release Notes +1.2.15 (2022.03.26): + +- [Fix] Skin files were not correctly loaded from disk +- [Fix] [UI Hoverland] LFO3 destination was a duplicate of LFO2 destination +- [Fix] [UI Hoverland] MidEQ Gain & Freq were duplicates of LowEQ Gain & Freq +- [Fix] [UI Trancy] Broken Part Volume & Pan knobs + +1.2.14 (2022.03.25): + +- [Imp] Performance improved in some circumstances when JIT compiler had to regenerate code +- [Imp] Drop down menus with lots of entries now use multiple columns + +- [Fix] UI skins were not found on disk (Mac/Linux) +- [Fix] [UI Trancy] Size of some labels reduced to be able to display more text +- [Fix] [UI Trancy] Unison Mode switch did not work + +1.2.13 (2022.03.23): + +UI only release + +The UI system has been rewritten and is now data-driven which allows custom skins. + +Some smaller changes & improvements have been integrated while reimplementing the UI + +- Debug-style drop down boxes in the upper left corner have been removed and replaced by a right-click context menu +- UI base scale has been adjusted so that both skins have about the same size +- The current skin can be exported to disk by right clicking and selecting "Export" in the context menu +- Additional skins that are found in the "skins" folder next to the plugin can be selected in the context menu +- Focused parameters now display the correct text instead of displaying a generic value of 0-127 + +Hoverland skin specific: +- Debug-style Single/Multi play mode switch buttons have been replaced with better graphics +- Version number & Donate Info have moved to the bottom and use the skin text color +- The duplicated Presets tab has been removed as a presets button was already there +- The info about the loaded ROM has been moved to the upper right and is prepared for runtime ROM switching (not implemented yet) +- Physical MIDI port selection has moved to the global page + 1.2.12 (2022.03.17): - [Imp] Both skins are now part of one plugin, the skin can be switched from a drop down menu in the upper left corner diff --git a/source/jucePlugin/CMakeLists.txt b/source/jucePlugin/CMakeLists.txt @@ -1,208 +1,130 @@ - -cmake_minimum_required(VERSION 3.15) -project(jucePlugin VERSION ${CMAKE_PROJECT_VERSION}) - -option(${CMAKE_PROJECT_NAME}_BUILD_FX_PLUGIN "Build FX plugin variants" off) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h) - -if(JUCE_GLOBAL_VST2_SDK_PATH) - set(VST "VST") -else() - set(VST "") -endif() - -set(SOURCES - parameterDescriptions_C.json - PluginEditor.cpp - PluginEditor.h - PluginProcessor.cpp - PluginProcessor.h - VirusController.cpp - VirusController.h - VirusParameter.cpp - VirusParameter.h - VirusParameterBinding.cpp - VirusParameterBinding.h - VirusParameterDescription.cpp - VirusParameterDescription.h - VirusParameterDescriptions.cpp - VirusParameterDescriptions.h - version.h -) - -set(SOURCES_UI - ui/Virus_Buttons.cpp - ui/Virus_LookAndFeel.cpp - ui/VirusEditor.cpp - ui/Virus_ArpEditor.cpp - ui/Virus_FxEditor.cpp - ui/Virus_LfoEditor.cpp - ui/Virus_OscEditor.cpp - ui/Virus_PatchBrowser.cpp - ui/Virus_Parts.cpp - ui/Virus_Buttons.h - ui/Virus_LookAndFeel.h - ui/VirusEditor.h - ui/Virus_ArpEditor.h - ui/Virus_FxEditor.h - ui/Virus_LfoEditor.h - ui/Virus_OscEditor.h - ui/Virus_PatchBrowser.h - ui/Virus_Parts.h - ui/Ui_Utils.h -) - -set(SOURCES_UI2 - ui2/Ui_Utils.h - ui2/Virus_Buttons.cpp - ui2/Virus_Buttons.h - ui2/Virus_LookAndFeel.cpp - ui2/Virus_LookAndFeel.h - ui2/Virus_Panel1_OscEditor.cpp - ui2/Virus_Panel1_OscEditor.h - ui2/Virus_Panel2_LfoEditor.cpp - ui2/Virus_Panel2_LfoEditor.h - ui2/Virus_Panel3_FxEditor.cpp - ui2/Virus_Panel3_FxEditor.h - ui2/Virus_Panel4_ArpEditor.cpp - ui2/Virus_Panel4_ArpEditor.h - ui2/Virus_Panel5_PatchBrowser.cpp - ui2/Virus_Panel5_PatchBrowser.h - ui2/VirusEditor.cpp - ui2/VirusEditor.h -) - -# 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 - "assets/bg_1377x800.png" - "assets/panels/bg_arp_1018x620.png" - "assets/panels/bg_fx_1018x620.png" - "assets/panels/bg_lfo_1018x620.png" - "assets/panels/bg_osc_1018x620.png" - "assets/panels/bg_fxreverb_481x234.png" - "assets/panels/bg_fxdelay_481x234.png" - "assets/buttons/GLOBAL_btn_arp_settings_141x26.png" - "assets/buttons/GLOBAL_btn_effects_141x26.png" - "assets/buttons/GLOBAL_btn_lfo_matrix_141x26.png" - "assets/buttons/GLOBAL_btn_osc_filter_141x26.png" - "assets/buttons/GLOBAL_btn_patch_browser_141x26.png" - "assets/buttons/env_pol_50x34.png" - "assets/buttons/lfo_btn_23_19.png" - "assets/buttons/link_vert_12x36.png" - "assets/buttons/link_horizon_36x12.png" - "assets/buttons/presets_btn_43_15.png" - "assets/buttons/Handle_18x47.png" - "assets/buttons/sync2_54x25.png" - "assets/buttons/part_select_btn_36x36.png" - "assets/buttons/arphold_btn_36x36.png" - "assets/knobs/Gen_70x70_100.png" - "assets/knobs/Gen_pol_70x70_100.png" - "assets/knobs/GenBlue_70x70_100.png" - "assets/knobs/GenRed_70x70_100.png" - "assets/knobs/multi_18x18_100.png" - - "assets2/main_background.png" - "assets2/panels/panel_1.png" - "assets2/panels/panel_2.png" - "assets2/panels/panel_3.png" - "assets2/panels/panel_4.png" - "assets2/panels/panel_5.png" - "assets2/buttons/btn_main_1.png" - "assets2/buttons/btn_main_2.png" - "assets2/buttons/btn_main_3.png" - "assets2/buttons/btn_main_4.png" - "assets2/buttons/btn_main_5.png" - "assets2/buttons/btn_1.png" - "assets2/buttons/btn_2.png" - "assets2/buttons/btn_3.png" - "assets2/buttons/btn_4.png" - "assets2/buttons/btn_left.png" - "assets2/buttons/btn_right.png" - "assets2/buttons/btn_down.png" - "assets2/buttons/btn_menu.png" - "assets2/buttons/btn_load_bank.png" - "assets2/buttons/btn_save_preset.png" - "assets2/combobox/cmb_1.png" - "assets2/combobox/cmb_2.png" - "assets2/knobs/knob_1_128.png" - "assets2/knobs/knob_2_128.png" - "assets2/font/Digital" - - "parameterDescriptions_C.json" -) - -macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProject componentName) - juce_add_plugin(${targetName} - # VERSION ... # Set this if the plugin version is different to the project version - # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone - # ICON_SMALL ... - COMPANY_NAME "The Usual Suspects" # Specify the name of the plugin's author - IS_SYNTH ${isSynth} # Is this a synth or an effect? - NEEDS_MIDI_INPUT TRUE # Does the plugin need midi input? - NEEDS_MIDI_OUTPUT TRUE # Does the plugin need midi output? - IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect? - EDITOR_WANTS_KEYBOARD_FOCUS TRUE # Does the editor need keyboard focus? - COPY_PLUGIN_AFTER_BUILD FALSE # Should the plugin be installed to a default location after building? - PLUGIN_MANUFACTURER_CODE TusP # A four-character manufacturer id with at least one upper-case character - PLUGIN_CODE ${plugin4CC} # A unique four-character plugin id with exactly one upper-case character - # GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case - FORMATS AU VST3 ${VST} Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 - PRODUCT_NAME ${productName} # The name of the final executable, which can differ from the target name - ) - - target_sources(${targetName} PRIVATE ${SOURCES} ${SOURCES_UI} ${SOURCES_UI2}) - - source_group("source" FILES ${SOURCES}) - source_group("source\\ui" FILES ${SOURCES_UI}) - source_group("source\\ui2" FILES ${SOURCES_UI2}) - - target_compile_definitions(${targetName} - PUBLIC - # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. - JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call - JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call - JUCE_VST3_CAN_REPLACE_VST2=0 - JUCE_WIN_PER_MONITOR_DPI_AWARE=0 - ) - - target_link_libraries(${targetName} - PRIVATE - ${binaryDataProject} - juce::juce_audio_utils - juce::juce_cryptography - PUBLIC - virusLib - #juce::juce_recommended_config_flags - #juce::juce_recommended_lto_flags - #juce::juce_recommended_warning_flags - ) - - if(UNIX AND NOT APPLE) - target_link_libraries(${targetName} PUBLIC -static-libgcc -static-libstdc++) - endif() - - if(MSVC OR APPLE) - if(JUCE_GLOBAL_VST2_SDK_PATH) - install(TARGETS ${targetName}_VST DESTINATION . COMPONENT VST2${componentName}) - endif() - install(TARGETS ${targetName}_VST3 DESTINATION . COMPONENT VST3${componentName}) - if(APPLE) - install(TARGETS ${targetName}_AU DESTINATION . COMPONENT AU${componentName}) - endif() - elseif(UNIX) - if(JUCE_GLOBAL_VST2_SDK_PATH) - install(TARGETS ${targetName}_VST LIBRARY DESTINATION /usr/local/lib/lxvst/ COMPONENT VST2${componentName}) - endif() - install(TARGETS ${targetName}_VST3 LIBRARY DESTINATION /usr/local/lib/vst3/ COMPONENT VST3${componentName}) - endif() -endmacro() - -juce_add_binary_data(jucePlugin_BinaryData${postfix} SOURCES ${ASSETS}) - -createJucePlugin(jucePlugin "DSP56300Emu" TRUE "TusV" jucePlugin_BinaryData${postfix} "") - -if(${CMAKE_PROJECT_NAME}_BUILD_FX_PLUGIN) - createJucePlugin(jucePlugin_FX "DSP56300EmuFX" FALSE "TusF" jucePlugin_BinaryData${postfix} "_FX") -endif() + +cmake_minimum_required(VERSION 3.15) +project(jucePlugin VERSION ${CMAKE_PROJECT_VERSION}) + +option(${CMAKE_PROJECT_NAME}_BUILD_FX_PLUGIN "Build FX plugin variants" off) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h) + +if(JUCE_GLOBAL_VST2_SDK_PATH) + set(VST "VST") +else() + set(VST "") +endif() + +set(SOURCES + parameterDescriptions_C.json + PluginEditor.cpp + PluginEditor.h + PluginProcessor.cpp + PluginProcessor.h + VirusController.cpp + VirusController.h + VirusParameter.cpp + VirusParameter.h + VirusParameterBinding.cpp + VirusParameterBinding.h + VirusParameterDescription.cpp + VirusParameterDescription.h + VirusParameterDescriptions.cpp + VirusParameterDescriptions.h + version.h +) + +set(SOURCES_UI3 + ui3/FxPage.cpp + ui3/FxPage.h + ui3/MidiPorts.cpp + ui3/MidiPorts.h + ui3/Parts.cpp + ui3/Parts.h + ui3/PatchBrowser.cpp + ui3/PatchBrowser.h + ui3/Tabs.cpp + ui3/Tabs.h + ui3/VirusEditor.cpp + ui3/VirusEditor.h +) + +# 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_C.json" +) + +include(skins/Hoverland/assets.cmake) +include(skins/Trancy/assets.cmake) + +macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProject componentName) + juce_add_plugin(${targetName} + # VERSION ... # Set this if the plugin version is different to the project version + # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone + # ICON_SMALL ... + COMPANY_NAME "The Usual Suspects" # Specify the name of the plugin's author + IS_SYNTH ${isSynth} # Is this a synth or an effect? + NEEDS_MIDI_INPUT TRUE # Does the plugin need midi input? + NEEDS_MIDI_OUTPUT TRUE # Does the plugin need midi output? + IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect? + EDITOR_WANTS_KEYBOARD_FOCUS TRUE # Does the editor need keyboard focus? + COPY_PLUGIN_AFTER_BUILD FALSE # Should the plugin be installed to a default location after building? + PLUGIN_MANUFACTURER_CODE TusP # A four-character manufacturer id with at least one upper-case character + PLUGIN_CODE ${plugin4CC} # A unique four-character plugin id with exactly one upper-case character + # GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case + FORMATS AU VST3 ${VST} Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 + PRODUCT_NAME ${productName} # The name of the final executable, which can differ from the target name + ) + + target_sources(${targetName} PRIVATE ${SOURCES} ${SOURCES_UI3}) + + source_group("source" FILES ${SOURCES}) + source_group("source\\ui3" FILES ${SOURCES_UI3}) + + target_compile_definitions(${targetName} + PUBLIC + # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. + JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call + JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call + JUCE_VST3_CAN_REPLACE_VST2=0 + JUCE_WIN_PER_MONITOR_DPI_AWARE=0 + ) + + target_link_libraries(${targetName} + PRIVATE + ${binaryDataProject} + juceUiLib + juce::juce_audio_utils + juce::juce_cryptography + PUBLIC + virusLib + #juce::juce_recommended_config_flags + #juce::juce_recommended_lto_flags + #juce::juce_recommended_warning_flags + ) + + if(UNIX AND NOT APPLE) + target_link_libraries(${targetName} PUBLIC -static-libgcc -static-libstdc++) + endif() + + if(MSVC OR APPLE) + if(JUCE_GLOBAL_VST2_SDK_PATH) + install(TARGETS ${targetName}_VST DESTINATION . COMPONENT VST2${componentName}) + endif() + install(TARGETS ${targetName}_VST3 DESTINATION . COMPONENT VST3${componentName}) + if(APPLE) + install(TARGETS ${targetName}_AU DESTINATION . COMPONENT AU${componentName}) + endif() + elseif(UNIX) + if(JUCE_GLOBAL_VST2_SDK_PATH) + install(TARGETS ${targetName}_VST LIBRARY DESTINATION /usr/local/lib/lxvst/ COMPONENT VST2${componentName}) + endif() + install(TARGETS ${targetName}_VST3 LIBRARY DESTINATION /usr/local/lib/vst3/ COMPONENT VST3${componentName}) + endif() +endmacro() + +juce_add_binary_data(jucePlugin_BinaryData${postfix} SOURCES ${ASSETS} ${ASSETS_VirusC_Hoverland} ${ASSETS_VirusC_Trancy}) + +createJucePlugin(jucePlugin "DSP56300Emu" TRUE "TusV" jucePlugin_BinaryData${postfix} "") + +if(${CMAKE_PROJECT_NAME}_BUILD_FX_PLUGIN) + createJucePlugin(jucePlugin_FX "DSP56300EmuFX" FALSE "TusF" jucePlugin_BinaryData${postfix} "_FX") +endif() diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp @@ -1,62 +1,45 @@ #include "PluginProcessor.h" #include "PluginEditor.h" + #include "VirusController.h" -#include "ui/VirusEditor.h" #include "ui2/VirusEditor.h" +#include "ui3/VirusEditor.h" + +#include "../synthLib/os.h" //============================================================================== AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) : - AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p), m_scale("Scale"), m_skin("Skin") + AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p) { - ignoreUnused (processorRef); + m_includedSkins.push_back({"Hoverland", "VirusC_Hoverland.json", ""}); + m_includedSkins.push_back({"Trancy", "VirusC_Trancy.json", ""}); + + addMouseListener(this, true); const auto config = processorRef.getController().getConfig(); const auto scale = config->getIntValue("scale", 100); - const int skinId = config->getIntValue("skin", 0); - //m_virusEditor->setTopLeftPosition(0, 0); - m_scale.setBounds(0,0,74,24); - m_scale.addItem("50%", 50); - m_scale.addItem("75%", 75); - m_scale.addItem("100%", 100); - m_scale.addItem("125%", 125); - m_scale.addItem("150%", 150); - m_scale.addItem("200%", 200); - - m_scale.setSelectedId(scale, juce::dontSendNotification); - m_scale.setColour(juce::ComboBox::textColourId, juce::Colours::white); - m_scale.onChange = [this, config]() { - const float value = m_scale.getSelectedIdAsValue().getValue(); - setScaleFactor(value/100.0f); - config->setValue("scale", static_cast<int>(value)); - config->saveIfNeeded(); - }; - setScaleFactor(scale/100.0f); - - m_skin.setBounds(m_scale.getBounds().getX(), m_scale.getBounds().getY() + m_scale.getBounds().getHeight(), 74, 24); - m_skin.addItem("Modern", 1); - m_skin.addItem("Classic", 2); - m_skin.setSelectedId(1, juce::dontSendNotification); - m_skin.setColour(juce::ComboBox::textColourId, juce::Colours::white); - m_skin.setSelectedItemIndex(skinId, juce::dontSendNotification); - addAndMakeVisible(m_scale); - addAndMakeVisible(m_skin); - m_skin.onChange = [this, config]() { - const int skinId = m_skin.getSelectedItemIndex(); - config->setValue("skin", skinId); - config->saveIfNeeded(); - LoadSkin(m_skin.getSelectedItemIndex()); - }; + + Skin skin = readSkinFromConfig(); + + if(skin.jsonFilename.empty()) + { + skin = m_includedSkins[0]; + } + + loadSkin(skin); - LoadSkin(skinId); - //addAndMakeVisible(m_virusEditor); + setGuiScale(scale); } -void AudioPluginAudioProcessorEditor::LoadSkin(int index) { - if(m_currentSkinId == index) +void AudioPluginAudioProcessorEditor::loadSkin(const Skin& _skin) +{ + if(m_currentSkin == _skin) return; - m_currentSkinId = index; + m_currentSkin = _skin; + writeSkinToConfig(_skin); + if (m_virusEditor) { m_parameterBinding.clearBindings(); @@ -66,24 +49,159 @@ void AudioPluginAudioProcessorEditor::LoadSkin(int index) { m_virusEditor.reset(); } - if (index == 1) + m_rootScale = 1.0f; + + try + { + auto* editor = new genericVirusUI::VirusEditor(m_parameterBinding, processorRef, _skin.jsonFilename, _skin.folder, [this] { openMenu(); }); + m_virusEditor.reset(editor); + setSize(m_virusEditor->getWidth(), m_virusEditor->getHeight()); + m_rootScale = editor->getScale(); + + m_virusEditor->setTopLeftPosition(0, 0); + addAndMakeVisible(m_virusEditor.get()); + } + catch(const std::runtime_error& _err) + { + LOG("ERROR: Failed to create editor: " << _err.what()); + + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Skin load failed", _err.what(), "OK"); + m_virusEditor.reset(); + + if(!_skin.folder.empty()) + loadSkin(m_includedSkins[0]); + } +} + +void AudioPluginAudioProcessorEditor::setGuiScale(int percent) +{ + setScaleFactor(static_cast<float>(percent)/100.0f * m_rootScale); + auto* config = processorRef.getController().getConfig(); + config->setValue("scale", percent); + config->saveIfNeeded(); +} + +void AudioPluginAudioProcessorEditor::openMenu() +{ + const auto config = processorRef.getController().getConfig(); + const auto scale = config->getIntValue("scale", 100); + const int skinId = config->getIntValue("skin", 0); + + juce::PopupMenu menu; + + juce::PopupMenu skinMenu; + + auto addSkinEntry = [this, &skinMenu](const Skin& _skin) + { + skinMenu.addItem(_skin.displayName, true, _skin == m_currentSkin,[this, _skin] {loadSkin(_skin);}); + }; + + for (const auto & skin : m_includedSkins) + addSkinEntry(skin); + + bool haveSkinsOnDisk = false; + + // find more skins on disk + const auto modulePath = synthLib::getModulePath(); + + std::vector<std::string> entries; + synthLib::getDirectoryEntries(entries, modulePath + "skins"); + + for (const auto& entry : entries) + { + std::vector<std::string> files; + synthLib::getDirectoryEntries(files, entry); + + for (const auto& file : files) + { + if(synthLib::hasExtension(file, ".json")) + { + if(!haveSkinsOnDisk) + { + haveSkinsOnDisk = true; + skinMenu.addSeparator(); + } + + const auto relativePath = entry.substr(modulePath.size()); + auto jsonName = file; + const auto pathEndPos = jsonName.find_last_of("/\\"); + if(pathEndPos != std::string::npos) + jsonName = file.substr(pathEndPos+1); + const Skin skin{jsonName + " (" + relativePath + ")", jsonName, relativePath}; + addSkinEntry(skin); + } + } + } + + if(m_virusEditor && m_currentSkin.folder.empty()) { - const auto virusEditor = new Trancy::VirusEditor(m_parameterBinding, processorRef); - setSize(virusEditor->iSkinSizeWidth, virusEditor->iSkinSizeHeight); - virusEditor->m_AudioPlugInEditor = this; - m_virusEditor.reset(virusEditor); + auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get()); + if(editor) + { + skinMenu.addSeparator(); + skinMenu.addItem("Export current skin to 'skins' folder on disk", true, false, [this]{exportCurrentSkin();}); + } } - else { - m_virusEditor.reset(new VirusEditor(m_parameterBinding, processorRef)); - setSize(1377, 800); + + juce::PopupMenu scaleMenu; + scaleMenu.addItem("50%", true, scale == 50, [this] { setGuiScale(50); }); + scaleMenu.addItem("75%", true, scale == 75, [this] { setGuiScale(75); }); + scaleMenu.addItem("100%", true, scale == 100, [this] { setGuiScale(100); }); + scaleMenu.addItem("125%", true, scale == 125, [this] { setGuiScale(125); }); + scaleMenu.addItem("150%", true, scale == 150, [this] { setGuiScale(150); }); + scaleMenu.addItem("200%", true, scale == 200, [this] { setGuiScale(200); }); + scaleMenu.addItem("300%", true, scale == 300, [this] { setGuiScale(300); }); + + menu.addSubMenu("GUI Skin", skinMenu); + menu.addSubMenu("GUI Scale", scaleMenu); + + menu.showMenuAsync(juce::PopupMenu::Options()); +} + +void AudioPluginAudioProcessorEditor::exportCurrentSkin() const +{ + if(!m_virusEditor) + return; + + auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get()); + + if(!editor) + return; + + const auto res = editor->exportToFolder(synthLib::getModulePath() + "skins/"); + + if(!res.empty()) + { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Export failed", "Failed to export skin:\n\n" + res, "OK", m_virusEditor.get()); } - m_virusEditor->setTopLeftPosition(0, 0); - addAndMakeVisible(m_virusEditor.get()); - m_scale.toFront(false); - m_skin.toFront(false); + else + { + juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::InfoIcon, "Export finished", "Skin successfully exported"); + } +} + +AudioPluginAudioProcessorEditor::Skin AudioPluginAudioProcessorEditor::readSkinFromConfig() const +{ + const auto* config = processorRef.getController().getConfig(); + + Skin skin; + skin.displayName = config->getValue("skinDisplayName", "").toStdString(); + skin.jsonFilename = config->getValue("skinFile", "").toStdString(); + skin.folder = config->getValue("skinFolder", "").toStdString(); + return skin; } -AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() { +void AudioPluginAudioProcessorEditor::writeSkinToConfig(const Skin& _skin) const +{ + auto* config = processorRef.getController().getConfig(); + + config->setValue("skinDisplayName", _skin.displayName.c_str()); + config->setValue("skinFile", _skin.jsonFilename.c_str()); + config->setValue("skinFolder", _skin.folder.c_str()); +} + +AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() +{ m_virusEditor.reset(); } @@ -104,3 +222,18 @@ void AudioPluginAudioProcessorEditor::resized() auto area = getLocalBounds(); area.removeFromTop(35); } + +void AudioPluginAudioProcessorEditor::mouseDown(const juce::MouseEvent& event) +{ + if(!event.mods.isPopupMenu()) + { + AudioProcessorEditor::mouseDown(event); + return; + } + + // file browsers have their own menu, do not display two menus at once + if(event.eventComponent && event.eventComponent->findParentComponentOfClass<juce::FileBrowserComponent>()) + return; + + openMenu(); +} diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h @@ -11,24 +11,42 @@ public: explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&); ~AudioPluginAudioProcessorEditor() override; - //============================================================================== - //void handleIncomingMidiMessage(juce::MidiInput *source, const juce::MidiMessage &message) override; void paint (juce::Graphics&) override; void resized() override; + void mouseDown(const juce::MouseEvent& event) override; private: + struct Skin + { + std::string displayName; + std::string jsonFilename; + std::string folder; + + bool operator == (const Skin& _other) const + { + return displayName == _other.displayName && jsonFilename == _other.jsonFilename && folder == _other.folder; + } + }; + void timerCallback() override; - void LoadSkin(int index); + void loadSkin(const Skin& _skin); + void setGuiScale(int percent); + void openMenu(); + void exportCurrentSkin() const; + Skin readSkinFromConfig() const; + void writeSkinToConfig(const Skin& _skin) const; + // This reference is provided as a quick way for your editor to // access the processor object that created it. AudioPluginAudioProcessor& processorRef; + VirusParameterBinding m_parameterBinding; std::unique_ptr<juce::Component> m_virusEditor; - juce::ComboBox m_scale; - juce::ComboBox m_skin; - int m_currentSkinId = -1; + Skin m_currentSkin; + float m_rootScale = 1.0f; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) + std::vector<Skin> m_includedSkins; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessorEditor) }; diff --git a/source/jucePlugin/VirusController.cpp b/source/jucePlugin/VirusController.cpp @@ -302,7 +302,7 @@ namespace Virus ignored */ } - uint8_t Controller::getVirusModel() const + virusLib::VirusModel Controller::getVirusModel() const { return parseAsciiText(m_singles[2][0].data, 128 + 112) == "Taurus JS" ? virusLib::VirusModel::B : virusLib::VirusModel::C; } diff --git a/source/jucePlugin/VirusController.h b/source/jucePlugin/VirusController.h @@ -23,8 +23,28 @@ namespace Virus class Controller : private juce::Timer { public: - friend Parameter; - static constexpr auto kNameLength = 10; + static constexpr size_t kDataSizeInBytes = 256; // same for multi and single + + struct MultiPatch + { + uint8_t bankNumber = 0; + uint8_t progNumber = 0; + std::array<uint8_t, kDataSizeInBytes> data{}; + }; + + struct SinglePatch + { + virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); + uint8_t progNumber = 0; + std::array<uint8_t, kDataSizeInBytes> data{}; + }; + + using Singles = std::array<std::array<SinglePatch, 128>, 8>; + using Multis = std::array<MultiPatch, 128>; + + friend Parameter; + + static constexpr auto kNameLength = 10; Controller(AudioPluginAudioProcessor &, unsigned char deviceId = 0x00); ~Controller() override; @@ -44,9 +64,15 @@ namespace Virus juce::Value* getParamValue(ParameterType _param); Parameter* getParameter(ParameterType _param) const; Parameter *getParameter(ParameterType _param, uint8_t _part) const; - uint8_t getVirusModel() const; + virusLib::VirusModel getVirusModel() const; // bank - 0-1 (AB) juce::StringArray getSinglePresetNames(virusLib::BankNumber bank) const; + + const Singles& getSinglePresets() const + { + return m_singles; + } + juce::StringArray getMultiPresetsName() const; void setSinglePresetName(uint8_t part, juce::String _name); bool isMultiMode() { return getParamValue(0, 2, 0x7a)->getValue(); } @@ -66,26 +92,13 @@ namespace Virus std::function<void()> onMsgDone = {}; std::vector<uint8_t> constructMessage(SysEx msg) const; + uint8_t getDeviceId() const { return m_deviceId; } + private: void timerCallback() override; - static constexpr size_t kDataSizeInBytes = 256; // same for multi and single - - struct MultiPatch - { - uint8_t bankNumber = 0; - uint8_t progNumber = 0; - std::array<uint8_t, kDataSizeInBytes> data{}; - }; - - struct SinglePatch - { - virusLib::BankNumber bankNumber = static_cast<virusLib::BankNumber>(0); - uint8_t progNumber = 0; - std::array<uint8_t, kDataSizeInBytes> data{}; - }; - MultiPatch m_multis[128]; // RAM has 128 Multi 'snapshots' - std::array<std::array<SinglePatch, 128>, 8> m_singles; + Multis m_multis; // RAM has 128 Multi 'snapshots' + Singles m_singles; MultiPatch m_currentMulti; struct ParamIndex diff --git a/source/jucePlugin/VirusParameterBinding.cpp b/source/jucePlugin/VirusParameterBinding.cpp @@ -8,25 +8,65 @@ VirusParameterBinding::~VirusParameterBinding() { clearBindings(); } -void VirusParameterBinding::clearBindings() { + +void VirusParameterBinding::clearBindings() +{ for (const auto b : m_bindings) - { - b->onValueChanged = nullptr; - } + b.parameter->onValueChanged = nullptr; m_bindings.clear(); } + void VirusParameterBinding::setPart(uint8_t _part) { - clearBindings(); m_processor.getController().setCurrentPart(_part); + + std::vector<BoundParameter> bindings; + bindings.swap(m_bindings); + + for(size_t i=0; i<bindings.size(); ++i) + { + auto& b = bindings[i]; + + if(b.part != CurrentPart) + { + m_bindings.push_back(b); + continue; + } + + const auto& desc = b.parameter->getDescription(); + const bool isNonPartExclusive = (desc.classFlags & Virus::Parameter::Class::GLOBAL) || (desc.classFlags & Virus::Parameter::Class::NON_PART_SENSITIVE); + + if(isNonPartExclusive) + continue; + + auto* slider = dynamic_cast<juce::Slider*>(b.component); + if(slider) + { + bind(*slider, b.type); + continue; + } + auto* button = dynamic_cast<juce::DrawableButton*>(b.component); + if(button) + { + bind(*button, b.type); + continue; + } + auto* comboBox = dynamic_cast<juce::ComboBox*>(b.component); + if(comboBox) + { + bind(*comboBox, b.type); + continue; + } + assert(false && "unknown component type"); + } } void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param) { - bind(_slider, _param, m_processor.getController().getCurrentPart()); + bind(_slider, _param, CurrentPart); } -void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param, uint8_t part) +void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _param, const uint8_t _part) { - const auto v = m_processor.getController().getParameter(_param, part); + const auto v = m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part); if (!v) { assert(false && "Failed to find parameter"); @@ -42,39 +82,60 @@ void VirusParameterBinding::bind(juce::Slider &_slider, Virus::ParameterType _pa if (v->isBipolar()) { _slider.getProperties().set("bipolar", true); } + const BoundParameter p{v, &_slider, _param, _part}; + m_bindings.emplace_back(p); } -void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param) { - bind(_combo, _param, m_processor.getController().getCurrentPart()); + +void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param) +{ + bind(_combo, _param, CurrentPart); } -void VirusParameterBinding::bind(juce::ComboBox& _combo, Virus::ParameterType _param, uint8_t _part) + +void VirusParameterBinding::bind(juce::ComboBox& _combo, const Virus::ParameterType _param, uint8_t _part) { - const auto v = m_processor.getController().getParameter(_param, _part); + const auto v = m_processor.getController().getParameter(_param, _part == CurrentPart ? m_processor.getController().getCurrentPart() : _part); if (!v) { assert(false && "Failed to find parameter"); return; } - _combo.setTextWhenNothingSelected("--"); + _combo.setTextWhenNothingSelected("-"); _combo.setScrollWheelEnabled(true); + + _combo.onChange = nullptr; + _combo.clear(); + int idx = 1; - for (auto vs : v->getAllValueStrings()) { + uint32_t count = 0; + for (const auto& vs : v->getAllValueStrings()) { if(vs.isNotEmpty()) + { _combo.addItem(vs, idx); + if(++count == 16) + { + _combo.getRootMenu()->addColumnBreak(); + count = 0; + } + } idx++; } //_combo.addItemList(v->getAllValueStrings(), 1); - _combo.setSelectedId((int)v->getValueObject().getValueSource().getValue() + 1, juce::dontSendNotification); + _combo.setSelectedId(static_cast<int>(v->getValueObject().getValueSource().getValue()) + 1, juce::dontSendNotification); _combo.onChange = [this, &_combo, v]() { - v->beginChangeGesture(); - v->setValueNotifyingHost(v->convertTo0to1(static_cast<float>(_combo.getSelectedId() - 1))); - v->endChangeGesture(); + if(v->getDescription().isPublic) + { + v->beginChangeGesture(); + v->setValueNotifyingHost(v->convertTo0to1(static_cast<float>(_combo.getSelectedId() - 1))); + v->endChangeGesture(); + } v->getValueObject().getValueSource().setValue((int)_combo.getSelectedId() - 1); }; - v->onValueChanged = [this, &_combo, v]() { _combo.setSelectedId((int)v->getValueObject().getValueSource().getValue() + 1, juce::dontSendNotification); }; - m_bindings.add(v); + v->onValueChanged = [this, &_combo, v]() { _combo.setSelectedId(static_cast<int>(v->getValueObject().getValueSource().getValue()) + 1, juce::dontSendNotification); }; + const BoundParameter p{v, &_combo, _param, _part}; + m_bindings.emplace_back(p); } -void VirusParameterBinding::bind(juce::DrawableButton &_btn, Virus::ParameterType _param) +void VirusParameterBinding::bind(juce::Button &_btn, const Virus::ParameterType _param) { const auto v = m_processor.getController().getParameter(_param, m_processor.getController().getCurrentPart()); if (!v) @@ -83,4 +144,6 @@ void VirusParameterBinding::bind(juce::DrawableButton &_btn, Virus::ParameterTyp return; } _btn.getToggleStateValue().referTo(v->getValueObject()); + const BoundParameter p{v, &_btn, _param, CurrentPart}; + m_bindings.emplace_back(p); } diff --git a/source/jucePlugin/VirusParameterBinding.h b/source/jucePlugin/VirusParameterBinding.h @@ -1,7 +1,7 @@ #pragma once -#include "VirusController.h" + #include "VirusParameter.h" -#include "ui/Virus_Buttons.h" + namespace juce { class Value; } @@ -34,8 +34,19 @@ public: void bind(juce::Slider& _control, Virus::ParameterType _param, uint8_t _part); void bind(juce::ComboBox &_control, Virus::ParameterType _param); void bind(juce::ComboBox &_control, Virus::ParameterType _param, uint8_t _part); - void bind(juce::DrawableButton &_control, Virus::ParameterType _param); + void bind(juce::Button &_control, Virus::ParameterType _param); AudioPluginAudioProcessor& m_processor; - juce::Array<Virus::Parameter*> m_bindings; + + static constexpr uint8_t CurrentPart = 0xff; + + struct BoundParameter + { + Virus::Parameter* parameter = nullptr; + juce::Component* component = nullptr; + Virus::ParameterType type = Virus::Param_Invalid; + uint8_t part = CurrentPart; + }; + + std::vector<BoundParameter> m_bindings; }; diff --git a/source/jucePlugin/skins/Hoverland/ARP_2034x1238_x2.png b/source/jucePlugin/skins/Hoverland/ARP_2034x1238_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/ARP_282x52_x2.png b/source/jucePlugin/skins/Hoverland/ARP_282x52_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/FX_2034x1238_x2_D.png b/source/jucePlugin/skins/Hoverland/FX_2034x1238_x2_D.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/FX_282x52_x2.png b/source/jucePlugin/skins/Hoverland/FX_282x52_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/FX_reverbOverlay.png b/source/jucePlugin/skins/Hoverland/FX_reverbOverlay.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/GenPol_140x140_x2.png b/source/jucePlugin/skins/Hoverland/GenPol_140x140_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/GenRed_140x140_x2.png b/source/jucePlugin/skins/Hoverland/GenRed_140x140_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/Gen_140x140_x2.png b/source/jucePlugin/skins/Hoverland/Gen_140x140_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/LFO_2034x1238_x2.png b/source/jucePlugin/skins/Hoverland/LFO_2034x1238_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/LFO_282x52_x2.png b/source/jucePlugin/skins/Hoverland/LFO_282x52_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/Mult_36x36_x2i.png b/source/jucePlugin/skins/Hoverland/Mult_36x36_x2i.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/OSC_2034x1238_x2.png b/source/jucePlugin/skins/Hoverland/OSC_2034x1238_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/OSC_282x52_x2.png b/source/jucePlugin/skins/Hoverland/OSC_282x52_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/Pres_2034x1238_x2.png b/source/jucePlugin/skins/Hoverland/Pres_2034x1238_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/VC_x2_N.png b/source/jucePlugin/skins/Hoverland/VC_x2_N.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/VirusC_Hoverland.json b/source/jucePlugin/skins/Hoverland/VirusC_Hoverland.json @@ -0,0 +1,5739 @@ +{ + "name" : "Root", + "root" : { + "x" : "0", + "y" : "0", + "width" : "2754", + "height" : "1600", + "scale" : "0.5" + }, + "children" : [ + { + "name" : "bg", + "image" : { + "x" : "0", + "y" : "0", + "width" : "2754", + "height" : "1600", + "texture" : "VC_x2_N" + } + }, + { + "name" : "singleLabel", + "label" : { + "text" : "SINGLE", + "textHeight" : "18", + "color" : "EBEBEBFF", + "alignH" : "R", + "alignV" : "C", + "bold" : "1", + "x" : "154", + "y" : "281", + "width" : "70", + "height" : "30" + } + }, + { + "name" : "PlayModeSingle", + "button" : { + "isToggle" : "1", + "radioGroupId" : "5", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "234", + "y" : "281", + "width" : "86", + "height" : "30", + "texture" : "presest_btn_select_86x60_x2", + "tileSizeX" : "86", + "tileSizeY" : "30" + } + }, + { + "name" : "PlayModeMulti", + "button" : { + "isToggle" : "1", + "radioGroupId" : "5", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "506", + "y" : "281", + "width" : "86", + "height" : "30", + "texture" : "presest_btn_select_86x60_x2", + "tileSizeX" : "86", + "tileSizeY" : "30" + } + }, + { + "name" : "multiLabel", + "label" : { + "text" : "MULTI", + "textHeight" : "18", + "color" : "EBEBEBFF", + "alignH" : "L", + "alignV" : "C", + "bold" : "1", + "x" : "600", + "y" : "281", + "width" : "70", + "height" : "30" + } + }, + { + "name" : "PatchName", + "label" : { + "text" : "OvertureCK", + "textHeight" : "64", + "color" : "FF7180FF", + "alignH" : "L", + "alignV" : "C", + "bold" : "1", + "x" : "818.6", + "y" : "95.90002", + "width" : "440", + "height" : "75" + } + }, + { + "name" : "FocusedParameterName", + "label" : { + "text" : "Osc 1 Semitone", + "textHeight" : "32", + "color" : "FF7180FF", + "alignH" : "R", + "alignV" : "T", + "bold" : "1", + "x" : "1190.6", + "y" : "92.90002", + "width" : "368", + "height" : "35.759" + } + }, + { + "name" : "FocusedParameterValue", + "label" : { + "text" : "-12", + "textHeight" : "32", + "color" : "FF7180FF", + "alignH" : "R", + "alignV" : "T", + "bold" : "1", + "x" : "1190.6", + "y" : "128.6589", + "width" : "368", + "height" : "42.241" + } + }, + { + "name" : "PresetSave", + "button" : { + "normalImage" : "0", + "overImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1626", + "y" : "93", + "width" : "86", + "height" : "30", + "texture" : "presest_btn_select_86x60_x2", + "tileSizeX" : "86", + "tileSizeY" : "30" + } + }, + { + "name" : "PresetLoad", + "button" : { + "normalImage" : "0", + "overImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1727", + "y" : "93", + "width" : "86", + "height" : "30", + "texture" : "presest_btn_select_86x60_x2", + "tileSizeX" : "86", + "tileSizeY" : "30" + } + }, + { + "name" : "Presets", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1827", + "y" : "93", + "width" : "86", + "height" : "30", + "texture" : "presest_btn_select_86x60_x2", + "tileSizeX" : "86", + "tileSizeY" : "30" + } + }, + { + "name" : "RomSelector", + "combobox" : { + "text" : "Virus_C_OS_6.5.bin", + "textHeight" : "24", + "color" : "FD9595FF", + "alignH" : "L", + "alignV" : "C", + "x" : "2024", + "y" : "119", + "width" : "508", + "height" : "37", + "tooltip" : "Copy a valid ROM file with file extension .bin next to this plugin" + } + }, + { + "name" : "TabOsc", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "786.9999", + "y" : "216", + "width" : "282", + "height" : "52", + "texture" : "OSC_282x52_x2", + "tileSizeX" : "282", + "tileSizeY" : "52" + } + }, + { + "name" : "TabLfo", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1080", + "y" : "216", + "width" : "282", + "height" : "52", + "texture" : "LFO_282x52_x2", + "tileSizeX" : "282", + "tileSizeY" : "52" + } + }, + { + "name" : "TabEffects", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1370", + "y" : "216", + "width" : "282", + "height" : "52", + "texture" : "FX_282x52_x2", + "tileSizeX" : "282", + "tileSizeY" : "52" + } + }, + { + "name" : "TabArp", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1662", + "y" : "216", + "width" : "282", + "height" : "52", + "texture" : "ARP_282x52_x2", + "tileSizeX" : "282", + "tileSizeY" : "52" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "1", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "322", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "322", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "343", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "341", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "341", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "340.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "340.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "2", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "394", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "394", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "415", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "413", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "413", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "412.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "412.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "3", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "466", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "466", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "487", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "485", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "485", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "484.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "484.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "4", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "538", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "538", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "559", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "557", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "557", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "556.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "556.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "5", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "610", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "610", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "631", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "629", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "629", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "628.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "628.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "6", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "682", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "682", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "703", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "701", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "701", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "700.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "700.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "7", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "754", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "754", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "775", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "773", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "773", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "772.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "772.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "8", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "826", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "826", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "847", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "845", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "845", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "844.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "844.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "9", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "898", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "898", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "919", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "917", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "917", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "916.1299", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "916.1299", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "10", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "970", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "970", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "991", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "989", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "989", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "988.1299", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "988.1299", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "11", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "1042", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "1042", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "1063", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "1061", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "1061", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "1060.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "1060.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "12", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "1114", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "1114", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "1135", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "1133", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "1133", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "1132.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "1132.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "13", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "1186", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "1186", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "1207", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "1205", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "1205", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "1204.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "1204.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "14", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "1258", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "1258", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "1279", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "1277", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "1277", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "1276.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "1276.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "15", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "1330", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "1330", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "1351", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "1349", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "1349", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "1348.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "1348.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartNumber", + "label" : { + "text" : "16", + "textHeight" : "32", + "color" : "878787FF", + "alignH" : "C", + "alignV" : "C", + "x" : "68", + "y" : "1402", + "width" : "50.3025", + "height" : "72" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "radioGroupId" : "16", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "1", + "x" : "68", + "y" : "1402", + "width" : "72", + "height" : "72", + "texture" : "multi_btn_select_72x72_x2", + "tileSizeX" : "72", + "tileSizeY" : "72" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "OvertureCK", + "textHeight" : "24", + "color" : "FC7576FF", + "x" : "163", + "y" : "1423", + "width" : "237", + "height" : "26" + } + }, + { + "name" : "PresetPrev", + "textbutton" : { + "text" : "<", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "454", + "y" : "1421", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PresetNext", + "textbutton" : { + "text" : ">", + "textHeight" : "38", + "color" : "FC7576FF", + "x" : "492", + "y" : "1421", + "width" : "36", + "height" : "30" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "552.3", + "y" : "1420.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "614.38", + "y" : "1420.13", + "width" : "36", + "height" : "36", + "texture" : "Mult_36x36_x2i", + "tileSizeX" : "36", + "tileSizeY" : "36" + } + }, + { + "name" : "page_osc", + "image" : { + "x" : "676", + "y" : "268", + "width" : "2034", + "height" : "1238", + "texture" : "OSC_2034x1238_x2" + }, + "children" : [ + { + "name" : "Osc1Shape", + "parameterAttachment" : { + "parameter" : "Osc1 Shape" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "246", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc1PW", + "parameterAttachment" : { + "parameter" : "Osc1 Pulsewidth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "370", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc1Semi", + "parameterAttachment" : { + "parameter" : "Osc1 Semitone" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "497.2", + "y" : "89", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc1Keyfollow", + "parameterAttachment" : { + "parameter" : "Osc1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "629", + "y" : "89", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2Shape", + "parameterAttachment" : { + "parameter" : "Osc2 Shape" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "245", + "y" : "328", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2PW", + "parameterAttachment" : { + "parameter" : "Osc2 Pulsewidth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "328", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2Semi", + "parameterAttachment" : { + "parameter" : "Osc2 Semitone" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "496.2", + "y" : "327", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2Keyfollow", + "parameterAttachment" : { + "parameter" : "Osc2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "628", + "y" : "327", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2FM", + "parameterAttachment" : { + "parameter" : "Osc2 FM Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "245", + "y" : "518", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2Detune", + "parameterAttachment" : { + "parameter" : "Osc2 Detune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "518", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2EnvFM", + "parameterAttachment" : { + "parameter" : "FM Filt Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "496.2", + "y" : "517", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc2Env", + "parameterAttachment" : { + "parameter" : "Osc2 Filt Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "628", + "y" : "517", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc3Semitone", + "parameterAttachment" : { + "parameter" : "Osc3 Semitone" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "247", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc3Detune", + "parameterAttachment" : { + "parameter" : "Osc3 Detune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc3Level", + "parameterAttachment" : { + "parameter" : "Osc3 Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "496", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "UnisonDetune", + "parameterAttachment" : { + "parameter" : "Unison Detune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "245", + "y" : "998.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "UnisonPanSpread", + "parameterAttachment" : { + "parameter" : "Unison Pan Spread" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "998.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "UnisonLfoPhase", + "parameterAttachment" : { + "parameter" : "Unison Lfo Phase" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "496.2", + "y" : "997.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "UnisonPhaseInit", + "parameterAttachment" : { + "parameter" : "Osc Init Phase" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "628", + "y" : "997.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Osc1WaveSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "138", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Osc1 Wave Select" + } + }, + { + "name" : "Osc2WaveSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "376", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Osc2 Wave Select" + } + }, + { + "name" : "Osc2FMMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "572", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Osc FM Mode" + } + }, + { + "name" : "Osc3Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "810", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Osc3 Mode" + } + }, + { + "name" : "UnisonVoices", + "combobox" : { + "text" : "8", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "1043", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Unison Mode" + } + }, + { + "name" : "SyncSwitch", + "parameterAttachment" : { + "parameter" : "Osc2 Sync" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "636", + "y" : "270", + "width" : "108", + "height" : "50", + "texture" : "sync2_108x100_x2", + "tileSizeX" : "108", + "tileSizeY" : "50" + } + }, + { + "name" : "OscBalance", + "parameterAttachment" : { + "parameter" : "Osc Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "836", + "y" : "90.5", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "OscLevel", + "parameterAttachment" : { + "parameter" : "Osc Mainvolume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "975", + "y" : "90.5", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "NoiseLevel", + "parameterAttachment" : { + "parameter" : "Noise Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "835", + "y" : "329.5", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "RingModLevel", + "parameterAttachment" : { + "parameter" : "Ringmodulator Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "975", + "y" : "329.5", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "NoiseColor", + "parameterAttachment" : { + "parameter" : "Noise Color" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "835", + "y" : "519", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SubLevel", + "parameterAttachment" : { + "parameter" : "Suboscillator Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "976", + "y" : "764", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Portamento", + "parameterAttachment" : { + "parameter" : "Portamento Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "828", + "y" : "1000", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SubOscTypeSwitch", + "parameterAttachment" : { + "parameter" : "Suboscillator Shape" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "865", + "y" : "796", + "width" : "36", + "height" : "94", + "texture" : "handle_72x94_x2", + "tileSizeX" : "36", + "tileSizeY" : "94" + } + }, + { + "name" : "Cutoff", + "parameterAttachment" : { + "parameter" : "Cutoff" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1197", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Reso", + "parameterAttachment" : { + "parameter" : "Filter1 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1318", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "EnvAmt", + "parameterAttachment" : { + "parameter" : "Filter1 Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1448", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Keytrack", + "parameterAttachment" : { + "parameter" : "Filter1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1577", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ResVel", + "parameterAttachment" : { + "parameter" : "Resonance1 Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1707", + "y" : "89", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "EnvVel", + "parameterAttachment" : { + "parameter" : "Flt1 EnvAmt Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1837", + "y" : "89", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1210", + "y" : "310", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Filter1 Mode" + } + }, + { + "name" : "EnvPolarity", + "parameterAttachment" : { + "parameter" : "Filter1 Env Polarity" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "1776", + "y" : "415", + "width" : "50", + "height" : "68", + "texture" : "env_pol_100x68_x2", + "tileSizeX" : "50", + "tileSizeY" : "68" + } + }, + { + "name" : "Cutoff", + "parameterAttachment" : { + "parameter" : "Cutoff2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1197", + "y" : "519.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Reso", + "parameterAttachment" : { + "parameter" : "Filter2 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1318", + "y" : "519.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "EnvAmt", + "parameterAttachment" : { + "parameter" : "Filter2 Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1448", + "y" : "519.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Keytrack", + "parameterAttachment" : { + "parameter" : "Filter2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1577", + "y" : "519.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ResVel", + "parameterAttachment" : { + "parameter" : "Resonance2 Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1707", + "y" : "518.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "EnvVel", + "parameterAttachment" : { + "parameter" : "Flt2 EnvAmt Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1837", + "y" : "518.7", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1210", + "y" : "409.8", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Filter2 Mode" + } + }, + { + "name" : "EnvPolarity", + "parameterAttachment" : { + "parameter" : "Filter2 Env Polarity" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "1872", + "y" : "414.7", + "width" : "50", + "height" : "68", + "texture" : "env_pol_100x68_x2", + "tileSizeX" : "50", + "tileSizeY" : "68" + } + }, + { + "name" : "FilterBalance", + "parameterAttachment" : { + "parameter" : "Filter Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1405", + "y" : "327", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "FilterRouting", + "combobox" : { + "text" : "Parallel 4", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1578", + "y" : "309", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Filter Routing" + } + }, + { + "name" : "FilterSaturationCurve", + "combobox" : { + "text" : "Parallel 4", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1578", + "y" : "407", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Saturation Curve" + } + }, + { + "name" : "FilterKeyfollowBase", + "combobox" : { + "text" : "Parallel 4", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1782.4", + "y" : "309", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Filter Keytrack Base" + } + }, + { + "name" : "LinkCutoff", + "parameterAttachment" : { + "parameter" : "Filter2 Cutoff Link" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "1154", + "y" : "340", + "width" : "24", + "height" : "72", + "texture" : "link_horizon_48x72_x2", + "tileSizeX" : "24", + "tileSizeY" : "72" + } + }, + { + "name" : "LinkEnv", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "1992", + "y" : "339", + "width" : "24", + "height" : "72", + "texture" : "link_horizon_48x72_x2", + "tileSizeX" : "24", + "tileSizeY" : "72" + } + }, + { + "name" : "Attack", + "parameterAttachment" : { + "parameter" : "Filter Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1197", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Decay", + "parameterAttachment" : { + "parameter" : "Filter Env Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1357", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SustainLevel", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1515", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SustainTime", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1672", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Release", + "parameterAttachment" : { + "parameter" : "Filter Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1836", + "y" : "761", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Attack", + "parameterAttachment" : { + "parameter" : "Amp Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1197", + "y" : "998", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Decay", + "parameterAttachment" : { + "parameter" : "Amp Env Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1357", + "y" : "998", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SustainLevel", + "parameterAttachment" : { + "parameter" : "Amp Env Sustain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1515", + "y" : "998", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SustainTime", + "parameterAttachment" : { + "parameter" : "Amp Env Sustain Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1672", + "y" : "998", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Release", + "parameterAttachment" : { + "parameter" : "Amp Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1836", + "y" : "998", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + } + ] + }, + { + "name" : "page_lfo", + "image" : { + "x" : "676", + "y" : "268", + "width" : "2034", + "height" : "1238", + "texture" : "LFO_2034x1238_x2" + }, + "children" : [ + { + "name" : "LfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "71", + "y" : "128", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo1 Shape" + } + }, + { + "name" : "LfoClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "71", + "y" : "213", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo1 Clock" + } + }, + { + "name" : "LfoMode", + "parameterAttachment" : { + "parameter" : "Lfo1 Mode" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "60", + "y" : "298", + "width" : "36", + "height" : "94", + "texture" : "handle_72x94_x2", + "tileSizeX" : "36", + "tileSizeY" : "94" + } + }, + { + "name" : "LfoEnvMode", + "parameterAttachment" : { + "parameter" : "Lfo1 Env Mode" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "178", + "y" : "299.4", + "width" : "46", + "height" : "38", + "texture" : "lfo_btn_46x76_x2", + "tileSizeX" : "46", + "tileSizeY" : "38" + } + }, + { + "name" : "LfoRate", + "parameterAttachment" : { + "parameter" : "Lfo1 Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "257", + "y" : "84", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoKeytrack", + "parameterAttachment" : { + "parameter" : "Lfo1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "386", + "y" : "84", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoContour", + "parameterAttachment" : { + "parameter" : "Lfo1 Symmetry" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "258", + "y" : "250", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoPhase", + "parameterAttachment" : { + "parameter" : "Lfo1 Keytrigger" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "386", + "y" : "251", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoOsc1Pitch", + "parameterAttachment" : { + "parameter" : "Osc1 Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "535", + "y" : "84", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoOsc2Pitch", + "parameterAttachment" : { + "parameter" : "Osc2 Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "663", + "y" : "84", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoFilterGain", + "parameterAttachment" : { + "parameter" : "FiltGain Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "534", + "y" : "251", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoAmount", + "parameterAttachment" : { + "parameter" : "LFO1 Assign Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "659", + "y" : "250", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoPW12", + "parameterAttachment" : { + "parameter" : "PW Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "793", + "y" : "84", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoReso12", + "parameterAttachment" : { + "parameter" : "Reso Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "925", + "y" : "84", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoAssignDestination", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "835", + "y" : "298", + "width" : "193", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "LFO1 Assign Dest" + } + }, + { + "name" : "Lfo1Link", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "630", + "y" : "70", + "width" : "72", + "height" : "24", + "texture" : "link_vert_72x48_x2", + "tileSizeX" : "72", + "tileSizeY" : "24" + } + }, + { + "name" : "LfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "71", + "y" : "508", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo2 Shape" + } + }, + { + "name" : "LfoClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "71", + "y" : "593", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo2 Clock" + } + }, + { + "name" : "LfoMode", + "parameterAttachment" : { + "parameter" : "Lfo2 Mode" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "60", + "y" : "678", + "width" : "36", + "height" : "94", + "texture" : "handle_72x94_x2", + "tileSizeX" : "36", + "tileSizeY" : "94" + } + }, + { + "name" : "LfoEnvMode", + "parameterAttachment" : { + "parameter" : "Lfo2 Env Mode" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "178", + "y" : "679.3999", + "width" : "46", + "height" : "38", + "texture" : "lfo_btn_46x76_x2", + "tileSizeX" : "46", + "tileSizeY" : "38" + } + }, + { + "name" : "LfoRate", + "parameterAttachment" : { + "parameter" : "Lfo2 Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "257", + "y" : "464", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoKeytrack", + "parameterAttachment" : { + "parameter" : "Lfo2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "386", + "y" : "464", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoContour", + "parameterAttachment" : { + "parameter" : "Lfo2 Symmetry" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "258", + "y" : "630", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoPhase", + "parameterAttachment" : { + "parameter" : "Lfo2 Keytrigger" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "386", + "y" : "631", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoOsc1Pitch", + "parameterAttachment" : { + "parameter" : "Cutoff1 Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "535", + "y" : "464", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoOsc2Pitch", + "parameterAttachment" : { + "parameter" : "Cutoff2 Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "663", + "y" : "464", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoFilterGain", + "parameterAttachment" : { + "parameter" : "Pan Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "534", + "y" : "631", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoAmount", + "parameterAttachment" : { + "parameter" : "LFO2 Assign Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "659", + "y" : "630", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoPW12", + "parameterAttachment" : { + "parameter" : "Shape Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "793", + "y" : "464", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoReso12", + "parameterAttachment" : { + "parameter" : "FM Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "925", + "y" : "464", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "LfoAssignDestination", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "835", + "y" : "678", + "width" : "193", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "LFO2 Assign Dest" + } + }, + { + "name" : "Lfo1Link", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "630", + "y" : "450", + "width" : "72", + "height" : "24", + "texture" : "link_vert_72x48_x2", + "tileSizeX" : "72", + "tileSizeY" : "24" + } + }, + { + "name" : "LfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "71", + "y" : "887", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Shape" + } + }, + { + "name" : "LfoClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "71", + "y" : "972", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Clock" + } + }, + { + "name" : "LfoMode", + "parameterAttachment" : { + "parameter" : "Lfo3 Mode" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "downImageOn" : "0", + "x" : "62", + "y" : "1061", + "width" : "36", + "height" : "94", + "texture" : "handle_72x94_x2", + "tileSizeX" : "36", + "tileSizeY" : "94" + } + }, + { + "name" : "LfoRate", + "parameterAttachment" : { + "parameter" : "Lfo3 Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "257", + "y" : "857", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Lfo3FadeIn", + "parameterAttachment" : { + "parameter" : "Lfo3 Fade-In Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "386", + "y" : "857", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Lfo3Keytrack", + "parameterAttachment" : { + "parameter" : "Lfo3 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "258", + "y" : "1011", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Lfo3Amount", + "parameterAttachment" : { + "parameter" : "Osc Lfo3 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "659", + "y" : "857", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Lfo3AssignDestination", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "835", + "y" : "905", + "width" : "193", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Destination" + } + }, + { + "name" : "ModAmount", + "parameterAttachment" : { + "parameter" : "Assign1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1608", + "y" : "226", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModSource", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "184", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign1 Source" + } + }, + { + "name" : "ModDest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "300", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign1 Destination" + } + }, + { + "name" : "ModAmount", + "parameterAttachment" : { + "parameter" : "Assign2 Amount1" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1135", + "y" : "819", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModSource", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1289", + "y" : "779", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign2 Source" + } + }, + { + "name" : "ModDest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1289", + "y" : "895", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign2 Destination1" + } + }, + { + "name" : "ModAmount2", + "parameterAttachment" : { + "parameter" : "Assign2 Amount2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1135", + "y" : "971.8", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModDest2", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1289", + "y" : "1047.8", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign2 Destination2" + } + }, + { + "name" : "ModAmount", + "parameterAttachment" : { + "parameter" : "Assign3 Amount1" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1137", + "y" : "225", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModSource", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1291", + "y" : "185", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Source" + } + }, + { + "name" : "ModDest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1291", + "y" : "301", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Destination1" + } + }, + { + "name" : "ModAmount2", + "parameterAttachment" : { + "parameter" : "Assign3 Amount2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1137", + "y" : "377.8", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModDest2", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1291", + "y" : "453.8", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Destination2" + } + }, + { + "name" : "ModAmount3", + "parameterAttachment" : { + "parameter" : "Assign3 Amount3" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1137", + "y" : "528", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModDest3", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1291", + "y" : "605", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Destination3" + } + }, + { + "name" : "ModAmount", + "parameterAttachment" : { + "parameter" : "Assign 4 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1608", + "y" : "475", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModSource", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "433", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign 4 Source" + } + }, + { + "name" : "ModDest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "549", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign 4 Destination" + } + }, + { + "name" : "ModAmount", + "parameterAttachment" : { + "parameter" : "Assign 5 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1608", + "y" : "721.7", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModSource", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "681.7", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign 5 Source" + } + }, + { + "name" : "ModDest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "797.7", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign 5 Destination" + } + }, + { + "name" : "ModAmount", + "parameterAttachment" : { + "parameter" : "Assign 6 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1608", + "y" : "970", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ModSource", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "930", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign 6 Source" + } + }, + { + "name" : "ModDest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1758", + "y" : "1046", + "width" : "190", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Assign 6 Destination" + } + } + ] + }, + { + "name" : "page_fx", + "image" : { + "x" : "676", + "y" : "268", + "width" : "2034", + "height" : "1238", + "texture" : "FX_2034x1238_x2_D" + }, + "children" : [ + { + "name" : "DistortionType", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "138", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Distortion Curve" + } + }, + { + "name" : "DistortionAmount", + "parameterAttachment" : { + "parameter" : "Distortion Intensity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "246", + "y" : "91", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "BassIntensity", + "parameterAttachment" : { + "parameter" : "Bass Intensity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "625", + "y" : "91", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "BassTune", + "parameterAttachment" : { + "parameter" : "Bass Tune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "758", + "y" : "91", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PhaserStages", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "372", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Phaser Mode" + } + }, + { + "name" : "PhaserRate", + "parameterAttachment" : { + "parameter" : "Phaser Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "246", + "y" : "324", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PhaserFreq", + "parameterAttachment" : { + "parameter" : "Phaser Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "324", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PhaserDepth", + "parameterAttachment" : { + "parameter" : "Phaser Depth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "495", + "y" : "324", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PhaserFeedback", + "parameterAttachment" : { + "parameter" : "Phaser Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "625", + "y" : "324", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PhaserSpread", + "parameterAttachment" : { + "parameter" : "Phaser Spread" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "753", + "y" : "324", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PhaserMix", + "parameterAttachment" : { + "parameter" : "Phaser Mix" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "881", + "y" : "325", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ChorusLfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "610", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Chorus Lfo Shape" + } + }, + { + "name" : "ChorusRate", + "parameterAttachment" : { + "parameter" : "Chorus Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "246", + "y" : "562", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ChorusDepth", + "parameterAttachment" : { + "parameter" : "Chorus Depth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "562", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ChorusFeedback", + "parameterAttachment" : { + "parameter" : "Chorus Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "497", + "y" : "562", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ChorusDelay", + "parameterAttachment" : { + "parameter" : "Chorus Delay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "623", + "y" : "562", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ChorusMix", + "parameterAttachment" : { + "parameter" : "Chorus Mix" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "755", + "y" : "563", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Gain", + "parameterAttachment" : { + "parameter" : "LowEQ Gain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "119", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Freq", + "parameterAttachment" : { + "parameter" : "LowEQ Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "246", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Gain", + "parameterAttachment" : { + "parameter" : "MidEQ Gain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Freq", + "parameterAttachment" : { + "parameter" : "MidEQ Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "495", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Q", + "parameterAttachment" : { + "parameter" : "MidEQ Q-Factor" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "625", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Gain", + "parameterAttachment" : { + "parameter" : "HighEQ Gain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "753", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Freq", + "parameterAttachment" : { + "parameter" : "HighEQ Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "880", + "y" : "800", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "InputFollowerMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "86", + "y" : "1058", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Input Follower Mode" + } + }, + { + "name" : "InputFollowerGain", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "246", + "y" : "1010", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "InputFollowerAttack", + "parameterAttachment" : { + "parameter" : "Filter Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "1010", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "InputFollowerRelease", + "parameterAttachment" : { + "parameter" : "Filter Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "495", + "y" : "1010", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "PunchIntensity", + "parameterAttachment" : { + "parameter" : "Punch Intensity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "753", + "y" : "1011", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ContainerReverb", + "image" : { + "x" : "1079", + "y" : "54", + "width" : "920", + "height" : "406", + "texture" : "FX_reverbOverlay" + }, + "children" : [ + { + "name" : "ReverbDecay", + "parameterAttachment" : { + "parameter" : "Dly Rate / Rev Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "225", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ReverbDamping", + "parameterAttachment" : { + "parameter" : "Reverb Damping" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "348", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ReverbColor", + "parameterAttachment" : { + "parameter" : "Delay Color" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "473", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ReverbPreDelay", + "parameterAttachment" : { + "parameter" : "Delay Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "607", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ReverbFeedback", + "parameterAttachment" : { + "parameter" : "Delay Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "741.7", + "y" : "35", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + } + ] + }, + { + "name" : "ContainerDelay", + "component" : { + "x" : "1079", + "y" : "54", + "width" : "920", + "height" : "406" + }, + "children" : [ + { + "name" : "DelayTime", + "parameterAttachment" : { + "parameter" : "Delay Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "225", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "DelayFeedback", + "parameterAttachment" : { + "parameter" : "Delay Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "348", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "DelayColor", + "parameterAttachment" : { + "parameter" : "Delay Color" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "473", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "DelayRate", + "parameterAttachment" : { + "parameter" : "Dly Rate / Rev Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "607", + "y" : "36", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "DelayDepth", + "parameterAttachment" : { + "parameter" : "Dly Depth " + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "741.7", + "y" : "35", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "DelayReverbLfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "254", + "y" : "280", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Delay Lfo Shape" + } + } + ] + }, + { + "name" : "DelayReverbMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1109", + "y" : "137", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Delay/Reverb Mode" + } + }, + { + "name" : "DelayReverbClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1109", + "y" : "334", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Delay Clock" + } + }, + { + "name" : "DelayReverbMix", + "parameterAttachment" : { + "parameter" : "Effect Send" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1821", + "y" : "288", + "width" : "140", + "height" : "140", + "texture" : "GenRed_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocoderMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1109", + "y" : "608", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Vocoder Mode" + } + }, + { + "name" : "VocSourceBalance", + "parameterAttachment" : { + "parameter" : "Filter Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1302", + "y" : "561", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocSpectralBalance", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1425", + "y" : "561", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocBands", + "parameterAttachment" : { + "parameter" : "Filter Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1552", + "y" : "561", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocAttack", + "parameterAttachment" : { + "parameter" : "Filter Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1687", + "y" : "561", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocRelease", + "parameterAttachment" : { + "parameter" : "Filter Env Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1822", + "y" : "561", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocCarrierCenterFreq", + "parameterAttachment" : { + "parameter" : "Cutoff" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1303", + "y" : "769", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocCarrierQFactor", + "parameterAttachment" : { + "parameter" : "Filter1 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1425", + "y" : "770", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocCarrierSpread", + "parameterAttachment" : { + "parameter" : "Filter1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1553", + "y" : "770", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocModulatorFreqOffset", + "parameterAttachment" : { + "parameter" : "Cutoff2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1303", + "y" : "953", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocModulatorQFactor", + "parameterAttachment" : { + "parameter" : "Filter2 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1425", + "y" : "954", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocModulatorSpread", + "parameterAttachment" : { + "parameter" : "Filter2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1553", + "y" : "952", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VocModulatorInput", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1109", + "y" : "1004", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Input Select" + } + } + ] + }, + { + "name" : "page_arp", + "image" : { + "x" : "676", + "y" : "268", + "width" : "2034", + "height" : "1238", + "texture" : "ARP_2034x1238_x2" + }, + "children" : [ + { + "name" : "VelOsc1Shape", + "parameterAttachment" : { + "parameter" : "Osc1 Shape Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "107", + "y" : "93", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelFlt1Freq", + "parameterAttachment" : { + "parameter" : "Flt1 EnvAmt Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "233", + "y" : "93", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelFlt1Res", + "parameterAttachment" : { + "parameter" : "Resonance1 Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "356", + "y" : "93", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelPW", + "parameterAttachment" : { + "parameter" : "PulseWidth Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "483", + "y" : "93", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelVol", + "parameterAttachment" : { + "parameter" : "Amp Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "616", + "y" : "93", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelPan", + "parameterAttachment" : { + "parameter" : "Panorama Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "739", + "y" : "93", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelOsc2Shape", + "parameterAttachment" : { + "parameter" : "Osc2 Shape Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "107", + "y" : "271", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelFlt2Freq", + "parameterAttachment" : { + "parameter" : "Flt2 EnvAmt Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "233", + "y" : "271", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelFlt2Res", + "parameterAttachment" : { + "parameter" : "Resonance2 Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "356", + "y" : "271", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "VelFM", + "parameterAttachment" : { + "parameter" : "Fm Amount Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "483", + "y" : "271", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "InputMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "137", + "y" : "609", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Input Mode" + } + }, + { + "name" : "InputSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "340", + "y" : "609", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Input Select" + } + }, + { + "name" : "ArpModeButton", + "parameterAttachment" : { + "parameter" : "Arp Mode" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1002.9", + "y" : "136.7", + "width" : "104", + "height" : "72", + "texture" : "arpall_104x432_X2", + "tileSizeX" : "104", + "tileSizeY" : "72" + }, + "children" : [ + { + "name" : "ArpModeComboBox", + "combobox" : { + "text" : "Hold", + "textHeight" : "30", + "color" : "FFFFFF00", + "alignH" : "L", + "alignV" : "C", + "x" : "0", + "y" : "0", + "width" : "104", + "height" : "72" + }, + "parameterAttachment" : { + "parameter" : "Arp Mode" + } + } + ] + }, + { + "name" : "ArpPattern", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "24", + "color" : "FD9595FF", + "alignH" : "L", + "alignV" : "C", + "x" : "1164", + "y" : "121", + "width" : "170", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Arp Pattern Selct" + } + }, + { + "name" : "ArpOctaveRange", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "24", + "color" : "FD9595FF", + "alignH" : "L", + "alignV" : "C", + "x" : "1164", + "y" : "187", + "width" : "170", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Arp Octave Range" + } + }, + { + "name" : "ArpResolution", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "24", + "color" : "FD9595FF", + "alignH" : "L", + "alignV" : "C", + "x" : "1377", + "y" : "121", + "width" : "170", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Arp Clock" + } + }, + { + "name" : "ArpHold", + "parameterAttachment" : { + "parameter" : "Arp Hold Enable" + }, + "button" : { + "isToggle" : "1", + "normalImage" : "1", + "downImage" : "0", + "normalImageOn" : "0", + "downImageOn" : "1", + "x" : "1362", + "y" : "187", + "width" : "72", + "height" : "37", + "texture" : "arp_72x37_X2", + "tileSizeX" : "72", + "tileSizeY" : "37" + } + }, + { + "name" : "ArpGlobalTempo", + "parameterAttachment" : { + "parameter" : "Clock Tempo" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1609", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ArpNoteLength", + "parameterAttachment" : { + "parameter" : "Arp Note Length" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1732", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "ArpNoteSwing", + "parameterAttachment" : { + "parameter" : "Arp Swing" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1857.5", + "y" : "90", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SoftKnobMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "969", + "y" : "381", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob-1 Mode" + } + }, + { + "name" : "SoftKnobName", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1180.55", + "y" : "381", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob1 ShortName" + } + }, + { + "name" : "SoftKnobMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1609", + "y" : "381", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob-2 Mode" + } + }, + { + "name" : "SoftKnobName", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1820.55", + "y" : "381", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob2 ShortName" + } + }, + { + "name" : "KeyMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "969", + "y" : "617.3999", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Key Mode" + } + }, + { + "name" : "PatchVolume", + "parameterAttachment" : { + "parameter" : "Patch Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1128", + "y" : "569.4", + "width" : "140", + "height" : "140", + "texture" : "Gen_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Panning", + "parameterAttachment" : { + "parameter" : "Panorama" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1252", + "y" : "569.4", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "SecondaryOutput", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "969", + "y" : "776.5", + "width" : "162", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Part Output Select" + } + }, + { + "name" : "OutputBalance", + "parameterAttachment" : { + "parameter" : "Second Output Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1129", + "y" : "728.5", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "Transpose", + "parameterAttachment" : { + "parameter" : "Transpose" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1252", + "y" : "728.5", + "width" : "140", + "height" : "140", + "texture" : "GenPol_140x140_x2", + "tileSizeX" : "140", + "tileSizeY" : "140" + } + }, + { + "name" : "BendUp", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1608", + "y" : "617.3999", + "width" : "160", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Bender Range Up" + } + }, + { + "name" : "BendDown", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1822", + "y" : "617.3999", + "width" : "160", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Bender Range Down" + } + }, + { + "name" : "BendScale", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1608", + "y" : "716.3999", + "width" : "160", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Bender Scale" + } + }, + { + "name" : "SmoothMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1822", + "y" : "716.3999", + "width" : "160", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Control Smooth Mode" + } + }, + { + "name" : "Category1", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1608", + "y" : "815.3999", + "width" : "160", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Category1" + } + }, + { + "name" : "Category2", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "30", + "alignH" : "L", + "alignV" : "C", + "x" : "1822", + "y" : "815.3999", + "width" : "160", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Category2" + } + }, + { + "name" : "MidiIn", + "combobox" : { + "text" : "In Device X", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "x" : "992", + "y" : "1116", + "width" : "124.2466", + "height" : "36" + } + }, + { + "name" : "MidiOut", + "combobox" : { + "text" : "Out Device X", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "x" : "1116", + "y" : "1116", + "width" : "124.2466", + "height" : "36" + } + } + ] + }, + { + "name" : "page_presets", + "image" : { + "x" : "676", + "y" : "268", + "width" : "2034", + "height" : "1238", + "texture" : "Pres_2034x1238_x2" + }, + "children" : [ + { + "name" : "ContainerFileSelector", + "component" : { + "x" : "88", + "y" : "96", + "width" : "928", + "height" : "1055" + } + }, + { + "name" : "ContainerPatchList", + "component" : { + "x" : "1016", + "y" : "96", + "width" : "929", + "height" : "1005" + } + }, + { + "name" : "ContainerPatchListSearchBox", + "component" : { + "x" : "1016", + "y" : "1101", + "width" : "929", + "height" : "50" + } + } + ] + }, + { + "name" : "DonateInfo", + "hyperlinkbutton" : { + "url" : "https://paypal.me/dsp56300", + "text" : "To donate: https://paypal.me/dsp56300", + "textHeight" : "28", + "color" : "FF7180FF", + "x" : "74", + "y" : "1543", + "width" : "604.98", + "height" : "45" + } + }, + { + "name" : "VersionInfo", + "label" : { + "text" : "DSP56300 Emulator Version 0.8.15 - March 12 2042 12:34:56", + "textHeight" : "28", + "color" : "FF7180FF", + "alignH" : "R", + "alignV" : "C", + "x" : "1416", + "y" : "1543", + "width" : "1280", + "height" : "45" + } + } + ] +} +\ No newline at end of file diff --git a/source/jucePlugin/skins/Hoverland/arp_72x37_X2.png b/source/jucePlugin/skins/Hoverland/arp_72x37_X2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/arpall_104x432_X2.png b/source/jucePlugin/skins/Hoverland/arpall_104x432_X2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/assets.cmake b/source/jucePlugin/skins/Hoverland/assets.cmake @@ -0,0 +1,29 @@ +set(ASSETS_VirusC_Hoverland + ${CMAKE_CURRENT_LIST_DIR}/ARP_2034x1238_x2.png + ${CMAKE_CURRENT_LIST_DIR}/ARP_282x52_x2.png + ${CMAKE_CURRENT_LIST_DIR}/arp_72x37_X2.png + ${CMAKE_CURRENT_LIST_DIR}/arpall_104x432_X2.png + ${CMAKE_CURRENT_LIST_DIR}/env_pol_100x68_x2.png + ${CMAKE_CURRENT_LIST_DIR}/FX_2034x1238_x2_D.png + ${CMAKE_CURRENT_LIST_DIR}/FX_282x52_x2.png + ${CMAKE_CURRENT_LIST_DIR}/FX_reverbOverlay.png + ${CMAKE_CURRENT_LIST_DIR}/Gen_140x140_x2.png + ${CMAKE_CURRENT_LIST_DIR}/GenPol_140x140_x2.png + ${CMAKE_CURRENT_LIST_DIR}/GenRed_140x140_x2.png + ${CMAKE_CURRENT_LIST_DIR}/handle_72x94_x2.png + ${CMAKE_CURRENT_LIST_DIR}/LFO_2034x1238_x2.png + ${CMAKE_CURRENT_LIST_DIR}/LFO_282x52_x2.png + ${CMAKE_CURRENT_LIST_DIR}/lfo_btn_46x76_x2.png + ${CMAKE_CURRENT_LIST_DIR}/link_horizon_48x72_x2.png + ${CMAKE_CURRENT_LIST_DIR}/link_vert_72x48_x2.png + ${CMAKE_CURRENT_LIST_DIR}/Mult_36x36_x2i.png + ${CMAKE_CURRENT_LIST_DIR}/multi_btn_select_72x72_x2.png + ${CMAKE_CURRENT_LIST_DIR}/OSC_2034x1238_x2.png + ${CMAKE_CURRENT_LIST_DIR}/OSC_282x52_x2.png + ${CMAKE_CURRENT_LIST_DIR}/Pres_2034x1238_x2.png + ${CMAKE_CURRENT_LIST_DIR}/presest_btn_select_86x60_x2.png + ${CMAKE_CURRENT_LIST_DIR}/sync2_108x100_x2.png + ${CMAKE_CURRENT_LIST_DIR}/VC_x2_N.png + + ${CMAKE_CURRENT_LIST_DIR}/VirusC_Hoverland.json +) diff --git a/source/jucePlugin/skins/Hoverland/env_pol_100x68_x2.png b/source/jucePlugin/skins/Hoverland/env_pol_100x68_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/handle_72x94_x2.png b/source/jucePlugin/skins/Hoverland/handle_72x94_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/lfo_btn_46x76_x2.png b/source/jucePlugin/skins/Hoverland/lfo_btn_46x76_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/link_horizon_48x72_x2.png b/source/jucePlugin/skins/Hoverland/link_horizon_48x72_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/link_vert_72x48_x2.png b/source/jucePlugin/skins/Hoverland/link_vert_72x48_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/multi_btn_select_72x72_x2.png b/source/jucePlugin/skins/Hoverland/multi_btn_select_72x72_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/presest_btn_select_86x60_x2.png b/source/jucePlugin/skins/Hoverland/presest_btn_select_86x60_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Hoverland/sync2_108x100_x2.png b/source/jucePlugin/skins/Hoverland/sync2_108x100_x2.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/Digital.ttf b/source/jucePlugin/skins/Trancy/Digital.ttf Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/VirusC_Trancy.json b/source/jucePlugin/skins/Trancy/VirusC_Trancy.json @@ -0,0 +1,5516 @@ +{ + "name" : "Root", + "root" : { + "x" : "0", + "y" : "0", + "width" : "2501", + "height" : "1152", + "scale" : "0.5" + }, + "children" : [ + { + "name" : "bg", + "image" : { + "x" : "0", + "y" : "0", + "width" : "2501", + "height" : "1152", + "texture" : "main_background" + } + }, + { + "name" : "PatchName", + "label" : { + "text" : "Osc 1 Shape", + "textHeight" : "36", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1477.7", + "y" : "37.2", + "width" : "391.35", + "height" : "51.289" + } + }, + { + "name" : "FocusedParameterValue", + "label" : { + "text" : "-42", + "textHeight" : "36", + "backgroundColor" : "230303FF", + "alignH" : "R", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1869.05", + "y" : "44.2", + "width" : "256.7", + "height" : "38" + } + }, + { + "name" : "FocusedParameterName", + "label" : { + "text" : "Osc", + "textHeight" : "36", + "backgroundColor" : "230303FF", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1477.7", + "y" : "44.2", + "width" : "391.35", + "height" : "38" + } + }, + { + "name" : "Menu", + "button" : { + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "disabledImage" : "0", + "normalImageOn" : "0", + "overImageOn" : "0", + "downImageOn" : "0", + "disabledImageOn" : "0", + "x" : "2216", + "y" : "37.344", + "width" : "172", + "height" : "50", + "texture" : "btn_menu", + "tileSizeX" : "172", + "tileSizeY" : "50" + } + }, + { + "name" : "PresetPrev", + "button" : { + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "disabledImage" : "0", + "normalImageOn" : "0", + "overImageOn" : "0", + "downImageOn" : "0", + "disabledImageOn" : "0", + "x" : "2148.73", + "y" : "36.844", + "width" : "33", + "height" : "51", + "texture" : "btn_left", + "tileSizeX" : "33", + "tileSizeY" : "51" + } + }, + { + "name" : "PresetNext", + "button" : { + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "disabledImage" : "0", + "normalImageOn" : "0", + "overImageOn" : "0", + "downImageOn" : "0", + "disabledImageOn" : "0", + "x" : "2183", + "y" : "36.844", + "width" : "33", + "height" : "51", + "texture" : "btn_right", + "tileSizeX" : "33", + "tileSizeY" : "51" + } + }, + { + "name" : "TabOsc", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "115", + "y" : "36", + "width" : "268", + "height" : "53", + "texture" : "btn_main_1", + "tileSizeX" : "268", + "tileSizeY" : "53" + } + }, + { + "name" : "TabLfo", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "383", + "y" : "36", + "width" : "268", + "height" : "53", + "texture" : "btn_main_2", + "tileSizeX" : "268", + "tileSizeY" : "53" + } + }, + { + "name" : "TabEffects", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "651", + "y" : "36", + "width" : "268", + "height" : "53", + "texture" : "btn_main_3", + "tileSizeX" : "268", + "tileSizeY" : "53" + } + }, + { + "name" : "TabArp", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "919", + "y" : "36", + "width" : "268", + "height" : "53", + "texture" : "btn_main_4", + "tileSizeX" : "268", + "tileSizeY" : "53" + } + }, + { + "name" : "Presets", + "button" : { + "isToggle" : "1", + "radioGroupId" : "42", + "normalImage" : "0", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1187", + "y" : "36", + "width" : "268", + "height" : "53", + "texture" : "btn_main_5", + "tileSizeX" : "268", + "tileSizeY" : "53" + } + }, + { + "name" : "page_osc", + "image" : { + "x" : "100", + "y" : "122", + "width" : "2300", + "height" : "995", + "texture" : "panel_1" + }, + "children" : [ + { + "name" : "WaveSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "18", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "57", + "y" : "83", + "width" : "122", + "height" : "39" + }, + "parameterAttachment" : { + "parameter" : "Osc1 Wave Select" + } + }, + { + "name" : "Shape", + "parameterAttachment" : { + "parameter" : "Osc1 Shape" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "250", + "y" : "66", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PulseWidth", + "parameterAttachment" : { + "parameter" : "Osc1 Pulsewidth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "395", + "y" : "66", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Semitone", + "parameterAttachment" : { + "parameter" : "Osc1 Semitone" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "540", + "y" : "66", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Keyfollow", + "parameterAttachment" : { + "parameter" : "Osc1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "685", + "y" : "66", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "WaveSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "18", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "57", + "y" : "291", + "width" : "122", + "height" : "39" + }, + "parameterAttachment" : { + "parameter" : "Osc2 Wave Select" + } + }, + { + "name" : "Shape", + "parameterAttachment" : { + "parameter" : "Osc2 Shape" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "250", + "y" : "274", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PulseWidth", + "parameterAttachment" : { + "parameter" : "Osc2 Pulsewidth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "395", + "y" : "274", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Semitone", + "parameterAttachment" : { + "parameter" : "Osc2 Semitone" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "540", + "y" : "274", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Keyfollow", + "parameterAttachment" : { + "parameter" : "Osc2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "685", + "y" : "274", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Osc2Sync", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "836", + "y" : "274", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Osc2 Sync" + } + }, + { + "name" : "WaveSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "59", + "y" : "466", + "width" : "120", + "height" : "39" + }, + "parameterAttachment" : { + "parameter" : "Osc FM Mode" + } + }, + { + "name" : "Shape", + "parameterAttachment" : { + "parameter" : "Osc2 FM Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "250", + "y" : "449", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PulseWidth", + "parameterAttachment" : { + "parameter" : "Osc2 Detune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "395", + "y" : "449", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Semitone", + "parameterAttachment" : { + "parameter" : "FM Filt Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "540", + "y" : "449", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Keyfollow", + "parameterAttachment" : { + "parameter" : "Osc2 Filt Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "685", + "y" : "449", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaseInit", + "parameterAttachment" : { + "parameter" : "Osc Init Phase" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "830", + "y" : "449", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "WaveSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "18", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "57", + "y" : "675", + "width" : "122", + "height" : "39" + }, + "parameterAttachment" : { + "parameter" : "Osc1 Wave Select" + } + }, + { + "name" : "Semitone", + "parameterAttachment" : { + "parameter" : "Osc3 Semitone" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "250", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Detune", + "parameterAttachment" : { + "parameter" : "Osc3 Detune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "395", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "UnisonMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "58.99213", + "y" : "884", + "width" : "120", + "height" : "39" + }, + "parameterAttachment" : { + "parameter" : "Unison Mode" + } + }, + { + "name" : "UnisonDetune", + "parameterAttachment" : { + "parameter" : "Unison Detune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "249.9921", + "y" : "867", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "UnisonPanSpread", + "parameterAttachment" : { + "parameter" : "Unison Pan Spread" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "394.9921", + "y" : "867", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "UnisonPhase", + "parameterAttachment" : { + "parameter" : "Unison Lfo Phase" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "539.9921", + "y" : "867", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Toggle", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "623", + "y" : "659", + "width" : "69", + "height" : "67", + "texture" : "btn_2", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Suboscillator Shape" + } + }, + { + "name" : "NoiseColor", + "parameterAttachment" : { + "parameter" : "Noise Color" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "804", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Punch", + "parameterAttachment" : { + "parameter" : "Punch Intensity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "804", + "y" : "867", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VeloModOsc1Shape", + "parameterAttachment" : { + "parameter" : "Osc1 Shape Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1030.5", + "y" : "66", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VeloModOsc2Shape", + "parameterAttachment" : { + "parameter" : "Osc2 Shape Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1030.5", + "y" : "226", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VeloModPW", + "parameterAttachment" : { + "parameter" : "PulseWidth Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1030.5", + "y" : "386", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VeloModFMAmount", + "parameterAttachment" : { + "parameter" : "Fm Amount Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1030.5", + "y" : "546", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VeloModAmp", + "parameterAttachment" : { + "parameter" : "Amp Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1030.5", + "y" : "706", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VeloModPanorama", + "parameterAttachment" : { + "parameter" : "Panorama Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1030.5", + "y" : "866", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "OscBalance", + "parameterAttachment" : { + "parameter" : "Osc Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1221.5", + "y" : "66", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "OscLevel", + "parameterAttachment" : { + "parameter" : "Osc Mainvolume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1221.5", + "y" : "226", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Osc3Level", + "parameterAttachment" : { + "parameter" : "Osc3 Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1221.5", + "y" : "386", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "SubLevel", + "parameterAttachment" : { + "parameter" : "Suboscillator Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1221.5", + "y" : "546", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "NoiseLevel", + "parameterAttachment" : { + "parameter" : "Noise Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1221.5", + "y" : "706", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "RingModLevel", + "parameterAttachment" : { + "parameter" : "Ringmodulator Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1221.5", + "y" : "866", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Cutoff", + "parameterAttachment" : { + "parameter" : "Cutoff" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1436", + "y" : "65", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Resonance", + "parameterAttachment" : { + "parameter" : "Filter1 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1580", + "y" : "65", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EnvAmount", + "parameterAttachment" : { + "parameter" : "Filter1 Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1724", + "y" : "65", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Keyfollow", + "parameterAttachment" : { + "parameter" : "Filter1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1868", + "y" : "65", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ResVelo", + "parameterAttachment" : { + "parameter" : "Resonance1 Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2012", + "y" : "65", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EnvVelo", + "parameterAttachment" : { + "parameter" : "Flt1 EnvAmt Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2156", + "y" : "65", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Cutoff", + "parameterAttachment" : { + "parameter" : "Cutoff2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1436", + "y" : "450", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Resonance", + "parameterAttachment" : { + "parameter" : "Filter2 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1580", + "y" : "450", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EnvAmount", + "parameterAttachment" : { + "parameter" : "Filter2 Env Amt" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1724", + "y" : "450", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Keyfollow", + "parameterAttachment" : { + "parameter" : "Filter2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1868", + "y" : "450", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ResVelo", + "parameterAttachment" : { + "parameter" : "Resonance2 Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2012", + "y" : "450", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EnvVelo", + "parameterAttachment" : { + "parameter" : "Flt2 EnvAmt Velocity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2156", + "y" : "450", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "FilterBalance", + "parameterAttachment" : { + "parameter" : "Filter Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1484", + "y" : "265", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Filter1Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1628", + "y" : "215", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Filter1 Mode" + } + }, + { + "name" : "FilterRouting", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1935", + "y" : "215", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Filter Routing" + } + }, + { + "name" : "Filter2Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1628", + "y" : "329", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Filter2 Mode" + } + }, + { + "name" : "SaturationCurve", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1935", + "y" : "329", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Saturation Curve" + } + }, + { + "name" : "Env1Pol", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "2131.9", + "y" : "209", + "width" : "69", + "height" : "67", + "texture" : "btn_2", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Filter1 Env Polarity" + } + }, + { + "name" : "Env2Pol", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "2131.9", + "y" : "318", + "width" : "69", + "height" : "67", + "texture" : "btn_2", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Filter2 Env Polarity" + } + }, + { + "name" : "LinkCutoff", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1383", + "y" : "270", + "width" : "36", + "height" : "68", + "texture" : "btn_1", + "tileSizeX" : "36", + "tileSizeY" : "68" + }, + "parameterAttachment" : { + "parameter" : "Filter2 Cutoff Link" + } + }, + { + "name" : "LinkEnv", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "2248", + "y" : "270", + "width" : "36", + "height" : "68", + "texture" : "btn_1", + "tileSizeX" : "36", + "tileSizeY" : "68" + }, + "parameterAttachment" : { + "parameter" : "" + } + }, + { + "name" : "KeytrackBase", + "combobox" : { + "text" : "C3", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1845", + "y" : "284", + "width" : "65", + "height" : "36" + }, + "parameterAttachment" : { + "parameter" : "Filter Keytrack Base" + } + }, + { + "name" : "Attack", + "parameterAttachment" : { + "parameter" : "Filter Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1437", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Decay", + "parameterAttachment" : { + "parameter" : "Filter Env Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1610", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Sustain", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1792", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Time", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1979", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Release", + "parameterAttachment" : { + "parameter" : "Filter Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2157", + "y" : "658", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Attack", + "parameterAttachment" : { + "parameter" : "Amp Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1437", + "y" : "869", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Decay", + "parameterAttachment" : { + "parameter" : "Amp Env Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1610", + "y" : "869", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Sustain", + "parameterAttachment" : { + "parameter" : "Amp Env Sustain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1792", + "y" : "869", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Time", + "parameterAttachment" : { + "parameter" : "Amp Env Sustain Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1979", + "y" : "869", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Release", + "parameterAttachment" : { + "parameter" : "Amp Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2157", + "y" : "869", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + } + ] + }, + { + "name" : "page_lfo", + "image" : { + "x" : "100", + "y" : "122", + "width" : "2299", + "height" : "994", + "texture" : "panel_2" + }, + "children" : [ + { + "name" : "LfoRate", + "parameterAttachment" : { + "parameter" : "Lfo1 Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "63", + "y" : "88", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoSymmetry", + "parameterAttachment" : { + "parameter" : "Lfo1 Symmetry" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "379", + "y" : "88", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoOsc1Pitch", + "parameterAttachment" : { + "parameter" : "Osc1 Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "63", + "y" : "276", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoOsc2Pitch", + "parameterAttachment" : { + "parameter" : "Osc2 Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "221", + "y" : "276", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoPW12", + "parameterAttachment" : { + "parameter" : "PW Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "379", + "y" : "276", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoReso12", + "parameterAttachment" : { + "parameter" : "Reso Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "63", + "y" : "480", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoKeyfollow", + "parameterAttachment" : { + "parameter" : "Lfo1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "221", + "y" : "480", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoKeytrigger", + "parameterAttachment" : { + "parameter" : "Lfo1 Keytrigger" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "379", + "y" : "480", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoAssignAmount", + "parameterAttachment" : { + "parameter" : "LFO1 Assign Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "63", + "y" : "667", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoFilterGain", + "parameterAttachment" : { + "parameter" : "FiltGain Lfo1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "379", + "y" : "849", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "200", + "y" : "59", + "width" : "122", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo1 Clock" + } + }, + { + "name" : "LfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "18", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "200", + "y" : "144", + "width" : "122", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo1 Shape" + } + }, + { + "name" : "LfoOscPitchLink", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "143", + "y" : "398", + "width" : "68", + "height" : "36", + "texture" : "btn_4", + "tileSizeX" : "68", + "tileSizeY" : "36" + }, + "parameterAttachment" : { + "parameter" : "" + } + }, + { + "name" : "LfoEnvMode", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "408", + "y" : "668", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + } + }, + { + "name" : "LfoMode", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "67", + "y" : "845", + "width" : "69", + "height" : "67", + "texture" : "btn_2", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Lfo1 Mode" + } + }, + { + "name" : "LfoAssignDestination", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "200", + "y" : "676", + "width" : "175", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "LFO1 Assign Dest" + } + }, + { + "name" : "LfoRate", + "parameterAttachment" : { + "parameter" : "Lfo2 Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "595", + "y" : "88", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoSymmetry", + "parameterAttachment" : { + "parameter" : "Lfo2 Symmetry" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "911", + "y" : "88", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoOsc1Pitch", + "parameterAttachment" : { + "parameter" : "Cutoff1 Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "595", + "y" : "276", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoOsc2Pitch", + "parameterAttachment" : { + "parameter" : "Cutoff2 Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "753", + "y" : "276", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoPW12", + "parameterAttachment" : { + "parameter" : "Shape Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "911", + "y" : "276", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoReso12", + "parameterAttachment" : { + "parameter" : "Pan Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "595", + "y" : "480", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoKeyfollow", + "parameterAttachment" : { + "parameter" : "Lfo2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "753", + "y" : "480", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoKeytrigger", + "parameterAttachment" : { + "parameter" : "Lfo2 Keytrigger" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "911", + "y" : "480", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoAssignAmount", + "parameterAttachment" : { + "parameter" : "LFO2 Assign Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "595", + "y" : "667", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoFilterGain", + "parameterAttachment" : { + "parameter" : "FM Lfo2 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "911", + "y" : "849", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "LfoClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "732", + "y" : "59", + "width" : "122", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo2 Clock" + } + }, + { + "name" : "LfoShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "18", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "732", + "y" : "144", + "width" : "122", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo2 Shape" + } + }, + { + "name" : "LfoOscPitchLink", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "675", + "y" : "398", + "width" : "68", + "height" : "36", + "texture" : "btn_4", + "tileSizeX" : "68", + "tileSizeY" : "36" + }, + "parameterAttachment" : { + "parameter" : "" + } + }, + { + "name" : "LfoEnvMode", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "940", + "y" : "668", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Lfo2 Env Mode" + } + }, + { + "name" : "LfoMode", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "599", + "y" : "845", + "width" : "69", + "height" : "67", + "texture" : "btn_2", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Lfo2 Mode" + } + }, + { + "name" : "LfoAssignDestination", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "732", + "y" : "676", + "width" : "175", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "LFO2 Assign Dest" + } + }, + { + "name" : "Lfo3Rate", + "parameterAttachment" : { + "parameter" : "Lfo3 Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1128", + "y" : "89", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Lfo3FadeInTime", + "parameterAttachment" : { + "parameter" : "Lfo3 Fade-In Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1128", + "y" : "277", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Lfo3Keyfollow", + "parameterAttachment" : { + "parameter" : "Lfo3 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1286", + "y" : "277", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "OscLfo3Amount", + "parameterAttachment" : { + "parameter" : "Osc Lfo3 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1128", + "y" : "481", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Lfo3Clock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1265", + "y" : "60", + "width" : "122", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Clock" + } + }, + { + "name" : "Lfo3Shape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "18", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1265", + "y" : "145", + "width" : "122", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Shape" + } + }, + { + "name" : "Lfo3Mode", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1270", + "y" : "476", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Mode" + } + }, + { + "name" : "Lfo3Destination", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1128", + "y" : "649", + "width" : "175", + "height" : "50" + }, + "parameterAttachment" : { + "parameter" : "Lfo3 Destination" + } + }, + { + "name" : "Assign1Source", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1516", + "y" : "57.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign1 Source" + } + }, + { + "name" : "Assign1Dest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1516", + "y" : "145.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign1 Destination" + } + }, + { + "name" : "Assign1Amount", + "parameterAttachment" : { + "parameter" : "Assign1 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1754", + "y" : "88.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign4Source", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1921", + "y" : "58.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign 4 Source" + } + }, + { + "name" : "Assign4Dest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1921", + "y" : "146.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign 4 Destination" + } + }, + { + "name" : "Assign4Amount", + "parameterAttachment" : { + "parameter" : "Assign 4 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2159", + "y" : "89.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign5Source", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1516", + "y" : "286.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign 5 Source" + } + }, + { + "name" : "Assign5Dest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1516", + "y" : "374.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign 5 Destination" + } + }, + { + "name" : "Assign5Amount", + "parameterAttachment" : { + "parameter" : "Assign 5 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1754", + "y" : "317.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign6Source", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1921", + "y" : "287.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign 6 Source" + } + }, + { + "name" : "Assign6Dest", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1921", + "y" : "375.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign 6 Destination" + } + }, + { + "name" : "Assign6Amount", + "parameterAttachment" : { + "parameter" : "Assign 6 Amount" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2159", + "y" : "318.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign2Source", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1515.5", + "y" : "551.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign2 Source" + } + }, + { + "name" : "Assign2Dest1", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1515.5", + "y" : "640.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign2 Destination1" + } + }, + { + "name" : "Assign2Amount1", + "parameterAttachment" : { + "parameter" : "Assign2 Amount1" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1753.5", + "y" : "587.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign2Dest2", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1921.6", + "y" : "600.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign2 Destination2" + } + }, + { + "name" : "Assign2Amount2", + "parameterAttachment" : { + "parameter" : "Assign2 Amount2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2159.5", + "y" : "587.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign3Source", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1129.5", + "y" : "814.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Source" + } + }, + { + "name" : "Assign3Dest1", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1129.5", + "y" : "903.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Destination1" + } + }, + { + "name" : "Assign3Amount1", + "parameterAttachment" : { + "parameter" : "Assign3 Amount1" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1367.5", + "y" : "850.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign3Dest2", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1515.5", + "y" : "862.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Destination2" + } + }, + { + "name" : "Assign3Amount2", + "parameterAttachment" : { + "parameter" : "Assign3 Amount2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1753.5", + "y" : "850.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Assign3Dest3", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1920.7", + "y" : "862.5", + "width" : "172", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Assign3 Destination3" + } + }, + { + "name" : "Assign3Amount3", + "parameterAttachment" : { + "parameter" : "Assign3 Amount3" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2158.7", + "y" : "850.5", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + } + ] + }, + { + "name" : "page_fx", + "image" : { + "x" : "100", + "y" : "122", + "width" : "2299", + "height" : "994", + "texture" : "panel_3" + }, + "children" : [ + { + "name" : "InputFollowerMode", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "51", + "y" : "79", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Input Follower Mode" + } + }, + { + "name" : "InputFollowerLevel", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "181", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "InputFollowerAttack", + "parameterAttachment" : { + "parameter" : "Filter Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "326", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "InputFollowerRelease", + "parameterAttachment" : { + "parameter" : "Filter Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "471", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DistortionIntensity", + "parameterAttachment" : { + "parameter" : "Distortion Intensity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "867", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DistortionCurve", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "661", + "y" : "88", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Distortion Curve" + } + }, + { + "name" : "ChorusShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1050", + "y" : "87", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Chorus Lfo Shape" + } + }, + { + "name" : "ChorusRate", + "parameterAttachment" : { + "parameter" : "Chorus Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1260", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ChorusDepth", + "parameterAttachment" : { + "parameter" : "Chorus Depth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1401", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ChorusFeedback", + "parameterAttachment" : { + "parameter" : "Chorus Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1542", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ChorusMix", + "parameterAttachment" : { + "parameter" : "Chorus Mix" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1683", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ChorusDelayTime", + "parameterAttachment" : { + "parameter" : "Chorus Delay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1824", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "AnalogBoostIntensity", + "parameterAttachment" : { + "parameter" : "Bass Tune" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2021", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "AnalogBoostTune", + "parameterAttachment" : { + "parameter" : "Bass Intensity" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2162", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaserStages", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "38", + "y" : "315", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Phaser Mode" + } + }, + { + "name" : "PhaserMix", + "parameterAttachment" : { + "parameter" : "Phaser Mix" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "248", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaserSpread", + "parameterAttachment" : { + "parameter" : "Phaser Spread" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "383", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaserRate", + "parameterAttachment" : { + "parameter" : "Phaser Rate" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "518", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaserDepth", + "parameterAttachment" : { + "parameter" : "Phaser Depth" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "653", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaerFrequency", + "parameterAttachment" : { + "parameter" : "Phaser Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "788", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PhaerFeedback", + "parameterAttachment" : { + "parameter" : "Phaser Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "923", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqLowGain", + "parameterAttachment" : { + "parameter" : "LowEQ Gain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1124", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqLowFreq", + "parameterAttachment" : { + "parameter" : "LowEQ Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1267", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqMidGain", + "parameterAttachment" : { + "parameter" : "MidEQ Gain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1410", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqMidFreq", + "parameterAttachment" : { + "parameter" : "MidEQ Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1553", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqMidQ", + "parameterAttachment" : { + "parameter" : "MidEQ Q-Factor" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1696", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqHighGain", + "parameterAttachment" : { + "parameter" : "HighEQ Gain" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1839", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "EqHighFreq", + "parameterAttachment" : { + "parameter" : "HighEQ Frequency" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1982", + "y" : "304", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "RingMod", + "parameterAttachment" : { + "parameter" : "Input Ringmodulator" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2174", + "y" : "303", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DelayReverbSend", + "parameterAttachment" : { + "parameter" : "Effect Send" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "75", + "y" : "569", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ContainerDelay", + "component" : { + "x" : "226", + "y" : "490", + "width" : "911", + "height" : "245" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + }, + "children" : [ + { + "name" : "DelayTime", + "parameterAttachment" : { + "parameter" : "Delay Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "34", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DelayRate", + "parameterAttachment" : { + "parameter" : "Dly Rate / Rev Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DelayFeedback", + "parameterAttachment" : { + "parameter" : "Delay Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "508", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DelayColor", + "parameterAttachment" : { + "parameter" : "Delay Color" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "647", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DelayDepth", + "parameterAttachment" : { + "parameter" : "Dly Depth " + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "790", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "DelayClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "162", + "y" : "40", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Delay Clock" + } + }, + { + "name" : "DelayShape", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "162", + "y" : "137", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Delay Lfo Shape" + } + } + ] + }, + { + "name" : "ContainerReverb", + "component" : { + "x" : "226", + "y" : "738", + "width" : "911", + "height" : "245" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + }, + "children" : [ + { + "name" : "ReverbDecayTime", + "parameterAttachment" : { + "parameter" : "Dly Rate / Rev Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "34", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ReverbDamping", + "parameterAttachment" : { + "parameter" : "Reverb Damping" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "369", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ReverbColor", + "parameterAttachment" : { + "parameter" : "Delay Color" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "508", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ReverbPredelay", + "parameterAttachment" : { + "parameter" : "Delay Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "647", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ReverbFeedback", + "parameterAttachment" : { + "parameter" : "Delay Feedback" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "790", + "y" : "80", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ReverbType", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "162", + "y" : "90", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Rev Size" + } + } + ] + }, + { + "name" : "DelayReverbMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "35", + "y" : "736", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Delay/Reverb Mode" + } + }, + { + "name" : "VocCenterFreq", + "parameterAttachment" : { + "parameter" : "Cutoff" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1448", + "y" : "588", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocModOffset", + "parameterAttachment" : { + "parameter" : "Cutoff2" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1590.04", + "y" : "588", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocModQ", + "parameterAttachment" : { + "parameter" : "Filter1 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1732.08", + "y" : "588", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocModSpread", + "parameterAttachment" : { + "parameter" : "Filter1 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1874.12", + "y" : "588", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocCarrQ", + "parameterAttachment" : { + "parameter" : "Filter2 Resonance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2016.16", + "y" : "588", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocCarrSpread", + "parameterAttachment" : { + "parameter" : "Filter2 Keyfollow" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2158.2", + "y" : "588", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocSpectralBalance", + "parameterAttachment" : { + "parameter" : "Filter Env Sustain Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1448", + "y" : "798.9", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocBands", + "parameterAttachment" : { + "parameter" : "Filter Env Release" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1590.04", + "y" : "798.9", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocAttack", + "parameterAttachment" : { + "parameter" : "Filter Env Attack" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1732.08", + "y" : "798.9", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocRelease", + "parameterAttachment" : { + "parameter" : "Filter Env Decay" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1874.12", + "y" : "798.9", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocSourceBalance", + "parameterAttachment" : { + "parameter" : "Filter Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "2016.16", + "y" : "798.9", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "VocoderMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1196", + "y" : "643", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Vocoder Mode" + } + }, + { + "name" : "VocoderLink", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "1953", + "y" : "509", + "width" : "68", + "height" : "36", + "texture" : "btn_4", + "tileSizeX" : "68", + "tileSizeY" : "36" + }, + "parameterAttachment" : { + "parameter" : "Filter2 Cutoff Link" + } + }, + { + "name" : "VocoderModulationInput", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1196", + "y" : "761", + "width" : "170", + "height" : "52" + }, + "parameterAttachment" : { + "parameter" : "Input Select" + } + } + ] + }, + { + "name" : "page_arp", + "image" : { + "x" : "100", + "y" : "122", + "width" : "2299", + "height" : "994", + "texture" : "panel_4" + }, + "children" : [ + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "63", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "63", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "71", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "71", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "181", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "181", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "189", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "189", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "299", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "299", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "307", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "307", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "417", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "417", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "425", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "425", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "535", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "535", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "543", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "543", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "653", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "653", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "661", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "661", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "771", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "771", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "779", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "779", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "62.20001", + "y" : "889", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "131", + "y" : "889", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "381", + "y" : "897", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "468", + "y" : "897", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "63", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "63", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "71", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "71", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "181", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "181", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "189", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "189", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "299", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "299", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "307", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "307", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "417", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "417", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "425", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "425", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "535", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "535", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "543", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "543", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "653", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "653", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "661", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "661", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "771", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "771", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "779", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "779", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "SelectPart", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "611.2", + "y" : "889", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PresetName", + "textbutton" : { + "text" : "New Text", + "textHeight" : "30", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "680", + "y" : "889", + "width" : "232", + "height" : "67" + } + }, + { + "name" : "PartVolume", + "rotary" : { + }, + "spritesheet" : { + "x" : "930", + "y" : "897", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "PartPan", + "rotary" : { + }, + "spritesheet" : { + "x" : "1017", + "y" : "897", + "width" : "55", + "height" : "55", + "texture" : "knob_2_128", + "tileSizeX" : "55", + "tileSizeY" : "55" + }, + "componentProperties" : { + "disabledAlpha" : "0.3" + } + }, + { + "name" : "Portamento", + "parameterAttachment" : { + "parameter" : "Portamento Time" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1170", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "PatchVolume", + "parameterAttachment" : { + "parameter" : "Patch Volume" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1333", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Panning", + "parameterAttachment" : { + "parameter" : "Panorama" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1496", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "OutputBalance", + "parameterAttachment" : { + "parameter" : "Balance" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1659", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "Transpose", + "parameterAttachment" : { + "parameter" : "Transpose" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1822", + "y" : "76", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "SmoothMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1153", + "y" : "233", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Control Smooth Mode" + } + }, + { + "name" : "BendScale", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1359", + "y" : "233", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Bender Scale" + } + }, + { + "name" : "BendUp", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1565", + "y" : "233", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Bender Range Up" + } + }, + { + "name" : "BendDown", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1771", + "y" : "233", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Bender Range Down" + } + }, + { + "name" : "Category1", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1153", + "y" : "354", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Category1" + } + }, + { + "name" : "Category2", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1359", + "y" : "354", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Category2" + } + }, + { + "name" : "KeyMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1565", + "y" : "354", + "width" : "177", + "height" : "55" + }, + "parameterAttachment" : { + "parameter" : "Key Mode" + } + }, + { + "name" : "MidiIn", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "2025", + "y" : "58", + "width" : "239", + "height" : "48" + } + }, + { + "name" : "MidiOut", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "2025", + "y" : "155", + "width" : "239", + "height" : "48" + } + }, + { + "name" : "ArpMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1153", + "y" : "535", + "width" : "170", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Arp Mode" + } + }, + { + "name" : "ArpPattern", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1153", + "y" : "632", + "width" : "170", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Arp Pattern Selct" + } + }, + { + "name" : "ArpOctRange", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1698", + "y" : "535", + "width" : "170", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Arp Octave Range" + } + }, + { + "name" : "ArpClock", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1698", + "y" : "632", + "width" : "170", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Arp Clock" + } + }, + { + "name" : "ArpNoteLength", + "parameterAttachment" : { + "parameter" : "Arp Note Length" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1387", + "y" : "575", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ArpNoteSwing", + "parameterAttachment" : { + "parameter" : "Arp Swing" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1536", + "y" : "575", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ArpGlobalTempo", + "parameterAttachment" : { + "parameter" : "Clock Tempo" + }, + "rotary" : { + }, + "spritesheet" : { + "x" : "1932.4", + "y" : "575", + "width" : "75", + "height" : "75", + "texture" : "knob_1_128", + "tileSizeX" : "75", + "tileSizeY" : "75" + } + }, + { + "name" : "ArpHold", + "button" : { + "isToggle" : "1", + "normalImage" : "0", + "overImage" : "0", + "downImage" : "0", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "2096", + "y" : "579", + "width" : "69", + "height" : "67", + "texture" : "btn_3", + "tileSizeX" : "69", + "tileSizeY" : "67" + }, + "parameterAttachment" : { + "parameter" : "Arp Hold Enable" + } + }, + { + "name" : "PlayModeToggle", + "button" : { + "isToggle" : "1", + "normalImage" : "1", + "overImage" : "1", + "downImage" : "1", + "normalImageOn" : "0", + "overImageOn" : "0", + "downImageOn" : "0", + "x" : "1169", + "y" : "835", + "width" : "69", + "height" : "67", + "texture" : "btn_2", + "tileSizeX" : "69", + "tileSizeY" : "67" + } + }, + { + "name" : "InputMode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1410", + "y" : "798", + "width" : "174", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Input Mode" + } + }, + { + "name" : "InputSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1410", + "y" : "897", + "width" : "174", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Input Select" + } + }, + { + "name" : "SoftKnob1Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1679", + "y" : "798", + "width" : "173", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob-1 Mode" + } + }, + { + "name" : "SoftKnob2Mode", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1679", + "y" : "897", + "width" : "173", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob-2 Mode" + } + }, + { + "name" : "SoftKnob1Name", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1906.1", + "y" : "798", + "width" : "173", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob1 ShortName" + } + }, + { + "name" : "SoftKnob2Name", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "1906.1", + "y" : "897", + "width" : "173", + "height" : "48" + }, + "parameterAttachment" : { + "parameter" : "Soft Knob2 ShortName" + } + } + ] + }, + { + "name" : "page_presets", + "image" : { + "x" : "100", + "y" : "122", + "width" : "2299", + "height" : "994", + "texture" : "panel_5" + }, + "children" : [ + { + "name" : "RomBankSelect", + "combobox" : { + "text" : "Wave 42", + "textHeight" : "20", + "alignH" : "L", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "37", + "y" : "52", + "width" : "950", + "height" : "54" + } + }, + { + "name" : "ContainerFileSelector", + "component" : { + "x" : "6", + "y" : "181", + "width" : "1008", + "height" : "813" + } + }, + { + "name" : "ContainerPatchList", + "component" : { + "x" : "1034", + "y" : "30", + "width" : "1041", + "height" : "914" + } + }, + { + "name" : "ContainerPatchListSearchBox", + "component" : { + "x" : "1034", + "y" : "944", + "width" : "1041", + "height" : "50" + } + }, + { + "name" : "PresetSave", + "button" : { + "normalImage" : "0", + "overImage" : "0", + "downImage" : "1", + "normalImageOn" : "1", + "overImageOn" : "1", + "downImageOn" : "1", + "x" : "2110", + "y" : "47", + "width" : "171", + "height" : "50", + "texture" : "btn_save_preset", + "tileSizeX" : "171", + "tileSizeY" : "50" + } + } + ] + }, + { + "name" : "DonateInfo", + "hyperlinkbutton" : { + "url" : "https://paypal.me/dsp56300", + "text" : "To donate: https://paypal.me/dsp56300", + "textHeight" : "20", + "color" : "009BFFFF", + "x" : "1030", + "y" : "1117", + "width" : "485", + "height" : "28" + } + }, + { + "name" : "VersionNumber", + "label" : { + "text" : "1.12.34", + "textHeight" : "18", + "color" : "009BFFFF", + "alignH" : "L", + "alignV" : "C", + "x" : "251.9017", + "y" : "1117", + "width" : "56.1816", + "height" : "28" + } + }, + { + "name" : "DeviceModel", + "label" : { + "text" : "C", + "textHeight" : "20", + "color" : "009BFFFF", + "alignH" : "R", + "alignV" : "C", + "x" : "448", + "y" : "1117", + "width" : "54.4257", + "height" : "28" + } + }, + { + "name" : "RomSelector", + "combobox" : { + "text" : "Virus_C_OS_6.5.bin", + "textHeight" : "20", + "color" : "009BFFFF", + "alignH" : "L", + "alignV" : "C", + "x" : "651", + "y" : "1117", + "width" : "274", + "height" : "28", + "tooltip" : "Copy a valid ROM file with file extension .bin next to this plugin" + } + }, + { + "name" : "FocusedParameterTooltip", + "label" : { + "text" : "42", + "textHeight" : "28", + "backgroundColor" : "000000FF", + "alignH" : "C", + "alignV" : "C", + "fontFile" : "Digital", + "fontName" : "Register", + "x" : "989", + "y" : "614.5", + "width" : "134", + "height" : "35" + }, + "componentProperties" : { + "offsetY" : "25" + } + } + ] +} +\ No newline at end of file diff --git a/source/jucePlugin/skins/Trancy/assets.cmake b/source/jucePlugin/skins/Trancy/assets.cmake @@ -0,0 +1,26 @@ +set(ASSETS_VirusC_Trancy + ${CMAKE_CURRENT_LIST_DIR}/btn_1.png + ${CMAKE_CURRENT_LIST_DIR}/btn_2.png + ${CMAKE_CURRENT_LIST_DIR}/btn_3.png + ${CMAKE_CURRENT_LIST_DIR}/btn_4.png + ${CMAKE_CURRENT_LIST_DIR}/btn_left.png + ${CMAKE_CURRENT_LIST_DIR}/btn_main_1.png + ${CMAKE_CURRENT_LIST_DIR}/btn_main_2.png + ${CMAKE_CURRENT_LIST_DIR}/btn_main_3.png + ${CMAKE_CURRENT_LIST_DIR}/btn_main_4.png + ${CMAKE_CURRENT_LIST_DIR}/btn_main_5.png + ${CMAKE_CURRENT_LIST_DIR}/btn_menu.png + ${CMAKE_CURRENT_LIST_DIR}/btn_right.png + ${CMAKE_CURRENT_LIST_DIR}/btn_save_preset.png + ${CMAKE_CURRENT_LIST_DIR}/Digital.ttf + ${CMAKE_CURRENT_LIST_DIR}/knob_1_128.png + ${CMAKE_CURRENT_LIST_DIR}/knob_2_128.png + ${CMAKE_CURRENT_LIST_DIR}/main_background.png + ${CMAKE_CURRENT_LIST_DIR}/panel_1.png + ${CMAKE_CURRENT_LIST_DIR}/panel_2.png + ${CMAKE_CURRENT_LIST_DIR}/panel_3.png + ${CMAKE_CURRENT_LIST_DIR}/panel_4.png + ${CMAKE_CURRENT_LIST_DIR}/panel_5.png + + ${CMAKE_CURRENT_LIST_DIR}/VirusC_Trancy.json +) diff --git a/source/jucePlugin/skins/Trancy/btn_1.png b/source/jucePlugin/skins/Trancy/btn_1.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_2.png b/source/jucePlugin/skins/Trancy/btn_2.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_3.png b/source/jucePlugin/skins/Trancy/btn_3.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_4.png b/source/jucePlugin/skins/Trancy/btn_4.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_left.png b/source/jucePlugin/skins/Trancy/btn_left.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_main_1.png b/source/jucePlugin/skins/Trancy/btn_main_1.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_main_2.png b/source/jucePlugin/skins/Trancy/btn_main_2.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_main_3.png b/source/jucePlugin/skins/Trancy/btn_main_3.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_main_4.png b/source/jucePlugin/skins/Trancy/btn_main_4.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_main_5.png b/source/jucePlugin/skins/Trancy/btn_main_5.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_menu.png b/source/jucePlugin/skins/Trancy/btn_menu.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_right.png b/source/jucePlugin/skins/Trancy/btn_right.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/btn_save_preset.png b/source/jucePlugin/skins/Trancy/btn_save_preset.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/knob_1_128.png b/source/jucePlugin/skins/Trancy/knob_1_128.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/knob_2_128.png b/source/jucePlugin/skins/Trancy/knob_2_128.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/main_background.png b/source/jucePlugin/skins/Trancy/main_background.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/panel_1.png b/source/jucePlugin/skins/Trancy/panel_1.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/panel_2.png b/source/jucePlugin/skins/Trancy/panel_2.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/panel_3.png b/source/jucePlugin/skins/Trancy/panel_3.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/panel_4.png b/source/jucePlugin/skins/Trancy/panel_4.png Binary files differ. diff --git a/source/jucePlugin/skins/Trancy/panel_5.png b/source/jucePlugin/skins/Trancy/panel_5.png Binary files differ. diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.cpp b/source/jucePlugin/ui/Virus_PatchBrowser.cpp @@ -1,388 +0,0 @@ -#include "../VirusParameterBinding.h" -#include "Virus_PatchBrowser.h" -#include "VirusEditor.h" -#include <juce_gui_extra/juce_gui_extra.h> -#include <juce_cryptography/juce_cryptography.h> - -#include "../../synthLib/midiToSysex.h" - -using namespace juce; -using namespace virusLib; - -const Array<String> g_categories = {"", "Lead", "Bass", "Pad", "Decay", "Pluck", - "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion", - "Input", "Vocoder", "Favourite 1", "Favourite 2", "Favourite 3"}; - -PatchBrowser::PatchBrowser(VirusParameterBinding & _parameterBinding, Virus::Controller& _controller) : - m_parameterBinding(_parameterBinding), - m_controller(_controller), - m_fileFilter("*.syx;*.mid;*.midi", "*", "virus patch dumps"), - m_bankList(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, nullptr), - m_search("Search Box"), - m_patchList("Patch browser"), - m_properties(m_controller.getConfig()) -{ - const auto bankDir = m_properties->getValue("virus_bank_dir", ""); - if (bankDir != "" && File(bankDir).isDirectory()) - { - m_bankList.setRoot(bankDir); - } - - setBounds(22, 30, 1000, 600); - m_bankList.setBounds(16, 28, 480, 540); - - m_patchList.setBounds(m_bankList.getBounds().translated(m_bankList.getWidth(), 0)); - - m_patchList.getHeader().addColumn("#", Columns::INDEX, 32); - m_patchList.getHeader().addColumn("Name", Columns::NAME, 130); - m_patchList.getHeader().addColumn("Category1", Columns::CAT1, 84); - m_patchList.getHeader().addColumn("Category2", Columns::CAT2, 84); - m_patchList.getHeader().addColumn("Arp", Columns::ARP, 32); - m_patchList.getHeader().addColumn("Uni", Columns::UNI, 32); - m_patchList.getHeader().addColumn("ST+-", Columns::ST, 32); - m_patchList.getHeader().addColumn("Ver", Columns::VER, 32); - addAndMakeVisible(m_bankList); - addAndMakeVisible(m_patchList); - - m_search.setSize(m_patchList.getWidth(), 20); - m_search.setColour(TextEditor::textColourId, Colours::white); - m_search.setTopLeftPosition(m_patchList.getBounds().getBottomLeft().translated(0, 8)); - m_search.onTextChange = [this] - { - m_filteredPatches.clear(); - for(const auto& patch : m_patches) - { - const auto searchValue = m_search.getText(); - if (searchValue.isEmpty()) - { - m_filteredPatches.add(patch); - } - else if(patch.name.containsIgnoreCase(searchValue)) - { - m_filteredPatches.add(patch); - } - } - m_patchList.updateContent(); - m_patchList.deselectAllRows(); - m_patchList.repaint(); - }; - m_search.setTextToShowWhenEmpty("search...", Colours::grey); - addAndMakeVisible(m_search); - m_bankList.addListener(this); - m_patchList.setModel(this); -} - -void PatchBrowser::selectionChanged() {} - -VirusModel guessVersion(const uint8_t* _data) -{ - const auto v = _data[0]; - - if (v < 5) - return VirusModel::A; - if (v == 6) - return VirusModel::B; - if (v == 7) - return VirusModel::C; - return VirusModel::TI; -} - -uint32_t PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets) -{ - uint32_t count = 0; - for (const auto& packet : _packets) - { - if (load(_result, _dedupeChecksums, packet)) - ++count; - } - return count; -} - -bool PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data) -{ - if (_data.size() < 267) - return false; - - auto* it = &_data.front(); - - if (*it == (uint8_t)0xf0 - && *(it + 1) == (uint8_t)0x00 - && *(it + 2) == (uint8_t)0x20 - && *(it + 3) == (uint8_t)0x33 - && *(it + 4) == (uint8_t)0x01 - && *(it + 6) == (uint8_t)virusLib::DUMP_SINGLE) - { - Patch patch; - patch.progNumber = static_cast<int>(_result.size()); - patch.sysex = _data; - patch.data.insert(patch.data.begin(), _data.begin() + 9, _data.end()); - patch.name = parseAsciiText(patch.data, 128 + 112); - patch.category1 = patch.data[251]; - patch.category2 = patch.data[252]; - patch.unison = patch.data[97]; - patch.transpose = patch.data[93]; - if ((uint8_t)*(it + 266) != (uint8_t)0xf7 && (uint8_t)*(it + 266) != (uint8_t)0xf8) { - patch.model = VirusModel::TI; - } - else { - patch.model = guessVersion(&patch.data[0]); - } - - if(!_dedupeChecksums) - { - _result.push_back(patch); - } - else - { - const auto md5 = std::string(MD5(it + 9 + 17, 256 - 17 - 3).toHexString().toRawUTF8()); - - if (_dedupeChecksums->find(md5) == _dedupeChecksums->end()) - { - _dedupeChecksums->insert(md5); - _result.push_back(patch); - } - } - - return true; - } - return false; -} - -uint32_t PatchBrowser::loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const File& file) -{ - const auto ext = file.getFileExtension().toLowerCase(); - const auto path = file.getParentDirectory().getFullPathName(); - - if (ext == ".syx") - { - MemoryBlock data; - - if (!file.loadFileAsData(data)) - return 0; - - std::vector<uint8_t> d; - d.resize(data.getSize()); - memcpy(&d[0], data.getData(), data.getSize()); - - std::vector<std::vector<uint8_t>> packets; - splitMultipleSysex(packets, d); - - return load(_result, _dedupeChecksums, packets); - } - - if (ext == ".mid" || ext == ".midi") - { - std::vector<uint8_t> data; - - synthLib::MidiToSysex::readFile(data, file.getFullPathName().getCharPointer()); - - if(data.empty()) - return 0; - - std::vector<std::vector<uint8_t>> packets; - splitMultipleSysex(packets, data); - - return load(_result, _dedupeChecksums, packets); - } - return 0; -} - -void PatchBrowser::fileClicked(const File &file, const MouseEvent &e) -{ - const auto ext = file.getFileExtension().toLowerCase(); - const auto path = file.getParentDirectory().getFullPathName(); - if (file.isDirectory() && e.mods.isRightButtonDown()) - { - auto p = PopupMenu(); - p.addItem("Add directory contents to patch list", [this, file]() - { - m_patches.clear(); - m_checksums.clear(); - std::set<std::string> dedupeChecksums; - - std::vector<Patch> patches; - - for (const auto& f : RangedDirectoryIterator(file, false, "*.syx;*.mid;*.midi", File::findFiles)) - loadBankFile(patches, &dedupeChecksums, f.getFile()); - - m_filteredPatches.clear(); - - for(const auto& patch : patches) - { - const auto searchValue = m_search.getText(); - - m_patches.add(patch); - - if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue)) - m_filteredPatches.add(patch); - } - m_patchList.updateContent(); - m_patchList.deselectAllRows(); - m_patchList.repaint(); - }); - p.showMenuAsync(PopupMenu::Options()); - - return; - } - m_properties->setValue("virus_bank_dir", path); - - if(file.existsAsFile() && ext == ".syx" || ext == ".midi" || ext == ".mid") - { - m_patches.clear(); - std::vector<Patch> patches; - loadBankFile(patches, nullptr, file); - m_filteredPatches.clear(); - for(const auto& patch : patches) - { - const auto searchValue = m_search.getText(); - m_patches.add(patch); - if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue)) - m_filteredPatches.add(patch); - } - m_patchList.updateContent(); - m_patchList.deselectAllRows(); - m_patchList.repaint(); - } - -} - -void PatchBrowser::fileDoubleClicked(const File &file) {} - -void PatchBrowser::browserRootChanged(const File &newRoot) {} - -int PatchBrowser::getNumRows() { return m_filteredPatches.size(); } - -void PatchBrowser::paintRowBackground(Graphics &g, int rowNumber, int width, int height, bool rowIsSelected) { - const auto alternateColour = getLookAndFeel() - .findColour(ListBox::backgroundColourId) - .interpolatedWith(getLookAndFeel().findColour(ListBox::textColourId), 0.03f); - if (rowIsSelected) - g.fillAll(Colours::lightblue); - else if (rowNumber & 1) - g.fillAll(alternateColour); -} - -void PatchBrowser::paintCell(Graphics &g, int rowNumber, int columnId, int width, int height, bool rowIsSelected) { - g.setColour(rowIsSelected ? Colours::darkblue - : getLookAndFeel().findColour(ListBox::textColourId)); // [5] - - if (rowNumber >= getNumRows()) - return; // Juce what are you up to? - - const auto rowElement = m_filteredPatches[rowNumber]; - //auto text = rowElement.name; - String text = ""; - if (columnId == Columns::INDEX) - text = String(rowElement.progNumber); - else if (columnId == Columns::NAME) - text = rowElement.name; - else if (columnId == Columns::CAT1) - text = g_categories[rowElement.category1]; - else if (columnId == Columns::CAT2) - text = g_categories[rowElement.category2]; - else if (columnId == Columns::ARP) - text = rowElement.data[129] != 0 ? "Y" : " "; - else if(columnId == Columns::UNI) - text = rowElement.unison == 0 ? " " : String(rowElement.unison+1); - else if(columnId == Columns::ST) - text = rowElement.transpose != 64 ? String(rowElement.transpose - 64) : " "; - else if (columnId == Columns::VER) { - if(rowElement.model < ModelList.size()) - text = ModelList[rowElement.model]; - } - g.drawText(text, 2, 0, width - 4, height, Justification::centredLeft, true); // [6] - g.setColour(getLookAndFeel().findColour(ListBox::backgroundColourId)); - g.fillRect(width - 1, 0, 1, height); // [7] -} - -void PatchBrowser::selectedRowsChanged(int lastRowSelected) -{ - const auto idx = m_patchList.getSelectedRow(); - - if (idx == -1) - return; - - // force to edit buffer - const auto part = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE); - - auto sysex = m_filteredPatches[idx].sysex; - sysex[7] = toMidiByte(virusLib::BankNumber::EditBuffer); - sysex[8] = part; - - m_controller.sendSysEx(sysex); - - m_controller.sendSysEx(m_controller.constructMessage({ virusLib::REQUEST_SINGLE, 0x0, part })); -} - -void PatchBrowser::cellDoubleClicked(int rowNumber, int columnId, const MouseEvent &) -{ - if(rowNumber == m_patchList.getSelectedRow()) - selectedRowsChanged(0); -} - -class PatchBrowser::PatchBrowserSorter -{ -public: - PatchBrowserSorter (const int attributeToSortBy, const bool forwards) - : attributeToSort (attributeToSortBy), - direction (forwards ? 1 : -1) - {} - - int compareElements (const Patch& first, const Patch& second) const - { - if(attributeToSort == Columns::INDEX) - return direction * (first.progNumber - second.progNumber); - if (attributeToSort == Columns::NAME) - return direction * first.name.compareIgnoreCase(second.name); - if (attributeToSort == Columns::CAT1) - return direction * (first.category1 - second.category1); - if (attributeToSort == Columns::CAT2) - return direction * (first.category2 - second.category2); - if (attributeToSort == Columns::ARP) - return direction * (first.data[129]- second.data[129]); - if (attributeToSort == Columns::UNI) - return direction * (first.unison - second.unison); - if (attributeToSort == Columns::VER) - return direction * (first.model - second.model); - if (attributeToSort == Columns::ST) - return direction * (first.transpose - second.transpose); - return direction * (first.progNumber - second.progNumber); - } - -private: - const int attributeToSort; - const int direction; -}; - -void PatchBrowser::sortOrderChanged(int newSortColumnId, bool isForwards) -{ - if (newSortColumnId != 0) - { - PatchBrowserSorter sorter (newSortColumnId, isForwards); - m_filteredPatches.sort(sorter); - m_patchList.updateContent(); - } -} - -void PatchBrowser::splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src) -{ - for(size_t i=0; i<_src.size(); ++i) - { - if(_src[i] != 0xf0) - continue; - - for(size_t j=i+1; j < _src.size(); ++j) - { - if(_src[j] != 0xf7) - continue; - - std::vector<uint8_t> entry; - entry.insert(entry.begin(), _src.begin() + i, _src.begin() + j + 1); - - _dst.emplace_back(entry); - - i = j; - break; - } - } -} diff --git a/source/jucePlugin/ui/Virus_PatchBrowser.h b/source/jucePlugin/ui/Virus_PatchBrowser.h @@ -1,81 +0,0 @@ -#pragma once - -#include "../PluginProcessor.h" -#include "Virus_Buttons.h" -#include <juce_gui_extra/juce_gui_extra.h> -#include "../VirusController.h" -class VirusParameterBinding; - -const juce::Array<juce::String> ModelList = {"A","B","C","TI"}; -struct Patch -{ - int progNumber; - juce::String name; - uint8_t category1; - uint8_t category2; - std::vector<uint8_t> data; - std::vector<uint8_t> sysex; - virusLib::VirusModel model; - uint8_t unison; - uint8_t transpose; -}; - -class PatchBrowser : public juce::Component, juce::FileBrowserListener, juce::TableListBoxModel -{ - -public: - PatchBrowser(VirusParameterBinding &_parameterBinding, Virus::Controller& _controller); - - static uint32_t load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets); - static bool load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data); - static uint32_t loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const juce::File& file); - - static void splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src); - -private: - VirusParameterBinding &m_parameterBinding; - Virus::Controller& m_controller; - template <typename T> static juce::String parseAsciiText(const T &msg, const int start) - { - char text[Virus::Controller::kNameLength + 1]; - text[Virus::Controller::kNameLength] = 0; // termination - for (auto pos = 0; pos < Virus::Controller::kNameLength; ++pos) - text[pos] = msg[start + pos]; - return juce::String(text); - } - juce::WildcardFileFilter m_fileFilter; - juce::FileBrowserComponent m_bankList; - juce::TextEditor m_search; - juce::TableListBox m_patchList; - juce::Array<Patch> m_patches; - juce::Array<Patch> m_filteredPatches; - juce::PropertiesFile *m_properties; - juce::HashMap<juce::String, bool> m_checksums; - // Inherited via FileBrowserListener - void selectionChanged() override; - void fileClicked(const juce::File &file, const juce::MouseEvent &e) override; - void fileDoubleClicked(const juce::File &file) override; - void browserRootChanged(const juce::File &newRoot) override; - - // Inherited via TableListBoxModel - virtual int getNumRows() override; - virtual void paintRowBackground(juce::Graphics &, int rowNumber, int width, int height, - bool rowIsSelected) override; - virtual void paintCell(juce::Graphics &, int rowNumber, int columnId, int width, int height, - bool rowIsSelected) override; - - virtual void selectedRowsChanged(int lastRowSelected) override; - virtual void cellDoubleClicked (int rowNumber, int columnId, const juce::MouseEvent &) override; - void sortOrderChanged(int newSortColumnId, bool isForwards) override; - class PatchBrowserSorter; - enum Columns { - INDEX = 1, - NAME = 2, - CAT1 = 3, - CAT2 = 4, - ARP = 5, - UNI = 6, - ST = 7, - VER = 8, - }; -}; diff --git a/source/jucePlugin/ui3/FxPage.cpp b/source/jucePlugin/ui3/FxPage.cpp @@ -0,0 +1,50 @@ +#include "FxPage.h" + +#include "VirusEditor.h" + +#include "../VirusController.h" + +namespace genericVirusUI +{ + FxPage::FxPage(VirusEditor& _editor) : m_editor(_editor) + { + m_reverbContainer = _editor.findComponent("ContainerReverb"); + m_delayContainer = _editor.findComponent("ContainerDelay"); + + const auto p = m_editor.getController().getParameter(Virus::Param_DelayReverbMode, 0); + + if (p) + { + p->getValueObject().addListener(this); + } + + updateReverbDelay(); + } + + FxPage::~FxPage() + { + const auto p = m_editor.getController().getParameter(Virus::Param_DelayReverbMode, 0); + if(p) + p->getValueObject().removeListener(this); + } + + void FxPage::valueChanged(juce::Value& value) + { + updateReverbDelay(); + } + + void FxPage::updateReverbDelay() const + { + const auto p = m_editor.getController().getParameter(Virus::Param_DelayReverbMode, 0); + + if (!p) + return; + + const auto value = static_cast<int>(p->getValueObject().getValueSource().getValue()); + + const bool isReverb = (value > 1 && value < 5); + + VirusEditor::setEnabled(*m_delayContainer, !isReverb); + VirusEditor::setEnabled(*m_reverbContainer, isReverb); + } +} diff --git a/source/jucePlugin/ui3/FxPage.h b/source/jucePlugin/ui3/FxPage.h @@ -0,0 +1,28 @@ +#pragma once + +#include <juce_audio_processors/juce_audio_processors.h> + +namespace juce +{ + class Component; +} + +namespace genericVirusUI +{ + class VirusEditor; + + class FxPage : public juce::Value::Listener + { + public: + explicit FxPage(VirusEditor& _editor); + ~FxPage() override; + + void valueChanged(juce::Value& value) override; + private: + void updateReverbDelay() const; + + VirusEditor& m_editor; + juce::Component* m_reverbContainer = nullptr; + juce::Component* m_delayContainer = nullptr; + }; +} diff --git a/source/jucePlugin/ui3/MidiPorts.cpp b/source/jucePlugin/ui3/MidiPorts.cpp @@ -0,0 +1,148 @@ +#include "MidiPorts.h" + +#include "VirusEditor.h" + +#include "../VirusController.h" +#include "../PluginProcessor.h" + +namespace genericVirusUI +{ + MidiPorts::MidiPorts(VirusEditor& _editor) : m_editor(_editor) + { + auto& processor = _editor.getProcessor(); + + { + const auto properties = _editor.getController().getConfig(); + + const auto midiIn = properties->getValue("midi_input", ""); + const auto midiOut = properties->getValue("midi_output", ""); + + if (!midiIn.isEmpty()) + processor.setMidiInput(midiIn); + + if (!midiOut.isEmpty()) + processor.setMidiOutput(midiOut); + } + + m_midiIn = _editor.findComponentT<juce::ComboBox>("MidiIn"); + m_midiOut = _editor.findComponentT<juce::ComboBox>("MidiOut"); + + m_midiIn->setTextWhenNoChoicesAvailable("-"); + + const auto midiInputs = juce::MidiInput::getAvailableDevices(); + + int inIndex = 0; + + m_midiIn->addItem("<none>", 1); + + for (int i = 0; i < midiInputs.size(); i++) + { + const auto input = midiInputs[i]; + + if (processor.getMidiInput() != nullptr && input.identifier == processor.getMidiInput()->getIdentifier()) + inIndex = i + 1; + + m_midiIn->addItem(input.name, i+2); + } + + m_midiIn->setSelectedItemIndex(inIndex, juce::dontSendNotification); + + m_midiIn->onChange = [this]() { updateMidiInput(m_midiIn->getSelectedItemIndex()); }; + + m_midiOut->setTextWhenNoChoicesAvailable("-"); + + const auto midiOutputs = juce::MidiOutput::getAvailableDevices(); + + auto outIndex = 0; + + m_midiOut->addItem("<none>", 1); + + for (int i = 0; i < midiOutputs.size(); i++) + { + const auto output = midiOutputs[i]; + if (processor.getMidiOutput() != nullptr && + output.identifier == processor.getMidiOutput()->getIdentifier()) + { + outIndex = i + 1; + } + m_midiOut->addItem(output.name, i+2); + } + + m_midiOut->setSelectedItemIndex(outIndex, juce::dontSendNotification); + + m_midiOut->onChange = [this]() { updateMidiOutput(m_midiOut->getSelectedItemIndex()); }; + + deviceManager = new juce::AudioDeviceManager(); + } + + MidiPorts::~MidiPorts() + { + delete deviceManager; + } + + void MidiPorts::updateMidiInput(int index) + { + const auto list = juce::MidiInput::getAvailableDevices(); + + const auto properties = m_editor.getController().getConfig(); + + if (index <= 0) + { + properties->setValue("midi_input", ""); + properties->save(); + m_lastInputIndex = 0; + m_midiIn->setSelectedItemIndex(index, juce::dontSendNotification); + return; + } + + index--; + + const auto newInput = list[index]; + + if (!deviceManager->isMidiInputDeviceEnabled(newInput.identifier)) + deviceManager->setMidiInputDeviceEnabled(newInput.identifier, true); + + if (!m_editor.getProcessor().setMidiInput(newInput.identifier)) + { + m_midiIn->setSelectedItemIndex(0, juce::dontSendNotification); + m_lastInputIndex = 0; + return; + } + + properties->setValue("midi_input", newInput.identifier); + properties->save(); + + m_midiIn->setSelectedItemIndex(index + 1, juce::dontSendNotification); + m_lastInputIndex = index; + } + + void MidiPorts::updateMidiOutput(int index) + { + const auto list = juce::MidiOutput::getAvailableDevices(); + + const auto properties = m_editor.getController().getConfig(); + + if (index == 0) + { + properties->setValue("midi_output", ""); + properties->save(); + m_midiOut->setSelectedItemIndex(index, juce::dontSendNotification); + m_lastOutputIndex = index; + m_editor.getProcessor().setMidiOutput(""); + return; + } + index--; + const auto newOutput = list[index]; + if (!m_editor.getProcessor().setMidiOutput(newOutput.identifier)) + { + m_midiOut->setSelectedItemIndex(0, juce::dontSendNotification); + m_lastOutputIndex = 0; + return; + } + properties->setValue("midi_output", newOutput.identifier); + properties->save(); + + m_midiOut->setSelectedItemIndex(index + 1, juce::dontSendNotification); + m_lastOutputIndex = index; + } +} diff --git a/source/jucePlugin/ui3/MidiPorts.h b/source/jucePlugin/ui3/MidiPorts.h @@ -0,0 +1,33 @@ +#pragma once + +namespace juce +{ + class AudioDeviceManager; + class ComboBox; +} + +namespace genericVirusUI +{ + class VirusEditor; + + class MidiPorts + { + public: + explicit MidiPorts(VirusEditor& _editor); + ~MidiPorts(); + + private: + VirusEditor& m_editor; + + juce::ComboBox* m_midiIn = nullptr; + juce::ComboBox* m_midiOut = nullptr; + + juce::AudioDeviceManager* deviceManager = nullptr; + int m_lastInputIndex = 0; + int m_lastOutputIndex = 0; + + void updateMidiInput(int _index); + void updateMidiOutput(int _index); + + }; +} diff --git a/source/jucePlugin/ui3/Parts.cpp b/source/jucePlugin/ui3/Parts.cpp @@ -0,0 +1,168 @@ +#include "Parts.h" + +#include "VirusEditor.h" +#include "../VirusController.h" +#include "../VirusParameterBinding.h" +#include "dsp56kEmu/logging.h" + +namespace genericVirusUI +{ + Parts::Parts(VirusEditor& _editor) : m_editor(_editor) + { + _editor.findComponents<juce::Button>(m_partSelect, "SelectPart"); + _editor.findComponents<juce::Button>(m_presetPrev, "PresetPrev"); + _editor.findComponents<juce::Button>(m_presetNext, "PresetNext"); + + _editor.findComponents<juce::Slider>(m_partVolume, "PartVolume"); + _editor.findComponents<juce::Slider>(m_partPan, "PartPan"); + _editor.findComponents<juce::TextButton>(m_presetName, "PresetName"); + + for(size_t i=0; i<m_partSelect.size(); ++i) + { + m_partSelect[i]->onClick = [this, i]{ selectPart(i); }; + + if(i < m_presetPrev.size()) + m_presetPrev[i]->onClick = [this, i]{ selectPrevPreset(i); }; + + if(i < m_presetNext.size()) + m_presetNext[i]->onClick = [this, i]{ selectNextPreset(i); }; + + m_presetName[i]->onClick = [this, i]{ selectPreset(i); }; + + _editor.getParameterBinding().bind(*m_partVolume[i], Virus::Param_PartVolume, static_cast<uint8_t>(i)); + _editor.getParameterBinding().bind(*m_partPan[i], Virus::Param_Panorama, static_cast<uint8_t>(i)); + + m_partVolume[i]->getProperties().set("parameter", (int)Virus::Param_PartVolume); + m_partPan[i]->getProperties().set("parameter", (int)Virus::Param_Panorama); + } + + updateAll(); + } + + Parts::~Parts() = default; + + void Parts::onProgramChange() const + { + updateAll(); + } + + void Parts::onPlayModeChanged() const + { + updateAll(); + } + + void Parts::onCurrentPartChanged() const + { + updateSelectedPart(); + } + + void Parts::selectPart(const size_t _part) const + { + m_editor.setPart(_part); + } + + void Parts::selectPrevPreset(size_t _part) const + { + if(m_presetPrev.size() == 1) + _part = m_editor.getController().getCurrentPart(); + + Virus::Controller& controller = m_editor.getController(); + + const auto pt = static_cast<uint8_t>(_part); + + if(controller.getCurrentPartProgram(pt) > 0) + { + controller.setCurrentPartPreset(pt, controller.getCurrentPartBank(pt), controller.getCurrentPartProgram(pt) - 1); + } + } + + void Parts::selectNextPreset(size_t _part) const + { + if(m_presetNext.size() == 1) + _part = m_editor.getController().getCurrentPart(); + + Virus::Controller& controller = m_editor.getController(); + + const auto pt = static_cast<uint8_t>(_part); + if(controller.getCurrentPartProgram(pt) < 127) // FIXME: magic value + { + controller.setCurrentPartPreset(pt, controller.getCurrentPartBank(pt), controller.getCurrentPartProgram(pt) + 1); + } + } + + void Parts::selectPreset(size_t _part) const + { + juce::PopupMenu selector; + + const auto pt = static_cast<uint8_t>(_part); + + for (uint8_t b = 0; b < m_editor.getController().getBankCount(); ++b) + { + const auto bank = virusLib::fromArrayIndex(b); + auto presetNames = m_editor.getController().getSinglePresetNames(bank); + juce::PopupMenu p; + for (uint8_t j = 0; j < presetNames.size(); j++) + { + const auto presetName = presetNames[j]; + p.addItem(presetName, [this, bank, j, pt] + { + m_editor.getController().setCurrentPartPreset(pt, bank, j); + }); + } + std::stringstream bankName; + bankName << "Bank " << static_cast<char>('A' + b); + selector.addSubMenu(std::string(bankName.str()), p); + } + selector.showMenuAsync(juce::PopupMenu::Options()); + } + + void Parts::updatePresetNames() const + { + for(size_t i=0; i<m_presetName.size(); ++i) + m_presetName[i]->setButtonText(m_editor.getController().getCurrentPartPresetName(static_cast<uint8_t>(i))); + } + + void Parts::updateSelectedPart() const + { + const auto part = m_editor.getController().getCurrentPart(); + + if(part < m_partSelect.size()) + m_partSelect[part]->setToggleState(true, juce::dontSendNotification); + + for(size_t i=0; i<m_partSelect.size(); ++i) + { + if(i == part) + continue; + m_partSelect[i]->setToggleState(false, juce::dontSendNotification); + } + } + + void Parts::updateSingleOrMultiMode() const + { + const auto multiMode = m_editor.getController().isMultiMode(); + + for(size_t i=0; i<m_partSelect.size(); ++i) + { + const bool visible = multiMode || !i; + + VirusEditor::setEnabled(*m_partSelect[i], visible); + VirusEditor::setEnabled(*m_partPan[i], visible); + VirusEditor::setEnabled(*m_partVolume[i], visible); + + if(i < m_presetPrev.size()) + VirusEditor::setEnabled(*m_presetPrev[i], visible); + + if(i < m_presetNext.size()) + VirusEditor::setEnabled(*m_presetNext[i], visible); + + m_presetName[i]->setVisible(visible); + } + } + + void Parts::updateAll() const + { + updatePresetNames(); + updateSelectedPart(); + updateSingleOrMultiMode(); + } +} diff --git a/source/jucePlugin/ui3/Parts.h b/source/jucePlugin/ui3/Parts.h @@ -0,0 +1,43 @@ +#pragma once + +#include <vector> + +#include <juce_audio_processors/juce_audio_processors.h> + +namespace genericVirusUI +{ + class VirusEditor; + + class Parts + { + public: + explicit Parts(VirusEditor& _editor); + virtual ~Parts(); + + void onProgramChange() const; + void onPlayModeChanged() const; + void onCurrentPartChanged() const; + + private: + void selectPart(size_t _part) const; + void selectPrevPreset(size_t _part) const; + void selectNextPreset(size_t _part) const; + void selectPreset(size_t _part) const; + + void updatePresetNames() const; + void updateSelectedPart() const; + void updateAll() const; + void updateSingleOrMultiMode() const; + + VirusEditor& m_editor; + + std::vector<juce::Button*> m_partSelect; + std::vector<juce::Button*> m_presetPrev; + std::vector<juce::Button*> m_presetNext; + + std::vector<juce::Slider*> m_partVolume; + std::vector<juce::Slider*> m_partPan; + + std::vector<juce::TextButton*> m_presetName; + }; +} diff --git a/source/jucePlugin/ui3/PatchBrowser.cpp b/source/jucePlugin/ui3/PatchBrowser.cpp @@ -0,0 +1,462 @@ +#include "PatchBrowser.h" + +#include "VirusEditor.h" + +#include "../../virusLib/microcontrollerTypes.h" + +#include "../VirusController.h" +#include "juce_cryptography/hashing/juce_MD5.h" + +#include "../../synthLib/midiToSysex.h" + +using namespace juce; + +const juce::Array<juce::String> ModelList = {"A","B","C","TI"}; + +const Array<String> g_categories = { "", "Lead", "Bass", "Pad", "Decay", "Pluck", + "Acid", "Classic", "Arpeggiator", "Effects", "Drums", "Percussion", + "Input", "Vocoder", "Favourite 1", "Favourite 2", "Favourite 3" }; + +namespace genericVirusUI +{ + virusLib::VirusModel guessVersion(const uint8_t* _data) + { + const auto v = _data[0]; + + if (v < 5) + return virusLib::A; + if (v == 6) + return virusLib::B; + if (v == 7) + return virusLib::C; + return virusLib::TI; + } + + static juce::String parseAsciiText(const std::vector<uint8_t>& msg, const int start) + { + char text[Virus::Controller::kNameLength + 1]; + text[Virus::Controller::kNameLength] = 0; // termination + for (int pos = 0; pos < Virus::Controller::kNameLength; ++pos) + text[pos] = static_cast<char>(msg[start + pos]); + return {text}; + } + + static void initializePatch(Patch& _patch) + { + _patch.name = parseAsciiText(_patch.data, 128 + 112); + _patch.category1 = _patch.data[251]; + _patch.category2 = _patch.data[252]; + _patch.unison = _patch.data[97]; + _patch.transpose = _patch.data[93]; + _patch.model = guessVersion(&_patch.data[0]); + } + + PatchBrowser::PatchBrowser(const VirusEditor& _editor) : m_editor(_editor), m_controller(_editor.getController()), + m_fileFilter("*.syx;*.mid;*.midi", "*", "Virus Patch Dumps"), + m_bankList(FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles, File::getSpecialLocation(File::SpecialLocationType::currentApplicationFile), &m_fileFilter, nullptr), + m_search("Search Box"), + m_patchList("Patch Browser"), + m_properties(m_controller.getConfig()) + { + const auto bankDir = m_properties->getValue("virus_bank_dir", ""); + + if (bankDir.isNotEmpty() && File(bankDir).isDirectory()) + m_bankList.setRoot(bankDir); + + m_patchList.getHeader().addColumn("#", Columns::INDEX, 32); + m_patchList.getHeader().addColumn("Name", Columns::NAME, 130); + m_patchList.getHeader().addColumn("Category1", Columns::CAT1, 84); + m_patchList.getHeader().addColumn("Category2", Columns::CAT2, 84); + m_patchList.getHeader().addColumn("Arp", Columns::ARP, 32); + m_patchList.getHeader().addColumn("Uni", Columns::UNI, 32); + m_patchList.getHeader().addColumn("ST+-", Columns::ST, 32); + m_patchList.getHeader().addColumn("Ver", Columns::VER, 32); + + fitInParent(m_bankList, "ContainerFileSelector"); + fitInParent(m_patchList, "ContainerPatchList"); + + m_search.setColour(TextEditor::textColourId, Colours::white); + m_search.onTextChange = [this] + { + m_filteredPatches.clear(); + for (const auto& patch : m_patches) + { + const auto searchValue = m_search.getText(); + if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue)) + m_filteredPatches.add(patch); + } + m_patchList.updateContent(); + m_patchList.deselectAllRows(); + m_patchList.repaint(); + }; + m_search.setTextToShowWhenEmpty("Search...", Colours::grey); + + fitInParent(m_search, "ContainerPatchListSearchBox"); + + m_bankList.addListener(this); + m_patchList.setModel(this); + + m_romBankSelect = _editor.findComponentT<juce::ComboBox>("RomBankSelect", false); + + if(m_romBankSelect) + { + int id=1; + + m_romBankSelect->addItem("-", 1); + + for(uint32_t i=0; i<m_editor.getController().getBankCount(); ++i) + { + std::stringstream ss; + ss << "Bank " << static_cast<char>('A' + i); + m_romBankSelect->addItem(ss.str(), ++id); + } + + m_romBankSelect->onChange = [this] + { + const auto index = m_romBankSelect->getSelectedItemIndex(); + if(index > 0) + loadRomBank(index - 1); + }; + } + } + + void PatchBrowser::fitInParent(juce::Component& _component, const std::string& _parentName) const + { + auto* parent = m_editor.findComponent(_parentName); + + _component.setTransform(juce::AffineTransform::scale(2.0f)); + + const auto& bounds = parent->getBounds(); + const auto w = bounds.getWidth() >> 1; + const auto h = bounds.getHeight() >> 1; + + _component.setBounds(0,0, w,h); + + parent->addAndMakeVisible(_component); + } + + void PatchBrowser::selectionChanged() {} + + uint32_t PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets) + { + uint32_t count = 0; + for (const auto& packet : _packets) + { + if (load(_result, _dedupeChecksums, packet)) + ++count; + } + return count; + } + + bool PatchBrowser::load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data) + { + if (_data.size() < 267) + return false; + + auto* it = &_data.front(); + + if (*it == (uint8_t)0xf0 + && *(it + 1) == (uint8_t)0x00 + && *(it + 2) == (uint8_t)0x20 + && *(it + 3) == (uint8_t)0x33 + && *(it + 4) == (uint8_t)0x01 + && *(it + 6) == (uint8_t)virusLib::DUMP_SINGLE) + { + Patch patch; + patch.progNumber = static_cast<int>(_result.size()); + patch.sysex = _data; + patch.data.insert(patch.data.begin(), _data.begin() + 9, _data.end()); + initializePatch(patch); + + if (!_dedupeChecksums) + { + _result.push_back(patch); + } + else + { + const auto md5 = std::string(MD5(it + 9 + 17, 256 - 17 - 3).toHexString().toRawUTF8()); + + if (_dedupeChecksums->find(md5) == _dedupeChecksums->end()) + { + _dedupeChecksums->insert(md5); + _result.push_back(patch); + } + } + + return true; + } + return false; + } + + uint32_t PatchBrowser::loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const File& file) + { + const auto ext = file.getFileExtension().toLowerCase(); + const auto path = file.getParentDirectory().getFullPathName(); + + if (ext == ".syx") + { + MemoryBlock data; + + if (!file.loadFileAsData(data)) + return 0; + + std::vector<uint8_t> d; + d.resize(data.getSize()); + memcpy(&d[0], data.getData(), data.getSize()); + + std::vector<std::vector<uint8_t>> packets; + synthLib::MidiToSysex::splitMultipleSysex(packets, d); + + return load(_result, _dedupeChecksums, packets); + } + + if (ext == ".mid" || ext == ".midi") + { + std::vector<uint8_t> data; + + synthLib::MidiToSysex::readFile(data, file.getFullPathName().getCharPointer()); + + if (data.empty()) + return 0; + + std::vector<std::vector<uint8_t>> packets; + synthLib::MidiToSysex::splitMultipleSysex(packets, data); + + return load(_result, _dedupeChecksums, packets); + } + + return 0; + } + + void PatchBrowser::fileClicked(const File& file, const MouseEvent& e) + { + const auto ext = file.getFileExtension().toLowerCase(); + const auto path = file.getParentDirectory().getFullPathName(); + if (file.isDirectory() && e.mods.isPopupMenu()) + { + auto p = PopupMenu(); + p.addItem("Add directory contents to patch list", [this, file]() + { + m_patches.clear(); + m_checksums.clear(); + std::set<std::string> dedupeChecksums; + + std::vector<Patch> patches; + + for (const auto& f : RangedDirectoryIterator(file, false, "*.syx;*.mid;*.midi", File::findFiles)) + loadBankFile(patches, &dedupeChecksums, f.getFile()); + + m_filteredPatches.clear(); + + for (const auto& patch : patches) + { + const auto searchValue = m_search.getText(); + + m_patches.add(patch); + + if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue)) + m_filteredPatches.add(patch); + } + m_patchList.updateContent(); + m_patchList.deselectAllRows(); + m_patchList.repaint(); + }); + p.showMenuAsync(PopupMenu::Options()); + + return; + } + m_properties->setValue("virus_bank_dir", path); + + if (file.existsAsFile() && ext == ".syx" || ext == ".midi" || ext == ".mid") + { + m_patches.clear(); + std::vector<Patch> patches; + loadBankFile(patches, nullptr, file); + m_filteredPatches.clear(); + for (const auto& patch : patches) + { + const auto searchValue = m_search.getText(); + m_patches.add(patch); + if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue)) + m_filteredPatches.add(patch); + } + m_patchList.updateContent(); + m_patchList.deselectAllRows(); + m_patchList.repaint(); + } + + if(m_romBankSelect) + m_romBankSelect->setSelectedItemIndex(0); + } + + int PatchBrowser::getNumRows() { return m_filteredPatches.size(); } + + void PatchBrowser::paintRowBackground(Graphics& g, int rowNumber, int width, int height, bool rowIsSelected) + { + const auto alternateColour = m_patchList.getLookAndFeel() + .findColour(ListBox::backgroundColourId) + .interpolatedWith(m_patchList.getLookAndFeel().findColour(ListBox::textColourId), 0.03f); + if (rowIsSelected) + g.fillAll(Colours::lightblue); + else if (rowNumber & 1) + g.fillAll(alternateColour); + } + + void PatchBrowser::paintCell(Graphics& g, int rowNumber, int columnId, int width, int height, bool rowIsSelected) { + g.setColour(rowIsSelected ? Colours::darkblue + : m_patchList.getLookAndFeel().findColour(ListBox::textColourId)); // [5] + + if (rowNumber >= getNumRows()) + return; // Juce what are you up to? + + const auto rowElement = m_filteredPatches[rowNumber]; + //auto text = rowElement.name; + String text = ""; + if (columnId == Columns::INDEX) + text = String(rowElement.progNumber); + else if (columnId == Columns::NAME) + text = rowElement.name; + else if (columnId == Columns::CAT1) + text = g_categories[rowElement.category1]; + else if (columnId == Columns::CAT2) + text = g_categories[rowElement.category2]; + else if (columnId == Columns::ARP) + text = rowElement.data[129] != 0 ? "Y" : " "; + else if (columnId == Columns::UNI) + text = rowElement.unison == 0 ? " " : String(rowElement.unison + 1); + else if (columnId == Columns::ST) + text = rowElement.transpose != 64 ? String(rowElement.transpose - 64) : " "; + else if (columnId == Columns::VER) { + if (rowElement.model < ModelList.size()) + text = ModelList[rowElement.model]; + } + g.drawText(text, 2, 0, width - 4, height, Justification::centredLeft, true); // [6] + g.setColour(m_patchList.getLookAndFeel().findColour(ListBox::backgroundColourId)); + g.fillRect(width - 1, 0, 1, height); // [7] + } + + void PatchBrowser::selectedRowsChanged(int lastRowSelected) + { + const auto idx = m_patchList.getSelectedRow(); + + if (idx == -1) + return; + + // force to edit buffer + const auto part = m_controller.isMultiMode() ? m_controller.getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE); + + auto sysex = m_filteredPatches[idx].sysex; + sysex[7] = toMidiByte(virusLib::BankNumber::EditBuffer); + sysex[8] = part; + + m_controller.sendSysEx(sysex); + + m_controller.sendSysEx(m_controller.constructMessage({ virusLib::REQUEST_SINGLE, 0x0, part })); + } + + void PatchBrowser::cellDoubleClicked(int rowNumber, int columnId, const MouseEvent&) + { + if (rowNumber == m_patchList.getSelectedRow()) + selectedRowsChanged(0); + } + + class PatchBrowser::PatchBrowserSorter + { + public: + PatchBrowserSorter(const int attributeToSortBy, const bool forwards) + : m_attributeToSort(attributeToSortBy), + m_direction(forwards ? 1 : -1) + {} + + int compareElements(const Patch& first, const Patch& second) const + { + if (m_attributeToSort == Columns::INDEX) + return m_direction * (first.progNumber - second.progNumber); + if (m_attributeToSort == Columns::NAME) + return m_direction * first.name.compareIgnoreCase(second.name); + if (m_attributeToSort == Columns::CAT1) + return m_direction * (first.category1 - second.category1); + if (m_attributeToSort == Columns::CAT2) + return m_direction * (first.category2 - second.category2); + if (m_attributeToSort == Columns::ARP) + return m_direction * (first.data[129] - second.data[129]); + if (m_attributeToSort == Columns::UNI) + return m_direction * (first.unison - second.unison); + if (m_attributeToSort == Columns::VER) + return m_direction * (first.model - second.model); + if (m_attributeToSort == Columns::ST) + return m_direction * (first.transpose - second.transpose); + return m_direction * (first.progNumber - second.progNumber); + } + + private: + const int m_attributeToSort; + const int m_direction; + }; + + void PatchBrowser::sortOrderChanged(int newSortColumnId, bool isForwards) + { + if (newSortColumnId != 0) + { + PatchBrowserSorter sorter(newSortColumnId, isForwards); + m_filteredPatches.sort(sorter); + m_patchList.updateContent(); + } + } + + void PatchBrowser::loadRomBank(uint32_t _bankIndex) + { + const auto& singles = m_editor.getController().getSinglePresets(); + + if(_bankIndex >= singles.size()) + return; + + const auto& bank = singles[_bankIndex]; + + const auto searchValue = m_search.getText(); + + m_patches.clear(); + m_filteredPatches.clear(); + + for(size_t s=0; s<bank.size(); ++s) + { + Patch patch; + + patch.progNumber = static_cast<int>(s); + patch.data.insert(patch.data.begin(), bank[s].data.begin(), bank[s].data.end()); + + initializePatch(patch); + + // build sysex message + + patch.sysex.push_back(0xf0); + patch.sysex.push_back(0x00); // Manufacturer + patch.sysex.push_back(0x20); + patch.sysex.push_back(0x33); + patch.sysex.push_back(0x01); // Product Id = Virus + patch.sysex.push_back(m_editor.getController().getDeviceId()); + patch.sysex.push_back(virusLib::DUMP_SINGLE); + patch.sysex.push_back(static_cast<uint8_t>(_bankIndex)); // bank + patch.sysex.push_back(static_cast<uint8_t>(patch.progNumber)); + patch.sysex.insert(patch.sysex.end(), patch.data.begin(), patch.data.end()); + + // checksum + uint8_t cs = 0; + + for(size_t i=5; i<patch.sysex.size(); ++i) + cs += patch.sysex[i]; + + patch.sysex.push_back(cs & 0x7f); + patch.sysex.push_back(0xf7); + + m_patches.add(patch); + + if (searchValue.isEmpty() || patch.name.containsIgnoreCase(searchValue)) + m_filteredPatches.add(patch); + } + + m_patchList.updateContent(); + m_patchList.deselectAllRows(); + m_patchList.repaint(); + } +} diff --git a/source/jucePlugin/ui3/PatchBrowser.h b/source/jucePlugin/ui3/PatchBrowser.h @@ -0,0 +1,92 @@ +#pragma once + +#include <set> + +#include <juce_audio_processors/juce_audio_processors.h> + +namespace Virus +{ + class Controller; +} + +namespace virusLib +{ + enum VirusModel : uint8_t; +} + +namespace genericVirusUI +{ + class VirusEditor; + + struct Patch + { + int progNumber = 0; + juce::String name; + uint8_t category1 = 0; + uint8_t category2 = 0; + std::vector<uint8_t> data; + std::vector<uint8_t> sysex; + virusLib::VirusModel model; + uint8_t unison = 0; + uint8_t transpose = 0; + }; + + class PatchBrowser : public juce::FileBrowserListener, juce::TableListBoxModel + { + public: + explicit PatchBrowser(const VirusEditor& _editor); + + static uint32_t load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<std::vector<uint8_t>>& _packets); + static bool load(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const std::vector<uint8_t>& _data); + static uint32_t loadBankFile(std::vector<Patch>& _result, std::set<std::string>* _dedupeChecksums, const juce::File& file); + + private: + juce::FileBrowserComponent& getBankList() {return m_bankList; } + juce::TableListBox& getPatchList() {return m_patchList; } + juce::TextEditor& getSearchBox() {return m_search; } + + void fitInParent(juce::Component& _component, const std::string& _parentName) const; + + // Inherited via FileBrowserListener + void selectionChanged() override; + void fileClicked(const juce::File &file, const juce::MouseEvent &e) override; + void fileDoubleClicked(const juce::File &file) override {} + void browserRootChanged(const juce::File &newRoot) override {} + + // Inherited via TableListBoxModel + int getNumRows() override; + void paintRowBackground(juce::Graphics &, int rowNumber, int width, int height, bool rowIsSelected) override; + void paintCell(juce::Graphics &, int rowNumber, int columnId, int width, int height, bool rowIsSelected) override; + void selectedRowsChanged(int lastRowSelected) override; + void cellDoubleClicked (int rowNumber, int columnId, const juce::MouseEvent &) override; + void sortOrderChanged(int newSortColumnId, bool isForwards) override; + + void loadRomBank(uint32_t _bankIndex); + + class PatchBrowserSorter; + + enum Columns + { + INDEX = 1, + NAME = 2, + CAT1 = 3, + CAT2 = 4, + ARP = 5, + UNI = 6, + ST = 7, + VER = 8, + }; + + const VirusEditor& m_editor; + Virus::Controller& m_controller; + juce::WildcardFileFilter m_fileFilter; + juce::FileBrowserComponent m_bankList; + juce::TextEditor m_search; + juce::TableListBox m_patchList; + juce::Array<Patch> m_patches; + juce::Array<Patch> m_filteredPatches; + juce::PropertiesFile *m_properties; + juce::ComboBox* m_romBankSelect; + juce::HashMap<juce::String, bool> m_checksums; + }; +} diff --git a/source/jucePlugin/ui3/Tabs.cpp b/source/jucePlugin/ui3/Tabs.cpp @@ -0,0 +1,38 @@ +#include "Tabs.h" + +#include "VirusEditor.h" + +namespace genericVirusUI +{ + Tabs::Tabs(VirusEditor& _editor): m_editor(_editor) + { + m_tabs.push_back(m_editor.findComponent("page_osc")); + m_tabs.push_back(m_editor.findComponent("page_lfo")); + m_tabs.push_back(m_editor.findComponent("page_fx")); + m_tabs.push_back(m_editor.findComponent("page_arp")); + m_tabs.push_back(m_editor.findComponent("page_presets")); + + m_tabButtons.push_back(m_editor.findComponentT<juce::Button>("TabOsc")); + m_tabButtons.push_back(m_editor.findComponentT<juce::Button>("TabLfo")); + m_tabButtons.push_back(m_editor.findComponentT<juce::Button>("TabEffects")); + m_tabButtons.push_back(m_editor.findComponentT<juce::Button>("TabArp")); + m_tabButtons.push_back(m_editor.findComponentT<juce::Button>("Presets")); + + if(m_tabs.size() != m_tabButtons.size()) + throw std::runtime_error("Number of tabs does not match number of tab buttons, not all requested objects have been found"); + + for(size_t i=0; i<m_tabButtons.size(); ++i) + m_tabButtons[i]->onClick = [this, i] { setPage(i); }; + + setPage(0); + } + + void Tabs::setPage(const size_t _page) const + { + for(size_t i=0; i<m_tabs.size(); ++i) + { + m_tabs[i]->setVisible(_page == i); + m_tabButtons[i]->setToggleState(_page == i, juce::dontSendNotification); + } + } +} diff --git a/source/jucePlugin/ui3/Tabs.h b/source/jucePlugin/ui3/Tabs.h @@ -0,0 +1,27 @@ +#pragma once + +#include <vector> + +namespace juce +{ + class Button; + class Component; +} + +namespace genericVirusUI +{ + class VirusEditor; + + class Tabs + { + public: + explicit Tabs(VirusEditor& _editor); + private: + void setPage(size_t _page) const; + + VirusEditor& m_editor; + + std::vector<juce::Component*> m_tabs; + std::vector<juce::Button*> m_tabButtons; + }; +} diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp @@ -0,0 +1,484 @@ +#include "VirusEditor.h" + +#include "BinaryData.h" + +#include "../PluginProcessor.h" +#include "../VirusController.h" +#include "../VirusParameterBinding.h" +#include "../version.h" + +#include "../../synthLib/os.h" + +namespace genericVirusUI +{ + VirusEditor::VirusEditor(VirusParameterBinding& _binding, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, const std::string& _skinFolder, std::function<void()> _openMenuCallback) : + Editor(static_cast<EditorInterface&>(*this)), + m_processor(_processorRef), + m_parameterBinding(_binding), + m_skinFolder(_skinFolder), + m_openMenuCallback(std::move(_openMenuCallback)) + { + create(_jsonFilename); + + m_parts.reset(new Parts(*this)); + m_tabs.reset(new Tabs(*this)); + m_midiPorts.reset(new MidiPorts(*this)); + m_fxPage.reset(new FxPage(*this)); + m_patchBrowser.reset(new PatchBrowser(*this)); + + m_presetName = findComponentT<juce::Label>("PatchName"); + m_focusedParameterName = findComponentT<juce::Label>("FocusedParameterName"); + m_focusedParameterValue = findComponentT<juce::Label>("FocusedParameterValue"); + m_focusedParameterTooltip = findComponentT<juce::Label>("FocusedParameterTooltip", false); + + if(m_focusedParameterTooltip) + m_focusedParameterTooltip->setVisible(false); + + m_romSelector = findComponentT<juce::ComboBox>("RomSelector"); + + m_playModeSingle = findComponentT<juce::Button>("PlayModeSingle", false); + m_playModeMulti = findComponentT<juce::Button>("PlayModeMulti", false); + + if(m_playModeSingle && m_playModeMulti) + { + m_playModeSingle->onClick = [this]{ setPlayMode(virusLib::PlayMode::PlayModeSingle); }; + m_playModeMulti->onClick = [this]{ setPlayMode(virusLib::PlayMode::PlayModeMulti); }; + } + else + { + m_playModeToggle = findComponentT<juce::Button>("PlayModeToggle"); + m_playModeToggle->onClick = [this]{ setPlayMode(m_playModeToggle->getToggleState() ? virusLib::PlayMode::PlayModeMulti : virusLib::PlayMode::PlayModeSingle); }; + } + + updatePlayModeButtons(); + + if(m_romSelector) + { + if(!_processorRef.isPluginValid()) + m_romSelector->addItem("<No ROM found>", 1); + else + m_romSelector->addItem(_processorRef.getRomName(), 1); + + m_romSelector->setSelectedId(1, juce::dontSendNotification); + } + + getController().onProgramChange = [this] { onProgramChange(); }; + + addMouseListener(this, true); + + m_focusedParameterName->setVisible(false); + m_focusedParameterValue->setVisible(false); + + if(auto* versionInfo = findComponentT<juce::Label>("VersionInfo", false)) + { + const std::string message = "DSP 56300 Emulator Version " + std::string(g_pluginVersionString) + " - " __DATE__ " " __TIME__; + versionInfo->setText(message, juce::dontSendNotification); + } + + if(auto* versionNumber = findComponentT<juce::Label>("VersionNumber", false)) + { + versionNumber->setText(g_pluginVersionString, juce::dontSendNotification); + } + + if(auto* deviceModel = findComponentT<juce::Label>("DeviceModel", false)) + { + std::string m; + switch(getController().getVirusModel()) + { + case virusLib::A: m = "A"; break; + case virusLib::B: m = "B"; break; + case virusLib::C: m = "C"; break; + case virusLib::TI: m = "TI"; break; + } + deviceModel->setText(m, juce::dontSendNotification); + } + + auto* presetSave = findComponentT<juce::Button>("PresetSave", false); + if(presetSave) + presetSave->onClick = [this] { savePreset(); }; + + auto* presetLoad = findComponentT<juce::Button>("PresetLoad", false); + if(presetLoad) + presetLoad->onClick = [this] { loadPreset(); }; + + m_presetName->setEditable(false, true, true); + m_presetName->onTextChange = [this]() + { + const auto text = m_presetName->getText(); + if (text.trim().length() > 0) + { + getController().setSinglePresetName(getController().getCurrentPart(), text); + onProgramChange(); + } + }; + + auto* menuButton = findComponentT<juce::Button>("Menu", false); + + if(menuButton) + menuButton->onClick = m_openMenuCallback; + + updatePresetName(); + updatePlayModeButtons(); + updateControlLabel(nullptr); + } + + VirusEditor::~VirusEditor() + { + m_parameterBinding.clearBindings(); + + getController().onProgramChange = nullptr; + } + + Virus::Controller& VirusEditor::getController() const + { + return m_processor.getController(); + } + + void VirusEditor::setEnabled(juce::Component& _component, bool _enable) + { + if(_component.getProperties().contains("disabledAlpha")) + { + const float a = _component.getProperties()["disabledAlpha"]; + + _component.setAlpha(_enable ? 1.0f : a); + _component.setEnabled(_enable); + } + else + { + _component.setVisible(_enable); + } + } + + const char* VirusEditor::getResourceByFilename(const std::string& _name, uint32_t& _dataSize) + { + if(!m_skinFolder.empty()) + { + auto readFromCache = [this, &_name, &_dataSize]() + { + const auto it = m_fileCache.find(_name); + if(it == m_fileCache.end()) + { + _dataSize = 0; + return static_cast<char*>(nullptr); + } + _dataSize = static_cast<uint32_t>(it->second.size()); + return &it->second.front(); + }; + + auto* res = readFromCache(); + + if(res) + return res; + + const auto modulePath = synthLib::getModulePath(); + const auto folder = synthLib::validatePath(m_skinFolder.find(modulePath) == 0 ? m_skinFolder : modulePath + m_skinFolder); + + // try to load from disk first + FILE* hFile = fopen((folder + _name).c_str(), "rb"); + if(hFile) + { + fseek(hFile, 0, SEEK_END); + _dataSize = ftell(hFile); + fseek(hFile, 0, SEEK_SET); + + std::vector<char> data; + data.resize(_dataSize); + const auto readCount = fread(&data.front(), 1, _dataSize, hFile); + fclose(hFile); + + if(readCount == _dataSize) + m_fileCache.insert(std::make_pair(_name, std::move(data))); + + res = readFromCache(); + + if(res) + return res; + } + } + + for(size_t i=0; i<BinaryData::namedResourceListSize; ++i) + { + if (BinaryData::originalFilenames[i] != _name) + continue; + + int size = 0; + const auto res = BinaryData::getNamedResource(BinaryData::namedResourceList[i], size); + _dataSize = static_cast<uint32_t>(size); + return res; + } + + throw std::runtime_error("Failed to find file named " + _name); + } + + int VirusEditor::getParameterIndexByName(const std::string& _name) + { + return getController().getParameterTypeByName(_name); + } + + bool VirusEditor::bindParameter(juce::Button& _target, int _parameterIndex) + { + m_parameterBinding.bind(_target, static_cast<Virus::ParameterType>(_parameterIndex)); + return true; + } + + bool VirusEditor::bindParameter(juce::ComboBox& _target, int _parameterIndex) + { + m_parameterBinding.bind(_target, static_cast<Virus::ParameterType>(_parameterIndex)); + return true; + } + + bool VirusEditor::bindParameter(juce::Slider& _target, int _parameterIndex) + { + m_parameterBinding.bind(_target, static_cast<Virus::ParameterType>(_parameterIndex)); + return true; + } + + void VirusEditor::onProgramChange() + { + m_parts->onProgramChange(); + updatePresetName(); + } + + void VirusEditor::onPlayModeChanged() + { + m_parts->onPlayModeChanged(); + updatePresetName(); + updatePlayModeButtons(); + } + + void VirusEditor::onCurrentPartChanged() + { + m_parts->onCurrentPartChanged(); + updatePresetName(); + } + + void VirusEditor::mouseDrag(const juce::MouseEvent & event) + { + updateControlLabel(event.eventComponent); + } + + void VirusEditor::mouseEnter(const juce::MouseEvent& event) + { + if (event.mouseWasDraggedSinceMouseDown()) + return; + updateControlLabel(event.eventComponent); + } + void VirusEditor::mouseExit(const juce::MouseEvent& event) + { + if (event.mouseWasDraggedSinceMouseDown()) + return; + updateControlLabel(nullptr); + } + + void VirusEditor::mouseUp(const juce::MouseEvent& event) + { + if (event.mouseWasDraggedSinceMouseDown()) + return; + updateControlLabel(event.eventComponent); + } + + void VirusEditor::updateControlLabel(juce::Component* _component) const + { + if(_component) + { + // combo boxes report the child label as event source, try the parent in this case + if(!_component->getProperties().contains("parameter")) + _component = _component->getParentComponent(); + } + + if(!_component || !_component->getProperties().contains("parameter")) + { + m_focusedParameterName->setVisible(false); + m_focusedParameterValue->setVisible(false); + if(m_focusedParameterTooltip) + m_focusedParameterTooltip->setVisible(false); + return; + } + + const int v = _component->getProperties()["parameter"]; + + const auto* p = getController().getParameter(static_cast<Virus::ParameterType>(v)); + + if(!p) + { + m_focusedParameterName->setVisible(false); + m_focusedParameterValue->setVisible(false); + if(m_focusedParameterTooltip) + m_focusedParameterTooltip->setVisible(false); + return; + } + + const auto value = p->getText(p->getValue(), 0); + + const auto& desc = p->getDescription(); + + m_focusedParameterName->setText(desc.name, juce::dontSendNotification); + m_focusedParameterValue->setText(value, juce::dontSendNotification); + + m_focusedParameterName->setVisible(true); + m_focusedParameterValue->setVisible(true); + + if(m_focusedParameterTooltip && dynamic_cast<juce::Slider*>(_component)) + { + int x = _component->getX(); + int y = _component->getY(); + + // local to global + auto parent = _component->getParentComponent(); + + while(parent && parent != this) + { + x += parent->getX(); + y += parent->getY(); + parent = parent->getParentComponent(); + } + + x += (_component->getWidth()>>1) - (m_focusedParameterTooltip->getWidth()>>1); + y += _component->getHeight() + (m_focusedParameterTooltip->getHeight()>>1); + + // global to local of tooltip parent + parent = m_focusedParameterTooltip->getParentComponent(); + + while(parent && parent != this) + { + x -= parent->getX(); + y -= parent->getY(); + parent = parent->getParentComponent(); + } + + if(m_focusedParameterTooltip->getProperties().contains("offsetY")) + y += static_cast<int>(m_focusedParameterTooltip->getProperties()["offsetY"]); + + m_focusedParameterTooltip->setTopLeftPosition(x,y); + m_focusedParameterTooltip->setText(value, juce::dontSendNotification); + m_focusedParameterTooltip->setVisible(true); + m_focusedParameterTooltip->toFront(false); + } + } + + void VirusEditor::updatePresetName() const + { + m_presetName->setText(getController().getCurrentPartPresetName(getController().getCurrentPart()), juce::dontSendNotification); + } + + void VirusEditor::updatePlayModeButtons() const + { + if(m_playModeSingle) + m_playModeSingle->setToggleState(!getController().isMultiMode(), juce::dontSendNotification); + if(m_playModeMulti) + m_playModeMulti->setToggleState(getController().isMultiMode(), juce::dontSendNotification); + if(m_playModeToggle) + m_playModeToggle->setToggleState(getController().isMultiMode(), juce::dontSendNotification); + } + + void VirusEditor::savePreset() + { + const auto path = getController().getConfig()->getValue("virus_bank_dir", ""); + m_fileChooser = std::make_unique<juce::FileChooser>( + "Save preset as syx", + m_previousPath.isEmpty() + ? (path.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : juce::File(path)) + : m_previousPath, + "*.syx", true); + + constexpr auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles; + + auto onFileChooser = [this](const juce::FileChooser& chooser) + { + if (chooser.getResults().isEmpty()) + return; + + const auto result = chooser.getResult(); + m_previousPath = result.getParentDirectory().getFullPathName(); + const auto ext = result.getFileExtension().toLowerCase(); + const uint8_t syxHeader[9] = { 0xF0, 0x00, 0x20, 0x33, 0x01, 0x00, 0x10, 0x01, 0x00 }; + constexpr uint8_t syxEof[1] = { 0xF7 }; + uint8_t cs = syxHeader[5] + syxHeader[6] + syxHeader[7] + syxHeader[8]; + uint8_t data[256]; + for (int i = 0; i < 256; i++) + { + const auto param = getController().getParamValue(getController().getCurrentPart(), i < 128 ? 0 : 1, i & 127); + + data[i] = param ? static_cast<int>(param->getValue()) : 0; + cs += data[i]; + } + cs = cs & 0x7f; + + result.deleteFile(); + result.create(); + result.appendData(syxHeader, 9); + result.appendData(data, 256); + result.appendData(&cs, 1); + result.appendData(syxEof, 1); + }; + m_fileChooser->launchAsync(flags, onFileChooser); + } + + void VirusEditor::loadPreset() + { + m_fileChooser = std::make_unique<juce::FileChooser>( + "Choose syx/midi banks to import", + m_previousPath.isEmpty() + ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() + : m_previousPath, + "*.syx,*.mid,*.midi", true); + + constexpr auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles; + + const std::function onFileChooser = [this](const juce::FileChooser& chooser) + { + if (chooser.getResults().isEmpty()) + return; + + const auto result = chooser.getResult(); + m_previousPath = result.getParentDirectory().getFullPathName(); + const auto ext = result.getFileExtension().toLowerCase(); + + std::vector<Patch> patches; + PatchBrowser::loadBankFile(patches, nullptr, result); + + if (patches.empty()) + return; + + if (patches.size() == 1) + { + // load to edit buffer of current part + auto data = patches.front().sysex; + data[7] = virusLib::toMidiByte(virusLib::BankNumber::EditBuffer); + if (getController().isMultiMode()) + data[8] = getController().getCurrentPart(); + else + data[8] = virusLib::SINGLE; + getController().sendSysEx(data); + } + else + { + // load to bank A + for (const auto& p : patches) + { + auto data = p.sysex; + data[7] = virusLib::toMidiByte(virusLib::BankNumber::A); + getController().sendSysEx(data); + } + } + + getController().onStateLoaded(); + }; + m_fileChooser->launchAsync(flags, onFileChooser); + } + + void VirusEditor::setPlayMode(uint8_t _playMode) + { + getController().getParameter(Virus::Param_PlayMode)->setValue(_playMode); + if (_playMode == virusLib::PlayModeSingle && getController().getCurrentPart() != 0) + m_parameterBinding.setPart(0); + + onPlayModeChanged(); + } + + void VirusEditor::setPart(size_t _part) + { + m_parameterBinding.setPart(static_cast<uint8_t>(_part)); + onCurrentPartChanged(); + } +} diff --git a/source/jucePlugin/ui3/VirusEditor.h b/source/jucePlugin/ui3/VirusEditor.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../../juceUiLib/editor.h" + +#include "Parts.h" +#include "Tabs.h" +#include "FxPage.h" +#include "MidiPorts.h" +#include "PatchBrowser.h" + +class VirusParameterBinding; +class AudioPluginAudioProcessor; + +namespace genericVirusUI +{ + class VirusEditor : public genericUI::EditorInterface, public genericUI::Editor + { + public: + VirusEditor(VirusParameterBinding& _binding, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, const std::string& _skinFolder, std::function<void()> _openMenuCallback); + ~VirusEditor() override; + + void setPart(size_t _part); + + AudioPluginAudioProcessor& getProcessor() const { return m_processor; } + VirusParameterBinding& getParameterBinding() const { return m_parameterBinding; } + + Virus::Controller& getController() const; + + static void setEnabled(juce::Component& _component, bool _enable); + + private: + const char* getResourceByFilename(const std::string& _name, uint32_t& _dataSize) override; + int getParameterIndexByName(const std::string& _name) override; + bool bindParameter(juce::Button& _target, int _parameterIndex) override; + bool bindParameter(juce::ComboBox& _target, int _parameterIndex) override; + bool bindParameter(juce::Slider& _target, int _parameterIndex) override; + + void onProgramChange(); + void onPlayModeChanged(); + void onCurrentPartChanged(); + + void mouseDrag(const juce::MouseEvent& event) override; + void mouseEnter(const juce::MouseEvent& event) override; + void mouseExit(const juce::MouseEvent& event) override; + void mouseUp(const juce::MouseEvent& event) override; + + void updateControlLabel(juce::Component* _component) const; + void updatePresetName() const; + void updatePlayModeButtons() const; + + void savePreset(); + void loadPreset(); + + void setPlayMode(uint8_t _playMode); + + AudioPluginAudioProcessor& m_processor; + VirusParameterBinding& m_parameterBinding; + + const std::string m_skinFolder; + + std::unique_ptr<Parts> m_parts; + std::unique_ptr<Tabs> m_tabs; + std::unique_ptr<MidiPorts> m_midiPorts; + std::unique_ptr<FxPage> m_fxPage; + std::unique_ptr<PatchBrowser> m_patchBrowser; + + juce::Label* m_presetName = nullptr; + juce::Label* m_focusedParameterName = nullptr; + juce::Label* m_focusedParameterValue = nullptr; + juce::Label* m_focusedParameterTooltip = nullptr; + juce::ComboBox* m_romSelector = nullptr; + + juce::Button* m_playModeSingle = nullptr; + juce::Button* m_playModeMulti = nullptr; + juce::Button* m_playModeToggle = nullptr; + + juce::TooltipWindow m_tooltipWindow; + + std::unique_ptr<juce::FileChooser> m_fileChooser; + juce::String m_previousPath; + std::function<void()> m_openMenuCallback; + + std::map<std::string, std::vector<char>> m_fileCache; + }; +} diff --git a/source/juceUiLib/CMakeLists.txt b/source/juceUiLib/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.15) +project(juceUiLib VERSION ${CMAKE_PROJECT_VERSION}) + +set(SOURCES + editor.cpp editor.h + editorInterface.h + image.cpp image.h + rotaryStyle.cpp rotaryStyle.h + comboboxStyle.cpp comboboxStyle.h + buttonStyle.cpp buttonStyle.h + labelStyle.cpp labelStyle.h + textbuttonStyle.cpp textbuttonStyle.h + hyperlinkbuttonStyle.cpp hyperlinkbuttonStyle.h + uiObject.cpp uiObject.h + uiObjectStyle.cpp uiObjectStyle.h +) + +add_library(juceUiLib STATIC) + +target_sources(juceUiLib PRIVATE ${SOURCES}) +source_group("source" FILES ${SOURCES}) + +target_include_directories(juceUiLib PUBLIC ../JUCE/modules) +target_compile_definitions(juceUiLib PRIVATE JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1) diff --git a/source/juceUiLib/buttonStyle.cpp b/source/juceUiLib/buttonStyle.cpp @@ -0,0 +1,88 @@ +#include "buttonStyle.h" + +#include "uiObject.h" + +namespace genericUI +{ + void ButtonStyle::apply(Editor& _editor, const UiObject& _object) + { + UiObjectStyle::apply(_editor, _object); + + m_isToggle = _object.getPropertyInt("isToggle") != 0; + m_radioGroupId = _object.getPropertyInt("radioGroupId"); + + if(!m_drawable) + return; + + juce::Drawable** imageDrawables[] = { + &m_normalImage, + &m_overImage, + &m_downImage, + &m_disabledImage, + &m_normalImageOn, + &m_overImageOn, + &m_downImageOn, + &m_disabledImageOn + }; + + const char* properties[] = { + "normalImage", + "overImage", + "downImage", + "disabledImage", + "normalImageOn", + "overImageOn", + "downImageOn", + "disabledImageOn" + }; + + const bool isVerticalTiling = m_tileSizeY <= (m_drawable->getHeight()>>1); + + static_assert(std::size(imageDrawables) == std::size(properties), "arrays must have same size"); + + std::map<int, juce::Drawable*> drawables; + + for(size_t i=0; i<std::size(imageDrawables); ++i) + { + const auto imageIndex = _object.getPropertyInt(properties[i], -1); + + if(imageIndex < 0) + continue; + + const auto it = drawables.find(imageIndex); + + if(it != drawables.end()) + { + *imageDrawables[i] = it->second; + } + else + { + juce::Drawable* d = m_drawable; + if(imageIndex > 0) + { + m_createdDrawables.emplace_back(d->createCopy()); + d = m_createdDrawables.back().get(); + if(isVerticalTiling) + d->setOriginWithOriginalSize({0, static_cast<float>(-imageIndex * m_tileSizeY)}); + else + d->setOriginWithOriginalSize({static_cast<float>(-imageIndex * m_tileSizeX), 0.0f}); + } + *imageDrawables[i] = d; + drawables.insert(std::make_pair(imageIndex, d)); + } + } + } + + void ButtonStyle::apply(juce::DrawableButton& _button) const + { + _button.setColour(juce::DrawableButton::ColourIds::backgroundColourId, juce::Colours::transparentBlack); + _button.setColour(juce::DrawableButton::ColourIds::backgroundOnColourId, juce::Colours::transparentBlack); + + _button.setClickingTogglesState(m_isToggle); + + if(m_radioGroupId) + _button.setRadioGroupId(m_radioGroupId); + + _button.setImages(m_normalImage, m_overImage, m_downImage, m_disabledImage, m_normalImageOn, m_overImageOn, m_downImageOn, m_disabledImageOn); + } +} diff --git a/source/juceUiLib/buttonStyle.h b/source/juceUiLib/buttonStyle.h @@ -0,0 +1,30 @@ +#pragma once + +#include "uiObjectStyle.h" + +namespace genericUI +{ + class ButtonStyle : public UiObjectStyle + { + public: + explicit ButtonStyle(Editor& _editor) : UiObjectStyle(_editor) {} + + void apply(Editor& _editor, const UiObject& _object) override; + void apply(juce::DrawableButton& _button) const; + + private: + bool m_isToggle = false; + int m_radioGroupId = 0; + + juce::Drawable* m_normalImage = nullptr; + juce::Drawable* m_overImage = nullptr; + juce::Drawable* m_downImage = nullptr; + juce::Drawable* m_disabledImage = nullptr; + juce::Drawable* m_normalImageOn = nullptr; + juce::Drawable* m_overImageOn = nullptr; + juce::Drawable* m_downImageOn = nullptr; + juce::Drawable* m_disabledImageOn = nullptr; + + std::vector<std::unique_ptr<juce::Drawable>> m_createdDrawables; + }; +} diff --git a/source/juceUiLib/comboboxStyle.cpp b/source/juceUiLib/comboboxStyle.cpp @@ -0,0 +1,13 @@ +#include "comboboxStyle.h" + +namespace genericUI +{ + void ComboboxStyle::apply(juce::ComboBox& _target) const + { + _target.setColour(juce::ComboBox::ColourIds::textColourId, m_color); + } + + void ComboboxStyle::drawComboBox(juce::Graphics& _graphics, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox& _comboBox) + { + } +} diff --git a/source/juceUiLib/comboboxStyle.h b/source/juceUiLib/comboboxStyle.h @@ -0,0 +1,17 @@ +#pragma once + +#include "uiObjectStyle.h" + +namespace genericUI +{ + class ComboboxStyle : public UiObjectStyle + { + public: + explicit ComboboxStyle(Editor& _editor) : UiObjectStyle(_editor) {} + + void apply(juce::ComboBox& _target) const; + + private: + void drawComboBox(juce::Graphics&, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox&) override; + }; +} +\ No newline at end of file diff --git a/source/juceUiLib/editor.cpp b/source/juceUiLib/editor.cpp @@ -0,0 +1,193 @@ +#include "editor.h" + +#include "uiObject.h" + +#include "../synthLib/os.h" + +namespace genericUI +{ + Editor::Editor(EditorInterface& _interface) : m_interface(_interface) + { + } + + void Editor::create(const std::string& _jsonFilename) + { + uint32_t jsonSize; + const auto jsonData = m_interface.getResourceByFilename(_jsonFilename, jsonSize); + + juce::var json; + const auto error = juce::JSON::parse(juce::String(std::string(jsonData, jsonSize)), json); + + if (error.failed()) + throw std::runtime_error("Failed to load json"); + + m_jsonFilename = _jsonFilename; + + m_rootObject.reset(new UiObject(json)); + + std::set<std::string> textures; + m_rootObject->collectVariants(textures, "texture"); + + for (const auto& texture : textures) + { + const auto dataName = texture + ".png"; + + uint32_t dataSize; + const auto* data = m_interface.getResourceByFilename(dataName, dataSize); + if (!data) + throw std::runtime_error("Failed to find image named " + dataName); + auto drawable = juce::Drawable::createFromImageData(data, dataSize); + m_drawables.insert(std::make_pair(texture, std::move(drawable))); + } + + std::set<std::string> fonts; + m_rootObject->collectVariants(fonts, "fontFile"); + + for(const auto& fontFile : fonts) + { + const auto dataName = fontFile + ".ttf"; + + uint32_t dataSize; + const auto* data = m_interface.getResourceByFilename(dataName, dataSize); + if (!data) + throw std::runtime_error("Failed to find font named " + dataName); + auto font = juce::Font(juce::Typeface::createSystemTypefaceFor(data, dataSize)); + m_fonts.insert(std::make_pair(fontFile, std::move(font))); + } + + m_rootObject->createJuceTree(*this); + + m_scale = m_rootObject->getPropertyFloat("scale", 1.0f); + } + + std::string Editor::exportToFolder(const std::string& _folder) const + { + if(!m_rootObject) + return "Nothing to export"; + + synthLib::createDirectory(_folder); + + std::string subfolder = m_jsonFilename; + const auto dotIndex = m_jsonFilename.rfind('.'); + if(dotIndex != std::string::npos) + subfolder = subfolder.substr(0, dotIndex); + const auto underscoreIndex = m_jsonFilename.find('_'); + if(underscoreIndex != std::string::npos) + subfolder = subfolder.substr(underscoreIndex+1); + + const auto folder = _folder + subfolder + '/'; + + synthLib::createDirectory(folder); + + std::stringstream errors; + + auto writeFile = [this, &folder, &errors](const std::string& _name) + { + uint32_t dataSize; + const auto data = m_interface.getResourceByFilename(_name, dataSize); + + FILE* hFile = fopen((folder + _name).c_str(), "wb"); + + if(!hFile) + { + errors << "Failed to create file " << folder << _name << std::endl; + } + else + { + fwrite(data, dataSize, 1, hFile); + fclose(hFile); + } + }; + + auto writeData = [this, writeFile](const std::set<std::string>& _names, const std::string& _ext) + { + for (const auto& name : _names) + { + const auto dataName = name + _ext; + writeFile(dataName); + } + }; + + std::set<std::string> textures; + std::set<std::string> fonts; + + m_rootObject->collectVariants(textures, "texture"); + m_rootObject->collectVariants(fonts, "fontFile"); + + writeFile(m_jsonFilename); + + writeData(textures, ".png"); + writeData(fonts, ".ttf"); + + return errors.str(); + } + + juce::Drawable* Editor::getImageDrawable(const std::string& _texture) + { + const auto it = m_drawables.find(_texture); + return it == m_drawables.end() ? nullptr : it->second.get(); + } + + std::unique_ptr<juce::Drawable> Editor::createImageDrawable(const std::string& _texture) + { + const auto existing = getImageDrawable(_texture); + return existing ? existing->createCopy() : nullptr; + } + + const juce::Font& Editor::getFont(const std::string& _fontFile) + { + const auto it = m_fonts.find(_fontFile); + if(it == m_fonts.end()) + throw std::runtime_error("Unable to find font named " + _fontFile); + return it->second; + } + + void Editor::registerComponent(const std::string& _name, juce::Component* _component) + { + const auto itExisting = m_componentsByName.find(_name); + + if(itExisting != m_componentsByName.end()) + { + itExisting->second.push_back(_component); + } + else + { + m_componentsByName.insert(std::make_pair(_name, std::vector{_component})); + } + } + + const std::vector<juce::Component*>& Editor::findComponents(const std::string& _name, uint32_t _expectedCount/* = 0*/) const + { + const auto it = m_componentsByName.find(_name); + if(it != m_componentsByName.end()) + { + if(_expectedCount && it->second.size() != _expectedCount) + { + std::stringstream ss; + ss << "Expected to find " << _expectedCount << " components named " << _name << " but found " << it->second.size(); + throw std::runtime_error(ss.str()); + } + return it->second; + } + + if(_expectedCount) + { + std::stringstream ss; + ss << "Unable to find component named " << _name << ", expected to find " << _expectedCount << " components"; + throw std::runtime_error(ss.str()); + } + + static std::vector<juce::Component*> empty; + return empty; + } + + juce::Component* Editor::findComponent(const std::string& _name, bool _mustExist/* = true*/) const + { + const auto comps = findComponents(_name); + if(comps.size() > 1) + throw std::runtime_error("Failed to find unique component named " + _name + ", found more than one object with that name"); + if(_mustExist && comps.empty()) + throw std::runtime_error("Failed to find component named " + _name); + return comps.empty() ? nullptr : comps.front(); + } +} diff --git a/source/juceUiLib/editor.h b/source/juceUiLib/editor.h @@ -0,0 +1,71 @@ +#pragma once + +#include <string> + +#include <juce_audio_processors/juce_audio_processors.h> + +#include "uiObject.h" + +#include "editorInterface.h" + +namespace genericUI +{ + class Editor : public juce::Component + { + public: + explicit Editor(EditorInterface& _interface); + Editor(const Editor&) = delete; + Editor(Editor&&) = delete; + + void create(const std::string& _jsonFilename); + + std::string exportToFolder(const std::string& _folder) const; + + juce::Drawable* getImageDrawable(const std::string& _texture); + std::unique_ptr<juce::Drawable> createImageDrawable(const std::string& _texture); + const juce::Font& getFont(const std::string& _fontFile); + + void registerComponent(const std::string& _name, juce::Component* _component); + + EditorInterface& getInterface() const { return m_interface; } + + const std::vector<juce::Component*>& findComponents(const std::string& _name, uint32_t _expectedCount = 0) const; + + template<typename T> + void findComponents(std::vector<T*>& _dst, const std::string& _name, uint32_t _expectedCount = 0) const + { + const auto& res = findComponents(_name, _expectedCount); + for (auto* s : res) + { + auto* t = dynamic_cast<T*>(s); + if(t) + _dst.push_back(t); + } + } + + juce::Component* findComponent(const std::string& _name, bool _mustExist = true) const; + + template<typename T> + T* findComponentT(const std::string& _name, bool _mustExist = true) const + { + juce::Component* c = findComponent(_name, _mustExist); + return dynamic_cast<T*>(c); + } + + float getScale() const { return m_scale; } + + private: + EditorInterface& m_interface; + + std::string m_jsonFilename; + + std::map<std::string, std::unique_ptr<juce::Drawable>> m_drawables; + std::map<std::string, juce::Font> m_fonts; + + std::unique_ptr<UiObject> m_rootObject; + + std::map<std::string, std::vector<juce::Component*>> m_componentsByName; + + float m_scale = 1.0f; + }; +} diff --git a/source/juceUiLib/editorInterface.h b/source/juceUiLib/editorInterface.h @@ -0,0 +1,18 @@ +#pragma once + +namespace genericUI +{ + class EditorInterface + { + public: + virtual ~EditorInterface() = default; + + virtual const char* getResourceByFilename(const std::string& _name, uint32_t& _dataSize) = 0; + + virtual int getParameterIndexByName(const std::string& _name) = 0; + + virtual bool bindParameter(juce::Slider& _target, int _parameterIndex) = 0; + virtual bool bindParameter(juce::Button& _target, int _parameterIndex) = 0; + virtual bool bindParameter(juce::ComboBox& _target, int _parameterIndex) = 0; + }; +} diff --git a/source/juceUiLib/hyperlinkbuttonStyle.cpp b/source/juceUiLib/hyperlinkbuttonStyle.cpp @@ -0,0 +1,12 @@ +#include "hyperlinkbuttonStyle.h" + +namespace genericUI +{ + void HyperlinkButtonStyle::apply(juce::HyperlinkButton& _target) const + { + _target.setColour(juce::HyperlinkButton::ColourIds::textColourId, m_color); + _target.setURL(juce::URL(juce::String(m_url))); + + TextButtonStyle::apply(_target); + } +} diff --git a/source/juceUiLib/hyperlinkbuttonStyle.h b/source/juceUiLib/hyperlinkbuttonStyle.h @@ -0,0 +1,19 @@ +#pragma once + +#include "textbuttonStyle.h" + +namespace juce +{ + class HyperlinkButton; +} + +namespace genericUI +{ + class HyperlinkButtonStyle : public TextButtonStyle + { + public: + explicit HyperlinkButtonStyle(Editor& _editor) : TextButtonStyle(_editor) {} + + void apply(juce::HyperlinkButton& _target) const; + }; +} diff --git a/source/juceUiLib/image.cpp b/source/juceUiLib/image.cpp @@ -0,0 +1,5 @@ +#include "image.h" + +namespace genericUI +{ +} diff --git a/source/juceUiLib/image.h b/source/juceUiLib/image.h @@ -0,0 +1,10 @@ +#pragma once + +#include <juce_audio_processors/juce_audio_processors.h> + +namespace genericUI +{ + class Image : public juce::Drawable + { + }; +} diff --git a/source/juceUiLib/labelStyle.cpp b/source/juceUiLib/labelStyle.cpp @@ -0,0 +1,13 @@ +#include "labelStyle.h" + +namespace genericUI +{ + void LabelStyle::apply(juce::Label& _target) const + { + if(m_bgColor.getARGB()) + _target.setColour(juce::Label::ColourIds::backgroundColourId, m_bgColor); + _target.setColour(juce::Label::ColourIds::textColourId, m_color); + _target.setText(m_text, juce::dontSendNotification); + _target.setJustificationType(m_align); + } +} diff --git a/source/juceUiLib/labelStyle.h b/source/juceUiLib/labelStyle.h @@ -0,0 +1,14 @@ +#pragma once + +#include "uiObjectStyle.h" + +namespace genericUI +{ + class LabelStyle : public UiObjectStyle + { + public: + explicit LabelStyle(Editor& _editor) : UiObjectStyle(_editor) {} + + void apply(juce::Label& _target) const; + }; +} diff --git a/source/juceUiLib/rotaryStyle.cpp b/source/juceUiLib/rotaryStyle.cpp @@ -0,0 +1,23 @@ +#include "rotaryStyle.h" + +namespace genericUI +{ + void RotaryStyle::drawRotarySlider(juce::Graphics& _graphics, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& _slider) + { + if(!m_drawable || !m_tileSizeX || !m_tileSizeY) + return; + +// const auto w = m_drawable->getWidth(); + const auto h = m_drawable->getHeight(); + +// const auto stepsX = w / m_tileSizeX; + const auto stepsY = h / m_tileSizeY; + +// const auto stepX = juce::roundToInt(static_cast<float>(stepsX - 1) * sliderPosProportional); + const auto stepY = juce::roundToInt(static_cast<float>(stepsY - 1) * sliderPosProportional); + + m_drawable->setOriginWithOriginalSize({0.0f, static_cast<float>(-m_tileSizeY * stepY)}); + + m_drawable->drawAt(_graphics, static_cast<float>(x), static_cast<float>(y), 1.0f); + } +} diff --git a/source/juceUiLib/rotaryStyle.h b/source/juceUiLib/rotaryStyle.h @@ -0,0 +1,17 @@ +#pragma once + +#include <juce_audio_processors/juce_audio_processors.h> + +#include "uiObjectStyle.h" + +namespace genericUI +{ + class RotaryStyle : public UiObjectStyle + { + public: + explicit RotaryStyle(Editor& _editor) : UiObjectStyle(_editor) {} + + private: + void drawRotarySlider(juce::Graphics&, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider&) override; + }; +} diff --git a/source/juceUiLib/textbuttonStyle.cpp b/source/juceUiLib/textbuttonStyle.cpp @@ -0,0 +1,15 @@ +#include "textbuttonStyle.h" + +namespace genericUI +{ + void TextButtonStyle::drawButtonBackground(juce::Graphics& _graphics, juce::Button& _button, const juce::Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) + { + } + + void TextButtonStyle::apply(juce::Button& _target) const + { + _target.setColour(juce::TextButton::ColourIds::textColourOffId, m_color); + _target.setColour(juce::TextButton::ColourIds::textColourOnId, m_color); + _target.setButtonText(m_text); + } +} diff --git a/source/juceUiLib/textbuttonStyle.h b/source/juceUiLib/textbuttonStyle.h @@ -0,0 +1,17 @@ +#pragma once + +#include "uiObjectStyle.h" + +namespace genericUI +{ + class TextButtonStyle : public UiObjectStyle + { + public: + explicit TextButtonStyle(Editor& _editor) : UiObjectStyle(_editor) {} + + private: + void drawButtonBackground(juce::Graphics&, juce::Button&, const juce::Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override; + public: + void apply(juce::Button& _target) const; + }; +} diff --git a/source/juceUiLib/uiObject.cpp b/source/juceUiLib/uiObject.cpp @@ -0,0 +1,332 @@ +#include "uiObject.h" + +#include <juce_audio_processors/juce_audio_processors.h> + +#include <utility> + +#include "editor.h" + +#include "rotaryStyle.h" +#include "comboboxStyle.h" +#include "buttonStyle.h" +#include "textbuttonStyle.h" +#include "hyperlinkbuttonStyle.h" +#include "labelStyle.h" + +namespace genericUI +{ + UiObject::UiObject(const juce::var& _json) + { + auto* obj = _json.getDynamicObject(); + + parse(obj); + } + + UiObject::~UiObject() + { + m_juceObjects.clear(); + m_style.reset(); + } + + void UiObject::createJuceTree(Editor& _editor) const + { + apply(_editor, _editor); + + createChildObjects(_editor, _editor); + } + + void UiObject::createChildObjects(Editor& _editor, juce::Component& _parent) const + { + for (auto& ch : m_children) + { + auto* obj = ch->createJuceObject(_editor); + + if(!obj) + continue; + + _parent.addAndMakeVisible(obj); + + ch->createChildObjects(_editor, *obj); + } + } + + void UiObject::apply(Editor& _editor, juce::Component& _target) const + { + const auto x = getPropertyInt("x"); + const auto y = getPropertyInt("y"); + const auto w = getPropertyInt("width"); + const auto h = getPropertyInt("height"); + + if(w < 1 || h < 1) + { + std::stringstream ss; + ss << "Size " << w << "x" << h << " for object named " << m_name << " is invalid, each side must be > 0"; + throw std::runtime_error(ss.str()); + } + + _target.setTopLeftPosition(x, y); + _target.setSize(w, h); + } + + void UiObject::apply(Editor& _editor, juce::Slider& _target) + { + _target.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + _target.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); + + apply(_editor, static_cast<juce::Component&>(_target)); + + createStyle(_editor, _target, new RotaryStyle(_editor)); + + bindParameter(_editor, _target); + } + + void UiObject::apply(Editor& _editor, juce::ComboBox& _target) + { + apply(_editor, static_cast<juce::Component&>(_target)); + auto* s = new ComboboxStyle(_editor); + createStyle(_editor, _target, s); + bindParameter(_editor, _target); + s->apply(_target); + } + + void UiObject::apply(Editor& _editor, juce::DrawableButton& _target) + { + apply(_editor, static_cast<juce::Component&>(_target)); + auto* s = new ButtonStyle(_editor); + createStyle(_editor, _target, s); + s->apply(_target); + bindParameter(_editor, _target); + } + + void UiObject::apply(Editor& _editor, juce::Label& _target) + { + apply(_editor, static_cast<juce::Component&>(_target)); + auto* s = new LabelStyle(_editor); + createStyle(_editor, _target, s); + s->apply(_target); + } + + void UiObject::apply(Editor& _editor, juce::TextButton& _target) + { + apply(_editor, static_cast<juce::Component&>(_target)); + auto* s = new TextButtonStyle(_editor); + createStyle(_editor, _target, s); + s->apply(_target); + } + + void UiObject::apply(Editor& _editor, juce::HyperlinkButton& _target) + { + apply(_editor, static_cast<juce::Component&>(_target)); + auto* s = new HyperlinkButtonStyle(_editor); + createStyle(_editor, _target, s); + s->apply(_target); + } + + void UiObject::collectVariants(std::set<std::string>& _dst, const std::string& _property) const + { + const std::string res = getProperty(_property); + + if (!res.empty()) + _dst.insert(res); + + for (const auto& child : m_children) + child->collectVariants(_dst, _property); + } + + juce::Component* UiObject::createJuceObject(Editor& _editor) + { + m_juceObjects.clear(); + + if(hasComponent("rotary")) + { + createJuceObject<juce::Slider>(_editor); + } + else if(hasComponent("image")) + { + // @Juce: Juce does not respect translations on drawables by using setPositionAndSize, even though a Drawable is a component?! Doesn't make sense to me + auto* c = createJuceObject<juce::Component>(_editor); + auto img = _editor.createImageDrawable(getProperty("texture")); + c->addAndMakeVisible(img.get()); + m_juceObjects.emplace_back(std::move(img)); + } + else if(hasComponent("combobox")) + { + createJuceObject<juce::ComboBox>(_editor); + } + else if(hasComponent("button")) + { + createJuceObject(_editor, new juce::DrawableButton(m_name, juce::DrawableButton::ImageRaw)); + } + else if(hasComponent("hyperlinkbutton")) + { + createJuceObject<juce::HyperlinkButton>(_editor); + } + else if(hasComponent("textbutton")) + { + createJuceObject<juce::TextButton>(_editor); + } + else if(hasComponent("label")) + { + createJuceObject<juce::Label>(_editor); + } + else if(hasComponent("root") || hasComponent("component")) + { + createJuceObject<juce::Component>(_editor); + } + else + { + throw std::runtime_error("Failed to determine object type for object named " + m_name); + } + + return m_juceObjects.empty() ? nullptr : m_juceObjects.front().get(); + } + + int UiObject::getPropertyInt(const std::string& _key, int _default) const + { + const auto res = getProperty(_key); + if (res.empty()) + return _default; + return strtol(res.c_str(), nullptr, 10); + } + + float UiObject::getPropertyFloat(const std::string& _key, float _default) const + { + const auto res = getProperty(_key); + if (res.empty()) + return _default; + return static_cast<float>(atof(res.c_str())); + } + + std::string UiObject::getProperty(const std::string& _key, const std::string& _default) const + { + for (const auto& properties : m_components) + { + for(const auto& prop : properties.second) + { + if (prop.first == _key) + return prop.second; + } + } + return _default; + } + + bool UiObject::parse(juce::DynamicObject* _obj) + { + if (!_obj) + return false; + + const auto& props = _obj->getProperties(); + + for (int i = 0; i < props.size(); ++i) + { + const auto key = std::string(props.getName(i).toString().toUTF8()); + const auto value = props.getValueAt(i); + + if (key == "name") + { + m_name = value.toString().toStdString(); + } + else if(key == "children") + { + const auto children = value.getArray(); + + if (children) + { + for(int c=0; c<children->size(); ++c) + { + std::unique_ptr<UiObject> child; + child.reset(new UiObject((*children)[c])); + m_children.emplace_back(std::move(child)); + } + } + } + else + { + auto* componentDesc = value.getDynamicObject(); + + if(componentDesc) + { + const auto& componentProps = componentDesc->getProperties(); + + std::map<std::string, std::string> properties; + + for(int p=0; p<componentProps.size(); ++p) + { + properties.insert(std::make_pair(componentProps.getName(p).toString().toStdString(), componentProps.getValueAt(p).toString().toStdString())); + } + + m_components.insert(std::make_pair(key, properties)); + } + } + } + + return true; + } + + void UiObject::readProperties(juce::Component& _target) + { + const auto it = m_components.find("componentProperties"); + if(it == m_components.end()) + return; + + const auto props = it->second; + + for (const auto& prop : props) + _target.getProperties().set(juce::Identifier(prop.first.c_str()), juce::var(prop.second.c_str())); + } + + template <typename T> T* UiObject::createJuceObject(Editor& _editor) + { + return createJuceObject(_editor, new T()); + } + + template <typename T> T* UiObject::createJuceObject(Editor& _editor, T* _object) + { + std::unique_ptr<T> c(_object); + apply(_editor, *c); + auto* comp = c.get(); + m_juceObjects.emplace_back(std::move(c)); + + if(!m_name.empty()) + _editor.registerComponent(m_name, comp); + + auto* tooltipClient = dynamic_cast<juce::SettableTooltipClient*>(_object); + + if(tooltipClient) + { + const auto tooltip = getProperty("tooltip"); + + if(!tooltip.empty()) + tooltipClient->setTooltip(tooltip); + } + + readProperties(*_object); + + return comp; + } + + template<typename T> + void UiObject::bindParameter(const Editor& _editor, T& _target) const + { + const std::string param = getProperty("parameter"); + + if(param.empty()) + return; + + const auto index = _editor.getInterface().getParameterIndexByName(param); + + if(index < 0) + throw std::runtime_error("Parameter named " + param + " not found"); + + _editor.getInterface().bindParameter(_target, index); + + _target.getProperties().set("parameter", index); + } + + template <typename Target, typename Style> void UiObject::createStyle(Editor& _editor, Target& _target, Style* _style) + { + m_style.reset(_style); + m_style->apply(_editor, *this); + _target.setLookAndFeel(m_style.get()); + } +} diff --git a/source/juceUiLib/uiObject.h b/source/juceUiLib/uiObject.h @@ -0,0 +1,80 @@ +#pragma once + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +namespace juce +{ + class HyperlinkButton; + class TextButton; + class Label; + class DrawableButton; + class ComboBox; + class LookAndFeel_V4; + class Slider; + class Component; + class DynamicObject; + class var; +} + +namespace genericUI +{ + class ButtonStyle; + class UiObjectStyle; + class Editor; + + class UiObject + { + public: + explicit UiObject(const juce::var& _json); + ~UiObject(); + + void createJuceTree(Editor& _editor) const; + void createChildObjects(Editor& _editor, juce::Component& _parent) const; + + void apply(Editor& _editor, juce::Component& _target) const; + void apply(Editor& _editor, juce::Slider& _target); + void apply(Editor& _editor, juce::ComboBox& _target); + void apply(Editor& _editor, juce::DrawableButton& _target); + void apply(Editor& _editor, juce::Label& _target); + void apply(Editor& _editor, juce::TextButton& _target); + void apply(Editor& _editor, juce::HyperlinkButton& _target); + + void collectVariants(std::set<std::string>& _dst, const std::string& _property) const; + + juce::Component* createJuceObject(Editor& _editor); + + int getPropertyInt(const std::string& _key, int _default = 0) const; + float getPropertyFloat(const std::string& _key, float _default = 0.0f) const; + std::string getProperty(const std::string& _key, const std::string& _default = std::string()) const; + + private: + bool hasComponent(const std::string& _component) const; + template<typename T> T* createJuceObject(Editor& _editor); + template<typename T> T* createJuceObject(Editor& _editor, T* _object); + + bool parse(juce::DynamicObject* _obj); + + template<typename T> void bindParameter(const Editor& _editor, T& _target) const; + + void readProperties(juce::Component& _target); + + template<typename Target, typename Style> + void createStyle(Editor& _editor, Target& _target, Style* _style); + + std::string m_name; + std::map<std::string, std::map<std::string, std::string>> m_components; + std::vector<std::unique_ptr<UiObject>> m_children; + + std::vector<std::unique_ptr<juce::Component>> m_juceObjects; + std::unique_ptr<UiObjectStyle> m_style; + }; + + inline bool UiObject::hasComponent(const std::string& _component) const + { + return m_components.find(_component) != m_components.end(); + } +} diff --git a/source/juceUiLib/uiObjectStyle.cpp b/source/juceUiLib/uiObjectStyle.cpp @@ -0,0 +1,151 @@ +#include "uiObjectStyle.h" + +#include "editor.h" +#include "uiObject.h" + +namespace genericUI +{ + UiObjectStyle::UiObjectStyle(Editor& _editor) : m_editor(_editor) {} + + void UiObjectStyle::setTileSize(int _w, int _h) + { + m_tileSizeX = _w; + m_tileSizeY = _h; + } + + void UiObjectStyle::setDrawable(juce::Drawable* _drawable) + { + m_drawable = _drawable; + } + + void UiObjectStyle::setTextHeight(int _height) + { + m_textHeight = _height; + } + + void UiObjectStyle::apply(Editor& _editor, const UiObject& _object) + { + const auto x = _object.getPropertyInt("tileSizeX"); + const auto y = _object.getPropertyInt("tileSizeY"); + + setTileSize(x,y); + + const auto tex = _object.getProperty("texture"); + if(!tex.empty()) + setDrawable(_editor.getImageDrawable(tex)); + + setTextHeight(_object.getPropertyInt("textHeight")); + + m_text = _object.getProperty("text"); + const auto color = _object.getProperty("color"); + + auto parseColor = [&_object](juce::Colour& _target, const std::string& _prop) + { + const auto color = _object.getProperty(_prop); + + if(color.size() != 8) + return; + + uint32_t r,g,b,a; + sscanf(color.c_str(), "%02x%02x%02x%02x", &r, &g, &b, &a); + _target = juce::Colour(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), static_cast<uint8_t>(a)); + }; + + parseColor(m_color, "color"); + parseColor(m_bgColor, "backgroundColor"); + + const auto alignH = _object.getProperty("alignH"); + if(!alignH.empty()) + { + juce::Justification a = 0; + switch (alignH[0]) + { + case 'L': a = juce::Justification::left; break; + case 'C': a = juce::Justification::horizontallyCentred; break; + case 'R': a = juce::Justification::right; break; + } + m_align = a; + } + + const auto alignV = _object.getProperty("alignV"); + if(!alignV.empty()) + { + juce::Justification a = 0; + switch (alignV[0]) + { + case 'T': a = juce::Justification::top; break; + case 'C': a = juce::Justification::verticallyCentred; break; + case 'B': a = juce::Justification::bottom; break; + } + m_align = m_align.getFlags() | a.getFlags(); + } + + m_fontFile = _object.getProperty("fontFile"); + m_fontName = _object.getProperty("fontName"); + + m_bold = _object.getPropertyInt("bold") != 0; + m_italic = _object.getPropertyInt("italic") != 0; + + m_url = _object.getProperty("url"); + } + + juce::Font UiObjectStyle::getComboBoxFont(juce::ComboBox& _comboBox) + { + if(!m_fontFile.empty()) + { + auto font = juce::Font(m_editor.getFont(m_fontFile).getTypeface()); + applyFontProperties(font); + return font; + } + auto font = LookAndFeel_V4::getComboBoxFont(_comboBox); + applyFontProperties(font); + return font; + } + + juce::Font UiObjectStyle::getLabelFont(juce::Label& _label) + { + if(!m_fontFile.empty()) + { + auto font = juce::Font(m_editor.getFont(m_fontFile).getTypeface()); + applyFontProperties(font); + return font; + } + auto font = LookAndFeel_V4::getLabelFont(_label); + applyFontProperties(font); + return font; + } + + juce::Font UiObjectStyle::getPopupMenuFont() + { + if(!m_fontFile.empty()) + { + auto font = juce::Font(m_editor.getFont(m_fontFile).getTypeface()); + applyFontProperties(font); + return font; + } + auto font = LookAndFeel_V4::getPopupMenuFont(); + applyFontProperties(font); + return font; + } + + juce::Font UiObjectStyle::getTextButtonFont(juce::TextButton& _textButton, int buttonHeight) + { + if(!m_fontFile.empty()) + { + auto font = juce::Font(m_editor.getFont(m_fontFile).getTypeface()); + applyFontProperties(font); + return font; + } + auto font = LookAndFeel_V4::getTextButtonFont(_textButton, buttonHeight); + applyFontProperties(font); + return font; + } + + void UiObjectStyle::applyFontProperties(juce::Font& _font) const + { + if(m_textHeight) + _font.setHeight(static_cast<float>(m_textHeight)); + _font.setBold(m_bold); + _font.setItalic(m_italic); + } +} diff --git a/source/juceUiLib/uiObjectStyle.h b/source/juceUiLib/uiObjectStyle.h @@ -0,0 +1,47 @@ +#pragma once + +#include <juce_audio_processors/juce_audio_processors.h> + +namespace genericUI +{ + class Editor; + class UiObject; + + class UiObjectStyle : public juce::LookAndFeel_V4 + { + public: + UiObjectStyle(Editor& _editor); + + void setTileSize(int _w, int _h); + void setDrawable(juce::Drawable* _drawable); + void setTextHeight(int _height); + + virtual void apply(Editor& _editor, const UiObject& _object); + + protected: + juce::Font getComboBoxFont(juce::ComboBox&) override; + juce::Font getLabelFont(juce::Label&) override; + juce::Font getPopupMenuFont() override; + juce::Font getTextButtonFont(juce::TextButton&, int buttonHeight) override; + + void applyFontProperties(juce::Font& _font) const; + + Editor& m_editor; + + int m_tileSizeX = 0; + int m_tileSizeY = 0; + juce::Drawable* m_drawable = nullptr; + + std::string m_fontFile; + std::string m_fontName; + int m_textHeight = 0; + std::string m_text; + juce::Colour m_color = juce::Colour(0xffffffff); + juce::Colour m_bgColor = juce::Colour(0); + juce::Justification m_align = 0; + bool m_bold = false; + bool m_italic = false; + + std::string m_url; + }; +} diff --git a/source/synthLib/midiToSysex.cpp b/source/synthLib/midiToSysex.cpp @@ -137,6 +137,29 @@ namespace synthLib return true; } + void MidiToSysex::splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src) + { + for (size_t i = 0; i < _src.size(); ++i) + { + if (_src[i] != 0xf0) + continue; + + for (size_t j = i + 1; j < _src.size(); ++j) + { + if (_src[j] != 0xf7) + continue; + + std::vector<uint8_t> entry; + entry.insert(entry.begin(), _src.begin() + i, _src.begin() + j + 1); + + _dst.emplace_back(entry); + + i = j; + break; + } + } + } + bool MidiToSysex::checkChunk(FILE* hFile, const char* _pCompareChunk) { char readChunk[4]; diff --git a/source/synthLib/midiToSysex.h b/source/synthLib/midiToSysex.h @@ -10,6 +10,7 @@ namespace synthLib { public: static bool readFile(std::vector<uint8_t>& _sysexMessages, const char* _filename); + static void splitMultipleSysex(std::vector<std::vector<uint8_t>>& _dst, const std::vector<uint8_t>& _src); private: static bool checkChunk(FILE* hFile, const char* _pCompareChunk); static uint32_t getChunkLength(FILE* hFile); diff --git a/source/synthLib/midiTypes.h b/source/synthLib/midiTypes.h @@ -17,17 +17,18 @@ namespace synthLib M_PITCHBEND = 0xe0, // single status bytes + M_STARTOFSYSEX = 0xf0, + M_QUARTERFRAME = 0xf1, M_SONGPOSITION = 0xf2, M_SONGSELECT = 0xf3, M_TUNEREQUEST = 0xf5, - M_SYSTEMRESET = 0xff, + M_ENDOFSYSEX = 0xf7, M_TIMINGCLOCK = 0xf8, M_START = 0xfa, M_CONTINUE = 0xfb, M_STOP = 0xfc, M_ACTIVESENSING = 0xfe, - M_STARTOFSYSEX = 0xf0, - M_ENDOFSYSEX = 0xf7, + M_SYSTEMRESET = 0xff, // send as data byte 1 for control changes M_LOCALOFF = 0x7a, diff --git a/source/synthLib/os.cpp b/source/synthLib/os.cpp @@ -1,8 +1,5 @@ #include "os.h" -#include <cassert> - -#include "../dsp56300/source/dsp56kEmu/buildconfig.h" #include "../dsp56300/source/dsp56kEmu/logging.h" #ifndef _WIN32 @@ -14,6 +11,7 @@ #ifdef USE_DIRENT #include <dirent.h> #include <unistd.h> +#include <sys/stat.h> #else #include <filesystem> #endif @@ -45,12 +43,12 @@ namespace synthLib reinterpret_cast<LPCSTR>(&getModulePath), &hm) == 0) { LOG("GetModuleHandle failed, error = " << GetLastError()); - return std::string(); + return {}; } if (GetModuleFileName(hm, buffer, sizeof(buffer)) == 0) { LOG("GetModuleFileName failed, error = " << GetLastError()); - return std::string(); + return {}; } path = buffer; @@ -92,111 +90,137 @@ namespace synthLib if (end != std::string::npos) path = path.substr(0, end + 1); - return path; + return validatePath(path); } - static std::string lowercase(const std::string &_src) + std::string getCurrentDirectory() { - std::string str(_src); - for (size_t i = 0; i < str.size(); ++i) - str[i] = tolower(str[i]); - return str; +#ifdef USE_DIRENT + char temp[1024]; + getcwd(temp, sizeof(temp)); + return temp; +#else + return std::filesystem::current_path().string(); +#endif } - static std::string getExtension(const std::string &_name) + bool createDirectory(const std::string& _dir) { - const auto pos = _name.find_last_of('.'); - if (pos != std::string::npos) - return _name.substr(pos); - return std::string(); +#ifdef USE_DIRENT + return mkdir(_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0; +#else + return std::filesystem::create_directories(_dir); +#endif } - std::string findROM(size_t _minSize, size_t _maxSize) + std::string validatePath(std::string _path) { - std::string path = getModulePath(); + if(_path.empty()) + return _path; + if(_path.back() == '/' || _path.back() == '\\') + return _path; + _path += '/'; + return _path; + } + bool getDirectoryEntries(std::vector<std::string>& _files, const std::string& _folder) + { #ifdef USE_DIRENT - if (path.empty()) - { - char temp[1024]; - getcwd(temp, sizeof(temp)); - path = temp; - } - DIR *dir; struct dirent *ent; - if ((dir = opendir(path.c_str()))) + if ((dir = opendir(_folder.c_str()))) { - /* print all the files and directories within directory */ while ((ent = readdir(dir))) { - const std::string file = path + ent->d_name; - - const std::string ext = lowercase(getExtension(file)); - - if (ext != ".bin") - continue; - - if (_minSize || _maxSize) - { - FILE *hFile = fopen(file.c_str(), "rb"); - if (!hFile) - continue; + std::string file = _folder; - fseek(hFile, 0, SEEK_END); - const auto size = ftell(hFile); - fclose(hFile); + if(file.back() != '/' && file.back() != '\\') + file += '/'; - if (_minSize && size < _minSize) - continue; - if (_maxSize && size > _maxSize) - continue; + file += ent->d_name; - LOG("Found ROM at path " << file); - - return file; - } + _files.push_back(file); } closedir(dir); } else { - return std::string(); + return false; } #else - if (path.empty()) - path = std::filesystem::current_path().string(); - - try + try { - for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(path)) + for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(_folder)) { const auto &file = entry.path(); - if (!file.has_extension()) - continue; - - if (_minSize && entry.file_size() < _minSize) - continue; - if (_maxSize && entry.file_size() > _maxSize) - continue; - - std::string ext = lowercase(file.extension().string()); - - if (ext != ".bin") - continue; - - LOG("Found ROM at path " << file); - - return file.string(); + _files.push_back(file.string()); } } catch (...) { + return false; } #endif + return !_files.empty(); + } - return std::string(); + static std::string lowercase(const std::string &_src) + { + std::string str(_src); + for (char& i : str) + i = static_cast<char>(tolower(i)); + return str; + } + + static std::string getExtension(const std::string &_name) + { + const auto pos = _name.find_last_of('.'); + if (pos != std::string::npos) + return _name.substr(pos); + return {}; + } + + std::string findROM(const size_t _minSize, const size_t _maxSize) + { + std::string path = getModulePath(); + + if(path.empty()) + path = getCurrentDirectory(); + + std::vector<std::string> files; + + getDirectoryEntries(files, path); + + for (const auto& file : files) + { + if(!hasExtension(file, ".bin")) + continue; + + if (!_minSize && !_maxSize) + { + LOG("Found ROM at path " << file); + return file; + } + + FILE *hFile = fopen(file.c_str(), "rb"); + if (!hFile) + continue; + + fseek(hFile, 0, SEEK_END); + const auto size = static_cast<size_t>(ftell(hFile)); + fclose(hFile); + + if (_minSize && size < _minSize) + continue; + if (_maxSize && size > _maxSize) + continue; + + LOG("Found ROM at path " << file); + + return file; + } + return {}; } std::string findROM(const size_t _expectedSize) @@ -204,6 +228,11 @@ namespace synthLib return findROM(_expectedSize, _expectedSize); } + bool hasExtension(const std::string& _filename, const std::string& _extension) + { + return lowercase(getExtension(_filename)) == lowercase(_extension); + } + void setFlushDenormalsToZero() { #if defined(_MSC_VER) diff --git a/source/synthLib/os.h b/source/synthLib/os.h @@ -1,10 +1,21 @@ #pragma once #include <string> +#include <vector> namespace synthLib { std::string getModulePath(); + + std::string getCurrentDirectory(); + bool createDirectory(const std::string& _dir);; + std::string validatePath(std::string _path); + + bool getDirectoryEntries(std::vector<std::string>& _files, const std::string& _folder); + std::string findROM(size_t _minSize, size_t _maxSize); std::string findROM(size_t _expectedSize = 524288); + + bool hasExtension(const std::string& _filename, const std::string& _extension); + void setFlushDenormalsToZero(); } // namespace synthLib diff --git a/source/virusLib/CMakeLists.txt b/source/virusLib/CMakeLists.txt @@ -4,6 +4,8 @@ project(virusLib) add_library(virusLib STATIC) set(SOURCES + demopacketvalidator.cpp demopacketvalidator.h + demoplayback.cpp demoplayback.h device.cpp device.h midiOutParser.cpp midiOutParser.h romfile.cpp romfile.h diff --git a/source/virusLib/demopacketvalidator.cpp b/source/virusLib/demopacketvalidator.cpp @@ -0,0 +1,134 @@ +#include "demopacketvalidator.h" + +#include <cassert> + +#include "dsp56kEmu/logging.h" + +namespace virusLib +{ + bool DemoPacketValidator::add(const Packet& _packet) + { + if(isComplete()) + return isValid(); + + if(_packet.size() < 11) + return isValid(); + + const auto cmd = _packet[5]; + + switch (cmd) + { + case 0x50: // Virus A second + case 0x55: // Virus B second + case 0x57: // Virus C second + { + const auto msb = _packet[6]; // packet number MSB + const auto lsb = _packet[7]; // packet number LSB + + uint8_t checksum = 0; + for(size_t i=5; i<_packet.size()-2; ++i) + checksum += _packet[i]; + checksum &= 0x7f; + + if(checksum != _packet[_packet.size()-2]) + { + LOG("Packet MSB " << static_cast<int>(msb) << " LSB " << static_cast<int>(lsb) << " is invalid, wrong checksum"); + m_valid = false; + return false; + } + + auto packetInvalid = [&]() + { + LOG("Packet invalid, expected packet index " << static_cast<int>(m_expectedMSB) << " " << static_cast<int>(m_expectedLSB) << " but got " << static_cast<int>(msb) << " " << static_cast<int>(lsb)); + m_valid = false; + return false; + }; + + auto matchExpected = [&]() + { + return msb == m_expectedMSB && lsb == m_expectedLSB; + }; + + if(msb == 127 && lsb >= 3) + { + if(lsb == 3) + { + if(!matchExpected()) + return packetInvalid(); + + m_packets.push_back(_packet); + + m_expectedLSB = ++m_offset; + } + else if(lsb < 126) + { + if(!matchExpected()) + return packetInvalid(); + m_expectedLSB = 0; + m_expectedMSB = 0; + } + else if(lsb == 127) + { + if(m_expectedMSB != msb) + { + LOG("Terminating too soon, missing packets"); + m_valid = false; + return false; + } + + m_complete = true; + if(!toBinary(m_data)) + m_valid = false; + return isComplete(); + } + } + else + { + if(!matchExpected()) + return packetInvalid(); + + m_packets.push_back(_packet); + + if(lsb == 3) + { + ++m_expectedMSB; + m_expectedLSB = 0; + } + else + ++m_expectedLSB; + } + } + break; + default: + // skip unknown packets + return true; + } + + return false; + } + + bool DemoPacketValidator::toBinary(std::vector<uint8_t>& _binary) const + { + if(!isComplete()) + return false; + + for (const auto& p : m_packets) + { + // midi bytes in a sysex frame can only carry 7 bit, not 8. They've chosen the easy way that costs more storage + // They transfer only one nibble of a ROM byte in one midi byte to ensure that the most significant nibble is + // always zero. By concating two nibbles together we get one ROM byte + for(size_t s=8; s<p.size()-2; s += 2) + { + const uint8_t a = p[s]; + const uint8_t b = p[s+1]; + if(a > 0xf || b > 0xf) + { + LOG("Invalid data, high nibble must be 0"); + return false; + } + _binary.push_back(static_cast<uint8_t>(b << 4) | a); + } + } + return true; + } +} diff --git a/source/virusLib/demopacketvalidator.h b/source/virusLib/demopacketvalidator.h @@ -0,0 +1,33 @@ +#pragma once + +#include <cstdint> +#include <vector> + +namespace virusLib +{ + class DemoPacketValidator + { + public: + using Packet = std::vector<uint8_t>; + + bool add(const Packet& _packet); + + bool isValid() const { return m_valid; } + bool isComplete() const { return isValid() && m_complete; } + + const std::vector<uint8_t>& getData() const { return m_data; } + + private: + bool toBinary(std::vector<uint8_t>& _binary) const; + + std::vector<Packet> m_packets; + std::vector<uint8_t> m_data; + + uint8_t m_expectedMSB = 127; + uint8_t m_expectedLSB = 8; + uint8_t m_offset = 8; + + bool m_valid = true; + bool m_complete = false; + }; +} diff --git a/source/virusLib/demoplayback.cpp b/source/virusLib/demoplayback.cpp @@ -0,0 +1,253 @@ +#include "demoplayback.h" + +#include <cassert> +#include <vector> + +#include "microcontroller.h" + +#include "../synthLib/midiToSysex.h" +#include "../synthLib/midiTypes.h" + +#include "dsp56kEmu/logging.h" + +#include <cstring> // memcpy + +#include "demopacketvalidator.h" + +namespace virusLib +{ + constexpr auto g_timeScale_C = 57; // C OS 6.6 + constexpr auto g_timeScale_A = 54; // A OS 2.8 + + bool DemoPlayback::loadMidi(const std::string& _filename) + { + std::vector<uint8_t> sysex; + + synthLib::MidiToSysex::readFile(sysex, _filename.c_str()); + + if(sysex.empty()) + { + LOG("Failed to load demo midi file " << _filename << ", no sysex data found in file"); + return false; + } + + std::vector<std::vector<uint8_t>> packets; + synthLib::MidiToSysex::splitMultipleSysex(packets, sysex); + + DemoPacketValidator validator; + + for (const auto& packet : packets) + validator.add(packet); + + if(!validator.isValid()) + { + LOG("Packet validation failed, packets missing or invalid"); + return false; + } + + const auto& data = validator.getData(); + + return loadBinData(data); + } + + bool DemoPlayback::loadBinData(const std::vector<uint8_t>& _data) + { + // the start is either a raw serial packet or midi sysex packet so find that data + for(size_t i=0; i<_data.size() - 6; ++i) + { + if(_data[i] != 0xf0) + continue; + + if( (_data[i+1] == 0x75 && _data[i+2] == 0x55) || + (_data[i+1] == 0x00 && _data[i+2] == 0x20 && _data[i+3] == 0x33 && _data[i+4] == 0x01)) + { + auto data = _data; + data.erase(data.begin(), data.begin() + i); + return parseData(data); + } + } + return false; + } + + bool DemoPlayback::parseData(const std::vector<uint8_t>& _data) + { + for(size_t i=0; i<_data.size();) + { + Event e; + + switch(_data[i]) + { + case 0xf0: + for(size_t j=i+1; j<_data.size(); ++j) + { + if(_data[j] == 0xf7) + { + e = parseSysex(&_data[i], static_cast<uint32_t>(j - i + 1)); + i = j+1; + break; + } + } + break; + default: + e = parseMidi(&_data[i]); + i += e.data.size(); + + if(e.data[0] == synthLib::M_STOP) + return true; + } + + if(e.data.empty()) + break; + + // the byte that follows the data gives the delay for the next packet + e.delay = _data[i++]; + + m_events.push_back(e); + } + + return false; + } + + DemoPlayback::Event DemoPlayback::parseMidi(const uint8_t* _data) + { + Event e; + + e.type = EventType::Midi; + + const auto status = _data[0] < 0xf0 ? (_data[0] & 0xf0) : _data[0]; + + e.data.push_back(_data[0]); + + switch (status) + { + case synthLib::M_ACTIVESENSING: // most probably used to define a pause that is > 0xff + case synthLib::M_STOP: // end of demo + break; + case synthLib::M_AFTERTOUCH: + e.data.push_back(_data[1]); + break; + case synthLib::M_QUARTERFRAME: + // two bytes for a quarter frame event is not valid normally. Assuming this is some playback speed definition? + // We use it for that. As the base is unknown we hardcode it for now + { + const auto a = _data[1]; + const auto b = _data[2]; + + switch(a << 8 | b) + { + case 0xeaed: + m_timeScale = g_timeScale_A; + break; + default: + m_timeScale = g_timeScale_C; + break; + } + e.data.push_back(a); + e.data.push_back(b); + } + break; + default: + assert(status <= synthLib::M_PITCHBEND); + e.data.push_back(_data[1]); + e.data.push_back(_data[2]); + } + return e; + } + + DemoPlayback::Event DemoPlayback::parseSysex(const uint8_t* _data, const uint32_t _count) + { + Event e; + + if(_data[1] == 0x75 && _data[2] == 0x55) + { + // This is not really sysex but arbitrary data that is sent directly to the DSP via HDI08 + // Only seen for single and multi patches for now + e.data.resize(_count); + memcpy(&e.data.front(), _data, _count); + e.type = EventType::RawSerial; + } + else + { + // regular midi sysex data, sent to the uC + e.data.resize(_count); + memcpy(&e.data.front(), _data, _count); + e.type = EventType::MidiSysex; + } + return e; + } + + void DemoPlayback::process(const uint32_t _samples) + { + if(m_currentEvent == 0 && m_remainingDelay == 0) + { + // switch to multi mode when playback starts + Microcontroller::TPreset data; + m_mc.requestMulti(BankNumber::A, 0, data); + m_mc.writeMulti(BankNumber::EditBuffer, 0, data); + --m_remainingDelay; + return; + } + + if(m_currentEvent >= m_events.size()) + return; + + m_remainingDelay -= _samples; + while(m_remainingDelay <= 0 && m_currentEvent < m_events.size()) + { + const auto& e = m_events[m_currentEvent]; + if(!processEvent(e)) + return; + ++m_currentEvent; + m_remainingDelay = e.delay * m_timeScale; + } + } + + bool DemoPlayback::processEvent(const Event& _event) const + { + switch (_event.type) + { + case EventType::MidiSysex: + { + std::vector<synthLib::SMidiEvent> responses; + m_mc.sendSysex(_event.data, false, responses, synthLib::MidiEventSourcePlugin); + } + break; + case EventType::Midi: + { + synthLib::SMidiEvent ev; + ev.a = _event.data[0]; + ev.b = _event.data.size() > 1 ? _event.data[1] : 0; + ev.c = _event.data.size() > 2 ? _event.data[2] : 0; + + if(ev.a != synthLib::M_ACTIVESENSING && ev.a != synthLib::M_QUARTERFRAME && ev.a != synthLib::M_STOP) + { + m_mc.sendMIDI(ev); + m_mc.sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + } + } + break; + case EventType::RawSerial: + { + if(m_mc.needsToWaitForHostBits(0,1)) + return false; + + std::vector<dsp56k::TWord> dspWords; + + for(size_t i=0; i<_event.data.size(); i += 3) + { + dsp56k::TWord d = static_cast<dsp56k::TWord>(_event.data[i]) << 16; + if(i+1 < _event.data.size()) + d |= static_cast<dsp56k::TWord>(_event.data[i+1]) << 8; + if(i+2 < _event.data.size()) + d |= static_cast<dsp56k::TWord>(_event.data[i+2]); + dspWords.push_back(d); + } + + m_mc.writeHostBitsWithWait(0,1); + m_mc.m_hdi08.writeRX(dspWords); + } + break; + } + return true; + } +} diff --git a/source/virusLib/demoplayback.h b/source/virusLib/demoplayback.h @@ -0,0 +1,50 @@ +#pragma once + +#include <string> +#include <vector> + +namespace virusLib +{ + class Microcontroller; + + class DemoPlayback + { + public: + DemoPlayback(Microcontroller& _mc) : m_mc(_mc) {} + + bool loadMidi(const std::string& _filename); + bool loadBinData(const std::vector<uint8_t>& _data); + + void process(uint32_t _samples); + + private: + enum class EventType + { + MidiSysex, + Midi, + RawSerial + }; + + struct Event + { + EventType type = EventType::MidiSysex; + std::vector<uint8_t> data; + uint8_t delay = 0; + }; + + static Event parseSysex(const uint8_t* _data, uint32_t _count); + Event parseMidi(const uint8_t* _data); + + bool parseData(const std::vector<uint8_t>& _data); + + bool processEvent(const Event& _event) const; + + Microcontroller& m_mc; + + std::vector<Event> m_events; + int32_t m_remainingDelay = 0; + uint32_t m_currentEvent = 0; + + uint32_t m_timeScale = 54; + }; +} diff --git a/source/virusLib/microcontroller.h b/source/virusLib/microcontroller.h @@ -15,8 +15,11 @@ namespace virusLib { +class DemoPlayback; + class Microcontroller { + friend class DemoPlayback; public: using TPreset = ROMFile::TPreset; diff --git a/source/virusTestConsole/virusTestConsole.cpp b/source/virusTestConsole/virusTestConsole.cpp @@ -12,6 +12,7 @@ #include "../virusLib/romfile.h" #include "../virusLib/microcontroller.h" #include "../virusLib/midiOutParser.h" +#include "../virusLib/demoplayback.h" using namespace dsp56k; using namespace virusLib; @@ -31,6 +32,8 @@ size_t g_nextWriteSize = g_writeBlockSize; Microcontroller::TPreset preset; Microcontroller* microcontroller = nullptr; +std::unique_ptr<DemoPlayback> demo; + size_t audioCallbackCount = 0; void writeWord(const TWord _word) @@ -50,19 +53,27 @@ void audioCallback(dsp56k::Audio* audio) microcontroller->sendInitControlCommands(); break; case 64: - LOG("Sending Preset"); - microcontroller->writeSingle(BankNumber::EditBuffer, SINGLE, preset); + if(!demo) + { + LOG("Sending Preset"); + microcontroller->writeSingle(BankNumber::EditBuffer, SINGLE, preset); + } break; case 128: - LOG("Sending Note On"); - microcontroller->sendMIDI(SMidiEvent(0x90, 60, 0x7f)); // Note On - microcontroller->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + if(!demo) + { + LOG("Sending Note On"); + microcontroller->sendMIDI(SMidiEvent(0x90, 60, 0x7f)); // Note On + microcontroller->sendPendingMidiEvents(std::numeric_limits<uint32_t>::max()); + } break; } + if(audioCallbackCount > 128 && demo) + demo->process(1); + ++audioCallbackCount; - static FILE *hFile=0; static int ctr=0; constexpr size_t sampleCount = 4; constexpr size_t channelsIn = 2; @@ -97,14 +108,21 @@ void audioCallback(dsp56k::Audio* audio) if(audioData.capacity()) { - audioFilename = ROMFile::getSingleName(preset); - - for(size_t i=0; i<audioFilename.size(); ++i) + if(demo) + { + audioFilename = "virusEmu_demo.wav"; + } + else { - if(audioFilename[i] == ' ') - audioFilename[i] = '_'; + audioFilename = ROMFile::getSingleName(preset); + + for(size_t i=0; i<audioFilename.size(); ++i) + { + if(audioFilename[i] == ' ') + audioFilename[i] = '_'; + } + audioFilename = "virusEmu_" + audioFilename + ".wav"; } - audioFilename = "virusEmu_" + audioFilename + ".wav"; LOG("Begin writing audio to file " << audioFilename); } } @@ -224,9 +242,23 @@ int main(int _argc, char* _argv[]) ROMFile v(romFile); auto loader = v.bootDSP(dsp, periph); + Microcontroller uc(periph.getHDI08(), v); + microcontroller = &uc; + if(_argc > 1) { - if(!loadSingle(v, _argv[1])) + const std::string name(_argv[1]); + + if(hasExtension(name, ".mid")) + { + // try to load demo + demo.reset(new DemoPlayback(uc)); + if(!demo->loadMidi(name)) + { + demo.reset(); + } + } + if(!demo && !loadSingle(v, name)) { std::cout << "Failed to find preset '" << _argv[1] << "', make sure to use a ROM that contains it" << std::endl; waitReturn(); @@ -259,9 +291,6 @@ int main(int _argc, char* _argv[]) } // Load preset - Microcontroller uc(periph.getHDI08(), v); - microcontroller = &uc; - dsp.enableTrace((DSP::TraceMode)(DSP::Ops | DSP::Regs | DSP::StackIndent)); DSPThread dspThread(dsp); diff --git a/temp/cmake_win64/gearmulator.sln.DotSettings b/temp/cmake_win64/gearmulator.sln.DotSettings @@ -25,6 +25,7 @@ <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Global_0020variables/@EntryIndexedValue">&lt;NamingElement Priority="8"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="global variable" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="g_" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Local_0020variables/@EntryIndexedValue">&lt;NamingElement Priority="7"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="local variable" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Namespaces/@EntryIndexedValue">&lt;NamingElement Priority="17"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="namespace" /&gt;&lt;type Name="namespace alias" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String> + <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Other_0020constants/@EntryIndexedValue">&lt;NamingElement Priority="15"&gt;&lt;Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="class field" /&gt;&lt;type Name="local variable" /&gt;&lt;type Name="struct field" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Parameters/@EntryIndexedValue">&lt;NamingElement Priority="6"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="function parameter" /&gt;&lt;type Name="lambda parameter" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Typedefs/@EntryIndexedValue">&lt;NamingElement Priority="18"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="type alias" /&gt;&lt;type Name="typedef" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/NamingElement&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>