Playing live
Everything for taking a sketch on stage: knobs and pads from a MIDI controller (with MIDI learn on the [Tweak] sliders), visuals that move in time with the music, a microphone that hears the room, and a full-screen mode that shows the code to the audience while you change it. No packages — MIDI and the microphone talk to the OS directly on Windows, macOS and Linux.
On this page · 6 entries
OnMidi · MidiCC · MidiNoteDown · MapMidi · MidiInputs
OnMidi(MidiMessage m)MidiCC(controller, channel?) → floatMidiNoteDown(note)MapMidi(field, controller)MidiInputs()
MIDI input from any controller, with no packages: the engine opens every MIDI input the OS has (WinMM on Windows, CoreMIDI on macOS, ALSA on Linux) the first time the sketch uses MIDI. Override OnMidi to react to each message on the drawing thread, right before Draw(): m.Kind is NoteOn, NoteOff, ControlChange, PitchBend..., m.Value01 is a knob or fader as 0..1, m.Velocity, m.Bend and m.Channel (1..16) the rest. Or just read the latest state: MidiCC(21) is knob 21 as 0..1, MidiNoteDown(36) is true while that pad is held. RefreshMidi() picks up a controller plugged in later.
Setup(): MapMidi("speed", 21), MapMidiNote("trails", 36), and MapMidi("bg.r", 22) for a channel of a Color.Beat · Bar · Phase · Pulse · PingPong · BeatTween · OnBeat
Beat()Bar() → doublePhase(beats)Pulse(beats)PingPong(beats)BeatTween(from, to, ease, beats) → floatOnBeat(beats) → bool
Musical time for visuals, so an animation is tied to the music without any arithmetic. Beat() is where the music is, in beats (2.5 = halfway through the third beat), Bar() the same in bars. Phase(n) ramps 0 → 1 every n beats, Pulse(n) is 1 on the beat and fades out before the next, PingPong(n) goes 0 → 1 → 0, and OnBeat(n) is true on the one frame a new beat (or every n beats: OnBeat(4) = each bar) starts. BeatTween(100, 400, Ease.OutBack) moves between two values landing exactly on the next beat; Easing.Apply(Ease.InOutCubic, t), Easing.OutBounce(t) and the rest are there for any 0..1 progress.
Bpm = 140, even from inside a loop) never makes the count jump. With no sound they follow Millis() at the current Bpm. The clock is read once per frame, so every call in one Draw() sees the same instant.ListenTo · MicLevel · MicGain · MicStatus
ListenTo(AudioSource source)MicLevel → floatMicGainMicStatus
Makes Amplitude, Waveform() and Spectrum() hear the real world: ListenTo(AudioSource.Microphone) switches them to the default microphone (or line in), AudioSource.Both hears the sketch's own sound and the room, AudioSource.Sketch goes back. MicLevel reads the microphone whatever was chosen. Microphones are quiet next to the sketch's own sound: MicGain = 4 boosts them. The microphone opens when the first sketch listens and closes when the last one stops, so the OS's "in use" light means a sketch is really listening.
Amplitude reads 0 and MicStatus says why.Performance mode F11
For playing on stage, algorave style. Sketch → Performance mode (F11) puts the running sketch full screen — on the second display when there is one, so the projector gets the visuals — with its code floating on top, big and see-through, still editable. The sketch, its music and its state never stop: the canvas is the same one the IDE was showing, and the code is the same tab, so every keystroke is in the IDE too.
| Key | Does |
|---|---|
Ctrl+Enter | ⚡ Hot Reload (a full Run if the fields changed) |
Ctrl+Shift+Enter | ▶ Run from the start |
Ctrl+H | Hide / show the code (hidden, the keyboard goes to the sketch) |
Ctrl+T | Hide / show the [Tweak] sliders — MIDI-mapped knobs keep working |
Ctrl + / Ctrl − | Code size |
Ctrl ↑ / Ctrl ↓ | How dark the band behind the code is |
Ctrl+Shift ↑ / Ctrl+Shift ↓ | How see-through the code is, 10% at a time (from solid down to 20%), so the visuals show through the text |
Esc | Back to the IDE |
Size, backdrop and transparency are remembered for the next show. To set them before going on stage, use Tools → Settings → Performance mode (see Settings).
Runnable example: visuals in time
Two live loops at 110 BPM; the ring throbs with Pulse(), the blocks land on the beat with BeatTween() and the palette changes every bar with OnBeat(4). Test in Dana opens it in the IDE, or paste it into a new tab and press Run. It is also in Sketch → Examples.
// Visuals in time with the music -- no math with Millis() or frame counts. // // Beat() where the music is, in beats (2.5 = halfway through beat 3) // Phase(n) 0 -> 1 ramp every n beats (0 = right on the beat) // Pulse(n) 1 on the beat, fading out before the next one // OnBeat(n) true on the one frame a new beat (or every n beats) starts // BeatTween(a, b, Ease.OutBack) a -> b, landing exactly on the next beat // // While sound plays they follow what you HEAR; the loops' first downbeat is // beat 0 of a bar. Change Bpm and press Hot Reload: everything keeps up. public class MySketch : Sketch { private int _palette; private readonly float[] _hues = { 200, 330, 40, 160 }; public override void Setup() { Size(720, 480); Bpm = 110; } [LiveLoop] async Task Drums() { for (int i = 0; i < 4; i++) { Sample("kick"); if (i % 2 == 1) Sample("snare", amp: 0.7); await Sleep(0.5); Sample("hat", amp: 0.4); await Sleep(0.5); } } [LiveLoop] async Task Bass() { foreach (var note in new[] { "E2", "E2", "G2", "A2" }) { UseSynth(Synth.Saw); Play(note, amp: 0.4, release: 0.5, cutoff: 75); await Sleep(1); } } public override void Draw() { if (OnBeat(4)) _palette = (_palette + 1) % _hues.Length; // a new color every bar float hue = _hues[_palette]; Background(12, 12, 18); // 1. The ring throbs on every beat. float throb = Pulse(1, sharpness: 5); NoFill(); StrokeWeight(6 + throb * 10); StrokeHSB(hue, 70, 100); Circle(180, 200, 140 + throb * 60); // 2. Blocks slide to a new spot on every beat, landing on the next one. NoStroke(); for (int i = 0; i < 4; i++) { int step = (int)Beat() + i; float from = 360 + (step % 4) * 80; float to = 360 + ((step + 1) % 4) * 80; float x = BeatTween(from, to, Ease.OutBack); FillHSB((hue + i * 30) % 360, 60, 95); Rect(x, 90 + i * 60, 50, 40); } // 3. A 16-step sequencer row: the cursor is Beat() in sixteenths. int sixteenth = (int)(Beat() * 4) % 16; for (int i = 0; i < 16; i++) { bool on = i == sixteenth; if (on) FillHSB(hue, 50, 100); else Fill(255, i % 4 == 0 ? 70 : 35); Rect(40 + i * 40, 400, 32, on ? 40 : 24); } // 4. Bars and beats as text. Fill(255, 200); TextSize(14); Text($"bar {(int)Bar() + 1} beat {(int)Beat() % 4 + 1} {Bpm:0} BPM", 40, 380); } }
Runnable example: MIDI learn
A flower played with a MIDI controller: click MIDI in the [Tweak] panel, touch a slider, turn a knob. Without a controller, the sliders work with the mouse and keys 1–8 fake pad hits. Test in Dana opens it in the IDE, or paste it into a new tab and press Run. It is also in Sketch → Examples.
// MIDI learn: play this flower with a MIDI controller. // // 1. Click MIDI in the [Tweak] panel (top right): it lights up. // 2. Touch a slider, then turn a knob or move a fader: they're connected. // 3. Do the same with as many sliders as you like, then click MIDI again. // // Connections last the whole session (Run and Hot Reload keep them). To make // one permanent, write it in Setup(): MapMidi("petals", 21); // Pads and keys fire bursts (OnMidi), sized by velocity; the pitch-bend wheel // twists the whole flower. No controller? Keys 1-8 fake pad hits. public class MySketch : Sketch { [Tweak(3, 24)] int petals = 9; [Tweak(30, 260)] float radius = 150; [Tweak(0, 3)] float spin = 0.5f; [Tweak(0, 360)] float hue = 200; [Tweak(0, 1)] float wobble = 0.35f; [Tweak] bool trails = true; private readonly List<Burst> _bursts = new(); private float _bend; // -1..1 from the pitch-bend wheel private float _angle; private string _status = ""; private sealed class Burst { public float X, Y, Size, Hue, Life = 1; } public override void Setup() { Size(720, 520); var inputs = MidiInputs(); _status = inputs.Length == 0 ? "No MIDI controller found -- keys 1-8 fake pad hits" : "MIDI: " + string.Join(", ", inputs); } public override void OnMidi(MidiMessage m) { if (m.Kind == MidiKind.NoteOn) Fire(m.Note, m.Velocity / 127f); else if (m.Kind == MidiKind.PitchBend) _bend = m.Bend / 8192f; } public override void KeyPressed() { if (Key >= '1' && Key <= '8') Fire(36 + (Key - '1'), 0.8f); } private void Fire(int note, float strength) { float a = note * 0.7f; _bursts.Add(new Burst { X = Width / 2f + Cos(a) * radius * 0.8f, Y = Height / 2f + Sin(a) * radius * 0.8f, Size = 30 + strength * 160, Hue = (hue + note * 15) % 360, }); } public override void Draw() { if (trails) Background(10, 12, 20, 40); else Background(10, 12, 20); _angle += spin * 0.02f; float t = Millis() / 1000f; PushMatrix(); Translate(Width / 2f, Height / 2f); Rotate(_angle + _bend * 1.2f); NoFill(); StrokeWeight(2); for (int ring = 0; ring < 4; ring++) { float r = radius * (1 - ring * 0.2f); StrokeHSB((hue + ring * 25) % 360, 70, 100, 200); BeginShape(); for (int i = 0; i <= 200; i++) { float a = TWO_PI * i / 200; float petal = Sin(a * petals + t * 1.5f + ring) * wobble; float rr = r * (0.6f + 0.4f * Abs(Cos(a * petals / 2f))) * (1 + petal * 0.3f); Vertex(Cos(a) * rr, Sin(a) * rr); } EndShape(true); } PopMatrix(); NoStroke(); for (int i = _bursts.Count - 1; i >= 0; i--) { var b = _bursts[i]; FillHSB(b.Hue, 60, 100, 180 * b.Life); Circle(b.X, b.Y, b.Size * (2 - b.Life)); b.Life -= 0.03f; if (b.Life <= 0) _bursts.RemoveAt(i); } Fill(255, 160); TextSize(13); Text(_status, 14, Height - 16); } }