diff --git a/src/config.cpp b/src/config.cpp index c4ab3b9..4d71ec3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,5 +1,6 @@ #include "config.h" #include "enum.h" +#include "../lib_ext/doctest/doctest/doctest.h" namespace OMC { @@ -360,6 +361,18 @@ namespace OMC ->DefMinMaxStandard_Uint(0, 1, 0) ->DefClientParameter(); + DefParameter(CHANNEL_LINKED, cat, "Stereo Channels", 20) + ->DefStandard_Bool(false) + ->DefClientParameter(); + + DefParameter(BUS_LINKED, cat, "Stereo Busses", 8) + ->DefStandard_Bool(false) + ->DefClientParameter(); + + DefParameter(MATRIX_LINKED, cat, "Stereo Matrices", 3) + ->DefStandard_Bool(false) + ->DefClientParameter(); + DefParameter(CARD_NUMBER_OF_CHANNELS, cat, "Card Channels") ->DefMinMaxStandard_Uint(0, 5, 0) ->DefCycleMode(1, 1) @@ -619,6 +632,20 @@ namespace OMC ->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) + ->DefClientParameter(); + + DefParameter(CHANNEL_STEREO_WIDTH, cat, "Stereo Width", MAX_VCHANNELS) + ->DefNameShort("Width") + ->DefUOM(MP_UOM::PERCENT) + ->DefMinMaxStandard_Float(0.0f, 1.0f, 1.0f) + ->DefStepsize(0.02f) + ->DefClientParameter(); + DefParameter(CHANNEL_SEND_LR, cat, "Send LR", MAX_VCHANNELS) ->DefStandard_Bool(true); @@ -1684,6 +1711,85 @@ namespace OMC void Config::Set(MP_ID mp, float value, uint index) { + if ((mp == CHANNEL_LINKED || mp == BUS_LINKED || mp == MATRIX_LINKED) && !isSyncingStereoLink) + { + bool wasLinked = GetBool(mp, index); + bool willLink = value > 0.5f; + if (wasLinked == willLink) + { + return; + } + + uint leftChannel = 0; + uint rightChannel = 0; + if (!GetLinkChannels(mp, index, leftChannel, rightChannel)) + { + return; + } + + mpm[(uint)mp]->Set(willLink ? 1.0f : 0.0f, index); + SetParameterChanged(mp, index); + + isSyncingStereoLink = true; + if (willLink) + { + for (uint parameterIndex = 0; parameterIndex < (uint)MP_ID::__ELEMENT_COUNTER_DO_NOT_MOVE; parameterIndex++) + { + MP_ID parameterId = (MP_ID)parameterIndex; + Mixerparameter* parameter = mpm[parameterIndex]; + if (parameter == nullptr || parameterId == NONE || parameter->IsReadonly() || + parameterId == CHANNEL_PANORAMA || parameterId == CHANNEL_STEREO_PAN || + parameterId == CHANNEL_STEREO_WIDTH) + { + continue; + } + + MP_CAT category = parameter->GetCategory(); + bool isChannelParameter = category == MP_CAT::CHANNEL || + category == MP_CAT::CHANNEL_GATE || category == MP_CAT::CHANNEL_EQ || + category == MP_CAT::CHANNEL_DYNAMICS || category == MP_CAT::CHANNEL_SENDS; + bool isRoutingParameter = parameterId == ROUTING_DSP_INPUT || + parameterId == ROUTING_DSP_INPUT_TAPPOINT || parameterId == ROUTING_DSP_OUTPUT || + parameterId == ROUTING_DSP_OUTPUT_TAPPOINT; + + if ((isChannelParameter || isRoutingParameter) && + leftChannel < parameter->GetInstances() && rightChannel < parameter->GetInstances()) + { + if (parameterId == ROUTING_DSP_INPUT || parameterId == ROUTING_DSP_OUTPUT) + { + float source = GetFloat(parameterId, leftChannel); + Set(parameterId, source > 0 ? source + 1.0f : source, rightChannel); + } + else if (parameter->GetType() == MP_VALUE_TYPE::STRING) + { + Set(parameterId, GetString(parameterId, leftChannel), rightChannel); + } + else + { + Set(parameterId, GetFloat(parameterId, leftChannel), rightChannel); + } + } + } + } + + Set(CHANNEL_STEREO_PAN, 0.0f, leftChannel); + Set(CHANNEL_STEREO_PAN, 0.0f, rightChannel); + Set(CHANNEL_STEREO_WIDTH, 1.0f, leftChannel); + Set(CHANNEL_STEREO_WIDTH, 1.0f, rightChannel); + Set(CHANNEL_PANORAMA, willLink ? CHANNEL_PANORAMA_MIN : 0.0f, leftChannel); + Set(CHANNEL_PANORAMA, willLink ? CHANNEL_PANORAMA_MAX : 0.0f, rightChannel); + isSyncingStereoLink = false; + return; + } + + if ((mp == CHANNEL_STEREO_PAN || mp == CHANNEL_STEREO_WIDTH) && !isSyncingStereoLink) + { + mpm[(uint)mp]->Set(value, index); + SetParameterChanged(mp, index); + ApplyStereoPanWidth(index); + return; + } + if (IsClientMode() && GetParameter(mp)->IsOSC() && !(GetParameter(mp)->IsClientParameter())) { // Send Parameter to Server via Callback @@ -1779,10 +1885,18 @@ namespace OMC helper->Log(message.c_str()); } + + SyncLinkedParameter(mp, index); } void Config::Change(MP_ID mp, int amount, uint index) { + if (mp == CHANNEL_PANORAMA && IsStereoLinkedMainRouted(index)) + { + Change(CHANNEL_STEREO_PAN, amount, index); + return; + } + if (IsClientMode() && GetParameter(mp)->IsOSC() && !(GetParameter(mp)->IsClientParameter())) { // Send Parameter to Server via Callback @@ -1796,11 +1910,22 @@ namespace OMC // Process Parameter localy mpm[(uint)mp]->Change(amount, index); SetParameterChanged(mp, index); + + if (mp == CHANNEL_STEREO_PAN || mp == CHANNEL_STEREO_WIDTH) + { + ApplyStereoPanWidth(index); + } } } void Config::Toggle(MP_ID mp, uint index) { + if (mp == CHANNEL_LINKED || mp == BUS_LINKED || mp == MATRIX_LINKED) + { + Set(mp, GetBool(mp, index) ? 0.0f : 1.0f, index); + return; + } + if (IsClientMode() && GetParameter(mp)->IsOSC() && !(GetParameter(mp)->IsClientParameter())) { // Send Parameter to Server via Callback @@ -1825,6 +1950,12 @@ namespace OMC void Config::Reset(MP_ID mp, uint index) { + if (mp == CHANNEL_PANORAMA && IsStereoLinkedMainRouted(index)) + { + Reset(CHANNEL_STEREO_PAN, index); + return; + } + if (IsClientMode() && GetParameter(mp)->IsOSC() && !(GetParameter(mp)->IsClientParameter())) { // Send Parameter to Server via Callback @@ -1838,6 +1969,11 @@ namespace OMC // Process Parameter localy mpm[(uint)mp]->Reset(index); SetParameterChanged(mp, index); + + if (mp == CHANNEL_STEREO_PAN || mp == CHANNEL_STEREO_WIDTH) + { + ApplyStereoPanWidth(index); + } } } @@ -3464,6 +3600,167 @@ namespace OMC return assingBanks[(uint)id]; } + bool Config::GetLinkChannels(MP_ID mp, uint index, uint& leftChannel, uint& rightChannel) + { + if (mp == CHANNEL_LINKED && index < 20) + { + leftChannel = index < 16 ? index * 2 : 32 + (index - 16) * 2; + } + else if (mp == BUS_LINKED && index < 8) + { + leftChannel = 48 + index * 2; + } + else if (mp == MATRIX_LINKED && index < 3) + { + leftChannel = 64 + index * 2; + } + else + { + return false; + } + + rightChannel = leftChannel + 1; + return true; + } + + bool Config::GetPeerVChannel(uint index, uint& peerIndex) + { + MP_ID linkParameter = NONE; + uint pairIndex = 0; + + if (index < 32) + { + linkParameter = CHANNEL_LINKED; + pairIndex = index / 2; + } + else if (index < 40) + { + linkParameter = CHANNEL_LINKED; + pairIndex = 16 + (index - 32) / 2; + } + else if (index >= 48 && index < 64) + { + linkParameter = BUS_LINKED; + pairIndex = (index - 48) / 2; + } + else if (index >= 64 && index < 70) + { + linkParameter = MATRIX_LINKED; + pairIndex = (index - 64) / 2; + } + + if (linkParameter != NONE && GetBool(linkParameter, pairIndex)) + { + peerIndex = index ^ 1; + return true; + } + return false; + } + + bool Config::IsRightChannelOfLinkedPair(uint index) + { + uint peerIndex = 0; + return GetPeerVChannel(index, peerIndex) && index > peerIndex; + } + + bool Config::IsStereoLinkedMainRouted(uint index) + { + uint peerIndex = 0; + if (!GetPeerVChannel(index, peerIndex)) + { + return false; + } + + if (index < 40) + { + return true; + } + + return index >= 48 && index < 64 && + (GetBool(CHANNEL_SEND_LR, index) || GetBool(CHANNEL_SEND_LR, peerIndex)); + } + + void Config::ApplyStereoPanWidth(uint index) + { + uint peerIndex = 0; + if (!IsStereoLinkedMainRouted(index) || !GetPeerVChannel(index, peerIndex)) + { + return; + } + + uint leftChannel = min(index, peerIndex); + uint rightChannel = max(index, peerIndex); + float center = GetFloat(CHANNEL_STEREO_PAN, index); + float width = GetFloat(CHANNEL_STEREO_WIDTH, index); + float panOffset = width * CHANNEL_PANORAMA_MAX; + + isSyncingStereoLink = true; + Set(CHANNEL_STEREO_PAN, center, leftChannel); + Set(CHANNEL_STEREO_PAN, center, rightChannel); + Set(CHANNEL_STEREO_WIDTH, width, leftChannel); + Set(CHANNEL_STEREO_WIDTH, width, rightChannel); + Set(CHANNEL_PANORAMA, helper->Saturate(center - panOffset, CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX), leftChannel); + Set(CHANNEL_PANORAMA, helper->Saturate(center + panOffset, CHANNEL_PANORAMA_MIN, CHANNEL_PANORAMA_MAX), rightChannel); + isSyncingStereoLink = false; + } + + void Config::SyncLinkedParameter(MP_ID mp, uint index) + { + if (isSyncingStereoLink || mp == NONE || mp == CHANNEL_PANORAMA || + mp == CHANNEL_STEREO_PAN || mp == CHANNEL_STEREO_WIDTH || + mp == CHANNEL_LINKED || mp == BUS_LINKED || mp == MATRIX_LINKED) + { + return; + } + + uint peerIndex = 0; + if (!GetPeerVChannel(index, peerIndex)) + { + return; + } + + Mixerparameter* parameter = GetParameter(mp); + MP_CAT category = parameter->GetCategory(); + bool isChannelParameter = category == MP_CAT::CHANNEL || + category == MP_CAT::CHANNEL_GATE || category == MP_CAT::CHANNEL_EQ || + category == MP_CAT::CHANNEL_DYNAMICS || category == MP_CAT::CHANNEL_SENDS; + bool isRoutingParameter = mp == ROUTING_DSP_INPUT || mp == ROUTING_DSP_INPUT_TAPPOINT || + mp == ROUTING_DSP_OUTPUT || mp == ROUTING_DSP_OUTPUT_TAPPOINT; + + if ((!isChannelParameter && !isRoutingParameter) || peerIndex >= parameter->GetInstances()) + { + return; + } + + // A client receives both sides back from the mixer. Only mirror the + // visible (left) strip so the returned right-side update cannot echo + // back indefinitely over OSC. + if (IsClientMode() && index > peerIndex) + { + return; + } + + isSyncingStereoLink = true; + if (mp == ROUTING_DSP_INPUT || mp == ROUTING_DSP_OUTPUT) + { + float source = GetFloat(mp, index); + if (source > 0) + { + source += index < peerIndex ? 1.0f : -1.0f; + } + Set(mp, source, 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 Config::PrintOscDoc() { printf("\n"); @@ -3519,4 +3816,64 @@ namespace OMC printf("\n"); printf("\n"); } -} \ No newline at end of file + + TEST_CASE("Stereo-linked channels mirror controls without dropping level") + { + Helper helper; + Config config("X32", &helper, false); + + config.Set(CHANNEL_VOLUME, -12.5f, 0); + config.Set(CHANNEL_GAIN, 6.0f, 0); + config.Set(ROUTING_DSP_INPUT, 11.0f, 0); + config.Set(CHANNEL_LINKED, 1.0f, 0); + + uint peer = 0; + CHECK(config.GetPeerVChannel(0, peer)); + CHECK(peer == 1); + CHECK(config.IsRightChannelOfLinkedPair(1)); + CHECK(config.GetFloat(CHANNEL_VOLUME, 0) == doctest::Approx(-12.5f)); + CHECK(config.GetFloat(CHANNEL_VOLUME, 1) == doctest::Approx(-12.5f)); + CHECK(config.GetFloat(CHANNEL_GAIN, 1) == doctest::Approx(6.0f)); + CHECK(config.GetFloat(ROUTING_DSP_INPUT, 1) == doctest::Approx(12.0f)); + CHECK(config.GetFloat(CHANNEL_PANORAMA, 0) == doctest::Approx(CHANNEL_PANORAMA_MIN)); + CHECK(config.GetFloat(CHANNEL_PANORAMA, 1) == doctest::Approx(CHANNEL_PANORAMA_MAX)); + + config.Set(CHANNEL_VOLUME, -8.0f, 1); + CHECK(config.GetFloat(CHANNEL_VOLUME, 0) == doctest::Approx(-8.0f)); + CHECK(config.GetFloat(CHANNEL_VOLUME, 1) == doctest::Approx(-8.0f)); + } + + TEST_CASE("Stereo pan and width drive the linked channel pair") + { + Helper helper; + Config config("X32", &helper, false); + config.Set(CHANNEL_LINKED, 1.0f, 0); + + config.Set(CHANNEL_STEREO_WIDTH, 0.5f, 0); + config.Set(CHANNEL_STEREO_PAN, 20.0f, 0); + + CHECK(config.GetFloat(CHANNEL_PANORAMA, 0) == doctest::Approx(-30.0f)); + CHECK(config.GetFloat(CHANNEL_PANORAMA, 1) == doctest::Approx(70.0f)); + CHECK(config.GetFloat(CHANNEL_STEREO_WIDTH, 1) == doctest::Approx(0.5f)); + CHECK(config.GetFloat(CHANNEL_STEREO_PAN, 1) == doctest::Approx(20.0f)); + + config.Set(CHANNEL_LINKED, 0.0f, 0); + CHECK(config.GetFloat(CHANNEL_PANORAMA, 0) == doctest::Approx(0.0f)); + CHECK(config.GetFloat(CHANNEL_PANORAMA, 1) == doctest::Approx(0.0f)); + } + + TEST_CASE("Bus and matrix link pairs use their virtual-channel blocks") + { + Helper helper; + Config config("X32", &helper, false); + uint peer = 0; + + config.Set(BUS_LINKED, 1.0f, 7); + CHECK(config.GetPeerVChannel(62, peer)); + CHECK(peer == 63); + + config.Set(MATRIX_LINKED, 1.0f, 2); + CHECK(config.GetPeerVChannel(68, peer)); + CHECK(peer == 69); + } +} diff --git a/src/config.h b/src/config.h index c00b517..f19bff5 100644 --- a/src/config.h +++ b/src/config.h @@ -51,6 +51,10 @@ namespace OMC // old OMC_MODEL _model; + bool isSyncingStereoLink = false; + + bool GetLinkChannels(MP_ID mp, uint index, uint& leftChannel, uint& rightChannel); + void SyncLinkedParameter(MP_ID mp, uint index); public: @@ -102,6 +106,11 @@ namespace OMC void Refresh(MP_ID mp, uint index = 0); void Reset(MP_ID mp, uint index = 0); + bool GetPeerVChannel(uint index, uint& peerIndex); + bool IsRightChannelOfLinkedPair(uint index); + bool IsStereoLinkedMainRouted(uint index); + void ApplyStereoPanWidth(uint index); + MP_ID ParameterCalcId(SurfaceBindingParameter* binding_parameter); uint ParameterCalcIndex(SurfaceBindingParameter* binding_parameter); MP_ID ParameterDependsOn(SurfaceBindingParameter* binding_parameter); @@ -180,4 +189,3 @@ namespace OMC vector entries; }; } - diff --git a/src/ctrl-client.cpp b/src/ctrl-client.cpp index 426c92d..5569b41 100644 --- a/src/ctrl-client.cpp +++ b/src/ctrl-client.cpp @@ -24,6 +24,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" @@ -361,6 +362,7 @@ void CtrlClient::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); @@ -501,6 +503,11 @@ void CtrlClient::syncGuiOrLcd() { // sync mixer state to Surface void CtrlClient::syncSurface(bool fullSync) { + if (config->HasParametersChanged({CHANNEL_LINKED, BUS_LINKED, MATRIX_LINKED})) + { + surface->UpdateStereoBanks(); + } + // ###################################### // // Check, if banking has changed diff --git a/src/enum.h b/src/enum.h index 120ec6d..f42c6a1 100644 --- a/src/enum.h +++ b/src/enum.h @@ -51,6 +51,7 @@ enum class X32_PAGE :int // sub-pages of setup SETUP_CARD, SETUP_SURFACE, + SETUP_MIXER_CONFIG, ABOUT, DEBUG, PROTOTYPEGUI, @@ -478,6 +479,14 @@ enum class MP_ID { DMX_ARTNET_VALUE, //DMX_ARTNET_OFFSET, + // Stereo-link configuration. Keep these at the end so existing scene + // files retain the numeric IDs of all established parameters. + CHANNEL_LINKED, + BUS_LINKED, + MATRIX_LINKED, + CHANNEL_STEREO_PAN, + CHANNEL_STEREO_WIDTH, + __ELEMENT_COUNTER_DO_NOT_MOVE }; @@ -1202,4 +1211,4 @@ enum class OMCBankTarget BusSection, WING_COMPACT, -}; \ No newline at end of file +}; diff --git a/src/mixerparameter.cpp b/src/mixerparameter.cpp index 880c2da..72cf121 100644 --- a/src/mixerparameter.cpp +++ b/src/mixerparameter.cpp @@ -32,6 +32,11 @@ namespace OMC { using enum MP_UOM; + if (parameter_id == MP_ID::CHANNEL_STEREO_WIDTH && value_float == 0.0f) + { + return "Mono"; + } + if (unitOfMeasurement == PERCENT) { value_float *= 100.0f; } @@ -506,4 +511,4 @@ namespace OMC // check that values are the same CHECK(metervalue == metervalue_received); } -} \ No newline at end of file +} diff --git a/src/page-about.h b/src/page-about.h index 695228d..199cc46 100755 --- a/src/page-about.h +++ b/src/page-about.h @@ -8,14 +8,14 @@ namespace OMC 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; hideEncoders = true; } }; -} \ No newline at end of file +} diff --git a/src/page-debug.h b/src/page-debug.h index 15812a7..e873fa8 100644 --- a/src/page-debug.h +++ b/src/page-debug.h @@ -20,7 +20,7 @@ class PageDebug: public Page tabLayer0 = objects.maintab; tabIndex0 = 3; tabLayer1 = objects.setuptab; - tabIndex1 = 4; + tabIndex1 = 5; osc_client = new OscClient(pagebasepar); } @@ -142,4 +142,4 @@ class PageDebug: public Page } }; -} \ No newline at end of file +} diff --git a/src/page-main.h b/src/page-main.h index ce7b4cc..9591200 100644 --- a/src/page-main.h +++ b/src/page-main.h @@ -14,6 +14,32 @@ class PageMain : public Page uint bankingSends = 0; uint bankingSendsBefore = 0; + bool stereoControlsBound = false; + + void BindMainEncoders() + { + bool stereo = 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, stereo ? CHANNEL_STEREO_PAN : CHANNEL_PANORAMA); + config->SurfaceBind(SurfaceElementId::DISPLAY_ENCODER_BUTTON_2, MixerparameterAction::RESET_SELECTED_CHANNEL, stereo ? CHANNEL_STEREO_PAN : CHANNEL_PANORAMA); + + if (stereo) + { + 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 = stereo; + } public: @@ -29,13 +55,18 @@ 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 stereo = config->IsStereoLinkedMainRouted(config->GetUint(SELECTED_CHANNEL)); + if (force_update || stereo != stereoControlsBound || + config->HasParametersChanged({CHANNEL_LINKED, BUS_LINKED, CHANNEL_SEND_LR})) + { + BindMainEncoders(); + } } }; -} \ No newline at end of file +} diff --git a/src/page-prototypegui.h b/src/page-prototypegui.h index fa88cb8..fe3c018 100755 --- a/src/page-prototypegui.h +++ b/src/page-prototypegui.h @@ -18,7 +18,7 @@ class PagePrototypeGui: public Page tabLayer0 = objects.maintab; tabIndex0 = 3; tabLayer1 = objects.setuptab; - tabIndex1 = 5; + tabIndex1 = 6; } void OnInit() override @@ -45,4 +45,4 @@ class PagePrototypeGui: public Page } }; -} \ No newline at end of file +} diff --git a/src/page-routing-channels.h b/src/page-routing-channels.h index b666e6c..15d92a7 100644 --- a/src/page-routing-channels.h +++ b/src/page-routing-channels.h @@ -19,6 +19,18 @@ class PageRoutingChannels: public Page int gui_selected_item = 0; int gui_selected_item_before = 0; bool page_routing_dsp1_table_drawn = false; + + String GetDestinationName(uint index) + { + String name = index < 32 ? String("Channel ") + String(index + 1) : + String("Aux ") + String(index - 31); + uint peerIndex = 0; + if (config->GetPeerVChannel(index, peerIndex)) + { + name += index < peerIndex ? " [ST L]" : " [ST R]"; + } + return name; + } static void draw_event_header_cb(lv_event_t * e) { lv_draw_task_t * draw_task = lv_event_get_draw_task(e); @@ -181,16 +193,7 @@ class PageRoutingChannels: public Page 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); - } + String inputChannelName = GetDestinationName(i); 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()); @@ -220,6 +223,14 @@ class PageRoutingChannels: public Page void OnChange(bool force) override { UpdateRowSelection(); + + if (config->HasParameterChanged(CHANNEL_LINKED) || force) + { + for (uint index = 0; index < 40; index++) + { + lv_table_set_cell_value(objects.table_routing_dsp_input, index, 4, GetDestinationName(index).c_str()); + } + } if(config->HasParametersChanged({ROUTING_DSP_INPUT, ROUTING_DSP_INPUT_TAPPOINT}) || force) { @@ -276,4 +287,4 @@ class PageRoutingChannels: public Page } }; -} \ No newline at end of file +} diff --git a/src/page-setup-mixer-config.h b/src/page-setup-mixer-config.h new file mode 100644 index 0000000..5798bda --- /dev/null +++ b/src/page-setup-mixer-config.h @@ -0,0 +1,406 @@ +#pragma once +#include "page.h" + +using namespace std; + +namespace OMC +{ + +class PageSetupMixerConfig: public Page +{ + using enum MP_ID; + + 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; + } + + 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_count(tab_bar) > 0) + { + lv_obj_t* tab_btn = lv_obj_get_child(tab_bar, lv_obj_get_child_count(tab_bar) - 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_button_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 c89ea45..210c286 100644 --- a/src/page-setup-surface.h +++ b/src/page-setup-surface.h @@ -302,7 +302,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/surface.cpp b/src/surface.cpp index cc37a94..f318c27 100644 --- a/src/surface.cpp +++ b/src/surface.cpp @@ -392,6 +392,7 @@ void Surface::InitBanks() InitBank_Flex(new X32FaderBank(OMCBankId::FLEX1, "Flex1", channel_strip_size)); InitBank_Flex(new X32FaderBank(OMCBankId::FLEX2, "Flex2", channel_strip_size)); InitBank_Flex(new X32FaderBank(OMCBankId::FLEX3, "Flex3", channel_strip_size)); + UpdateStereoBanks(); } else if (config->IsModelWingCompact()) { @@ -595,6 +596,70 @@ void Surface::ResetBank(OMCBankId id) banks[(uint)id]->Reset(); } +vector Surface::GetActiveChannels(X32_VCHANNEL_BLOCK blockType) +{ + uint start = 0; + uint size = 0; + if (blockType == X32_VCHANNEL_BLOCK::NORMAL) { 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; } + + vector active; + for (uint index = start; index < start + size; index++) + { + if (!config->IsRightChannelOfLinkedPair(index)) + { + active.push_back(index); + } + } + return active; +} + +void Surface::PopulateBankWithActive(X32FaderBank* bank, X32_VCHANNEL_BLOCK blockType, uint activeOffset) +{ + vector active = GetActiveChannels(blockType); + bank->Reset(); + for (uint strip = 0; strip < 8 && activeOffset + strip < active.size(); strip++) + { + SetChannelstripBinding(bank, strip, active[activeOffset + strip]); + } +} + +void Surface::UpdateStereoBanks() +{ + if (!config->IsModelX32FullOrCompactOrProducerOrM32OrM32R()) + { + return; + } + + PopulateBankWithActive(banks[(uint)OMCBankId::CH1_8], X32_VCHANNEL_BLOCK::NORMAL, 0); + PopulateBankWithActive(banks[(uint)OMCBankId::CH9_16], X32_VCHANNEL_BLOCK::NORMAL, 8); + PopulateBankWithActive(banks[(uint)OMCBankId::CH17_24], X32_VCHANNEL_BLOCK::NORMAL, 16); + PopulateBankWithActive(banks[(uint)OMCBankId::CH25_32], X32_VCHANNEL_BLOCK::NORMAL, 24); + PopulateBankWithActive(banks[(uint)OMCBankId::AUX_USB], X32_VCHANNEL_BLOCK::AUX, 0); + PopulateBankWithActive(banks[(uint)OMCBankId::BUS1_8], X32_VCHANNEL_BLOCK::BUS, 0); + PopulateBankWithActive(banks[(uint)OMCBankId::BUS9_16], X32_VCHANNEL_BLOCK::BUS, 8); + + X32FaderBank* matrixBank = banks[(uint)OMCBankId::MATRIX_MAIN]; + vector matrices = GetActiveChannels(X32_VCHANNEL_BLOCK::MATRIX); + matrixBank->Reset(); + uint strip = 0; + for (; strip < matrices.size() && strip < 8; strip++) + { + SetChannelstripBinding(matrixBank, strip, matrices[strip]); + } + if (strip < 8) { SetChannelstripBinding(matrixBank, strip++, 70); } + if (strip < 8) { SetChannelstripBinding(matrixBank, strip, 71); } + + const auto loadedBanks = bankloaded; + for (const auto& [target, bankId] : loadedBanks) + { + LoadBank(target, bankId); + } +} + // bit 0=CCW, bit 6=center, bit 12 = CW, bit 15=encoder-backlight // CCW <- XXXXXX X XXXXXX -> CW uint16_t Surface::CalcEncoderRingLedIncrement(uint8_t pct) { @@ -1115,4 +1180,4 @@ void Surface::SetLed(SurfaceElementId buttonOrLed, bool state, bool blink) } } -} \ No newline at end of file +} diff --git a/src/surface.h b/src/surface.h index 8e761ea..bd3e9b0 100644 --- a/src/surface.h +++ b/src/surface.h @@ -53,6 +53,8 @@ class Surface : public X32Base void InitBank_Channelstrip_DCA(X32FaderBank* bank, uint offset); void InitBank_Flex(X32FaderBank* bank); void InitBank_DMX(X32FaderBank* bank, uint offset); + vector GetActiveChannels(X32_VCHANNEL_BLOCK blockType); + void PopulateBankWithActive(X32FaderBank* bank, X32_VCHANNEL_BLOCK blockType, uint activeOffset); public: @@ -75,6 +77,7 @@ class Surface : public X32Base void LoadDefaultSurfaceBinding(); void LoadMainFaderSurfaceBinding(); void ResetBank(OMCBankId id); + void UpdateStereoBanks(); void LoadX32CoreDefinitions(); @@ -105,4 +108,4 @@ class Surface : public X32Base void Blink(); }; -} \ No newline at end of file +}