dreamstack/examples/step-sequencer.ds
enzotar bd926b9e0a feat: keyboard input, Web Audio synthesis, and multiplayer demo
Compiler changes:
- emit_handler: DOM events (keydown/keyup/etc) route to document.addEventListener
- emit_handler: handler params (ev) pushed into scope to prevent .value suffix
- play_tone(freq, dur, type) and play_noise(dur, vol) builtins
- Web Audio runtime: _playTone (oscillator) and _playNoise (filtered noise)
- Reactive textContent: signal-dependent If expressions wrapped in DS.effect

Example updates:
- game-pong.ds: Arrow Up/Down for paddle, Space for pause/resume, paddle-hit sounds
- step-sequencer.ds: 4 audio trigger timers (kick=60Hz, snare=noise, hihat=noise, bass=110Hz)

Verification:
- All 136 tests pass, 45 examples compile
- Relay connects successfully, multiplayer sync confirmed
- Keyboard controls and pause toggle verified in browser
2026-02-27 09:34:20 -08:00

116 lines
5 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- DreamStack Step Sequencer
-- Collaborative beat grid: two tabs, same pads, real-time sync
--
-- Run:
-- Tab 1: cargo run -p ds-stream (relay)
-- Tab 2: dreamstack dev examples/step-sequencer.ds (player 1)
-- Tab 3: dreamstack dev examples/step-sequencer.ds --port 3001 (player 2)
let bpm = 120
let step = 0
let playing = 1
-- 4 instruments × 16 steps = 64 pads
let kick = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let snare = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let hihat = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let bass = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
-- Playhead advances every beat (8th note = 60000 / bpm / 2)
every (60000 / bpm / 2) -> step = if playing then (step + 1) % 16 else step
-- ── Audio triggers ──
-- On each step, play the sound if the pad is active
every (60000 / bpm / 2) -> play_tone(if playing then (if kick[step] then 60 else 0) else 0, 100, "sine")
every (60000 / bpm / 2) -> play_noise(if playing then (if snare[step] then 100 else 0) else 0, 0.4)
every (60000 / bpm / 2) -> play_noise(if playing then (if hihat[step] then 40 else 0) else 0, 0.15)
every (60000 / bpm / 2) -> play_tone(if playing then (if bass[step] then 110 else 0) else 0, 120, "triangle")
-- Stream for multiplayer collaboration
stream beats on "ws://localhost:9100/peer/beats" {
mode: signal,
output: bpm, step, playing, kick, snare, hihat, bass
}
view beats =
column [
text "🎹 DreamStack Beats" { variant: "title" }
text "Collaborative step sequencer — synced via bitstream relay" { variant: "subtitle" }
-- Transport controls
row [
button (if playing then "⏸ Pause" else "▶ Play") {
click: playing = if playing then 0 else 1,
variant: "primary"
}
button "⏮ Reset" {
click: step = 0,
variant: "ghost"
}
text "BPM: {bpm}" { variant: "caption" }
button "" { click: bpm = if bpm > 40 then bpm - 10 else bpm, variant: "secondary" }
button "+" { click: bpm = if bpm < 300 then bpm + 10 else bpm, variant: "secondary" }
]
-- Playhead indicator
row [
text " " { variant: "muted" }
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] ->
text (if i == step then "▼" else "·") { variant: "muted" }
]
-- Kick row
row [
text "KICK " { variant: "caption" }
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] ->
button (if kick[i] then "●" else "○") {
click: kick[i] = if kick[i] then 0 else 1,
variant: (if kick[i] then "primary" else "secondary")
}
]
-- Snare row
row [
text "SNRE " { variant: "caption" }
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] ->
button (if snare[i] then "●" else "○") {
click: snare[i] = if snare[i] then 0 else 1,
variant: (if snare[i] then "primary" else "secondary")
}
]
-- HiHat row
row [
text "HHAT " { variant: "caption" }
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] ->
button (if hihat[i] then "●" else "○") {
click: hihat[i] = if hihat[i] then 0 else 1,
variant: (if hihat[i] then "primary" else "secondary")
}
]
-- Bass row
row [
text "BASS " { variant: "caption" }
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] ->
button (if bass[i] then "●" else "○") {
click: bass[i] = if bass[i] then 0 else 1,
variant: (if bass[i] then "primary" else "secondary")
}
]
-- Presets
text "Presets" { variant: "caption" }
row [
button "Four on the Floor" {
click: kick[0] = 1; kick[4] = 1; kick[8] = 1; kick[12] = 1; snare[4] = 1; snare[12] = 1; hihat[0] = 1; hihat[2] = 1; hihat[4] = 1; hihat[6] = 1; hihat[8] = 1; hihat[10] = 1; hihat[12] = 1; hihat[14] = 1,
variant: "ghost"
}
button "Clear All" {
click: kick[0] = 0; kick[1] = 0; kick[2] = 0; kick[3] = 0; kick[4] = 0; kick[5] = 0; kick[6] = 0; kick[7] = 0; kick[8] = 0; kick[9] = 0; kick[10] = 0; kick[11] = 0; kick[12] = 0; kick[13] = 0; kick[14] = 0; kick[15] = 0; snare[0] = 0; snare[1] = 0; snare[2] = 0; snare[3] = 0; snare[4] = 0; snare[5] = 0; snare[6] = 0; snare[7] = 0; snare[8] = 0; snare[9] = 0; snare[10] = 0; snare[11] = 0; snare[12] = 0; snare[13] = 0; snare[14] = 0; snare[15] = 0; hihat[0] = 0; hihat[1] = 0; hihat[2] = 0; hihat[3] = 0; hihat[4] = 0; hihat[5] = 0; hihat[6] = 0; hihat[7] = 0; hihat[8] = 0; hihat[9] = 0; hihat[10] = 0; hihat[11] = 0; hihat[12] = 0; hihat[13] = 0; hihat[14] = 0; hihat[15] = 0; bass[0] = 0; bass[1] = 0; bass[2] = 0; bass[3] = 0; bass[4] = 0; bass[5] = 0; bass[6] = 0; bass[7] = 0; bass[8] = 0; bass[9] = 0; bass[10] = 0; bass[11] = 0; bass[12] = 0; bass[13] = 0; bass[14] = 0; bass[15] = 0,
variant: "destructive"
}
]
text "🔴 Streaming via ws://localhost:9100 — open another tab to collab!" { variant: "muted" }
]