← JamDojo Demoscene Music

Demoscene Music

The demoscene emerged from computer hobbyists who pushed hardware to its limits—creating music and visuals that fit in kilobytes, not megabytes. This comprehensive guide takes you from basic chip sounds all the way to creating your own complete audio-visual demos.


Part 1: Demoscene Foundations

The Demoscene Spirit

What is Demoscene Music?

Born in the 1980s, the demoscene grew from cracking groups who added custom intros to pirated software. These “cracktros” evolved into an art form—demos that showcased programming and audio skills within severe constraints: 3-4 audio channels, limited memory, and primitive sound chips.

The constraints bred creativity—if you only have 3 channels, you learn tricks to make them sound like more. This “constraint-driven creativity” mindset is at the heart of the scene. In 2020, UNESCO recognized demoscene culture as intangible cultural heritage in Finland and Germany.

Today, competitions like Revision (Germany) and Assembly (Finland) continue the tradition. Artists create 4K intros (entire demos in 4 kilobytes), 64K productions, and size-unlimited demos that push modern hardware.

The Classic Waveforms

Chip music uses simple waveforms. Each has a distinct character. Let’s see them with oscilloscope visualization:

Square wave - Bright and hollow, the NES and Game Boy signature:

note("c4 e4 g4 c5").s("square")
.decay(0.1).sustain(0.3)
.color('lime')._scope({ smear: 0.8 })

Triangle wave - Softer and rounder, used for bass on NES:

note("c3 c3 g3 c3").s("triangle")
.decay(0.2).sustain(0.5)
.color('cyan')._scope({ smear: 0.8 })

Sawtooth wave - Harsh and rich, the aggressive C64 lead:

note("c4 eb4 g4 c5").s("sawtooth")
.decay(0.1).sustain(0.4).lpf(2000)
.color('orange')._scope({ smear: 0.8 })

Compare all three on the same melody:

note("c4 e4 g4 c5")
.s("<square triangle sawtooth>/4")
.decay(0.1).sustain(0.3)
.lpf(2500)
._scope({ smear: 0.8 })

Chip Drums Deep Dive

No drum samples? No problem. Use noise and pitched oscillators to build an entire drum kit from scratch.

Hi-Hat

Short noise burst with high-pass filtering. The shorter the decay, the tighter the hat:

sound("white").decay(0.02)
.hpf(8000).gain(0.4)
.struct("x*8")

Open hi-hat with longer decay:

sound("white").decay(0.08)
.hpf(6000).gain(0.3)
.struct("~ ~ x ~")

Snare

Combine noise with a pitched component for more punch:

sound("white").decay(0.08)
.lpf(4000).gain(0.6)
.struct("~ x")

More aggressive snare with pitch sweep:

stack(
sound("white").decay(0.1).lpf(5000),
note("c3").s("sine").decay(0.05)
).gain(0.6).struct("~ x")

Kick Drum

The secret to chip kicks: rapid pitch sweep from high to low. The penv (pitch envelope) parameter is perfect for this:

note("c1").s("sine")
.decay(0.15).gain(1)
.penv(24).pdec(0.03)
.struct("x ~ x ~")

Different kick characters—try adjusting penv and pdec:

note("c1").s("sine")
.decay("<0.1 0.15 0.2>")
.penv("<12 24 36>")
.pdec("<0.02 0.03 0.05>")
.gain(1)
.struct("x*4")

Tom Drums

Toms use the same pitch envelope trick at higher frequencies:

note("<g2 d2 a1>").s("sine")
.decay(0.12).penv(12).pdec(0.04)
.struct("~ ~ ~ x")

Milestone 1: “8-Bit Beat Box”

Let’s build a complete chip drum kit. This is your first milestone—a drum loop using only synthesized sounds:

