Overview
Each example teaches one concept. They build on each other — by the final section you will have seen the full feature set that makes Sigil different: effects verified at compile time, linear ownership without a borrow checker, source-to-source autodiff, and a type system that refuses to compile bugs that other languages let through at runtime.
Sigil has two parseable surfaces. These examples use the Rust-hybrid surface —
familiar keywords, good for learning. Both surfaces parse to the same HIR; a formatter round-trips
between them losslessly. The effect row sits after / in the function signature:
fn f(x: T) -> R / {Effect1, Effect2} { ... }. A pure function omits the / entirely.
- Language reference: cssl.dev/sigil
- Specification: github.com/Apocky/CSSL3 — 25
.cslspec files - CSL notation version of this page: cssl.dev/csl/examples.csl
- Machine-readable text: cssl.dev/examples.txt
01 · Basics
Module declarations, let bindings, primitive types, vector types, and pure functions. A Sigil source file begins with a module declaration; everything else follows from there.
01-A · Hello World
Module, IO, and a pure string
The simplest Sigil program. main requires the {IO} effect to write to stdout —
the compiler will reject a call to print from a function that does not declare it.
The / {IO} syntax is the effect row.
module com.apocky.examples.hello fn greet(name : str) -> str { // Pure function: no effect row needed. str::concat("hello, ", name) } fn main() -> () / {IO} { let msg = greet("Sigil") print(msg) // print requires {IO} — compiler enforced }
01-B · Primitive Types and Vectors
Scalars, vectors, and dimensional units
Primitive types: i32 i64 u32 u64 f32 f64 bool str. Vector types
(vec2 vec3 vec4 mat4) are compiler built-ins, not structs.
Dimensional suffixes like f32'pos (refined to positive reals) are part of the type,
not annotations — mixing them is a compile error caught during type checking.
module com.apocky.examples.types fn demo_types() -> () / {IO} { let count : u32 = 42 let ratio : f64 = 3.14159265358979 let ok : bool = true let label : str = "density = sovereignty" // Refined type: compiler guarantees radius > 0 at every use site. let radius : f32'pos = 0.5 // Vector built-ins: swizzle, arithmetic, length all work out of the box. let origin : vec3 = vec3(0.0, 0.0, 0.0) let point : vec3 = vec3(1.0, 2.0, 3.0) let dist : f32 = length(point - origin) // = sqrt(14) print_f32(dist) } // Struct with annotated memory layout (F2 — layout refinement). @layout(std430) struct Vertex { pos : vec3, // 12 bytes, std430 aligned normal : vec3, // 12 bytes uv : vec2, // 8 bytes }
f32'pos refinement suffix · vec3 built-in · @layout struct annotation · type inference01-C · Generics and Traits
Row-polymorphic functions
Sigil supports ML-style parametric polymorphism plus type-class style traits.
The effect row is itself polymorphic — μ is the tail variable that lets callers
extend a function's effects without changing its signature.
trait Blend { fn lerp(self, other : Self, t : f32) -> Self } impl Blend for f32 { fn lerp(self, other : f32, t : f32) -> f32 { self + (other - self) * t } } impl Blend for vec3 { fn lerp(self, other : vec3, t : f32) -> vec3 { self + (other - self) * vec3(t, t, t) } } // Effect-polymorphic: μ is the inferred tail. // Any effect the body needs is propagated to callers automatically. fn blend_pair<T : Blend>(a : T, b : T, t : f32) -> T { a.lerp(b, t) }
02 · Memory & Capabilities
CSSL uses Pony-6 reference capabilities instead of a borrow checker.
Six keywords — iso trn ref val box tag — control aliasing and mutability.
The compiler statically tracks ownership transitions; there is no runtime overhead.
02-A · GPU Buffer Lifecycle
iso → write → val: build, fill, freeze
A GPU buffer starts as iso (exclusively owned, writable).
After uploading vertex data, it is consumed and returned as val (immutable-shared),
safe for concurrent reads from multiple shader stages.
The capability transition is tracked in the type — not at runtime.
module com.apocky.examples.gpu_buffer // iso = exclusively owned, linear-mutable. // Caller cannot alias this — moving it is the only option. fn alloc_vertex_buffer(capacity : u64) -> iso GpuBuffer / {GPU, NoRecurse} { GpuBuffer::alloc(capacity) } // Consumes iso, uploads verts, returns val (immutable-shared). // box [Vertex] = read-only view; no ownership transfer. fn upload_mesh( buf : iso GpuBuffer, verts : box [Vertex], ) -> val GpuBuffer / {GPU} { buf.write(verts) // mutable write, iso required buf.freeze() // iso → val: now safe to share } // val GpuBuffer can be read by multiple callers simultaneously. fn bind_and_draw( buf : val GpuBuffer, shader : val ShaderPipeline, ) -> () / {GPU, Deadline<16ms>} { shader.bind(buf) shader.draw_indexed(0, buf.vertex_count()) }
iso exclusive ownership · val immutable-shared · box read-only view · {GPU} and {Deadline} effects · linear consumption02-B · trn: Transition Capability
Build phase → immutable scene
trn is write-unique: only one writable reference exists, but read-only (box)
aliases are permitted. Once construction is complete, trn transitions to val.
This models the classic "builder → frozen object" pattern with static safety.
// trn = write-unique: one writer, many readers allowed. fn build_scene() -> val Scene / {Alloc} { let s : trn Scene = Scene::new() // box aliasing is fine during build: let aabb = compute_aabb(s as box) log_bounds(aabb) // does not consume s s.add_mesh(load_mesh("terrain.cssl_mesh")) s.add_light(DirectionalLight { dir: vec3(0.0, -1.0, -0.5), intensity: 1.0 }) s.freeze() // trn → val: builder pattern complete } // val Scene: freely shared across threads, render passes, frames. fn render_pass(scene : val Scene, cam : Camera) -> Image / {GPU, Deadline<16ms>, NoAlloc} { rasterize(scene, cam) }
trn transition cap · box aliasing during build · .freeze() seal · capability promotion to val02-C · Entity Handles
Generational references — stale handle = compile guard
Handle<T> is an opaque tag-capability generational reference packed into a single
u64. The generation counter prevents use-after-free. The compiler refuses direct
dereference of a tag — you must go through World::get which checks liveness.
// Handle= tag + generation packed into u64. // Solves the stale-entity-reference bug from the Labyrinth of Apocalypse retrospective. fn spawn_enemy(world : ref World, pos : vec3) -> Handle<Enemy> / {State<World>} { world.alloc(Enemy { pos, hp: 100 }) // returns tag-cap Handle } fn damage_enemy( world : ref World, handle : Handle<Enemy>, amount : u32, ) -> Option<()> / {State<World>} { // World::get checks the generation; returns None if entity was freed. let enemy = world.get<Enemy>(handle)? enemy.hp -= amount.min(enemy.hp) Some(()) }
Handle<T> generational ref · tag cap (opaque, no deref) · ref shared-mutable · {State<S>} effect · ? propagation03 · Effect System
Sigil's effect system is row-polymorphic (Koka lineage). A function declares its observable side-effects in the signature; the compiler unions them up the call stack. Effects compile to evidence-passing — zero runtime overhead. Handlers let you define the semantics of user-defined effects.
03-A · Declaring and Using Effects
Custom effect: Logger
effect declares a set of operations. Functions that call those operations must declare
the effect in their row. At the call site you provide a handle ... with block that
gives the concrete implementation — separating the what from the how.
module com.apocky.examples.logger // Declare the effect interface. effect Log { fn emit(level : LogLevel, msg : str) -> () fn span(name : str) -> SpanId } // This function uses Log but doesn't define it. fn load_asset(path : str) -> Asset / {Log, IO} { let span = Log::span("load_asset") Log::emit(LogLevel::Debug, str::concat("loading: ", path)) let data = read_file(path)? Log::emit(LogLevel::Debug, "loaded ok") Asset::parse(data) } // Provide the Log handler: writes to stdout. fn main() -> () / {IO} { handle load_asset("player.mesh") with { Log::emit(level, msg) => { write_stdout(str::format("[{}] {}", level, msg)) resume(()) } Log::span(name) => { resume(SpanId::new(name)) } } }
effect declaration · effect rows propagated up the call stack · handle ... with algebraic handler · resume continuation03-B · Resource and Timing Effects
NoAlloc + Deadline: audio-safe function
{NoAlloc} forbids heap allocation anywhere in the call tree — the compiler walks every
callee and rejects if any path reaches an allocator.
{Deadline<1ms>} adds a cost-model check at compile time and a wallclock guard at runtime.
Together they express audio-callback safety without a separate linting pass.
// Audio callback: must complete in under 1ms, no heap allocation. // The compiler refuses to compile this if any callee allocates. fn audio_tick( buf : iso AudioBuffer, // exclusive ownership of the output buffer freq : f32'pos, // frequency > 0 — refinement enforced sr : f32'pos, // sample rate > 0 ) -> iso AudioBuffer / {NoAlloc, Deadline<1ms>, DetRNG} { let step = 2.0 * PI * freq / sr let n = buf.len() // Stack-allocated phase state; no heap involved. let mut phase : f32 = 0.0 for i in 0..n { buf[i] = sin(phase) * 0.5 phase += step } phase = phase % (2.0 * PI) buf } // Calling a heap-allocating function from audio_tick is a type error: // fn bad_tick(...) -> ... / {NoAlloc} { // let v = Vec::new() // error: {Alloc} ∉ {NoAlloc} context // }
{NoAlloc} compile-time enforcement · {Deadline<N>} latency gate · {DetRNG} determinism · iso buffer ownership · refinement types03-C · IFC: Information-Flow Effects
Sensitive data — declassification required
{Sensitive<dom>} tags data with a confidentiality domain. The compiler threads
this label through every SSA value and refuses to let sensitive data escape without an explicit
declassify operation carrying a {Privilege<lvl>} gate.
// player_id is sensitive: not allowed to leak to GPU telemetry. fn compute_score( player_id : u64, raw_score : i32, ) -> i32 / {Sensitive<PlayerData>} { let bonus = lookup_bonus(player_id) // tainted: Sensitive<PlayerData> raw_score + bonus } // Only a Privilegefunction may declassify the result. fn broadcast_score( player_id : u64, raw_score : i32, ) -> () / {Privilege<GameServer>, IO} { let s = compute_score(player_id, raw_score) // declassify is an explicit privilege exercise — auditable. let pub_score = declassify<PlayerData, GameServer>(s) network::broadcast(pub_score) }
{Sensitive<dom>} taint propagation · {Privilege<lvl>} gate · declassify explicit escape hatch04 · Manifold & SDF Geometry
Signed Distance Functions (SDFs) and manifold operations are compiler-aware primitives in Sigil,
not library wrappers. The SDF'L<k≤1> refinement type tracks the Lipschitz constant
at compile time. IFS (Iterated Function System) rules compose into fractal geometry without
special syntax.
04-A · SDF Algebra
Union, intersection, smooth blend
SDF combinators obey known Lipschitz bounds. The compiler tracks these through
f32'L<k> refinements and rejects combinations that would violate the bound
required by the ray marcher. smin (smooth minimum) introduces a blend
radius; the compiler widens the Lipschitz estimate accordingly.
@differentiable @lipschitz(k = 1.0) fn sphere_sdf(p : vec3, r : f32'pos) -> f32 { length(p) - r } @differentiable @lipschitz(k = 1.0) fn box_sdf(p : vec3, half : vec3) -> f32 { let q = abs(p) - half length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) } // SDF union: min of two Lipschitz-1 SDFs is still Lipschitz-1. @differentiable fn scene_sdf(p : vec3) -> f32 { let d_sphere = sphere_sdf(p, 0.5) let d_box = box_sdf(p - vec3(1.2, 0.0, 0.0), vec3(0.3, 0.3, 0.3)) min(d_sphere, d_box) // union combinator } // smin: smooth union with blend radius k. // Lipschitz bound widens to 1 + k/blend; compiler checks ray-march steps. @differentiable fn melted_scene(p : vec3) -> f32 { let k = 0.1 let d1 = sphere_sdf(p, 0.5) let d2 = sphere_sdf(p - vec3(0.8, 0.0, 0.0), 0.4) smin(d1, d2, k) // smooth minimum — organic blend } // Normal via autodiff. The compiler inserts the reverse-mode AD pass. fn surface_normal(hit : vec3) -> vec3 { normalize(bwd_diff(scene_sdf)(hit).d_p) }
@differentiable · @lipschitz refinement tracking · SDF union/smin combinators · bwd_diff surface-normal derivation04-B · IFS: Iterated Function System
Fractal geometry via chaos game
An IFS is a set of affine contractions whose attractor forms a fractal. The chaos game
iteratively applies a randomly-chosen contraction — with {DetRNG} ensuring bit-exact
replay across machines. {NoAlloc} keeps it audio/GPU safe.
struct IFSRule { scale : f32'pos, // contraction factor in (0, 1) offset : vec2, rotate : f32, // rotation angle, radians } // Chaos game: iterate IFS rules to sample the attractor. // DetRNG: bit-exact cross-machine reproducibility. fn ifs_chaos_game( rules : box [IFSRule], iters : u32, seed : vec2, out : iso [vec2], ) -> iso [vec2] / {DetRNG, NoAlloc} { let mut p = seed let n = rules.len() as f32 for i in 0..iters { // DetRNG: seeded RNG, same sequence on every machine. let r = (DetRNG::next_f32() * n) as u32 let rul = rules[r % rules.len()] let cos_a = cos(rul.rotate) let sin_a = sin(rul.rotate) p = vec2( rul.scale * (cos_a * p.x - sin_a * p.y) + rul.offset.x, rul.scale * (sin_a * p.x + cos_a * p.y) + rul.offset.y, ) out[i] = p } out }
{DetRNG} deterministic RNG · {NoAlloc} + iso output buffer · fractal attractor iteration pattern05 · Autodiff (F1)
Source-to-source automatic differentiation is a non-negotiable feature (F1). The Slang.D
interface — @differentiable, fwd_diff, bwd_diff,
IDifferentiable — is lowered by source-to-source transformation on the structured MIR.
No LLVM-Enzyme; no runtime AD library. Zero overhead on non-differentiable paths.
05-A · Forward-Mode AD
fwd_diff: derivative of a scalar function
fwd_diff(f) returns a function that takes a dual number (x, dx) and
returns (f(x), f'(x)·dx). This is forward-mode (Jacobian-vector product).
The transformed function is emitted by the compiler — no closures, no tape.
@differentiable fn sigmoid(x : f32) -> f32 { 1.0 / (1.0 + exp(-x)) } @differentiable fn activation(x : f32, w : f32, b : f32) -> f32 { sigmoid(w * x + b) } fn demo_fwd() -> () / {IO} { // fwd_diff returns a function that computes (value, d/dx) simultaneously. let x = 1.5 let dx = 1.0 // tangent seed let result = fwd_diff(activation)(x, dx, w=2.0, b=-0.5) print_f32(result.val) // σ(2·1.5 - 0.5) = σ(2.5) print_f32(result.d_x) // ∂σ/∂x at x=1.5 }
@differentiable annotation · fwd_diff forward-mode JVP · dual-number result .val and .d_x · compiler-emitted AD transform05-B · Reverse-Mode AD
bwd_diff: gradient of a loss over many parameters
bwd_diff(f) returns the transpose of the Jacobian — a vector-Jacobian product.
This is reverse-mode AD, the basis of backpropagation. The compiler emits a reversed
computation graph by source-to-source transformation on the structured MIR, before SPIR-V emission.
@differentiable fn linear_layer( x : box [f32], w : box [[f32]], b : box [f32], n_in : u32, n_out : u32, out : iso [f32], ) -> iso [f32] / {NoAlloc} { for j in 0..n_out { let mut acc = b[j] for i in 0..n_in { acc += w[j][i] * x[i] } out[j] = acc } out } @differentiable fn mse_loss(pred : box [f32], target : box [f32], n : u32) -> f32 { let mut acc = 0.0 for i in 0..n { let diff = pred[i] - target[i] acc += diff * diff } acc / (n as f32) } fn backprop_step( x : box [f32], w : ref [[f32]], b : ref [f32], target : box [f32], lr : f32'pos, n_in : u32, n_out : u32, ) -> f32 / {NoAlloc, State<[[f32]]>} { // bwd_diff emits the VJP; .d_w and .d_b are the weight/bias gradients. let scratch = alloc_stack([f32; n_out]) let pred = linear_layer(x, w, b, n_in, n_out, scratch) let loss_fn = |p| mse_loss(p, target, n_out) let grads = bwd_diff(loss_fn)(pred) // SGD update step. for j in 0..n_out { for i in 0..n_in { w[j][i] -= lr * grads.d_w[j][i] } b[j] -= lr * grads.d_b[j] } mse_loss(pred, target, n_out) }
bwd_diff reverse-mode VJP · gradient struct .d_w / .d_b · SGD update · {NoAlloc} + stack allocation · {State<S>} mutable weight update05-C · Jet: Higher-Order AD
Jet<T,N>: up to N-th order derivatives
Jet<T,N> carries the value and its first N derivatives simultaneously —
useful for curvature (N=2) in physics simulations and Hessian approximations.
The Jet type is a compiler built-in; no special syntax needed to compute with it.
// Jet: value + 1st + 2nd derivative. fn potential_energy(r : Jet<f32, 2>) -> Jet<f32, 2> { // Lennard-Jones potential: V(r) = ε[(σ/r)¹² - 2(σ/r)⁶] let eps = jet_const(1.0) let sigma = jet_const(1.0) let s6 = (sigma / r).powi(6) eps * (s6 * s6 - 2.0 * s6) } fn force_and_curvature(r : f32'pos) -> (f32, f32) { // Seed with r + 1st tangent 1.0, 2nd tangent 0.0. let r_jet = Jet::seed(r, 1.0, 0.0) let v_jet = potential_energy(r_jet) // v_jet.d[0] = -∂V/∂r (force), v_jet.d[1] = ∂²V/∂r² (curvature). (-v_jet.d[0], v_jet.d[1]) }
Jet<T,N> higher-order AD · Jet::seed tangent seeding · multi-order derivative access via .d[k] · physics simulation pattern06 · Quantum-Inspired & DCU Operations
Sigil's determinism and numerical precision guarantees make it a natural substrate for
quantum-inspired classical computation and DCU (Digital Cognitive Unit) synchronization.
These examples use {PureDet} for bit-exact cross-machine reproducibility and
{NoAlloc} to keep operations bounded for real-time use.
06-A · Krylov Subspace Evolution
Sparse Hamiltonian: |ψ'⟩ = exp(−iHt)|ψ⟩
Time evolution of a quantum state under a sparse Hamiltonian via Arnoldi iteration —
a classical simulation technique used in quantum chemistry and quantum-inspired optimization.
{PureDet} guarantees the same floating-point result on every machine, enabling
deterministic simulation replay.
struct SparseH { row_ptr : box [u32], // CSR format col_idx : box [u32], vals : box [f32], // real-valued Hamiltonian elements dim : u32, } // Sparse matrix-vector product H|ψ⟩. fn sparse_matvec( H : box SparseH, psi : box [f32], out : iso [f32], ) -> iso [f32] / {NoAlloc} { for i in 0..H.dim { let mut acc = 0.0 for j in H.row_ptr[i]..H.row_ptr[i+1] { acc += H.vals[j] * psi[H.col_idx[j]] } out[i] = acc } out } // k-step Arnoldi: build orthonormal Krylov basis V_k. // PureDet: bit-exact across architectures — replay-safe simulation. fn krylov_evolve( H : box SparseH, psi0 : box [f32], // initial state vector, length H.dim t : f32'pos, // evolution time k : u32, // Krylov dimension (k ≪ H.dim) out : iso [f32], ) -> iso [f32] / {PureDet, NoAlloc, Deadline<1ms>} { // V[0..k]: orthonormal Krylov basis vectors (stack-allocated). let V = alloc_stack([[f32; H.dim]; k]) let h = alloc_stack([[f32; k ]; k]) // Hessenberg matrix let tmp = alloc_stack([f32; H.dim]) V[0] = normalized(psi0) for j in 0..k-1 { // w = H · V[j] let w = sparse_matvec(H, V[j], tmp) // Modified Gram-Schmidt orthogonalization. for i in 0..=j { h[i][j] = dot(w, V[i]) w -= h[i][j] * V[i] } h[j+1][j] = norm(w) V[j+1] = w / h[j+1][j] } // exp(-i·h·t) via Padé approximant; project back: out = V · exp_h · e₀. let exp_h = pade_expm_real(h, t, k) matvec_t(V, exp_h[0..k], out, k, H.dim) }
{PureDet} bit-exact replay · {NoAlloc} + stack-allocated workspace06-B · DCU Kuramoto Synchronization
Phase-lock detection via order parameter
The Kuramoto model describes synchronization of coupled oscillators — here applied to
DCU (Digital Cognitive Unit) agents synchronizing their internal phase.
The order parameter r ∈ [0,1] measures coherence: r≈1 is phase-locked,
r≈0 is incoherent. {PureDet} ensures the entire simulation is
deterministically replayable.
struct DCUOscillator { phi : f32, // phase ∈ [0, 2π) omega : f32'pos, // natural frequency, rad/s kappa : f32'pos, // coupling strength } // Single Euler step: dφ/dt = ω + (κ/N)·Σ sin(φⱼ - φᵢ) fn kuramoto_step( agents : ref [DCUOscillator], idx : u32, dt : f32'pos, ) -> () / {State<[DCUOscillator]>, PureDet} { let n = agents.len() as f32 let osc = agents[idx] let mut coupling_sum = 0.0 for j in 0..agents.len() { if j != idx { coupling_sum += sin(agents[j].phi - osc.phi) } } agents[idx].phi += dt * (osc.omega + (osc.kappa / n) * coupling_sum) agents[idx].phi = agents[idx].phi % (2.0 * PI) } // Order parameter r = |Σ exp(iφⱼ)| / N — measures phase coherence. // r ≈ 1.0: phase-locked (consensus). r ≈ 0.0: incoherent (independence). fn order_parameter(agents : box [DCUOscillator]) -> f32 / {PureDet} { let n = agents.len() as f32 let (mut re, mut im) = (0.0, 0.0) for osc in agents { re += cos(osc.phi) im += sin(osc.phi) } sqrt(re * re + im * im) / n } // Simulate until phase-lock (r > threshold) or max_steps reached. fn run_until_sync( agents : ref [DCUOscillator], dt : f32'pos, threshold : f32'pos, // e.g. 0.95 for near-perfect sync max_steps : u32, ) -> u32 / {State<[DCUOscillator]>, PureDet} { for step in 0..max_steps { for i in 0..agents.len() { kuramoto_step(agents, i, dt) } if order_parameter(agents) > threshold { return step } } max_steps }
{State<S>} mutable agent array · {PureDet} deterministic simulation07 · Complete Program
A self-contained additive synthesizer tying together capabilities, effects, refinement types,
and algebraic handlers. The audio callback is hard-real-time safe: {NoAlloc} forbids
all heap allocation, {Deadline<1ms>} bounds latency, {DetRNG} keeps the
vibrato LFO reproducible, and {Telemetry} captures per-callback timing into the
signed audit ring without any user-visible code.
07 · Additive Synth
A complete mini-program: all features in one
This program defines a multi-voice additive synthesizer: each voice is a SineVoice
with frequency, phase, amplitude, and a vibrato LFO. The fill_buffer callback is
safe for audio threads. A Log effect handler captures telemetry through the
application layer without changing the audio callback's signature.
module com.apocky.examples.synth use std::math::{sin, cos, PI} use std::audio::{AudioBuffer, AudioConfig} // F2: Refined types. Amplitude ∈ [0,1]. Frequency > 0. Phase ∈ [0,2π). type Amplitude = f32 where v >= 0.0 && v <= 1.0 type Phase = f32 where v >= 0.0 && v < 2.0 * PI struct SineVoice { freq : f32'pos, // fundamental Hz phase : Phase, // current phase (refined) amp : Amplitude, // loudness (refined) lfo_hz : f32'pos, // vibrato rate Hz lfo_dep : f32'pos, // vibrato depth, semitones lfo_phi : Phase, // LFO phase } // F3: Effect declaration — audio thread uses this to report metrics. effect AudioMetrics { fn report_cpu_us(micros : u32) -> () } // F1: @differentiable — vibrato depth can be auto-differentiated for // inverse synthesis (finding parameters that match a target spectrum). @differentiable fn vibrato_freq(base : f32'pos, depth : f32'pos, phi : f32) -> f32'pos { // depth in semitones → multiplicative pitch deviation. base * exp2(depth * sin(phi) / 12.0) } // F3: NoAlloc + Deadline — hard real-time audio callback. // F6: Telemetry — CPU time emitted into signed audit ring. fn fill_buffer( voices : ref [SineVoice], cfg : box AudioConfig, buf : iso AudioBuffer, ) -> iso AudioBuffer / {NoAlloc, Deadline<1ms>, DetRNG, AudioMetrics, Telemetry<AudioCPU>} { let sr = cfg.sample_rate as f32 let n = buf.frames() let nv = voices.len() // Zero the mix buffer (stack allocation only). let mix = alloc_stack([f32; n]) for v_idx in 0..nv { let v = &voices[v_idx] let step = 2.0 * PI * v.freq / sr let lfo = 2.0 * PI * v.lfo_hz / sr let mut phi = v.phase let mut lfo_phi = v.lfo_phi for i in 0..n { let mod_freq = vibrato_freq(v.freq, v.lfo_dep, lfo_phi) let sample = v.amp * sin(phi) mix[i] += sample phi = (phi + 2.0 * PI * mod_freq / sr) % (2.0 * PI) lfo_phi = (lfo_phi + lfo) % (2.0 * PI) } // Write back updated phases (ref cap permits mutation). voices[v_idx].phase = phi voices[v_idx].lfo_phi = lfo_phi } // Normalize and write to output buffer. let scale = 1.0 / (nv as f32).max(1.0) for i in 0..n { buf.write(i, mix[i] * scale) } buf } fn main() -> () / {IO} { // Build three voices: A4, E5, A5 (major chord). let mut voices : ref [SineVoice] = [ SineVoice { freq: 440.0, phase: 0.0, amp: 0.6, lfo_hz: 5.0, lfo_dep: 0.15, lfo_phi: 0.0 }, SineVoice { freq: 659.2, phase: 0.0, amp: 0.5, lfo_hz: 5.2, lfo_dep: 0.10, lfo_phi: 0.5 }, SineVoice { freq: 880.0, phase: 0.0, amp: 0.4, lfo_hz: 4.8, lfo_dep: 0.12, lfo_phi: 1.0 }, ] let cfg = AudioConfig { sample_rate: 48000, frames: 256 } // handle wires the AudioMetrics effect to stdout. let buf = AudioBuffer::alloc_stack(cfg.frames) handle fill_buffer(voices, cfg, buf) with { AudioMetrics::report_cpu_us(us) => { print_u32(us) resume(()) } } }
@differentiable on vibrato function ·
F2 refined types Amplitude / Phase / f32'pos ·
F3 {NoAlloc} + {Deadline<1ms>} + {DetRNG} audio safety ·
F3 AudioMetrics custom effect + handler ·
F6 {Telemetry<AudioCPU>} ·
iso/ref/box caps in one function
Next Steps
- Sigil language reference — full feature documentation including all 28+ effects, the six capabilities, and the compilation pipeline
- Effect system deep-dive — all built-in effects and their discharge timing
- The Non-Negotiable Six (F1–F6) — the features CSSL commits to in v1
- CSL specification notation — the 74-glyph notation used throughout the CSSL spec files
- CSSL3 repository — compiler source, spec files,
examples/directory - CSL-encoded version of this page — the examples in dense notation