diff --git a/.gitignore b/.gitignore index 19ad330..19717c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ src/lv_conf.h build/ +bin +scene*.json diff --git a/dev.sh b/dev.sh new file mode 100755 index 0000000..92acc19 --- /dev/null +++ b/dev.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SRC_DIR="${ROOT_DIR}/src" +BIN="${ROOT_DIR}/bin/x32ctrl" + +usage() { + cat <<'EOF' +Usage: ./dev.sh [--clean] [--skip-submodules] [--] [x32ctrl args...] + +Builds the SDL2 desktop development simulator, then starts it in bodyless +development mode. + +Options: + --clean Remove the simulator build output before compiling. + --skip-submodules Do not run git submodule update --init --recursive. + -h, --help Show this help text. + +Any remaining arguments are forwarded to x32ctrl after --bodyless. +EOF +} + +clean=0 +update_submodules=1 +app_args=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --clean) + clean=1 + shift + ;; + --skip-submodules) + update_submodules=0 + shift + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + app_args+=("$@") + break + ;; + *) + app_args+=("$1") + shift + ;; + esac +done + +jobs_count() { + if command -v nproc >/dev/null 2>&1; then + nproc 2>/dev/null || echo 4 + elif command -v sysctl >/dev/null 2>&1; then + sysctl -n hw.ncpu 2>/dev/null || echo 4 + else + echo 4 + fi +} + +sdl_cflags="" +sdl_libs="" + +if command -v sdl2-config >/dev/null 2>&1; then + sdl_cflags="$(sdl2-config --cflags)" + sdl_libs="$(sdl2-config --libs)" +elif command -v pkg-config >/dev/null 2>&1 && pkg-config --exists sdl2; then + sdl_cflags="$(pkg-config --cflags sdl2)" + sdl_libs="$(pkg-config --libs sdl2)" +else + cat >&2 <<'EOF' +SDL2 development files were not found. + +Install SDL2, then run this script again: + macOS: brew install sdl2 + Debian/Ubuntu: sudo apt install libsdl2-dev + Fedora: sudo dnf install SDL2-devel +EOF + exit 1 +fi + +case "$(uname -s)" in + Linux) + platform_ldflags="-lrt" + ;; + Darwin) + platform_ldflags="" + ;; + *) + platform_ldflags="" + ;; +esac + +if [[ "${update_submodules}" -eq 1 ]]; then + git -C "${ROOT_DIR}" submodule update --init --recursive +fi + +if [[ "${clean}" -eq 1 ]]; then + make -C "${SRC_DIR}" -f Makefile_x64_SDL2 clean +fi + +common_flags="-g -I../lib -I../lib/libartnet -I../lib/lv_port_linux -I../lib/glaze/include ${sdl_cflags} -MMD -MP -DBODYLESS_SDL2" + +make -C "${SRC_DIR}" -f Makefile_x64_SDL2 -j"$(jobs_count)" \ + CC="${CC:-cc}" \ + CXX="${CXX:-c++}" \ + CFLAGS="${CFLAGS:-${common_flags}}" \ + CXXFLAGS="${CXXFLAGS:-"-std=c++23 ${common_flags}"}" \ + LDFLAGS="${LDFLAGS:-"${sdl_libs} -lm -lpthread ${platform_ldflags}"}" + +exec "${BIN}" --bodyless "${app_args[@]}" diff --git a/files/lv_conf_SDL2.h b/files/lv_conf_SDL2.h index d15358d..673874d 100644 --- a/files/lv_conf_SDL2.h +++ b/files/lv_conf_SDL2.h @@ -1179,7 +1179,11 @@ #endif /** Driver for /dev/fb */ +#ifdef __APPLE__ +#define LV_USE_LINUX_FBDEV 0 +#else #define LV_USE_LINUX_FBDEV 1 +#endif #if LV_USE_LINUX_FBDEV #define LV_LINUX_FBDEV_BSD 0 #define LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL diff --git a/src/Makefile_x64_SDL2 b/src/Makefile_x64_SDL2 index 67b6a51..bb11c1f 100644 --- a/src/Makefile_x64_SDL2 +++ b/src/Makefile_x64_SDL2 @@ -7,28 +7,35 @@ #AR = arm-linux-gnueabi-ar #LD = arm-linux-gnueabi-ld -LIB_DIR = ../lib +LVGL_DIR_NAME ?= lvgl LVGL_DIR ?= ../lib/lv_port_linux LVGL_PATH = demosAreDisabledByThisHack -GLAZE_PATH = ../lib/glaze/include +LIB_DIR = ../lib +GLAZE_PATH = ../lib/glaze/include WARNINGS := -Wall -Wshadow -Wundef -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith \ - -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits \ + -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits \ -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security \ - -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body \ - -Wshift-negative-value -Wstack-usage=2048 -Wno-unused-value + -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wdeprecated -Wempty-body \ + -Wshift-negative-value -Wno-unused-value -DEPFLAGS = -MMD -MP -D=BODYLESS_SDL2 +DEPFLAGS = -MMD -MP -DBODYLESS_SDL2 # normal build -#CFLAGS ?= -std=c11 -O3 -g0 -I$(LIB_DIR)/ -I$(LVGL_DIR)/ -I $(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) -#CXXFLAGS ?= -std=c++23 -O3 -g0 -I$(LIB_DIR)/ -I$(LVGL_DIR)/ -I$(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) +#CFLAGS ?= -O3 -g0 -I$(LIB_DIR) -I$(LIB_DIR)/libartnet -I$(LVGL_DIR)/ -I $(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) +#CXXFLAGS ?= -std=c++23 -O3 -g0 -I$(LIB_DIR) -I$(LIB_DIR)/libartnet -I$(LVGL_DIR)/ -I$(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) # debug build -CFLAGS ?= -g -I$(LIB_DIR)/ -I$(LVGL_DIR)/ -I$(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) -CXXFLAGS ?= -std=c++23 -g -I$(LIB_DIR)/ -I$(LVGL_DIR)/ -I$(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) +CFLAGS ?= -g -I$(LIB_DIR) -I$(LIB_DIR)/libartnet -I$(LVGL_DIR)/ -I$(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) +CXXFLAGS ?= -std=c++23 -g -I$(LIB_DIR) -I$(LIB_DIR)/libartnet -I$(LVGL_DIR)/ -I$(GLAZE_PATH)/ $(WARNINGS) $(DEPFLAGS) -LDFLAGS ?= -lSDL2 -lm -lrt -lpthread #-lartnet +LDFLAGS ?= -lSDL2 -lm -lpthread + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Linux) + LDFLAGS += -lrt + WARNINGS += -Wmaybe-uninitialized -Wclobbered -Wstack-usage=2048 +endif BIN = omc BUILD_DIR = ../build @@ -38,25 +45,26 @@ BUILD_BIN_DIR = $(BUILD_DIR) prefix ?= /usr bindir ?= $(prefix)/bin -# Collect source files recursively -CSRCS := $(shell find -type f -name '*.c' -print) \ - $(shell find ../lib \ - -path '../lib/libartnet' -prune -o \ - -path '../lib/lv_port_linux/example' -prune -o \ +# Collect source files recursively (macOS and Linux compatible find) +CSRCS := $(shell find . -type f -name '*.c' -print) \ + $(shell find ../lib \ + -path '../lib/libartnet' -prune -o \ + -path '../lib/lv_port_linux/example' -prune -o \ -path '../lib/lv_port_linux/lvgl/demos' -prune -o \ -path '../lib/lv_port_linux/lvgl/examples' -prune -o \ -path '../lib/lv_port_linux/lvgl/tests' -prune -o \ - -path '../lib/glaze' -prune -o \ - -type f -name '*.c' -not -path '../lib/lv_port_linux/src/main.c' -print) -CXXSRCS := $(shell find -type f -name '*.cpp' -print) \ - $(shell find ../lib \ - -path '../lib/libartnet' -prune -o \ - -path '../lib/lv_port_linux/example' -prune -o \ + -path '../lib/glaze' -prune -o \ + -type f -name '*.c' -not -path '../lib/lv_port_linux/src/main.c' -print) + +CXXSRCS := $(shell find . -type f -name '*.cpp' -print) \ + $(shell find ../lib \ + -path '../lib/libartnet' -prune -o \ + -path '../lib/lv_port_linux/example' -prune -o \ -path '../lib/lv_port_linux/lvgl/demos' -prune -o \ -path '../lib/lv_port_linux/lvgl/examples' -prune -o \ -path '../lib/lv_port_linux/lvgl/tests' -prune -o \ - -path '../lib/glaze' -prune -o \ - -type f -name '*.cpp' -not -path '../lib/lv_port_linux/src/main.c' -print) + -path '../lib/glaze' -prune -o \ + -type f -name '*.cpp' -not -path '../lib/lv_port_linux/src/main.c' -print) all: copy default @@ -101,7 +109,7 @@ copy: @if ! diff -q ../files/lv_conf_SDL2.h lv_conf.h >/dev/null 2>&1; then \ cp ../files/lv_conf_SDL2.h lv_conf.h; \ - echo "Update $(LVGL_DIR)/lv_conf.h (changed content)"; \ + echo "Update lv_conf.h (changed content)"; \ else \ echo "lv_conf.h is up to date."; \ fi diff --git a/src/artnet.h b/src/artnet.h index 4d0677e..56423e6 100755 --- a/src/artnet.h +++ b/src/artnet.h @@ -9,14 +9,13 @@ #include "base.h" using namespace std; -using enum MP_ID; - #if ENABLE_ARTNET #include "../lib/libartnet/artnet/artnet.h" class Artnet : public X32Base { + using enum MP_ID; public: Artnet(X32BaseParameter* basepar); void Init(); diff --git a/src/base.h b/src/base.h index 9cc130c..1131ccd 100644 --- a/src/base.h +++ b/src/base.h @@ -14,6 +14,10 @@ class X32Base State* state; Helper* helper; +#ifdef BODYLESS_SDL2 + friend class SimulatorGUI; +#endif + public: X32Base(X32BaseParameter* basepar); }; \ No newline at end of file diff --git a/src/ctrl.cpp b/src/ctrl.cpp index 9e662f7..28ca41d 100644 --- a/src/ctrl.cpp +++ b/src/ctrl.cpp @@ -65,6 +65,7 @@ void X32Ctrl::Init() helper->DEBUG_X32CTRL(DEBUGLEVEL_NORMAL, "mixer->Init()"); mixer->Init(); + UpdateFaderBanks(); helper->DEBUG_X32CTRL(DEBUGLEVEL_NORMAL, "surface->Init()"); surface->Init(); @@ -187,6 +188,12 @@ void X32Ctrl::Tick10ms(void) artnet->Sync(); #endif + if (config->HasParametersChanged({CHANNEL_LINKED, BUS_LINKED, MATRIX_LINKED})) + { + UpdateFaderBanks(); + ReloadLoadedBanks(); + } + syncGuiOrLcd(); //syncXRemote(false); } @@ -651,6 +658,7 @@ void X32Ctrl::InitPagesAndGUI() pages[X32_PAGE::SETUP] = new PageSetup(pagebasepar); pages[X32_PAGE::SETUP_CARD] = new PageSetupCard(pagebasepar); pages[X32_PAGE::SETUP_SURFACE] = new PageSetupSurface(pagebasepar); + pages[X32_PAGE::SETUP_MIXER_CONFIG] = new PageSetupMixerConfig(pagebasepar); pages[X32_PAGE::ABOUT] = new PageAbout(pagebasepar); pages[X32_PAGE::DEBUG] = new PageDebug(pagebasepar); pages[X32_PAGE::PROTOTYPEGUI] = new PagePrototypeGui(pagebasepar); @@ -1076,7 +1084,7 @@ void X32Ctrl::syncSurface(bool fullSync) switch(config->GetUint(CHANNEL_LCD_MODE)) { case 0: - if (config->HasParametersChanged({CHANNEL_PANORAMA, CHANNEL_NAME, CHANNEL_COLOR, CHANNEL_COLOR_INVERTED }, binding_parameter->mp_index) || + if (config->HasParametersChanged({CHANNEL_PANORAMA, CHANNEL_STEREO_PAN, CHANNEL_STEREO_WIDTH, CHANNEL_NAME, CHANNEL_COLOR, CHANNEL_COLOR_INVERTED }, binding_parameter->mp_index) || config->HasParameterChanged(CHANNEL_LCD_MODE) ) { @@ -1084,7 +1092,7 @@ void X32Ctrl::syncSurface(bool fullSync) } break; case 1: - if (config->HasParametersChanged({CHANNEL_PHASE_INVERT, CHANNEL_VOLUME, CHANNEL_PANORAMA, CHANNEL_GAIN, CHANNEL_GATE_TRESHOLD, + if (config->HasParametersChanged({CHANNEL_PHASE_INVERT, CHANNEL_VOLUME, CHANNEL_PANORAMA, CHANNEL_STEREO_PAN, CHANNEL_STEREO_WIDTH, CHANNEL_GAIN, CHANNEL_GATE_TRESHOLD, CHANNEL_DYNAMICS_TRESHOLD, CHANNEL_PHANTOM, CHANNEL_NAME, CHANNEL_COLOR, CHANNEL_COLOR_INVERTED }, binding_parameter->mp_index) || config->HasParametersChanged({MP_CAT::CHANNEL_EQ}, binding_parameter->mp_index) || config->HasParameterChanged(CHANNEL_LCD_MODE) @@ -1453,6 +1461,45 @@ void X32Ctrl::SetLcdFromChannel(uint8_t p_boardId, uint8_t lcdIndex, uint8_t cha LcdData* data = new LcdData(); uint textcount = 0; + auto getBalanceTextIndex = [](float balance) -> uint8_t + { + if (balance < -70){ return 0; } + else if (balance < -40){ return 1; } + else if (balance < -10){ return 2; } + else if (balance > 70){ return 6; } + else if (balance > 40){ return 5; } + else if (balance > 10){ return 4; } + return 3; + }; + auto setBalanceText = [&](char* balanceText, uint8_t channelIndex) + { + uint peerIdx = 0; + bool isStereo = config->GetPeerVChannel(channelIndex, peerIdx); + float balance = config->GetFloat(isStereo ? CHANNEL_STEREO_PAN : CHANNEL_PANORAMA, channelIndex); + uint8_t balanceIndex = getBalanceTextIndex(balance); + + if (isStereo) + { + float width = config->GetFloat(CHANNEL_STEREO_WIDTH, channelIndex); + if (width < 0.995f) + { + float widthPan = width * CHANNEL_PANORAMA_MAX; + uint8_t leftIndex = getBalanceTextIndex(helper->Saturate(balance - widthPan, CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX)); + uint8_t rightIndex = getBalanceTextIndex(helper->Saturate(balance + widthPan, CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX)); + if (leftIndex == rightIndex) + { + if (balanceIndex > 0) { leftIndex = balanceIndex - 1; } + if (balanceIndex < 6) { rightIndex = balanceIndex + 1; } + } + if (leftIndex == balanceIndex && leftIndex > 0) { leftIndex--; } + if (rightIndex == balanceIndex && rightIndex < 6) { rightIndex++; } + balanceText[leftIndex] = ':'; + balanceText[rightIndex] = ':'; + } + } + + balanceText[balanceIndex] = '|'; + }; switch(config->GetUint(CHANNEL_LCD_MODE)) { @@ -1469,24 +1516,8 @@ void X32Ctrl::SetLcdFromChannel(uint8_t p_boardId, uint8_t lcdIndex, uint8_t cha // Volume / Panorama - float balance = config->GetFloat(CHANNEL_PANORAMA, channelIndex); - char balanceText[8] = "-------"; - if (balance < -70){ - balanceText[0] = '|'; - } else if (balance < -40){ - balanceText[1] = '|'; - } else if (balance < -10){ - balanceText[2] = '|'; - } else if (balance > 70){ - balanceText[6] = '|'; - } else if (balance > 40){ - balanceText[5] = '|'; - } else if (balance > 10){ - balanceText[4] = '|'; - } else { - balanceText[3] = '|'; - } + setBalanceText(balanceText, channelIndex); data->texts[textIndex].text = balanceText; data->texts[textIndex].size = 0; data->texts[textIndex].x = 0; @@ -1503,7 +1534,24 @@ void X32Ctrl::SetLcdFromChannel(uint8_t p_boardId, uint8_t lcdIndex, uint8_t cha textIndex++; // Channel Internal Name - data->texts[textIndex].text = config->GetString(CHANNEL_NAME_INTERN, channelIndex); + String displayInternName = config->GetString(CHANNEL_NAME_INTERN, channelIndex); + uint peerIdx = 0; + if (config->GetPeerVChannel(channelIndex, peerIdx)) + { + String prefix = ""; + String digits = ""; + for (uint k = 0; k < displayInternName.length(); k++) + { + if (displayInternName[k] >= '0' && displayInternName[k] <= '9') { digits += displayInternName[k]; } + else { prefix += displayInternName[k]; } + } + if (digits.length() > 0) + { + int val1 = atoi(digits.c_str()); + displayInternName = prefix + String(val1) + "/" + String(val1 + 1); + } + } + data->texts[textIndex].text = displayInternName; data->texts[textIndex].size = 0; data->texts[textIndex].x = 35; data->texts[textIndex].y = 51; @@ -1527,7 +1575,10 @@ void X32Ctrl::SetLcdFromChannel(uint8_t p_boardId, uint8_t lcdIndex, uint8_t cha data->texts[0].y = 0; // Phanton / Invert / Gate / Dynamics / EQ active + uint peerIdx = 0; + String stereoText = config->GetPeerVChannel(channelIndex, peerIdx) ? "ST " : ""; data->texts[1].text = + stereoText + String(config->GetBool(CHANNEL_PHANTOM, channelIndex) ? "48V " : " ") + String(config->GetBool(CHANNEL_PHASE_INVERT, channelIndex) ? "@ " : " ") + String(config->GetFloat(CHANNEL_GATE_TRESHOLD, channelIndex) > -80.0f ? "G " : " ") + @@ -1540,24 +1591,8 @@ void X32Ctrl::SetLcdFromChannel(uint8_t p_boardId, uint8_t lcdIndex, uint8_t cha // Volume / Panorama - float balance = config->GetFloat(CHANNEL_PANORAMA, channelIndex); - char balanceText[8] = "-------"; - if (balance < -70){ - balanceText[0] = '|'; - } else if (balance < -40){ - balanceText[1] = '|'; - } else if (balance < -10){ - balanceText[2] = '|'; - } else if (balance > 70){ - balanceText[6] = '|'; - } else if (balance > 40){ - balanceText[5] = '|'; - } else if (balance > 10){ - balanceText[4] = '|'; - } else { - balanceText[3] = '|'; - } + setBalanceText(balanceText, channelIndex); float volume = config->GetFloat(CHANNEL_VOLUME, channelIndex); if (volume > -100) { @@ -3040,4 +3075,116 @@ void X32Ctrl::SimulatorButton(uint32_t key) ProcessSurface(X32_BOARD_R, 'b', 0, 0x00); break; } -} \ No newline at end of file +} + +void X32Ctrl::UpdateFaderBanks() +{ + // Re-populate CH1_8, CH9_16, CH17_24, CH25_32 + PopulateBankWithActive(banks[(uint)X32BankId::CH1_8], X32_VCHANNEL_BLOCK::NORMAL, 0); + PopulateBankWithActive(banks[(uint)X32BankId::CH9_16], X32_VCHANNEL_BLOCK::NORMAL, 8); + PopulateBankWithActive(banks[(uint)X32BankId::CH17_24], X32_VCHANNEL_BLOCK::NORMAL, 16); + PopulateBankWithActive(banks[(uint)X32BankId::CH25_32], X32_VCHANNEL_BLOCK::NORMAL, 24); + + // Re-populate AUX_USB + PopulateBankWithActive(banks[(uint)X32BankId::AUX_USB], X32_VCHANNEL_BLOCK::AUX, 0); + + // Re-populate FX_RET + PopulateBankWithActive(banks[(uint)X32BankId::FX_RET], X32_VCHANNEL_BLOCK::FXRET, 0); + + // Re-populate BUS1_8, BUS9_16 + PopulateBankWithActive(banks[(uint)X32BankId::BUS1_8], X32_VCHANNEL_BLOCK::BUS, 0); + PopulateBankWithActive(banks[(uint)X32BankId::BUS9_16], X32_VCHANNEL_BLOCK::BUS, 8); + + // Re-populate MATRIX_MAIN + vector activeMatrix = GetActiveChannels(X32_VCHANNEL_BLOCK::MATRIX); + X32FaderBank* matrixBank = banks[(uint)X32BankId::MATRIX_MAIN]; + for (uint i = 0; i < 8; i++) + { + if (i < activeMatrix.size()) + { + SetChannelstripBinding(matrixBank, i, activeMatrix[i]); + } + else + { + uint extraIdx = i - activeMatrix.size(); + if (extraIdx == 0) + { + SetChannelstripBinding(matrixBank, i, 70); // SPECIAL + } + else if (extraIdx == 1) + { + SetChannelstripBinding(matrixBank, i, 71); // MAINSUB + } + else + { + matrixBank->channelstrip[i]->select->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + matrixBank->channelstrip[i]->vumeter->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + matrixBank->channelstrip[i]->solo->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + matrixBank->channelstrip[i]->lcd->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + matrixBank->channelstrip[i]->mute->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + matrixBank->channelstrip[i]->fader->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + } + } + } +} + +void X32Ctrl::ReloadLoadedBanks() +{ + if (bankLoadedInputsection != nullptr) + { + LoadBank(X32BankTarget::InputSection, bankLoadedInputsection->GetID()); + } + if (bankLoadedInputsection2 != nullptr) + { + LoadBank(X32BankTarget::InputSection2, bankLoadedInputsection2->GetID()); + } + if (bankLoadedBussection != nullptr) + { + LoadBank(X32BankTarget::BusSection, bankLoadedBussection->GetID()); + } +} + +vector X32Ctrl::GetActiveChannels(X32_VCHANNEL_BLOCK blockType) +{ + vector list; + uint start = 0; + uint size = 0; + if (blockType == X32_VCHANNEL_BLOCK::NORMAL) { start = 0; size = 32; } + else if (blockType == X32_VCHANNEL_BLOCK::AUX) { start = 32; size = 8; } + else if (blockType == X32_VCHANNEL_BLOCK::FXRET) { start = 40; size = 8; } + else if (blockType == X32_VCHANNEL_BLOCK::BUS) { start = 48; size = 16; } + else if (blockType == X32_VCHANNEL_BLOCK::MATRIX) { start = 64; size = 6; } + else { return list; } + + for (uint i = 0; i < size; i++) + { + uint chanIdx = start + i; + if (!config->IsRightChannelOfLinkedPair(chanIdx)) + { + list.push_back(chanIdx); + } + } + return list; +} + +void X32Ctrl::PopulateBankWithActive(X32FaderBank* bank, X32_VCHANNEL_BLOCK blockType, uint activeOffset) +{ + vector active = GetActiveChannels(blockType); + for (uint i = 0; i < 8; i++) + { + uint activeIdx = activeOffset + i; + if (activeIdx < active.size()) + { + SetChannelstripBinding(bank, i, active[activeIdx]); + } + else + { + bank->channelstrip[i]->select->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + bank->channelstrip[i]->vumeter->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + bank->channelstrip[i]->solo->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + bank->channelstrip[i]->lcd->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + bank->channelstrip[i]->mute->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + bank->channelstrip[i]->fader->FillBindingParameter(MixerparameterAction::NONE, MP_ID::NONE, 0); + } + } +} diff --git a/src/ctrl.h b/src/ctrl.h index 1798fd1..ba3b436 100755 --- a/src/ctrl.h +++ b/src/ctrl.h @@ -32,6 +32,7 @@ #include "page-setup.h" #include "page-setup-card.h" #include "page-setup-surface.h" +#include "page-setup-mixer-config.h" #include "page-debug.h" #include "page-about.h" #include "page-scenes.h" @@ -111,6 +112,11 @@ class X32Ctrl : public X32Base void LoadAssignBank(X32AssignBankId id); void LoadDefaultSurfaceBinding(); void LoadMainFaderSurfaceBinding(); + + void UpdateFaderBanks(); + void ReloadLoadedBanks(); + vector GetActiveChannels(X32_VCHANNEL_BLOCK blockType); + void PopulateBankWithActive(X32FaderBank* bank, X32_VCHANNEL_BLOCK blockType, uint activeOffset); int surfacePacketCurrentIndex = 0; int surfacePacketCurrent = 0; @@ -124,6 +130,10 @@ class X32Ctrl : public X32Base uint autosavewait = 0; void AutoSave(); +#ifdef BODYLESS_SDL2 + friend class SimulatorGUI; +#endif + public: X32Ctrl(X32BaseParameter* basepar); diff --git a/src/enum.h b/src/enum.h index 3bd76b7..a3fce92 100644 --- a/src/enum.h +++ b/src/enum.h @@ -391,6 +391,7 @@ enum class X32_PAGE :int // sub-pages of setup SETUP_CARD, SETUP_SURFACE, + SETUP_MIXER_CONFIG, ABOUT, DEBUG, PROTOTYPEGUI, @@ -498,6 +499,9 @@ enum class MP_ID { DISPLAY_BRIGHTNESS, SAMPLERATE, CHANNEL_LCD_MODE, + CHANNEL_LINKED, + BUS_LINKED, + MATRIX_LINKED, // Card CARD_NUMBER_OF_CHANNELS, @@ -586,6 +590,8 @@ enum class MP_ID { CHANNEL_COLOR_INVERTED, CHANNEL_GAIN, CHANNEL_PANORAMA, + CHANNEL_STEREO_PAN, + CHANNEL_STEREO_WIDTH, CHANNEL_VOLUME, CHANNEL_VOLUME_SUB, CHANNEL_MUTE, @@ -1346,4 +1352,4 @@ enum class X32BankTarget InputSection, InputSection2, BusSection -}; \ No newline at end of file +}; diff --git a/src/external.h b/src/external.h index d8098af..834631e 100644 --- a/src/external.h +++ b/src/external.h @@ -9,7 +9,9 @@ #include #include #include +#ifndef __APPLE__ #include +#endif #include #include diff --git a/src/lcd-menu.h b/src/lcd-menu.h index ad88f76..6bea9d9 100644 --- a/src/lcd-menu.h +++ b/src/lcd-menu.h @@ -10,6 +10,7 @@ using namespace std; class LcdMenu : public X32Base { private: + using enum MP_ID; Mixer* mixer; Surface* surface; bool initDone = false; diff --git a/src/main.cpp b/src/main.cpp index fe82379..3c3e199 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,6 +48,9 @@ #include "ctrl.h" +#ifdef BODYLESS_SDL2 +#include "simulator-gui.h" +#endif X32Ctrl* ctrl; State* state; @@ -61,9 +64,11 @@ static lv_indev_t *mouse_wheel; static lv_indev_t *keyboard; #endif +#ifndef __APPLE__ timer_t timerid_10ms; struct sigevent sev_10ms; struct itimerspec trigger_10ms; +#endif uint8_t vtimercounter = 0; void timer100msCallbackLvgl(_lv_timer_t* lv_timer) { @@ -77,6 +82,8 @@ void timer50msCallbackLvgl(_lv_timer_t* lv_timer) { void timer10msCallbackLvgl(_lv_timer_t* lv_timer) { ui_tick(); ctrl->Tick10ms(); } + +#ifndef __APPLE__ void timer10msCallbackLinux(int timer) { ctrl->Tick10ms(); @@ -122,8 +129,10 @@ void init10msTimer_NonGUI(void) { perror("timer_settime"); } } +#endif void guiInit(X32Config* config) { + using enum MP_ID; lv_init(); @@ -174,6 +183,12 @@ void guiInit(X32Config* config) { printf("ctrl->InitPagesAndGUI()\n"); ctrl->InitPagesAndGUI(); +#ifdef BODYLESS_SDL2 + if (state->bodyless) { + SimulatorGUI::Init(ctrl); + } +#endif + // trigger first update of display header printf("config->Refresh(SELECTED_CHANNEL)\n"); config->Refresh(SELECTED_CHANNEL); @@ -207,6 +222,7 @@ void guiInit(X32Config* config) { while (1) { + SimulatorGUI::Tick(); idle_time = lv_timer_handler(); usleep(idle_time * 1000); } @@ -397,7 +413,11 @@ int main(int argc, char* argv[]) { if (config->IsModelX32Core()){ // only necessary if LVGL is not used helper->Log("Starting Timers...\n"); +#ifndef __APPLE__ init10msTimer_NonGUI(); +#else + helper->Log("Timers not supported on macOS.\n"); +#endif helper->Log("Press Ctrl+C to terminate program.\n"); while (1) { diff --git a/src/mixer.h b/src/mixer.h index abd06f7..f241d5c 100644 --- a/src/mixer.h +++ b/src/mixer.h @@ -20,10 +20,9 @@ #include "card.h" using namespace std; -using enum MP_ID; - class Mixer : public X32Base { + using enum MP_ID; private: // solo is (somewhere) activated diff --git a/src/mixerparameter.h b/src/mixerparameter.h index 67ae0aa..c8e6d7d 100644 --- a/src/mixerparameter.h +++ b/src/mixerparameter.h @@ -9,6 +9,9 @@ #include "defines.h" #include "enum.h" +#include +#include + using namespace std; using namespace WString; @@ -83,7 +86,7 @@ class Mixerparameter { if (index >= instances) { - __throw_out_of_range((String("The index ") + String(index) + String(" is bigger than the specified instances of ") + String(instances) + String(" (zero based!) of the Mixerparameter ") + GetName() + String(".")).c_str()); + throw std::out_of_range((String("The index ") + String(index) + String(" is bigger than the specified instances of ") + String(instances) + String(" (zero based!) of the Mixerparameter ") + GetName() + String(".")).c_str()); } } @@ -94,7 +97,7 @@ class Mixerparameter { if (mp_value_type != value_type) { - __throw_bad_typeid(); + throw std::bad_typeid(); } } @@ -104,7 +107,7 @@ class Mixerparameter { if (readonly) { - __throw_logic_error((String("The Mixerparameter ") + GetName() + String(" can not be changed, it is readonly.")).c_str()); + throw std::logic_error((String("The Mixerparameter ") + GetName() + String(" can not be changed, it is readonly.")).c_str()); } } @@ -386,6 +389,17 @@ class Mixerparameter { using enum MP_UOM; + if (parameter_id == MP_ID::CHANNEL_STEREO_WIDTH) + { + uint width_percent = (uint)(value_float * 100.0f + 0.5f); + if (width_percent == 0) + { + return String("Mono"); + } + + return String(width_percent) + String("%"); + } + if (unitOfMeasurement == PERCENT) { value_float *= 100.0f; } @@ -884,7 +898,7 @@ class Mixerparameter if (stepsize == 0) { - __throw_logic_error((String("Stepsize of Mixerparameter ") + GetName() + String(" is 0, so no change can happen!")).c_str()); + throw std::logic_error((String("Stepsize of Mixerparameter ") + GetName() + String(" is 0, so no change can happen!")).c_str()); } float newValue; @@ -1143,4 +1157,4 @@ namespace glz serialize::op(value.c_str(), ctx, b, ix); } }; -} \ No newline at end of file +} diff --git a/src/page-about.h b/src/page-about.h index 8fc7f2b..868177e 100755 --- a/src/page-about.h +++ b/src/page-about.h @@ -5,12 +5,12 @@ using namespace std; class PageAbout: public Page { public: PageAbout(PageBaseParameter* pagebasepar) : Page(pagebasepar) { - prevPage = X32_PAGE::SETUP_SURFACE; + prevPage = X32_PAGE::SETUP_MIXER_CONFIG; nextPage = X32_PAGE::DEBUG; tabLayer0 = objects.maintab; tabIndex0 = 3; tabLayer1 = objects.setuptab; - tabIndex1 = 3; + tabIndex1 = 4; led = X32_BTN_SETUP; hideEncoders = true; } diff --git a/src/page-debug.h b/src/page-debug.h index f55187d..27330a4 100644 --- a/src/page-debug.h +++ b/src/page-debug.h @@ -12,7 +12,7 @@ class PageDebug: public Page tabLayer0 = objects.maintab; tabIndex0 = 3; tabLayer1 = objects.setuptab; - tabIndex1 = 4; + tabIndex1 = 5; led = X32_BTN_SETUP; } diff --git a/src/page-home.h b/src/page-home.h index a8ea9fa..e7eb47e 100644 --- a/src/page-home.h +++ b/src/page-home.h @@ -7,7 +7,6 @@ class PageHome : public Page using enum MP_ID; private: - lv_obj_t* vumeters[MAX_DISPLAY_ENCODER] = { objects.home_vumeter_1, @@ -19,12 +18,9 @@ class PageHome : public Page }; uint lastImageOffset[MAX_DISPLAY_ENCODER] = {0, 0, 0, 0, 0, 0}; - uint channelindex[6] = {0, 1, 2, 3, 4, 5}; - public: - PageHome(PageBaseParameter* pagebasepar) : Page(pagebasepar) { nextPage = X32_PAGE::CONFIG; @@ -37,7 +33,14 @@ class PageHome : public Page void OnShow() override { - EncoderBind_NormalMode(); + if (config->GetBool(DISPLAY_UTILITY)) + { + EncoderBind_EditMode(); + } + else + { + EncoderBind_NormalMode(); + } } void EncoderBind_NormalMode() @@ -61,7 +64,6 @@ class PageHome : public Page { String edittext = String("\n\nSelect ") + String(LV_SYMBOL_REFRESH); - // Show the Channelname config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_1, config->GetString(CHANNEL_NAME, channelindex[0]) + edittext); config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_2, config->GetString(CHANNEL_NAME, channelindex[1]) + edittext); config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_3, config->GetString(CHANNEL_NAME, channelindex[2]) + edittext); @@ -69,7 +71,6 @@ class PageHome : public Page config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_5, config->GetString(CHANNEL_NAME, channelindex[4]) + edittext); config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_6, config->GetString(CHANNEL_NAME, channelindex[5]) + edittext); - // Button has no function in this mode config->SurfaceUnbind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_1); config->SurfaceUnbind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_2); config->SurfaceUnbind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_3); @@ -101,6 +102,7 @@ class PageHome : public Page EncoderBind_NormalMode(); } } + } void OnChangeCustomEncoder(SurfaceElementId surface_element_id, int amount) @@ -134,7 +136,6 @@ class PageHome : public Page uint imageOffset = helper->rescale(dbValue, -100.0f, 10.0f, 0.0f, 31.0f); uint newImageOffset = imageOffset * -lv_obj_get_width(vumeters[i]); - // only set new offset if it has changed if (newImageOffset != lastImageOffset[i]) { lv_image_set_offset_x(vumeters[i], newImageOffset); @@ -142,4 +143,4 @@ class PageHome : public Page } } } -}; \ No newline at end of file +}; diff --git a/src/page-main.h b/src/page-main.h index eeb8c00..6b312f1 100644 --- a/src/page-main.h +++ b/src/page-main.h @@ -11,6 +11,34 @@ class PageMain : public Page uint bankingSends = 0; uint bankingSendsBefore = 0; + bool stereoControlsBound = false; + + void BindMainEncoders() + { + bool useStereoControls = config->IsStereoLinkedMainRouted(config->GetUint(SELECTED_CHANNEL)); + + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_1, MixerparameterAction::CHANGE_SELECTED_CHANNEL, CHANNEL_VOLUME); + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_1, MixerparameterAction::TOGGLE_SELECTED_CHANNEL, CHANNEL_SEND_LR); + + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_2, MixerparameterAction::CHANGE_SELECTED_CHANNEL, useStereoControls ? CHANNEL_STEREO_PAN : CHANNEL_PANORAMA); + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_2, MixerparameterAction::RESET_SELECTED_CHANNEL, useStereoControls ? CHANNEL_STEREO_PAN : CHANNEL_PANORAMA); + + if (useStereoControls) + { + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_3, MixerparameterAction::CHANGE_SELECTED_CHANNEL, CHANNEL_STEREO_WIDTH); + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_3, MixerparameterAction::RESET_SELECTED_CHANNEL, CHANNEL_STEREO_WIDTH); + } + else + { + config->SurfaceUnbind(SurfaceElementId::DISPLAY_ENCODER_3); + config->SurfaceUnbind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_3); + } + + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_4, MixerparameterAction::CHANGE_SELECTED_CHANNEL, CHANNEL_VOLUME_SUB); + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_4, MixerparameterAction::TOGGLE_SELECTED_CHANNEL, CHANNEL_SEND_SUB); + + stereoControlsBound = useStereoControls; + } public: @@ -27,11 +55,15 @@ class PageMain : public Page void OnShow() override { - config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_1, MixerparameterAction::CHANGE_SELECTED_CHANNEL, CHANNEL_VOLUME); - config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_1, MixerparameterAction::TOGGLE_SELECTED_CHANNEL, CHANNEL_SEND_LR); - config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_2, MixerparameterAction::CHANGE_SELECTED_CHANNEL, CHANNEL_PANORAMA); - config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_2, MixerparameterAction::RESET_SELECTED_CHANNEL, CHANNEL_PANORAMA); - config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_4, MixerparameterAction::CHANGE_SELECTED_CHANNEL, CHANNEL_VOLUME_SUB); - config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_4, MixerparameterAction::TOGGLE_SELECTED_CHANNEL, CHANNEL_SEND_SUB); + BindMainEncoders(); + } + + void OnChange(bool force_update) override + { + bool useStereoControls = config->IsStereoLinkedMainRouted(config->GetUint(SELECTED_CHANNEL)); + if (force_update || useStereoControls != stereoControlsBound || config->HasParametersChanged({CHANNEL_LINKED, BUS_LINKED, CHANNEL_SEND_LR})) + { + BindMainEncoders(); + } } -}; \ No newline at end of file +}; diff --git a/src/page-meters.h b/src/page-meters.h index c1e9fbc..e5de12d 100644 --- a/src/page-meters.h +++ b/src/page-meters.h @@ -2,10 +2,9 @@ #include "page.h" using namespace std; -using enum MP_ID; - class PageMeters : public Page { + using enum MP_ID; private: lv_obj_t* meterBlocks[9]; diff --git a/src/page-prototypegui.h b/src/page-prototypegui.h index bb21f20..e20053a 100755 --- a/src/page-prototypegui.h +++ b/src/page-prototypegui.h @@ -15,7 +15,7 @@ class PagePrototypeGui: public Page tabLayer0 = objects.maintab; tabIndex0 = 3; tabLayer1 = objects.setuptab; - tabIndex1 = 5; + tabIndex1 = 6; led = X32_BTN_SETUP; } diff --git a/src/page-routing-channels.h b/src/page-routing-channels.h index 2e20deb..b1722c0 100644 --- a/src/page-routing-channels.h +++ b/src/page-routing-channels.h @@ -6,7 +6,9 @@ using namespace std; class PageRoutingChannels: public Page { +#ifndef __clang__ using enum MP_ID; +#endif private: @@ -110,34 +112,164 @@ class PageRoutingChannels: public Page } } + vector GetActiveRoutingChannels() + { + vector list; + for (uint i = 0; i < 40; i++) + { + if (!config->IsRightChannelOfLinkedPair(i)) + { + list.push_back(i); + } + } + return list; + } + + void RebuildTable() + { + vector active = GetActiveRoutingChannels(); + uint rowCount = active.size(); + if (rowCount == 0) return; + + lv_table_set_row_count(objects.table_routing_dsp_input, rowCount); + + for (uint i = 0; i < rowCount; i++) + { + uint chanIdx = active[i]; + + // Format Destination text + String inputChannelName; + if (chanIdx < 40) + { + // Check if this channel is the left of a linked pair + bool isLinked = false; + uint linkIdx = chanIdx < 32 ? chanIdx / 2 : 16 + (chanIdx - 32) / 2; + if (chanIdx % 2 == 0) + { + isLinked = config->GetBool(CHANNEL_LINKED, linkIdx); + } + if (isLinked) + { + if (chanIdx < 32) + { + inputChannelName = String("Channel ") + (chanIdx + 1) + "/" + (chanIdx + 2); + } + else + { + inputChannelName = String("Aux ") + (chanIdx - 32 + 1) + "/" + (chanIdx - 32 + 2); + } + } + else + { + if (chanIdx < 32) + { + inputChannelName = String("Channel ") + (chanIdx + 1); + } + else + { + inputChannelName = String("Aux ") + (chanIdx - 32 + 1); + } + } + } + + // Format Source text + String sourceName; + if (chanIdx < 40 && chanIdx % 2 == 0 && config->GetBool(CHANNEL_LINKED, chanIdx < 32 ? chanIdx / 2 : 16 + (chanIdx - 32) / 2)) + { + // It's a linked stereo pair. Display both left and right sources. + uint routingL = config->GetUint(ROUTING_DSP_INPUT, chanIdx); + uint routingR = config->GetUint(ROUTING_DSP_INPUT, chanIdx + 1); + + String srcL_fpga = config->GetParameter(ROUTING_FPGA)->GetFormatedValue(72 + chanIdx); + String srcL_dsp = config->GetParameter(ROUTING_DSP_INPUT)->GetFormatedValue(chanIdx); + String srcR_fpga = config->GetParameter(ROUTING_FPGA)->GetFormatedValue(72 + chanIdx + 1); + String srcR_dsp = config->GetParameter(ROUTING_DSP_INPUT)->GetFormatedValue(chanIdx + 1); + + String fullL = (routingL >= 1 && routingL <= 40) ? (srcL_fpga + " -> " + srcL_dsp) : srcL_dsp; + String fullR = (routingR >= 1 && routingR <= 40) ? (srcR_fpga + " -> " + srcR_dsp) : srcR_dsp; + + sourceName = fullL + " / " + fullR; + } + else + { + // Single channel routing + uint routingVal = config->GetUint(ROUTING_DSP_INPUT, chanIdx); + String src_fpga = config->GetParameter(ROUTING_FPGA)->GetFormatedValue(72 + chanIdx); + String src_dsp = config->GetParameter(ROUTING_DSP_INPUT)->GetFormatedValue(chanIdx); + + if (routingVal >= 1 && routingVal <= 40) + { + sourceName = src_fpga + " -> " + src_dsp; + } + else + { + sourceName = src_dsp; + } + } + + // Set cell values + lv_table_set_cell_value(objects.table_routing_dsp_input, i, 0, sourceName.c_str()); + lv_table_set_cell_value(objects.table_routing_dsp_input, i, 1, " "); + lv_table_set_cell_value(objects.table_routing_dsp_input, i, 2, config->GetParameter(ROUTING_DSP_INPUT_TAPPOINT)->GetFormatedValue(chanIdx).c_str()); + lv_table_set_cell_value(objects.table_routing_dsp_input, i, 3, " "); + lv_table_set_cell_value(objects.table_routing_dsp_input, i, 4, inputChannelName.c_str()); + } + + // Reapply selection indicators + if (gui_selected_item >= (int)rowCount) + { + gui_selected_item = rowCount - 1; + } + if (gui_selected_item < 0) + { + gui_selected_item = 0; + } + gui_selected_item_before = gui_selected_item; + + lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 1, LV_SYMBOL_RIGHT); + lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 3, LV_SYMBOL_RIGHT); + lv_table_set_selected_cell(objects.table_routing_dsp_input, gui_selected_item, 2); + } + void UpdateRowSelection() { - if(gui_selected_item_before != gui_selected_item) - { - if (gui_selected_item < 0) - { - // limit list at the top - gui_selected_item = 0; - } - else if (gui_selected_item >= 40) - { - // limit list at the bottom - gui_selected_item = 40 - 1; - } - - // remove old indicator - lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item_before, 1, " "); - lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item_before, 3, " "); + vector active = GetActiveRoutingChannels(); + int rowCount = (int)active.size(); + if (rowCount == 0) return; + + if (gui_selected_item < 0) + { + gui_selected_item = 0; + } + else if (gui_selected_item >= rowCount) + { + gui_selected_item = rowCount - 1; + } + + if (gui_selected_item_before < 0) + { + gui_selected_item_before = 0; + } + else if (gui_selected_item_before >= rowCount) + { + gui_selected_item_before = rowCount - 1; + } + + if(gui_selected_item_before != gui_selected_item) + { + // remove old indicator + lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item_before, 1, " "); + lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item_before, 3, " "); - // display new indicator - lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 1, LV_SYMBOL_RIGHT); - lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 3, LV_SYMBOL_RIGHT); + // display new indicator + lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 1, LV_SYMBOL_RIGHT); + lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 3, LV_SYMBOL_RIGHT); - // set select to scroll table - lv_table_set_selected_cell(objects.table_routing_dsp_input, gui_selected_item, 2); + // set select to scroll table + lv_table_set_selected_cell(objects.table_routing_dsp_input, gui_selected_item, 2); - gui_selected_item_before = gui_selected_item; - } + gui_selected_item_before = gui_selected_item; + } } public: @@ -171,32 +303,14 @@ class PageRoutingChannels: public Page // Selection-Table lv_table_set_row_count(objects.table_routing_dsp_input, 40); /*Not required but avoids a lot of memory reallocation lv_table_set_set_value*/ - lv_table_set_column_count(objects.table_routing_dsp_input, 5); // Input | # | Source | # | Tap - lv_table_set_column_width(objects.table_routing_dsp_input, 0, 400); - lv_table_set_column_width(objects.table_routing_dsp_input, 1, 50); - lv_table_set_column_width(objects.table_routing_dsp_input, 2, 100); - lv_table_set_column_width(objects.table_routing_dsp_input, 3, 50); - lv_table_set_column_width(objects.table_routing_dsp_input, 4, 200); - for (uint8_t i = 0; i < 40; i++) - { - String inputChannelName; - if (((i + 1) >= DSP_BUF_IDX_DSPCHANNEL) && ((i + 1) < (DSP_BUF_IDX_DSPCHANNEL + 32))) - { - inputChannelName = String("Channel ") + (i + 1); - - } - else if (((i + 1) >= DSP_BUF_IDX_AUX) && ((i + 1) < (DSP_BUF_IDX_AUX + 8))) - { - inputChannelName = String("Aux ") + (i + 1 - 32); - } + lv_table_set_column_count(objects.table_routing_dsp_input, 5); // Input | # | Source | # | Tap + lv_table_set_column_width(objects.table_routing_dsp_input, 0, 400); + lv_table_set_column_width(objects.table_routing_dsp_input, 1, 50); + lv_table_set_column_width(objects.table_routing_dsp_input, 2, 100); + lv_table_set_column_width(objects.table_routing_dsp_input, 3, 50); + lv_table_set_column_width(objects.table_routing_dsp_input, 4, 200); - lv_table_set_cell_value(objects.table_routing_dsp_input, i, 0, (config->GetParameter(ROUTING_FPGA)->GetFormatedValue(72 + i) + " -> " + config->GetParameter(ROUTING_DSP_INPUT)->GetFormatedValue(i)).c_str()); - lv_table_set_cell_value(objects.table_routing_dsp_input, i, 2, config->GetParameter(ROUTING_DSP_INPUT_TAPPOINT)->GetFormatedValue(i).c_str()); - lv_table_set_cell_value(objects.table_routing_dsp_input, i, 4, inputChannelName.c_str()); - } - - lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 1, LV_SYMBOL_RIGHT); - lv_table_set_cell_value(objects.table_routing_dsp_input, gui_selected_item, 3, LV_SYMBOL_RIGHT); + RebuildTable(); lv_obj_add_event_cb(objects.table_routing_dsp_input, draw_event_cb, LV_EVENT_DRAW_TASK_ADDED, NULL); lv_obj_add_flag(objects.table_routing_dsp_input, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS); @@ -212,17 +326,18 @@ class PageRoutingChannels: public Page config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_3, String(LV_SYMBOL_REFRESH) + String("\nSource")); config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_4, String(LV_SYMBOL_REFRESH) + String("\nSource (8)")); - config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_5, String(LV_SYMBOL_REFRESH) + String("\nTAP")); + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_5, String(LV_SYMBOL_REFRESH) + String("\nTappoint")); + + RebuildTable(); } void OnChange(bool force) override { - UpdateRowSelection(); - + /* Original code: if(config->HasParametersChanged({ROUTING_DSP_INPUT, ROUTING_DSP_INPUT_TAPPOINT}) || force) { for(auto const& index : config->GetChangedParameterIndexes({ROUTING_DSP_INPUT, ROUTING_DSP_INPUT_TAPPOINT})) - { + { if ((config->GetUint(ROUTING_DSP_INPUT, index) >= 1) && (config->GetUint(ROUTING_DSP_INPUT, index) <= 40)) { // external signal from FPGA lv_table_set_cell_value(objects.table_routing_dsp_input, index, 0, (config->GetParameter(ROUTING_FPGA)->GetFormatedValue(FPGA_OUTPUT_IDX_DSP - 1 + index) + " -> " + config->GetParameter(ROUTING_DSP_INPUT)->GetFormatedValue(index)).c_str()); @@ -233,10 +348,25 @@ class PageRoutingChannels: public Page lv_table_set_cell_value(objects.table_routing_dsp_input, index, 2, config->GetParameter(ROUTING_DSP_INPUT_TAPPOINT)->GetFormatedValue(index).c_str()); } } + */ + if (config->HasParametersChanged({CHANNEL_LINKED, ROUTING_DSP_INPUT, ROUTING_DSP_INPUT_TAPPOINT}) || force) + { + RebuildTable(); + } + else + { + UpdateRowSelection(); + } } void OnChangeCustomEncoder(SurfaceElementId surface_element_id, int amount) override { + vector active = GetActiveRoutingChannels(); + if (active.empty()) return; + + if (gui_selected_item < 0) gui_selected_item = 0; + if (gui_selected_item >= (int)active.size()) gui_selected_item = active.size() - 1; + switch (surface_element_id) { case SurfaceElementId::DISPLAY_ENCODER_1: @@ -252,9 +382,10 @@ class PageRoutingChannels: public Page UpdateRowSelection(); break; case SurfaceElementId::DISPLAY_ENCODER_3: - config->Change(ROUTING_DSP_INPUT, amount, gui_selected_item); + config->Change(ROUTING_DSP_INPUT, amount, active[gui_selected_item]); break; case SurfaceElementId::DISPLAY_ENCODER_4: + /* Original code: int8_t absoluteChange; if (amount < 0) { absoluteChange = -8; @@ -264,12 +395,22 @@ class PageRoutingChannels: public Page for (uint8_t i = (gui_selected_item); i < (gui_selected_item + 8); i++) { config->Change(ROUTING_DSP_INPUT, absoluteChange, i); } + */ + { + int8_t absoluteChange = (amount < 0) ? -8 : 8; + for (uint8_t i = 0; i < 8; i++) { + uint targetRow = gui_selected_item + i; + if (targetRow < active.size()) { + config->Change(ROUTING_DSP_INPUT, absoluteChange, active[targetRow]); + } + } + } break; case SurfaceElementId::DISPLAY_ENCODER_5: - config->Change(ROUTING_DSP_INPUT_TAPPOINT, amount, gui_selected_item); + config->Change(ROUTING_DSP_INPUT_TAPPOINT, amount, active[gui_selected_item]); break; default: break; } } -}; \ No newline at end of file +}; diff --git a/src/page-routing-dsp.h b/src/page-routing-dsp.h index a38d0d7..e62a17d 100644 --- a/src/page-routing-dsp.h +++ b/src/page-routing-dsp.h @@ -3,10 +3,9 @@ #include "page.h" using namespace std; -using enum MP_ID; - class PageRoutingDsp: public Page { + using enum MP_ID; private: int gui_selected_item = 0; diff --git a/src/page-rta.h b/src/page-rta.h index c3c482d..aa8baa8 100644 --- a/src/page-rta.h +++ b/src/page-rta.h @@ -2,10 +2,9 @@ #include "page.h" using namespace std; -using enum MP_ID; - class PageRta : public Page { + using enum MP_ID; private: lv_chart_series_t* chartSeriesRta; uint8_t updateRateReducer = 0; diff --git a/src/page-scenes.h b/src/page-scenes.h index 0186b2b..f7b23a1 100644 --- a/src/page-scenes.h +++ b/src/page-scenes.h @@ -2,10 +2,9 @@ #include "page.h" using namespace std; -using enum MP_ID; - class PageScenes: public Page { + using enum MP_ID; private: int configindex = 0; diff --git a/src/page-setup-mixer-config.h b/src/page-setup-mixer-config.h new file mode 100644 index 0000000..0fcd59a --- /dev/null +++ b/src/page-setup-mixer-config.h @@ -0,0 +1,404 @@ +#pragma once +#include "page.h" +#include "ctrl.h" + +using namespace std; + +class PageSetupMixerConfig: public Page +{ +#ifndef __clang__ + using enum MP_ID; +#endif + + private: + lv_obj_t* scroll_container = nullptr; + + static constexpr lv_coord_t ENCODER_HINT_RESERVED_HEIGHT = 60; + + // Navigation state for physical encoders + uint selected_type = 0; // 0: Channels/Auxes, 1: Busses, 2: Matrixes + uint selected_idx[3] = { 0, 0, 0 }; // Index within each type (0..19, 0..7, 0..2) + uint pending_toggle_type = 0; + uint pending_toggle_idx = 0; + lv_obj_t* confirm_box = nullptr; + X32_PAGE confirm_prev_page = X32_PAGE::NONE; + X32_PAGE confirm_next_page = X32_PAGE::NONE; + + // We will store pointers to our buttons so we can dynamically style/update them + vector ch_buttons; + vector bus_buttons; + vector mtx_buttons; + + // Flex columns + lv_obj_t* col_channels = nullptr; + lv_obj_t* col_busses = nullptr; + lv_obj_t* col_matrixes = nullptr; + + void ScrollSelectionIntoView() + { + vector* btn_lists[3] = { &ch_buttons, &bus_buttons, &mtx_buttons }; + if (selected_type > 2 || selected_idx[selected_type] >= btn_lists[selected_type]->size()) + { + return; + } + + lv_obj_t* btn = btn_lists[selected_type]->at(selected_idx[selected_type]); + lv_obj_update_layout(btn); + lv_obj_scroll_to_view(btn, LV_ANIM_OFF); + } + + String GetPairLabel(MP_ID mp_id, uint pair_index) + { + if (mp_id == CHANNEL_LINKED) + { + if (pair_index >= 16) + { + return String("AUX ") + String((pair_index - 16) * 2 + 1) + "/" + String((pair_index - 16) * 2 + 2); + } + return String("CH ") + String(pair_index * 2 + 1) + "/" + String(pair_index * 2 + 2); + } + else if (mp_id == BUS_LINKED) + { + return String("BUS ") + String(pair_index * 2 + 1) + "/" + String(pair_index * 2 + 2); + } + else if (mp_id == MATRIX_LINKED) + { + return String("MTX ") + String(pair_index * 2 + 1) + "/" + String(pair_index * 2 + 2); + } + + return "Unknown"; + } + + void UpdateButtonVisuals(lv_obj_t* btn, MP_ID mp_id, uint pair_index, bool is_focused) + { + bool is_linked = config->GetBool(mp_id, pair_index); + + // Format labels + String label_text = GetPairLabel(mp_id, pair_index); + + if (is_linked) + { + label_text += " [ST]"; + lv_obj_set_style_bg_color(btn, lv_color_make(38, 162, 252), LV_PART_MAIN | LV_STATE_DEFAULT); // bright blue + } + else + { + label_text += " [M]"; + lv_obj_set_style_bg_color(btn, lv_color_make(50, 60, 70), LV_PART_MAIN | LV_STATE_DEFAULT); // gray + } + + // Set label + lv_obj_t* label = lv_obj_get_child(btn, 0); + if (label) + { + lv_label_set_text(label, label_text.c_str()); + } + + // Handle focus outline + if (is_focused) + { + lv_obj_set_style_outline_width(btn, 2, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_outline_color(btn, lv_palette_main(LV_PALETTE_YELLOW), LV_PART_MAIN | LV_STATE_DEFAULT); + } + else + { + lv_obj_set_style_outline_width(btn, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + } + } + + void OnButtonClicked(lv_obj_t* btn) + { + uintptr_t pack = (uintptr_t)lv_obj_get_user_data(btn); + MP_ID mp_id = (MP_ID)(pack >> 16); + uint pair_index = pack & 0xFFFF; + if (mp_id == CHANNEL_LINKED) { selected_type = 0; } + else if (mp_id == BUS_LINKED) { selected_type = 1; } + else if (mp_id == MATRIX_LINKED) { selected_type = 2; } + selected_idx[selected_type] = pair_index; + + // Toggle link parameter + config->Toggle(mp_id, pair_index); + + // Force redraw of all buttons to stay in sync + OnChange(true); + } + + static void link_btn_event_cb(lv_event_t * e) + { + lv_obj_t * btn = (lv_obj_t*)lv_event_get_target(e); + PageSetupMixerConfig* page = (PageSetupMixerConfig*)lv_event_get_user_data(e); + page->OnButtonClicked(btn); + } + + MP_ID GetTypeParameter(uint type) + { + MP_ID mps[3] = { CHANNEL_LINKED, BUS_LINKED, MATRIX_LINKED }; + return mps[type]; + } + + uint GetTypeMaxRows(uint type) + { + uint max_rows[3] = { 20, 8, 3 }; + return max_rows[type]; + } + + void ChangeTypeSelection(uint type, int amount) + { + selected_type = type; + selected_idx[type] = helper->CheckBoundaries(selected_idx[type], amount, 0, GetTypeMaxRows(type) - 1); + OnChange(true); + } + + void CloseConfirmBox() + { + if (confirm_box) + { + lv_msgbox_close(confirm_box); + confirm_box = nullptr; + prevPage = confirm_prev_page; + nextPage = confirm_next_page; + } + } + + void ConfirmPendingModeChange() + { + config->Toggle(GetTypeParameter(pending_toggle_type), pending_toggle_idx); + CloseConfirmBox(); + OnChange(true); + } + + void ShowToggleConfirm(uint type) + { + selected_type = type; + pending_toggle_type = type; + pending_toggle_idx = selected_idx[type]; + + MP_ID mp_id = GetTypeParameter(type); + bool is_linked = config->GetBool(mp_id, pending_toggle_idx); + String pair_label = GetPairLabel(mp_id, pending_toggle_idx); + String next_mode = is_linked ? "mono" : "stereo"; + String message = String("Change ") + pair_label + String(" to ") + next_mode + String(" mode?"); + + CloseConfirmBox(); + confirm_prev_page = prevPage; + confirm_next_page = nextPage; + prevPage = X32_PAGE::NONE; + nextPage = X32_PAGE::NONE; + + confirm_box = lv_msgbox_create(objects.main); + lv_msgbox_add_title(confirm_box, "Confirm Mode Change"); + lv_msgbox_add_text(confirm_box, message.c_str()); + + lv_obj_t* cancel_btn = lv_msgbox_add_footer_button(confirm_box, "Left: Cancel"); + lv_obj_add_event_cb(cancel_btn, cancel_btn_event_cb, LV_EVENT_CLICKED, this); + + lv_obj_t* confirm_btn = lv_msgbox_add_footer_button(confirm_box, "Right: Confirm"); + lv_obj_add_event_cb(confirm_btn, confirm_btn_event_cb, LV_EVENT_CLICKED, this); + + lv_obj_center(confirm_box); + } + + static void confirm_btn_event_cb(lv_event_t * e) + { + PageSetupMixerConfig* page = (PageSetupMixerConfig*)lv_event_get_user_data(e); + page->ConfirmPendingModeChange(); + } + + static void cancel_btn_event_cb(lv_event_t * e) + { + PageSetupMixerConfig* page = (PageSetupMixerConfig*)lv_event_get_user_data(e); + page->CloseConfirmBox(); + } + + public: + PageSetupMixerConfig(PageBaseParameter* pagebasepar) : Page(pagebasepar) + { + prevPage = X32_PAGE::SETUP_SURFACE; + nextPage = X32_PAGE::ABOUT; + tabLayer0 = objects.maintab; + tabIndex0 = 3; + tabLayer1 = objects.setuptab; + tabIndex1 = 3; + led = X32_BTN_SETUP; + } + + void OnInit() override + { + // Dynamically add a 4th tab to setuptab! + lv_obj_t* tab = lv_tabview_add_tab(objects.setuptab, "Mixer Config"); + lv_obj_set_scrollbar_mode(tab, LV_SCROLLBAR_MODE_OFF); + + // Move the tab page to index 3 in the tab content container + lv_obj_t* content = lv_tabview_get_content(objects.setuptab); + if (content) + { + lv_obj_move_to_index(tab, 3); + } + + // Move the tab button to index 3 in the tab bar + lv_obj_t* tab_bar = lv_tabview_get_tab_bar(objects.setuptab); + if (tab_bar && lv_obj_get_child_cnt(tab_bar) > 0) + { + lv_obj_t* tab_btn = lv_tabview_get_tab_button(objects.setuptab, -1); + if (tab_btn) lv_obj_move_to_index(tab_btn, 3); + } + + // Main container inside the tab - using a horizontal layout for the three columns + scroll_container = lv_obj_create(tab); + lv_obj_update_layout(tab); + lv_coord_t scroll_height = lv_obj_get_height(tab) - ENCODER_HINT_RESERVED_HEIGHT; + if (scroll_height <= 0) + { + scroll_height = 320; + } + lv_obj_set_size(scroll_container, LV_PCT(100), scroll_height); + lv_obj_set_pos(scroll_container, 0, 0); + lv_obj_set_style_pad_all(scroll_container, 10, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_border_width(scroll_container, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(scroll_container, LV_OPA_TRANSP, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_set_layout(scroll_container, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(scroll_container, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(scroll_container, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + + // Columns + col_channels = lv_obj_create(scroll_container); + col_busses = lv_obj_create(scroll_container); + col_matrixes = lv_obj_create(scroll_container); + + lv_obj_t* cols[3] = { col_channels, col_busses, col_matrixes }; + const char* titles[3] = { "Input Channels / Auxes", "Mix Busses", "Matrixes" }; + uint count[3] = { 20, 8, 3 }; + MP_ID mps[3] = { CHANNEL_LINKED, BUS_LINKED, MATRIX_LINKED }; + vector* btn_lists[3] = { &ch_buttons, &bus_buttons, &mtx_buttons }; + + for (int c = 0; c < 3; c++) + { + lv_obj_set_size(cols[c], LV_PCT(31), LV_PCT(100)); + lv_obj_set_style_pad_all(cols[c], 5, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(cols[c], 35, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_color(cols[c], lv_color_make(30, 40, 50), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_bg_opa(cols[c], LV_OPA_COVER, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_radius(cols[c], 8, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_set_layout(cols[c], LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(cols[c], LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(cols[c], LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_row(cols[c], 6, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_add_flag(cols[c], LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(cols[c], LV_DIR_VER); + lv_obj_set_scrollbar_mode(cols[c], LV_SCROLLBAR_MODE_AUTO); + + // Title Label + lv_obj_t* title_label = lv_label_create(cols[c]); + lv_label_set_text(title_label, titles[c]); + lv_obj_set_style_text_color(title_label, lv_color_make(255, 255, 255), LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_text_font(title_label, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_bottom(title_label, 5, LV_PART_MAIN | LV_STATE_DEFAULT); + + // Buttons + for (uint i = 0; i < count[c]; i++) + { + lv_obj_t* btn = lv_btn_create(cols[c]); + lv_obj_set_size(btn, LV_PCT(95), 25); + lv_obj_set_style_radius(btn, 4, LV_PART_MAIN | LV_STATE_DEFAULT); + lv_obj_set_style_pad_all(btn, 0, LV_PART_MAIN | LV_STATE_DEFAULT); + + lv_obj_t* lbl = lv_label_create(btn); + lv_obj_center(lbl); + lv_obj_set_style_text_font(lbl, &lv_font_montserrat_12, LV_PART_MAIN | LV_STATE_DEFAULT); + + uintptr_t pack = ((uintptr_t)mps[c] << 16) | i; + lv_obj_set_user_data(btn, (void*)pack); + lv_obj_add_event_cb(btn, link_btn_event_cb, LV_EVENT_CLICKED, this); + + btn_lists[c]->push_back(btn); + } + } + } + + void OnShow() override + { + // Bind display encoders for navigation + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_1, String(LV_SYMBOL_UP) + " / " + LV_SYMBOL_DOWN + "\nCh/Aux"); + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_BUTTON_1, "Change Mode"); + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_3, String(LV_SYMBOL_UP) + " / " + LV_SYMBOL_DOWN + "\nMix Busses"); + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_BUTTON_3, "Change Mode"); + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_5, String(LV_SYMBOL_UP) + " / " + LV_SYMBOL_DOWN + "\nMatrixes"); + config->SurfaceBindCustom(SurfaceElementId::DISPLAY_ENCODER_BUTTON_5, "Change Mode"); + + OnChange(true); + } + + void OnChange(bool force_update) override + { + if (confirm_box) + { + if (config->HasParameterChanged(DISPLAY_LEFT)) + { + config->SetParameterUnchanged(DISPLAY_LEFT); + CloseConfirmBox(); + return; + } + + if (config->HasParameterChanged(DISPLAY_RIGHT)) + { + config->SetParameterUnchanged(DISPLAY_RIGHT); + ConfirmPendingModeChange(); + return; + } + } + + // Redraw/re-style all buttons according to their state and current focus + MP_ID mps[3] = { CHANNEL_LINKED, BUS_LINKED, MATRIX_LINKED }; + vector* btn_lists[3] = { &ch_buttons, &bus_buttons, &mtx_buttons }; + uint count[3] = { 20, 8, 3 }; + + for (int c = 0; c < 3; c++) + { + for (uint i = 0; i < count[c]; i++) + { + bool is_focused = (selected_type == (uint)c && selected_idx[c] == i); + UpdateButtonVisuals(btn_lists[c]->at(i), mps[c], i, is_focused); + } + } + + ScrollSelectionIntoView(); + } + + void OnChangeCustomEncoder(SurfaceElementId surface_element_id, int amount) override + { + switch (surface_element_id) + { + case SurfaceElementId::DISPLAY_ENCODER_1: + ChangeTypeSelection(0, amount); + break; + case SurfaceElementId::DISPLAY_ENCODER_3: + ChangeTypeSelection(1, amount); + break; + case SurfaceElementId::DISPLAY_ENCODER_5: + ChangeTypeSelection(2, amount); + break; + default: + break; + } + } + + void OnChangeCustomButton(SurfaceElementId surface_element_id) override + { + switch (surface_element_id) + { + case SurfaceElementId::DISPLAY_ENCODER_BUTTON_1: + ShowToggleConfirm(0); + break; + case SurfaceElementId::DISPLAY_ENCODER_BUTTON_3: + ShowToggleConfirm(1); + break; + case SurfaceElementId::DISPLAY_ENCODER_BUTTON_5: + ShowToggleConfirm(2); + break; + default: + break; + } + } +}; diff --git a/src/page-setup-surface.h b/src/page-setup-surface.h index 9260acf..b87c1cd 100644 --- a/src/page-setup-surface.h +++ b/src/page-setup-surface.h @@ -299,7 +299,7 @@ class PageSetupSurface: public Page PageSetupSurface(PageBaseParameter* pagebasepar) : Page(pagebasepar) { prevPage = X32_PAGE::SETUP_CARD ; - nextPage = X32_PAGE::ABOUT; + nextPage = X32_PAGE::SETUP_MIXER_CONFIG; tabLayer0 = objects.maintab; tabIndex0 = 3; tabLayer1 = objects.setuptab; diff --git a/src/page-setup.h b/src/page-setup.h index 1acaff0..663dccb 100644 --- a/src/page-setup.h +++ b/src/page-setup.h @@ -4,7 +4,9 @@ using namespace std; class PageSetup: public Page { +#ifndef __clang__ using enum MP_ID; +#endif public: PageSetup(PageBaseParameter* pagebasepar) : Page(pagebasepar) diff --git a/src/page.cpp b/src/page.cpp index bd7073a..ad058f0 100644 --- a/src/page.cpp +++ b/src/page.cpp @@ -315,7 +315,22 @@ void Page::SyncEncoderWidget(SurfaceElementId elementIdEncoder, SurfaceElementId else { lv_obj_set_flag(lvgl_encoder_widget->Slider, LV_OBJ_FLAG_HIDDEN, false); - lv_slider_set_value(lvgl_encoder_widget->Slider, parameter->GetPercent(index), LV_ANIM_OFF); + if (id == MP_ID::CHANNEL_STEREO_WIDTH) + { + uint width_percent = parameter->GetPercent(index); + int width_start = (100 - (int)width_percent) / 2; + int width_end = width_start + (int)width_percent; + lv_slider_set_mode(lvgl_encoder_widget->Slider, LV_SLIDER_MODE_RANGE); + lv_slider_set_range(lvgl_encoder_widget->Slider, 0, 100); + lv_slider_set_start_value(lvgl_encoder_widget->Slider, width_start, LV_ANIM_OFF); + lv_slider_set_value(lvgl_encoder_widget->Slider, width_end, LV_ANIM_OFF); + } + else + { + lv_slider_set_mode(lvgl_encoder_widget->Slider, LV_SLIDER_MODE_NORMAL); + lv_slider_set_range(lvgl_encoder_widget->Slider, -2, 101); + lv_slider_set_value(lvgl_encoder_widget->Slider, parameter->GetPercent(index), LV_ANIM_OFF); + } } } } @@ -329,6 +344,13 @@ void Page::SyncEncoderWidget(SurfaceElementId elementIdEncoder, SurfaceElementId } else { + if (surface_binding_button->mp_action == MixerparameterAction::CUSTOM) + { + lv_label_set_text(lvgl_encoder_widget->ButtonLabel, surface_binding_button->custom_label.c_str()); + remove_style_label_bg_yellow(lvgl_encoder_widget->ButtonLabel); + return; + } + MP_ID id = config->ParameterCalcId(surface_binding_button); uint index = config->ParameterCalcIndex(surface_binding_button); @@ -380,4 +402,4 @@ void Page::ClearEncoderButton(LVGLEncoderWidget *binding) { lv_label_set_text(binding->ButtonLabel, ""); remove_style_label_bg_yellow(binding->ButtonLabel); -} \ No newline at end of file +} diff --git a/src/page.h b/src/page.h index d15b023..dfa3b21 100644 --- a/src/page.h +++ b/src/page.h @@ -15,11 +15,10 @@ #include "eez/src/ui/styles.h" using namespace std; -using enum MP_ID; - class Page : public X32Base { protected: + using enum MP_ID; Mixer* mixer; Surface* surface; diff --git a/src/simulator-gui.cpp b/src/simulator-gui.cpp new file mode 100644 index 0000000..0e64f3c --- /dev/null +++ b/src/simulator-gui.cpp @@ -0,0 +1,1093 @@ +#include "simulator-gui.h" + +#ifdef BODYLESS_SDL2 +#include "enum.h" +#include +#include +#include +#include + +X32Ctrl* SimulatorGUI::ctrl = nullptr; +lv_display_t* SimulatorGUI::nav_disp = nullptr; +lv_display_t* SimulatorGUI::display_encoders_disp = nullptr; +lv_display_t* SimulatorGUI::arrows_disp = nullptr; +lv_display_t* SimulatorGUI::channel_strip_disp = nullptr; +lv_display_t* SimulatorGUI::left_sel_disp = nullptr; +lv_display_t* SimulatorGUI::board_l_disp = nullptr; +lv_display_t* SimulatorGUI::board_m_disp = nullptr; +lv_display_t* SimulatorGUI::right_sel_disp = nullptr; +lv_display_t* SimulatorGUI::board_r_disp = nullptr; +std::vector SimulatorGUI::simulator_indevs; +std::vector SimulatorGUI::virtual_faders; +std::vector SimulatorGUI::virtual_buttons; +std::vector SimulatorGUI::virtual_display_encoders; + +namespace { +constexpr int kMainWindowX = 100; +constexpr int kMainWindowY = 100; +constexpr int kMainWindowW = DISPLAY_RESOLUTION_X; +constexpr int kMainWindowH = DISPLAY_RESOLUTION_Y; +constexpr int kWindowGap = 10; +constexpr int kDisplayEncoderWindowW = DISPLAY_RESOLUTION_X; +constexpr int kDisplayEncoderWindowH = 130; +constexpr int kChannelStripWindowW = 1180; +constexpr int kChannelStripWindowH = 390; +constexpr int kFaderStripWidth = 96; +constexpr int kFaderWindowHeight = 390; +constexpr int kSelectorWindowWidth = 112; +constexpr int kSelectorButtonHeight = 42; +constexpr int kSelectorButtonGap = 8; +constexpr int kEncoderDragPixelsPerStep = 4; +constexpr int kEncoderWheelStepsPerNotch = 2; +bool sdl_event_watch_added = false; + +uint32_t CurrentDisplayWindowId() { + lv_display_t* disp = lv_display_get_default(); + if (disp == nullptr) return 0; + + struct SDL_Window* win = lv_sdl_window_get_window(disp); + if (win == nullptr) return 0; + + return SDL_GetWindowID(win); +} + +lv_obj_t* BuildEncoderHitbox(lv_obj_t* parent, int x, int y, int size) { + lv_obj_t* hitbox = lv_obj_create(parent); + lv_obj_set_pos(hitbox, x, y); + lv_obj_set_size(hitbox, size, size); + lv_obj_set_style_bg_opa(hitbox, LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_border_width(hitbox, 0, LV_PART_MAIN); + lv_obj_set_style_pad_all(hitbox, 0, LV_PART_MAIN); + lv_obj_add_flag(hitbox, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(hitbox, LV_OBJ_FLAG_SCROLLABLE); + return hitbox; +} + +lv_obj_t* BuildPanel(lv_obj_t* parent, const char* title, int x, int y, int w, int h) { + lv_obj_t* panel = lv_obj_create(parent); + lv_obj_set_pos(panel, x, y); + lv_obj_set_size(panel, w, h); + lv_obj_set_style_pad_all(panel, 4, LV_PART_MAIN); + lv_obj_set_style_radius(panel, 12, LV_PART_MAIN); + lv_obj_set_style_border_width(panel, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(panel, lv_color_hex(0x5E6969), LV_PART_MAIN); + lv_obj_set_style_bg_color(panel, lv_color_hex(0x485454), LV_PART_MAIN); + lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* title_lbl = lv_label_create(panel); + lv_label_set_text(title_lbl, title); + lv_obj_set_style_text_color(title_lbl, lv_color_hex(0xF0F0F0), LV_PART_MAIN); + lv_obj_set_style_text_font(title_lbl, &lv_font_montserrat_12, LV_PART_MAIN); + lv_obj_set_style_text_letter_space(title_lbl, 1, LV_PART_MAIN); + lv_obj_align(title_lbl, LV_ALIGN_TOP_MID, 0, 2); + return panel; +} + +void BuildSmallLabel(lv_obj_t* parent, const char* text, int x, int y, int w = 70) { + lv_obj_t* label = lv_label_create(parent); + lv_label_set_text(label, text); + lv_obj_set_pos(label, x, y); + lv_obj_set_width(label, w); + lv_obj_set_style_text_color(label, lv_color_hex(0xF0F0F0), LV_PART_MAIN); + lv_obj_set_style_text_font(label, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); +} +} + +void SimulatorGUI::Init(X32Ctrl* ctrl_inst) { + ctrl = ctrl_inst; + virtual_faders.clear(); + virtual_buttons.clear(); + virtual_display_encoders.clear(); + simulator_indevs.clear(); + if (!sdl_event_watch_added) { + SDL_AddEventWatch(SDLEventWatch, nullptr); + sdl_event_watch_added = true; + } + + // Find the main display and position it + lv_display_t* main_disp = lv_display_get_next(nullptr); + if (main_disp) { + lv_sdl_window_set_title(main_disp, "OpenX32 - omc - Main Screen"); + struct SDL_Window* main_win = lv_sdl_window_get_window(main_disp); + if (main_win) { + SDL_SetWindowPosition(main_win, kMainWindowX, kMainWindowY); + } + PrepareDisplay(main_disp); + } + + // Create and position split windows + CreateNavWindow(kMainWindowX + kMainWindowW + kWindowGap, kMainWindowY); + CreateDisplayEncodersWindow(kMainWindowX, kMainWindowY + kMainWindowH + kWindowGap); + CreateArrowsWindow(kMainWindowX + (kMainWindowW - 150) / 2, kMainWindowY + kMainWindowH + kWindowGap + kDisplayEncoderWindowH + kWindowGap); + int channel_strip_y = kMainWindowY + kMainWindowH + kWindowGap + kDisplayEncoderWindowH + kWindowGap + 130 + kWindowGap; + CreateChannelStripWindow(kMainWindowX, channel_strip_y); + CreateFadersWindows(channel_strip_y + kChannelStripWindowH + kWindowGap); +} + +void SimulatorGUI::PrepareDisplay(lv_display_t* disp) { + lv_display_set_default(disp); + lv_indev_t* mouse = lv_sdl_mouse_create(); + lv_indev_set_display(mouse, disp); + simulator_indevs.push_back(mouse); + lv_indev_t* mousewheel = lv_sdl_mousewheel_create(); + lv_indev_set_display(mousewheel, disp); + simulator_indevs.push_back(mousewheel); +} + +void SimulatorGUI::RaiseAllWindows(uint32_t focused_window_id) { + static bool raising = false; + if (raising) return; + + raising = true; + std::vector windows; + for (lv_display_t* disp = lv_display_get_next(nullptr); disp != nullptr; disp = lv_display_get_next(disp)) { + SDL_Window* win = lv_sdl_window_get_window(disp); + if (win != nullptr) { + windows.push_back(win); + } + } + + for (SDL_Window* win : windows) { + if (SDL_GetWindowID(win) != focused_window_id) { + SDL_RaiseWindow(win); + } + } + for (SDL_Window* win : windows) { + if (SDL_GetWindowID(win) == focused_window_id) { + SDL_RaiseWindow(win); + break; + } + } + raising = false; +} + +void SimulatorGUI::CreateNavWindow(int x, int y) { + nav_disp = lv_sdl_window_create(110, 480); + lv_sdl_window_set_title(nav_disp, "OpenX32 - omc - Navigation Buttons"); + struct SDL_Window* win = lv_sdl_window_get_window(nav_disp); + if (win) { + SDL_SetWindowPosition(win, x, y); + } + PrepareDisplay(nav_disp); + + lv_obj_t* scr = lv_obj_create(nullptr); + lv_scr_load(scr); + lv_obj_set_style_bg_color(scr, lv_color_hex(0x1B1B1B), LV_PART_MAIN); + + BuildConsoleButton(scr, "HOME", 10, 10, 90, 45, SurfaceElementId::HOME); + BuildConsoleButton(scr, "METERS", 10, 65, 90, 45, SurfaceElementId::METERS); + BuildConsoleButton(scr, "ROUTING", 10, 120, 90, 45, SurfaceElementId::ROUTING); + BuildConsoleButton(scr, "SETUP", 10, 175, 90, 45, SurfaceElementId::SETUP); + BuildConsoleButton(scr, "LIBRARY", 10, 230, 90, 45, SurfaceElementId::LIBRARY); + BuildConsoleButton(scr, "EFFECTS", 10, 285, 90, 45, SurfaceElementId::EFFECTS); + BuildConsoleButton(scr, "MUTE GRP", 10, 340, 90, 45, SurfaceElementId::MUTE_GRP); + BuildConsoleButton(scr, "UTILITY", 10, 395, 90, 45, SurfaceElementId::UTILITY); +} + +void SimulatorGUI::CreateDisplayEncodersWindow(int x, int y) { + display_encoders_disp = lv_sdl_window_create(kDisplayEncoderWindowW, kDisplayEncoderWindowH); + lv_sdl_window_set_title(display_encoders_disp, "OpenX32 - omc - Display Encoders"); + struct SDL_Window* win = lv_sdl_window_get_window(display_encoders_disp); + if (win) { + SDL_SetWindowPosition(win, x, y); + } + PrepareDisplay(display_encoders_disp); + + lv_obj_t* scr = lv_obj_create(nullptr); + lv_scr_load(scr); + lv_obj_set_style_bg_color(scr, lv_color_hex(0x1B1B1B), LV_PART_MAIN); + + for (int i = 0; i < MAX_DISPLAY_ENCODER; i++) { + BuildDisplayEncoder(scr, i); + } +} + +void SimulatorGUI::CreateArrowsWindow(int x, int y) { + arrows_disp = lv_sdl_window_create(150, 130); + lv_sdl_window_set_title(arrows_disp, "OpenX32 - omc - Direction Buttons"); + struct SDL_Window* win = lv_sdl_window_get_window(arrows_disp); + if (win) { + SDL_SetWindowPosition(win, x, y); + } + PrepareDisplay(arrows_disp); + + lv_obj_t* scr = lv_obj_create(nullptr); + lv_scr_load(scr); + lv_obj_set_style_bg_color(scr, lv_color_hex(0x1B1B1B), LV_PART_MAIN); + + BuildConsoleButton(scr, "UP", 55, 10, 40, 35, SurfaceElementId::UP); + BuildConsoleButton(scr, "LEFT", 10, 50, 45, 35, SurfaceElementId::LEFT); + BuildConsoleButton(scr, "RIGHT", 95, 50, 45, 35, SurfaceElementId::RIGHT); + BuildConsoleButton(scr, "DOWN", 55, 90, 40, 35, SurfaceElementId::DOWN); +} + +void SimulatorGUI::CreateChannelStripWindow(int x, int y) { + channel_strip_disp = lv_sdl_window_create(kChannelStripWindowW, kChannelStripWindowH); + lv_sdl_window_set_title(channel_strip_disp, "OpenX32 - omc - Channel Controls"); + struct SDL_Window* win = lv_sdl_window_get_window(channel_strip_disp); + if (win) { + SDL_SetWindowPosition(win, x, y); + } + PrepareDisplay(channel_strip_disp); + + lv_obj_t* scr = lv_obj_create(nullptr); + lv_scr_load(scr); + lv_obj_set_style_bg_color(scr, lv_color_hex(0x303838), LV_PART_MAIN); + + lv_obj_t* preamp = BuildPanel(scr, "CONFIG / PREAMP", 0, 0, 370, 190); + BuildSurfaceEncoder(preamp, "GAIN", 36, 35, 70, SurfaceElementId::GAIN_ENCODER); + BuildSurfaceEncoder(preamp, "FREQUENCY", 242, 35, 70, SurfaceElementId::LOW_CUT_FREQ_ENCODER); + BuildConsoleButton(preamp, "48V", 30, 136, 44, 38, SurfaceElementId::PHANTOM_48V); + BuildConsoleButton(preamp, "Ø", 102, 136, 44, 38, SurfaceElementId::PHASE_INVERT); + BuildConsoleButton(preamp, "LOW CUT", 256, 136, 46, 38, SurfaceElementId::LOW_CUT); + BuildConsoleButton(preamp, "VIEW", 330, 142, 30, 26, SurfaceElementId::VIEW_CONFIG); + BuildSmallLabel(preamp, "SIG", 170, 124, 40); + + lv_obj_t* gate = BuildPanel(scr, "GATE", 0, 198, 180, 190); + BuildSurfaceEncoder(gate, "THRESHOLD", 50, 35, 70, SurfaceElementId::GATE_THRESHOLD_ENCODER); + BuildConsoleButton(gate, "GATE", 32, 136, 46, 38, SurfaceElementId::GATE); + BuildConsoleButton(gate, "VIEW", 140, 142, 30, 26, SurfaceElementId::VIEW_GATE); + + lv_obj_t* dynamics = BuildPanel(scr, "DYNAMICS", 188, 198, 180, 190); + BuildSurfaceEncoder(dynamics, "THRESHOLD", 50, 35, 70, SurfaceElementId::DYNAMICS_THRESHOLD_ENCODER); + BuildConsoleButton(dynamics, "COMP / EXP", 64, 136, 58, 38, SurfaceElementId::COMP_EXP); + BuildConsoleButton(dynamics, "VIEW", 140, 142, 30, 26, SurfaceElementId::VIEW_DYNAMICS); + + lv_obj_t* eq = BuildPanel(scr, "EQUALIZER", 378, 0, 418, 388); + BuildSurfaceEncoder(eq, "Q", 136, 32, 82, SurfaceElementId::EQ_Q_ENCODER); + BuildSurfaceEncoder(eq, "FREQ", 210, 150, 70, SurfaceElementId::EQ_FREQ_ENCODER); + BuildSurfaceEncoder(eq, "GAIN", 88, 218, 70, SurfaceElementId::EQ_GAIN_ENCODER); + BuildConsoleButton(eq, "MODE", 76, 140, 42, 34, SurfaceElementId::EQ_MODE); + BuildConsoleButton(eq, "EQUALIZER", 106, 336, 48, 38, SurfaceElementId::EQ); + BuildConsoleButton(eq, "HIGH", 334, 88, 42, 34, SurfaceElementId::EQ_HIGH); + BuildConsoleButton(eq, "HIGH\nMID", 334, 154, 42, 34, SurfaceElementId::EQ_HIGH_MID); + BuildConsoleButton(eq, "LOW\nMID", 334, 220, 42, 34, SurfaceElementId::EQ_LOW_MID); + BuildConsoleButton(eq, "LOW", 334, 286, 42, 34, SurfaceElementId::EQ_LOW); + BuildConsoleButton(eq, "VIEW", 378, 354, 30, 26, SurfaceElementId::VIEW_EQ); + BuildSmallLabel(eq, "HCUT\nHSHV\n\nVEQ\nPEQ\n\nLSHV\nLCUT", 0, 90, 54); + + lv_obj_t* sends = BuildPanel(scr, "BUS SENDS", 802, 0, 206, 388); + BuildSurfaceEncoder(sends, "1", 28, 32, 70, SurfaceElementId::BUS_SEND_ENCODER_1); + BuildSurfaceEncoder(sends, "2", 28, 124, 70, SurfaceElementId::BUS_SEND_ENCODER_2); + BuildSurfaceEncoder(sends, "3", 28, 216, 70, SurfaceElementId::BUS_SEND_ENCODER_3); + BuildSurfaceEncoder(sends, "4", 28, 308, 70, SurfaceElementId::BUS_SEND_ENCODER_4); + BuildConsoleButton(sends, "1-4", 126, 88, 44, 34, SurfaceElementId::BUS_SEND_1_4); + BuildConsoleButton(sends, "5-8", 126, 154, 44, 34, SurfaceElementId::BUS_SEND_5_8); + BuildConsoleButton(sends, "9-12", 126, 220, 44, 34, SurfaceElementId::BUS_SEND_9_12); + BuildConsoleButton(sends, "13-16", 126, 286, 44, 34, SurfaceElementId::BUS_SEND_13_16); + BuildConsoleButton(sends, "VIEW", 168, 354, 30, 26, SurfaceElementId::VIEW_MIX_BUS_SENDS); + + lv_obj_t* main_bus = BuildPanel(scr, "MAIN BUS", 1016, 0, 162, 388); + BuildSurfaceEncoder(main_bus, "LEVEL", 46, 32, 70, SurfaceElementId::MAIN_BUS_LEVEL_ENCODER); + BuildConsoleButton(main_bus, "MONO BUS", 58, 136, 44, 38, SurfaceElementId::MONO_BUS); + BuildSurfaceEncoder(main_bus, "PAN / BAL", 46, 214, 70, SurfaceElementId::PAN_BAL_ENCODER); + BuildConsoleButton(main_bus, "STEREO BUS", 58, 326, 44, 38, SurfaceElementId::MAIN_LR_BUS); + BuildConsoleButton(main_bus, "VIEW", 124, 354, 30, 26, SurfaceElementId::VIEW_MAIN); +} + +void SimulatorGUI::CreateFadersWindows(int y_start) { + int x_offset = kMainWindowX; + const int fader_window_width = kFaderStripWidth * 8; + + // --- 1. Left Bank Selector Window --- + left_sel_disp = lv_sdl_window_create(kSelectorWindowWidth, kFaderWindowHeight); + lv_sdl_window_set_title(left_sel_disp, "OpenX32 - omc - Left Bank Selector"); + struct SDL_Window* win_lsel = lv_sdl_window_get_window(left_sel_disp); + if (win_lsel) { + SDL_SetWindowPosition(win_lsel, x_offset, y_start); + } + PrepareDisplay(left_sel_disp); + + lv_obj_t* scr_lsel = lv_obj_create(nullptr); + lv_scr_load(scr_lsel); + lv_obj_set_style_bg_color(scr_lsel, lv_color_hex(0x1B1B1B), LV_PART_MAIN); + lv_obj_add_flag(scr_lsel, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(scr_lsel, LV_DIR_VER); + lv_obj_set_scrollbar_mode(scr_lsel, LV_SCROLLBAR_MODE_AUTO); + + if (ctrl->config->IsModelX32FullOrM32()) { + BuildConsoleButton(scr_lsel, "CH 1-16", 10, 34, 92, 50, SurfaceElementId::CH1_16); + BuildConsoleButton(scr_lsel, "CH 17-32", 10, 116, 92, 50, SurfaceElementId::CH17_32); + BuildConsoleButton(scr_lsel, "AUX/USB", 10, 198, 92, 50, SurfaceElementId::AUX_USB_RX_RET); + BuildConsoleButton(scr_lsel, "BUS MAST", 10, 280, 92, 50, SurfaceElementId::BUS_MASTER); + } else { + const std::pair buttons[] = { + {"CH 1-8", SurfaceElementId::CH1_8}, + {"CH 9-16", SurfaceElementId::CH9_16}, + {"CH 17-24", SurfaceElementId::CH17_24}, + {"CH 25-32", SurfaceElementId::CH25_32}, + {"AUX/USB", SurfaceElementId::AUX_USB}, + {"FX RET", SurfaceElementId::FX_RET}, + {"BUS 1-8", SurfaceElementId::BUS1_8_MASTER}, + {"BUS 9-16", SurfaceElementId::BUS9_16_MASTER}, + }; + for (int i = 0; i < 8; i++) { + BuildConsoleButton(scr_lsel, buttons[i].first, 10, 10 + i * (kSelectorButtonHeight + kSelectorButtonGap), + 92, kSelectorButtonHeight, buttons[i].second); + } + } + + x_offset += kSelectorWindowWidth + kWindowGap; + + // --- 2. Board L Faders Window (8 faders) --- + board_l_disp = lv_sdl_window_create(fader_window_width, kFaderWindowHeight); + lv_sdl_window_set_title(board_l_disp, "OpenX32 - omc - Input Faders"); + struct SDL_Window* win_bl = lv_sdl_window_get_window(board_l_disp); + if (win_bl) { + SDL_SetWindowPosition(win_bl, x_offset, y_start); + } + PrepareDisplay(board_l_disp); + + lv_obj_t* scr_bl = lv_obj_create(nullptr); + lv_scr_load(scr_bl); + lv_obj_set_style_bg_color(scr_bl, lv_color_hex(0x222222), LV_PART_MAIN); + + for (int i = 0; i < 8; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "IN %d", i + 1); + BuildFaderStrip(scr_bl, i, (uint8_t)X32_BOARD::X32_BOARD_L, i, buf); + } + + x_offset += fader_window_width + kWindowGap; + + // --- 3. Board M Faders Window (8 faders, if full model) --- + bool is_full = ctrl->config->IsModelX32FullOrM32(); + if (is_full) { + board_m_disp = lv_sdl_window_create(fader_window_width, kFaderWindowHeight); + lv_sdl_window_set_title(board_m_disp, "OpenX32 - omc - Mid Faders"); + struct SDL_Window* win_bm = lv_sdl_window_get_window(board_m_disp); + if (win_bm) { + SDL_SetWindowPosition(win_bm, x_offset, y_start); + } + PrepareDisplay(board_m_disp); + + lv_obj_t* scr_bm = lv_obj_create(nullptr); + lv_scr_load(scr_bm); + lv_obj_set_style_bg_color(scr_bm, lv_color_hex(0x222222), LV_PART_MAIN); + + for (int i = 0; i < 8; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "MID %d", i + 1); + BuildFaderStrip(scr_bm, i, (uint8_t)X32_BOARD::X32_BOARD_M, i, buf); + } + + x_offset += fader_window_width + kWindowGap; + } + + // --- 4. Right Bank Selector Window --- + right_sel_disp = lv_sdl_window_create(kSelectorWindowWidth, kFaderWindowHeight); + lv_sdl_window_set_title(right_sel_disp, "OpenX32 - omc - Right Bank Selector"); + struct SDL_Window* win_rsel = lv_sdl_window_get_window(right_sel_disp); + if (win_rsel) { + SDL_SetWindowPosition(win_rsel, x_offset, y_start); + } + PrepareDisplay(right_sel_disp); + + lv_obj_t* scr_rsel = lv_obj_create(nullptr); + lv_scr_load(scr_rsel); + lv_obj_set_style_bg_color(scr_rsel, lv_color_hex(0x1B1B1B), LV_PART_MAIN); + lv_obj_add_flag(scr_rsel, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_scroll_dir(scr_rsel, LV_DIR_VER); + lv_obj_set_scrollbar_mode(scr_rsel, LV_SCROLLBAR_MODE_AUTO); + + const std::pair right_buttons_full[] = { + {"SND ON FD", SurfaceElementId::SEND_ON_FADER}, + {"DCA", SurfaceElementId::DCA}, + {"BUS 1-8", SurfaceElementId::BUS1_8}, + {"BUS 9-16", SurfaceElementId::BUS9_16}, + {"MTX/MAIN", SurfaceElementId::MATRIX_MAIN}, + {"CLR SOLO", SurfaceElementId::CLEAR_SOLO}, + }; + const std::pair right_buttons_compact[] = { + {"DAW REM", SurfaceElementId::DAW_REMOTE}, + {"SND ON FD", SurfaceElementId::SEND_ON_FADER}, + {"DCA", SurfaceElementId::DCA}, + {"BUS 1-8", SurfaceElementId::BUS1_8}, + {"BUS 9-16", SurfaceElementId::BUS9_16}, + {"MTX/MAIN", SurfaceElementId::MATRIX_MAIN}, + {"CLR SOLO", SurfaceElementId::CLEAR_SOLO}, + }; + const auto* right_buttons = is_full ? right_buttons_full : right_buttons_compact; + const int right_button_count = is_full ? 6 : 7; + for (int i = 0; i < right_button_count; i++) { + BuildConsoleButton(scr_rsel, right_buttons[i].first, 10, 14 + i * (kSelectorButtonHeight + kSelectorButtonGap), + 92, kSelectorButtonHeight, right_buttons[i].second); + } + + x_offset += kSelectorWindowWidth + kWindowGap; + + // --- 5. Board R Faders Window (8 faders + 1 main LR fader) --- + board_r_disp = lv_sdl_window_create(kFaderStripWidth * 9, kFaderWindowHeight); + lv_sdl_window_set_title(board_r_disp, "OpenX32 - omc - Bus Faders"); + struct SDL_Window* win_br = lv_sdl_window_get_window(board_r_disp); + if (win_br) { + SDL_SetWindowPosition(win_br, x_offset, y_start); + } + PrepareDisplay(board_r_disp); + + lv_obj_t* scr_br = lv_obj_create(nullptr); + lv_scr_load(scr_br); + lv_obj_set_style_bg_color(scr_br, lv_color_hex(0x222222), LV_PART_MAIN); + + for (int i = 0; i < 8; i++) { + char buf[16]; + snprintf(buf, sizeof(buf), "BUS %d", i + 1); + BuildFaderStrip(scr_br, i, (uint8_t)X32_BOARD::X32_BOARD_R, i, buf); + } + BuildFaderStrip(scr_br, 8, (uint8_t)X32_BOARD::X32_BOARD_R, 8, "MAIN LR"); +} + +void SimulatorGUI::BuildDisplayEncoder(lv_obj_t* parent, int encoder_idx) { + SurfaceElementId encoder_id = ctrl->config->CalcSurfaceElementId(SurfaceElementId::DISPLAY_ENCODER_1, encoder_idx); + SurfaceElementId button_id = ctrl->config->CalcSurfaceElementId(SurfaceElementId::DISPLAY_ENCODER_BUTTON_1, encoder_idx); + SurfaceElement* encoder = ctrl->config->GetSurfaceElement(encoder_id); + SurfaceElement* button = ctrl->config->GetSurfaceElement(button_id); + if (encoder == nullptr || button == nullptr || encoder->GetType() != SurfaceElementType::Encoder || button->GetType() != SurfaceElementType::Button) { + return; + } + + constexpr int cell_width = kDisplayEncoderWindowW / MAX_DISPLAY_ENCODER; + int x = encoder_idx * cell_width; + + lv_obj_t* cell = lv_obj_create(parent); + lv_obj_set_pos(cell, x + 4, 5); + lv_obj_set_size(cell, cell_width - 8, kDisplayEncoderWindowH - 10); + lv_obj_set_style_pad_all(cell, 2, LV_PART_MAIN); + lv_obj_set_style_border_width(cell, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(cell, lv_color_hex(0x444444), LV_PART_MAIN); + lv_obj_set_style_bg_color(cell, lv_color_hex(0x242424), LV_PART_MAIN); + lv_obj_remove_flag(cell, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* title = lv_label_create(cell); + lv_label_set_text_fmt(title, "ENC %d", encoder_idx + 1); + lv_obj_set_style_text_color(title, lv_color_hex(0xCCCCCC), LV_PART_MAIN); + lv_obj_set_style_text_font(title, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 2); + + lv_obj_t* arc = lv_arc_create(cell); + lv_obj_set_size(arc, 72, 72); + lv_obj_align(arc, LV_ALIGN_TOP_MID, 0, 22); + lv_arc_set_range(arc, 0, 127); + lv_arc_set_value(arc, 64); + lv_arc_set_rotation(arc, 135); + lv_arc_set_bg_angles(arc, 0, 270); + lv_obj_set_style_arc_width(arc, 7, LV_PART_MAIN); + lv_obj_set_style_arc_width(arc, 7, LV_PART_INDICATOR); + lv_obj_set_style_bg_opa(arc, LV_OPA_TRANSP, LV_PART_KNOB); + lv_obj_set_style_arc_color(arc, lv_color_hex(0x333333), LV_PART_MAIN); + lv_obj_set_style_arc_color(arc, lv_color_hex(0x333333), LV_PART_INDICATOR); + lv_obj_set_style_arc_opa(arc, LV_OPA_TRANSP, LV_PART_MAIN); + lv_obj_set_style_arc_opa(arc, LV_OPA_TRANSP, LV_PART_INDICATOR); + lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE); + + lv_obj_t* knob = lv_obj_create(cell); + lv_obj_set_size(knob, 44, 44); + lv_obj_align(knob, LV_ALIGN_TOP_MID, 0, 36); + lv_obj_set_style_radius(knob, LV_RADIUS_CIRCLE, LV_PART_MAIN); + lv_obj_set_style_bg_color(knob, lv_color_hex(0x2A2A2A), LV_PART_MAIN); + lv_obj_set_style_border_width(knob, 2, LV_PART_MAIN); + lv_obj_set_style_border_color(knob, lv_color_hex(0x111111), LV_PART_MAIN); + lv_obj_remove_flag(knob, LV_OBJ_FLAG_CLICKABLE); + lv_obj_remove_flag(knob, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* push_btn = lv_button_create(cell); + lv_obj_set_size(push_btn, 48, 24); + lv_obj_align(push_btn, LV_ALIGN_BOTTOM_MID, 0, -4); + lv_obj_set_style_radius(push_btn, 3, LV_PART_MAIN); + lv_obj_set_style_bg_color(push_btn, lv_color_hex(0x353535), LV_PART_MAIN); + lv_obj_set_style_bg_color(push_btn, lv_color_hex(0x00A0A0), LV_STATE_CHECKED); + lv_obj_t* push_lbl = lv_label_create(push_btn); + lv_label_set_text(push_lbl, "PUSH"); + lv_obj_set_style_text_font(push_lbl, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_center(push_lbl); + + uintptr_t encoder_vec_idx = virtual_display_encoders.size(); + VirtualEncoder vencoder = { + (uint8_t)encoder->GetBoard(), + (uint8_t)encoder->GetIndex(), + (uint8_t)button->GetBoard(), + (uint8_t)button->GetIndex(), + arc, + push_btn, + 64, + 0, + CurrentDisplayWindowId(), + true, + false + }; + virtual_display_encoders.push_back(vencoder); + lv_obj_t* hitbox = BuildEncoderHitbox(cell, (cell_width - 8 - 78) / 2, 19, 78); + lv_obj_add_event_cb(hitbox, DisplayEncoderEventCB, LV_EVENT_ALL, (void*)encoder_vec_idx); + lv_obj_add_event_cb(push_btn, DisplayEncoderButtonEventCB, LV_EVENT_ALL, (void*)encoder_vec_idx); +} + +void SimulatorGUI::BuildSurfaceEncoder(lv_obj_t* parent, const char* label, int x, int y, int size, SurfaceElementId elementId) { + SurfaceElement* encoder = ctrl->config->GetSurfaceElement(elementId); + if (encoder == nullptr || encoder->GetType() != SurfaceElementType::Encoder) { + return; + } + + lv_obj_t* arc = lv_arc_create(parent); + lv_obj_set_pos(arc, x, y); + lv_obj_set_size(arc, size, size); + lv_arc_set_range(arc, 0, 127); + lv_arc_set_value(arc, 64); + lv_arc_set_rotation(arc, 135); + lv_arc_set_bg_angles(arc, 0, 270); + lv_obj_set_style_arc_width(arc, 7, LV_PART_MAIN); + lv_obj_set_style_arc_width(arc, 7, LV_PART_INDICATOR); + lv_obj_set_style_arc_color(arc, lv_color_hex(0x222222), LV_PART_MAIN); + lv_obj_set_style_arc_color(arc, lv_color_hex(0xFF7A16), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(arc, lv_color_hex(0x222222), LV_PART_KNOB); + lv_obj_set_style_radius(arc, LV_RADIUS_CIRCLE, LV_PART_KNOB); + lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE); + + lv_obj_t* inner = lv_obj_create(parent); + int inner_size = size / 2; + lv_obj_set_pos(inner, x + (size - inner_size) / 2, y + (size - inner_size) / 2); + lv_obj_set_size(inner, inner_size, inner_size); + lv_obj_set_style_radius(inner, LV_RADIUS_CIRCLE, LV_PART_MAIN); + lv_obj_set_style_bg_color(inner, lv_color_hex(0x2A2A2A), LV_PART_MAIN); + lv_obj_set_style_border_width(inner, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(inner, lv_color_hex(0x111111), LV_PART_MAIN); + lv_obj_remove_flag(inner, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_remove_flag(inner, LV_OBJ_FLAG_CLICKABLE); + + BuildSmallLabel(parent, label, x - 8, y + size + 2, size + 16); + + uintptr_t encoder_vec_idx = virtual_display_encoders.size(); + VirtualEncoder vencoder = { + (uint8_t)encoder->GetBoard(), + (uint8_t)encoder->GetIndex(), + 0, + 0, + arc, + nullptr, + 64, + 0, + CurrentDisplayWindowId(), + false, + true + }; + virtual_display_encoders.push_back(vencoder); + lv_obj_t* hitbox = BuildEncoderHitbox(parent, x, y, size); + lv_obj_add_event_cb(hitbox, DisplayEncoderEventCB, LV_EVENT_ALL, (void*)encoder_vec_idx); +} + +void SimulatorGUI::BuildFaderStrip(lv_obj_t* parent, int local_col_idx, uint8_t boardId, uint8_t faderIndex, const char* default_label) { + int strip_width = kFaderStripWidth; + + // Fader strip container + lv_obj_t* strip_container = lv_obj_create(parent); + lv_obj_set_pos(strip_container, local_col_idx * strip_width + 1, 5); + lv_obj_set_size(strip_container, strip_width - 2, kFaderWindowHeight - 10); + lv_obj_set_style_pad_all(strip_container, 2, LV_PART_MAIN); + lv_obj_set_style_border_width(strip_container, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(strip_container, lv_color_hex(0x444444), LV_PART_MAIN); + lv_obj_set_style_bg_color(strip_container, lv_color_hex(0x2D2D2D), LV_PART_MAIN); + lv_obj_remove_flag(strip_container, LV_OBJ_FLAG_SCROLLABLE); + + // Calculate element indices + uint8_t sel_code = (faderIndex == 8) ? 0x28 : (0x20 + faderIndex); + uint8_t solo_code = (faderIndex == 8) ? 0x38 : (0x30 + faderIndex); + uint8_t mute_code = (faderIndex == 8) ? 0x48 : (0x40 + faderIndex); + + // 1. Select Button + lv_obj_t* sel_btn = lv_button_create(strip_container); + lv_obj_set_pos(sel_btn, 2, 5); + lv_obj_set_size(sel_btn, strip_width - 8, 32); + lv_obj_set_style_radius(sel_btn, 2, LV_PART_MAIN); + lv_obj_set_style_bg_color(sel_btn, lv_color_hex(0x3E3E3E), LV_PART_MAIN); + lv_obj_set_style_bg_color(sel_btn, lv_color_hex(0x0078D7), LV_STATE_CHECKED); // blue glow + lv_obj_t* sel_lbl = lv_label_create(sel_btn); + lv_label_set_text(sel_lbl, "SEL"); + lv_obj_set_style_text_font(sel_lbl, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_center(sel_lbl); + + VirtualButton vbtn_sel = { boardId, sel_code, sel_btn }; + uintptr_t sel_idx = virtual_buttons.size(); + virtual_buttons.push_back(vbtn_sel); + lv_obj_add_event_cb(sel_btn, ButtonEventCB, LV_EVENT_ALL, (void*)sel_idx); + + // 2. Solo Button + lv_obj_t* solo_btn = lv_button_create(strip_container); + lv_obj_set_pos(solo_btn, 2, 42); + lv_obj_set_size(solo_btn, strip_width - 8, 32); + lv_obj_set_style_radius(solo_btn, 2, LV_PART_MAIN); + lv_obj_set_style_bg_color(solo_btn, lv_color_hex(0x3E3E3E), LV_PART_MAIN); + lv_obj_set_style_bg_color(solo_btn, lv_color_hex(0xD19A0A), LV_STATE_CHECKED); // yellow glow + lv_obj_t* solo_lbl = lv_label_create(solo_btn); + lv_label_set_text(solo_lbl, "SOLO"); + lv_obj_set_style_text_font(solo_lbl, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_center(solo_lbl); + + VirtualButton vbtn_solo = { boardId, solo_code, solo_btn }; + uintptr_t solo_idx = virtual_buttons.size(); + virtual_buttons.push_back(vbtn_solo); + lv_obj_add_event_cb(solo_btn, ButtonEventCB, LV_EVENT_ALL, (void*)solo_idx); + + // 3. Scribble LCD Container + lv_obj_t* lcd_cont = lv_obj_create(strip_container); + lv_obj_set_pos(lcd_cont, 2, 80); + lv_obj_set_size(lcd_cont, strip_width - 8, 70); + lv_obj_set_style_pad_all(lcd_cont, 1, LV_PART_MAIN); + lv_obj_set_style_border_width(lcd_cont, 1, LV_PART_MAIN); + lv_obj_set_style_border_color(lcd_cont, lv_color_hex(0x111111), LV_PART_MAIN); + lv_obj_set_style_bg_color(lcd_cont, lv_color_hex(0x000000), LV_PART_MAIN); + lv_obj_remove_flag(lcd_cont, LV_OBJ_FLAG_SCROLLABLE); + + lv_obj_t* lcd_lbl = lv_label_create(lcd_cont); + lv_label_set_text(lcd_lbl, default_label); + lv_label_set_long_mode(lcd_lbl, LV_LABEL_LONG_WRAP); + lv_obj_set_width(lcd_lbl, strip_width - 14); + lv_obj_set_style_text_color(lcd_lbl, lv_color_hex(0xFFFFFF), LV_PART_MAIN); + lv_obj_set_style_text_font(lcd_lbl, &lv_font_montserrat_14, LV_PART_MAIN); + lv_obj_set_style_text_align(lcd_lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + lv_obj_center(lcd_lbl); + + // 4. Mute Button + lv_obj_t* mute_btn = lv_button_create(strip_container); + lv_obj_set_pos(mute_btn, 2, 155); + lv_obj_set_size(mute_btn, strip_width - 8, 32); + lv_obj_set_style_radius(mute_btn, 2, LV_PART_MAIN); + lv_obj_set_style_bg_color(mute_btn, lv_color_hex(0x3E3E3E), LV_PART_MAIN); + lv_obj_set_style_bg_color(mute_btn, lv_color_hex(0xE06C75), LV_STATE_CHECKED); // red glow + lv_obj_t* mute_lbl = lv_label_create(mute_btn); + lv_label_set_text(mute_lbl, "MUTE"); + lv_obj_set_style_text_font(mute_lbl, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_center(mute_lbl); + + VirtualButton vbtn_mute = { boardId, mute_code, mute_btn }; + uintptr_t mute_idx = virtual_buttons.size(); + virtual_buttons.push_back(vbtn_mute); + lv_obj_add_event_cb(mute_btn, ButtonEventCB, LV_EVENT_ALL, (void*)mute_idx); + + // 5. Slider + lv_obj_t* slider = lv_slider_create(strip_container); + lv_obj_set_pos(slider, (strip_width - 44) / 2, 195); + lv_obj_set_size(slider, 44, 175); + lv_slider_set_range(slider, 0, 4095); + lv_obj_set_style_bg_color(slider, lv_color_hex(0x1B1B1B), LV_PART_MAIN); + lv_obj_set_style_bg_color(slider, lv_color_hex(0x555555), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(slider, lv_color_hex(0xEEEEEE), LV_PART_KNOB); + lv_obj_set_style_radius(slider, 0, LV_PART_KNOB); + lv_obj_set_style_width(slider, 44, LV_PART_KNOB); + lv_obj_set_style_height(slider, 20, LV_PART_KNOB); + + uintptr_t fader_idx = virtual_faders.size(); + VirtualFaderStrip vstrip = { + boardId, + faderIndex, + sel_btn, + solo_btn, + mute_btn, + slider, + lcd_cont, + lcd_lbl + }; + virtual_faders.push_back(vstrip); + lv_obj_add_event_cb(slider, FaderSliderEventCB, LV_EVENT_VALUE_CHANGED, (void*)fader_idx); +} + +lv_obj_t* SimulatorGUI::BuildConsoleButton(lv_obj_t* parent, const char* label, int x, int y, int w, int h, SurfaceElementId elementId) { + SurfaceElement* element = ctrl->config->GetSurfaceElement(elementId); + if (element == nullptr || element->GetType() != SurfaceElementType::Button) { + return nullptr; + } + + lv_obj_t* btn = lv_button_create(parent); + lv_obj_set_pos(btn, x, y); + lv_obj_set_size(btn, w, h); + lv_obj_set_style_radius(btn, 3, LV_PART_MAIN); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x353535), LV_PART_MAIN); + lv_obj_set_style_bg_color(btn, lv_color_hex(0x00A0A0), LV_STATE_CHECKED); // greenish/cyan glow for active panel buttons + + lv_obj_t* lbl = lv_label_create(btn); + lv_label_set_text(lbl, label); + lv_obj_set_style_text_font(lbl, &lv_font_montserrat_10, LV_PART_MAIN); + lv_obj_center(lbl); + + VirtualButton vbtn = { (uint8_t)element->GetBoard(), (uint8_t)element->GetIndex(), btn }; + uintptr_t btn_idx = virtual_buttons.size(); + virtual_buttons.push_back(vbtn); + lv_obj_add_event_cb(btn, ButtonEventCB, LV_EVENT_ALL, (void*)btn_idx); + return btn; +} + +void SimulatorGUI::DisplayEncoderEventCB(lv_event_t* e) { + uintptr_t encoder_idx = (uintptr_t)lv_event_get_user_data(e); + if (encoder_idx >= virtual_display_encoders.size()) return; + + VirtualEncoder& vencoder = virtual_display_encoders[encoder_idx]; + lv_event_code_t code = lv_event_get_code(e); + if (code == LV_EVENT_PRESSED) { + vencoder.dragRemainder = 0; + } else if (code == LV_EVENT_PRESSING) { + lv_indev_t* indev = lv_event_get_indev(e); + if (indev == nullptr) return; + + lv_point_t vect; + lv_indev_get_vect(indev, &vect); + vencoder.dragRemainder += -vect.y; + + int amount = vencoder.dragRemainder / kEncoderDragPixelsPerStep; + if (amount == 0) return; + + vencoder.dragRemainder -= amount * kEncoderDragPixelsPerStep; + amount = std::max(-24, std::min(24, amount)); + TurnEncoder(encoder_idx, amount); + } else if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST || code == LV_EVENT_CANCEL) { + vencoder.dragRemainder = 0; + } +} + +void SimulatorGUI::TurnEncoder(uintptr_t encoder_idx, int amount) { + if (encoder_idx >= virtual_display_encoders.size()) return; + if (amount == 0) return; + + if (amount > 127) amount = 127; + if (amount < -127) amount = -127; + + VirtualEncoder& vencoder = virtual_display_encoders[encoder_idx]; + if (vencoder.showFeedback) { + vencoder.lastValue = std::max(0, std::min(127, vencoder.lastValue + amount)); + lv_arc_set_value(vencoder.arc_obj, vencoder.lastValue); + } + + uint8_t encoded_amount = amount > 0 ? (uint8_t)amount : (uint8_t)(256 + amount); + SendEmulatedEvent(vencoder.boardId, 'e', vencoder.encoderIndex, encoded_amount); +} + +void SimulatorGUI::PressSurfaceButton(SurfaceElementId elementId, bool pressed) { + if (ctrl == nullptr || ctrl->config == nullptr) return; + + SurfaceElement* element = ctrl->config->GetSurfaceElement(elementId); + if (element == nullptr || element->GetType() != SurfaceElementType::Button) { + return; + } + + uint8_t board_id = (uint8_t)element->GetBoard(); + uint8_t button_code = (uint8_t)element->GetIndex(); + SendEmulatedEvent(board_id, 'b', 0, pressed ? button_code + 0x80 : button_code); + + for (auto& vbtn : virtual_buttons) { + if (vbtn.boardId == board_id && vbtn.buttonCode == button_code) { + if (pressed) { + lv_obj_add_state(vbtn.btn_obj, LV_STATE_PRESSED); + } else { + lv_obj_clear_state(vbtn.btn_obj, LV_STATE_PRESSED); + } + } + } +} + +int SimulatorGUI::SDLEventWatch(void* userdata, SDL_Event* sdl_event) { + (void)userdata; + if (sdl_event == nullptr) { + return 0; + } + + if (sdl_event->type == SDL_WINDOWEVENT && sdl_event->window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { + RaiseAllWindows(sdl_event->window.windowID); + return 0; + } + + if (sdl_event->type == SDL_KEYDOWN || sdl_event->type == SDL_KEYUP) { + if (sdl_event->type == SDL_KEYDOWN && sdl_event->key.repeat != 0) { + return 0; + } + + SurfaceElementId element_id = SurfaceElementId::NONE; + switch (sdl_event->key.keysym.sym) { + case SDLK_UP: element_id = SurfaceElementId::UP; break; + case SDLK_DOWN: element_id = SurfaceElementId::DOWN; break; + case SDLK_LEFT: element_id = SurfaceElementId::LEFT; break; + case SDLK_RIGHT: element_id = SurfaceElementId::RIGHT; break; + default: break; + } + + if (element_id != SurfaceElementId::NONE) { + PressSurfaceButton(element_id, sdl_event->type == SDL_KEYDOWN); + } + return 0; + } + + if (sdl_event->type != SDL_MOUSEWHEEL) { + return 0; + } + + int mouse_x = 0; + int mouse_y = 0; + SDL_GetMouseState(&mouse_x, &mouse_y); + + for (uintptr_t i = 0; i < virtual_display_encoders.size(); i++) { + VirtualEncoder& vencoder = virtual_display_encoders[i]; + if (vencoder.windowId != sdl_event->wheel.windowID || vencoder.arc_obj == nullptr) { + continue; + } + + lv_area_t coords; + lv_obj_get_coords(vencoder.arc_obj, &coords); + if (mouse_x >= coords.x1 && mouse_x <= coords.x2 && mouse_y >= coords.y1 && mouse_y <= coords.y2) { + TurnEncoder(i, sdl_event->wheel.y * kEncoderWheelStepsPerNotch); + break; + } + } + + return 0; +} + +void SimulatorGUI::DisplayEncoderButtonEventCB(lv_event_t* e) { + lv_event_code_t code = lv_event_get_code(e); + uintptr_t encoder_idx = (uintptr_t)lv_event_get_user_data(e); + if (encoder_idx >= virtual_display_encoders.size()) return; + + VirtualEncoder& vencoder = virtual_display_encoders[encoder_idx]; + if (!vencoder.hasButton) return; + if (code == LV_EVENT_PRESSED) { + SendEmulatedEvent(vencoder.buttonBoardId, 'b', 0, vencoder.buttonCode + 0x80); + } else if (code == LV_EVENT_RELEASED) { + SendEmulatedEvent(vencoder.buttonBoardId, 'b', 0, vencoder.buttonCode); + } +} + +void SimulatorGUI::FaderSliderEventCB(lv_event_t* e) { + lv_obj_t* slider = (lv_obj_t*)lv_event_get_target(e); + uintptr_t fader_idx = (uintptr_t)lv_event_get_user_data(e); + if (fader_idx >= virtual_faders.size()) return; + + VirtualFaderStrip& vstrip = virtual_faders[fader_idx]; + uint16_t val = (uint16_t)lv_slider_get_value(slider); + + SendEmulatedEvent(vstrip.boardId, 'f', vstrip.faderIndex, val); +} + +void SimulatorGUI::ButtonEventCB(lv_event_t* e) { + lv_event_code_t code = lv_event_get_code(e); + uintptr_t btn_idx = (uintptr_t)lv_event_get_user_data(e); + if (btn_idx >= virtual_buttons.size()) return; + + VirtualButton& vbtn = virtual_buttons[btn_idx]; + + if (code == LV_EVENT_PRESSED) { + SendEmulatedEvent(vbtn.boardId, 'b', 0, vbtn.buttonCode + 0x80); + } else if (code == LV_EVENT_RELEASED) { + SendEmulatedEvent(vbtn.boardId, 'b', 0, vbtn.buttonCode); + } +} + +void SimulatorGUI::SendEmulatedEvent(uint8_t boardId, uint8_t classId, uint8_t index, uint16_t value) { + if (ctrl == nullptr || ctrl->surface == nullptr || ctrl->surface->uart == nullptr) return; + + // 1. Send Board ID Announcement Frame + char boardMsg[4]; + boardMsg[0] = 0xFE; + boardMsg[1] = 0x80 | boardId; + boardMsg[2] = 0xFE; + int32_t sumBoard = 0xFE - 0xFE - (0x80 | boardId) - 1; + boardMsg[3] = sumBoard & 0x7F; + ctrl->surface->uart->WriteEmulatedRx(boardMsg, 4); + + // 2. Send Event Frame + if (classId == 'b' || classId == 'e') { // Short package + char eventMsg[5]; + eventMsg[0] = classId; + eventMsg[1] = index; + eventMsg[2] = (uint8_t)value; + eventMsg[3] = 0xFE; + int32_t sum = 0xFE - classId - index - (uint8_t)value - 2; + eventMsg[4] = sum & 0x7F; + ctrl->surface->uart->WriteEmulatedRx(eventMsg, 5); + } else if (classId == 'f') { // Long package + char eventMsg[6]; + eventMsg[0] = classId; + eventMsg[1] = index; + eventMsg[2] = value & 0xFF; + eventMsg[3] = (value >> 8) & 0xFF; + eventMsg[4] = 0xFE; + int32_t sum = 0xFE - classId - index - (value & 0xFF) - ((value >> 8) & 0xFF) - 3; + eventMsg[5] = sum & 0x7F; + ctrl->surface->uart->WriteEmulatedRx(eventMsg, 6); + } +} + +void SimulatorGUI::Tick() { + if (ctrl == nullptr || ctrl->surface == nullptr || ctrl->surface->uart == nullptr) return; + + // Parse commands from emulated TX buffer + ParseEmulatedTxData(); +} + +void SimulatorGUI::ParseEmulatedTxData() { + char buf[1024]; + size_t len = ctrl->surface->uart->ReadEmulatedTx(buf, sizeof(buf)); + if (len == 0) return; + + static std::vector parse_buf; + parse_buf.insert(parse_buf.end(), buf, buf + len); + + size_t i = 0; + while (i < parse_buf.size()) { + if (parse_buf[i] != 0xFE) { + i++; + continue; + } + + if (i + 1 >= parse_buf.size()) { + break; // Needs more bytes + } + + uint8_t first = parse_buf[i + 1]; + if (first >= 0x80) { + // Find next end divider 0xFE + size_t end_idx = i + 2; + while (end_idx < parse_buf.size() && parse_buf[end_idx] != 0xFE) { + end_idx++; + } + + if (end_idx >= parse_buf.size()) { + break; // Incomplete packet + } + + if (end_idx + 1 >= parse_buf.size()) { + break; // Checksum not ready + } + + uint8_t board = first & 0x7F; + if (i + 2 < end_idx) { + uint8_t cls = parse_buf[i + 2]; + uint8_t idx = (i + 3 < end_idx) ? parse_buf[i + 3] : 0; + + std::vector data; + for (size_t d = i + 4; d < end_idx; d++) { + data.push_back(parse_buf[d]); + } + + ProcessEmulatedOutput(board, cls, idx, data); + } + + i = end_idx + 2; // Jump past 0xFE and checksum + } else { + i++; // Skip invalid + } + } + + if (i > 0) { + parse_buf.erase(parse_buf.begin(), parse_buf.begin() + std::min(i, parse_buf.size())); + } +} + +void SimulatorGUI::ProcessEmulatedOutput(uint8_t boardId, uint8_t classId, uint8_t index, const std::vector& data) { + if (classId == 'F') { // Fader update + if (data.size() < 2) return; + uint16_t position = ((uint16_t)data[1] << 8) | data[0]; + + // Find matching fader strip + for (auto& vstrip : virtual_faders) { + if (vstrip.boardId == boardId && vstrip.faderIndex == index) { + if (!lv_obj_has_state(vstrip.slider, LV_STATE_PRESSED)) { + lv_slider_set_value(vstrip.slider, position, LV_ANIM_OFF); + } + break; + } + } + } else if (classId == 'L') { // LED update + if (data.empty()) return; + uint8_t val = data[0]; + bool ledOn = (val >= 0x80); + uint8_t buttonCode = ledOn ? (val - 0x80) : val; + + // Search virtual buttons + for (auto& vbtn : virtual_buttons) { + if (vbtn.boardId == boardId && vbtn.buttonCode == buttonCode) { + if (ledOn) { + lv_obj_add_state(vbtn.btn_obj, LV_STATE_CHECKED); + } else { + lv_obj_clear_state(vbtn.btn_obj, LV_STATE_CHECKED); + } + } + } + for (auto& vencoder : virtual_display_encoders) { + if (vencoder.buttonBoardId == boardId && vencoder.buttonCode == buttonCode) { + if (ledOn) { + if (vencoder.button_obj) lv_obj_add_state(vencoder.button_obj, LV_STATE_CHECKED); + } else { + if (vencoder.button_obj) lv_obj_clear_state(vencoder.button_obj, LV_STATE_CHECKED); + } + } + } + } else if (classId == 'R') { // Encoder ring update + if (data.size() < 2) return; + uint16_t ring = data[0] | ((uint16_t)data[1] << 8); + ring &= 0x1FFF; + + int highest_led = 0; + for (int bit = 0; bit < 13; bit++) { + if (ring & (1U << bit)) { + highest_led = bit; + } + } + int value = (highest_led * 127) / 12; + + for (auto& vencoder : virtual_display_encoders) { + if (vencoder.boardId == boardId && vencoder.encoderIndex == index) { + if (vencoder.showFeedback && !lv_obj_has_state(vencoder.arc_obj, LV_STATE_PRESSED)) { + vencoder.lastValue = value; + lv_arc_set_value(vencoder.arc_obj, value); + } + break; + } + } + } else if (classId == 'D') { // Scribble LCD update + if (data.empty()) return; + uint8_t color_byte = data[0]; + + // Map color (bits 0,1,2) to hexadecimal + // color values: BLACK=0, RED=1, GREEN=2, YELLOW=3, BLUE=4, PINK=5, CYAN=6, WHITE=7 + uint8_t color_idx = color_byte & 0x07; + uint32_t color_hex = 0x000000; + switch (color_idx) { + case 0: color_hex = 0x1A1A1A; break; // Black + case 1: color_hex = 0x990000; break; // Red + case 2: color_hex = 0x008000; break; // Green + case 3: color_hex = 0xD4AF37; break; // Yellow + case 4: color_hex = 0x000080; break; // Blue + case 5: color_hex = 0xC000C0; break; // Pink + case 6: color_hex = 0x008080; break; // Cyan + case 7: color_hex = 0xCCCCCC; break; // White + } + + // Parse text blocks starting at index 4 (skip icon metadata) + std::string display_text; + size_t d_idx = 4; + while (d_idx + 3 < data.size()) { + uint8_t size_and_len = data[d_idx]; + uint8_t len = size_and_len & 0x1F; + std::string s; + for (size_t k = 0; k < len && d_idx + 3 + k < data.size(); k++) { + s += (char)data[d_idx + 3 + k]; + } + if (!s.empty()) { + if (!display_text.empty()) display_text += " "; + display_text += s; + } + d_idx += 3 + len; + } + + // Update virtual fader LCD strip matching board and index + for (auto& vstrip : virtual_faders) { + // Index of display matches the channel strip index + if (vstrip.boardId == boardId && vstrip.faderIndex == index) { + lv_obj_set_style_bg_color(vstrip.lcd_container, lv_color_hex(color_hex), LV_PART_MAIN); + lv_label_set_text(vstrip.lcd_label, display_text.c_str()); + + // Set text color to contrast with the background + bool is_dark = (color_idx == 0 || color_idx == 4 || color_idx == 1); + lv_obj_set_style_text_color(vstrip.lcd_label, is_dark ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000), LV_PART_MAIN); + break; + } + } + } +} +#endif diff --git a/src/simulator-gui.h b/src/simulator-gui.h new file mode 100644 index 0000000..ae055a6 --- /dev/null +++ b/src/simulator-gui.h @@ -0,0 +1,99 @@ +#pragma once + +#ifdef BODYLESS_SDL2 + +#include +#include +#include +#include +#include "ctrl.h" + +// Forward declaration of X32Ctrl +class X32Ctrl; +union SDL_Event; + +class SimulatorGUI { +public: + static void Init(X32Ctrl* ctrl_inst); + static void Tick(); + +private: + static void CreateNavWindow(int x, int y); + static void CreateDisplayEncodersWindow(int x, int y); + static void CreateArrowsWindow(int x, int y); + static void CreateChannelStripWindow(int x, int y); + static void CreateFadersWindows(int y_start); + static void PrepareDisplay(lv_display_t* disp); + + // UI creation helpers + static void BuildDisplayEncoder(lv_obj_t* parent, int encoder_idx); + static void BuildSurfaceEncoder(lv_obj_t* parent, const char* label, int x, int y, int size, SurfaceElementId elementId); + static void BuildFaderStrip(lv_obj_t* parent, int local_col_idx, uint8_t boardId, uint8_t faderIndex, const char* default_label); + static lv_obj_t* BuildConsoleButton(lv_obj_t* parent, const char* label, int x, int y, int w, int h, SurfaceElementId elementId); + + // Event callbacks + static void DisplayEncoderEventCB(lv_event_t* e); + static void DisplayEncoderButtonEventCB(lv_event_t* e); + static void FaderSliderEventCB(lv_event_t* e); + static void ButtonEventCB(lv_event_t* e); + static int SDLEventWatch(void* userdata, SDL_Event* sdl_event); + static void RaiseAllWindows(uint32_t focused_window_id = 0); + + // Protocol helper + static void TurnEncoder(uintptr_t encoder_idx, int amount); + static void PressSurfaceButton(SurfaceElementId elementId, bool pressed); + static void SendEmulatedEvent(uint8_t boardId, uint8_t classId, uint8_t index, uint16_t value); + static void ParseEmulatedTxData(); + static void ProcessEmulatedOutput(uint8_t boardId, uint8_t classId, uint8_t index, const std::vector& data); + + // GUI Widget structure + struct VirtualFaderStrip { + uint8_t boardId; + uint8_t faderIndex; + lv_obj_t* select_btn; + lv_obj_t* solo_btn; + lv_obj_t* mute_btn; + lv_obj_t* slider; + lv_obj_t* lcd_container; + lv_obj_t* lcd_label; + }; + + struct VirtualButton { + uint8_t boardId; + uint8_t buttonCode; + lv_obj_t* btn_obj; + }; + + struct VirtualEncoder { + uint8_t boardId; + uint8_t encoderIndex; + uint8_t buttonBoardId; + uint8_t buttonCode; + lv_obj_t* arc_obj; + lv_obj_t* button_obj; + int lastValue; + int dragRemainder; + uint32_t windowId; + bool hasButton; + bool showFeedback; + }; + + static X32Ctrl* ctrl; + + static lv_display_t* nav_disp; + static lv_display_t* display_encoders_disp; + static lv_display_t* arrows_disp; + static lv_display_t* channel_strip_disp; + static lv_display_t* left_sel_disp; + static lv_display_t* board_l_disp; + static lv_display_t* board_m_disp; + static lv_display_t* right_sel_disp; + static lv_display_t* board_r_disp; + static std::vector simulator_indevs; + + static std::vector virtual_faders; + static std::vector virtual_buttons; + static std::vector virtual_display_encoders; +}; + +#endif diff --git a/src/spi.cpp b/src/spi.cpp index ab2bea3..6d84152 100644 --- a/src/spi.cpp +++ b/src/spi.cpp @@ -24,6 +24,7 @@ #include "spi.h" +#ifndef __APPLE__ SPI::SPI(X32BaseParameter* basepar) : X32Base(basepar) {} // configures a Xilinx Spartan 3A via SPI @@ -1289,3 +1290,28 @@ SpiEvent* SPI::GetNextEvent(void){ eventBuffer.pop_back(); return event; } +#endif + +#ifdef __APPLE__ +SPI::SPI(X32BaseParameter* basepar) : X32Base(basepar) { + connected = false; +} +int SPI::UploadBitstreamFpgaXilinx() { return 0; } +int SPI::UploadBitstreamFpgaLattice() { return 0; } +int SPI::UploadBitstreamDsps(bool useCli) { return 0; } +bool SPI::OpenConnectionFpga() { return false; } +bool SPI::CloseConnectionFpga() { return true; } +bool SPI::OpenConnectionDsps() { return false; } +bool SPI::CloseConnectionDsps() { return true; } +bool SPI::SendFpgaData(uint8_t txData[], uint8_t rxData[], uint8_t len) { return false; } +void SPI::QueueDspData(uint8_t dsp, uint8_t classId, uint8_t channel, uint8_t index, uint8_t valueCount, float values[]) {} +void SPI::ProcessDspTxQueue(uint8_t dsp) {} +uint32_t SPI::GetDspTxQueueLength(uint8_t dsp) { return 0; } +bool SPI::SendDspData(uint8_t dsp, sSpiTxBufferElement* buffer) { return false; } +void SPI::UpdateNumberOfExpectedReadBytes(uint8_t dsp, uint8_t classId, uint8_t channel, uint8_t index) {} +bool SPI::ReadDspData(uint8_t dsp, uint8_t classId, uint8_t channel, uint8_t index) { return false; } +void SPI::PushValuesToRxBuffer(uint8_t dsp, uint32_t valueCount, uint32_t values[]) {} +void SPI::ProcessRxData(uint8_t dsp) {} +bool SPI::HasNextEvent(void) { return false; } +SpiEvent* SPI::GetNextEvent(void) { return nullptr; } +#endif diff --git a/src/spi.h b/src/spi.h index d85eb95..59e5a72 100644 --- a/src/spi.h +++ b/src/spi.h @@ -1,7 +1,9 @@ #pragma once +#ifndef __APPLE__ #include #include +#endif #include #include diff --git a/src/uart.cpp b/src/uart.cpp index 7962ae4..4717438 100644 --- a/src/uart.cpp +++ b/src/uart.cpp @@ -88,6 +88,7 @@ int Uart::Open(const char* ttydev, uint32_t baudrate, bool raw) { } else if (baudrate == 115200) { cfsetispeed(&tty, B115200); cfsetospeed(&tty, B115200); +#ifndef __APPLE__ } else if (baudrate == 500000) { cfsetispeed(&tty, B500000); cfsetospeed(&tty, B500000); @@ -115,6 +116,7 @@ int Uart::Open(const char* ttydev, uint32_t baudrate, bool raw) { } else if (baudrate == 4000000) { cfsetispeed(&tty, B4000000); cfsetospeed(&tty, B4000000); +#endif } else { perror("Error: unsupported baudrate!"); return 1; @@ -130,6 +132,21 @@ int Uart::Open(const char* ttydev, uint32_t baudrate, bool raw) { } int Uart::Tx(MessageBase* message) { +#ifdef BODYLESS_SDL2 + if (state->bodyless && !state->bodyless_with_surface_and_adda) + { + emulated_tx_buffer.insert(emulated_tx_buffer.end(), message->buffer, message->buffer + message->current_length); + if (helper->DEBUG_UART(DEBUGLEVEL_TRACE)) { + printf("DEBUG_UART (EMULATED): TransmitRaw: "); + for (uint8_t i=0; i < message->current_length; i++){ + printf("%.2X ", (uint8_t)message->buffer[i]); + } + printf("\n"); + } + return message->current_length; + } +#endif + if (fd < 0) { fprintf(stderr, "Error: Problem on opening serial port\n"); return -1; @@ -161,6 +178,27 @@ int Uart::Tx(MessageBase* message) { } int Uart::Rx(char* buf, uint16_t bufLen) { +#ifdef BODYLESS_SDL2 + if (state->bodyless && !state->bodyless_with_surface_and_adda) + { + if (emulated_rx_buffer.empty()) { + return 0; + } + int to_read = std::min((size_t)bufLen, emulated_rx_buffer.size()); + std::copy(emulated_rx_buffer.begin(), emulated_rx_buffer.begin() + to_read, buf); + emulated_rx_buffer.erase(emulated_rx_buffer.begin(), emulated_rx_buffer.begin() + to_read); + + if (helper->DEBUG_UART(DEBUGLEVEL_TRACE)){ + printf("DEBUG_UART (EMULATED): Receive: "); + for (uint8_t i=0; i < to_read; i++){ + printf("%.2X ", (uint8_t)buf[i]); + } + printf("\n"); + } + return to_read; + } +#endif + int bytesRead; int bytesAvailable; int bytesToRead; @@ -239,4 +277,22 @@ void Uart::MirrorBack() { void Uart::FlushRxBuffer() { char buf; while (Rx(&buf, 1) > 0); -} \ No newline at end of file +} + +#ifdef BODYLESS_SDL2 +void Uart::WriteEmulatedRx(const char* data, size_t len) { + emulated_rx_buffer.insert(emulated_rx_buffer.end(), data, data + len); +} + +size_t Uart::ReadEmulatedTx(char* buf, size_t max_len) { + if (emulated_tx_buffer.empty()) return 0; + size_t to_read = std::min(max_len, emulated_tx_buffer.size()); + std::copy(emulated_tx_buffer.begin(), emulated_tx_buffer.begin() + to_read, buf); + emulated_tx_buffer.erase(emulated_tx_buffer.begin(), emulated_tx_buffer.begin() + to_read); + return to_read; +} + +bool Uart::HasEmulatedTx() const { + return !emulated_tx_buffer.empty(); +} +#endif \ No newline at end of file diff --git a/src/uart.h b/src/uart.h index 2f135fa..15dfa4b 100644 --- a/src/uart.h +++ b/src/uart.h @@ -3,7 +3,9 @@ #include #include #include +#ifndef __APPLE__ #include +#endif #include #include #include // for FIONREAD @@ -13,11 +15,20 @@ #include "message-base.h" #include "types.h" +#ifdef BODYLESS_SDL2 +#include +#include +#endif + class Uart : public X32Base { private: int fd; +#ifdef BODYLESS_SDL2 + std::vector emulated_rx_buffer; + std::vector emulated_tx_buffer; +#endif public: Uart(X32BaseParameter* basepar); @@ -26,4 +37,9 @@ class Uart : public X32Base int Rx(char* buf, uint16_t bufLen); //void MirrorBack(); void FlushRxBuffer(); +#ifdef BODYLESS_SDL2 + void WriteEmulatedRx(const char* data, size_t len); + size_t ReadEmulatedTx(char* buf, size_t max_len); + bool HasEmulatedTx() const; +#endif }; \ No newline at end of file diff --git a/src/wsm.cpp b/src/wsm.cpp index 7493e74..3ad8a64 100644 --- a/src/wsm.cpp +++ b/src/wsm.cpp @@ -50,7 +50,7 @@ int WSM::Init() return -1; } - if (bind(UdpHandle, (const struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) < 0) { + if (::bind(UdpHandle, (const struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) < 0) { //fprintf(stderr, "Error on binding UDP-socket!"); close(UdpHandle); return -1; diff --git a/src/wsm.h b/src/wsm.h index a11efd4..f8fd252 100644 --- a/src/wsm.h +++ b/src/wsm.h @@ -8,7 +8,9 @@ #include #include #include +#ifndef __APPLE__ #include +#endif #include // includes for UDP-communication diff --git a/src/x32config.cpp b/src/x32config.cpp index 1cda813..9640fac 100644 --- a/src/x32config.cpp +++ b/src/x32config.cpp @@ -307,6 +307,15 @@ void X32Config::DefineMixerparameters() { ->DefHideEncoderReset() ->DefMinMaxStandard_Uint(0, 1, 0); + DefParameter(CHANNEL_LINKED, cat, "Ch Link", 20) + ->DefStandard_Bool(false); + + DefParameter(BUS_LINKED, cat, "Bus Link", 8) + ->DefStandard_Bool(false); + + DefParameter(MATRIX_LINKED, cat, "Mtx Link", 3) + ->DefStandard_Bool(false); + DefParameter(CARD_NUMBER_OF_CHANNELS, cat, "Card Channels") ->DefMinMaxStandard_Uint(0, 5, 0) ->DefCycleMode(1, 1) @@ -545,6 +554,18 @@ void X32Config::DefineMixerparameters() { ->DefMinMaxStandard_Float(CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX, 0.0f) ->DefStepsize(2); + DefParameter(CHANNEL_STEREO_PAN, cat, "Stereo Pan", MAX_VCHANNELS) + ->DefNameShort("StPan") + ->DefUOM(MP_UOM::PANORAMA) + ->DefMinMaxStandard_Float(CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX, 0.0f) + ->DefStepsize(2); + + DefParameter(CHANNEL_STEREO_WIDTH, cat, "Width", MAX_VCHANNELS) + ->DefNameShort("Width") + ->DefUOM(MP_UOM::PERCENT) + ->DefMinMaxStandard_Float(0.0f, 1.0f, 1.0f) + ->DefStepsize(0.02f); + DefParameter(CHANNEL_SEND_LR, cat, "Send LR", MAX_VCHANNELS) ->DefStandard_Bool(true); @@ -1548,6 +1569,107 @@ bool X32Config::GetBlink(MP_ID mp) void X32Config::Set(MP_ID mp, float value, uint index) { + if ((mp == MP_ID::CHANNEL_STEREO_PAN || mp == MP_ID::CHANNEL_STEREO_WIDTH) && !isSyncingStereoLink) + { + mpm[(uint)mp]->Set(value, index); + SetParameterChanged(mp, index); + ApplyStereoPanWidth(index); + return; + } + + if ((mp == MP_ID::CHANNEL_LINKED || mp == MP_ID::BUS_LINKED || mp == MP_ID::MATRIX_LINKED) && !isSyncingStereoLink) + { + float oldValue = GetFloat(mp, index); + if (value != oldValue) + { + mpm[(uint)mp]->Set(value, index); + SetParameterChanged(mp, index); + + uint left = 0, right = 0; + GetLinkChannels(mp, index, left, right); + + if (value > 0.5f) + { + isSyncingStereoLink = true; + for (uint i = 0; i < (uint)MP_ID::__ELEMENT_COUNTER_DO_NOT_MOVE; i++) + { + MP_ID parId = (MP_ID)i; + Mixerparameter* par = mpm[i]; + if (par == nullptr || parId == NONE || par->IsReadonly() || parId == CHANNEL_PANORAMA) + { + continue; + } + + MP_CAT category = par->GetCategory(); + bool shouldCopy = false; + if (category == MP_CAT::CHANNEL || + category == MP_CAT::CHANNEL_GATE || + category == MP_CAT::CHANNEL_EQ || + category == MP_CAT::CHANNEL_DYNAMICS || + category == MP_CAT::CHANNEL_SENDS) + { + shouldCopy = true; + } + else if (parId == ROUTING_DSP_INPUT || + parId == ROUTING_DSP_INPUT_TAPPOINT || + parId == ROUTING_DSP_OUTPUT || + parId == ROUTING_DSP_OUTPUT_TAPPOINT) + { + shouldCopy = true; + } + + if (shouldCopy) + { + if (left < par->GetInstances() && right < par->GetInstances()) + { + if (parId == ROUTING_DSP_INPUT || parId == ROUTING_DSP_OUTPUT) + { + float val = GetFloat(parId, left); + if (val > 0) { Set(parId, val + 1, right); } + else { Set(parId, val, right); } + } + else + { + if (par->GetType() == MP_VALUE_TYPE::STRING) + { + Set(parId, GetString(parId, left), right); + } + else + { + Set(parId, GetFloat(parId, left), right); + } + } + } + } + } + + Set(CHANNEL_STEREO_PAN, 0.0f, left); + Set(CHANNEL_STEREO_PAN, 0.0f, right); + Set(CHANNEL_STEREO_WIDTH, 1.0f, left); + Set(CHANNEL_STEREO_WIDTH, 1.0f, right); + Set(CHANNEL_VOLUME, CHANNEL_VOLUME_MIN, left); + Set(CHANNEL_VOLUME, CHANNEL_VOLUME_MIN, right); + Set(CHANNEL_PANORAMA, 0.0f, left); + Set(CHANNEL_PANORAMA, 0.0f, right); + isSyncingStereoLink = false; + } + else + { + isSyncingStereoLink = true; + Set(CHANNEL_STEREO_PAN, 0.0f, left); + Set(CHANNEL_STEREO_PAN, 0.0f, right); + Set(CHANNEL_STEREO_WIDTH, 1.0f, left); + Set(CHANNEL_STEREO_WIDTH, 1.0f, right); + Set(CHANNEL_VOLUME, CHANNEL_VOLUME_MIN, left); + Set(CHANNEL_VOLUME, CHANNEL_VOLUME_MIN, right); + Set(CHANNEL_PANORAMA, 0.0f, left); + Set(CHANNEL_PANORAMA, 0.0f, right); + isSyncingStereoLink = false; + } + return; + } + } + mpm[(uint)mp]->Set(value, index); SetParameterChanged(mp, index); @@ -1615,17 +1737,105 @@ void X32Config::SetParameterChanged(MP_ID mp, uint index) helper->Log(message.c_str()); } + + // Sync peer channel if linked + if (!isSyncingStereoLink) + { + uint peerIndex = 0; + if (GetPeerVChannel(index, peerIndex)) + { + isSyncingStereoLink = true; + + Mixerparameter* parameter = GetParameter(mp); + if (parameter != nullptr && mp != NONE) + { + MP_CAT category = parameter->GetCategory(); + bool shouldSync = false; + if (category == MP_CAT::CHANNEL || + category == MP_CAT::CHANNEL_GATE || + category == MP_CAT::CHANNEL_EQ || + category == MP_CAT::CHANNEL_DYNAMICS || + category == MP_CAT::CHANNEL_SENDS) + { + shouldSync = true; + } + else if (mp == ROUTING_DSP_INPUT || + mp == ROUTING_DSP_INPUT_TAPPOINT || + mp == ROUTING_DSP_OUTPUT || + mp == ROUTING_DSP_OUTPUT_TAPPOINT) + { + shouldSync = true; + } + + if (shouldSync && mp != CHANNEL_PANORAMA) + { + if (index < parameter->GetInstances() && peerIndex < parameter->GetInstances()) + { + if (mp == ROUTING_DSP_INPUT || mp == ROUTING_DSP_OUTPUT) + { + float val = GetFloat(mp, index); + if (val > 0) + { + if (index % 2 == 0) + { + Set(mp, val + 1.0f, peerIndex); + } + else + { + Set(mp, val - 1.0f, peerIndex); + } + } + else + { + Set(mp, val, peerIndex); + } + } + else + { + if (parameter->GetType() == MP_VALUE_TYPE::STRING) + { + Set(mp, GetString(mp, index), peerIndex); + } + else + { + Set(mp, GetFloat(mp, index), peerIndex); + } + } + } + } + } + + isSyncingStereoLink = false; + } + } } void X32Config::Change(MP_ID mp, int amount, uint index) { + if (mp == MP_ID::CHANNEL_PANORAMA && IsStereoLinkedMainRouted(index)) + { + Change(MP_ID::CHANNEL_STEREO_PAN, amount, index); + return; + } + mpm[(uint)mp]->Change(amount, index); SetParameterChanged(mp, index); + + if (mp == MP_ID::CHANNEL_STEREO_PAN || mp == MP_ID::CHANNEL_STEREO_WIDTH) + { + ApplyStereoPanWidth(index); + } } void X32Config::Toggle(MP_ID mp, uint index) { + if (mp == MP_ID::CHANNEL_LINKED || mp == MP_ID::BUS_LINKED || mp == MP_ID::MATRIX_LINKED) + { + Set(mp, GetBool(mp, index) ? 0.0f : 1.0f, index); + return; + } + mpm[(uint)mp]->Toggle(index); SetParameterChanged(mp, index); @@ -1639,9 +1849,20 @@ void X32Config::Refresh(MP_ID mp, uint index) void X32Config::Reset(MP_ID mp, uint index) { + if (mp == MP_ID::CHANNEL_PANORAMA && IsStereoLinkedMainRouted(index)) + { + Reset(MP_ID::CHANNEL_STEREO_PAN, index); + return; + } + mpm[(uint)mp]->Reset(index); SetParameterChanged(mp, index); + + if (mp == MP_ID::CHANNEL_STEREO_PAN || mp == MP_ID::CHANNEL_STEREO_WIDTH) + { + ApplyStereoPanWidth(index); + } } MP_ID X32Config::ParameterCalcId(SurfaceBindingParameter* binding_parameter) @@ -3011,3 +3232,135 @@ X32AssignBank* X32Config::GetAssignBank(X32AssignBankId id) { return assingBanks[(uint)id]; } + +bool X32Config::GetPeerVChannel(uint index, uint& peerIndex) +{ + if (index < 40) + { + uint pairIdx = index < 32 ? index / 2 : 16 + (index - 32) / 2; + if (GetBool(MP_ID::CHANNEL_LINKED, pairIdx)) + { + peerIndex = index ^ 1; + return true; + } + } + else if (index >= 48 && index < 64) + { + uint pairIdx = (index - 48) / 2; + if (GetBool(MP_ID::BUS_LINKED, pairIdx)) + { + peerIndex = index ^ 1; + return true; + } + } + else if (index >= 64 && index < 70) + { + uint pairIdx = (index - 64) / 2; + if (GetBool(MP_ID::MATRIX_LINKED, pairIdx)) + { + peerIndex = index ^ 1; + return true; + } + } + return false; +} + +bool X32Config::IsRightChannelOfLinkedPair(uint index) +{ + if (index < 40) + { + if (index % 2 == 1) + { + uint pairIdx = index < 32 ? index / 2 : 16 + (index - 32) / 2; + return GetBool(MP_ID::CHANNEL_LINKED, pairIdx); + } + } + else if (index >= 48 && index < 64) + { + if ((index - 48) % 2 == 1) + { + return GetBool(MP_ID::BUS_LINKED, (index - 48) / 2); + } + } + else if (index >= 64 && index < 70) + { + if ((index - 64) % 2 == 1) + { + return GetBool(MP_ID::MATRIX_LINKED, (index - 64) / 2); + } + } + return false; +} + +bool X32Config::IsStereoLinkedMainRouted(uint index) +{ + uint peerIndex = 0; + if (!GetPeerVChannel(index, peerIndex)) + { + return false; + } + + if (index < 40) + { + return true; + } + + if (index >= 48 && index < 64) + { + return GetBool(MP_ID::CHANNEL_SEND_LR, index) || GetBool(MP_ID::CHANNEL_SEND_LR, peerIndex); + } + + return false; +} + +void X32Config::ApplyStereoPanWidth(uint index) +{ + uint peerIndex = 0; + if (!IsStereoLinkedMainRouted(index) || !GetPeerVChannel(index, peerIndex)) + { + return; + } + + uint leftChan = index < peerIndex ? index : peerIndex; + uint rightChan = index < peerIndex ? peerIndex : index; + float center = GetFloat(MP_ID::CHANNEL_STEREO_PAN, index); + float width = GetFloat(MP_ID::CHANNEL_STEREO_WIDTH, index) * CHANNEL_PANORAMA_MAX; + float leftPan = helper->Saturate(center - width, CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX); + float rightPan = helper->Saturate(center + width, CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX); + + isSyncingStereoLink = true; + Set(MP_ID::CHANNEL_STEREO_PAN, center, leftChan); + Set(MP_ID::CHANNEL_STEREO_PAN, center, rightChan); + Set(MP_ID::CHANNEL_STEREO_WIDTH, GetFloat(MP_ID::CHANNEL_STEREO_WIDTH, index), leftChan); + Set(MP_ID::CHANNEL_STEREO_WIDTH, GetFloat(MP_ID::CHANNEL_STEREO_WIDTH, index), rightChan); + Set(MP_ID::CHANNEL_PANORAMA, leftPan, leftChan); + Set(MP_ID::CHANNEL_PANORAMA, rightPan, rightChan); + isSyncingStereoLink = false; +} + +void X32Config::GetLinkChannels(MP_ID mp, uint index, uint& leftChan, uint& rightChan) +{ + if (mp == MP_ID::CHANNEL_LINKED) + { + if (index < 16) + { + leftChan = index * 2; + rightChan = index * 2 + 1; + } + else + { + leftChan = 32 + (index - 16) * 2; + rightChan = 32 + (index - 16) * 2 + 1; + } + } + else if (mp == MP_ID::BUS_LINKED) + { + leftChan = 48 + index * 2; + rightChan = 48 + index * 2 + 1; + } + else if (mp == MP_ID::MATRIX_LINKED) + { + leftChan = 64 + index * 2; + rightChan = 64 + index * 2 + 1; + } +} diff --git a/src/x32config.h b/src/x32config.h index c43b467..c3e867c 100644 --- a/src/x32config.h +++ b/src/x32config.h @@ -45,11 +45,18 @@ class X32Config // old X32_MODEL _model; + bool isSyncingStereoLink = false; public: X32Config(Helper* h); + bool GetPeerVChannel(uint index, uint& peerIndex); + bool IsRightChannelOfLinkedPair(uint index); + bool IsStereoLinkedMainRouted(uint index); + void ApplyStereoPanWidth(uint index); + void GetLinkChannels(MP_ID mp, uint index, uint& leftChan, uint& rightChan); + bool LoadConfig(uint scene); void Save(uint scene); @@ -157,4 +164,3 @@ class X32ConfigFile public: vector entries; }; - diff --git a/src/xremote.cpp b/src/xremote.cpp index 5182e73..8b779e7 100644 --- a/src/xremote.cpp +++ b/src/xremote.cpp @@ -49,7 +49,7 @@ int8_t XRemote::Init() { ServerAddr.sin_addr.s_addr = INADDR_ANY; ServerAddr.sin_port = htons(10023); - if (bind(UdpHandle, (const struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) < 0) { + if (::bind(UdpHandle, (const struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) < 0) { //fprintf(stderr, "Error on binding UDP-socket!"); close(UdpHandle); return -1; diff --git a/src/xremote.h b/src/xremote.h index 1b4ebde..7916b76 100644 --- a/src/xremote.h +++ b/src/xremote.h @@ -8,7 +8,9 @@ #include #include #include +#ifndef __APPLE__ #include +#endif // includes for UDP-communication #include