stack(
// Kick with pitch envelope
note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
  .gain(1).struct("x ~ [~ x] ~"),
// Snare - noise + pitched body
stack(
  sound("white").decay(0.1).lpf(4500),
  note("c3").s("sine").decay(0.04)
).gain(0.5).struct("~ x ~ x"),
// Closed hi-hat
sound("white").decay(0.02).hpf(8000)
  .gain(0.3).struct("x*8"),
// Open hi-hat
sound("white").decay(0.08).hpf(6000)
  .gain(0.25).struct("~ ~ ~ ~ ~ ~ x ~"),
// Tom fill
note("g2").s("sine").decay(0.1).penv(10).pdec(0.03)
  .gain(0.4).struct("~ ~ ~ ~ ~ ~ ~ x")
).cpm(65)

The Magic of Arpeggios

With only 3-4 channels, you can’t play full chords. The solution? Arpeggios—cycling through chord notes so fast they blend together. This is THE defining technique of chip music.

Basic Arpeggio Patterns

Major chord arpeggio—bright and happy:

note("c4 e4 g4 c5 g4 e4").fast(4)
.s("square").decay(0.05)

Minor arpeggio—darker moods:

note("a3 c4 e4 a4 e4 c4").fast(4)
.s("square").decay(0.05)

Seventh chord arpeggio—jazzy chip:

note("c4 e4 g4 b4 g4 e4").fast(4)
.s("triangle").decay(0.04)

Diminished arpeggio—tense and unsettling:

note("c4 eb4 gb4 a4 gb4 eb4").fast(4)
.s("square").decay(0.05)

Arpeggio Speed

Speed dramatically changes the character. Slow arpeggios are distinct notes, fast ones blur into chords:

note("c4 e4 g4 c5 g4 e4")
.fast("<1 2 4 8>")
.s("square").decay(0.05)

Arpeggio Patterns

Up - Rising energy:

note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)

Down - Falling, relaxed:

note("c5 g4 e4 c4").fast(4)
.s("square").decay(0.05)

Up-Down - Classic bouncing feel:

note("c4 e4 g4 c5 g4 e4").fast(4)
.s("square").decay(0.05)

Alternating - More complex movement:

note("c4 g4 e4 c5").fast(4)
.s("square").decay(0.05)

Chord Progressions with Arpeggios

The real power comes from changing the arpeggio’s root chord:

note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
.fast(4).s("square").decay(0.05)
.cpm(70)

Using chord and arp

Strudel has built-in support for arpeggios from chords:

chord("<C Am F G>")
.arp("0 1 2 3 2 1").fast(2)
.note().s("square").decay(0.05)
.cpm(70)

Milestone 2: Your First Chip Track

Time to combine everything! Let’s build a complete chip track step by step:

Step 1: Arpeggio Chords

note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
.fast(4).s("square").decay(0.05).gain(0.5)
.cpm(65)

Step 2: Add Triangle Bass

stack(
note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
  .fast(4).s("square").decay(0.05).gain(0.5),
note("<c2 a1 f2 g2>")
  .s("triangle").decay(0.2).gain(0.7)
).cpm(65)

Step 3: Add Chip Drums

stack(
// Arpeggio
note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
  .fast(4).s("square").decay(0.05).gain(0.5),
// Bass
note("<c2 a1 f2 g2>")
  .s("triangle").decay(0.2).gain(0.7),
// Kick
note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
  .gain(0.9).struct("x ~ x ~"),
// Snare
sound("white").decay(0.08).lpf(4000)
  .gain(0.5).struct("~ x"),
// Hi-hat
sound("white").decay(0.02).hpf(8000)
  .gain(0.25).struct("x*8")
).cpm(65)

Complete “Chip Loop” with melody:

stack(
// Lead melody (sparse)
note("~ ~ g5 ~ c5 ~ e5 ~ ~ ~ g5 ~ ~ e5 ~ ~")
  .s("square").decay(0.12).sustain(0.2).gain(0.5),
// Arpeggio chords
note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
  .fast(4).s("triangle").decay(0.04).gain(0.4),
// Bass
note("<c2 a1 f2 g2>")
  .s("sawtooth").lpf(600).decay(0.15).gain(0.7),
// Drums
stack(
  note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
    .gain(0.9).struct("x ~ x ~"),
  sound("white").decay(0.08).lpf(4000)
    .gain(0.5).struct("~ x"),
  sound("white").decay(0.02).hpf(8000)
    .gain(0.25).struct("x*8")
)
).room(0.1).cpm(65)

