SESI PROGRAMMING LANGUAGE DOCS
⌕

Audio in Sesi

Sesi includes three optional standard-library modules for working with sound: std/audio for synthesis and playback, std/theory for music-theory helpers, and std/mpc for Akai MPC-inspired track sequencing, Roger Linn swing, pad matrices, choke groups, AIR multi-mode effects, and vintage hardware converter modeling. None require external installation — just allow the module and go.

---

Importing the Modules

allow "std/audio" in as <alias>
allow "std/theory" in as <alias>
allow "std/mpc" in as <alias>

allow loads a module and binds it to any identifier you choose — the name after as is just a local namespace alias. These are all equally valid:

allow "std/audio" in as Audio      // conventional
allow "std/audio" in as sound      // also fine
allow "std/audio" in as snd        // also fine
allow "std/audio" in as a          // also fine

The examples below use Audio, Music, and MPC as readable conventions, but you are free to pick any name that fits your script.

---

std/audio — Sound Synthesis & Playback

Beep — Audio.beep

Audio.beep(frequency, duration)

Play a simple sine-wave tone. frequency is in Hz; duration is in milliseconds.

allow "std/audio" in as Audio

Audio.beep(440, 200)   // 440 Hz (concert A) for 200 ms
Audio.beep(880, 100)   // one octave up, shorter

---

Play a Note — Audio.play

Audio.play(note, duration, options?)

Play a musical note by name (e.g. "C4", "A#3", "Bb5"). options accepts ADSR, volume, and panning.

allow "std/audio" in as Audio

Audio.play("C4", 500)
Audio.play("E4", 500)
Audio.play("G4", 500)   // C-major chord, played in sequence

---

Synthesize to Base64 — Audio.synth

Audio.synth(frequency_or_note, duration, type, options?) -> string

Returns a base64-encoded WAV string instead of playing audio. Useful for passing audio data to another function or writing it to a file manually.

Supported type values: "sine", "square", "saw", "triangle", "noise", "kick", "snare", "hat", "clap".

allow "std/audio" in as Audio

let wav_b64 = Audio.synth(440, 1000, "square")
show "WAV data length:" len(wav_b64)

---

Save a Tone — Audio.save

Audio.save(path, frequency_or_note, duration, type, options?)

Synthesize a tone and write it directly to a WAV file.

allow "std/audio" in as Audio

// Save a 2-second sine-wave A4 note with fade-in and fade-out
Audio.save("tone.wav", "A4", 2000, "sine", {attack: 50, release: 500})

---

Load a WAV File — Audio.load

Audio.load(path) -> audio_sample

Read a WAV file from disk and return an audio_sample object containing the decoded PCM data. Useful for inspecting or processing existing audio files.

Returns: An object with:

Field Type Description
samples array<number> Normalized PCM floats in the range [-1, 1]
sampleRate number Sample rate in Hz (e.g. 44100)

allow "std/audio" in as Audio

let s = Audio.load("loop.wav")
show "Sample rate:" s.sampleRate
show "Total samples:" len(s.samples)

---

Save a Sequence — Audio.sequence

Audio.sequence(path, notes_array, type, options?)

Save a multi-note sequence to a single WAV file. notes_array can be:

  • An array of note strings: ["C4", "D4", "E4"]
  • An array of note objects with per-note control
  • Pre-rendered SF2 note objects (see Audio.sf2 below)

Note object fields:

Field Type Description
note string Note name (e.g. "C4")
ms number Duration in milliseconds
vol number Volume 0.0–1.0 (default 1.0)
pan number Stereo pan -1.0 (left) to 1.0 (right)
cutoff number Low-pass filter cutoff frequency in Hz

allow "std/audio" in as Audio

let song = [
  {note: "C4", ms: 500, vol: 0.8},
  {note: "E4", ms: 500, pan: -0.5},
  {note: "G4", ms: 1000, cutoff: 5000}
]

Audio.sequence("song.wav", song, "triangle")
show "Saved song.wav"

---

