From 8f563e11bdeef081d6b78014170ccfaee3b65c55 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 17 Sep 2024 17:40:00 -0400 Subject: [PATCH] CLAP/VST3 Note Expressions (#1341) Support note expressions. For the basic ones (volume pan tuning) hard code them into the engine but also make all of them available in the mod matrix. Closes #479 --- .../clap-first/scxt-plugin/scxt-plugin.cpp | 15 ++++++++++++++ clients/clap-first/scxt-plugin/scxt-plugin.h | 14 ++++++++++++- src/engine/engine.h | 2 +- src/engine/engine_voice_responder.cpp | 15 ++++++++++++++ src/modulation/voice_matrix.cpp | 12 +++++++++++ src/modulation/voice_matrix.h | 20 ++++++++++++++++++- src/voice/voice.cpp | 17 +++++++++++++--- src/voice/voice.h | 14 +++++++++++++ 8 files changed, 103 insertions(+), 6 deletions(-) diff --git a/clients/clap-first/scxt-plugin/scxt-plugin.cpp b/clients/clap-first/scxt-plugin/scxt-plugin.cpp index 90166c9b..f2875cd0 100644 --- a/clients/clap-first/scxt-plugin/scxt-plugin.cpp +++ b/clients/clap-first/scxt-plugin/scxt-plugin.cpp @@ -439,6 +439,21 @@ bool SCXTPlugin::handleEvent(const clap_event_header_t *nextEvent) handleParamValueEvent(pevt); } break; + + case CLAP_EVENT_NOTE_EXPRESSION: + { + auto nevt = reinterpret_cast(nextEvent); + engine->voiceManager.routeNoteExpression(nevt->port_index, nevt->channel, nevt->key, + nevt->note_id, nevt->expression_id, + nevt->value); + } + break; + default: + { + std::cout << __FILE__ << ":" << __LINE__ << " Unhandled event " << nextEvent->type + << std::endl; + } + break; } } return true; diff --git a/clients/clap-first/scxt-plugin/scxt-plugin.h b/clients/clap-first/scxt-plugin/scxt-plugin.h index 69788d6b..459038f7 100644 --- a/clients/clap-first/scxt-plugin/scxt-plugin.h +++ b/clients/clap-first/scxt-plugin/scxt-plugin.h @@ -44,11 +44,23 @@ #include "sst/clap_juce_shim/clap_juce_shim.h" -#include +#include "engine/engine.h" +#include "voice/voice.h" #include "clap-config.h" #include "utils.h" +static_assert((int)scxt::voice::Voice::ExpressionIDs::VOLUME == (int)CLAP_NOTE_EXPRESSION_VOLUME); +static_assert((int)scxt::voice::Voice::ExpressionIDs::PAN == (int)CLAP_NOTE_EXPRESSION_PAN); +static_assert((int)scxt::voice::Voice::ExpressionIDs::TUNING == (int)CLAP_NOTE_EXPRESSION_TUNING); +static_assert((int)scxt::voice::Voice::ExpressionIDs::VIBRATO == (int)CLAP_NOTE_EXPRESSION_VIBRATO); +static_assert((int)scxt::voice::Voice::ExpressionIDs::EXPRESSION == + (int)CLAP_NOTE_EXPRESSION_EXPRESSION); +static_assert((int)scxt::voice::Voice::ExpressionIDs::BRIGHTNESS == + (int)CLAP_NOTE_EXPRESSION_BRIGHTNESS); +static_assert((int)scxt::voice::Voice::ExpressionIDs::PRESSURE == + (int)CLAP_NOTE_EXPRESSION_PRESSURE); + namespace scxt::clap_first::scxt_plugin { diff --git a/src/engine/engine.h b/src/engine/engine.h index 17ad98ce..7a489143 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -213,7 +213,7 @@ struct Engine : MoveableOnly, SampleRateSupport { } - void setNoteExpression(voice::Voice *v, int32_t expression, double value) {} + 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(); } diff --git a/src/engine/engine_voice_responder.cpp b/src/engine/engine_voice_responder.cpp index 97f583c6..f3dfab74 100644 --- a/src/engine/engine_voice_responder.cpp +++ b/src/engine/engine_voice_responder.cpp @@ -226,4 +226,19 @@ void Engine::VoiceManagerResponder::setMIDI1CC(voice::Voice *v, int8_t controlle auto part = v->zone->parentGroup->parentPart; part->midiCCSmoothers[controller].setTarget(fv); } + +void Engine::VoiceManagerResponder::setNoteExpression(voice::Voice *v, int32_t expression, + double value) +{ + if (expression >= 0 && expression < voice::Voice::noteExpressionCount) + { + // bitwig shows timbre/brightness as bipolar. Correctly? + if (expression == (int)voice::Voice::ExpressionIDs::BRIGHTNESS) + { + value = value * 2 - 1; + } + v->noteExpressions[expression] = value; + } +} + } // namespace scxt::engine diff --git a/src/modulation/voice_matrix.cpp b/src/modulation/voice_matrix.cpp index 9d555ce9..c7efe95c 100644 --- a/src/modulation/voice_matrix.cpp +++ b/src/modulation/voice_matrix.cpp @@ -137,6 +137,18 @@ void MatrixEndpoints::Sources::bind(scxt::voice::modulation::Matrix &m, engine:: m.bindSourceValue(midiSources.velocitySource, v.velocity); m.bindSourceValue(midiSources.keytrackSource, v.keytrackPerOct); + m.bindSourceValue(noteExpressions.volume, v.noteExpressions[(int)Voice::ExpressionIDs::VOLUME]); + m.bindSourceValue(noteExpressions.pan, v.noteExpressions[(int)Voice::ExpressionIDs::PAN]); + m.bindSourceValue(noteExpressions.tuning, v.noteExpressions[(int)Voice::ExpressionIDs::TUNING]); + m.bindSourceValue(noteExpressions.vibrato, + v.noteExpressions[(int)Voice::ExpressionIDs::VIBRATO]); + m.bindSourceValue(noteExpressions.expression, + v.noteExpressions[(int)Voice::ExpressionIDs::EXPRESSION]); + m.bindSourceValue(noteExpressions.brightness, + v.noteExpressions[(int)Voice::ExpressionIDs::BRIGHTNESS]); + m.bindSourceValue(noteExpressions.pressure, + v.noteExpressions[(int)Voice::ExpressionIDs::PRESSURE]); + for (int i = 0; i < scxt::numTransportPhasors; ++i) { m.bindSourceValue(transportSources.phasors[i], z.getEngine()->transportPhasors[i]); diff --git a/src/modulation/voice_matrix.h b/src/modulation/voice_matrix.h index 069d95aa..7ae7f4e3 100644 --- a/src/modulation/voice_matrix.h +++ b/src/modulation/voice_matrix.h @@ -223,7 +223,7 @@ struct MatrixEndpoints struct Sources { Sources(engine::Engine *e) - : lfoSources(e), midiSources(e), aegSource{'zneg', 'aeg ', 0}, + : lfoSources(e), midiSources(e), noteExpressions(e), aegSource{'zneg', 'aeg ', 0}, eg2Source{'zneg', 'eg2 ', 0}, transportSources(e), rngSources(e), macroSources(e) { registerVoiceModSource(e, aegSource, "", "AEG"); @@ -245,6 +245,24 @@ struct MatrixEndpoints SR modWheelSource, velocitySource, keytrackSource; } midiSources; + struct NoteExpressionSources + { + NoteExpressionSources(engine::Engine *e) + : volume{'znte', 'volu'}, pan{'znte', 'pan '}, tuning{'znte', 'tuni'}, + vibrato{'znte', 'vibr'}, expression{'znte', 'expr'}, brightness{'znte', 'brit'}, + pressure{'znte', 'pres'} + { + registerVoiceModSource(e, volume, "Note Expressions", "Volume"); + registerVoiceModSource(e, pan, "Note Expressions", "Pan"); + registerVoiceModSource(e, tuning, "Note Expressions", "Tuning"); + registerVoiceModSource(e, vibrato, "Note Expressions", "Vibrato"); + registerVoiceModSource(e, expression, "Note Expressions", "Expression"); + registerVoiceModSource(e, brightness, "Note Expressions", "Brightness"); + registerVoiceModSource(e, pressure, "Note Expressions", "Pressure"); + } + SR volume, pan, tuning, vibrato, expression, brightness, pressure; + } noteExpressions; + TransportSourceBase transportSources; struct RNGSources diff --git a/src/voice/voice.cpp b/src/voice/voice.cpp index 04f9fa6c..45d1ef2c 100644 --- a/src/voice/voice.cpp +++ b/src/voice/voice.cpp @@ -25,6 +25,7 @@ * https://github.com/surge-synthesizer/shortcircuit-xt */ +#include "voice.h" #include "voice.h" #include "tuning/equal.h" #include @@ -82,6 +83,13 @@ void Voice::cleanupVoice() void Voice::voiceStarted() { + noteExpressions[(int)ExpressionIDs::VOLUME] = 1.0; + noteExpressions[(int)ExpressionIDs::PAN] = 0.5; + for (int i = (int)ExpressionIDs::TUNING; i <= (int)ExpressionIDs::PRESSURE; ++i) + { + noteExpressions[i] = 0.0; + } + forceOversample = zone->parentGroup->outputInfo.oversample; lfosActive = zone->lfosActive; @@ -467,7 +475,9 @@ template bool Voice::processWithOS() /* * Implement output pan */ - auto pvo = *endpoints->outputTarget.panP; + auto pvo = std::clamp(*endpoints->outputTarget.panP + + 2.0 * (noteExpressions[(int)ExpressionIDs::PAN] - 0.5), + -1., 1.); if (pvo != 0.f) { outputPan.set_target(pvo); @@ -486,7 +496,7 @@ template bool Voice::processWithOS() if constexpr (OS) { - outputAmpOS.set_target(pao * pao * pao); + outputAmpOS.set_target(pao * pao * pao * noteExpressions[(int)ExpressionIDs::VOLUME]); if (chainIsMono) { outputAmpOS.multiply_block(output[0]); @@ -498,7 +508,7 @@ template bool Voice::processWithOS() } else { - outputAmp.set_target(pao * pao * pao); + outputAmp.set_target(pao * pao * pao * noteExpressions[(int)ExpressionIDs::VOLUME]); if (chainIsMono) { outputAmp.multiply_block(output[0]); @@ -665,6 +675,7 @@ float Voice::calculateVoicePitch() zone->getEngine()->midikeyRetuner.retuneRemappedKey(channel, key, originalMidiKey); fpitch += retuner; + fpitch += noteExpressions[(int)ExpressionIDs::TUNING]; keytrackPerOct = (key + retuner - zone->mapping.rootKey) / 12.0; diff --git a/src/voice/voice.h b/src/voice/voice.h index ea182b53..7e051ff0 100644 --- a/src/voice/voice.h +++ b/src/voice/voice.h @@ -72,6 +72,20 @@ struct alignas(16) Voice : MoveableOnly, float velocity{1.f}; float velKeyFade{1.f}; float keytrackPerOct{0.f}; // resolvee key - pitch cnter / 12 + static constexpr size_t noteExpressionCount{7}; + float noteExpressions[noteExpressionCount]{}; + // These are the same as teh CLAP expression IDs but I dont want to include + // clap.h here + enum struct ExpressionIDs + { + VOLUME = 0, + PAN = 1, + TUNING = 2, + VIBRATO = 3, + EXPRESSION = 4, + BRIGHTNESS = 5, + PRESSURE = 6 + }; scxt::voice::modulation::Matrix modMatrix; std::unique_ptr endpoints;