Part 2: Sound Design Techniques

The SID Sound (Commodore 64)

The C64’s SID chip had 3 voices, a resonant filter, and ring modulation. It became legendary for its warm, aggressive sound.

Filter Sweeps

The SID’s resonant filter is its most distinctive feature. High resonance (lpq) creates that singing, aggressive sound:

note("c3 eb3 g3 c4 g3 eb3").fast(4)
.s("sawtooth")
.lpf(sine.range(400, 3000).fast(2))
.lpq(8)
.decay(0.08)

Try different resonance levels:

note("c3 eb3 g3 c4").fast(4)
.s("sawtooth")
.lpf(sine.range(400, 2500).fast(2))
.lpq("<2 5 10 15>")
.decay(0.06)

PWM (Pulse Width Modulation) Simulation

The SID could modulate pulse width for an animated, moving sound. We simulate this with detuning and filter modulation:

note("c4 e4 g4 c5").s("square")
.lpf(sine.range(1000, 3000).fast(4))
.lpq(4)
.decay(0.15)
.superimpose(x => x.detune(sine.range(-15, 15).fast(3)))

Classic SID Bass Patches

Punchy SID bass:

note("c2 c2 eb2 g2")
.s("sawtooth")
.lpf(800).lpq(5)
.decay(0.15).sustain(0.3)
.slide(1)

Filtered bass with envelope:

note("c2 c2 g2 eb2")
.s("sawtooth")
.lpf(400).lpenv(4).lpdecay(0.1)
.decay(0.2).sustain(0.2)

Aggressive SID bass:

note("c2 c2 c2 c2 eb2 eb2 g2 f2")
.s("sawtooth")
.lpf(sine.range(300, 1500).fast(4))
.lpq(10)
.decay(0.1)
.cpm(70)

Acid-Style Filter Patterns

Acid basslines (TB-303 style) use heavy filter resonance and slides:

note("c3 c3 eb3 c3 c3 g3 c3 eb3")
.s("sawtooth")
.lpf(sine.range(300, 2000).fast(2))
.lpq(15)
.slide(0.5)
.cpm(70)

Tracker Effects

Vibrato

Essential for expressive leads. .vib() controls speed, .vibmod() controls depth:

note("c4 ~ e4 ~ g4 ~ e4 ~")
.s("sawtooth")
.vib(4).vibmod(0.2)
.lpf(2500)
.decay(0.2).sustain(0.4)

Varying vibrato intensity:

note("c5 e5 g5 c6")
.s("sawtooth")
.vib("<2 4 6 8>")
.vibmod("<0.1 0.2 0.3 0.4>")
.lpf(3000).decay(0.3)

Portamento (Slides)

Sliding between notes. Higher .slide() values = slower slides:

note("c4 e4 g4 c5")
.s("sawtooth")
.slide(2)
.lpf(2000)
.decay(0.15)
note("c4 g4 e4 c5")
.s("sawtooth")
.slide("<0 1 2 4>")
.lpf(2500).decay(0.2)

Retrigger Effects

Rapid retriggering creates stuttering, glitchy effects:

note("c4").s("sawtooth")
.ply("<1 2 4 8>")
.decay(0.05).lpf(2000)
.cpm(60)

Volume Patterns

Dynamic .gain() sequences add groove and movement:

note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)
.gain("1 0.5 0.7 0.3 0.9 0.4 0.8 0.5")

ZZFX Synthesis Deep Dive

Strudel’s ZZFX synth (the “Zuper Zmall Zound Zynth”) creates authentic retro sounds. It was originally designed for size-coded games—perfect for demoscene aesthetics.

Basic ZZFX Waveforms

The z_ prefix gives you chip-style synthesis with more parameters:

