// 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) } 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))) fmt.Println("skrev", filepath.Join(out, "melody-approach.wav"), "och door-chime.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) } }