Upgrade to Pro — share decks privately, control downloads, hide ads and more …

From Live Code to Sound: Building a Ruby Live C...

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

From Live Code to Sound: Building a Ruby Live Coding Engine

Avatar for あそなす

あそなす

May 07, 2026

More Decks by あそなす

Other Decks in Technology

Transcript

  1. ˒ Yuya Fujiwara (@asonas) ˒ Software Engineer at ˒ Last

    year: "How to make the Groovebox" at RubyKaigi 2025 ˒ This year: asonas/strudel-rb self.introduction
  2. ˒ Learn the approach behind Strudel and TidalCycles ˒ Layer

    sounds, li tt le by li tt le, and build music from there ˒ Music theory is hard --- start by making sound and having fun Today's Goal
  3. Demo: Play Drums setcpm 120/4 sound("bd hh sd hh") samples/

    ├ ─ ─ bd/ │ └ ─ ─ 0.wav ├ ─ ─ cp/ │ └ ─ ─ 0.wav ├ ─ ─ hh/ │ └ ─ ─ 0.wav ... └ ─ ─ sd/ └ ─ ─ 0.wav
  4. ˒ Mini-Notation: a short text format for pa tt erns

    ˒ "bd hh sd hh" --- 4 sounds, evenly split in 1 cycle ˒ "bd*4" --- repeat 4 times ˒ "bd - sd -" --- - is a rest (silence) ˒ "[bd sd] hh" --- group: bd and sd share the fi rst half ˒ ...and many more (see TidalCycles reference) ˒ Parsed by Parslet (PEG parser library for Ruby) Parsing Mini-Notation
  5. ˒ A Pa tt ern is a function: give it

    a time range, get back Haps ˒ Pa tt erns compose: each method returns a new Pa tt ern ˒ `.gain(0.8)` → wraps original, adds gain to every Hap ˒ `.s("sine")` → sets the sound source ˒ `stack(a, b)` → queries both, concatenates Haps Pa tt ern Composition
  6. ˒ Time is measured in cycles, not seconds ˒ Temp

    = cycles per second (CPS) ˒ `setcpm(120/4)`→ 30 CPM → 0.5 CPS → 1 cycle = 2 seconds ˒ Positions are Rational numbers (not Float) ˒ Each event is a Hap (from "Happening") ˒ When: the time span of the event ˒ What: the value to play Cycle, Time, and Hap
  7. Cycle and Time: Timeline cycle 0 cycle 1 |--- bd

    ---|--- hh ---|--- sd ---|--- hh ---|--- bd ---|--- ... 0 1/4 2/4 3/4 1(0) 1/4 sound("bd hh sd hh")
  8. Demo: Recap setcpm 120/4 track(:drums) { sound("bd hh sd hh").gain(0.8)

    } _track(:harmony) { note("<[c4,e4,g4] [a3,c4,e4] [f3,a3,c4] [g3,b3,d4]>").s("sine").gain(0.5) } _track(:arpeggio) { note("<c4 e4 g4 b4>*8").s("sawtooth").gain(0.3) }
  9. Live Coding: Edit, Save, Hear ┌ ─ ─ ─ ─

    ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ loop (never stops) ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ ▼ │ [Edit] ─ ► [Save] ─ ► [Detect] ─ ► [Evaluate] │ music.rb listen gem instance_eval │ │ │ ┌ ─ success ─ ┴ ─ error ─ ┐ │ ▼ ▼ │ [Swap Pattern] [Keep old pattern] │ │ │ + error to console │ │ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ▼ │ [Audio plays] ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
  10. track / _track # track(:arp) { # note("<c4 e4 g4

    b4>*8") # .s("sawtooth") # .gain(0.3) # } # mute! _track(:arp) { note("<c4 e4 g4 b4>*8") .s("sawtooth") .gain(0.3) } # mute! You want to silence the arpeggio for a few bars.
  11. Error Handling in Live Coding New code ─ ┬ ─

    [OK] ─ ─ → swap in the new pattern │ └ ─ [NG] ─ ─ → keep the old pattern running + print error to console Key points: - Both OK and NG paths lead to "sound keeps playing" - Silence is not an acceptable failure mode
  12. Randomness in Live Coding Normal rand (Ruby) Deterministic rand (strudel-rb)

    ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ save #1: pan = 0.31 save #1: pan @ t=1.5 → 0.42 save #2: pan = 0.87 ← jumps pan @ t=2.0 → 0.81 save #3: pan = 0.12 ← jumps ─ ─ ─ save (re-evaluate) ─ ─ ─ save #2: pan @ t=1.5 → 0.42 (same) pan @ t=2.0 → 0.81 (same) → stereo position jumps on → same time, same value; every save saves don't move the sound (rand's seed = event start time)
  13. ˒ Day 2(Afternoon Break) ˒ Live performance at Sub Arena

    (this venue!) ˒ Day 3 (20:00-20:20) ˒ RubyIllumination 2026 ˒ at BLUE POINT strudel-rb in the Wild
  14. ˒ BGM of the RubyKaigi 2026 commercial at Goryokaku-Honcho (五稜郭本町)

    intersection ˒ Composed with strudel-rb Hidden Track