diff --git a/synth/waveform_test.go b/synth/waveform_test.go new file mode 100644 index 0000000..fc1582b --- /dev/null +++ b/synth/waveform_test.go @@ -0,0 +1,136 @@ +package synth_test + +import ( + "testing" + + "github.com/netsynth/netsynth/synth" +) + +func TestWaveformPresetHarmonics_Sine(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformSine, 440.0, 44100) + if len(harmonics) != 1 { + t.Fatalf("WaveformSine: expected 1 harmonic, got %d", len(harmonics)) + } + if harmonics[0].Ratio != 1 { + t.Errorf("WaveformSine: expected Ratio=1, got %d", harmonics[0].Ratio) + } + if harmonics[0].Amplitude != 1.0 { + t.Errorf("WaveformSine: expected Amplitude=1.0, got %.4f", harmonics[0].Amplitude) + } +} + +func TestWaveformPresetHarmonics_Square(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformSquare, 440.0, 44100) + if len(harmonics) == 0 { + t.Fatal("WaveformSquare: expected at least one harmonic, got 0") + } + nyquist := 22050.0 + for _, h := range harmonics { + if float64(h.Ratio)*440.0 >= nyquist { + t.Errorf("WaveformSquare: harmonic ratio %d exceeds Nyquist (freq=%.1f)", h.Ratio, float64(h.Ratio)*440.0) + } + expectedAmp := 1.0 / float64(h.Ratio) + if abs(h.Amplitude-expectedAmp) > 1e-9 { + t.Errorf("WaveformSquare: harmonic %d: expected amplitude %.6f, got %.6f", h.Ratio, expectedAmp, h.Amplitude) + } + } +} + +func TestWaveformPresetHarmonics_Sawtooth(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformSawtooth, 440.0, 44100) + if len(harmonics) == 0 { + t.Fatal("WaveformSawtooth: expected at least one harmonic, got 0") + } + nyquist := 22050.0 + for _, h := range harmonics { + if float64(h.Ratio)*440.0 >= nyquist { + t.Errorf("WaveformSawtooth: harmonic ratio %d exceeds Nyquist (freq=%.1f)", h.Ratio, float64(h.Ratio)*440.0) + } + expectedAmp := 1.0 / float64(h.Ratio) + if abs(h.Amplitude-expectedAmp) > 1e-9 { + t.Errorf("WaveformSawtooth: harmonic %d: expected amplitude %.6f, got %.6f", h.Ratio, expectedAmp, h.Amplitude) + } + } +} + +func TestWaveformPresetHarmonics_Triangle(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformTriangle, 440.0, 44100) + if len(harmonics) == 0 { + t.Fatal("WaveformTriangle: expected at least one harmonic, got 0") + } + nyquist := 22050.0 + sign := 1.0 + for i, h := range harmonics { + if float64(h.Ratio)*440.0 >= nyquist { + t.Errorf("WaveformTriangle: harmonic ratio %d exceeds Nyquist (freq=%.1f)", h.Ratio, float64(h.Ratio)*440.0) + } + expectedAmp := sign / float64(h.Ratio*h.Ratio) + if abs(h.Amplitude-expectedAmp) > 1e-9 { + t.Errorf("WaveformTriangle: harmonic %d (index %d): expected amplitude %.6f, got %.6f", h.Ratio, i, expectedAmp, h.Amplitude) + } + sign = -sign + } +} + +func TestWaveformPresetHarmonics_Custom(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformCustom, 440.0, 44100) + if harmonics != nil { + t.Errorf("WaveformCustom: expected nil, got %v", harmonics) + } +} + +func TestBandlimitedHarmonicsNoAliasing(t *testing.T) { + waveforms := []synth.WaveformType{ + synth.WaveformSine, + synth.WaveformSquare, + synth.WaveformSawtooth, + synth.WaveformTriangle, + } + for _, cfg := range synth.ClassFreqConfigs { + for _, wt := range waveforms { + harmonics := synth.WaveformPresetHarmonics(wt, cfg.BaseHz, 44100) + for _, h := range harmonics { + freq := float64(h.Ratio) * cfg.BaseHz + if freq >= 22050.0 { + t.Errorf("waveform %d, baseHz=%.1f: harmonic ratio %d produces freq=%.1f >= Nyquist 22050", wt, cfg.BaseHz, h.Ratio, freq) + } + } + } + } +} + +func TestWaveformPresetHarmonics_SquareOddOnly(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformSquare, 440.0, 44100) + for _, h := range harmonics { + if h.Ratio%2 == 0 { + t.Errorf("WaveformSquare: found even ratio %d (should be odd-only)", h.Ratio) + } + } +} + +func TestWaveformPresetHarmonics_TriangleOddOnly(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformTriangle, 440.0, 44100) + for _, h := range harmonics { + if h.Ratio%2 == 0 { + t.Errorf("WaveformTriangle: found even ratio %d (should be odd-only)", h.Ratio) + } + } +} + +func TestWaveformPresetHarmonics_SawtoothConsecutive(t *testing.T) { + harmonics := synth.WaveformPresetHarmonics(synth.WaveformSawtooth, 440.0, 44100) + for i, h := range harmonics { + expected := i + 1 + if h.Ratio != expected { + t.Errorf("WaveformSawtooth: index %d: expected ratio %d, got %d", i, expected, h.Ratio) + } + } +} + +// abs returns the absolute value of x. +func abs(x float64) float64 { + if x < 0 { + return -x + } + return x +}