package synth_test import ( "math" "testing" "github.com/netsynth/netsynth/synth" ) func TestLFOBounds(t *testing.T) { lfo := synth.NewLFO(1.0, 0.5, 44100) // 1 Hz, depth 0.5 for i := 0; i < 44100; i++ { val := lfo.Advance() if val < -0.5 || val > 0.5 { t.Errorf("LFO value %v out of bounds [-0.5, 0.5] at sample %d", val, i) break } } } func TestLFOPeriod(t *testing.T) { // A 1 Hz LFO should complete one cycle in exactly SampleRate samples lfo := synth.NewLFO(1.0, 1.0, 44100) // Advance to first zero-crossing (quarter period) var firstPositive float64 for i := 0; i < 44100; i++ { val := lfo.Advance() if i == 0 { firstPositive = val } // After one full cycle, value should be close to the first value if i == 44099 { lastVal := val // They won't be exactly equal due to phase advancement, but should be close if math.Abs(lastVal-firstPositive) > 0.01 { t.Errorf("LFO not periodic: first=%v, after 1 cycle=%v", firstPositive, lastVal) } } } } func TestPitchLFOFreq(t *testing.T) { // 0 semitones should return baseHz unchanged if synth.PitchLFOFreq(440.0, 0.0) != 440.0 { t.Errorf("PitchLFOFreq(440, 0) should be 440, got %v", synth.PitchLFOFreq(440.0, 0.0)) } // 12 semitones = one octave up result := synth.PitchLFOFreq(440.0, 12.0) if math.Abs(result-880.0) > 0.01 { t.Errorf("PitchLFOFreq(440, 12) should be 880, got %v", result) } // Small detuning: 0.1 semitones result = synth.PitchLFOFreq(440.0, 0.1) if result <= 440.0 || result >= 445.0 { t.Errorf("PitchLFOFreq(440, 0.1) should be slightly above 440, got %v", result) } } func TestLFOConfigForGroup(t *testing.T) { cfg := synth.LFOConfigForGroup("Web") if cfg.PitchRate == 0 { t.Error("Web group should have non-zero PitchRate") } if cfg.TremoloRate == 0 { t.Error("Web group should have non-zero TremoloRate") } // Unknown group should return default cfg2 := synth.LFOConfigForGroup("NonexistentGroup") if cfg2.PitchRate == 0 { t.Error("fallback config should have non-zero PitchRate") } } func TestGroupLFORatesIncommensurable(t *testing.T) { // Verify that no two groups share the exact same pitch or tremolo rate groups := []string{"Infrastructure", "Web", "Mail", "Remote Access", "File Transfer", "Database", "VoIP", "Unknown"} pitchRates := make(map[float64]string) tremoloRates := make(map[float64]string) for _, g := range groups { cfg := synth.LFOConfigForGroup(g) if prev, exists := pitchRates[cfg.PitchRate]; exists { t.Errorf("groups %q and %q share PitchRate=%v", prev, g, cfg.PitchRate) } pitchRates[cfg.PitchRate] = g if prev, exists := tremoloRates[cfg.TremoloRate]; exists { t.Errorf("groups %q and %q share TremoloRate=%v", prev, g, cfg.TremoloRate) } tremoloRates[cfg.TremoloRate] = g } }