Files
Tobias GesellchenandClaude Opus 4.7 147a69d1c3 refactor(ding): synthesise on demand instead of vendoring the WAV
Move the ding renderer into pkg/service/ding so it can run both
at request time (from the new HandleDing handler) and offline
(from the existing scripts/gen-aftertouch-ding CLI, now a thin
wrapper around the same package).

- GET /media/aftertouch-ding.wav synthesises on first call,
  caches the default-options bytes via sync.Once, and accepts
  query-string overrides for every knob (pitch-{high,mid,low},
  chirp-ms, gap-ms, attack-ms, release-ms, sample-rate, peak).
  Invalid / out-of-range values silently fall back to defaults.
- Embedded WAV is gone from VCS — no 52 KB binary in the
  repo, and tweaking the sound is now a query-param away rather
  than a regenerate-and-commit cycle.
- Health-tab playback_test check is unchanged: the URL it
  references (/media/aftertouch-ding.wav) keeps the same shape,
  the handler just produces the bytes dynamically now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 23:20:40 +02:00

66 lines
2.0 KiB
Go

// Offline generator for the AfterTouch "ding" WAV. Thin CLI
// wrapper around pkg/service/ding so the same renderer used at
// runtime (HandleDing on GET /media/aftertouch-ding.wav) can
// also be invoked from the shell — handy for previewing parameter
// tweaks or producing a one-off file for sharing.
//
// Run:
//
// go run ./scripts/gen-aftertouch-ding > ding.wav
// go run ./scripts/gen-aftertouch-ding -o ding.wav
// go run ./scripts/gen-aftertouch-ding -pitch-high 1200 -o ding.wav
//
// All flags fall back to defaults defined in pkg/service/ding;
// pass only the knobs you want to override.
package main
import (
"flag"
"fmt"
"io"
"os"
"github.com/gesellix/bose-soundtouch/pkg/service/ding"
)
func main() {
var (
outPath string
opts ding.Options
)
flag.StringVar(&outPath, "o", "", "output WAV path; default stdout")
flag.IntVar(&opts.SampleRate, "sample-rate", 0, "Hz; 0 → default 22050")
flag.Float64Var(&opts.PitchHigh, "pitch-high", 0, "Hz, top row; 0 → default 880 (A5)")
flag.Float64Var(&opts.PitchMid, "pitch-mid", 0, "Hz, mid row; 0 → default 659.25 (E5)")
flag.Float64Var(&opts.PitchLow, "pitch-low", 0, "Hz, bottom row; 0 → default 440 (A4)")
flag.Float64Var(&opts.ChirpDuration, "chirp-sec", 0, "chirp duration; 0 → default 0.25")
flag.Float64Var(&opts.GapDuration, "gap-sec", 0, "between-chirp gap; 0 → default 0.10")
flag.Float64Var(&opts.AttackDuration, "attack-sec", 0, "fade-in; 0 → default 0.020")
flag.Float64Var(&opts.ReleaseDuration, "release-sec", 0, "fade-out; 0 → default 0.060")
flag.Float64Var(&opts.Peak, "peak", 0, "0..1 headroom; 0 → default 0.85")
flag.Parse()
data := ding.Render(opts)
var w io.Writer = os.Stdout
if outPath != "" {
f, err := os.Create(outPath)
if err != nil {
fmt.Fprintf(os.Stderr, "gen-aftertouch-ding: create %s: %v\n", outPath, err)
os.Exit(1)
}
defer func() { _ = f.Close() }()
w = f
}
if _, err := w.Write(data); err != nil {
fmt.Fprintf(os.Stderr, "gen-aftertouch-ding: write: %v\n", err)
os.Exit(1)
}
}