Files
metro-puzzle-jp/tools/gensound/main.go
claude de61568e44
All checks were successful
release / build-release (push) Successful in 1m33s
M9b: folk, filer, reklam, sorl och vagnval
Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot
utgången, blockerar spelaren men inte varandra), vänster-/högerfiler
som bromsar på fel sida, bredare gångar, reklamaffischer (clutter),
japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med
man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per
vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn,
släpp förbi passagerare) och att folk kliver av innan man får gå på.
Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 12:33:44 +02:00

141 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Genererar placeholder-ljud tills riktiga inspelningar finns:
//
// go run ./tools/gensound
//
// Skriver resources/sound/melody-approach.wav (en kort pentatonisk
// avgångsmelodi i japansk stationsstil, spelas när tåget är på väg in) och
// resources/sound/door-chime.wav (dörrsignal strax före avgång). 16-bit
// mono 44,1 kHz, syntetiserade med enkla vågformer så att allt är
// deterministiskt.
package main
import (
"encoding/binary"
"fmt"
"math"
"os"
"path/filepath"
)
const rate = 44100
type note struct {
freq float64 // Hz, 0 = paus
dur float64 // sekunder
}
// Pentatonisk melodi (C-dur-pentatonik), två takter.
var approachMelody = []note{
{523.25, 0.18}, {587.33, 0.18}, {659.25, 0.18}, {783.99, 0.36},
{880.00, 0.18}, {783.99, 0.18}, {659.25, 0.18}, {523.25, 0.36},
{0, 0.12},
{659.25, 0.18}, {783.99, 0.18}, {880.00, 0.18}, {1046.50, 0.54},
}
// Dörrsignal: två toner växelvis.
var doorChime = []note{
{1318.51, 0.14}, {1046.50, 0.14}, {1318.51, 0.14}, {1046.50, 0.14},
{1318.51, 0.14}, {1046.50, 0.14}, {1318.51, 0.14}, {1046.50, 0.28},
}
func render(notes []note, attack, release float64, overtone float64) []int16 {
var out []int16
for _, n := range notes {
samples := int(n.dur * rate)
for i := 0; i < samples; i++ {
if n.freq == 0 {
out = append(out, 0)
continue
}
t := float64(i) / rate
env := 1.0
if t < attack {
env = t / attack
}
if rem := n.dur - t; rem < release {
env *= rem / release
}
v := math.Sin(2*math.Pi*n.freq*t) + overtone*math.Sin(2*math.Pi*2*n.freq*t)
v = v / (1 + overtone) * env * 0.5
out = append(out, int16(v*32767))
}
}
return out
}
func writeWAV(path string, pcm []int16) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
dataLen := uint32(len(pcm) * 2)
hdr := []any{
[]byte("RIFF"), uint32(36 + dataLen), []byte("WAVE"),
[]byte("fmt "), uint32(16), uint16(1), uint16(1), uint32(rate), uint32(rate * 2), uint16(2), uint16(16),
[]byte("data"), dataLen,
}
for _, h := range hdr {
if err := binary.Write(f, binary.LittleEndian, h); err != nil {
return err
}
}
return binary.Write(f, binary.LittleEndian, pcm)
}
// chatter syntetiserar sex sekunder loopbart stationssorl: lågpassat brus
// med stavelseliknande amplitudmodulation från en deterministisk LCG.
func chatter(seconds float64) []int16 {
n := int(seconds * rate)
out := make([]int16, n)
var lcg uint32 = 12345
next := func() float64 { lcg = lcg*1664525 + 1013904223; return float64(lcg>>8)/float64(1<<24)*2 - 1 }
lp := 0.0
env, envTarget, envT := 0.3, 0.3, 0.0
for i := 0; i < n; i++ {
t := float64(i) / rate
// ny "stavelse" var 120–250 ms
if t >= envT {
envTarget = 0.1 + 0.6*math.Abs(next())
envT = t + 0.12 + 0.13*math.Abs(next())
}
env += (envTarget - env) * 0.002
lp += (next() - lp) * 0.08 // lågpass ≈ 600 Hz
v := lp * env * 0.6
// mjuk in-/utfasning för sömlös loop
fade := math.Min(1, math.Min(t, seconds-t)/0.2)
out[i] = int16(v * fade * 32767)
}
return out
}
func main() {
out := filepath.Join(repoRoot(), "resources", "sound")
must(os.MkdirAll(out, 0o755))
must(writeWAV(filepath.Join(out, "melody-approach.wav"), render(approachMelody, 0.01, 0.06, 0.3)))
must(writeWAV(filepath.Join(out, "door-chime.wav"), render(doorChime, 0.005, 0.04, 0.1)))
must(writeWAV(filepath.Join(out, "chatter.wav"), chatter(6)))
fmt.Println("skrev", filepath.Join(out, "melody-approach.wav"), ", door-chime.wav och chatter.wav")
}
func repoRoot() string {
dir, _ := os.Getwd()
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
must(fmt.Errorf("hittade ingen go.mod"))
}
dir = parent
}
}
func must(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, "gensound:", err)
os.Exit(1)
}
}