Skip to content

Commit

Permalink
Handle monophonic midi messages (CC, Channel Bend) better (#1402)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
baconpaul authored Oct 2, 2024
1 parent d66b13f commit 1994b54
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 5 additions & 8 deletions clients/clap-first/scxt-plugin/scxt-plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -418,24 +416,23 @@ bool SCXTPlugin::handleEvent(const clap_event_header_t *nextEvent)
case CLAP_EVENT_MIDI:
{
auto mevt = reinterpret_cast<const clap_event_midi *>(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<const clap_event_note *>(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<const clap_event_note *>(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:
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
41 changes: 41 additions & 0 deletions src/engine/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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
28 changes: 20 additions & 8 deletions src/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ struct Engine : MoveableOnly<Engine>, 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
Expand All @@ -138,8 +143,7 @@ struct Engine : MoveableOnly<Engine>, 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))
{
Expand Down Expand Up @@ -205,8 +209,6 @@ struct Engine : MoveableOnly<Engine>, 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,
Expand All @@ -216,17 +218,27 @@ struct Engine : MoveableOnly<Engine>, 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<VMConfig, VoiceManagerResponder>;
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<VMConfig, VoiceManagerResponder, MonoVoiceManagerResponder>;
voiceManager_t voiceManager{voiceManagerResponder, monoVoiceManagerResponder};

void onSampleRateChanged() override;

Expand Down
15 changes: 0 additions & 15 deletions src/engine/engine_voice_responder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
28 changes: 28 additions & 0 deletions src/engine/group_triggers.cpp
Original file line number Diff line number Diff line change
@@ -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"
65 changes: 65 additions & 0 deletions src/engine/group_triggers.h
Original file line number Diff line number Diff line change
@@ -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 <memory>

#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<Group> &) const = 0;

datamodel::pmd argMetadata(int argNo) const;
std::string displayName;
};

std::unique_ptr<GroupTrigger> makeMacroGroupTrigger(GroupTriggerInstrumentState &);
std::unique_ptr<GroupTrigger> makeMIDI1CCGroupTrigger(GroupTriggerInstrumentState &);

} // namespace scxt::engine
#endif // GROUP_TRIGGERS_H
6 changes: 6 additions & 0 deletions src/engine/part.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ struct Part : MoveableOnly<Part>, 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};
Expand Down

0 comments on commit 1994b54

Please sign in to comment.