2026-02-25 19:33:12 -08:00
|
|
|
|
-- DreamStack Step Sequencer
|
|
|
|
|
|
-- Collaborative beat grid: two tabs, same pads, real-time sync
|
2026-02-26 23:42:29 -08:00
|
|
|
|
--
|
|
|
|
|
|
-- 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)
|
2026-02-25 19:33:12 -08:00
|
|
|
|
|
|
|
|
|
|
let bpm = 120
|
|
|
|
|
|
let step = 0
|
2026-02-26 23:42:29 -08:00
|
|
|
|
let playing = 1
|
2026-02-25 19:33:12 -08:00
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- 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]
|
2026-02-25 19:33:12 -08:00
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Playhead advances every beat (8th note = 60000 / bpm / 2)
|
|
|
|
|
|
every (60000 / bpm / 2) -> step = if playing then (step + 1) % 16 else step
|
2026-02-25 19:33:12 -08:00
|
|
|
|
|
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
|
|
|
|
-- ── 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")
|
|
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Stream for multiplayer collaboration
|
|
|
|
|
|
stream beats on "ws://localhost:9100/peer/beats" {
|
|
|
|
|
|
mode: signal,
|
|
|
|
|
|
output: bpm, step, playing, kick, snare, hihat, bass
|
|
|
|
|
|
}
|
2026-02-25 19:33:12 -08:00
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
view beats =
|
2026-02-25 19:33:12 -08:00
|
|
|
|
column [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
text "🎹 DreamStack Beats" { variant: "title" }
|
|
|
|
|
|
text "Collaborative step sequencer — synced via bitstream relay" { variant: "subtitle" }
|
2026-02-25 19:33:12 -08:00
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Transport controls
|
2026-02-25 19:33:12 -08:00
|
|
|
|
row [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
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" }
|
2026-02-25 19:33:12 -08:00
|
|
|
|
]
|
|
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Kick row
|
2026-02-25 19:33:12 -08:00
|
|
|
|
row [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
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")
|
2026-02-25 19:33:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Snare row
|
2026-02-25 19:33:12 -08:00
|
|
|
|
row [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
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")
|
2026-02-25 19:33:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- HiHat row
|
2026-02-25 19:33:12 -08:00
|
|
|
|
row [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
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")
|
2026-02-25 19:33:12 -08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Bass row
|
2026-02-25 19:33:12 -08:00
|
|
|
|
row [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
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")
|
|
|
|
|
|
}
|
2026-02-25 19:33:12 -08:00
|
|
|
|
]
|
|
|
|
|
|
|
2026-02-26 23:42:29 -08:00
|
|
|
|
-- Presets
|
|
|
|
|
|
text "Presets" { variant: "caption" }
|
2026-02-25 19:33:12 -08:00
|
|
|
|
row [
|
2026-02-26 23:42:29 -08:00
|
|
|
|
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"
|
|
|
|
|
|
}
|
2026-02-25 19:33:12 -08:00
|
|
|
|
]
|
2026-02-26 23:42:29 -08:00
|
|
|
|
|
|
|
|
|
|
text "🔴 Streaming via ws://localhost:9100 — open another tab to collab!" { variant: "muted" }
|
2026-02-25 19:33:12 -08:00
|
|
|
|
]
|