Mix Multiple Tracks — Audio.mix

Audio.mix(path, tracks_array, type, options?)

Mix several tracks into a single stereo WAV. tracks_array is an array of note arrays — each inner array is one track. The mixer supports ADSR envelopes, LPF filtering (cutoff), stereo pan, and soft-clipping saturation (saturate).

allow "std/audio" in as Audio

let melody = [
  {note: "C4", ms: 500},
  {note: "E4", ms: 500},
  {note: "G4", ms: 500}
]

let bass = [
  {note: "C2", ms: 1500, vol: 0.7}
]

Audio.mix("mix.wav", [melody, bass], "sine", {saturate: 1.2})
show "Saved mix.wav"

---

SoundFont Instruments — Audio.sf2

Audio.sf2(path, options?) -> fn(note, duration)

Load a SoundFont (.sf2) file and return an instrument function bound to a specific program/patch. Call that function with a note and duration to produce a Sesi-native note object that Audio.mix or Audio.sequence will batch-render via FluidSynth.

Options:

Field Type Description
instrument number GM program number (default 0 = piano)
channel number MIDI channel (default 0)
gain number Output gain multiplier (default 1.0)

allow "std/audio" in as Audio

let piano      = Audio.sf2("GeneralUser-GS.sf2", {instrument: 0,  gain: 1.5})
let string_pad = Audio.sf2("GeneralUser-GS.sf2", {instrument: 49})

let lead = [piano("C4", 500), piano("E4", 500), piano("G4", 500)]
let pad  = [string_pad("C3", 1500)]

Audio.mix("full_mix.wav", [lead, pad], "sine")
show "Saved full_mix.wav"

---

Drum Hits — Audio.kick / Audio.snare / Audio.hat / Audio.clap

Audio.kick(duration?, volume?)  -> string  // base64 WAV
Audio.snare(duration?, volume?) -> string
Audio.hat(duration?, volume?)   -> string
Audio.clap(duration?, volume?)   -> string

Generate a single drum hit using native physical modeling — no SoundFont required. Each function returns a base64-encoded WAV string that can be written to disk with write_file(..., "base64") or passed directly into Audio.mix.

Function Default duration Default volume Character
Audio.kick 300 ms 1.0 Deep, punchy bass
Audio.snare 200 ms 1.0 Bright crack
Audio.hat 50 ms 1.0 Short, sharp click
Audio.clap 100 ms 1.0 Bright clap

allow "std/audio" in as Audio

let k = Audio.kick(300, 1.0)
let s = Audio.snare(200, 0.8)
let h = Audio.hat(50, 0.6)

// Save a kick drum directly to disk
write_file("kick.wav", Audio.kick(300), "base64")

// Mix a drum pattern
Audio.mix("drums.wav", [[k, s, h]], "sine")

You can also reference them by type field inside a sequence note object (the older style still works):

Audio.sequence("drums.wav", [
  {note: "C1", ms: 300, type: "kick"},
  {note: "C4", ms: 200, type: "snare"}
], "kick")

---

MIDI Export — Audio.midi

Audio.midi(path, tracks) -> bool

Saves one or more tracks (arrays of note objects/strings) directly as a standard MIDI (.mid) file on disk.

allow "std/audio" in as Audio

let track = [
  {note: "C4", ms: 500},
  {note: "E4", ms: 500},
  {note: "G4", ms: 1000}
]

Audio.midi("song.mid", track)
show "MIDI file saved to song.mid"

---

---

std/theory — Music Theory Helpers

std/theory removes the manual math from algorithmic composition. It includes helper functions for converting time divisions and chords/scales into Sesi-native note array inputs.

allow "std/theory" in as Music

---

Convert Absolute Time — Music.duration

Music.duration(minutes, seconds) -> number

Convert minutes and seconds to absolute milliseconds.

allow "std/theory" in as Music

let ms = Music.duration(1, 30) // 90000 ms

---

Convert Bars — Music.bar

Music.bar(bars, bpm, beatsPerBar?) -> number

