diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-27 09:30:32 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-27 09:30:32 +0300 |
| commit | 06dd860ddc915031d2356faf23e73a16bb2b19cf (patch) | |
| tree | 8239ca6d6810af2922b1bbc24048dc54eca9e159 | |
| parent | 91ac0b2136b5cdf19d30141b8575f21c06afbfd1 (diff) | |
refactor(generator): move theme sound data from Go code to embedded JSON files
- Generate sounds.json for each theme inside templates/themes/<name>/
- Load sound data at runtime from embedded FS via templates.ThemeSounds()
- Replace ~1100 lines of hardcoded builder functions and presets with a
145-line loader (soundCache, initSoundCache, loadThemeSounds)
- Update embed.go to include themes/*/sounds.json in the embedded FS
- Update tests to use loadThemeSounds() instead of themeSoundPresets
- Eliminates duplication between themeSoundPresets and JSON output
Improves cohesion and DRY by keeping theme assets (CSS, JS, meta, sounds)
in a single location per theme directory.
23 files changed, 8444 insertions, 1170 deletions
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index 50cd3d0..10116a2 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -94,15 +94,6 @@ func TestJSONStringOrNull(t *testing.T) { } } -func TestThemeSoundPresetsMatchRegistry(t *testing.T) { - t.Parallel() - for name := range getThemeSet() { - if _, ok := themeSoundPresets[name]; !ok { - t.Errorf("theme %q has no sound preset in themeSoundPresets", name) - } - } -} - func TestThemeSoundsJSONNonEmpty(t *testing.T) { t.Parallel() j := themeSoundsJSON("neon") @@ -152,9 +143,9 @@ func TestThemeSoundPresetsAmbientPopulated(t *testing.T) { t.Parallel() for name := range getThemeSet() { - preset, ok := themeSoundPresets[name] - if !ok { - t.Errorf("theme %q missing from themeSoundPresets", name) + preset, err := loadThemeSounds(name) + if err != nil { + t.Errorf("theme %q loadThemeSounds: %v", name, err) continue } @@ -174,8 +165,8 @@ func TestThemeSoundPresetsAmbientValuesBounded(t *testing.T) { t.Parallel() for name := range getThemeSet() { - preset, ok := themeSoundPresets[name] - if !ok { + preset, err := loadThemeSounds(name) + if err != nil { continue } diff --git a/internal/generator/templates/embed.go b/internal/generator/templates/embed.go index ac3efbd..c5a4b22 100644 --- a/internal/generator/templates/embed.go +++ b/internal/generator/templates/embed.go @@ -1,6 +1,7 @@ // Package templates exposes the embedded HTML shell, shared CSS/JS bundles, // and per-theme assets used by the generator. Themes live as directories -// under templates/themes/<name>/ containing theme.css, theme.js, and meta.json. +// under templates/themes/<name>/ containing theme.css, theme.js, meta.json, +// and sounds.json. // All assets are compiled into the binary via //go:embed so snonux still ships // as a single self-contained executable. package templates @@ -13,7 +14,7 @@ import ( "sort" ) -//go:embed shell.tmpl shared/*.tmpl shared/shared.css shared/shared.js themes/*/theme.css themes/*/theme.js themes/*/meta.json +//go:embed shell.tmpl shared/*.tmpl shared/shared.css shared/shared.js themes/*/theme.css themes/*/theme.js themes/*/meta.json themes/*/sounds.json var FS embed.FS // Shell returns the body of shell.tmpl — the single page template used for @@ -64,6 +65,11 @@ func ThemeMeta(name string) ([]byte, error) { return FS.ReadFile(path.Join("themes", name, "meta.json")) } +// ThemeSounds returns the per-theme sounds.json bytes for the named theme. +func ThemeSounds(name string) ([]byte, error) { + return FS.ReadFile(path.Join("themes", name, "sounds.json")) +} + // ThemeNames returns a sorted list of available theme names derived from the // directories present under templates/themes/. func ThemeNames() ([]string, error) { diff --git a/internal/generator/templates/themes/aurora/sounds.json b/internal/generator/templates/themes/aurora/sounds.json new file mode 100644 index 0000000..1440fe9 --- /dev/null +++ b/internal/generator/templates/themes/aurora/sounds.json @@ -0,0 +1,447 @@ +{ + "splash": { + "freqs": [ + 392, + 493.88, + 587.33, + 783.99 + ], + "spacing": 0.08, + "gain": 0.09, + "wave": "sine" + }, + "nav": { + "freq": 261.63, + "wave": "triangle", + "dur": 0.085, + "gain": 0.1 + }, + "open": { + "wave": "sine", + "start": 390, + "end": 780, + "dur": 0.16, + "gain": 0.1 + }, + "close": { + "wave": "sine", + "start": 523, + "end": 196, + "dur": 0.18, + "gain": 0.09 + }, + "bounce": { + "wave": "triangle", + "start": 180, + "end": 90, + "dur": 0.12, + "gain": 0.09 + }, + "ambient": { + "normal": { + "bpm": 130, + "gain": 0.032, + "wave": "triangle", + "droneFreqs": [ + 98, + 146.83, + 196 + ], + "cutoffMin": 1200, + "cutoffMax": 5500, + "attack": 0.02, + "release": 0.1, + "melody": [ + { + "freq": 98, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 196, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 98, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 146.8340935339148, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 196, + "dur": 1.77408, + "step": 0.6283200000000001 + }, + { + "freq": 246.9445257793951, + "dur": 1.1457600000000001, + "step": 0.60984 + }, + { + "freq": 293.6681870678296, + "dur": 0.5544, + "step": 0.60984 + }, + { + "freq": 783.99, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 880, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 987.77, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 1174.66, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 987.77, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 880, + "dur": 0.42504000000000003, + "step": 0.462 + }, + { + "freq": 783.99, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 82.41, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 164.82, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 82.41, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 123.47548620540732, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 164.81, + "dur": 1.77408, + "step": 0.6283200000000001 + }, + { + "freq": 195.99322462359845, + "dur": 1.1457600000000001, + "step": 0.60984 + }, + { + "freq": 246.9359893400459, + "dur": 0.5544, + "step": 0.60984 + }, + { + "freq": 659.25, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 783.99, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 880, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 987.77, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 880, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 783.99, + "dur": 0.42504000000000003, + "step": 0.462 + }, + { + "freq": 659.25, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 65.41, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 130.82, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 65.41, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 98.00426589850373, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 130.81, + "dur": 1.77408, + "step": 0.6283200000000001 + }, + { + "freq": 164.81027253674833, + "dur": 1.1457600000000001, + "step": 0.60984 + }, + { + "freq": 195.9935487262387, + "dur": 0.5544, + "step": 0.60984 + }, + { + "freq": 783.99, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 880, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 987.77, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 1174.66, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 1318.5, + "dur": 0.42504000000000003, + "step": 0.462 + }, + { + "freq": 1174.66, + "dur": 0.42504000000000003, + "step": 0.462 + }, + { + "freq": 73.42, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 146.84, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 73.42, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 110.00570558428596, + "dur": 0.3927, + "step": 0.462 + }, + { + "freq": 146.83, + "dur": 1.77408, + "step": 0.6283200000000001 + }, + { + "freq": 184.9942077560642, + "dur": 1.1457600000000001, + "step": 0.60984 + }, + { + "freq": 219.99642809780318, + "dur": 0.5544, + "step": 0.60984 + }, + { + "freq": 880, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 987.77, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 1174.66, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 1318.5, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 1174.66, + "dur": 0.21252000000000001, + "step": 0.231 + }, + { + "freq": 987.77, + "dur": 0.42504000000000003, + "step": 0.462 + }, + { + "freq": 880, + "dur": 0.21252000000000001, + "step": 0.231 + } + ], + "drums": [ + "kick", + "_", + "hat", + "kick", + "snare", + "_", + "hat", + "_", + "kick", + "kick", + "hat", + "_", + "snare", + "_", + "hat", + "clap" + ] + }, + "wild": { + "bpm": 170, + "gain": 0.065, + "wave": "sawtooth", + "droneFreqs": [ + 98, + 196, + 293.66, + 392 + ], + "cutoffMin": 2200, + "cutoffMax": 7500, + "attack": 0.02, + "release": 0.05, + "detuneCents": 12, + "melody": [ + { + "freq": 196, + "dur": 0.30005, + "step": 0.353 + }, + { + "freq": 392, + "dur": 0.30005, + "step": 0.353 + }, + { + "freq": 196, + "dur": 0.30005, + "step": 0.353 + }, + { + "freq": 293.6681870678296, + "dur": 0.30005, + "step": 0.353 + }, + { + "freq": 1567.98, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 1760, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 1975.53, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 2349.32, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 1975.53, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 1760, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 1567.98, + "dur": 0.08119, + "step": 0.08825 + }, + { + "freq": 1318.5, + "dur": 0.08119, + "step": 0.08825 + } + ], + "drums": [ + "kick", + "_", + "hat", + "kick", + "snare", + "_", + "hat", + "_", + "kick", + "kick", + "hat", + "_", + "snare", + "_", + "hat", + "clap" + ] + } + } +} diff --git a/internal/generator/templates/themes/biomech/sounds.json b/internal/generator/templates/themes/biomech/sounds.json new file mode 100644 index 0000000..ecb54ae --- /dev/null +++ b/internal/generator/templates/themes/biomech/sounds.json @@ -0,0 +1,492 @@ +{ + "splash": { + "freqs": [ + 164.81, + 246.94, + 311.13, + 466.16 + ], + "spacing": 0.09, + "gain": 0.095, + "wave": "triangle" + }, + "nav": { + "freq": 233.08, + "wave": "sine", + "dur": 0.085, + "gain": 0.1 + }, + "open": { + "wave": "triangle", + "start": 220, + "end": 523.25, + "dur": 0.18, + "gain": 0.095 + }, + "close": { + "wave": "sine", + "start": 392, + "end": 130.81, + "dur": 0.2, + "gain": 0.09 + }, + "bounce": { + "wave": "triangle", + "start": 160, + "end": 80, + "dur": 0.14, + "gain": 0.09 + }, + "ambient": { + "normal": { + "bpm": 150, + "gain": 0.032, + "wave": "sawtooth", + "droneFreqs": [ + 69.3, + 110, + 138.59 + ], + "cutoffMin": 1200, + "cutoffMax": 5500, + "attack": 0.02, + "release": 0.08, + "melody": [ + { + "freq": 69.3, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 82.41205306968857, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 98.00499987245549, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 123.47856233425104, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 138.59, + "dur": 1.536, + "step": 0.544 + }, + { + "freq": 164.81221406822712, + "dur": 0.992, + "step": 0.528 + }, + { + "freq": 207.6503777843393, + "dur": 0.48, + "step": 0.528 + }, + { + "freq": 138.59, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 164.81221406822712, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 195.99585760928727, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 246.93930669413928, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 277.18, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 329.62442813645424, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 391.99171521857454, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 493.87861338827855, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 82.41, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 98.00255834737423, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 116.54533967516677, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 146.83792672389072, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 164.81, + "dur": 1.536, + "step": 0.544 + }, + { + "freq": 195.99322462359845, + "dur": 0.992, + "step": 0.528 + }, + { + "freq": 246.9359893400459, + "dur": 0.48, + "step": 0.528 + }, + { + "freq": 164.81, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 195.99322462359845, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 233.07653721470982, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 293.6580354734187, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 329.63, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 391.99834131834695, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 466.16721656504336, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 587.3338889212001, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 58.27, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 69.29509859120856, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 82.40622427948026, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 103.82533661207516, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 116.54, + "dur": 1.536, + "step": 0.544 + }, + { + "freq": 146.8311991547485, + "dur": 0.992, + "step": 0.528 + }, + { + "freq": 174.61270673920848, + "dur": 0.48, + "step": 0.528 + }, + { + "freq": 116.54, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 138.5901971824171, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 164.8124485589605, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 207.65067322415032, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 233.08, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 277.1803943648342, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 329.624897117921, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 415.30134644830065, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 73.42, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 87.31158638349979, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 103.83155974943264, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 130.81956777172744, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 146.83, + "dur": 1.536, + "step": 0.544 + }, + { + "freq": 174.61128069584953, + "dur": 0.992, + "step": 0.528 + }, + { + "freq": 219.99642809780318, + "dur": 0.48, + "step": 0.528 + }, + { + "freq": 146.83, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 174.61128069584953, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 207.64897736324158, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 261.62131756909207, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 293.66, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 349.22256139169906, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 415.29795472648317, + "dur": 0.17, + "step": 0.2 + }, + { + "freq": 523.2426351381841, + "dur": 0.17, + "step": 0.2 + } + ], + "drums": [ + "kick", + "_", + "hat", + "kick", + "_", + "snare", + "kick", + "_", + "_", + "kick", + "hat", + "_", + "snare", + "_", + "kick", + "hat" + ] + }, + "wild": { + "bpm": 190, + "gain": 0.075, + "wave": "sawtooth", + "droneFreqs": [ + 69.3, + 138.59, + 220, + 277.18 + ], + "cutoffMin": 2400, + "cutoffMax": 7500, + "attack": 0.02, + "release": 0.04, + "detuneCents": 22, + "melody": [ + { + "freq": 138.59, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 164.81221406822712, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 195.99585760928727, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 246.93930669413928, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 277.18, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 329.62442813645424, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 391.99171521857454, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 493.87861338827855, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 554.37, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 659.2607483440585, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 783.9975725727728, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 987.7750447509198, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 1108.73, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 1318.509604616967, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 1567.9810030099218, + "dur": 0.1343, + "step": 0.158 + }, + { + "freq": 1975.532271527477, + "dur": 0.1343, + "step": 0.158 + } + ], + "drums": [ + "kick", + "_", + "hat", + "kick", + "_", + "snare", + "kick", + "_", + "_", + "kick", + "hat", + "_", + "snare", + "_", + "kick", + "hat" + ] + } + } +} diff --git a/internal/generator/templates/themes/brutalist/sounds.json b/internal/generator/templates/themes/brutalist/sounds.json new file mode 100644 index 0000000..ffafee1 --- /dev/null +++ b/internal/generator/templates/themes/brutalist/sounds.json @@ -0,0 +1,342 @@ +{ + "splash": { + "freqs": [ + 261.63, + 329.63, + 392, + 523.25 + ], + "spacing": 0.09, + "gain": 0.1, + "wave": "square" + }, + "nav": { + "freq": 440, + "wave": "square", + "dur": 0.06, + "gain": 0.11 + }, + "open": { + "wave": "square", + "start": 440, + "end": 880, + "dur": 0.15, + "gain": 0.1 + }, + "close": { + "wave": "square", + "start": 660, + "end": 220, + "dur": 0.15, + "gain": 0.09 + }, + "bounce": { + "wave": "square", + "start": 200, + "end": 100, + "dur": 0.1, + "gain": 0.1 + }, + "ambient": { + "normal": { + "bpm": 140, + "gain": 0.032, + "wave": "sawtooth", + "droneFreqs": [ + 65.41, + 98, + 130.81 + ], + "cutoffMin": 1200, + "cutoffMax": 5500, + "attack": 0.02, + "release": 0.1, + "melody": [ + { + "freq": 65.41, + "dur": 0.096525, + "step": 0.2145 + }, + { + "freq": 65.41, + "dur": 0.096525, + "step": 0.2145 + }, |
