Salta ai contenuti

Add a MIDI controller and MIDI learn

Questa pagina è in inglese: in italiano ci sono per ora i tutorial e il glossario; menu, ricerca e navigazione sono in italiano.

A footswitch, a pad or a fader becomes a binding: a MIDI trigger that fires a command. Learn lets the user press the control instead of typing its number. Everything here runs without hardware on the fakes; swap in a device adapter for the real thing.

Uses: core/midi (USES midi). Runnable version: examples/midi/gate.

MidiInputs opens ports by name through a MidiInputDirectory and feeds every message, in order, to an activity monitor, the learn arbiter, the trigger matcher, an MSC receiver and your own consumer:

midi::ActivityMonitor monitor;
midi::TriggerMatcher matcher;
midi::MidiInputs inputs (midiInputDirectory, { monitor, &matcher, nullptr, nullptr, &keyboard });
inputs.open ("Stage Keys");

midiInputDirectory is base::testing::FakeMidiInputDirectory in a test. In an application it is LibremidiDevices from the libremidi adapter (no JUCE; MIDI 1.0 and MIDI 2.0, see examples/libremidi/ports) or JuceMidiDevices from juce_midi (in the shuffle_juce satellite). Both also implement the output directory.

midi::Binding footswitch;
footswitch.id = 1;
footswitch.trigger.kind = midi::TriggerKind::controller;
footswitch.trigger.number = 85; // CC 85, any channel
footswitch.onPress = commands::makeCommand ("cues.go");
matcher.publish ({ footswitch });

The matcher runs on the MIDI input thread without allocating. The command is not applied there: on a control timer, take what fired and submit it like any other request:

for (const auto& fired : matcher.takeFired())
session.submit (fired.command, midi::showControlTriggerProfile(), {});

showControlTriggerProfile() marks the source, so GO protection, show mode and the recorder treat it as a show-control trigger.

In an application, bindings are show state and change through the MIDI commands (midiCommands(), MidiCommandTarget). Learn is a request the target hands out; the arbiter captures the next eligible message for one owner at a time:

midi::LearnArbiter learn ([&ui] (const std::string& owner) { ui.learnCancelled (owner); });
midi::MidiInputs inputs (midiInputDirectory, { monitor, &matcher, &learn, nullptr, nullptr });
// after every applied command:
matcher.publish (midi::bindingsToPublish (target.setup()));
for (auto& r : target.takeLearnRequests()) r.arm ? learn.arm (r.owner, r.kinds) : learn.end (r.owner);
// on the control timer:
if (auto capture = learn.takeCapture())
session.submit (midi::bindTriggerCommand (*capture, valueOfOwner), ownerProfile, {});

The capture becomes a command too, so learning a pedal can be undone. The full wiring, with MSC and inhibit switches, is in core/midi.

A tracked output remembers held notes and pedals; panic releases exactly those, then sends all-notes-off:

midi::PanicService panic (midiOutputDirectory, tasks);
const auto report = panic.panicAll(); // report.messagesSent, report.portsSent

Reference: specs/midi.md (triggers and learn MI-R76 to MI-R86).