Convert a number of musical bars into milliseconds based on BPM and time signature (default: 4/4).

allow "std/theory" in as Music

let ms = Music.bar(8, 120) // 8 bars at 120bpm -> 16000 ms

---

Generate a Chord — Music.chord

Music.chord(root, type) -> array<string>

Return the notes of a chord rooted at root.

Supported types: "M", "m", "dim", "aug", "7", "M7", "m7", "sus2", "sus4"

allow "std/theory" in as Music

let c_maj7  = Music.chord("C4", "M7")  // ["C4", "E4", "G4", "B4"]
let a_minor = Music.chord("A3", "m")   // ["A3", "C4", "E4"]

show c_maj7

---

Generate a Scale — Music.scale

Music.scale(root, type) -> array<string>

Return all notes of a scale starting at root.

Supported types: "major", "minor", "dorian", "phrygian", "lydian", "mixolydian", "locrian"

allow "std/theory" in as Music

let a_minor = Music.scale("A3", "minor")
show a_minor

---

Transpose Notes — Music.transpose

Music.transpose(notes, semitones) -> array<string>

Shift a note or an array of notes up (positive) or down (negative) by the given number of semitones.

allow "std/theory" in as Music

let c_maj7   = Music.chord("C4", "M7")          // ["C4", "E4", "G4", "B4"]
let f_maj7   = Music.transpose(c_maj7, 5)        // ["F4", "A4", "C5", "E5"]
let b_maj7   = Music.transpose(c_maj7, -1)       // ["B3", "D#4", "F#4", "A#4"]

show f_maj7

---

Combining std/audio and std/theory

allow "std/audio"  in as Audio
allow "std/theory" in as Music

// Build a progression from music theory
let c_chord = Music.chord("C4", "M")    // ["C4", "E4", "G4"]
let g_chord = Music.chord("G3", "M7")   // ["G3", "B3", "D4", "F4"]

// Sequence each chord
fn chord_notes(notes, duration) {
  let out = []
  let i = 0
  while i < len(notes) {
    push(out, {note: notes[i], ms: duration})
    i = i + 1
  }
  return out
}

let c_track = chord_notes(c_chord, 600)
let g_track = chord_notes(g_chord, 600)

Audio.sequence("c_chord.wav", c_track, "sine")
Audio.sequence("g_chord.wav", g_track, "triangle")
show "Chord files saved."

---

Error Handling

Wrap audio I/O in try/catch to guard against missing files or unsupported formats:

allow "std/audio" in as Audio

try {
  Audio.save("output.wav", "C4", 1000, "sine")
  show "Saved successfully"
} catch (err) {
  show "Audio error:" err
}

---

Quick Reference

allow "std/audio"  in as Audio
allow "std/theory" in as Music

// Beep
Audio.beep(440, 200)

// Play a note
Audio.play("A4", 500)

// Synth to base64
let b64 = Audio.synth("C4", 1000, "square")

// Save a single tone
Audio.save("tone.wav", "C4", 1000, "sine", {attack: 30, release: 200})

// Load a WAV file into an audio_sample object
let sample = Audio.load("loop.wav")
show sample.sampleRate

// Drum hits (returns base64 WAV)
let k = Audio.kick(300, 1.0)
let s = Audio.snare(200, 0.8)
let h = Audio.hat(50, 0.6)
write_file("kick.wav", k, "base64")
write_file("snare.wav", s, "base64")
write_file("hat.wav", h, "base64")

// Save a sequence
Audio.sequence("seq.wav", [{note: "C4", ms: 500}, {note: "E4", ms: 500}], "triangle")

// Mix tracks
Audio.mix("mix.wav", [[{note: "C4", ms: 500}], [{note: "C2", ms: 500}]], "sine")

// Export MIDI
Audio.midi("song.mid", [{note: "C4", ms: 500}, {note: "E4", ms: 500}])

// SoundFont instrument
let piano = Audio.sf2("font.sf2", {instrument: 0})
Audio.mix("song.wav", [[piano("C4", 500), piano("G4", 500)]], "sine")

