From 20390369017fea5f5becd8771447bacc7e3ab1d3 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 7 Jan 2023 21:51:53 -0500 Subject: [PATCH] Act as ODDSound MTS Main ('Master') (#6771) This allows you to have a single surge instance act as an oddsound MTS-ESP Master (which we call Main) component. In this mode, the tunign editor features stay active but are broadcast to all other MTS-ESP clients on your system if the master is initializable. --- libs/oddsound-mts/CMakeLists.txt | 3 +- src/common/SurgePatch.cpp | 24 +++- src/common/SurgeStorage.cpp | 49 ++++++- src/common/SurgeStorage.h | 16 ++- src/common/SurgeSynthesizer.cpp | 6 +- src/common/dsp/SurgeVoice.cpp | 10 +- .../dsp/oscillators/TwistOscillator.cpp | 2 +- src/surge-xt/gui/SurgeGUIEditor.cpp | 128 ++++++++++-------- .../gui/SurgeGUIEditorValueCallbacks.cpp | 2 +- 9 files changed, 164 insertions(+), 76 deletions(-) diff --git a/libs/oddsound-mts/CMakeLists.txt b/libs/oddsound-mts/CMakeLists.txt index 15a51cd1b1b..b40fec08369 100644 --- a/libs/oddsound-mts/CMakeLists.txt +++ b/libs/oddsound-mts/CMakeLists.txt @@ -2,8 +2,9 @@ project(oddsound-mts VERSION 0.0.0 LANGUAGES CXX) add_library(${PROJECT_NAME} MTS-ESP/Client/libMTSClient.cpp + MTS-ESP/Master/libMTSMaster.cpp ) -target_include_directories(${PROJECT_NAME} PUBLIC MTS-ESP/Client) +target_include_directories(${PROJECT_NAME} PUBLIC MTS-ESP/Client MTS-ESP/Master) target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_DL_LIBS}) add_library(surge::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) diff --git a/src/common/SurgePatch.cpp b/src/common/SurgePatch.cpp index 2a31542421c..c6abc9b45ae 100644 --- a/src/common/SurgePatch.cpp +++ b/src/common/SurgePatch.cpp @@ -1800,6 +1800,23 @@ void SurgePatch::load_xml(const void *data, int datasize, bool is_preset) } } + auto mts_main = + TINYXML_SAFE_TO_ELEMENT(nonparamconfig->FirstChild("oddsound_mts_active_as_main")); + if (mts_main) + { + int tv; + + if (tam->QueryIntAttribute("v", &tv) == TIXML_SUCCESS) + { + if (tv) + { +#ifndef SURGE_SKIP_ODDSOUND_MTS + storage->connect_as_oddsound_main(); +#endif + } + } + } + auto *hcs = TINYXML_SAFE_TO_ELEMENT(nonparamconfig->FirstChild("hardclipmodes")); if (hcs) @@ -3046,7 +3063,7 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b // Revision 16 adds the TAM TiXmlElement tam("tuningApplicationMode"); - if (storage->oddsound_mts_active) + if (storage->oddsound_mts_active_as_client) { tam.SetAttribute("v", (int)(storage->patchStoredTuningApplicationMode)); } @@ -3056,6 +3073,11 @@ unsigned int SurgePatch::save_xml(void **data) // allocates mem, must be freed b } nonparamconfig.InsertEndChild(tam); + // Revision 21 adds MTS as main + TiXmlElement oam("oddsound_mts_active_as_main"); + oam.SetAttribute("v", (int)(storage->oddsound_mts_active_as_main)); + nonparamconfig.InsertEndChild(oam); + patch.InsertEndChild(nonparamconfig); TiXmlElement eod("extraoscdata"); diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index 3926e3c9abc..822f134eb11 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -39,6 +39,7 @@ #include "sst/plugininfra/strnatcmp.h" #ifndef SURGE_SKIP_ODDSOUND_MTS #include "libMTSClient.h" +#include "libMTSMaster.h" #endif #include "FxPresetAndClipboardManager.h" #include "ModulatorPresetManager.h" @@ -522,7 +523,7 @@ SurgeStorage::SurgeStorage(const SurgeStorage::SurgeStorageConfig &config) : oth else { oddsound_mts_client = nullptr; - oddsound_mts_active = false; + oddsound_mts_active_as_client = false; } #endif @@ -1995,6 +1996,9 @@ void SurgeStorage::load_midi_controllers() SurgeStorage::~SurgeStorage() { #ifndef SURGE_SKIP_ODDSOUND_MTS + if (oddsound_mts_active_as_main) + disconnect_as_oddsound_main(); + deinitialize_oddsound(); #endif } @@ -2261,6 +2265,7 @@ bool SurgeStorage::resetToCurrentScaleAndMapping() table_note_omega[1][i] = (float)cos(2 * M_PI * min(0.5, 440 * table_pitch[i] * dsamplerate_os_inv)); } + tuningUpdates++; return true; } @@ -2269,7 +2274,7 @@ void SurgeStorage::setTuningApplicationMode(const TuningApplicationMode m) tuningApplicationMode = m; patchStoredTuningApplicationMode = m; resetToCurrentScaleAndMapping(); - if (oddsound_mts_active) + if (oddsound_mts_active_as_client) { tuningApplicationMode = RETUNE_MIDI_ONLY; } @@ -2449,8 +2454,8 @@ void SurgeStorage::deinitialize_oddsound() void SurgeStorage::setOddsoundMTSActiveTo(bool b) { - bool poa = oddsound_mts_active; - oddsound_mts_active = b; + bool poa = oddsound_mts_active_as_client; + oddsound_mts_active_as_client = b; if (b && b != poa) { // Oddsound right now is MIDI_ONLY so force that to avoid lingering problems @@ -2461,6 +2466,42 @@ void SurgeStorage::setOddsoundMTSActiveTo(bool b) tuningApplicationMode = patchStoredTuningApplicationMode; } } + +void SurgeStorage::connect_as_oddsound_main() +{ + if (oddsound_mts_client) + { + deinitialize_oddsound(); + } + if (oddsound_mts_active_as_main) // should never happen + { + MTS_DeregisterMaster(); + } + oddsound_mts_active_as_main = true; + MTS_RegisterMaster(); + lastSentTuningUpdate = -1; +} +void SurgeStorage::disconnect_as_oddsound_main() +{ + oddsound_mts_active_as_main = false; + MTS_DeregisterMaster(); + initialize_oddsound(); +} +void SurgeStorage::send_tuning_update() +{ + if (lastSentTuningUpdate == tuningUpdates) + return; + + lastSentTuningUpdate = tuningUpdates; + if (!oddsound_mts_active_as_main) + return; + + for (int i = 0; i < 128; ++i) + { + MTS_SetNoteTuning(currentTuning.frequencyForMidiNote(i), i); + } + MTS_SetScaleName(currentTuning.scale.description.c_str()); +} #endif void SurgeStorage::toggleTuningToCache() diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index e1fab314911..47664c73f11 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -105,6 +105,7 @@ const int FIRoffsetI16 = FIRipolI16_N >> 1; // added Extend to Delay Feedback parameter (allows negative delay) // 19 -> 20 (XT 1.1 release) added voice envelope mode, but super late so don't break 19 // 20 -> 21 (XT 1.2 nightlies) added absolutable mode for Combulator Offset 1/2 (to match the behavior of Center parameter) +// oddsound_as_mts_main // clang-format on const int ff_revision = 21; @@ -1304,6 +1305,7 @@ class alignas(16) SurgeStorage bool isStandardTuningAndHasNoToggle(); void resetTuningToggle(); + std::atomic tuningUpdates{2}; // the 'last sent' starts at 0. just a different value bool retuneToScale(const Tunings::Scale &s) { currentScale = s; @@ -1393,10 +1395,16 @@ class alignas(16) SurgeStorage void initialize_oddsound(); void deinitialize_oddsound(); void setOddsoundMTSActiveTo(bool b); + + void connect_as_oddsound_main(); + void disconnect_as_oddsound_main(); + uint64_t lastSentTuningUpdate{0}; // since tuning udpate starts at 2 + void send_tuning_update(); #endif MTSClient *oddsound_mts_client = nullptr; - std::atomic oddsound_mts_active{false}; + std::atomic oddsound_mts_active_as_client{false}; uint32_t oddsound_mts_on_check = 0; + std::atomic oddsound_mts_active_as_main{false}; enum OddsoundRetuneMode { RETUNE_CONSTANT = 0, @@ -1409,9 +1417,9 @@ class alignas(16) SurgeStorage */ inline bool tuningTableIs12TET() { - if ((isStandardTuning) || // nothing changed - (oddsound_mts_client && oddsound_mts_active) || // MTS in command - tuningApplicationMode == RETUNE_MIDI_ONLY // tune the keyboard, not the tables + if ((isStandardTuning) || // nothing changed + (oddsound_mts_client && oddsound_mts_active_as_client) || // MTS in command + tuningApplicationMode == RETUNE_MIDI_ONLY // tune the keyboard, not the tables ) return true; return false; diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index 2b3f5a22212..4080cfa773d 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -378,7 +378,7 @@ void SurgeSynthesizer::playNote(char channel, char key, char velocity, char detu } #ifndef SURGE_SKIP_ODDSOUND_MTS - if (storage.oddsound_mts_client && storage.oddsound_mts_active) + if (storage.oddsound_mts_client && storage.oddsound_mts_active_as_client) { if (MTS_ShouldFilterNote(storage.oddsound_mts_client, key, channel)) { @@ -4104,10 +4104,10 @@ void SurgeSynthesizer::processControl() storage.oddsound_mts_on_check = (storage.oddsound_mts_on_check + 1) & (1024 - 1); if (storage.oddsound_mts_on_check == 0) { - bool prior = storage.oddsound_mts_active; + bool prior = storage.oddsound_mts_active_as_client; storage.setOddsoundMTSActiveTo(MTS_HasMaster(storage.oddsound_mts_client)); - if (prior != storage.oddsound_mts_active) + if (prior != storage.oddsound_mts_active_as_client) { refresh_editor = true; } diff --git a/src/common/dsp/SurgeVoice.cpp b/src/common/dsp/SurgeVoice.cpp index b7603923f00..143ca6ba0b5 100644 --- a/src/common/dsp/SurgeVoice.cpp +++ b/src/common/dsp/SurgeVoice.cpp @@ -51,7 +51,7 @@ float SurgeVoiceState::getPitch(SurgeStorage *storage) auto res = key + /* mainChannelState->pitchBendInSemitones + */ mpeBend + detune; #ifndef SURGE_SKIP_ODDSOUND_MTS - if (storage->oddsound_mts_client && storage->oddsound_mts_active) + if (storage->oddsound_mts_client && storage->oddsound_mts_active_as_client) { if (storage->oddsoundRetuneMode == SurgeStorage::RETUNE_CONSTANT || key != keyRetuningForKey) @@ -80,7 +80,7 @@ float SurgeVoice::channelKeyEquvialent(float key, int channel, bool isMpeEnabled SurgeStorage *storage, bool remapKeyForTuning) { float res = key; - if (storage->mapChannelToOctave && !storage->oddsound_mts_active && !isMpeEnabled) + if (storage->mapChannelToOctave && !storage->oddsound_mts_active_as_client && !isMpeEnabled) { if (remapKeyForTuning) { @@ -390,7 +390,7 @@ void SurgeVoice::legato(int key, int velocity, char detune) void SurgeVoice::retriggerPortaIfKeyChanged() { - if (!storage->oddsound_mts_active && + if (!storage->oddsound_mts_active_as_client && (storage->isStandardTuning || storage->tuningApplicationMode == SurgeStorage::RETUNE_ALL)) { if (floor(state.pkey + 0.5) != state.priorpkey) @@ -406,7 +406,7 @@ void SurgeVoice::retriggerPortaIfKeyChanged() }; #ifndef SURGE_SKIP_ODDSOUND_MTS - if (storage->oddsound_mts_client && storage->oddsound_mts_active) + if (storage->oddsound_mts_client && storage->oddsound_mts_active_as_client) { v4k = [this](int k) { return log2f(MTS_NoteToFrequency(storage->oddsound_mts_client, k, state.channel) / @@ -1485,7 +1485,7 @@ void SurgeVoice::resetPortamentoFrom(int key, int channel) { float lk = key; #ifndef SURGE_SKIP_ODDSOUND_MTS - if (storage->oddsound_mts_client && storage->oddsound_mts_active) + if (storage->oddsound_mts_client && storage->oddsound_mts_active_as_client) { lk += MTS_RetuningInSemitones(storage->oddsound_mts_client, lk, channel); state.portasrc_key = lk; diff --git a/src/common/dsp/oscillators/TwistOscillator.cpp b/src/common/dsp/oscillators/TwistOscillator.cpp index 25251b31fbe..fdbc8bfea3b 100644 --- a/src/common/dsp/oscillators/TwistOscillator.cpp +++ b/src/common/dsp/oscillators/TwistOscillator.cpp @@ -306,7 +306,7 @@ TwistOscillator::TwistOscillator(SurgeStorage *storage, OscillatorStorage *oscda float TwistOscillator::tuningAwarePitch(float pitch) { if (storage->tuningApplicationMode == SurgeStorage::RETUNE_ALL && - !(storage->oddsound_mts_client && storage->oddsound_mts_active) && + !(storage->oddsound_mts_client && storage->oddsound_mts_active_as_client) && !(storage->isStandardTuning)) { auto idx = (int)floor(pitch); diff --git a/src/surge-xt/gui/SurgeGUIEditor.cpp b/src/surge-xt/gui/SurgeGUIEditor.cpp index 2337bef4c24..6d48972a3bc 100644 --- a/src/surge-xt/gui/SurgeGUIEditor.cpp +++ b/src/surge-xt/gui/SurgeGUIEditor.cpp @@ -548,6 +548,10 @@ void SurgeGUIEditor::idle() synth->sustainpedalCC); } +#ifndef SURGE_SKIP_ODDSOUND_MTS + getStorage()->send_tuning_update(); +#endif + if (errorItemCount) { std::vector> cp; @@ -778,8 +782,8 @@ void SurgeGUIEditor::idle() if ((v < 0.5 && !synth->storage.isStandardTuning) || (v > 0.5 && synth->storage.isStandardTuning)) { - bool hasmts = - synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active; + bool hasmts = synth->storage.oddsound_mts_client && + synth->storage.oddsound_mts_active_as_client; statusTune->setValue(!synth->storage.isStandardTuning || hasmts); statusTune->asJuceComponent()->repaint(); @@ -1698,7 +1702,8 @@ void SurgeGUIEditor::openOrRecreateEditor() case Surge::Skin::Connector::NonParameterConnection::STATUS_TUNE: { statusTune = layoutComponentForSkin(skinCtrl, tag_status_tune); - auto hasmts = synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active; + auto hasmts = + synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active_as_client; statusTune->setValue(synth->storage.isStandardTuning ? hasmts : synth->storage.isToggledToCache); setAccessibilityInformationByTitleAndAction(statusTune->asJuceComponent(), "Tune", @@ -2166,7 +2171,7 @@ void SurgeGUIEditor::openOrRecreateEditor() } // if the Tuning Editor was open and ODDSound was activated (which causes a refresh), close it - if (synth->storage.oddsound_mts_active) + if (synth->storage.oddsound_mts_active_as_client) { closeOverlay(TUNING_EDITOR); } @@ -2715,7 +2720,8 @@ void SurgeGUIEditor::toggleTuning() if (statusTune) { - bool hasmts = synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active; + bool hasmts = + synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active_as_client; statusTune->setValue(this->synth->storage.isStandardTuning ? hasmts : 1); } @@ -3225,8 +3231,8 @@ juce::PopupMenu SurgeGUIEditor::makeTuningMenu(const juce::Point &where, bo bool isScaleEnabled = !synth->storage.isStandardScale; bool isMappingEnabled = !synth->storage.isStandardMapping; - bool isOddsoundOn = - this->synth->storage.oddsound_mts_active && this->synth->storage.oddsound_mts_client; + bool isOddsoundOnAsClient = this->synth->storage.oddsound_mts_active_as_client && + this->synth->storage.oddsound_mts_client; auto tuningSubMenu = juce::PopupMenu(); auto hu = helpURLForSpecial("tun-menu"); @@ -3235,13 +3241,13 @@ juce::PopupMenu SurgeGUIEditor::makeTuningMenu(const juce::Point &where, bo { auto lurl = fullyResolvedHelpURL(hu); - addHelpHeaderTo(isOddsoundOn ? "Tuning (MTS-ESP)" : "Tuning", lurl, tuningSubMenu); + addHelpHeaderTo(isOddsoundOnAsClient ? "Tuning (MTS-ESP)" : "Tuning", lurl, tuningSubMenu); tuningSubMenu.addSeparator(); } #ifndef SURGE_SKIP_ODDSOUND_MTS - if (isOddsoundOn) + if (isOddsoundOnAsClient) { std::string mtsScale = MTS_GetScaleName(synth->storage.oddsound_mts_client); @@ -3252,7 +3258,7 @@ juce::PopupMenu SurgeGUIEditor::makeTuningMenu(const juce::Point &where, bo } #endif - if (!isOddsoundOn) + if (!isOddsoundOnAsClient) { if (isScaleEnabled) { @@ -3505,61 +3511,71 @@ juce::PopupMenu SurgeGUIEditor::makeTuningMenu(const juce::Point &where, bo } #ifndef SURGE_SKIP_ODDSOUND_MTS - if (synth->juceWrapperType.compare("Standalone") != 0) - { - bool tsMode = Surge::Storage::getUserDefaultValue(&(this->synth->storage), - Surge::Storage::UseODDMTS, false); - std::string txt = "Use ODDSound" + Surge::GUI::toOSCase(" MTS-ESP"); - - tuningSubMenu.addItem(txt, true, tsMode, [this, tsMode]() { - Surge::Storage::updateUserDefaultValue(&(this->synth->storage), - Surge::Storage::UseODDMTS, !tsMode); - if (tsMode) - { - this->synth->storage.deinitialize_oddsound(); - } - else - { - this->synth->storage.initialize_oddsound(); - } - }); + bool tsMode = Surge::Storage::getUserDefaultValue(&(this->synth->storage), + Surge::Storage::UseODDMTS, false); + std::string txt = "Use ODDSound" + Surge::GUI::toOSCase(" MTS-ESP"); - if (tsMode && !this->synth->storage.oddsound_mts_client) + tuningSubMenu.addItem(txt, true, tsMode, [this, tsMode]() { + Surge::Storage::updateUserDefaultValue(&(this->synth->storage), Surge::Storage::UseODDMTS, + !tsMode); + if (tsMode) { - tuningSubMenu.addItem(Surge::GUI::toOSCase("Reconnect to MTS-ESP"), [this]() { - this->synth->storage.initialize_oddsound(); - this->synth->refresh_editor = true; - }); + this->synth->storage.deinitialize_oddsound(); + } + else + { + this->synth->storage.initialize_oddsound(); } + }); - if (this->synth->storage.oddsound_mts_active && this->synth->storage.oddsound_mts_client) + std::string mtxt = "Act as ODDSound" + Surge::GUI::toOSCase(" MTS-ESP Main"); + tuningSubMenu.addItem(mtxt, true, getStorage()->oddsound_mts_active_as_main, [this]() { + if (getStorage()->oddsound_mts_active_as_main) { - tuningSubMenu.addItem(Surge::GUI::toOSCase("Disconnect from MTS-ESP"), [this]() { - auto q = this->synth->storage.oddsound_mts_client; - this->synth->storage.oddsound_mts_active = false; - this->synth->storage.oddsound_mts_client = nullptr; - MTS_DeregisterClient(q); - }); + this->synth->storage.disconnect_as_oddsound_main(); + } + else + { + this->synth->storage.connect_as_oddsound_main(); + } + }); - tuningSubMenu.addSeparator(); + if (tsMode && !this->synth->storage.oddsound_mts_client && + !getStorage()->oddsound_mts_active_as_main) + { + tuningSubMenu.addItem(Surge::GUI::toOSCase("Reconnect to MTS-ESP"), [this]() { + this->synth->storage.initialize_oddsound(); + this->synth->refresh_editor = true; + }); + } - tuningSubMenu.addItem( - Surge::GUI::toOSCase("Query Tuning at Note On Only"), true, - (this->synth->storage.oddsoundRetuneMode == SurgeStorage::RETUNE_NOTE_ON_ONLY), - [this]() { - if (this->synth->storage.oddsoundRetuneMode == SurgeStorage::RETUNE_CONSTANT) - { - this->synth->storage.oddsoundRetuneMode = SurgeStorage::RETUNE_NOTE_ON_ONLY; - } - else - { - this->synth->storage.oddsoundRetuneMode = SurgeStorage::RETUNE_CONSTANT; - } - }); + if (this->synth->storage.oddsound_mts_active_as_client && + this->synth->storage.oddsound_mts_client) + { + tuningSubMenu.addItem(Surge::GUI::toOSCase("Disconnect from MTS-ESP"), [this]() { + auto q = this->synth->storage.oddsound_mts_client; + this->synth->storage.oddsound_mts_active_as_client = false; + this->synth->storage.oddsound_mts_client = nullptr; + MTS_DeregisterClient(q); + }); - return tuningSubMenu; - } + tuningSubMenu.addSeparator(); + + tuningSubMenu.addItem( + Surge::GUI::toOSCase("Query Tuning at Note On Only"), true, + (this->synth->storage.oddsoundRetuneMode == SurgeStorage::RETUNE_NOTE_ON_ONLY), + [this]() { + if (this->synth->storage.oddsoundRetuneMode == SurgeStorage::RETUNE_CONSTANT) + { + this->synth->storage.oddsoundRetuneMode = SurgeStorage::RETUNE_NOTE_ON_ONLY; + } + else + { + this->synth->storage.oddsoundRetuneMode = SurgeStorage::RETUNE_CONSTANT; + } + }); } + #endif return tuningSubMenu; diff --git a/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp b/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp index 45fbdc52ada..728c42b3ba7 100644 --- a/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp +++ b/src/surge-xt/gui/SurgeGUIEditorValueCallbacks.cpp @@ -2323,7 +2323,7 @@ int32_t SurgeGUIEditor::controlModifierClicked(Surge::GUI::IComponentTagValue *c case ct_freq_audible_very_low_minval: { auto hasmts = synth->storage.oddsound_mts_client && - synth->storage.oddsound_mts_active; + synth->storage.oddsound_mts_active_as_client; std::string tuningmode = hasmts ? "MTS" : "SCL/KBM";