package sfx import ( "bytes" "math" "strings" "testing" ) func TestParseAndValidate(t *testing.T) { src := ` # a jump sfx: jump wave: square freq: 330 freq-slide: 900 attack: 0.01 sustain: 0.08 decay: 0.18 duty: 0.4 ` p, err := Parse(strings.NewReader(src)) if err != nil { t.Fatal(err) } if p.Name != "jump" || p.Wave != "square" || p.Freq != 330 || p.Duty != 0.4 { t.Errorf("parsed wrong: %+v", p) } if math.Abs(p.Duration()-0.27) > 1e-9 { t.Errorf("duration = %g, want 0.27", p.Duration()) } } func TestParseErrors(t *testing.T) { cases := map[string]string{ "unknown key": "wat: 3\n", "bad wave": "wave: wobble\n", "bad value": "freq: abc\n", "zero length": "attack: 0\nsustain: 0\ndecay: 0\n", "volume range": "volume: 2\n", "duty range": "duty: 0.99\n", } for name, src := range cases { if _, err := Parse(strings.NewReader(src)); err == nil { t.Errorf("%s: expected error", name) } } } func TestRenderBasics(t *testing.T) { p := Defaults() p.Wave = "sine" p.Attack, p.Sustain, p.Decay = 0.01, 0.05, 0.05 samples := Render(&p) want := int(0.11 * 44100) if len(samples) != want { t.Errorf("samples = %d, want %d", len(samples), want) } var peak float64 for _, s := range samples { if math.Abs(s) > peak { peak = math.Abs(s) } if s > 1 || s < -1 { t.Fatalf("sample %g out of range", s) } } if peak < 0.5 { t.Errorf("peak %g suspiciously quiet", peak) } // end of decay should be silent-ish tail := samples[len(samples)-10:] for _, s := range tail { if math.Abs(s) > 0.1 { t.Errorf("tail sample %g not decayed", s) } } } func TestRenderDeterministic(t *testing.T) { p := Defaults() p.Wave = "noise" p.Seed = 42 a := Render(&p) b := Render(&p) for i := range a { if a[i] != b[i] { t.Fatalf("noise render not deterministic at sample %d", i) } } } func TestAllWavesAndPresets(t *testing.T) { for _, w := range []string{"square", "saw", "sine", "triangle", "noise"} { p := Defaults() p.Wave = w if s := Render(&p); len(s) == 0 { t.Errorf("wave %s rendered nothing", w) } } for _, name := range []string{"blip", "coin", "explosion", "hurt", "jump", "laser", "powerup"} { p, err := Preset(name, 0) if err != nil { t.Errorf("preset %s: %v", name, err) continue } s := Render(p) var sum float64 for _, v := range s { sum += v * v } rms := math.Sqrt(sum / float64(len(s))) if rms < 0.01 { t.Errorf("preset %s is nearly silent (rms %g)", name, rms) } // variants stay valid if _, err := Preset(name, 7); err != nil { t.Errorf("preset %s seed 7: %v", name, err) } } if _, err := Preset("nope", 0); err == nil { t.Error("unknown preset should error") } } func TestWAVRoundTrip(t *testing.T) { p := Defaults() samples := Render(&p) var buf bytes.Buffer if err := WriteWAV(&buf, samples, p.SampleRate); err != nil { t.Fatal(err) } sr, bits, ch, dataBytes, err := ReadWAVHeader(&buf) if err != nil { t.Fatal(err) } if sr != 44100 || bits != 16 || ch != 1 { t.Errorf("header: sr=%d bits=%d ch=%d", sr, bits, ch) } if dataBytes != len(samples)*2 { t.Errorf("dataBytes = %d, want %d", dataBytes, len(samples)*2) } if buf.Len() != dataBytes { t.Errorf("body length %d != declared %d", buf.Len(), dataBytes) } } func TestParamsWriteRoundTrip(t *testing.T) { p, err := Preset("coin", 3) if err != nil { t.Fatal(err) } var buf bytes.Buffer if err := p.Write(&buf, "test"); err != nil { t.Fatal(err) } back, err := Parse(&buf) if err != nil { t.Fatalf("re-parse of written .sfx failed: %v\n%s", err, buf.String()) } if back.Freq != p.Freq || back.ArpFactor != p.ArpFactor || back.Seed != p.Seed { t.Errorf("roundtrip mismatch: %+v vs %+v", back, p) } }