Sanic Boom

Language Basics

Core syntax and concepts

Variables

Use let to define variables:

let bass = [C2 _ C2 _]
let melody = [E4 G4 A4 G4]

Tempo

Set the tempo in BPM:

tempo 120
tempo 90

Time Signatures

Set the time signature after tempo with a comma:

tempo 120              // Default 4/4
tempo 120, 7/8         // 120 BPM in 7/8 time
tempo 90, 5/4          // 90 BPM in 5/4 time

Time signature affects the default pattern duration (one bar).

Notes

Notes use scientific pitch notation: note name + octave number.

C4      // Middle C
Eb4     // E flat
F#5     // F sharp

Microtonal Pitches

Add + or - followed by a number (in cents) after a note to shift its pitch by fractions of a semitone. 100 cents = 1 semitone.

C4+50     // Quarter tone above C4
Eb3-15    // Eb3 lowered 15 cents
A4+50.5   // Fractional cents for fine tuning
D#5-50    // Quarter tone below E5

This enables microtonal scales, quarter-tone melodies, and just-intonation approximations.

Frequency Literals

Use hz values directly in patterns to play an exact frequency:

[432hz 440hz 448hz]   // A4 in different tunings
[267.5hz 300hz]       // Arbitrary frequencies

Rests

Use _ for rests (silence):

[C4 _ E4 _]  // Note, rest, note, rest

Per-Note Probability

Add ? after a note to give it a chance of being replaced with silence:

[C4? E4 G4?]           // C4 and G4 have 50% chance (default)
[C4?0.3 E4 G4?0.7]     // C4 has 30%, G4 has 70% chance
[_?0.5]                // Rest with 50% chance
[Cm7?0.5]              // Chord with probability

The probability is deterministic per cycle - the same notes will play/skip on each loop.

Per-Note Velocity

Add @ followed by a value (0.0-1.0) to set note velocity:

[C4@0.8 E4@0.5 G4@1.0] // Explicit velocities
[C4@0.3 E4 G4]         // E4 and G4 use default (1.0)
[Cm7@0.6]              // Chord with velocity

Combined Modifiers

Modifiers can be combined in any order after the note weight. Cent offsets are part of the note itself:

[C4:2?0.5@0.8 E4 G4]          // Double duration, 50% chance, 80% velocity
[C4?0.5@0.8 E4@0.6?0.3 G4]   // Order of ? and @ is flexible
[C4+50:2@0.8 Eb3-15?0.5]      // Microtonal with modifiers

Units

Sanic supports unit literals for time and frequency values. Units are attached directly to numbers.

Time Units

0.5s          // 0.5 seconds
500ms         // 500 milliseconds (= 0.5s)
1b            // 1 beat (tempo-relative)
1/4           // Quarter note (1 beat)
1/8           // Eighth note (0.5 beats)
3/16          // Dotted eighth (0.75 beats)

Frequency Units

2hz           // 2 cycles per second
0.5hz         // 1 cycle every 2 seconds

Tempo-Aware Conversion

Beat-based units automatically convert based on the current tempo:

tempo 120

// At 120 BPM:
// - 1 beat = 0.5 seconds
// - 1/4 (quarter note) = 0.5 seconds
// - 1/8 (eighth note) = 0.25 seconds

|> delay(1/4)           // 0.5 second delay at 120 BPM
|> delay(1/8)           // 0.25 second delay at 120 BPM
|> lfo(rate: 1/4)       // 2 Hz LFO at 120 BPM (one cycle per beat)

If you change the tempo, beat-based values automatically adjust:

tempo 60                // Half as fast
|> delay(1/4)           // Now 1 second delay (1 beat at 60 BPM)

Patterns

Patterns are sequences of notes in square brackets:

[C4 E4 G4]           // Three notes
[C4 _ E4 _ G4 _]     // Notes with rests
[C4 E4 | G4 B4]      // Bar lines (visual only)

Pipe Operator

Chain operations with |>:

let sound = [C4 E4 G4]
    |> saw
    |> reverb(0.5)
    |> delay(0.25)

Each step flows into the next: pattern → transforms → instrument → envelope → effects.

Pipe order matters:

StageWhatExamples
1. PatternNotes to play[C4 E4 G4], Cm7
2. TransformsModify the patternreverse, transpose, every, arp
3. InstrumentHow it soundssaw, sine, supersaw, fm
4. EnvelopeShape over timeadsr
5. EffectsProcess the soundlowpass, reverb, delay

Comments

// This is a comment
let x = 42  // Inline comment

Imports

Import from other files or packages:

import "github:SanicBoom/drums"
from "./bass.sanic" import { bassline }
from "github:SanicBoom/some-lib" import { kick }

GitHub libraries can expose .sanic modules — see Sample Libraries for details.

Play

Use play to output sound:

play melody
play stack(drums, bass, melody)

Sections (Arrangement)

Use section blocks to sequence parts over time. Each section plays for a fixed number of bars, then the next section starts. After all sections finish, the arrangement loops.

section "intro" 8
    play [C4 E4 G4] |> sine |> reverb(0.8)

section "drop" 8
    play [C2 C2 C2 C2] |> saw |> lowpass(800)
    play [C4 E4 G4 B4] |> supersaw

Sections play sequentially: "intro" for 8 bars, then "drop" for 8 bars, then back to "intro".

Crossfading Sections

Add fade N after the bar count to fade in over the first N bars and fade out over the last N bars. This creates smooth crossfades between adjacent sections:

section "intro" 8 fade 2
    play [C4 E4 G4] |> sine |> reverb(0.8)

section "drop" 8 fade 2
    play [C2 C2 C2 C2] |> saw |> lowpass(800)

The fade linearly scales note velocity from 0 to full over the fade-in region and back to 0 over the fade-out region. When two sections both have fades, their overlapping fade-out/fade-in creates a crossfade.

Mixing Sections and Free-Standing Play

play statements outside of sections loop forever, unaffected by the arrangement:

play [C2 _ C2 _] |> sine   // loops forever (no section)

section "verse" 16
    play [E4 G4 A4 G4] |> saw

section "chorus" 16
    play [C4 E4 G4 C5] |> supersaw

The bass loops continuously while verse and chorus alternate every 16 bars.

User-Defined Functions

Define your own functions with fn:

fn name(params) = body

Basic Functions

fn octaveUp(p) = p |> transpose(12)
fn darkSaw(p) = p |> saw |> lowpass(400)

play [C4 E4 G4] |> octaveUp |> saw
play [C4 E4 G4] |> darkSaw

Functions with Multiple Parameters

fn transposedArp(p, semitones) = p |> transpose(semitones) |> arp(up)

play Cm7 |> transposedArp(5) |> saw

Building Complex Transforms

fn wobble(p) = p |> slow(2) |> every(2, fast(2))
fn dreamy(p) = p |> reverb(size: 0.8) |> delay(time: 0.3)

play [C4 E4 G4 B4] |> wobble |> saw |> dreamy

Functions as Transforms

User-defined functions work with every and probability functions:

fn myTransform(p) = p |> reverse |> transpose(12)

play [C4 D4 E4 F4] |> every(4, myTransform) |> saw
play [C4 D4 E4 F4] |> sometimes(myTransform) |> saw

On this page