// Music theory
let scale  = Music.scale("C4", "major")
let chord  = Music.chord("A3", "m7")
let moved  = Music.transpose(chord, 7)

---

std/mpc — Akai MPC Track Sequencing & Performance Controls

std/mpc brings authentic Akai MPC (Music Production Center) workflow to Sesi: 16-pad matrix banks, the Roger Linn MPC swing algorithm (50%–75%), 16 Levels performance mode, Note Repeat with triplet divisions, voice Choke Groups, multi-track step sequencing, vintage converter modeling (mpc60, mpc3000, sp1200), AIR multi-mode insert effects, and broadcast-ready WAV and multi-track MIDI file export.

allow "std/mpc" in as MPC

---

Create an MPC Sequence — MPC.sequence

MPC.sequence(name, bars?, bpm?, time_sig?) -> sequence

Creates a sequence container with tracks, tempo, bar count, and swing settings.

allow "std/mpc" in as MPC

let seq = MPC.sequence("Boom Bap Groove", 2, 92, "4/4")
seq.set_swing(58)         // 58% classic Roger Linn MPC swing
seq.set_vintage("mpc60")  // 12-bit punchy vintage converter emulation

---

Setup a 16-Pad Drum Kit — MPC.drum_kit

MPC.drum_kit(nameOrPreset?) -> drum_kit

Creates an Akai MPC 16-pad drum program. Pads support levels, panning, semitone tuning, fine tuning (cents), envelopes, and choke groups.

allow "std/mpc" in as MPC

let kit = MPC.drum_kit("MPC3000 Drums")

// Configure pads
kit.set_pad(1, {sound: "kick", tune: -1, level: 1.0})
kit.set_pad(2, {sound: "rimshot", level: 0.85})
kit.set_pad(3, {sound: "snare", tune: 1, level: 0.95})
kit.set_pad(4, {sound: "clap", level: 0.8})

// Choke group: closed hat (Pad 5) and open hat (Pad 7) share mute_group 1
kit.set_pad(5, {sound: "hat", mute_group: 1, level: 0.75})
kit.set_pad(7, {sound: "open_hat", mute_group: 1, level: 0.85})

// 808 sub bass
kit.set_pad(13, {sound: "808", tune: -2, level: 0.9})

---

Step Sequencing & Track Patterns — track.pattern

Add tracks to a sequence and program rhythmic patterns using step notation:

  • 'x': normal hit (velocity 100)
  • 'X': full accent (velocity 127)
  • '-': ghost note (velocity 45)
  • '1'-'9': stepped velocity from 10% to 90%
  • '.': rest

allow "std/mpc" in as MPC

let seq = MPC.sequence("Dope Beat", 2, 90, "4/4")
let kit = MPC.drum_kit("Boom Bap")

let drums = seq.add_track("Drums", "drum", kit)
drums.pattern(1, "x...x...x...x...") // Kick drum
drums.pattern(3, "....x.......x...") // Snare on beats 2 and 4
drums.pattern(5, "x.x.x.x.x.x.x.x.") // 16th hats with 58% swing
drums.pattern(7, "..x.......x.....") // Open hats (choked by closed hats)

For a more visual grid, use bracketed cells. Each cell uses the selected

resolution; rows are read left-to-right, then top-to-bottom.

For an eight-column grid that reads as 1 & 2 & 3 & 4 &, set

"resolution": "1/8". This is one clear 4/4 bar at 130 BPM, with one kick

on every quarter-note beat:

let four_on_floor = "
[X][ ][x][ ][x][ ][x][ ]
"
let quantization = {resolution: "1/8"}
drums.pattern(1, four_on_floor, quantization)

The four hits map to internal 16th-note steps 0, 4, 8, and 12:

Visual grid: [X] [ ] [x] [ ] [x] [ ] [x] [ ]
Musical time:  1   &   2   &   3   &   4   &
Kick timing:   X       x       x       x

