From 1994b54b7c72d8070630359b2f7a72b6ea034c62 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 2 Oct 2024 12:19:29 -0400 Subject: [PATCH] Handle monophonic midi messages (CC, Channel Bend) better (#1402) Monophonic messages used to come through the voice manager to each voice which would find the right spot. Refactor the voice manager so this is more reasonably sent less frequently etc... and have a new monophonic midi signal handler. --- .../scxt-juce-standalone.cpp | 3 +- .../clap-first/scxt-plugin/scxt-plugin.cpp | 13 ++-- libs/sst/sst-voicemanager | 2 +- src/CMakeLists.txt | 1 + src/engine/engine.cpp | 41 ++++++++++++ src/engine/engine.h | 28 +++++--- src/engine/engine_voice_responder.cpp | 15 ----- src/engine/group_triggers.cpp | 28 ++++++++ src/engine/group_triggers.h | 65 +++++++++++++++++++ src/engine/part.h | 6 ++ 10 files changed, 168 insertions(+), 34 deletions(-) create mode 100644 src/engine/group_triggers.cpp create mode 100644 src/engine/group_triggers.h diff --git a/clients/clap-first/scxt-juce-standalone/scxt-juce-standalone.cpp b/clients/clap-first/scxt-juce-standalone/scxt-juce-standalone.cpp index f797164c..f04e3d3c 100644 --- a/clients/clap-first/scxt-juce-standalone/scxt-juce-standalone.cpp +++ b/clients/clap-first/scxt-juce-standalone/scxt-juce-standalone.cpp @@ -255,8 +255,7 @@ struct SCXTApplicationWindow : juce::DocumentWindow, juce::Button::Listener { auto msg = (scope.blockSize1 == 1) ? midiBuffer[scope.startIndex1] : midiBuffer[scope.startIndex2]; - sst::voicemanager::applyMidi1Message(window.engine->voiceManager, 0, - msg.getRawData()); + window.engine->processMIDI1Event(0, msg.getRawData()); } } // Drain thread safe midi queue diff --git a/clients/clap-first/scxt-plugin/scxt-plugin.cpp b/clients/clap-first/scxt-plugin/scxt-plugin.cpp index c2944840..02ca0525 100644 --- a/clients/clap-first/scxt-plugin/scxt-plugin.cpp +++ b/clients/clap-first/scxt-plugin/scxt-plugin.cpp @@ -32,8 +32,6 @@ #include "version.h" #include "app/SCXTEditor.h" -#include "sst/voicemanager/midi1_to_voicemanager.h" - namespace scxt::clap_first::scxt_plugin { const clap_plugin_descriptor *getDescription() @@ -418,24 +416,23 @@ bool SCXTPlugin::handleEvent(const clap_event_header_t *nextEvent) case CLAP_EVENT_MIDI: { auto mevt = reinterpret_cast(nextEvent); - sst::voicemanager::applyMidi1Message(engine->voiceManager, mevt->port_index, - mevt->data); + engine->processMIDI1Event(mevt->port_index, mevt->data); } break; case CLAP_EVENT_NOTE_ON: { auto nevt = reinterpret_cast(nextEvent); - engine->voiceManager.processNoteOnEvent(nevt->port_index, nevt->channel, nevt->key, - nevt->note_id, nevt->velocity, 0.f); + engine->processNoteOnEvent(nevt->port_index, nevt->channel, nevt->key, nevt->note_id, + nevt->velocity, 0.f); } break; case CLAP_EVENT_NOTE_OFF: { auto nevt = reinterpret_cast(nextEvent); - engine->voiceManager.processNoteOffEvent(nevt->port_index, nevt->channel, nevt->key, - nevt->note_id, nevt->velocity); + engine->processNoteOffEvent(nevt->port_index, nevt->channel, nevt->key, nevt->note_id, + nevt->velocity); } case CLAP_EVENT_PARAM_VALUE: diff --git a/libs/sst/sst-voicemanager b/libs/sst/sst-voicemanager index 952a4b89..b10309de 160000 --- a/libs/sst/sst-voicemanager +++ b/libs/sst/sst-voicemanager @@ -1 +1 @@ -Subproject commit 952a4b893ed7d685dfb7c55bdc21ba33d484631f +Subproject commit b10309de18814a6eb6f9f5b56bf3bbbaf66019a7 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4706e252..857df8cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(${PROJECT_NAME} STATIC engine/missing_resolution.cpp engine/bus.cpp engine/macros.cpp + engine/group_triggers.cpp json/stream.cpp diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 99453fdb..f8a143a5 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -56,6 +56,7 @@ #include "messaging/client/client_serial.h" #include "feature_enums.h" #include "missing_resolution.h" +#include "sst/voicemanager/midi1_to_voicemanager.h" namespace scxt::engine { @@ -1135,4 +1136,44 @@ void Engine::setMacro01ValueFromPlugin(int part, int index, float value01) getMessageController()->sendAudioToSerialization(a2s); } +void Engine::processMIDI1Event(uint16_t idx, const uint8_t data[3]) +{ + sst::voicemanager::applyMidi1Message(voiceManager, idx, data); +} + +void Engine::processNoteOnEvent(int16_t port, int16_t channel, int16_t key, int32_t note_id, + double velocity, float retune) +{ + voiceManager.processNoteOnEvent(port, channel, key, note_id, velocity, retune); +} +void Engine::processNoteOffEvent(int16_t port, int16_t channel, int16_t key, int32_t note_id, + double velocity) +{ + voiceManager.processNoteOffEvent(port, channel, key, note_id, velocity); +} + +void Engine::MonoVoiceManagerResponder::setMIDIPitchBend(int16_t channel, int16_t pb14bit) +{ + auto fv = (pb14bit - 8192) / 8192.f; + for (const auto &p : engine.getPatch()->getParts()) + { + if (p->configuration.active && p->respondsToMIDIChannel(channel)) + { + p->pitchBendValue = fv; + } + } +} +void Engine::MonoVoiceManagerResponder::setMIDI1CC(int16_t channel, int16_t cc, int16_t val) +{ + auto fv = val / 127.0; + + for (const auto &p : engine.getPatch()->getParts()) + { + if (p->configuration.active && p->respondsToMIDIChannel(channel)) + { + p->midiCCValues[cc] = fv; + } + } +} +void Engine::MonoVoiceManagerResponder::setMIDIChannelPressure(int16_t channel, int16_t pres) {} } // namespace scxt::engine diff --git a/src/engine/engine.h b/src/engine/engine.h index bf159382..f199f080 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -118,6 +118,11 @@ struct Engine : MoveableOnly, SampleRateSupport * Midi-style events. Each event is assumed to be at the top of the * blockSize sample block */ + void processMIDI1Event(uint16_t midiPort, const uint8_t data[3]); + void processNoteOnEvent(int16_t port, int16_t channel, int16_t key, int32_t note_id, + double velocity, float retune); + void processNoteOffEvent(int16_t port, int16_t channel, int16_t key, int32_t note_id, + double velocity); // TODO: This is obviusly a pretty inefficient search method. We can definitel // pre-cache some of this lookup when the patch mutates @@ -138,8 +143,7 @@ struct Engine : MoveableOnly, SampleRateSupport for (const auto &[pidx, part] : sst::cpputils::enumerate(*patch)) { if (!part->configuration.mute && part->configuration.active && - (part->configuration.channel == channel || - part->configuration.channel == Part::PartConfiguration::omniChannel)) + part->respondsToMIDIChannel(channel)) { for (const auto &[gidx, group] : sst::cpputils::enumerate(*part)) { @@ -205,8 +209,6 @@ struct Engine : MoveableOnly, SampleRateSupport SCLOG("Retrigger Voice Unimplemented") } - void setVoiceMIDIPitchBend(voice::Voice *v, uint16_t pb14bit); - void setVoiceMIDIMPEChannelPitchBend(voice::Voice *v, uint16_t pb14bit) {} void setVoicePolyphonicParameterModulation(voice::Voice *v, uint32_t parameter, @@ -216,17 +218,27 @@ struct Engine : MoveableOnly, SampleRateSupport void setNoteExpression(voice::Voice *v, int32_t expression, double value); void setPolyphonicAftertouch(voice::Voice *v, int8_t pat); - void setChannelPressure(voice::Voice *v, int8_t pres) {} void allSoundsOff() { engine.releaseAllVoices(); } void allNotesOff() { engine.stopAllSounds(); } - void setMIDI1CC(voice::Voice *v, int8_t cc, int8_t val); private: bool transactionValid{false}; int32_t transactionVoiceCount{0}; } voiceManagerResponder{*this}; - using voiceManager_t = sst::voicemanager::VoiceManager; - voiceManager_t voiceManager{voiceManagerResponder}; + + struct MonoVoiceManagerResponder + { + Engine &engine; + + MonoVoiceManagerResponder(Engine &e) : engine(e) {} + + void setMIDIPitchBend(int16_t channel, int16_t pb14bit); + void setMIDI1CC(int16_t channel, int16_t cc, int16_t val); + void setMIDIChannelPressure(int16_t channel, int16_t pres); + } monoVoiceManagerResponder{*this}; + using voiceManager_t = + sst::voicemanager::VoiceManager; + voiceManager_t voiceManager{voiceManagerResponder, monoVoiceManagerResponder}; void onSampleRateChanged() override; diff --git a/src/engine/engine_voice_responder.cpp b/src/engine/engine_voice_responder.cpp index 32c91a6b..dedf363d 100644 --- a/src/engine/engine_voice_responder.cpp +++ b/src/engine/engine_voice_responder.cpp @@ -212,21 +212,6 @@ void Engine::VoiceManagerResponder::endVoiceCreationTransaction(uint16_t port, u void Engine::VoiceManagerResponder::releaseVoice(voice::Voice *v, float velocity) { v->release(); } -void Engine::VoiceManagerResponder::setVoiceMIDIPitchBend(voice::Voice *v, uint16_t pb14bit) -{ - auto fv = (pb14bit - 8192) / 8192.f; - auto part = v->zone->parentGroup->parentPart; - part->pitchBendValue = fv; -} - -void Engine::VoiceManagerResponder::setMIDI1CC(voice::Voice *v, int8_t controller, int8_t val) -{ - assert(controller >= 0); - auto fv = val / 127.0; - auto part = v->zone->parentGroup->parentPart; - part->midiCCValues[controller] = fv; -} - void Engine::VoiceManagerResponder::setNoteExpression(voice::Voice *v, int32_t expression, double value) { diff --git a/src/engine/group_triggers.cpp b/src/engine/group_triggers.cpp new file mode 100644 index 00000000..26217df9 --- /dev/null +++ b/src/engine/group_triggers.cpp @@ -0,0 +1,28 @@ +/* + * Shortcircuit XT - a Surge Synth Team product + * + * A fully featured creative sampler, available as a standalone + * and plugin for multiple platforms. + * + * Copyright 2019 - 2024, Various authors, as described in the github + * transaction log. + * + * ShortcircuitXT is released under the Gnu General Public Licence + * V3 or later (GPL-3.0-or-later). The license is found in the file + * "LICENSE" in the root of this repository or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Individual sections of code which comprises ShortcircuitXT in this + * repository may also be used under an MIT license. Please see the + * section "Licensing" in "README.md" for details. + * + * ShortcircuitXT is inspired by, and shares code with, the + * commercial product Shortcircuit 1 and 2, released by VemberTech + * in the mid 2000s. The code for Shortcircuit 2 was opensourced in + * 2020 at the outset of this project. + * + * All source for ShortcircuitXT is available at + * https://github.com/surge-synthesizer/shortcircuit-xt + */ + +#include "group_triggers.h" diff --git a/src/engine/group_triggers.h b/src/engine/group_triggers.h new file mode 100644 index 00000000..c6b3bd73 --- /dev/null +++ b/src/engine/group_triggers.h @@ -0,0 +1,65 @@ +/* + * Shortcircuit XT - a Surge Synth Team product + * + * A fully featured creative sampler, available as a standalone + * and plugin for multiple platforms. + * + * Copyright 2019 - 2024, Various authors, as described in the github + * transaction log. + * + * ShortcircuitXT is released under the Gnu General Public Licence + * V3 or later (GPL-3.0-or-later). The license is found in the file + * "LICENSE" in the root of this repository or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Individual sections of code which comprises ShortcircuitXT in this + * repository may also be used under an MIT license. Please see the + * section "Licensing" in "README.md" for details. + * + * ShortcircuitXT is inspired by, and shares code with, the + * commercial product Shortcircuit 1 and 2, released by VemberTech + * in the mid 2000s. The code for Shortcircuit 2 was opensourced in + * 2020 at the outset of this project. + * + * All source for ShortcircuitXT is available at + * https://github.com/surge-synthesizer/shortcircuit-xt + */ + +#ifndef SCXT_SRC_ENGINE_GROUP_TRIGGERS_H +#define SCXT_SRC_ENGINE_GROUP_TRIGGERS_H + +#include + +#include "datamodel/metadata.h" + +namespace scxt::engine +{ + +struct Group; + +/** + * Maintain the state of keys ccs etc... for a particular instrument + */ +struct GroupTriggerInstrumentState +{ +}; + +/** + * Calculate if a group is triggered + */ +struct GroupTrigger +{ + GroupTriggerInstrumentState &state; + GroupTrigger(GroupTriggerInstrumentState &onState) : state(onState) {} + virtual ~GroupTrigger() = default; + virtual bool value(const std::unique_ptr &) const = 0; + + datamodel::pmd argMetadata(int argNo) const; + std::string displayName; +}; + +std::unique_ptr makeMacroGroupTrigger(GroupTriggerInstrumentState &); +std::unique_ptr makeMIDI1CCGroupTrigger(GroupTriggerInstrumentState &); + +} // namespace scxt::engine +#endif // GROUP_TRIGGERS_H diff --git a/src/engine/part.h b/src/engine/part.h index ab371db2..40abee7e 100644 --- a/src/engine/part.h +++ b/src/engine/part.h @@ -68,6 +68,12 @@ struct Part : MoveableOnly, SampleRateSupport int16_t partNumber; Patch *parentPatch{nullptr}; + bool respondsToMIDIChannel(int16_t channel) const + { + return channel < 0 || configuration.channel == PartConfiguration::omniChannel || + channel == configuration.channel; + } + struct PartConfiguration { static constexpr int16_t omniChannel{-1};