Skip to main content

6. Hello world in Processing

Every device has a starter sketch under 40 lines that reads the device and draws something right away. This is the PipSqueak one, because a joystick is about as simple as it gets: two axes and a button. The other starters have the same shape, so once this one makes sense, so do they.

Open it​

  1. Plug the PipSqueak in before you press Run. Java's MIDI on macOS does not see devices plugged in after a sketch starts. Plugged in late? Stop and run again. The browser Explorer does not have this limit.
  2. Open starters/java/PipSqueakStarter/PipSqueakStarter.pde, or starters/python/pipsqueak_starter/pipsqueak_starter.pyde in Python Mode.
  3. Press Run. Push the stick. A dot moves. Tap the button and it changes colour.

No stick? The helper fakes one. The arrow keys move it and space is the button.

The whole sketch​

PipSqueakStarter.pde
// PipSqueak starter: the joystick pushes a dot around, the button changes its colour.
// No stick: arrows move, space is the button. Edit the lines marked "change this".
PipSqueak stick;
float px, py, hue = 200;

void setup() {
size(800, 600);
colorMode(HSB, 360, 100, 100, 100);
background(0, 0, 8);
px = width / 2;
py = height / 2;
stick = new PipSqueak(this); // change this: new PipSqueak(this, "name") if yours has another name
stick.connect(); // prints the MIDI devices it found; fine with no device
}

void draw() {
px = constrain(px + stick.x * 6, 0, width); // stick.x is -1..1. change this: 6 is the speed
py = constrain(py - stick.y * 6, 0, height); // stick.y is +1 when pushed up; screen y grows downward
if (stick.justPressed()) hue = (hue + 47) % 360; // change this: what a tap of the button does
fill(0, 0, 8, 12); // change this: a lower alpha leaves a longer trail
rect(0, 0, width, height);
fill(hue, 80, 100);
float size = 40 + stick.magnitude * 30 + (stick.pressed ? 40 : 0);
circle(px, py, size); // change this: what gets drawn
}

Line by line​

Look at the tabs across the top of the Processing window. Next to the starter sit MidiCore and PipSqueak. Those two tabs are the whole MIDI side of the sketch: they talk to javax.sound.midi, decode the three bytes, and turn them into a few fields you can read. You never edit them. Every starter carries its own copy because Processing can't import a sibling folder.

PipSqueak stick; declares the helper. In Python Mode the import line does the same.

stick = new PipSqueak(this); creates it. this is your sketch, so the helper can update itself before every draw(). If the Explorer shows your stick under another name, pass that name as a second argument. In p5.js it is new PipSqueak().

stick.connect(); opens the device. It prints every MIDI port it can see, opens the first one whose name contains "PipSqueak", and returns. With no device it prints one line and the keyboard stand-in takes over. Nothing here ever throws. In p5.js the line is stick.connectOnClick(), because a browser won't hand out MIDI until you click something.

stick.x and stick.y are the joystick, -1 to 1, calibrated for the stick's off-centre rest and a small deadzone. y is +1 when you push up, so the starter subtracts it from screen y. The 6 is the speed.

stick.justPressed() is true for one frame after the button goes down. stick.pressed is true while it is held.

stick.magnitude is how far the stick is pushed, 0 to 1. stick.angle is radians and NaN inside the deadzone, so check magnitude first.

The translucent rectangle drawn over the frame each time is what leaves the trail. Lower the alpha for a longer one.

Python Mode uses snake_case: stick.just_pressed(). Its stop() calls stick.close(). In Java the helper does that itself.

Callbacks​

Instead of polling in draw(), define a function with the right name and the helper calls it: void stickPressed(), void stickReleased(), or for any device void noteOn(int channel, int note, int velocity) and void controlChange(int channel, int number, int value). Nothing to register.

How every helper behaves​

Channels are 1..16 in every call and callback. Status 0x92 is channel 3.

connect() prints the inputs and outputs Java sees, opens the first input and first output whose name or description contains the substring (any case), and returns a boolean. No match: one line on the console and the sketch keeps going. connected() means an input is open. The constructor never touches hardware, so new PipSqueak(this) is fine as a field initializer.

Messages arrive on Java's MIDI thread and queue. Java helpers register pre() and drain the queue right before draw(), so pressed and justPressed() hold for a whole frame, and justPressed() is true for one frame per press. Python Mode has no per-frame hook for a module, so a Python helper drains the queue the first time you touch it in a frame (it watches this.frameCount). Callbacks only? Call helper.update() at the top of draw().

Callbacks work the Processing way. Define a sketch function with the right name and arguments and the helper calls it. Java finds it by reflection, so it must be a top-level sketch function. Python looks in the sketch module; pass callbacks=globals() if that ever fails. Every helper offers noteOn(channel, note, velocity), noteOff(channel, note, velocity) and controlChange(channel, number, value) (Python: note_on, note_off, control_change). Device callbacks are listed per device. Callbacks run on the animation thread, before draw().

No device? Each helper that has a keyboard stand-in keeps the sketch usable from the keyboard. Details per device. Python stand-ins read one key at a time.

dispose() (Java) restores the device: LEDs off, Launchpad back to Live mode, ports closed. Python helpers have close(); call it from stop() if you care.

Java helpers are public class. Keep it that way: registerMethod() throws IllegalAccessException on a package-private inner class. The tabs also compile with plain javac (float literals carry an f, no color type, no #hex literals). That is how the tests build them.

MidiCore works on its own for anything unusual: new MidiCore(this, "input substring", "output substring"), send, noteOn, noteOff, controlChange, programChange, pitchBend, sysex(int[]), poll(handler), inject(status, d1, d2) to fake a message, and the sketch-level printMidiDevices(), midiInputs(), midiOutputs(). Python: MidiCore(this, "in", "out"), print_midi_devices(), midi_inputs(), midi_outputs().

Now change something​

Each change this comment marks a line worth changing. When you've had a go, head to your device page and the ideas list.