note("c4 e4 g4 c5")
.s("<z_square z_sawtooth z_triangle z_sine>/4")
.decay(0.1)

Bit Crushing

.zcrush() reduces bit depth for crunchy lo-fi sound. Values from 0 (clean) to 1 (extreme):

note("c4 e4 g4 c5").s("z_sawtooth")
.zcrush("<0 0.2 0.5 0.8>")
.decay(0.1)

Extreme crush for ZX Spectrum-style harshness:

note("c3 c3 eb3 g3").s("z_square")
.zcrush(0.8)
.decay(0.08)

FM Modulation with ZZFX

.zmod() adds FM-style modulation for metallic, bell-like tones:

note("c4 e4 g4 c5").s("z_sine")
.zmod("<0 2 5 10>")
.decay(0.15)

Pitch Effects

.pitchJump() and .pitchJumpTime() create chip-style sound effects:

note("c4").s("z_square")
.pitchJump("<0 5 12 -12>")
.pitchJumpTime(0.05)
.decay(0.2)

Waveshaping

.curve() changes the waveform shape (1-3):

note("c4 e4 g4 c5").s("z_square")
.curve("<1 1.5 2 2.5>")
.decay(0.1)

Complete ZZFX Sound Design

Combine parameters for unique chip instruments:

note("c4 e4 g4 c5")
.s("z_sawtooth")
.zcrush(0.3)
.zmod(3)
.slide(0.5)
.decay(0.12)
.lpf(2000)

FM Synthesis for Chip Sounds

FM (Frequency Modulation) synthesis can create metallic, bell-like tones reminiscent of DOS/AdLib music.

Basic FM

.fm() controls modulation depth, .fmh() controls the harmonic ratio:

note("c4 e4 g4 c5")
.fm("<0 1 2 4>")
.decay(0.2)

FM Ratios for Different Timbres

Different .fmh() ratios create different characters:

note("c4 e4 g4 c5")
.fm(3)
.fmh("<1 2 3 4>")
.decay(0.2)

FM with Envelope

.fmenv() applies an envelope to the FM depth—great for percussive sounds:

note("c4 e4 g4 c5")
.fm(5).fmh(2)
.fmenv("<0 2 4 -2>")
.fmdecay(0.1)
.decay(0.3)

FM Bass

note("c2 c2 g2 c2")
.fm(2).fmh(1)
.fmenv(3).fmdecay(0.05)
.decay(0.15)
.lpf(800)

FM Bell Tones

note("c5 e5 g5 c6")
.fm(4).fmh(3.5)
.fmenv(2).fmdecay(0.2)
.decay(0.5).sustain(0.1)
.room(0.3)

Layering and Width

Detuning

Layer slightly detuned oscillators for a fatter sound:

stack(
note("c4 e4 g4 c5").s("sawtooth").detune(-10),
note("c4 e4 g4 c5").s("sawtooth").detune(10)
).decay(0.15).lpf(3000).gain(0.5)

Superimpose

.superimpose() creates layered variations of the same pattern:

note("c4 e4 g4 c5")
.s("sawtooth")
.decay(0.15)
.lpf(2500)
.superimpose(
  x => x.detune(12),
  x => x.detune(-12)
).gain(0.4)

Octave Stacking

Layer octaves for massive leads:

note("c4 e4 g4 c5")
.s("sawtooth")
.decay(0.2)
.lpf(3000)
.superimpose(x => x.add(note(12)))
.gain(0.4)

Stereo with Jux

.jux() applies a function to the right channel only—great for stereo width:

note("c4 e4 g4 c5")
.s("sawtooth")
.decay(0.15)
.lpf(2500)
.jux(x => x.detune(15))
.gain(0.5)

Milestone 3: “Layered Lead”

Build a thick, modern chip lead combining techniques:

note("c5 ~ eb5 ~ g5 ~ eb5 c5")
.s("sawtooth")
.decay(0.15).sustain(0.2)
.lpf(3000).lpq(3)
.superimpose(
  x => x.detune(8),
  x => x.detune(-8).add(note(-12)).gain(0.5)
)
.jux(x => x.detune(4))
.vib(4).vibmod(0.1)
.room(0.15)
.gain(0.4)
.cpm(65)