The default "resolution": "1/16" remains useful for a 16-column bar, where

each cell is one sixteenth note.

Visual grid cells use the same trigger notation as compact patterns:

Cell Meaning
[x] Normal hit at the pattern velocity
[X] Full accent, velocity 127
[-] Ghost hit, velocity 45
[1] to [9] Stepped velocity from 10% to 90%
[ ] Empty step / rest

Use start_step to place a visible grid later in a longer arrangement. Since

there are 16 internal steps per 4/4 bar, start_step: 64 begins the grid at

bar 5:

drums.pattern(1, four_on_floor, {
  start_step: 64,
  resolution: "1/8",
  velocity: 112
})

---

16 Levels Mode — MPC.sixteen_levels

MPC.sixteen_levels(pad, mode?, options?) -> array<pad_config>

The official Akai MPC 16 Levels button spreads a pad sound across all 16 pads:

  • "velocity": 16 stepped velocity gradations from 8 to 127
  • "tune": Chromatic tuning across 16 semitones (-12 to +3 semitones or custom base)
  • "filter": Stepped lowpass cutoff frequencies from 200 Hz to 18,000 Hz
  • "decay": Stepped decay times from 30 ms to 1,200 ms
  • "attack": Stepped attack envelope from 0 ms to 500 ms

allow "std/mpc" in as MPC

let kit = MPC.drum_kit("Sample Kit")
let sub = kit.get_pad(13)

// Chromatic bassline across 16 pads
let chromatic_pads = MPC.sixteen_levels(sub, "tune")

---

Note Repeat — MPC.note_repeat

MPC.note_repeat(pad, rate?, bars?, options?) -> array<event>

Quantized rhythmic repeats across "1/4", "1/8", "1/16", "1/32", "1/64", and triplet divisions ("1/4T", "1/8T", "1/16T", "1/32T"), with velocity ramps ("crescendo", "decrescendo") and accents.

allow "std/mpc" in as MPC

// 1 bar of 1/32 hi-hat roll with crescendo
let rolls = MPC.note_repeat(5, "1/32", 1, {ramp: "crescendo", accent: 4})

---

Sample Slicing ("Chop Shop") — MPC.chop

MPC.chop(sampleOrPath, slices?, options?) -> drum_kit

Slices an audio sample or WAV file into equal regions or transients, automatically mapping each slice to Pads 1..16 with voice Choke Group 1 enabled so slices cut each other off.

allow "std/mpc" in as MPC

let chops = MPC.chop("vinyl_sample.wav", 16)
let chop1 = chops.get_pad(1)

---

AIR Multi-Mode Effects & Vintage Converters

Apply insert effects on tracks or master bus:

  • "filter": cutoff, resonance, type ("lowpass", "highpass", "bandpass"), drive
  • "delay": time_ms or sync division ("1/8", "1/16"), feedback, ping_pong, damp, mix
  • "reverb": room decay, damp, mix
  • "compressor": threshold, ratio, attack, release, makeup gain
  • "lofi": bit depth, downsampling rate, drive, mix
  • "eq": low, mid, high dB gain
  • Vintage modeling: "mpc60", "mpc3000", "sp1200"

allow "std/mpc" in as MPC

let seq = MPC.sequence("Filtered Beat", 2, 90, "4/4")
seq.add_effect("compressor", {threshold: -14, ratio: 4, makeup: 2})
seq.add_effect("delay", {sync: "1/8", feedback: 0.35, mix: 0.25, ping_pong: true})

---

Exporting Audio and MIDI

allow "std/mpc" in as MPC

let seq = MPC.sequence("Master Project", 2, 92, "4/4")
// ... configure tracks ...

// Render 16-bit 44.1kHz Stereo WAV
seq.render_wav("beat.wav")

// Export Type 1 Standard MIDI File with Roger Linn swing preserved
seq.render_midi("beat.mid")

// Or render directly in-memory
let sample = seq.render_wav("memory")
show "Rendered sample count:" len(sample.samples)
MPC.play(sample)

---