diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-27 07:54:41 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-27 07:54:41 +0300 |
| commit | eb6e4851110fc6a9ae336793fd87a2ba6fd48a5b (patch) | |
| tree | 3b09061cdf6ad8ed53d32b90366b370cd4ea03b1 /internal/generator/theme_sounds.go | |
| parent | f075b701736d8e7527b00a0b9bdd47e3466c698b (diff) | |
Add nukem theme: action-hero hard rock with explosive WebGL effects
- New theme directory: templates/themes/nukem/ (meta.json, theme.css, theme.js)
- Red/gold/black color palette with monospace typography
- WebGL: rising sparks, falling debris, rubble blocks, explosion glow
- Sound preset: 140 BPM E-minor power riffs with fanfare stabs (wild: 180 BPM thrash)
- All nav/open/close/scroll/wild/page effects implemented
Amp-Thread-ID: https://ampcode.com/threads/T-019dcd42-8558-748b-8c28-3f848400026d
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'internal/generator/theme_sounds.go')
| -rw-r--r-- | internal/generator/theme_sounds.go | 1368 |
1 files changed, 753 insertions, 615 deletions
diff --git a/internal/generator/theme_sounds.go b/internal/generator/theme_sounds.go index a4c4f0c..6b638bc 100644 --- a/internal/generator/theme_sounds.go +++ b/internal/generator/theme_sounds.go @@ -79,13 +79,10 @@ type themeSounds struct { Ambient ambientSounds `json:"ambient,omitempty"` } -// ── helpers ──────────────────────────────────────────────────────── +// ── basic note + chord helpers ───────────────────────────────────── -func n(freq, dur float64) melodyNote { - return melodyNote{Freq: freq, Dur: dur} -} - -// ns builds a note that rings for 'dur' but advances the sequencer by 'step'. +// ns builds a note that rings for 'dur' but advances the sequencer by 'step', +// so multiple voices appended back-to-back can overlap when dur > step. func ns(freq, dur, step float64) melodyNote { return melodyNote{Freq: freq, Dur: dur, Step: step} } @@ -94,6 +91,7 @@ var ( intMajor3rd = math.Pow(2, 4.0/12) intMinor3rd = math.Pow(2, 3.0/12) intPerf5th = math.Pow(2, 7.0/12) + intMinor7th = math.Pow(2, 10.0/12) ) func major(freq float64) [3]float64 { @@ -103,87 +101,230 @@ func minor(freq float64) [3]float64 { return [3]float64{freq, freq * intMinor3rd, freq * intPerf5th} } -// power returns a root+5th power chord (only two voices — aggressive and clear). -func power(freq float64) [3]float64 { - return [3]float64{freq, freq * intPerf5th, 0} +// ── voice builders ──────────────────────────────────────────────── +// Each builder returns a slice of notes appended back-to-back into a melody. +// Because the engine advances time by note.Step (not note.Dur), notes with +// dur > step ring through the next entries — that is how distinct "voices" +// (bass, pad, lead) are stacked into one flat array. + +// hook turns explicit (freq, beats) pairs into a melodic phrase. A pair with +// freq <= 0 OR beats <= 0 is treated as a rest, and its duration is folded +// into the preceding note's step so the next real note triggers later. We +// can't emit a zero-frequency rest entry because the JS engine substitutes +// 440 Hz for falsy freq values, which would turn rests into audible tones. +// beat = seconds-per-quarter-note. dur multiplier of 1.0 = quarter note. +func hook(beat float64, pairs ...float64) []melodyNote { + out := make([]melodyNote, 0, len(pairs)/2) + leadIn := 0.0 + for i := 0; i+1 < len(pairs); i += 2 { + f, b := pairs[i], pairs[i+1] + if f <= 0 || b <= 0 { + silence := math.Abs(b) * beat + if len(out) == 0 { + leadIn += silence + } else { + out[len(out)-1].Step += silence + } + continue + } + step := b * beat + // 0.92 leaves a small gap so repeated notes re-trigger cleanly. + out = append(out, ns(f, step*0.92, step)) + } + // A leading rest in a looped phrase is equivalent to a trailing delay on + // the last note (the loop wraps), so push it onto the final note's step. + if leadIn > 0 && len(out) > 0 { + out[len(out)-1].Step += leadIn + } + return out } -// chordArp returns an up/down arpeggio of a triad; each note sustains for 'dur' -// while the sequencer only advances by 'step', so multiple voices overlap. -func chordArp(chord [3]float64, dur, step float64) []melodyNote { +// padHold lays a chord triad as a long sustained pad. The three notes are +// emitted back-to-back but each rings for almost the full duration, so they +// pile into a held chord. +func padHold(chord [3]float64, totalBeats, beat float64) []melodyNote { + dur := totalBeats * beat return []melodyNote{ - ns(chord[0], dur, step), ns(chord[1], dur, step), - ns(chord[2], dur, step), ns(chord[1], dur, step), + ns(chord[0], dur*0.96, dur*0.34), + ns(chord[1], dur*0.62, dur*0.33), + ns(chord[2], dur*0.30, dur*0.33), } } -// powerArp arpeggiates a power chord (root→5th→root→5th). -func powerArp(chord [3]float64, dur, step float64) []melodyNote { +// octaveBass: pumping synth-pop bassline — root, octave, root, fifth — over 4 +// quarter-notes. Iconic Outrun / "Take On Me" feel. +func octaveBass(root, beat float64) []melodyNote { + fifth := root * intPerf5th return []melodyNote{ - ns(chord[0], dur, step), ns(chord[1], dur, step), - ns(chord[0], dur, step), ns(chord[1], dur, step), + ns(root, beat*0.85, beat), + ns(root*2, beat*0.85, beat), + ns(root, beat*0.85, beat), + ns(fifth, beat*0.85, beat), } } -func bassNote(freq, dur, step float64) melodyNote { - return ns(freq, dur, step) +// palmMute: 8 chugging eighth-notes alternating root and fifth — heavy-metal +// palm-mute feel (think Doom Eternal / Megadeth). +func palmMute(root, beat float64) []melodyNote { + out := make([]melodyNote, 0, 8) + half := beat * 0.5 + fifth := root * intPerf5th + for i := 0; i < 8; i++ { + f := root + if i == 4 || i == 6 { + f = fifth + } + out = append(out, ns(f, half*0.45, half)) + } + return out } -// ── song builders ────────────────────────────────────────────────── +// arpUpDown: chord up-and-down across 8 sixteenths (1-3-5-8-5-3-1-3) — Mario +// chiptune feel. +func arpUpDown(chord [3]float64, beat float64) []melodyNote { + s := beat * 0.5 + return []melodyNote{ + ns(chord[0], s*0.85, s), ns(chord[1], s*0.85, s), + ns(chord[2], s*0.85, s), ns(chord[0]*2, s*0.85, s), + ns(chord[2], s*0.85, s), ns(chord[1], s*0.85, s), + ns(chord[0], s*0.85, s), ns(chord[1], s*0.85, s), + } +} -// buildRockSong creates a 4-bar loop with power-chord pads, sub-bass, and a -// driving melody line. BPM is fast; every slot is a 16th-note. -func buildRockSong( - bassOct [4]float64, chords [4][3]float64, melody [4][]melodyNote, - step float64, -) []melodyNote { - var out []melodyNote - for i := 0; i < 4; i++ { - // Sub-bass: low octave, long sustain so drone + bass overlap - out = append(out, bassNote(bassOct[i]/2, step*7.9, step*8)) - // Main bass hits on each beat - out = append(out, bassNote(bassOct[i], step*3.8, step*4)) - // Power chord stab (sustains across the measure) - out = append(out, powerArp(chords[i], step*3.5, step)...) - // Melody top-line - out = append(out, melody[i]...) +// walkBass: jazz quarter-note walking pattern root → 3rd → 5th → 3rd. +func walkBass(root, beat float64) []melodyNote { + return []melodyNote{ + ns(root, beat*0.92, beat), + ns(root*intMajor3rd, beat*0.92, beat), + ns(root*intPerf5th, beat*0.92, beat), + ns(root*intMajor3rd, beat*0.92, beat), } - return out } -// buildDriveMelody: straight eighth-note drive, no swing. Each note is a short -// punch stab so the line stays rhythmic and aggressive. -func buildDriveMelody(freqs []float64, step float64) []melodyNote { - var out []melodyNote - for _, freq := range freqs { - out = append(out, ns(freq, step*0.85, step)) +// chromDesc: 8-note chromatic descent from 'top' — Bond / Pink Panther feel. +func chromDesc(top, beat float64) []melodyNote { + out := make([]melodyNote, 0, 8) + step := beat * 0.5 + for i := 0; i < 8; i++ { + f := top * math.Pow(2, -float64(i)/12.0) + out = append(out, ns(f, step*0.6, step)) } return out } -// buildStabMelody: short 16th-note stabs with gaps for air. Perfect for fast -// drum-and-bass, hardstyle, or industrial grooves. -func buildStabMelody(freqs []float64, step float64) []melodyNote { +// fanfareStab: a punchy orchestral chord-hit held across 'beats' beats. +// Three voices land together because each rings for most of the duration. +func fanfareStab(chord [3]float64, beats, beat float64) []melodyNote { + dur := beats * beat + return []melodyNote{ + ns(chord[0], dur*0.9, dur*0.34), + ns(chord[1], dur*0.7, dur*0.33), + ns(chord[2], dur*0.5, dur*0.33), + } +} + +// reverseKick: hardstyle reverse-bass effect — a sub-octave thump followed by +// a short octave-up snap (the "wub" before the kick). +func reverseKick(root, beat float64) []melodyNote { + return []melodyNote{ + ns(root/2, beat*0.85, beat*0.5), + ns(root, beat*0.45, beat*0.5), + } +} + +// bossaBass: bossa nova bass — dotted-quarter root, eighth fifth, repeat. +func bossaBass(root, beat float64) []melodyNote { + fifth := root * intPerf5th + return []melodyNote{ + ns(root, beat*1.4, beat*1.5), + ns(fifth, beat*0.45, beat*0.5), + ns(root, beat*1.4, beat*1.5), + ns(fifth, beat*0.45, beat*0.5), + } +} + +// dubSwell: very long sustained bass — held across the whole bar. Caller +// passes the actual played frequency (no implicit octave-down) so we don't +// drop below ~30 Hz, where most speakers reproduce nothing but clicks. +func dubSwell(freq, beats, beat float64) []melodyNote { + return []melodyNote{ns(freq, beats*beat*0.95, beats*beat)} +} + +// alienSlide: 4 sliding atonal arpeggio notes spaced by minor-3rds and tritones. +func alienSlide(root, beat float64) []melodyNote { + tritone := math.Pow(2, 6.0/12) + s := beat * 0.5 + return []melodyNote{ + ns(root, s*0.85, s), + ns(root*intMinor3rd, s*0.85, s), + ns(root*tritone, s*0.85, s), + ns(root*intMinor7th, s*0.85, s), + } +} + +// concat flattens several voices into one note slice. +func concat(parts ...[]melodyNote) []melodyNote { var out []melodyNote - for i, freq := range freqs { - // On-beat stabs hit hard; off-beat ghosts are tighter - dur := step * 0.4 - if i%2 == 0 { - dur = step * 0.8 - } - out = append(out, ns(freq, dur, step)) + for _, p := range parts { + out = append(out, p...) } return out } -// ── drum-pattern helpers ──────────────────────────────────────────── +// ── drum patterns ───────────────────────────────────────────────── +// Each is a 16-step bar (one entry per 16th-note at the preset BPM). -func pat(names ...string) []string { - return names -} +func pat(names ...string) []string { return names } + +var ( + // Synth-pop punch — kick on 1+4+8, claps on 16. + drumPop = pat("kick", "_", "hat", "kick", "snare", "_", "hat", "_", "kick", "kick", "hat", "_", "snare", "_", "hat", "clap") + // Four-on-the-floor dance — kick every quarter, hat offbeat. + drumFour = pat("kick", "_", "hat", "_", "kick", "_", "hat", "_", "kick", "_", "hat", "_", "kick", "_", "hat", "_") + // Heavy rock — straight 4/4 with offbeat hats (Duke Nukem feel). + drumRock = pat("kick", "_", "hat", "_", "snare", "_", "hat", "_", "kick", "_", "hat", "_", "snare", "_", "hat", "kick") + // Industrial pulse — hammering double-kick, syncopated snare. + drumPulse = pat("kick", "kick", "_", "hat", "snare", "_", "kick", "hat", "kick", "_", "_", "hat", "snare", "kick", "_", "hat") + // Hardstyle — kick on every beat plus the offbeats (gabber). + drumHardstyle = pat("kick", "_", "_", "kick", "kick", "_", "_", "kick", "kick", "_", "_", "kick", "kick", "kick", "_", "kick") + // DnB amen-break — kick on 1+11, snare with ghosted "skips" on 4. + drumDnb = pat("kick", "_", "hat", "kick", "snare", "hat", "_", "snare", "_", "kick", "hat", "_", "snare", "_", "hat", "kick") + // Bossa nova clave (3-2 son) — Latin syncopation. + drumBossa = pat("kick", "_", "hat", "_", "_", "_", "hat", "snare", "_", "kick", "hat", "_", "snare", "_", "hat", "_") + // Boom-bap — hip-hop / film-noir groove. + drumBoom = pat("kick", "_", "_", "hat", "snare", "_", "_", "hat", "_", "_", "kick", "hat", "snare", "_", "kick", "hat") + // Dub — kick on 1, snare on 9, hi-hat on 16. Spacious. + drumDub = pat("kick", "_", "_", "_", "_", "_", "_", "_", "snare", "_", "_", "_", "_", "_", "_", "hat") + // Ambient — single soft kick + sparse clap. Mostly silence. + drumAmbient = pat("kick", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "_", "clap", "_", "_", "_") + // March — kick/snare alternate, no hat (cosmos military feel). + drumMarch = pat("kick", "_", "_", "_", "snare", "_", "_", "_", "kick", "_", "_", "_", "snare", "_", "_", "_") + // Bell tolls — only deep kicks, very sparse (cathedral). + drumBell = pat("kick", "_", "_", "_", "_", "_", "_", "_", "kick", "_", "_", "_", "_", "_", "_", "_") + // Latin funk — busy clave with conga-like accents. + drumLatin = pat("kick", "hat", "_", "hat", "snare", "kick", "hat", "_", "kick", "hat", "_", "hat", "snare", "_", "hat", "kick") + // Chiptune — bouncy NES drum-machine: kick+snare on every other 16th. + drumChip = pat("kick", "snare", "kick", "snare", "kick", "snare", "kick", "snare", "kick", "snare", "kick", "snare", "kick", "snare", "kick", "snare") + // Spy — sneaky offbeat snare with double-kick pickup. + drumSpy = pat("kick", "_", "hat", "_", "_", "snare", "hat", "_", "kick", "kick", "hat", "_", "snare", "_", "hat", "snare") + // Organic / biomech — irregular limb-like accents. + drumOrganic = pat("kick", "_", "hat", "kick", "_", "snare", "kick", "_", "_", "kick", "hat", "_", "snare", "_", "kick", "hat") + // Metal chug — double-kick galloping metal rhythm (plasma normal). + drumChug = pat("kick", "kick", "_", "kick", "snare", "_", "kick", "_", "kick", "kick", "_", "kick", "snare", "kick", "kick", "kick") + // Gated 80s electro — tight punchy clap on 5+13 (synthwave). + drumElectro = pat("kick", "_", "_", "_", "clap", "_", "hat", "_", "kick", "kick", "_", "_", "clap", "_", "hat", "snare") + // Wild rock — extra-busy version of drumRock for wild mode. + drumRockWild = pat("kick", "kick", "hat", "kick", "snare", "_", "hat", "kick", "kick", "kick", "hat", "kick", "snare", "_", "hat", "kick") +) + +// ── theme songs ─────────────────────────────────────────────────── +// +// Each theme builds two ambient presets (normal + wild). Wild is the same +// musical character as normal but faster, denser, brighter — never a +// different song, so the listener always hears "the theme" intensifying. // ════════════════════════════════════════════════════════════════════ -// NEON – synth-pop banger, C major, 150/220 +// NEON – synth-pop banger, C major, Outrun pumping octave bass // ════════════════════════════════════════════════════════════════════ func soundsNeon() themeSounds { var s themeSounds @@ -194,47 +335,37 @@ func soundsNeon() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 880, 261.63, 0.16, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 180, 90, 0.12, 0.1 - c, f, g1, c2 := power(65.41), power(87.31), power(98.00), power(130.81) - - normalMel := buildRockSong( - [4]float64{130.81, 174.61, 196.00, 130.81}, - [4][3]float64{c, f, g1, c2}, - [4][]melodyNote{ - buildDriveMelody([]float64{523.25, 587.33, 659.25, 783.99}, 0.25), - buildDriveMelody([]float64{698.46, 783.99, 880.00, 1046.5}, 0.25), - buildDriveMelody([]float64{783.99, 880.00, 1046.5, 783.99}, 0.25), - buildDriveMelody([]float64{523.25, 659.25, 783.99, 1046.5}, 0.25), - }, 0.25) - - wildMel := buildRockSong( - [4]float64{261.63, 349.23, 392.00, 261.63}, - [4][3]float64{c, f, g1, c2}, - [4][]melodyNote{ - buildStabMelody([]float64{1046.5, 1174.66, 1318.5, 1567.98}, 0.125), - buildStabMelody([]float64{1396.92, 1567.98, 1760.00, 2093.0}, 0.125), - buildStabMelody([]float64{1567.98, 1760.00, 2093.0, 1567.98}, 0.125), - buildStabMelody([]float64{1046.5, 1318.5, 1567.98, 2093.0}, 0.125), - }, 0.125) - + // Hook: sparkly major arpeggio that resolves down — C-E-G-C-A-G-E-C feel. + beat := 0.4 // 150 BPM quarter + mel := concat( + octaveBass(130.81, beat), padHold(major(261.63), 4, beat), + hook(beat, 1046.5, 0.5, 783.99, 0.5, 659.25, 0.5, 1046.5, 0.5, + 880.00, 0.5, 783.99, 0.5, 659.25, 0.5, 523.25, 0.5), + octaveBass(174.61, beat), padHold(major(349.23), 4, beat), + hook(beat, 1396.92, 0.5, 1046.5, 0.5, 880.00, 0.5, 1396.92, 0.5, + 1174.66, 0.5, 1046.5, 0.5, 880.00, 0.5, 698.46, 0.5), + ) + wbeat := 0.273 // 220 BPM + wmel := concat( + octaveBass(261.63, wbeat), padHold(major(523.25), 4, wbeat), + hook(wbeat, 1046.5, 0.25, 1318.5, 0.25, 1567.98, 0.25, 2093.0, 0.25, + 1567.98, 0.25, 1318.5, 0.25, 1046.5, 0.25, 783.99, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.028, BPM: 150, Wave: "square", - DroneFreqs: []float64{130.81, 196.00, 261.63}, - Attack: 0.02, Release: 0.15, CutoffMin: 1200, CutoffMax: 5000, - Drums: pat("kick", "_", "hat", "kick", "snare", "kick", "hat", "kick", "kick", "kick", "hat", "snare", "kick", "_", "hat", "clap"), - Melody: normalMel, + Gain: 0.03, BPM: 150, Wave: "square", + DroneFreqs: []float64{130.81, 196.00, 261.63}, Attack: 0.02, Release: 0.15, + CutoffMin: 1200, CutoffMax: 5000, Drums: drumPop, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 220, Wave: "square", - DroneFreqs: []float64{130.81, 196.00, 261.63, 392.00}, - Attack: 0.02, Release: 0.08, CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 12, - Drums: pat("kick", "kick", "hat", "kick", "snare", "kick", "hat", "kick", "kick", "kick", "hat", "snare", "kick", "kick", "hat", "clap"), - Melody: wildMel, + Gain: 0.06, BPM: 220, Wave: "square", + DroneFreqs: []float64{130.81, 261.63, 392.00, 523.25}, Attack: 0.02, Release: 0.08, + CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 12, Drums: drumPop, Melody: wmel, } return s } // ════════════════════════════════════════════════════════════════════ -// TERMINAL – dark industrial, E minor, 160/240 +// TERMINAL – industrial techno, E minor, hammering pulse // ════════════════════════════════════════════════════════════════════ func soundsTerminal() themeSounds { var s themeSounds @@ -245,44 +376,37 @@ func soundsTerminal() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 900, 400, 0.14, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 200, 100, 0.1, 0.1 - e, b, a := power(82.41), power(61.74), power(110.00) - - normalMel := buildRockSong( - [4]float64{164.81, 123.47, 220.00, 164.81}, [4][3]float64{e, b, a, e}, - [4][]melodyNote{ - buildDriveMelody([]float64{329.63, 392.00, 440.00, 523.25}, 0.233), - buildDriveMelody([]float64{246.94, 293.66, 329.63, 392.00}, 0.233), - buildDriveMelody([]float64{440.00, 523.25, 587.33, 440.00}, 0.233), - buildDriveMelody([]float64{329.63, 392.00, 493.88, 329.63}, 0.233), - }, 0.233) - wildMel := buildRockSong( - [4]float64{329.63, 246.94, 440.00, 329.63}, [4][3]float64{e, b, a, e}, - [4][]melodyNote{ - buildStabMelody([]float64{659.25, 783.99, 880.00, 1046.5}, 0.117), - buildStabMelody([]float64{493.88, 587.33, 659.25, 783.99}, 0.117), - buildStabMelody([]float64{880.00, 1046.5, 1174.66, 880.00}, 0.117), - buildStabMelody([]float64{659.25, 783.99, 987.77, 659.25}, 0.117), - }, 0.117) - + // Hook: dissonant rising tritone stab — Hotline Miami / industrial menace. + beat := 0.375 // 160 BPM + mel := concat( + palmMute(82.41, beat), padHold(minor(164.81), 4, beat), + hook(beat, 329.63, 0.5, 392.00, 0.5, 466.16, 0.5, 329.63, 0.5, + 466.16, 0.5, 622.25, 0.5, 466.16, 0.5, 329.63, 0.5), + palmMute(110.00, beat), padHold(minor(220.00), 4, beat), + hook(beat, 440.00, 0.5, 523.25, 0.5, 622.25, 0.5, 440.00, 0.5, + 622.25, 0.5, 783.99, 0.5, 622.25, 0.5, 440.00, 0.5), + ) + wbeat := 0.353 // 170 BPM, but denser stabs + wmel := concat( + palmMute(164.81, wbeat), + hook(wbeat, 659.25, 0.25, 783.99, 0.25, 932.33, 0.25, 659.25, 0.25, + 932.33, 0.25, 1244.51, 0.25, 932.33, 0.25, 659.25, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.028, BPM: 160, Wave: "square", - DroneFreqs: []float64{82.41, 123.47, 164.81}, - Attack: 0.02, Release: 0.1, CutoffMin: 1000, CutoffMax: 5000, - Drums: pat("kick", "_", "hat", "kick", "snare", "kick", "hat", "snare", "kick", "kick", "hat", "kick", "snare", "_", "hat", "clap"), - Melody: normalMel, + Gain: 0.03, BPM: 160, Wave: "sawtooth", + DroneFreqs: []float64{82.41, 123.47, 164.81}, Attack: 0.02, Release: 0.1, + CutoffMin: 1000, CutoffMax: 5000, Drums: drumPulse, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 240, Wave: "square", - DroneFreqs: []float64{164.81, 246.94, 329.63, 440.00}, - Attack: 0.02, Release: 0.05, CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 15, - Drums: pat("kick", "kick", "hat", "kick", "snare", "kick", "hat", "snare", "kick", "kick", "hat", "kick", "snare", "kick", "hat", "clap"), - Melody: wildMel, + Gain: 0.065, BPM: 170, Wave: "sawtooth", + DroneFreqs: []float64{82.41, 164.81, 246.94, 329.63}, Attack: 0.02, Release: 0.05, + CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 18, Drums: drumPulse, Melody: wmel, } return s } // ════════════════════════════════════════════════════════════════════ -// SYNTHWAVE – retro-future racing, A minor, 140/200 +// SYNTHWAVE – Carpenter Brut "Turbo Killer" anthem rock, A minor // ════════════════════════════════════════════════════════════════════ func soundsSynthwave() themeSounds { var s themeSounds @@ -293,44 +417,43 @@ func soundsSynthwave() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 440, 110, 0.17, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "sine", 150, 75, 0.14, 0.09 - am, f, c, g := minor(55.00), power(87.31), power(65.41), power(98.00) - - normalMel := buildRockSong( - [4]float64{110.00, 174.61, 130.81, 196.00}, [4][3]float64{am, f, c, g}, - [4][]melodyNote{ - buildDriveMelody([]float64{220.00, 261.63, 293.66, 349.23}, 0.268), - buildDriveMelody([]float64{349.23, 392.00, 440.00, 523.25}, 0.268), - buildDriveMelody([]float64{261.63, 329.63, 392.00, 440.00}, 0.268), - buildDriveMelody([]float64{220.00, 293.66, 349.23, 392.00}, 0.268), - }, 0.268) - wildMel := buildRockSong( - [4]float64{220.00, 349.23, 261.63, 392.00}, [4][3]float64{am, f, c, g}, - [4][]melodyNote{ - buildStabMelody([]float64{440.00, 523.25, 587.33, 698.46}, 0.134), - buildStabMelody([]float64{698.46, 783.99, 880.00, 1046.5}, 0.134), - buildStabMelody([]float64{523.25, 659.25, 783.99, 880.00}, 0.134), - buildStabMelody([]float64{440.00, 587.33, 698.46, 783.99}, 0.134), - }, 0.134) - + // Driving Am-F-C-G with a sawtooth-stab lead riff. Action-rock not chill. + beat := 0.4 // 150 BPM — punchy synth-anthem + mel := concat( + octaveBass(110.00, beat), padHold(minor(220.00), 4, beat), + hook(beat, 880.00, 0.25, 1046.5, 0.25, 1318.5, 0.5, 1046.5, 0.25, 880.00, 0.25, + 659.25, 0.5, 880.00, 0.5, 1046.5, 1.0), + octaveBass(87.31, beat), padHold(major(174.61), 4, beat), + hook(beat, 698.46, 0.25, 880.00, 0.25, 1046.5, 0.5, 880.00, 0.25, 698.46, 0.25, + 523.25, 0.5, 698.46, 0.5, 880.00, 1.0), + octaveBass(130.81, beat), padHold(major(261.63), 4, beat), + hook(beat, 1046.5, 0.25, 1318.5, 0.25, 1567.98, 0.5, 1318.5, 0.25, 1046.5, 0.25, + 783.99, 0.5, 1046.5, 0.5, 1318.5, 1.0), + octaveBass(98.00, beat), padHold(major(196.00), 4, beat), + hook(beat, 783.99, 0.25, 987.77, 0.25, 1174.66, 0.5, 987.77, 0.25, 783.99, 0.25, + 587.33, 0.5, 783.99, 0.5, 987.77, 1.0), + ) + wbeat := 0.316 // 190 BPM + wmel := concat( + octaveBass(220.00, wbeat), + hook(wbeat, 1760.00, 0.25, 2093.0, 0.25, 2637.0, 0.25, 2093.0, 0.25, + 1760.00, 0.25, 1318.5, 0.25, 1760.00, 0.25, 2093.0, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.03, BPM: 140, Wave: "square", - DroneFreqs: []float64{110.00, 174.61, 220.00}, - Attack: 0.02, Release: 0.15, CutoffMin: 1200, CutoffMax: 5000, - Drums: pat("kick", "hat", "kick", "hat", "snare", "hat", "kick", "hat", "kick", "hat", "snare", "hat", "kick", "hat", "kick", "clap"), - Melody: normalMel, + Gain: 0.032, BPM: 150, Wave: "sawtooth", + DroneFreqs: []float64{110.00, 174.61, 220.00}, Attack: 0.02, Release: 0.08, + CutoffMin: 1200, CutoffMax: 5500, Drums: drumElectro, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.06, BPM: 200, Wave: "square", - DroneFreqs: []float64{220.00, 349.23, 440.00, 523.25}, - Attack: 0.02, Release: 0.06, CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 12, - Drums: pat("kick", "hat", "kick", "hat", "snare", "hat", "kick", "hat", "kick", "hat", "snare", "hat", "kick", "kick", "kick", "clap"), - Melody: wildMel, + Gain: 0.065, BPM: 190, Wave: "sawtooth", + DroneFreqs: []float64{220.00, 349.23, 440.00, 523.25}, Attack: 0.02, Release: 0.05, + CutoffMin: 2400, CutoffMax: 7500, DetuneCents: 14, Drums: drumElectro, Melody: wmel, } return s } // ════════════════════════════════════════════════════════════════════ -// PLASMA – white-hot fusion drive, F# minor, 170/250 +// PLASMA – Doom Eternal djent, F# minor, drop-tuned palm-mute chug // ════════════════════════════════════════════════════════════════════ func soundsPlasma() themeSounds { var s themeSounds @@ -341,44 +464,37 @@ func soundsPlasma() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 740, 246.94, 0.17, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 120, 60, 0.1, 0.1 - fsm, bm, cs, d := minor(92.50), minor(61.74), power(69.30), power(73.42) - - normalMel := buildRockSong( - [4]float64{185.00, 246.94, 138.59, 146.83}, [4][3]float64{fsm, bm, cs, d}, - [4][]melodyNote{ - buildDriveMelody([]float64{369.99, 440.00, 493.88, 554.37}, 0.22), - buildDriveMelody([]float64{493.88, 554.37, 587.33, 369.99}, 0.22), - buildDriveMelody([]float64{277.18, 329.63, 369.99, 440.00}, 0.22), - buildDriveMelody([]float64{293.66, 349.23, 392.00, 293.66}, 0.22), - }, 0.22) - wildMel := buildRockSong( - [4]float64{369.99, 493.88, 277.18, 293.66}, [4][3]float64{fsm, bm, cs, d}, - [4][]melodyNote{ - buildStabMelody([]float64{739.99, 880.00, 987.77, 1108.73}, 0.11), - buildStabMelody([]float64{987.77, 1108.73, 1174.66, 739.99}, 0.11), - buildStabMelody([]float64{554.37, 659.25, 739.99, 880.00}, 0.11), - buildStabMelody([]float64{587.33, 698.46, 783.99, 587.33}, 0.11), - }, 0.11) - + // Djent: low palm-mute chugs against a screaming high lead. + beat := 0.4 // 150 BPM — heavy not blistering + mel := concat( + palmMute(46.25, beat), padHold(minor(92.50), 4, beat), + hook(beat, 369.99, 0.5, 0, 0.5, 369.99, 0.5, 440.00, 0.5, + 554.37, 0.5, 440.00, 0.5, 369.99, 1.0), + palmMute(46.25, beat), padHold(minor(92.50), 4, beat), + hook(beat, 369.99, 0.5, 440.00, 0.5, 554.37, 0.5, 739.99, 0.5, + 659.25, 0.5, 554.37, 0.5, 440.00, 0.5, 369.99, 0.5), + ) + wbeat := 0.333 // 180 BPM thrash + wmel := concat( + palmMute(92.50, wbeat), + hook(wbeat, 739.99, 0.25, 880.00, 0.25, 1108.73, 0.25, 1480.00, 0.25, + 1108.73, 0.25, 880.00, 0.25, 739.99, 0.25, 554.37, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.028, BPM: 170, Wave: "square", - DroneFreqs: []float64{92.50, 138.59, 185.00}, - Attack: 0.02, Release: 0.12, CutoffMin: 1400, CutoffMax: 5500, - Drums: pat("kick", "_", "hat", "kick", "snare", "kick", "hat", "snare", "kick", "kick", "hat", "kick", "snare", "_", "hat", "clap"), - Melody: normalMel, + Gain: 0.032, BPM: 150, Wave: "square", + DroneFreqs: []float64{46.25, 92.50, 138.59}, Attack: 0.01, Release: 0.08, + CutoffMin: 800, CutoffMax: 5500, Drums: drumChug, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 250, Wave: "square", - DroneFreqs: []float64{92.50, 138.59, 185.00, 277.18}, - Attack: 0.02, Release: 0.04, CutoffMin: 2400, CutoffMax: 7500, DetuneCents: 15, - Drums: pat("kick", "kick", "hat", "kick", "snare", "kick", "hat", "snare", "kick", "kick", "hat", "kick", "snare", "kick", "hat", "clap"), - Melody: wildMel, + Gain: 0.07, BPM: 180, Wave: "square", + DroneFreqs: []float64{46.25, 92.50, 138.59, 277.18}, Attack: 0.01, Release: 0.04, + CutoffMin: 2400, CutoffMax: 8000, DetuneCents: 22, Drums: drumChug, Melody: wmel, } return s } // ════════════════════════════════════════════════════════════════════ -// BRUTALIST – concrete demolition, C minor, 140/210 +// BRUTALIST – Akira-style minimalist techno, C minor, concrete pulse // ════════════════════════════════════════════════════════════════════ func soundsBrutalist() themeSounds { var s themeSounds @@ -389,43 +505,37 @@ func soundsBrutalist() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 660, 220, 0.15, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 200, 100, 0.1, 0.1 - cm, gm, dm := minor(65.41), minor(98.00), minor(73.42) - normalMel := buildRockSong( - [4]float64{130.81, 196.00, 146.83, 130.81}, [4][3]float64{cm, gm, dm, cm}, - [4][]melodyNote{ - buildDriveMelody([]float64{261.63, 311.13, 392.00, 523.25}, 0.268), - buildDriveMelody([]float64{392.00, 415.30, 523.25, 392.00}, 0.268), - buildDriveMelody([]float64{293.66, 349.23, 392.00, 293.66}, 0.268), - buildDriveMelody([]float64{261.63, 311.13, 392.00, 523.25}, 0.268), - }, 0.268) - wildMel := buildRockSong( - [4]float64{261.63, 196.00, 146.83, 261.63}, [4][3]float64{cm, gm, dm, cm}, - [4][]melodyNote{ - buildStabMelody([]float64{523.25, 622.25, 783.99, 1046.5}, 0.134), - buildStabMelody([]float64{783.99, 830.61, 1046.5, 783.99}, 0.134), - buildStabMelody([]float64{587.33, 698.46, 783.99, 587.33}, 0.134), - buildStabMelody([]float64{523.25, 622.25, 783.99, 1046.5}, 0.134), - }, 0.134) - + // Hook: pulse-step bass with metallic stab leads. Cm-Ab-Gm-Fm. + beat := 0.429 // 140 BPM driving + mel := concat( + palmMute(65.41, beat), padHold(minor(130.81), 4, beat), + hook(beat, 523.25, 0.5, 0, 0.5, 622.25, 0.5, 0, 0.5, + 783.99, 1.0, 622.25, 1.0), + palmMute(51.91, beat), padHold(major(103.83), 4, beat), + hook(beat, 415.30, 0.5, 0, 0.5, 622.25, 0.5, 0, 0.5, + 830.61, 1.0, 622.25, 1.0), + ) + wbeat := 0.375 // 160 BPM + wmel := concat( + palmMute(130.81, wbeat), + hook(wbeat, 1046.5, 0.25, 1244.5, 0.25, 1567.98, 0.25, 1046.5, 0.25, + 1244.5, 0.25, 1046.5, 0.25, 830.61, 0.25, 622.25, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.03, BPM: 140, Wave: "square", - DroneFreqs: []float64{65.41, 98.00, 130.81}, - Attack: 0.02, Release: 0.15, CutoffMin: 1200, CutoffMax: 5000, - Drums: pat("kick", "_", "hat", "kick", "snare", "kick", "hat", "kick", "kick", "kick", "hat", "snare", "kick", "_", "hat", "clap"), - Melody: normalMel, + Gain: 0.032, BPM: 140, Wave: "sawtooth", + DroneFreqs: []float64{65.41, 98.00, 130.81}, Attack: 0.02, Release: 0.1, + CutoffMin: 1200, CutoffMax: 5500, Drums: drumPulse, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.065, BPM: 210, Wave: "square", - DroneFreqs: []float64{65.41, 98.00, 130.81, 196.00}, - Attack: 0.02, Release: 0.05, CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 18, - Drums: pat("kick", "kick", "hat", "kick", "snare", "kick", "hat", "kick", "kick", "kick", "hat", "snare", "kick", "kick", "hat", "clap"), - Melody: wildMel, + Gain: 0.075, BPM: 180, Wave: "sawtooth", + DroneFreqs: []float64{65.41, 98.00, 130.81, 196.00}, Attack: 0.02, Release: 0.04, + CutoffMin: 2400, CutoffMax: 7500, DetuneCents: 20, Drums: drumPulse, Melody: wmel, } return s } // ════════════════════════════════════════════════════════════════════ -// VOLCANO – molten hardstyle, D minor, 160/240 +// VOLCANO – hardstyle / gabber, D minor, reverse-bass kicks // ════════════════════════════════════════════════════════════════════ func soundsVolcano() themeSounds { var s themeSounds @@ -436,43 +546,39 @@ func soundsVolcano() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 440, 146.83, 0.18, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "triangle", 130, 65, 0.12, 0.09 - dm, am, gm := minor(73.42), power(110.00), power(98.00) - normalMel := buildRockSong( - [4]float64{146.83, 220.00, 196.00, 146.83}, [4][3]float64{dm, am, gm, dm}, - [4][]melodyNote{ - buildDriveMelody([]float64{293.66, 349.23, 440.00, 493.88}, 0.233), - buildDriveMelody([]float64{440.00, 493.88, 587.33, 440.00}, 0.233), - buildDriveMelody([]float64{392.00, 440.00, 493.88, 392.00}, 0.233), - buildDriveMelody([]float64{293.66, 349.23, 440.00, 587.33}, 0.233), - }, 0.233) - wildMel := buildRockSong( - [4]float64{293.66, 440.00, 392.00, 293.66}, [4][3]float64{dm, am, gm, dm}, - [4][]melodyNote{ - buildStabMelody([]float64{587.33, 698.46, 880.00, 987.77}, 0.117), - buildStabMelody([]float64{880.00, 987.77, 1174.66, 880.00}, 0.117), - buildStabMelody([]float64{783.99, 880.00, 987.77, 783.99}, 0.117), - buildStabMelody([]float64{587.33, 698.46, 880.00, 1174.66}, 0.117), - }, 0.117) - + // Reverse-bass + screech lead. Dm-Bb-F-C — classic hardstyle. + beat := 0.4 // 150 BPM + mel := concat( + reverseKick(73.42, beat), reverseKick(73.42, beat), + reverseKick(73.42, beat), reverseKick(73.42, beat), + hook(beat, 587.33, 0.5, 698.46, 0.5, 880.00, 0.5, 1174.66, 0.5, + 880.00, 0.5, 698.46, 0.5, 587.33, 1.0), + reverseKick(58.27, beat), reverseKick(58.27, beat), + reverseKick(87.31, beat), reverseKick(65.41, beat), + hook(beat, 466.16, 0.5, 587.33, 0.5, 698.46, 0.5, 880.00, 0.5, + 698.46, 0.5, 587.33, 0.5, 466.16, 1.0), + ) + wbeat := 0.333 // 180 BPM + wmel := concat( + reverseKick(146.83, wbeat), reverseKick(146.83, wbeat), + hook(wbeat, 1174.66, 0.25, 1396.92, 0.25, 1760.00, 0.25, 2349.32, 0.25, + 1760.00, 0.25, 1396.92, 0.25, 1174.66, 0.25, 880.00, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.03, BPM: 160, Wave: "square", - DroneFreqs: []float64{73.42, 110.00, 146.83}, - Attack: 0.02, Release: 0.15, CutoffMin: 1200, CutoffMax: 5000, - Drums: pat("kick", "hat", "kick", "hat", "snare", "kick", "hat", "snare", "kick", "kick", "hat", "kick", "snare", "_", "hat", "clap"), - Melody: normalMel, + Gain: 0.032, BPM: 150, Wave: "sawtooth", + DroneFreqs: []float64{73.42, 110.00, 146.83}, Attack: 0.01, Release: 0.08, + CutoffMin: 1200, CutoffMax: 5500, Drums: drumHardstyle, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.065, BPM: 240, Wave: "square", - DroneFreqs: []float64{73.42, 110.00, 146.83, 196.00}, - Attack: 0.02, Release: 0.05, CutoffMin: 2000, CutoffMax: 7000, DetuneCents: 12, - Drums: pat("kick", "kick", "hat", "kick", "snare", "kick", "hat", "snare", "kick", "kick", "hat", "kick", "snare", "kick", "hat", "clap"), - Melody: wildMel, + Gain: 0.07, BPM: 180, Wave: "sawtooth", + DroneFreqs: []float64{73.42, 110.00, 146.83, 220.00}, Attack: 0.01, Release: 0.04, + CutoffMin: 2400, CutoffMax: 8000, DetuneCents: 18, Drums: drumHardstyle, Melody: wmel, } return s } // ════════════════════════════════════════════════════════════════════ -// AURORA – arctic storm surge, G major, 130/190 +// AURORA – arctic anthem rock, G major, soaring stadium lead // ════════════════════════════════════════════════════════════════════ func soundsAurora() themeSounds { var s themeSounds @@ -483,43 +589,43 @@ func soundsAurora() themeSounds { s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 523, 196, 0.18, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "triangle", 180, 90, 0.12, 0.09 - gm, d, em, c := power(98.00), power(73.42), power(82.41), power(65.41) - normalMel := buildRockSong( - [4]float64{196.00, 146.83, 164.81, 130.81}, [4][3]float64{gm, d, em, c}, - [4][]melodyNote{ - buildDriveMelody([]float64{392.00, 440.00, 493.88, 587.33}, 0.288), - buildDriveMelody([]float64{293.66, 349.23, 392.00, 440.00}, 0.288), - buildDriveMelody([]float64{329.63, 392.00, 440.00, 523.25}, 0.288), - buildDriveMelody([]float64{261.63, 329.63, 392.00, 523.25}, 0.288), - }, 0.288) - wildMel := buildRockSong( - [4]float64{196.00, 146.83, 164.81, 130.81}, [4][3]float64{gm, d, em, c}, - [4][]melodyNote{ - buildStabMelody([]float64{783.99, 880.00, 987.77, 1174.66}, 0.144), - buildStabMelody([]float64{587.33, 698.46, 783.99, 880.00}, 0.144), - buildStabMelody([]float64{659.25, 783.99, 880.00, 1046.5}, 0.144), - buildStabMelody([]float64{523.25, 659.25, 783.99, 1046.5}, 0.144), - }, 0.144) - + // Driving G-major anthem (G-Em-C-D) with a soaring fist-pump lead hook. + beat := 0.462 // 130 BPM + mel := concat( + octaveBass(98.00, beat), padHold(major(196.00), 4, beat), + hook(beat, 783.99, 0.5, 880.00, 0.5, 987.77, 0.5, 1174.66, 0.5, + 987.77, 0.5, 880.00, 1.0, 783.99, 0.5), + octaveBass(82.41, beat), padHold(minor(164.81), 4, beat), + hook(beat, 659.25, 0.5, 783.99, 0.5, 880.00, 0.5, 987.77, 0.5, + 880.00, 0.5, 783.99, 1.0, 659.25, 0.5), + octaveBass(65.41, beat), padHold(major(130.81), 4, beat), + hook(beat, 783.99, 0.5, 880.00, 0.5, 987.77, 0.5, 1174.66, 0.5, + 1318.5, 1.0, 1174.66, 1.0), + octaveBass(73.42, beat), padHold(major(146.83), 4, beat), + hook(beat, 880.00, 0.5, 987.77, 0.5, 1174.66, 0.5, 1318.5, 0.5, + 1174.66, 0.5, 987.77, 1.0, 880.00, 0.5), + ) + wbeat := 0.353 // 170 BPM thrash anthem + wmel := concat( + octaveBass(196.00, wbeat), + hook(wbeat, 1567.98, 0.25, 1760.00, 0.25, 1975.53, 0.25, 2349.32, 0.25, + 1975.53, 0.25, 1760.00, 0.25, 1567.98, 0.25, 1318.5, 0.25), + ) s.Ambient.Normal = ambientPreset{ - Gain: 0.03, BPM: 130, Wave: "square", - DroneFreqs: []float64{98.00, 146.83, 196.00}, - Attack: 0.02, Release: 0.18, CutoffMin: 1000, CutoffMax: 4500, - Drums: pat("kick", "_", "hat", "_", "kick", "_", "hat", "_", "kick", "_", "hat", "_", "snare", "_", "hat", "_"), - Melody: normalMel, + Gain: 0.032, BPM: 130, Wave: "triangle", + DroneFreqs: []float64{98.00, 146.83, 196.00}, Attack: 0.02, Release: 0.1, + CutoffMin: 1200, CutoffMax: 5500, Drums: drumPop, Melody: mel, } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 190, Wave: "square", - DroneFreqs: []float64{98.00, 146.83, 196.00, 261.63}, - Attack: 0.02, Release: 0.08, CutoffMin: 1800, CutoffMax: 6000, DetuneCents: 12, - Drums: pat("kick", "hat", "kick", "hat", "snare", "hat", "kick", "hat", "kick", "hat", "snare", "hat", "kick", "kick", "hat", "clap"), - Melody: wildMel, + Gain: 0.065, BPM: 170, Wave: "sawtooth", + DroneFreqs: []float64{98.00, 196.00, 293.66, 392.00}, Attack: 0.02, Release: 0.05, + CutoffMin: 2200, CutoffMax: 7500, DetuneCents: 12, Drums: drumPop, Melody: wmel, } return s } // ═══════════════════════════════════════ |
