Skip to main content

Anything else

Trinkeys, the NeoTrellis, a keyboard you brought from home, a drum pad, a foot pedal. If the operating system sees it as a MIDI device, the generic helper can read it and the Explorer will show you what it sends.

Find out what it sends​

  1. Plug it in and open the Explorer.
  2. Press, turn, slide or tilt everything once. Write down the note and CC numbers.
  3. Note the device name at the top. That is the substring you give the helper.

On the table​

DeviceWhat it isWhat it sends
Slide TrinkeyAdafruit USB stick with a slider, a touch pad and two pixels. Shows up as "Slide Trinkey M0".Slider is CC 1, 0 at the left to 127 at the right. Touch pad is note 60. Send it a note on and the pixels take that note's colour for a second. Firmware in trinkeys/.
Rotary TrinkeyA click knob with a push switch, a touch pad and one pixel. Shows up as "Rotary Trinkey M0".Knob is CC 2 as an absolute 0..127 position and CC 3 as relative clicks (1 clockwise, 127 counter-clockwise). Pressing the knob is note 61, the touch pad note 62. Same colour trick on incoming notes.
NeoTrellis M4Adafruit's 4×8 grid of RGB-lit buttons with an accelerometerProbably notes for the buttons and CCs for tilt. Check the Explorer. It is a grid, so the Midi Fighter ideas transfer.
YoursAnythingSame drill. Explorer first.

Start here​

Open starters/java/AnyMidiStarter/AnyMidiStarter.pde. It draws every note and CC it sees.

AnyMidiStarter.pde
// AnyMidi starter: works with whatever MIDI device you plug in. Notes pop a circle, knobs and
// sliders (control changes) move bars. No device: letters are notes, drag the mouse for a knob.
AnyMidi m;
float[] pop = new float[128]; // one per note number, shrinks every frame
int[] knob = new int[128]; // last value of each control change number

void setup() {
size(900, 400);
m = new AnyMidi(this); // change this: new AnyMidi(this, "part of the device name")
m.connect(); // prints the MIDI devices it found; with no name it takes the first input
}

void draw() {
background(15);
noStroke();
for (int n = 0; n < 128; n++) {
float x = map(n, 0, 127, 10, width - 10);
fill(90, 200, 255);
rect(x - 2, height - 10, 4, -knob[n] * 2); // change this: bars for CC values 0..127
if (pop[n] > 0) {
fill(255, 120, 80, pop[n] * 2);
circle(x, height / 2, pop[n]); // change this: what a note looks like
pop[n] -= 2;
}
}
}

// The helper calls these whenever a message arrives. Channel is 1..16; note / number / value are 0..127.
void noteOn(int channel, int note, int velocity) { pop[note] = 40 + velocity; } // change this
void controlChange(int channel, int number, int value) { knob[number] = value; } // change this

// Stand-ins so the sketch does something without a device.
void keyPressed() { if (key != CODED) noteOn(1, 48 + key % 36, 100); }
void mouseDragged() { controlChange(1, 1 + mouseY * 8 / height, int(map(mouseX, 0, width, 0, 127))); }

The helper​

new AnyMidi(this), new AnyMidi(this, "name substring")The first input on the system, or the first whose name contains the substring.
m.note(n), m.cc(n), m.down(n)The last value seen for that note or controller, 0..127, or -1 if it has never arrived. Whether a note is held.
m.lastNote, m.lastCC, m.lastCCValue, m.lastThe most recent thing that happened, for sketches that react to whatever arrives.
m.send(status, d1, d2), m.noteOn(ch, n, vel), m.controlChange(ch, cc, val)Output, for devices with lights.
void noteOn(…), void noteOff(…), void controlChange(…), void midiMessage(…)Define any in your sketch and the helper calls it with channel, number and value.

From the helpers' README​

For the NeoTrellis and whatever people bring.

AnyMidi m = new AnyMidi(this, "name substring"); // or new AnyMidi(this): first input (and first output)
m.note(n); m.cc(n); // last value seen, 0..127, -1 if never. A note's value is its velocity, 0 after Note Off
m.down(n); // note held?
m.lastNote; m.lastVelocity; m.lastCC; m.lastCCValue; m.lastChannel; m.pitchBend; m.pressure; m.count; m.last
m.send(status, d1, d2); m.noteOn(ch, n, vel); m.noteOff(ch, n); m.controlChange(ch, cc, val);
m.programChange(ch, p); m.pitchBend(ch, value); m.sysex(bytes);

Callbacks: the three generic ones, plus pitchBend(int channel, int value) (-8192..8191) and midiMessage(int status, int data1, int data2) for anything else. m.last is a MidiMsg with status, type, channel, data1, data2, millis, sysex and a readable toString(). The Explorer prints it.

Python: AnyMidi(this, name=""), m.note(n), m.cc(n), m.down(n), m.last_note, m.last_cc, m.pitch_bend, m.count, m.last, m.send, m.note_on, m.note_off, m.control_change, m.send_pitch_bend, m.sysex.

The p5.js helpers​

The same helpers for the browser: one script per device, or the bundle midi-helpers/p5/midi-helpers.js, served from this site.

The same API for the browser, as plain classic scripts: no modules, no bundler, no dependencies. Each device script defines one global constructor and carries its own copy of the Web MIDI plumbing (MidiCore), guarded so it is defined once however many scripts you load. midi-helpers.js has all of them. Works from file://, GitHub Pages and the workshop host. Sources are in p5/src/; p5/build.sh concatenates them into the committed scripts.

