diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d69673..245a0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # RNBO Runner Changes * *current* + * added optional device midi input filtering + * OSCQuery endpoint: `/rnbo/inst//config/midi_input_channel` + * set to "all" (default) for no filtering + * set to "1" to only accept messages on channel 1 + * any realtime or non channel based messages should always get thru + * fixed bug where `set_preset` config for instance wasn't persisting with graph * expanded ability to read from / write to various runner OSC endpoints with patcher exports via inport, outport, param * added the concept of `Param Views` * these are sorted collections of parameters that can span multiple devices diff --git a/src/Controller.cpp b/src/Controller.cpp index df2e3c5..72869e8 100644 --- a/src/Controller.cpp +++ b/src/Controller.cpp @@ -2110,22 +2110,15 @@ void Controller::doLoadSet(SetInfo& setInfo, boost::optional& if (entry.config.size()) { auto instConfig = RNBO::Json::parse(entry.config); - //override datarefs - if (instConfig.contains("datarefs")) - config["datarefs"] = instConfig["datarefs"]; //load the last preset as the initial one if (instConfig["preset_last"].is_string()) { config["preset_initial"] = instConfig["preset_last"]; } - //override meta - if (instConfig["metaoverride"].is_object()) { - config["metaoverride"] = instConfig["metaoverride"]; - } - if (instConfig.contains("setpreset")) { - config["setpreset"] = instConfig["setpreset"]; - } - if (instConfig["namealias"].is_string()) { - config["namealias"] = instConfig["namealias"]; + //overrides + for (const auto& key: { "namealias", "setpreset", "insetpreset", "midi_input_channel", "metaoverride", "datarefs" }) { + if (instConfig.contains(key)) { + config[key] = instConfig[key]; + } } } diff --git a/src/Instance.cpp b/src/Instance.cpp index aab57fe..1d89187 100644 --- a/src/Instance.cpp +++ b/src/Instance.cpp @@ -149,6 +149,7 @@ Instance::Instance( mInSetPreset = conf["insetpreset"]; } + //setup initial preset channel mapping try { std::string chanName = "omni"; @@ -215,6 +216,10 @@ Instance::Instance( mCore->setExternalDataHandler(mDataHandler.get()); } + if (conf.contains("midi_input_channel") && conf["midi_input_channel"].is_number()) { + mAudio->setMidiInputChannel(conf["midi_input_channel"].get()); + } + mPresetSaveQueue = RNBO::make_unique, 32>>(32); mDB->presets(mName, [this](const std::string& name, bool initial, int /*presetindex*/) { @@ -252,6 +257,7 @@ Instance::Instance( if (v.get_type() == ossia::val_type::BOOL) { mSetPresetPatcherNamed = v.get(); } + queueConfigChangeSignal(); }); } @@ -264,6 +270,40 @@ Instance::Instance( if (v.get_type() == ossia::val_type::BOOL) { mInSetPreset = v.get(); } + queueConfigChangeSignal(); + }); + } + + { + auto n = config->create_child("midi_input_channel"); + auto p = n->create_parameter(ossia::val_type::STRING); + + n->set(ossia::net::description_attribute{}, "Optionally select a MIDI channel that this device should accept for input, omni for all channels"); + + std::vector values = { + "all", + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16" + }; + + auto dom = ossia::init_domain(ossia::val_type::STRING); + ossia::set_values(dom, values); + n->set(ossia::net::domain_attribute{}, dom); + n->set(ossia::net::bounding_mode_attribute{}, ossia::bounding_mode::CLIP); + + p->push_value(values[mAudio->midiInputChannel() + 1]); + p->add_callback([this, values](const ossia::value& v) { + if (v.get_type() == ossia::val_type::STRING) { + auto s = v.get(); + if (s == "all") { + mAudio->setMidiInputChannel(-1); + } else { + int i = std::stoi(s); + if (i > 0 && i <= 16) { + mAudio->setMidiInputChannel(i - 1); + } + } + } + queueConfigChangeSignal(); }); } @@ -1428,6 +1468,7 @@ RNBO::Json Instance::currentConfig() { config["datarefs"] = datarefs; config["setpreset"] = mSetPresetPatcherNamed ? "patchernamed" : "values"; config["insetpreset"] = mInSetPreset; + config["midi_input_channel"] = (int)mAudio->midiInputChannel(); //mAudio->addConfig(config); diff --git a/src/InstanceAudio.h b/src/InstanceAudio.h index 1c79e5b..12e4c76 100644 --- a/src/InstanceAudio.h +++ b/src/InstanceAudio.h @@ -35,6 +35,15 @@ class InstanceAudio { virtual void registerConfigChangeCallback(std::function cb) { }; virtual size_t bufferSize() = 0; + + int8_t midiInputChannel() { + return mMIDIInputChannel; + } + + void setMidiInputChannel(int c) { + mMIDIInputChannel = static_cast(std::clamp(c, -1, 15)); + } protected: std::atomic mAudioState = AudioState::Idle; + int8_t mMIDIInputChannel = -1; //0 based, negative means omni }; diff --git a/src/JackAudio.cpp b/src/JackAudio.cpp index 07795bf..a015a56 100644 --- a/src/JackAudio.cpp +++ b/src/JackAudio.cpp @@ -2560,6 +2560,8 @@ void InstanceAudioJack::process(jack_nframes_t nframes) { std::unique_lock guard(mMIDIMapMutex, std::try_to_lock); uint16_t lastKey = 0; + + int8_t acceptchan = mMIDIInputChannel; for (auto i = 0; i < count; i++) { jack_midi_event_get(&evt, midi_buf, i); @@ -2607,6 +2609,28 @@ void InstanceAudioJack::process(jack_nframes_t nframes) { } } + //if we're filtering midi and the byte has status + uint8_t status = bytes[0] & 0xF0; + if (acceptchan >= 0 && (status & 0x80) != 0) { + switch (status) { + case midimap::NOTE_ON: + case midimap::NOTE_OFF: + case midimap::CONTROL_CHANGE: + case midimap::KEY_PRESSURE: + case midimap::CHANNEL_PRESSURE: + case midimap::PITCH_BEND_CHANGE: + case midimap::PROGRAM_CHANGE: { + int8_t chan = static_cast(bytes[0] & 0x0F); + if (chan != acceptchan) { + continue; + } + } + break; + default: + break; + }; + } + //look for program change to change preset if (mProgramChangeQueue && evt.size == 2 && (evt.buffer[0] & 0xF0) == 0xC0) { mProgramChangeQueue->enqueue(ProgramChange { .chan = static_cast(evt.buffer[0] & 0x0F), .prog = static_cast(evt.buffer[1]) }); diff --git a/src/MIDIMap.cpp b/src/MIDIMap.cpp index 3def75f..e57f896 100644 --- a/src/MIDIMap.cpp +++ b/src/MIDIMap.cpp @@ -1,27 +1,5 @@ #include "MIDIMap.h" -namespace { - static constexpr uint8_t NOTE_OFF = 0x80; - static constexpr uint8_t NOTE_ON = 0x90; - static constexpr uint8_t KEY_PRESSURE = 0xA0; - static constexpr uint8_t CONTROL_CHANGE = 0xB0; - static constexpr uint8_t PITCH_BEND_CHANGE = 0xE0; - static constexpr uint8_t SONG_POSITION_POINTER = 0xF2; - static constexpr uint8_t PROGRAM_CHANGE = 0xC0; - static constexpr uint8_t CHANNEL_PRESSURE = 0xD0; - static constexpr uint8_t QUARTER_FRAME = 0xF1; - static constexpr uint8_t SONG_SELECT = 0xF3; - static constexpr uint8_t TUNE_REQUEST = 0xF6; - static constexpr uint8_t TIMING_CLOCK = 0xF8; - static constexpr uint8_t START = 0xFA; - static constexpr uint8_t CONTINUE = 0xFB; - static constexpr uint8_t STOP = 0xFC; - static constexpr uint8_t ACTIVE_SENSING = 0xFE; - static constexpr uint8_t RESET = 0xFF; - static constexpr uint8_t SYSEX_START = 0xF0; - static constexpr uint8_t SYSEX_END = 0xF7; -} - namespace midimap { double value(uint8_t status, uint8_t data0, uint8_t data1, bool normalize) { switch (status & 0xF0) { diff --git a/src/MIDIMap.h b/src/MIDIMap.h index 59f3f07..6867d2a 100644 --- a/src/MIDIMap.h +++ b/src/MIDIMap.h @@ -5,6 +5,26 @@ //always mapping to/from normalized values namespace midimap { + inline constexpr uint8_t NOTE_OFF = 0x80; + inline constexpr uint8_t NOTE_ON = 0x90; + inline constexpr uint8_t KEY_PRESSURE = 0xA0; + inline constexpr uint8_t CONTROL_CHANGE = 0xB0; + inline constexpr uint8_t PITCH_BEND_CHANGE = 0xE0; + inline constexpr uint8_t SONG_POSITION_POINTER = 0xF2; + inline constexpr uint8_t PROGRAM_CHANGE = 0xC0; + inline constexpr uint8_t CHANNEL_PRESSURE = 0xD0; + inline constexpr uint8_t QUARTER_FRAME = 0xF1; + inline constexpr uint8_t SONG_SELECT = 0xF3; + inline constexpr uint8_t TUNE_REQUEST = 0xF6; + inline constexpr uint8_t TIMING_CLOCK = 0xF8; + inline constexpr uint8_t START = 0xFA; + inline constexpr uint8_t CONTINUE = 0xFB; + inline constexpr uint8_t STOP = 0xFC; + inline constexpr uint8_t ACTIVE_SENSING = 0xFE; + inline constexpr uint8_t RESET = 0xFF; + inline constexpr uint8_t SYSEX_START = 0xF0; + inline constexpr uint8_t SYSEX_END = 0xF7; + double value(uint8_t status, uint8_t data0, uint8_t data1, bool normalize = true); //0 means we don't map this type uint16_t key(uint8_t status, uint8_t data0 = 0);