diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 6e2a6f5..0160bcc 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -27,6 +27,8 @@ jobs: - name: Set Up Node.js uses: actions/setup-node@v2 + with: + node-version: 16 - name: Set Up Yarn run: npm install -g yarn @@ -44,10 +46,24 @@ jobs: run: | sudo apt-get update && sudo apt install libasound2-dev libcurl4-openssl-dev libx11-dev libxinerama-dev libxext-dev libfreetype6-dev libwebkit2gtk-4.0-dev libglu1-mesa-dev - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Patch Blocks + working-directory: ./external/roli_blocks_basics + run: git apply --reject --whitespace=fix -v ../../scripts/patches/roli.patch + + - name: Patch React-Juce + working-directory: ./external/react-juce + run: git apply --reject --whitespace=fix -v ../../scripts/patches/react.patch + + - name: Configure CMake for Standalone + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_PLUGIN_IS_STANDALONE=1 + + - name: Build Standalone + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Configure CMake for Plugins + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_PLUGIN_IS_STANDALONE=0 - - name: Build + - name: Build Plugins run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Zip diff --git a/.gitmodules b/.gitmodules index 12fbc81..463a8cc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "external/juce"] path = external/juce url = https://github.com/juce-framework/JUCE +[submodule "external/roli_blocks_basics"] + path = external/roli_blocks_basics + url = https://github.com/WeAreROLI/roli_blocks_basics diff --git a/.vscode/launch.json b/.vscode/launch.json index 67d2500..704277e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,16 +3,29 @@ { "type": "lldb", "request": "launch", - "name": "CMake Standalone", - "program": "${command:cmake.launchTargetPath}", + "name": "LLDB - Launch Debug", + "program": "${workspaceRoot}/build/ScaleRemapperDev_artefacts/Debug/Standalone/ScaleRemapperDev.exe", "args": [], "cwd": "${workspaceFolder}" }, { "type": "lldb", "request": "attach", - "name": "Attach to AudioHost", - "pid": "${command:pickMyProcess}" + "name": "LLDB - Attach to AudioHost", + "program": "AudioPluginHost.exe" + }, + { + "name": "VSDB - Launch Debug", + "type": "cppvsdbg", + "cwd": "${workspaceRoot}", + "request": "launch", + "program": "${workspaceRoot}/build/ScaleRemapperDev_artefacts/Debug/Standalone/ScaleRemapperDev.exe" + }, + { + "name": "VSDB - Attach to AudioHost", + "type": "cppvsdbg", + "request": "attach", + "processId": "${command:pickProcess}" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7fdc06a..dd65f97 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,6 +10,24 @@ "detail": "webpack --mode=production", "hide": true }, + { + "type": "npm", + "script": "build-dev", + "path": "ui", + "problemMatcher": [], + "label": "yarn build ui debug", + "detail": "webpack --mode=development", + "hide": true + }, + { + "type": "process", + "command": "xcopy", + "args": ["/y", "${workspaceRoot}\\ui\\build\\js", "${workspaceRoot}\\build\\ScaleRemapperDev_artefacts\\Debug\\Standalone\\js\\"], + "problemMatcher": [], + "label": "copy js assets", + "dependsOn": ["yarn build ui debug"], + "hide": true + }, { "type": "process", "command": "cmake", @@ -30,6 +48,26 @@ "problemMatcher": [], "hide": true }, + { + "type": "process", + "command": "cmake", + "args": [ + "-A", + "x64", + "../", + "-D", + "CMAKE_BUILD_TYPE=Debug" + ], + "label": "cmake prepare build debug", + "options": { + "cwd": "${workspaceRoot}/build" + }, + "dependsOn": [ + "copy js assets" + ], + "problemMatcher": [], + "hide": true + }, { "type": "process", "command": "cmake", @@ -49,6 +87,25 @@ ], "hide": true }, + { + "type": "process", + "command": "cmake", + "args": [ + "--build", + ".", + "--config", + "Debug" + ], + "label": "cmake build debug", + "options": { + "cwd": "${workspaceRoot}/build" + }, + "problemMatcher": [], + "dependsOn": [ + "cmake prepare build debug" + ], + "hide": true + }, { "type": "process", "command": "./ScaleRemapper_artefacts/Release/Standalone/ScaleRemapper.exe", @@ -60,6 +117,18 @@ "dependsOn": [ "cmake build" ] + }, + { + "type": "process", + "command": "./ScaleRemapperDev_artefacts/Debug/Standalone/ScaleRemapperDev.exe", + "label": "build & run executable (debug)", + "options": { + "cwd": "${workspaceRoot}/build/" + }, + "problemMatcher": [], + "dependsOn": [ + "cmake build debug" + ] } ] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e37780a..ac82fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Change Log All notable changes to this project will be documented in this file. +## [0.7.0] - 2023-02-15 + +### Fixed + +* LV2 plugins didn't work due to incorrect URL +* [ui] incorrect sizing of list elements + +### Added + +* [ui] colors mode for remapped keyboard +* buttons to cycle through the modes with the same keys selected +* icon for standalone app +* [LUMI keys](https://roli.com/products/blocks/lumi-keys-studio-edition) hardware support (4 modes, if plugin is disabled it will only highlight the key, otherwise it will highlight remapped keys, power button is changing color mode) +* custom scales override is possible if `scales.txt` is placed near the plugin binary file. Text file should list scales in format `intervals scaleName`, ex: + + ``` + 2 1 2 2 2 2 1 Melodic Minor + 2 1 2 2 1 3 1 Harmonic Minor + ``` + +### Changed + +* updated JUCE to 7.0.5 +* [ui] updated minor deps + ## [0.6.1] - 2023-02-08 ### Changed diff --git a/CMakeLists.txt b/CMakeLists.txt index 01640a1..c70524b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,35 +6,47 @@ if(CMAKE_BUILD_TYPE MATCHES "Debug") set(PROJECT_NAME "${PROJECT_NAME}Dev") endif() -set(VERSION "0.6.1") -set(JUCE_DIR "external/juce") -set(SRC_DIR "./src") -set(FORMATS "Standalone" "LV2" "VST3") +set(VERSION "0.7.0") + +set(FORMATS "") +set(SRC_DIR "./src") +set(JUCE_DIR "external/juce") set(REACT_JUCE "react_juce") -# see https://github.com/nick-thompson/react-juce/tree/master/react_juce -set(REACT_JUCE_DIR "external/react-juce/${REACT_JUCE}") # path to REACT JUCE +set(ROLI_BLOCKS "roli_blocks_basics") +set(BLOCKS_DIR "external/${ROLI_BLOCKS}") +set(REACT_JUCE_DIR "external/react-juce/${REACT_JUCE}") set(JS_BUNDLE_PATH "./ui/build/js/bundle.js") set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(CMAKE_PLUGIN_IS_STANDALONE) + message(STATUS "Building as STANDALONE") + list(APPEND FORMATS "Standalone") +else() + message(STATUS "Building as Plugin") + list(APPEND FORMATS "LV2") + list(APPEND FORMATS "VST3") +endif() + project(${PROJECT_NAME} VERSION ${VERSION}) add_subdirectory(${JUCE_DIR} "juce") +juce_add_module(${BLOCKS_DIR}) juce_add_module(${REACT_JUCE_DIR}) -if(WIN32) +if(WIN32 AND CMAKE_PLUGIN_IS_STANDALONE) message(STATUS "Building for Windows...") set(ASIO_DIR "sdk/asio") if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${ASIO_DIR}) set(BUILD_ASIO 1) endif() -endif(WIN32) +endif() set(VST2_DIR "sdk/vst2") -if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${VST2_DIR}) +if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/${VST2_DIR} AND NOT CMAKE_PLUGIN_IS_STANDALONE) set(BUILD_VST2 1) juce_set_vst2_sdk_path(${VST2_DIR}) list(APPEND FORMATS "VST") @@ -42,6 +54,8 @@ endif() target_compile_definitions(${REACT_JUCE} INTERFACE REACTJUCE_USE_DUKTAPE=1) +message(STATUS "Formats added: ${FORMATS}") + juce_add_plugin(${PROJECT_NAME} COMPANY_NAME XivilaY PRODUCT_NAME "${PROJECT_NAME}" @@ -49,6 +63,9 @@ juce_add_plugin(${PROJECT_NAME} NEEDS_MIDI_OUTPUT TRUE PLUGIN_MANUFACTURER_CODE XivY PLUGIN_CODE Xiv8 + LV2URI "https://github.com/xivilay/scale-remapper" + VST3_CATEGORIES "Instrument Tools" + ICON_SMALL "${SRC_DIR}/icons/small.png" FORMATS ${FORMATS} ) @@ -67,10 +84,12 @@ target_sources(${PROJECT_NAME} PRIVATE target_compile_definitions(${PROJECT_NAME} PRIVATE JUCE_USE_CURL=0 JUCE_WEB_BROWSER=0 - P_WIDTH=550 - P_HEIGHT=750 ) +if(WIN32 AND NOT CMAKE_PLUGIN_IS_STANDALONE) + target_compile_definitions(${PROJECT_NAME} PRIVATE JUCE_USE_WINRT_MIDI=1) +endif() + if(BUILD_VST2) message(STATUS "Adding VST2...") target_compile_definitions(${PROJECT_NAME} PUBLIC JUCE_VST3_CAN_REPLACE_VST2=1) @@ -102,6 +121,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE juce::juce_gui_basics juce::juce_gui_extra + ${ROLI_BLOCKS} ${REACT_JUCE} BinaryData ) \ No newline at end of file diff --git a/external/juce b/external/juce index 2b16c1b..69795dc 160000 --- a/external/juce +++ b/external/juce @@ -1 +1 @@ -Subproject commit 2b16c1b94c90d0db3072f6dc9da481a9484d0435 +Subproject commit 69795dc8e589a9eb5df251b6dd994859bf7b3fab diff --git a/external/roli_blocks_basics b/external/roli_blocks_basics new file mode 160000 index 0000000..9a6df11 --- /dev/null +++ b/external/roli_blocks_basics @@ -0,0 +1 @@ +Subproject commit 9a6df113bf570aec53d33cbf97938d8602e4ac2f diff --git a/screen.png b/screen.png index fa146f0..98fca2e 100644 Binary files a/screen.png and b/screen.png differ diff --git a/scripts/patches/react.patch b/scripts/patches/react.patch new file mode 100644 index 0000000..e1b0d40 --- /dev/null +++ b/scripts/patches/react.patch @@ -0,0 +1,12 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 40dd357..4cfa1ca 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -10,7 +10,6 @@ option(JUCE_BUILD_EXTRAS "Build JUCE Extras" OFF) + set(REACTJUCE_JS_LIBRARY DUKTAPE CACHE STRING "The JS Engine to use: either HERMES or DUKTAPE") + + +-add_subdirectory(ext/juce) + + # Adding any custom modules you might have: + juce_add_module(react_juce) diff --git a/scripts/patches/roli.patch b/scripts/patches/roli.patch new file mode 100644 index 0000000..5037dea --- /dev/null +++ b/scripts/patches/roli.patch @@ -0,0 +1,22 @@ +diff --git a/topology/internal/roli_MIDIDeviceDetector.cpp b/topology/internal/roli_MIDIDeviceDetector.cpp +index 4b77ff0..191c7e5 100644 +--- a/topology/internal/roli_MIDIDeviceDetector.cpp ++++ b/topology/internal/roli_MIDIDeviceDetector.cpp +@@ -112,7 +112,7 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector + { + MidiInputOutputPair pair; + pair.input = input; +- ++ auto inputId = input.identifier; // winRT workaround + juce::String cleanedInputName = cleanBlocksDeviceName (input.name); + + int inputOccurences = 0; +@@ -124,7 +124,7 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector + + for (const auto& output : midiOutputs) + { +- if (cleanBlocksDeviceName (output.name) == cleanedInputName) ++ if (cleanBlocksDeviceName (output.name) == cleanedInputName || output.identifier == inputId) + { + if (outputOccurences == inputOccurences) + { diff --git a/src/CustomEditor.h b/src/CustomEditor.h index b236a0b..69845ec 100644 --- a/src/CustomEditor.h +++ b/src/CustomEditor.h @@ -2,12 +2,14 @@ #include +#include "lumi/Mediator.h" + using namespace reactjuce; class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter::Listener, public Timer { public: CustomEditor(AudioProcessor& proc) - : AudioProcessorEditor(proc), engine(std::make_shared()), appRoot(engine), harness(appRoot) { + : AudioProcessorEditor(proc), engine(std::make_shared()), appRoot(engine), harness(appRoot), mediator(appRoot, proc) { auto& params = processor.getParameters(); paramReadouts.resize(static_cast(params.size())); @@ -25,8 +27,9 @@ class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter p->addListener(this); } -#if JUCE_DEBUG File exeDir = File::getSpecialLocation(File::currentExecutableFile).getParentDirectory(); +#if JUCE_DEBUG + File bundle = exeDir.getChildFile("js/bundle.js"); jassert(bundle.existsAsFile()); @@ -43,6 +46,12 @@ class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter afterBundleEvaluated(); #endif + File localScales = exeDir.getChildFile("scales.txt"); + if (localScales.existsAsFile()) { + auto fileText = localScales.loadFileAsString(); + appRoot.dispatchEvent("getLocalScales", fileText); + } + addAndMakeVisible(appRoot); setSize(400, 200); @@ -53,6 +62,7 @@ class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter for (auto& p : processor.getParameters()) { p->removeListener(this); } + mediator.onQuit(); } void parameterValueChanged(int parameterIndex, float newValue) { @@ -90,13 +100,6 @@ class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter private: void beforeBundleEvaluated() { - engine->registerNativeMethod("beginParameterChangeGesture", [this](const var::NativeFunctionArgs& args) { - if (auto it = parameters.find(args.arguments[0].toString()); it != parameters.cend()) - it->second->beginChangeGesture(); - - return var::undefined(); - }); - engine->registerNativeMethod("setParameterValueNotifyingHost", [this](const var::NativeFunctionArgs& args) { if (auto it = parameters.find(args.arguments[0].toString()); it != parameters.cend()) it->second->setValueNotifyingHost(args.arguments[1]); @@ -104,9 +107,10 @@ class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter return var::undefined(); }); - engine->registerNativeMethod("endParameterChangeGesture", [this](const var::NativeFunctionArgs& args) { - if (auto it = parameters.find(args.arguments[0].toString()); it != parameters.cend()) - it->second->endChangeGesture(); + engine->registerNativeMethod("sendComputedKeysData", [this](const var::NativeFunctionArgs& args) { + int a = args.arguments[0]; + + mediator.sendCommand(a); return var::undefined(); }); @@ -119,6 +123,8 @@ class CustomEditor : public AudioProcessorEditor, public AudioProcessorParameter ReactApplicationRoot appRoot; AppHarness harness; + Mediator mediator; + File bundleFile; std::map parameters; diff --git a/src/ScaleRemapper.h b/src/ScaleRemapper.h index 2a9f828..e51cb8c 100644 --- a/src/ScaleRemapper.h +++ b/src/ScaleRemapper.h @@ -20,7 +20,7 @@ AudioProcessorValueTreeState::ParameterLayout createParameterLayout() { p.add(std::make_unique("baseOctave", "Base Octave", 0, 10, 4)); p.add(std::make_unique("root", "Root Note", 0, 11, 0)); - for (size_t i = 0; i < scaleLength; i++) { + for (int i = 0; i < scaleLength; i++) { auto istr = std::to_string(i); auto defaultInterval = defaultScaleIntervals[i]; p.add(std::make_unique("interval" + istr, "Scale Interval " + istr, 1, scaleLength, defaultInterval)); @@ -49,7 +49,7 @@ class MidiScaleRemapper : public AudioProcessor { AudioProcessorEditor *createEditor() { auto *editor = new CustomEditor(*this); - editor->setSize(P_WIDTH, P_HEIGHT); + editor->setSize(550, 750); return editor; } diff --git a/src/icons/small.png b/src/icons/small.png new file mode 100644 index 0000000..9a6ead4 Binary files /dev/null and b/src/icons/small.png differ diff --git a/src/lumi/CustomProgram.h b/src/lumi/CustomProgram.h new file mode 100644 index 0000000..267426a --- /dev/null +++ b/src/lumi/CustomProgram.h @@ -0,0 +1,153 @@ +#pragma once + +class CustomProgram : public roli::Block::Program { + public: + CustomProgram(roli::Block& b) : Program(b) {} + + String getLittleFootProgram() override { + return R"littlefoot( + const int max = 64; + const int rootId = 27; + const int globalKeyColor = 34; + const int rootKeyColor = 35; + const int octaveId = 4; + const int colorModeId = 64; + int dimKeyColor; + int keyColor; + int rootColor; + int root; + int octave; + int state; + int keysState; + int colorMode; + int fullColors[12]; + int whiteKeys[14]; + int remapIsOn; + + void initialise() { + setUseDefaultKeyHandler(true, false); + keyColor = getLocalConfig(globalKeyColor); + rootColor = getLocalConfig(rootKeyColor); + root = getLocalConfig(rootId); + octave = getLocalConfig(octaveId); + sendMessageToHost(octaveId, octave, 0); + initColors(); + initWhiteKeys(); + } + void initWhiteKeys() { + int whiteKeyIndex = 0; + for (int i = 0; i < 24; i++) { + if (isWhiteKey(i)) { + whiteKeys[whiteKeyIndex] = i; + whiteKeyIndex++; + } + } + } + void initColors() { + fullColors[0] = 0xcf3550; + fullColors[1] = 0xf55333; + fullColors[2] = 0xfd7033; + fullColors[3] = 0xffa53e; + fullColors[4] = 0xffc255; + fullColors[5] = 0xffff00; + fullColors[6] = 0xd1d545; + fullColors[7] = 0x6da951; + fullColors[8] = 0x2191ce; + fullColors[9] = 0x4d6db5; + fullColors[10] = 0x564a9d; + fullColors[11] = 0x8c55a2; + dimKeyColor = makeARGB(255, 0, 0, 0); + } + bool isWhiteKey(int noteIndex) { + int i = noteIndex % 12; + if (i == 1 || i == 3 || i == 6 || i == 8 || i == 10) return false; + return true; + } + int getLastBits (int k, int n) { + return k & ((1 << n) - 1); + } + int getMidBits (int k, int m, int n) { + return getLastBits(k >> m, n); + } + void renderKeys() { + if (remapIsOn == 0) { + for (int i = 0; i < 12; i++) { + int highlighted = (keysState >> i) & 1; + int num = 12 - 1 - i; + int color; + if (colorMode == 1) { + color = fullColors[num]; + } else { + color = num == root ? rootColor : keyColor; + } + int fill = highlighted == 1 ? color : dimKeyColor; + fillPixel(fill, num, 0); + fillPixel(fill, num + 12, 0); + } + } else { + int selectedCount = 0; + for (int i = 0; i < 12; i++) { + selectedCount += ((keysState >> i) & 1); + } + int scaleIndex = 0; + for (int i = root; i < 12 + root; i++) { + int highlighted = (keysState >> (11 - i%12)) & 1; + int num = (12+i) % 12; + + if (highlighted == 1) { + int w = scaleIndex; + int color; + while (w <= 13) { + if (colorMode == 1) { + color = fullColors[num]; + } else { + color = num == root ? rootColor : keyColor; + } + fillPixel(color, whiteKeys[w], 0); + w += selectedCount; + } + scaleIndex++; + } + if (!isWhiteKey(i-root)) { + fillPixel(dimKeyColor, i-root, 0); + fillPixel(dimKeyColor, i-root + 12, 0); + } + } + } + } + void handleMessage(int a, int b, int c) { + if (state == a) return; + state = a; + keysState = getMidBits(a, 0, 12); + root = getMidBits(a, 12, 4); + colorMode = getMidBits(a, 12+4, 1); + remapIsOn = getMidBits(a, 12+4+1, 1); + renderKeys(); + } + void handleButtonDown (int index) { + if (index == 1 || index == 2) { + int nextOctave; + if (index == 1) { + nextOctave = --octave; + } + if (index == 2) { + nextOctave = ++octave; + } + if (nextOctave < -4 || nextOctave > 8) return; + setLocalConfig(octaveId, nextOctave); + sendMessageToHost(octaveId, nextOctave, 0); + } + } + + void handleButtonUp (int index) { + if (index == 0) { + colorMode = colorMode ^ 1; + sendMessageToHost(colorModeId, colorMode, 0); + } + } + )littlefoot"; + } + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CustomProgram) +}; diff --git a/src/lumi/Mediator.h b/src/lumi/Mediator.h new file mode 100644 index 0000000..a191f66 --- /dev/null +++ b/src/lumi/Mediator.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CustomProgram.h" + +using namespace roli; + +class Mediator : private TopologySource::Listener, Block::ProgramEventListener, Block::ProgramLoadedListener { + public: + Mediator(reactjuce::ReactApplicationRoot& root, AudioProcessor& proc) : processor(proc) { + appRoot = &root; + pts.addListener(this); + }; + void onQuit() { + if (b != nullptr) { + b->setProgram(nullptr); + } + }; + void sendCommand(int a) { + if (b != nullptr) { + Block::ProgramEventMessage e; + + e.values[0] = a; + b->sendProgramEvent(e); + } + }; + + private: + void handleProgramEvent(Block& source, const Block::ProgramEventMessage& event) { + int messageId = event.values[0]; + int messageValue = event.values[1]; + if (messageId == octaveId) { + float value = static_cast(messageValue + octaveShift) / octavesCount; + processor.setParameterNotifyingHost(octaveId, value); + } + if (messageId == colorModeId) { + appRoot->dispatchEvent("uiSettingsChange", colorModeId, messageValue); + } + }; + void handleProgramLoaded(Block& block) { + appRoot->dispatchEvent("requestComputedKeysData"); + block.removeProgramLoadedListener(this); + }; + void topologyChanged() override { + auto currentTopology = pts.getCurrentTopology(); + for (auto& block : currentTopology.blocks) { + if (block->getType() == Block::lumiKeysBlock) { + b = block; + p = block->getProgram(); + block->setProgram(std::make_unique(*block)); + block->addProgramLoadedListener(this); + block->addProgramEventListener(this); + return; + } + } + }; + PhysicalTopologySource pts; + Block::Program* p; + Block* b = nullptr; + reactjuce::ReactApplicationRoot* appRoot; + AudioProcessor& processor; + + const int octaveShift = 4; + const int octaveId = 4; + const int octavesCount = 10; + const int colorModeId = 64; +}; diff --git a/ui b/ui index 3e3c2eb..1e7d2b0 160000 --- a/ui +++ b/ui @@ -1 +1 @@ -Subproject commit 3e3c2eb2b3040b50fa06592d6b3dcb6393d01cca +Subproject commit 1e7d2b04585a9d8177f7e876521aaaa24e1f86c7