M7b: tågfaser, räls, in-/utkörning, vind, ankomstindikator och melodier
All checks were successful
release / build-release (push) Successful in 1m42s
All checks were successful
release / build-release (push) Successful in 1m42s
- level.Schedule.Phase: away/approaching/arriving/dwelling/closing/leaving ur tidtabellen med progress; Boardable (öppna dörrar) och Present; validering av minsta uppehåll/borta-tid; TrackTile för rälsen - sim.Run: ombordstigning bara med öppna dörrar, TrainVisible via Present, PlatformPhase och NearTrack (närmaste perrong inom två rutor) - game/trains.go: rälsrutor längs kantraden (roterade för vertikala spår), tre vagnar som bromsar in och accelererar ut längs rälsen, strålkastare under inrullningen, blinkande lampor vid kanten för den som står nära, dammpartiklar längs perrongen; kameran får en rutas marginal så att kanträlsen syns - game/audio.go: ebiten/v2/audio med avgångsmelodi (approaching) och dörrsignal (closing), volym efter avstånd, -mute; tools/gensound syntetiserar placeholder-WAV:ar - HUD: fasberoende perrongtext och "tåg på väg in" nära rälsen; skriptkommandot waitphase; skärmdump av ankomsten - plan (Björn 2026-10-24): räls/väg-grafik och melodierna i M7b; folk/NPC:er, vänster-/högertrafik, bredare gångar, reklam- och ljudröra som nytt steg M9b – i spelbeskrivning, roadmap, testplan - docs/README/wiki uppdaterade Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
113
tools/gensound/main.go
Normal file
113
tools/gensound/main.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user