Part 3: Modern & Procedural Techniques

Procedural/Generative Music

Modern demoscene productions often use procedural techniques—generating music algorithmically to save space or create endless variation.

Controlled Randomness

Use rand for random values that change each cycle:

note("c4 e4 g4 c5")
.fast(4).s("square")
.decay(rand.range(0.02, 0.1))
.lpf(rand.range(800, 3000))
.gain(rand.range(0.3, 0.8))

Probability Triggers

.sometimes(), .often(), .rarely() add controlled chaos:

note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)
.sometimes(x => x.add(note(12)))
.rarely(x => x.lpf(500))
note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)
.someCycles(x => x.fast(2))
.gain(0.6)

Perlin Noise

perlin creates smooth, organic randomness—great for filter sweeps:

note("c3 eb3 g3 c4").fast(4)
.s("sawtooth")
.lpf(perlin.range(400, 3000).slow(4))
.lpq(8)
.decay(0.08)

Euclidean Rhythms

Euclidean rhythms distribute hits evenly—mathematically perfect grooves:

stack(
note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
  .euclid(4, 16).gain(0.9),
sound("white").decay(0.08).lpf(4000)
  .euclid(3, 8).gain(0.5),
sound("white").decay(0.02).hpf(8000)
  .euclid(7, 16).gain(0.25)
).cpm(65)

Try different patterns:

note("c3").s("sawtooth").lpf(1000)
.decay(0.1)
.euclid("<3 5 7 9>", 16)
.cpm(70)

Algorithmic Melodies

Generate melodies from scales and patterns:

n([0, 2, 4, 5, 7, 9, 11, 12].sort(() => Math.random() - 0.5).slice(0, 4))
.scale("C:minor")
.s("square").decay(0.1)
.fast(2)

Or use irand for random scale degrees:

n(irand(8)).segment(8)
.scale("C:minor:pentatonic")
.s("square").decay(0.08)
.lpf(2000)
.cpm(65)

Live Coding Techniques

Pattern Registration with $:

The $: prefix registers patterns for live modification. Each named pattern can be changed without stopping:

$: kick = note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
.struct("x ~ x ~").gain(0.9)

$: bass = note("c2 c2 eb2 g2")
.s("sawtooth").lpf(800).decay(0.15)

$: hats = sound("white").decay(0.02).hpf(8000)
.struct("x*8").gain(0.25)

stack(kick, bass, hats).cpm(65)

Building Up Arrangements

Start minimal and add layers progressively:

// Uncomment layers one by one to build up
stack(
// Layer 1: Kick
note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
  .struct("x ~ x ~").gain(0.9),
// Layer 2: Hi-hats
sound("white").decay(0.02).hpf(8000)
  .struct("x*8").gain(0.25),
// Layer 3: Bass
note("<c2 eb2 f2 g2>")
  .s("sawtooth").lpf(700).decay(0.15),
// Layer 4: Arpeggio
note("<c4 e4 g4 c5> <eb4 g4 bb4 eb5>").fast(4)
  .s("square").decay(0.05).gain(0.4)
).cpm(65)

Controlled Variations

Use modifiers for evolving patterns:

note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)
.iter(4)
.sometimes(x => x.fast(2))
.cpm(70)
note("c4 e4 g4 c5 g4 e4").fast(4)
.s("triangle").decay(0.04)
.every(4, x => x.rev())
.every(3, x => x.fast(2))
.cpm(70)

Size-Optimized Synthesis

Minimal Code, Maximum Impact

In 4K/64K intros, every byte counts. Write compact patterns:

// Compact: one-liner beat
stack(n("c1").s("sine").decay(.1).penv(24).pdec(.03).struct("x~x~"),s("white").decay(.02).hpf(8e3).struct("x*8").gain(.3)).cpm(65)