<script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>
<script src="midi-helpers.js"></script> <!-- or pipsqueak.js alone -->
<script>
const stick = new PipSqueak(); // never touches hardware until connect()
function setup() { createCanvas(600, 600); stick.connectOnClick(); }
function draw() {
background(20);
circle(300 + stick.x * 250, 300 - stick.y * 250, stick.pressed ? 60 : 30);
}
function stickPressed() { console.log("click"); }
</script>

In the p5.js web editor (editor.p5js.org, in Chrome, Edge or Opera): open index.html in the sketch files panel and add, after the p5 tag,

<script src="https://pcd2026.mand.is/midi-helpers/p5/midi-helpers.js"></script>

Write the sketch as above. Press Play, click the canvas once. The browser asks for MIDI permission the first time and the helper connects. The editor's preview iframe allows Web MIDI: PreviewFrame.jsx on the develop branch lists midi in the iframe's allow attribute, with allow-same-origin and allow-scripts in its sandbox. A real click-through is still on the hardware checklist. If anything is off, use the editor's full-screen preview or download the sketch and open it in Chrome.

Connecting. Web MIDI wants a user gesture, so every helper has connectOnClick(). It waits for the first click, touch or key anywhere on the page, calls connect(), and keeps a status string in helper.status (click to connect MIDI, connecting..., connected to X, no MIDI device matching 'pipsqueak', Web MIDI unavailable: ...). On p5 1.x it also registers a post hook that draws that status bottom-left until a device connects. On p5 2.x, or for your own look, call helper.drawHint() at the end of draw() or pass a function: stick.connectOnClick((status) => text(status, 10, 20)). await stick.connect() works too, from mousePressed() say, and resolves to true or false. Not found never throws. The constructor never touches hardware.

Frame sync. Each helper runs its own requestAnimationFrame loop, so pressed and justPressed() change at most once per frame, between draws. Set helper.autoUpdate = false and call helper.update() yourself if you need control (instance mode, tests).

Callbacks are global functions: noteOn(channel, note, velocity), noteOff(...), controlChange(channel, number, value) from every helper; stickPressed() / stickReleased(); padPressed(index) / padReleased(index) / bankChanged(bank) (Midi Fighter); padPressed(x, y) / padReleased(x, y) / buttonPressed(id) / buttonReleased(id) (Launchpad); touchPressed(pad) / touchReleased(pad) / accelChanged(); pitchBend(channel, value) / midiMessage(status, d1, d2) (AnyMidi). JavaScript cannot overload padPressed, so a sketch with both grid devices passes { callbacks: {...} } to one of them. Any helper takes { callbacks: { padPressed(i) {...} } } to skip the global lookup (instance mode).

Keyboard stand-ins work while nothing is connected: arrows + SPACE (PipSqueak), 1234 qwer asdf zxcv (Midi Fighter), 1..8 (Circuit Playground touch pads), arrows / T / Enter (Trinkeys).

Per device, the surface is the Java one with JS idioms. Constructors take an options object or a name string: new PipSqueak({ name, x: {...}, y: {...}, button: {...}, deadzone, smoothing }) (same JSON shape as everywhere else), new MidiFighter({ name, channel, mode, map }), new Launchpad({ name }), new CircuitPlayground({ name }), new AnyMidi("name") / new AnyMidi().

  • stick.x, stick.y, stick.angle (null in the deadzone), stick.magnitude, stick.pressed, stick.justPressed(), stick.justReleased(), stick.recenter(), stick.flash(), stick.send(status, d1, d2). On the constructor: PipSqueak.normalize(rawX, rawY, config), PipSqueak.normalizeAxis, PipSqueak.mergeConfig.
  • mf.pressed(i), mf.justPressed(i), mf.justReleased(i), mf.bank (0..3), mf.mode, mf.led(i, on, bank), mf.leds([...]) or a 16-bit mask, mf.clear(), mf.setMap(json); MidiFighter.noteForIndex(i, mode, bank), MidiFighter.indexForNote(note, mode).
  • pad.pressed(x, y), pad.justPressed(x, y), pad.buttonPressed(id), pad.set(x, y, c) where c is a p5 color, "#ff0080", [r, g, b], a palette index 0..127, or a name ("red", "cyan", ...), pad.setRGB, pad.flash, pad.pulse, pad.button(id, c), pad.clear(), pad.text(str, c, speed, loop), pad.stopText(), pad.close(). The Launchpad asks for SysEx permission (programmer mode, RGB). Refused, it still connects and snaps RGB colours to the nearest palette entry. pagehide restores Live mode. Launchpad.toColor(c), Launchpad.xyToNote and the rest are exposed.
  • cpx.touch(i), cpx.accel.x/y/z, cpx.light, cpx.sound, cpx.temperature, cpx.sensor, cpx.mode, cpx.pixels(r, g, b), cpx.clear().
  • m.note(n), m.cc(n), m.down(n), m.lastNote, m.lastCC, m.pitchBend, m.count, m.last, m.send, m.noteOn, m.noteOff, m.controlChange, m.programChange, m.sendPitchBend, m.sysex.
  • slide.value, slide.raw, slide.touched, slide.justTouched(), slide.pixel(note); knob.value, knob.raw, knob.delta, knob.pressed, knob.justPressed(), knob.touched, knob.pixel(note).

The p5 ES modules in grid-controllers/ and pipsqueak/ are untouched; these scripts port their protocol logic. Differences: Midi Fighter banks are 0..3 here (1..4 there), setRGB takes 0..255 (0..127 there), and there is no event emitter. Use the global callbacks or poll in draw().

Try first​

  • One CC, one number on screen, huge. Then a colour, then a speed.
  • A piano keyboard as 61 buttons. Each note a column, velocity its height.
  • Two controls as x and y.

More on the ideas page.

Without the helper​

For an unknown device the raw path is HelloMidi: it opens every input and prints every message, which is most of what AnyMidi does. The three bytes, no helpers.