Skip to content

Common Lisp client

The Lisp client is the ASDF system gloopy, in common-lisp/ (dependencies vendored via ocicl). It mirrors the two control lanes as two packages. For a guided start, do the Common Lisp quickstart.

Hand-maintained (for now)

Unlike the gRPC / OSC / Python references, this page is written by hand from the export lists and :documentation strings in common-lisp/src/. A generated version (40ants-doc, from the same docstrings) is planned; until it lands, treat common-lisp/src/packages.lisp as the authoritative symbol list and this page as the guide to it.

Two packages, two lanes

Package Nickname Lane Transport Role
gloopy Structural (gRPC) TCP 127.0.0.1:50051 Create/edit the composition, query state, render
gloopy.osc glosc Live (OSC) UDP 127.0.0.1:9000 Fire-and-forget notes, CC, knob turns, transport

gloopy creates and names things and hands back stable ids; gloopy.osc performs on those ids with the lowest possible latency. Load the system with (asdf:load-system :gloopy) from the common-lisp/ directory.

Two conventions run through the whole gloopy package:

  • Queries return plists. Every read call (transport, get-state, list-tracks, list-inserts, effect-params, subscribe) returns Lisp plists — pull fields out with getf.
  • Actions signal on failure. A rejected RPC signals a Lisp error with the server's message rather than returning a status code, so you can wrap calls in ordinary handler-case.

Structural — gloopy (gRPC)