Efficient Pattern Reuse

Define once, use multiple ways:

// Base pattern used multiple ways
let p = "c4 e4 g4 c5"
stack(
note(p).fast(4).s("square").decay(0.05).gain(0.5),
note(p).slow(4).s("triangle").decay(0.3).add(note(-12)).gain(0.4),
note(p).fast(4).s("triangle").decay(0.03).add(note(12)).gain(0.3)
).cpm(70)

Procedural Melodies from Math

Generate melodies using mathematical formulas:

n("[0 2 4 5]".add(sine.range(0, 7).segment(8)))
.scale("C:minor")
.s("square").decay(0.1)
.lpf(2000).cpm(65)

Milestone 4: “Procedural Chip”

A generative arrangement that evolves over time:

stack(
// Evolving arpeggio with filter movement
note("c4 e4 g4 c5 g4 e4").fast(4)
  .s("square").decay(0.05)
  .lpf(perlin.range(800, 3000).slow(8))
  .sometimes(x => x.add(note(12)))
  .gain(0.4),
// Euclidean bass
note("c2 eb2 g2 c2")
  .s("sawtooth").lpf(600)
  .decay(0.15)
  .euclid(5, 8)
  .gain(0.6),
// Procedural drums
stack(
  note("c1").s("sine").decay(0.1).penv(24).pdec(0.03)
    .euclid(4, 16).gain(0.9),
  sound("white").decay(0.06).lpf(4000)
    .euclid(3, 8).gain(0.5),
  sound("white").decay(0.02).hpf(8000)
    .euclid(7, 16).gain(0.25)
),
// Occasional melodic hits
n(irand(5)).segment(4)
  .scale("C:minor:pentatonic")
  .add(note(24))
  .s("triangle").decay(0.2)
  .struct("~ ~ ~ x")
  .rarely(x => x.s("square"))
  .gain(0.3)
).room(0.15).cpm(70)

Part 4: Visual Integration

Demoscenes aren’t just about audio—they’re about synchronized audio-visual experiences. Strudel has powerful built-in visualizations and integrates with Hydra for shader-based effects.

Built-in Visualizations

Punchcard/Pianoroll

Show your patterns as note bars. Use _punchcard (inline) or punchcard() (background):

note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)
.color("cyan")
._punchcard()

Scope (Oscilloscope)

See your waveforms in real-time:

note("c3").s("sawtooth")
.lpf(1000).decay(0.3)
.color('lime')._scope({ smear: 0.9 })

Compare waveforms visually:

note("c3").s("<square triangle sawtooth>/2")
.decay(0.3)
._scope({ smear: 0.8 })

Frequency Scope

See the frequency content of your sounds:

note("c3 e3 g3 c4").fast(2)
.s("sawtooth")
.lpf(sine.range(400, 2000).slow(4))
.decay(0.15)
._fscope({ smear: 0.95 })

Spectrum Analyzer

Scrolling spectrum display:

stack(
note("c4 e4 g4 c5").fast(4).s("square").decay(0.05),
note("c2").s("sawtooth").lpf(600).decay(0.2)
)._spectrum({ speed: 2 })

Spiral Visualization

Circular time display—great for cyclic patterns:

note("c4 e4 g4 c5 g4 e4").fast(4)
.s("square").decay(0.05)
.color("<cyan magenta yellow>")
._spiral({ steady: 0.96, thickness: 10 })

Color Patterns

Color your visualizations to match the music:

note("c4 e4 g4 c5").fast(4)
.s("square").decay(0.05)
.color("<cyan magenta yellow lime>")
._punchcard()

Introduction to Hydra

Hydra is a live-codable video synthesizer. Initialize it with await initHydra():

Basic Hydra Functions

osc() - Oscillator patterns:

shape() - Geometric shapes:

noise() - Random patterns:

Transformations

kaleid() - Kaleidoscope:

pixelate() - Retro pixel look:

rotate() - Spinning effects:

Color Effects

colorama() - Color shifting:

Classic Demoscene Effects

Plasma Effect

