diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-26 11:47:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-26 11:47:55 +0300 |
| commit | e9bddae89614f12ce7d2d26e70c2c2de3b90590e (patch) | |
| tree | f74bccce3e0c7a4240a680deee82312acd286820 /internal/generator/theme_sounds.go | |
| parent | 44f82e79f0b1a65d9df62c3fed7271affcb0a673 (diff) | |
ambient: polyphonic songs, fix cascaded gain and resume bugs
Rewrite all 19 theme melodies into proper ~10-16 second looping songs
with bass drones, chord pads, and scalar melody lines using the new
'step' field for polyphonic overlap on a monophonic step sequencer.
Fix four bugs that caused total silence:
1. masterGain was faded to preset.gain while each note was ALSO scaled
by preset.gain*0.6, squaring volume to ~-68 dB.
2. fadeMasterTo ramped the gate for 0.5-1.5s, swallowing first notes.
3. startDrones set per-osc gain to 1.0/numDrones, clipping and burying
the melody.
4. AudioContext resume was not awaited; notes fired into a suspended
context and were lost.
Shared.js:
- startEngine() waits for ctx.resume() before scheduling anything.
- masterGain acts as a binary gate (fades to 1.0 over 50ms).
- playMelodyNote uses twin detuned oscillators for richer timbre.
- startDrones scales by preset.gain so drones match melody level.
Theme_sounds.go:
- Add 'step' field to melodyNote struct.
- Add chord-building helpers (major/minor triads).
- Add buildSong() / buildMeasure() for structured 4-bar loops.
- All 19 themes: audible bass (130-220Hz), chord pads (260-520Hz),
melodies (330-1046Hz), real chord progressions, proper BPM.
Diffstat (limited to 'internal/generator/theme_sounds.go')
| -rw-r--r-- | internal/generator/theme_sounds.go | 900 |
1 files changed, 670 insertions, 230 deletions
diff --git a/internal/generator/theme_sounds.go b/internal/generator/theme_sounds.go index c3235dd..045c06a 100644 --- a/internal/generator/theme_sounds.go +++ b/internal/generator/theme_sounds.go @@ -3,18 +3,18 @@ package generator import ( "encoding/json" "html/template" + "math" ) // melodyNote is a single note in a looping ambient melody. type melodyNote struct { Freq float64 `json:"freq"` Dur float64 `json:"dur"` + Step float64 `json:"step,omitempty"` Gain float64 `json:"gain,omitempty"` } // ambientPreset describes a generative ambient background layer for a theme. -// All fields are optional at runtime; missing values should be treated as -// silence or safe defaults by the consumer. type ambientPreset struct { BPM float64 `json:"bpm,omitempty"` PulseInterval float64 `json:"pulseInterval,omitempty"` @@ -77,12 +77,111 @@ type themeSounds struct { Ambient ambientSounds `json:"ambient,omitempty"` } -// n is a concise helper for building a melodyNote. +// ── helpers ──────────────────────────────────────────────────────── + func n(freq, dur float64) melodyNote { return melodyNote{Freq: freq, Dur: dur} } -// themeSoundPresets maps CLI theme names to synth parameters (see themes.go registry). +// ns builds a note that rings for 'dur' but advances the sequencer by 'step'. +// When dur > step, the note overlaps the next one — creating chords on a +// monophonic step sequencer. +func ns(freq, dur, step float64) melodyNote { + return melodyNote{Freq: freq, Dur: dur, Step: step} +} + +// equal-temperament intervals +var ( + intMajor3rd = math.Pow(2, 4.0/12) + intMinor3rd = math.Pow(2, 3.0/12) + intPerf5th = math.Pow(2, 7.0/12) +) + +func major(freq float64) [3]float64 { + return [3]float64{freq, freq * intMajor3rd, freq * intPerf5th} +} +func minor(freq float64) [3]float64 { + return [3]float64{freq, freq * intMinor3rd, freq * intPerf5th} +} + +// mNote returns one note in a measure. +func mNote(freq, dur, step float64) melodyNote { + return ns(freq, dur, step) +} + +// 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 { + return []melodyNote{ + ns(chord[0], dur, step), + ns(chord[1], dur, step), + ns(chord[2], dur, step), + ns(chord[1], dur, step), + } +} + +// concatNotes flattens many melody slices into one. +func concatNotes(groups ...[]melodyNote) []melodyNote { + var out []melodyNote + for _, g := range groups { + out = append(out, g...) + } + return out +} + +// ── song builder ─────────────────────────────────────────────────── + +// buildMeasure creates one measure: bass + chord pad + melody top-line. +// bassFreq: audible bass root (130–260 Hz). +// chord: triad in middle register (260–520 Hz). +// melody: 2–3 scalar notes in upper register (400–1000 Hz). +// Returns notes that sum to ~4.0 s total when stepNorm=0.5s. +func buildMeasure(bassFreq float64, chord [3]float64, melody []float64, step float64) []melodyNote { + // Bass sustains for 7.5×step, giving long drone during the measure. + bassDur := step * 7.5 + chordDur := step * 3.0 + melDur := step * 1.6 + + // If bass is too low, shift it up one octave. + if bassFreq < 120 { + bassFreq *= 2 + } + + // Ensure chord is in audible middle register (260–520 Hz). + if chord[0] < 200 { + chord = [3]float64{chord[0] * 4, chord[1] * 4, chord[2] * 4} + } else if chord[0] < 260 { + chord = [3]float64{chord[0] * 2, chord[1] * 2, chord[2] * 2} + } + + var notes []melodyNote + notes = append(notes, ns(bassFreq, bassDur, step)) + notes = append(notes, chordArp(chord, chordDur, step)...) + + for _, f := range melody { + ff := f + if ff < 300 { + ff *= 4 + } else if ff < 400 { + ff *= 2 + } + notes = append(notes, ns(ff, melDur, step)) + } + return notes +} + +// buildSong creates a 4-measure loop (~16 s normal, ~8 s wild) from a chord +// progression and matching melody lines. +func buildSong(bassFreqs [4]float64, chords [4][3]float64, melodies [4][]float64, step float64) []melodyNote { + var out []melodyNote + for i := 0; i < 4; i++ { + out = append(out, buildMeasure(bassFreqs[i], chords[i], melodies[i], step)...) + } + return out +} + +// ── theme registry ───────────────────────────────────────────────── + var themeSoundPresets = map[string]themeSounds{ "neon": soundsNeon(), "terminal": soundsTerminal(), @@ -105,6 +204,9 @@ var themeSoundPresets = map[string]themeSounds{ "biomech": soundsBiomech(), } +// ════════════════════════════════════════════════════════════════════ +// NEON – bright synth-pop in C major (C → F → G → C) +// ════════════════════════════════════════════════════════════════════ func soundsNeon() themeSounds { var s themeSounds s.Splash.Freqs = []float64{523.25, 659.25, 783.99, 1046.5} @@ -113,27 +215,44 @@ func soundsNeon() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "triangle", 523.25, 1046.5, 0.13, 0.1 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 + + cm := major(65.41) + fm := major(87.31) + gm := major(98.00) + + bass := [4]float64{130.81, 174.61, 196.00, 130.81} // C3 F3 G3 C3 + chords := [4][3]float64{cm, fm, gm, cm} + normalMel := [4][]float64{ + {523.25, 587.33, 659.25, 523.25}, // C D E C + {698.46, 783.99, 880.00, 698.46}, // F G A F + {783.99, 880.00, 1046.5, 783.99}, // G A C G + {523.25, 659.25, 783.99, 1046.5}, // C E G C + } + wildMel := [4][]float64{ + {1046.5, 1174.66, 1318.5, 1046.5}, + {1396.92, 1567.98, 1760.00, 1396.92}, + {1567.98, 1760.00, 2093.0, 1567.98}, + {1046.5, 1318.5, 1567.98, 2093.0}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 50, Wave: "sine", - DroneFreqs: []float64{523.25, 659.25, 783.99, 1046.5}, + Gain: 0.03, BPM: 60, Wave: "sine", + DroneFreqs: []float64{130.81, 174.61, 196.00, 261.63}, Attack: 0.6, Release: 1.5, CutoffMin: 800, CutoffMax: 3000, - Melody: []melodyNote{ - n(523.25, 0.15), n(659.25, 0.15), n(783.99, 0.15), n(1046.5, 0.15), - n(783.99, 0.15), n(659.25, 0.15), n(523.25, 0.15), n(659.25, 0.15), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 120, Wave: "triangle", - DroneFreqs: []float64{261.63, 523.25, 659.25}, + Gain: 0.06, BPM: 120, Wave: "triangle", + DroneFreqs: []float64{261.63, 349.23, 392.00, 523.25}, Attack: 0.2, Release: 0.6, CutoffMin: 1500, CutoffMax: 6000, DetuneCents: 8, - Melody: []melodyNote{ - n(523.25, 0.08), n(659.25, 0.08), n(783.99, 0.08), n(1046.5, 0.08), - n(1318.5, 0.08), n(1046.5, 0.08), n(783.99, 0.08), n(659.25, 0.08), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// TERMINAL – dark industrial, E minor (Em → Bm → Am → Em) +// ════════════════════════════════════════════════════════════════════ func soundsTerminal() themeSounds { var s themeSounds s.Splash.Freqs = []float64{523.25, 659.25, 783.99} @@ -142,25 +261,44 @@ func soundsTerminal() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "square", 600, 1200, 0.12, 0.1 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 + + em := minor(82.41) + bm := minor(61.74) + am := minor(110.00) + + bass := [4]float64{164.81, 123.47, 220.00, 164.81} // E3 B2 A3 E3 + chords := [4][3]float64{em, bm, am, em} + normalMel := [4][]float64{ + {329.63, 392.00, 440.00, 329.63}, // E3=164, so E4=329 + {246.94, 293.66, 329.63, 246.94}, // B2=123, B3=246 + {440.00, 523.25, 587.33, 440.00}, // A3=220 + {329.63, 392.00, 493.88, 329.63}, // E4=E4 + } + wildMel := [4][]float64{ + {659.25, 783.99, 880.00, 659.25}, + {493.88, 587.33, 659.25, 493.88}, + {880.00, 1046.5, 1174.66, 880.00}, + {659.25, 783.99, 987.77, 659.25}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.02, BPM: 40, Wave: "square", - DroneFreqs: []float64{60, 120}, - Attack: 0.1, Release: 0.3, PulseInterval: 2.0, - Melody: []melodyNote{ - n(196.00, 0.6), n(246.94, 0.3), - }, + Gain: 0.025, BPM: 60, Wave: "square", + DroneFreqs: []float64{82.41, 123.47, 164.81}, + Attack: 0.1, Release: 0.3, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.05, BPM: 160, Wave: "square", - DroneFreqs: []float64{50, 100}, - Attack: 0.05, Release: 0.15, PulseInterval: 0.25, - Melody: []melodyNote{ - n(196.00, 0.15), n(246.94, 0.15), n(293.66, 0.15), n(392.00, 0.15), - }, + Gain: 0.055, BPM: 140, Wave: "square", + DroneFreqs: []float64{164.81, 246.94, 329.63}, + Attack: 0.05, Release: 0.15, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// SYNTHWAVE – retro-future, A minor (Am → F → C → G) +// ════════════════════════════════════════════════════════════════════ func soundsSynthwave() themeSounds { var s themeSounds s.Splash.Freqs = []float64{196, 246.94, 293.66, 349.23} @@ -169,28 +307,45 @@ func soundsSynthwave() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "sine", 220, 440, 0.18, 0.1 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 := minor(55.00) + fm := major(87.31) + cm := major(65.41) + gm := major(98.00) + + bass := [4]float64{110.00, 174.61, 130.81, 196.00} // A2 F3 C3 G3 + chords := [4][3]float64{am, fm, cm, gm} + normalMel := [4][]float64{ + {220.00, 261.63, 293.66, 220.00}, + {349.23, 392.00, 440.00, 349.23}, + {261.63, 329.63, 392.00, 261.63}, + {392.00, 440.00, 493.88, 392.00}, + } + wildMel := [4][]float64{ + {440.00, 523.25, 587.33, 440.00}, + {698.46, 783.99, 880.00, 698.46}, + {523.25, 659.25, 783.99, 523.25}, + {783.99, 880.00, 987.77, 783.99}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 55, Wave: "sine", - DroneFreqs: []float64{98, 196, 293.66}, + Gain: 0.03, BPM: 60, Wave: "sine", + DroneFreqs: []float64{110.00, 130.81, 174.61, 196.00}, Attack: 0.8, Release: 1.5, - Melody: []melodyNote{ - n(220.00, 0.3), n(261.63, 0.3), n(329.63, 0.3), - n(293.66, 0.3), n(261.63, 0.3), n(246.94, 0.3), n(220.00, 0.3), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.06, BPM: 110, Wave: "triangle", - DroneFreqs: []float64{65.41, 130.81, 196}, + Gain: 0.065, BPM: 130, Wave: "triangle", + DroneFreqs: []float64{220.00, 261.63, 349.23, 392.00}, Attack: 0.3, Release: 0.7, DetuneCents: 12, - Melody: []melodyNote{ - n(220.00, 0.15), n(261.63, 0.15), n(329.63, 0.15), n(392.00, 0.15), - n(329.63, 0.15), n(261.63, 0.15), n(220.00, 0.15), n(261.63, 0.15), - n(329.63, 0.15), n(440.00, 0.15), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// PLASMA – energetic, D minor (Dm → Bb → F → C) +// ════════════════════════════════════════════════════════════════════ func soundsPlasma() themeSounds { var s themeSounds s.Splash.Freqs = []float64{311.13, 415.3, 466.16, 622.25} @@ -199,27 +354,46 @@ func soundsPlasma() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "triangle", 349.23, 698.46, 0.15, 0.1 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 523.25, 174.61, 0.17, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "triangle", 200, 100, 0.13, 0.09 + + dm := minor(73.42) + bb := major(58.27) + fm := major(87.31) + cm := major(65.41) + + bass := [4]float64{146.83, 116.54, 174.61, 130.81} + chords := [4][3]float64{dm, bb, fm, cm} + normalMel := [4][]float64{ + {293.66, 349.23, 392.00, 293.66}, + {233.08, 293.66, 349.23, 233.08}, + {349.23, 440.00, 523.25, 349.23}, + {261.63, 329.63, 392.00, 261.63}, + } + wildMel := [4][]float64{ + {587.33, 698.46, 783.99, 587.33}, + {466.16, 587.33, 698.46, 466.16}, + {698.46, 880.00, 1046.5, 698.46}, + {523.25, 659.25, 783.99, 523.25}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.03, BPM: 70, Wave: "triangle", - DroneFreqs: []float64{329.63, 466.16}, + Gain: 0.035, BPM: 60, Wave: "triangle", + DroneFreqs: []float64{146.83, 233.08, 349.23}, Attack: 0.4, Release: 0.9, DetuneCents: 15, - Melody: []melodyNote{ - n(261.63, 0.25), n(369.99, 0.25), n(261.63, 0.25), n(369.99, 0.25), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.065, BPM: 140, Wave: "square", - DroneFreqs: []float64{164.81, 329.63}, + Gain: 0.07, BPM: 150, Wave: "square", + DroneFreqs: []float64{293.66, 466.16, 698.46}, Attack: 0.1, Release: 0.4, DetuneCents: 25, CutoffMin: 400, CutoffMax: 3000, NoiseGain: 0.02, - Melody: []melodyNote{ - n(261.63, 0.12), n(369.99, 0.12), n(261.63, 0.12), n(369.99, 0.12), - n(523.25, 0.12), n(369.99, 0.12), n(261.63, 0.12), n(369.99, 0.12), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// BRUTALIST – concrete minimalism, D major (D → A → D → A) +// ════════════════════════════════════════════════════════════════════ func soundsBrutalist() themeSounds { var s themeSounds s.Splash.Freqs = []float64{100, 150, 200, 120} @@ -228,25 +402,43 @@ func soundsBrutalist() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "square", 200, 400, 0.12, 0.11 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 400, 100, 0.14, 0.1 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 120, 60, 0.1, 0.12 + + dm := major(73.42) + am := major(55.00) + + bass := [4]float64{146.83, 110.00, 146.83, 110.00} + chords := [4][3]float64{dm, am, dm, am} + normalMel := [4][]float64{ + {293.66, 349.23, 392.00, 293.66}, + {220.00, 261.63, 293.66, 220.00}, + {293.66, 392.00, 440.00, 293.66}, + {220.00, 293.66, 349.23, 220.00}, + } + wildMel := [4][]float64{ + {587.33, 698.46, 783.99, 587.33}, + {440.00, 523.25, 587.33, 440.00}, + {587.33, 783.99, 880.00, 587.33}, + {440.00, 587.33, 698.46, 440.00}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 45, Wave: "triangle", - DroneFreqs: []float64{80, 120}, + Gain: 0.03, BPM: 60, Wave: "triangle", + DroneFreqs: []float64{73.42, 110.00, 146.83}, Attack: 0.5, Release: 1.0, - Melody: []melodyNote{ - n(65.41, 1.2), n(98.00, 0.4), n(65.41, 1.2), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.06, BPM: 100, Wave: "square", - DroneFreqs: []float64{40, 80}, + Gain: 0.065, BPM: 120, Wave: "square", + DroneFreqs: []float64{146.83, 220.00, 293.66}, Attack: 0.05, Release: 0.3, - Melody: []melodyNote{ - n(65.41, 0.4), n(98.00, 0.2), n(65.41, 0.4), n(77.78, 0.2), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// VOLCANO – molten, C major (C → D → F → G) +// ════════════════════════════════════════════════════════════════════ func soundsVolcano() themeSounds { var s themeSounds s.Splash.Freqs = []float64{196, 246.94, 293.66, 349.23} @@ -255,26 +447,45 @@ func soundsVolcano() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "triangle", 261.63, 523.25, 0.16, 0.1 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 392, 98, 0.17, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "sine", 160, 80, 0.13, 0.1 + + cm := major(65.41) + dm := major(73.42) + fm := major(87.31) + gm := major(98.00) + + bass := [4]float64{130.81, 146.83, 174.61, 196.00} + chords := [4][3]float64{cm, dm, fm, gm} + normalMel := [4][]float64{ + {261.63, 293.66, 329.63, 261.63}, + {293.66, 349.23, 392.00, 293.66}, + {349.23, 440.00, 523.25, 349.23}, + {392.00, 440.00, 493.88, 392.00}, + } + wildMel := [4][]float64{ + {523.25, 587.33, 659.25, 523.25}, + {587.33, 698.46, 783.99, 587.33}, + {698.46, 880.00, 1046.5, 698.46}, + {783.99, 880.00, 987.77, 783.99}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 35, Wave: "sine", - DroneFreqs: []float64{82.41, 123.47}, + Gain: 0.03, BPM: 60, Wave: "sine", + DroneFreqs: []float64{65.41, 130.81, 174.61}, Attack: 1.0, Release: 2.0, - Melody: []melodyNote{ - n(65.41, 0.5), n(77.78, 0.5), n(98.00, 0.5), n(130.81, 0.5), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 85, Wave: "triangle", - DroneFreqs: []float64{65.41, 98}, + Gain: 0.06, BPM: 110, Wave: "triangle", + DroneFreqs: []float64{130.81, 261.63, 349.23}, Attack: 0.2, Release: 0.6, NoiseGain: 0.015, - Melody: []melodyNote{ - n(65.41, 0.25), n(77.78, 0.25), n(98.00, 0.25), n(130.81, 0.25), - n(155.56, 0.25), n(196.00, 0.25), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// AURORA – ethereal, E major (E → C#m → A → E) +// ════════════════════════════════════════════════════════════════════ func soundsAurora() themeSounds { var s themeSounds s.Splash.Freqs = []float64{659.25, 880, 987.77, 1046.5} @@ -283,27 +494,44 @@ func soundsAurora() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "sine", 523.25, 880, 0.2, 0.09 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 704, 352, 0.18, 0.085 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "sine", 220, 110, 0.15, 0.08 + + em := major(82.41) + csm := minor(69.30) + am := major(55.00) + + bass := [4]float64{164.81, 138.59, 110.00, 164.81} + chords := [4][3]float64{em, csm, am, em} + normalMel := [4][]float64{ + {329.63, 369.99, 440.00, 329.63}, + {277.18, 329.63, 369.99, 277.18}, + {220.00, 261.63, 293.66, 220.00}, + {329.63, 369.99, 440.00, 329.63}, + } + wildMel := [4][]float64{ + {659.25, 739.99, 880.00, 659.25}, + {554.37, 659.25, 739.99, 554.37}, + {440.00, 523.25, 587.33, 440.00}, + {659.25, 739.99, 880.00, 659.25}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.02, BPM: 40, Wave: "sine", - DroneFreqs: []float64{440, 880, 1320}, + Gain: 0.025, BPM: 60, Wave: "sine", + DroneFreqs: []float64{82.41, 110.00, 138.59, 164.81}, Attack: 1.2, Release: 2.5, - Melody: []melodyNote{ - n(659.25, 0.3), n(783.99, 0.3), n(987.77, 0.3), n(1318.5, 0.3), - n(987.77, 0.3), n(783.99, 0.3), n(659.25, 0.3), n(783.99, 0.3), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.05, BPM: 95, Wave: "sine", - DroneFreqs: []float64{220, 440, 880}, + Gain: 0.055, BPM: 120, Wave: "sine", + DroneFreqs: []float64{164.81, 220.00, 277.18, 329.63}, Attack: 0.3, Release: 0.8, DetuneCents: 20, - Melody: []melodyNote{ - n(659.25, 0.15), n(783.99, 0.15), n(987.77, 0.15), n(1318.5, 0.15), - n(1567.98, 0.15), n(1318.5, 0.15), n(987.77, 0.15), n(783.99, 0.15), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// MATRIX – cyberpunk, E minor (Em → D → C → B) +// ════════════════════════════════════════════════════════════════════ func soundsMatrix() themeSounds { var s themeSounds s.Splash.Freqs = []float64{523.25, 587.33, 659.25, 698.46} @@ -312,27 +540,45 @@ func soundsMatrix() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "square", 880, 1318.5, 0.11, 0.1 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 880, 330, 0.13, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 260, 130, 0.1, 0.09 + + em := minor(82.41) + dm := major(73.42) + cm := major(65.41) + bm := major(61.74) + + bass := [4]float64{164.81, 146.83, 130.81, 123.47} + chords := [4][3]float64{em, dm, cm, bm} + normalMel := [4][]float64{ + {329.63, 392.00, 440.00, 329.63}, + {293.66, 349.23, 392.00, 293.66}, + {261.63, 329.63, 392.00, 261.63}, + {246.94, 293.66, 349.23, 246.94}, + } + wildMel := [4][]float64{ + {659.25, 783.99, 880.00, 659.25}, + {587.33, 698.46, 783.99, 587.33}, + {523.25, 659.25, 783.99, 523.25}, + {493.88, 587.33, 698.46, 493.88}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 75, Wave: "square", - DroneFreqs: []float64{523.25, 659.25}, + Gain: 0.03, BPM: 60, Wave: "square", + DroneFreqs: []float64{82.41, 130.81, 164.81}, Attack: 0.1, Release: 0.3, - Melody: []melodyNote{ - n(293.66, 0.2), n(440.00, 0.2), n(587.33, 0.2), n(739.99, 0.2), - n(587.33, 0.2), n(440.00, 0.2), n(293.66, 0.2), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.065, BPM: 160, Wave: "square", - DroneFreqs: []float64{261.63, 523.25}, + Gain: 0.07, BPM: 150, Wave: "square", + DroneFreqs: []float64{164.81, 261.63, 329.63}, Attack: 0.05, Release: 0.15, - Melody: []melodyNote{ - n(293.66, 0.1), n(440.00, 0.1), n(587.33, 0.1), n(739.99, 0.1), - n(880.00, 0.1), n(739.99, 0.1), n(587.33, 0.1), n(440.00, 0.1), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// OCEAN – flowing, G major (G → D → Em → C) +// ════════════════════════════════════════════════════════════════════ func soundsOcean() themeSounds { var s themeSounds s.Splash.Freqs = []float64{174.61, 196, 220, 246.94} @@ -341,27 +587,45 @@ func soundsOcean() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "sine", 349.23, 523.25, 0.2, 0.09 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 415.3, 246.94, 0.18, 0.085 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "sine", 140, 70, 0.15, 0.08 + + gm := major(98.00) + dm := major(73.42) + em := minor(82.41) + cm := major(65.41) + + bass := [4]float64{196.00, 146.83, 164.81, 130.81} + chords := [4][3]float64{gm, dm, em, cm} + normalMel := [4][]float64{ + {392.00, 440.00, 493.88, 392.00}, + {293.66, 349.23, 392.00, 293.66}, + {329.63, 392.00, 440.00, 329.63}, + {261.63, 329.63, 392.00, 261.63}, + } + wildMel := [4][]float64{ + {783.99, 880.00, 987.77, 783.99}, + {587.33, 698.46, 783.99, 587.33}, + {659.25, 783.99, 880.00, 659.25}, + {523.25, 659.25, 783.99, 523.25}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.02, BPM: 30, Wave: "sine", - DroneFreqs: []float64{130.81, 174.61}, + Gain: 0.025, BPM: 60, Wave: "sine", + DroneFreqs: []float64{98.00, 130.81, 164.81, 196.00}, Attack: 1.5, Release: 3.0, NoiseGain: 0.02, - Melody: []melodyNote{ - n(130.81, 0.35), n(164.81, 0.35), n(196.00, 0.35), n(164.81, 0.35), - n(130.81, 0.35), n(196.00, 0.35), n(164.81, 0.35), n(130.81, 0.35), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 80, Wave: "triangle", - DroneFreqs: []float64{98, 130.81}, + Gain: 0.06, BPM: 120, Wave: "triangle", + DroneFreqs: []float64{196.00, 261.63, 329.63, 392.00}, Attack: 0.3, Release: 0.7, NoiseGain: 0.04, - Melody: []melodyNote{ - n(130.81, 0.18), n(164.81, 0.18), n(196.00, 0.18), n(261.63, 0.18), - n(196.00, 0.18), n(164.81, 0.18), n(130.81, 0.18), n(98.00, 0.18), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// DOS – 8-bit chip-tune, C major (C → G → Am → F) +// ════════════════════════════════════════════════════════════════════ func soundsDos() themeSounds { var s themeSounds s.Splash.Freqs = []float64{800, 1000} @@ -370,27 +634,45 @@ func soundsDos() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "square", 400, 800, 0.1, 0.1 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 800, 200, 0.1, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 300, 150, 0.08, 0.1 + + cm := major(65.41) + gm := major(98.00) + am := minor(110.00) + fm := major(87.31) + + bass := [4]float64{130.81, 196.00, 220.00, 174.61} + chords := [4][3]float64{cm, gm, am, fm} + normalMel := [4][]float64{ + {523.25, 587.33, 659.25, 523.25}, + {783.99, 880.00, 987.77, 783.99}, + {880.00, 1046.5, 1174.66, 880.00}, + {698.46, 783.99, 880.00, 698.46}, + } + wildMel := [4][]float64{ + {1046.5, 1174.66, 1318.5, 1046.5}, + {1567.98, 1760.00, 1975.53, 1567.98}, + {1760.00, 2093.0, 2349.32, 1760.00}, + {1396.92, 1567.98, 1760.00, 1396.92}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 60, Wave: "square", - DroneFreqs: []float64{200, 400}, + Gain: 0.03, BPM: 70, Wave: "square", + DroneFreqs: []float64{65.41, 130.81, 196.00, 220.00}, Attack: 0.05, Release: 0.15, - Melody: []melodyNote{ - n(261.63, 0.18), n(329.63, 0.18), n(392.00, 0.18), n(523.25, 0.18), - n(392.00, 0.18), n(329.63, 0.18), n(261.63, 0.18), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.065, BPM: 200, Wave: "square", - DroneFreqs: []float64{100, 200}, + Gain: 0.07, BPM: 180, Wave: "square", + DroneFreqs: []float64{130.81, 261.63, 392.00, 440.00}, Attack: 0.02, Release: 0.08, - Melody: []melodyNote{ - n(261.63, 0.08), n(329.63, 0.08), n(392.00, 0.08), n(523.25, 0.08), - n(659.25, 0.08), n(523.25, 0.08), n(392.00, 0.08), n(329.63, 0.08), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// RETRO – warm nostalgia, C major (C → Am → F → G) +// ════════════════════════════════════════════════════════════════════ func soundsRetro() themeSounds { var s themeSounds s.Splash.Freqs = []float64{1046.5, 1318.5} @@ -399,28 +681,46 @@ func soundsRetro() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "square", 800, 1600, 0.14, 0.1 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "square", 1600, 400, 0.15, 0.09 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "square", 400, 200, 0.1, 0.1 + + cm := major(65.41) + am := minor(55.00) + fm := major(87.31) + gm := major(98.00) + + bass := [4]float64{130.81, 110.00, 174.61, 196.00} + chords := [4][3]float64{cm, am, fm, gm} + normalMel := [4][]float64{ + {261.63, 329.63, 392.00, 261.63}, + {220.00, 261.63, 293.66, 220.00}, + {349.23, 392.00, 440.00, 349.23}, + {392.00, 440.00, 493.88, 392.00}, + } + wildMel := [4][]float64{ + {523.25, 659.25, 783.99, 523.25}, + {440.00, 523.25, 587.33, 440.00}, + {698.46, 783.99, 880.00, 698.46}, + {783.99, 880.00, 987.77, 783.99}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 80, Wave: "square", - DroneFreqs: []float64{523.25, 659.25}, + Gain: 0.03, BPM: 60, Wave: "square", + DroneFreqs: []float64{65.41, 110.00, 130.81, 174.61}, Attack: 0.1, Release: 0.3, - Melody: []melodyNote{ - n(261.63, 0.25), n(349.23, 0.25), n(440.00, 0.25), n(523.25, 0.25), - n(440.00, 0.25), n(349.23, 0.25), n(261.63, 0.25), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 155, Wave: "square", - DroneFreqs: []float64{261.63, 523.25}, + Gain: 0.06, BPM: 140, Wave: "square", + DroneFreqs: []float64{130.81, 220.00, 261.63, 349.23}, Attack: 0.05, Release: 0.15, DetuneCents: 30, NoiseGain: 0.02, - Melody: []melodyNote{ - n(261.63, 0.12), n(349.23, 0.12), n(440.00, 0.12), n(523.25, 0.12), - n(698.46, 0.12), n(523.25, 0.12), n(440.00, 0.12), n(349.23, 0.12), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// COSMOS – vast space, A major (A → D → C → G) +// ════════════════════════════════════════════════════════════════════ func soundsCosmos() themeSounds { var s themeSounds s.Splash.Freqs = []float64{220, 277.18, 329.63, 392} @@ -429,27 +729,45 @@ func soundsCosmos() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "triangle", 392, 587.33, 0.22, 0.09 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 587.33, 196, 0.2, 0.085 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "sine", 170, 85, 0.16, 0.08 + + am := major(55.00) + dm := major(73.42) + cm := major(65.41) + gm := major(98.00) + + bass := [4]float64{110.00, 146.83, 130.81, 196.00} + chords := [4][3]float64{am, dm, cm, gm} + normalMel := [4][]float64{ + {220.00, 261.63, 293.66, 220.00}, + {293.66, 349.23, 392.00, 293.66}, + {261.63, 329.63, 392.00, 261.63}, + {392.00, 440.00, 493.88, 392.00}, + } + wildMel := [4][]float64{ + {440.00, 523.25, 587.33, 440.00}, + {587.33, 698.46, 783.99, 587.33}, + {523.25, 659.25, 783.99, 523.25}, + {783.99, 880.00, 987.77, 783.99}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.02, BPM: 30, Wave: "sine", - DroneFreqs: []float64{220, 440, 660}, + Gain: 0.025, BPM: 60, Wave: "sine", + DroneFreqs: []float64{55.00, 110.00, 146.83, 196.00}, Attack: 1.5, Release: 3.0, - Melody: []melodyNote{ - n(130.81, 0.5), n(196.00, 0.5), n(261.63, 0.5), n(329.63, 0.5), - n(261.63, 0.5), n(196.00, 0.5), n(130.81, 0.5), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.05, BPM: 75, Wave: "triangle", - DroneFreqs: []float64{110, 220, 440}, + Gain: 0.06, BPM: 120, Wave: "triangle", + DroneFreqs: []float64{110.00, 220.00, 293.66, 392.00}, Attack: 0.3, Release: 0.8, - Melody: []melodyNote{ - n(130.81, 0.2), n(196.00, 0.2), n(261.63, 0.2), n(329.63, 0.2), - n(392.00, 0.2), n(329.63, 0.2), n(261.63, 0.2), n(196.00, 0.2), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } +// ════════════════════════════════════════════════════════════════════ +// RETROFUTURE – retro sci-fi, E minor (Em → C → G → D) +// ════════════════════════════════════════════════════════════════════ func soundsRetrofuture() themeSounds { var s themeSounds s.Splash.Freqs = []float64{196, 246.94, 329.63, 440} @@ -458,28 +776,45 @@ func soundsRetrofuture() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "sine", 330, 523.25, 0.18, 0.09 s.Close.Wave, s.Close.Start, s.Close.End, s.Close.Dur, s.Close.Gain = "sine", 415.3, 165, 0.17, 0.085 s.Bounce.Wave, s.Bounce.Start, s.Bounce.End, s.Bounce.Dur, s.Bounce.Gain = "triangle", 190, 95, 0.14, 0.09 + + em := minor(82.41) + cm := major(65.41) + gm := major(98.00) + dm := major(73.42) + + bass := [4]float64{164.81, 130.81, 196.00, 146.83} + chords := [4][3]float64{em, cm, gm, dm} + normalMel := [4][]float64{ + {329.63, 392.00, 440.00, 329.63}, + {261.63, 329.63, 392.00, 261.63}, + {392.00, 440.00, 493.88, 392.00}, + {293.66, 349.23, 392.00, 293.66}, + } + wildMel := [4][]float64{ + {659.25, 783.99, 880.00, 659.25}, + {523.25, 659.25, 783.99, 523.25}, + {783.99, 880.00, 987.77, 783.99}, + {587.33, 698.46, 783.99, 587.33}, + } + s.Ambient.Normal = ambientPreset{ - Gain: 0.025, BPM: 55, Wave: "triangle", - DroneFreqs: []float64{220, 330, 440}, + Gain: 0.03, BPM: 60, Wave: "triangle", + DroneFreqs: []float64{82.41, 130.81, 164.81, 196.00}, Attack: 0.8, Release: 1.8, - Melody: []melodyNote{ - n(261.63, 0.3), n(311.13, 0.3), n(392.00, 0.3), n(466.16, 0.3), - n(392.00, 0.3), n(311.13, 0.3), n(261.63, 0.3), - }, + Melody: buildSong(bass, chords, normalMel, 0.5), } s.Ambient.Wild = ambientPreset{ - Gain: 0.055, BPM: 120, Wave: "square", - DroneFreqs: []float64{110, 220}, + Gain: 0.06, BPM: 130, Wave: "square", + DroneFreqs: []float64{164.81, 261.63, 329.63, 392.00}, Attack: 0.05, Release: 0.15, - Melody: []melodyNote{ - n(261.63, 0.12), n(311.13, 0.12), n(392.00, 0.12), n(466.16, 0.12), - n(523.25, 0.12), n(466.16, 0.12), n(392.00, 0.12), n(311.13, 0.12), - }, + Melody: buildSong(bass, chords, wildMel, 0.25), } return s } -// soundsSpaceage returns synth parameters for the Space Age theme. +// ════════════════════════════════════════════════════════════════════ +// SPACEAGE – mid-century optimism, C major (C → Am → F → G) +// ════════════════════════════════════════════════════════════════════ func soundsSpaceage() themeSounds { var s themeSounds s.Splash.Freqs = []float64{440, 554.37, 659.25, 880} @@ -488,28 +823,45 @@ func soundsSpaceage() themeSounds { s.Open.Wave, s.Open.Start, s.Open.End, s.Open.Dur, s.Open.Gain = "sine", 440, 880, 0.18, 0.09 s |
