SESI LANGUAGE SPECIFICATION

Audio in Sesi

Sesi includes two optional standard-library modules for working with sound: std/audio for synthesis and playback, and std/theory for music-theory helpers. Neither requires any external installation — just allow the module and go.

---

Importing the Modules

allow "std/audio" in with 
allow "std/theory" in with 

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

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

The examples below use Audio and Music 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 with 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 with 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 with Audio

let wav_b64 = Audio.synth(440, 1000, "square")
print "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 with 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})

---

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 with 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")
print "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 with 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})
print "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 with 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")
print "Saved full_mix.wav"

---

Drum Synthesis

The "kick", "snare", "hat", and "clap" waveform types use native physical modeling, so no SoundFont is required for basic drum tracks:

allow "std/audio" in with Audio

let kick  = {"note": "C1", "ms": 500, "type": "kick"}
let snare = {"note": "C4", "ms": 500, "type": "snare"}
let hat   = {"note": "G8", "ms": 250, "type": "hat", "pan": 0.3}

Audio.sequence("drums.wav", [kick, snare, hat], "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 with Audio

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

Audio.midi("song.mid", track)
print "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 with Music

---

Convert Absolute Time — Music.duration

Music.duration(minutes, seconds) -> number

Convert minutes and seconds to absolute milliseconds.

allow "std/theory" in with 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 with Music

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

---

Generate a Chord — Music.chord

Music.chord(root, type) -> array

Return the notes of a chord rooted at root.

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

allow "std/theory" in with Music

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

print c_maj7

---

Generate a Scale — Music.scale

Music.scale(root, type) -> array

Return all notes of a scale starting at root.

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

allow "std/theory" in with Music

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

---

Transpose Notes — Music.transpose

Music.transpose(notes, semitones) -> array

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

allow "std/theory" in with 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"]

print f_maj7

---

Combining std/audio and std/theory

allow "std/audio"  in with Audio
allow "std/theory" in with 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")
print "Chord files saved."

---

Error Handling

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

allow "std/audio" in with Audio

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

---

Quick Reference

allow "std/audio"  in with Audio
allow "std/theory" in with 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})

// 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)

---