The classic demoscene plasma:

Tunnel Effect

Copper Bars (Amiga-style)

Starfield

Pattern-Driven Visuals with H()

The H() function connects Strudel patterns to Hydra parameters:

Beat-synced kaleidoscope:

Audio-Reactive Visuals

Use detectAudio: true to access FFT data:

Feeding Strudel to Hydra

Process Strudel’s visualizations through Hydra shaders:

Milestone 5: “Audio-Visual Chip Demo”


Part 5: Demoscene Styles

Keygen Music Mastery

Keygen music is pure nostalgia—uplifting melodies at 150-180 BPM with bright synths and relentless arpeggios.

The Keygen Formula

  1. Fast arpeggios (major/minor progressions)
  2. Punchy bass following chord roots
  3. 4-on-the-floor kick
  4. Bright hi-hats
  5. Optional: lead melody hook

Chord Progressions That Work

Classic I-vi-IV-V:

note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
.fast(4).s("square").decay(0.05).gain(0.5)
.cpm(80)

Minor i-VI-III-VII:

note("<a3 c4 e4 a4> <f4 a4 c5 f5> <c4 e4 g4 c5> <g4 b4 d5 g5>")
.fast(4).s("square").decay(0.05).gain(0.5)
.cpm(80)

Counter-Melody Arpeggios

Add a second arpeggio at a different octave or rhythm:

stack(
note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
  .fast(4).s("square").decay(0.05).gain(0.5),
note("<g4 c5 e5 g5> <e4 a4 c5 e5> <c5 f5 a5 c6> <d5 g5 b5 d6>")
  .fast(4).s("triangle").decay(0.03).gain(0.35)
).cpm(80)

Milestone 6: “Your Keygen Banger”

stack(
// Main arpeggio
note("<c4 e4 g4 c5> <a3 c4 e4 a4> <f4 a4 c5 f5> <g4 b4 d5 g5>")
  .fast(4).s("square").decay(0.04).gain(0.45),
// Counter arpeggio (higher, quieter)
note("<g4 c5 e5 g5> <e4 a4 c5 e5> <c5 f5 a5 c6> <d5 g5 b5 d6>")
  .fast(4).s("triangle").decay(0.03).gain(0.3),
// Punchy bass
note("<c2 a1 f2 g2>").s("sawtooth")
  .lpf(800).decay(0.12).gain(0.75),
// Lead melody (sparse, catchy)
note("~ ~ g5 ~ ~ c6 ~ g5 ~ ~ e5 ~ ~ ~ ~ ~")
  .s("square").decay(0.1).sustain(0.2)
  .vib(4).vibmod(0.1).gain(0.55),
// Drums
stack(
  note("c1").s("sine").decay(0.1).penv(24).pdec(0.03)
    .struct("x x x x").gain(0.85),
  sound("white").decay(0.06).lpf(4000)
    .struct("~ x").gain(0.5),
  sound("white").decay(0.02).hpf(7000)
    .struct("x*8").gain(0.25)
)
).room(0.15).cpm(80)

Cracktro Intros

Short, dramatic intros with bold filter sweeps.

Dramatic Filter Sweep

note("c3 c3 c3 c3 eb3 eb3 g3 g3")
.s("sawtooth")
.lpf(sine.range(300, 4000).slow(4))
.lpq(12)
.distort(0.2)
.cpm(70)

Logo Reveal Style

Build tension then release:

note("c3 c3 c3 c3 c3 c3 c3 c3 c3 c3 c3 c3 eb3 g3 c4 c4")
.s("sawtooth")
.lpf(saw.range(200, 3500).slow(2))
.lpq(10)
.gain(saw.range(0.3, 1).slow(2))
.cpm(70)

Demo Soundtracks

Longer compositions with multiple sections.

Epic Build Structure