(asdf:load-system :gloopy)
(in-package :gloopy)
(connect)                                     ; 127.0.0.1:50051
(let ((id (add-synth-track "Lead" :wave :saw)))
  (add-clip id :notes (list (note 60 0 1) (note 64 1 1) (note 67 2 1)))
  (play)
  (subscribe :seconds 3 :on-event #'print)    ; stream playhead + meters
  (stop)
  (render "/tmp/mix.wav"))                     ; offline bounce

Connection

Function Purpose
(connect &key (host "127.0.0.1") (port 50051)) Open (or reopen) the channel to a running Gloopy. Returns the channel.
(disconnect) Close the channel.
(connectedp) T when a channel is open.
*channel* The active ag-grpc channel, or NIL.

Transport

Function Purpose
(play) / (stop) Start / stop-and-rewind the transport.
(set-tempo bpm) Set the tempo in BPM.
(set-swing amount) Swing feel: 0.5 straight … 0.75 triplet.
(seek beats) Move the playhead to beats.
(transport) (:playing :bpm :position-beats).

Tempo map

For mid-song tempo changes. An empty map means the transport's single constant tempo; adding markers makes the beat↔time mapping piecewise (constant between markers). The render path honours the map. See Tempo map & time.

Function Purpose
(add-tempo-marker beat bpm) Set the tempo to bpm from beat onward (upsert by beat).
(remove-tempo-marker beat) Remove the marker at beat.
(list-tempo-markers) → list of (:beat :bpm) plists, sorted by beat.
(add-tempo-marker 0 120)      ; verse at 120
(add-tempo-marker 32 140)     ; lift to 140 at bar 9 (beat 32)
(list-tempo-markers)          ; => ((:BEAT 0.0 :BPM 120.0) (:BEAT 32.0 :BPM 140.0))

Tracks

Function Purpose
(add-synth-track name &key (wave :saw) attack decay sustain release gain) Built-in synth instrument; returns its id.
(add-sampler-track name path &key (root-note 60)) One-shot sampler from a server-side .wav; returns its id.
(add-sfz-track name path) Native SFZ instrument from a server-side path; returns its id.
(add-audio-track name) Empty audio track (holds audio clips); returns its id.
(add-plugin-track identifier) Instrument track hosting a plugin (see list-plugins); returns its id.
(set-track-params id &key volume pan mute solo name) Set only the supplied params (see the proto3 caveat below).
(set-synth-param id name value) Tweak the built-in synth engine (see param names below).
(remove-track id) Delete a track.
(list-tracks) → list of (:id :name :type :volume :pan :mute :clips).
(get-state) Whole-project snapshot: (:transport … :tracks (…) :inserts (…)).

wave is a keyword — one of :sine :saw :square :triangle (or the raw integer). set-synth-param names (strings): wave osc2wave osc2detune oscmix sub attack decay sustain release gain ftype cutoff reso fenvamt fattack fdecay fsustain frelease lfotarget lforate lfodepth.

Macros (the rack layer)

A macro is one perceptual encoder (a single 0..1 knob) mapped onto one or more underlying params. Each mapping has an authored safe range [lo, hi]; turning the macro sweeps every mapped param across its range (param = lerp(lo, hi, value)). Because a macro can only move a param inside that guardrail, randomize-macros stays musical. Macros live on a track and are saved with the project.

Function Purpose
(add-macro id &optional name) Add a macro to a track; returns its index.
(set-macro-value id macro value) Turn the macro (0..1); every mapped param sweeps its [lo,hi].
(map-macro-synth id macro param &optional lo hi) Map onto a built-in synth param (names above), swept lo..hi.
(map-macro-effect id macro insert slot param &optional lo hi) Map onto a mixer insert-effect param.
(randomize-macros id) Roll every macro to a fresh random 0..1 and apply.
(rename-macro id macro name) Rename a macro (empty names rejected).
(clear-macro-mappings id macro) Remove all of a macro's mappings (keep the macro).
(remove-macro id macro) Delete a macro (later macros shift down one index).
(let ((tr (add-synth-track "Lead")))
  (add-macro tr "Brightness")                 ; macro 0
  (map-macro-synth tr 0 "cutoff" 0.2 0.9)     ; open the filter as the knob turns up
  (map-macro-synth tr 0 "reso"   0.0 0.4)     ; …and add a little resonance with it
  (set-macro-value tr 0 0.75)                 ; 3/4 bright
  (randomize-macros tr))                       ; or roll the dice — stays in range

Clips

Function Purpose
(note pitch start length &optional (velocity 0.8)) Build a note for add-clip: pitch a MIDI number or a name ("C#4"), start/length in beats (length also takes a dur code), velocity 0–1.
(seq steps &key (start 0) (velocity 0.8)) Lay (pitch duration) steps end-to-end; a :rest/nil/"r" pitch leaves a gap. Returns a list of notes.
(mini string &key (start 0) (velocity 0.8)) Parse mini-notation into a list of notes.
(add-clip track-id &key (start 0) (length 4) (content 0) (looped t) notes (name "")) Add a MIDI clip; returns (:track-id :index). content 0 means = length.
(add-audio-clip track-id path &key (start 0) (gain 1.0)) Import a server-side .wav as an audio clip; returns (:track-id :index).
(remove-clip track-id index) Delete a clip.
(move-clip track-id index start &key to-track) Move a clip along its track, or :to-track another track.

Note names, durations, scales & chords

Pure client-side helpers for building notes musically. Full syntax — pitch names, duration codes, scale and chord tables, the mini-notation grammar — is in Describing notes.

Function Purpose
(pitch name-or-midi) MIDI number for a name ("C#4", "Eb3"; default octave 4); ints pass through.
(pitch-name midi) Sharp-spelled name for a MIDI number (61"C#4").
(dur code) Beats for a duration code ("q" "h" "w" "e" "s", dotted "q.", triplet "8t"); numbers pass through.
(scale root &optional (name :major) (octaves 1)) List of MIDI pitches of a named scale from root.
(chord root &optional (quality :maj) (inversion 0)) List of MIDI pitches of a chord above root.

Mixer & effects

Mixer inserts are indexed; index 0 is the master bus. Effects live in each insert's chain, addressed by slot.

Function Purpose
(list-inserts) → list of (:index :name :volume :pan :mute :solo :effects).
(add-effect insert type) Add a built-in effect; returns its slot.
(add-plugin-effect insert identifier) Host a plugin effect (see list-plugins); returns its slot.
(remove-effect insert slot) Delete an effect from the chain.
(set-effect-param insert slot name value) Set a named effect parameter.
(set-effect-bypass insert slot bypassed) Bypass / re-enable an effect.
(effect-params insert slot) → list of (:name :value :min :max).

type is a keyword — one of :gain :filter :delay :reverb (or the raw integer).

Plugins

Function Purpose
(list-plugins) → list of (:name :format :instrument :identifier).
(scan-plugins &optional force) Rescan (or restore the cache) and return the plugin list.
(open-plugin-editor track-id) Open a hosted plugin's editor window.

Project & render

Function Purpose
(new-project) Reset to a fresh project.
(load-project path) / (save-project path) Load / save the single-file project.
(load-composition path) / (save-composition path) Load / save the directory "composition as code" format.
(render path &key (tail-seconds 2.0)) Bounce the whole song to a server-side WAV; blocks until done.

Events (server stream)

(subscribe &key (transport t) (meters t) (interval-ms 200) (seconds 3) on-event)

Streams playhead + meter events for roughly seconds seconds. Returns the collected event plists and, if on-event is supplied, calls it with each event as it arrives. Each event is tagged by :kind:

  • (:kind :transport :playing :bpm :position-beats)
  • (:kind :meters :peak-l :peak-r)

proto3 omits defaults — set-track-params can't zero a value

Because proto3 drops default (zero / false) fields on the wire, set-track-params cannot set volume/pan to exactly 0.0, nor turn mute/solo off. For "set to 0" or "turn a flag off", use the OSC lane (glosc:vol, glosc:mute) instead. This is a property of the protocol, not the client — see the model.


Live — gloopy.osc / glosc (OSC)

The live lane is fire-and-forget over UDP. connect prepares the socket but does not confirm anyone is listening; a stray "port unreachable" never aborts a performance. with-osc scopes the connection.

(glosc:with-osc ()
  (glosc:note-on 5 60 100)      ; middle C on track 5, right now
  (glosc:chord   5 '(57 60 64)) ; an A-minor chord
  (glosc:cc      5 74 0.5)      ; filter cutoff via CC 74
  (glosc:vol     5 0.8)         ; track volume
  (glosc:fx-param 6 0 "Wet" 0.6); reverb wet on insert 6, slot 0
  (glosc:tempo   128.0)
  (glosc:play))

Connection & lifecycle

Function Purpose
(connect &key host port) Open the UDP socket (defaults *host* / *port*).
(disconnect) Close the socket.
(connectedp) T when the socket is open.
(with-osc (&key host port) &body body) Connect for the duration of body, then disconnect.
*host* / *port* / *transmitter* Defaults 127.0.0.1 / 9000, and the live socket.

Live MIDI

Function Purpose
(note-on track pitch &optional (velocity 100)) Note-on now.
(note-off track pitch) Note-off now.
(chord track pitches &optional (velocity 100)) Note-on for each pitch in the list.
(cc track controller value) A MIDI CC (value 0–1) — the backbone of MIDI-learn.

Params & effects

Function Purpose
(vol track v) Track volume (0–1).
(pan track p) Track pan (−1…+1).
(mute track &optional (on t)) Mute / unmute a track.
(fx-param insert slot name value) Set a named effect parameter live.

Transport & demo

Function Purpose
(play) / (stop) Start / stop the transport.
(tempo bpm) Set the tempo.
(seek beats) Jump to beats.
(demo &key (track 5) (velocity 95)) A tiny built-in arpeggio, for a quick "is it wired up?" check.

OSC is encode-only

The Lisp OSC support encodes messages onto the wire (message construction + type-tagging via the third-party osc library; the datagram socket is driven with usocket). It does not receive — reads come back over gRPC. See common-lisp/README.md for the current status.


Tests

Offline — no running Gloopy required. They check the OSC wire encoding byte-for-byte against an independent reference and exercise the pure gRPC-side helpers:

(asdf:test-system :gloopy)

Related: Common Lisp quickstart · The Gloopy model · gRPC reference · OSC reference