stack(
// Pad swell
note("<c3,eb3,g3> <ab2,c3,eb3>")
  .s("sawtooth")
  .attack(0.5).decay(0.5).sustain(0.7)
  .lpf(saw.range(400, 2000).slow(8))
  .gain(0.4),
// Arpeggio enters
note("c4 eb4 g4 c5 g4 eb4").fast(4)
  .s("triangle").decay(0.04)
  .gain(saw.range(0, 0.5).slow(4)),
// Drums build
stack(
  note("c1").s("sine").decay(0.12).penv(24).pdec(0.03)
    .struct("x ~ x ~").gain(0.8),
  sound("white").decay(0.02).hpf(8000)
    .struct("x*8").gain(saw.range(0, 0.3).slow(4))
)
).room(0.2).cpm(60)

Modern Demoscene

4K Intro Style

Minimal, efficient, procedural:

stack(
n(irand(5)).segment(8).scale("C:minor:pentatonic")
  .s("z_square").decay(0.08).zcrush(0.3).gain(0.5),
note("c2").s("z_sawtooth").lpf(perlin.range(300, 800).slow(4))
  .decay(0.15).euclid(5, 8).gain(0.6),
stack(
  note("c1").s("sine").decay(0.1).penv(20).pdec(0.02)
    .euclid(4, 16).gain(0.8),
  sound("white").decay(0.02).hpf(8000)
    .euclid(7, 16).gain(0.25)
)
).cpm(70)

Part 6: Complete Demo Projects

”Chip Intro” - Classic Cracktro

”Keygen Paradise” - Full Production

”Digital Dreams” - Ambient Chip

Milestone 7: “Your First Demo”

A complete 2-minute demo structure. Modify and extend this template:


Part 7: Reference & Appendices

Quick Reference Tables

Waveform Guide

WaveformCharacterBest For
squareBright, hollowNES leads, Game Boy
triangleSoft, roundBass, pads
sawtoothHarsh, richAggressive leads, SID
z_squareRetro chipAuthentic 8-bit
z_sawtoothChip sawZZFX leads
white/noiseUnpitchedDrums, effects

Key Functions

FunctionPurposeExample
.decay()Note length.decay(0.1)
.lpf()Low-pass filter.lpf(1000)
.lpq()Filter resonance.lpq(10)
.slide()Portamento.slide(2)
.vib()Vibrato speed.vib(4)
.vibmod()Vibrato depth.vibmod(0.2)
.penv()Pitch envelope.penv(24)
.zcrush()Bit crush.zcrush(0.5)
.fm()FM depth.fm(3)
.fmh()FM ratio.fmh(2)

BPM by Style

StyleBPMcpm ValueFeel
Slow chip80-10040-50Melancholic
Chiptune120-14060-70Bouncy, playful
Cracktro130-16065-80Dramatic, bold
Keygen150-18075-90Uplifting, energetic

Hydra Quick Reference

FunctionEffectExample
osc(f,s,o)Oscillatorosc(10,0.1,1)
shape(n)Polygonshape(4)
noise(s,o)Randomnoise(10,0.1)
kaleid(n)Mirror.kaleid(6)
pixelate(x,y)Pixels.pixelate(64,48)
colorama(a)Color shift.colorama(0.3)
rotate(a)Spin.rotate(0.1)
modulate(s,a)Warp.modulate(osc(),0.1)
H(pattern)Pattern linkshape(H("3 4 5"))

Further Learning

  • Pouet.net - The demoscene archive with thousands of productions
  • Revision - Largest annual demoparty (Easter, Germany)
  • Assembly - Historic Finnish demoparty
  • Scene.org - Awards and resources
  • Demozoo - Database of productions and groups

What You Learned

  • Classic chip waveforms and their characters
  • Synthesizing drums from noise and oscillators
  • Fast arpeggios to simulate chords
  • SID-style filter sweeps with resonance
  • ZZFX synthesis and bit crushing
  • FM synthesis for metallic tones
  • Layering and detuning for width
  • Procedural/generative techniques
  • Built-in visualizations (scope, spectrum, spiral)
  • Hydra shader integration
  • Pattern-driven audio-visual sync
  • Complete demo production from scratch

Now go make some demos!