diff --git a/synth/bank_test.go b/synth/bank_test.go index 4056bda..37dd5e2 100644 --- a/synth/bank_test.go +++ b/synth/bank_test.go @@ -8,7 +8,7 @@ import ( ) func TestNewBankHas14Layers(t *testing.T) { - b := NewBank(1.0) + b := NewBank(1.0, ClassFreqConfigs) if len(b.layers) != 14 { t.Errorf("NewBank() has %d layers, want 14", len(b.layers)) } @@ -21,7 +21,7 @@ func TestNewBankHas14Layers(t *testing.T) { } func TestRenderWindowOutputLength(t *testing.T) { - b := NewBank(1.0) + b := NewBank(1.0, ClassFreqConfigs) snap := classify.WindowSnapshot{ Counts: make(map[classify.TrafficClass]int64), TotalPackets: 0, @@ -34,7 +34,7 @@ func TestRenderWindowOutputLength(t *testing.T) { } func TestRenderWindowSilentWhenNoTraffic(t *testing.T) { - b := NewBank(1.0) + b := NewBank(1.0, ClassFreqConfigs) // Empty counts — no class ever seen — all layers should stay at zero amplitude snap := classify.WindowSnapshot{ Counts: make(map[classify.TrafficClass]int64), @@ -51,7 +51,7 @@ func TestRenderWindowSilentWhenNoTraffic(t *testing.T) { } func TestRenderWindowNonZeroWithTraffic(t *testing.T) { - b := NewBank(1.0) + b := NewBank(1.0, ClassFreqConfigs) counts := make(map[classify.TrafficClass]int64) counts[classify.ClassICMP] = 100 snap := classify.WindowSnapshot{ @@ -74,15 +74,15 @@ func TestRenderWindowNonZeroWithTraffic(t *testing.T) { } func TestMixerNoClip(t *testing.T) { - b := NewBank(0.01) // fast EMA to quickly ramp up to near-max amplitude + b := NewBank(0.01, ClassFreqConfigs) // fast EMA to quickly ramp up to near-max amplitude counts := make(map[classify.TrafficClass]int64) // All 14 classes at max count — worst-case mixing scenario - for _, class := range classify.AllClasses() { + for class := range ClassFreqConfigs { counts[class] = 1000 } snap := classify.WindowSnapshot{ Counts: counts, - TotalPackets: 14000, + TotalPackets: int64(len(ClassFreqConfigs)) * 1000, WindowIndex: 0, } // Render multiple windows to let EMA converge @@ -102,7 +102,7 @@ func TestMixerNoClip(t *testing.T) { } func TestStereoPan(t *testing.T) { - b := NewBank(0.01) // fast EMA + b := NewBank(0.01, ClassFreqConfigs) // fast EMA counts := make(map[classify.TrafficClass]int64) // ClassDHCP has pan=-0.75 (wide-left in config.go) counts[classify.ClassDHCP] = 1000 @@ -130,7 +130,7 @@ func TestStereoPan(t *testing.T) { } func TestMultipleWindowsEMAConvergence(t *testing.T) { - b := NewBank(1.0) + b := NewBank(1.0, ClassFreqConfigs) counts := make(map[classify.TrafficClass]int64) counts[classify.ClassICMP] = 100 snap := classify.WindowSnapshot{ @@ -150,6 +150,44 @@ func TestMultipleWindowsEMAConvergence(t *testing.T) { } } +func TestNewBankDynamicGain(t *testing.T) { + // Create a config map with only 3 classes + cfgs := map[classify.TrafficClass]FreqConfig{ + classify.ClassICMP: ClassFreqConfigs[classify.ClassICMP], + classify.ClassDNS: ClassFreqConfigs[classify.ClassDNS], + classify.ClassHTTPS: ClassFreqConfigs[classify.ClassHTTPS], + } + b := NewBank(0.01, cfgs) + if len(b.layers) != 3 { + t.Errorf("NewBank with 3 configs has %d layers, want 3", len(b.layers)) + } + // Verify gainPerLayer is 1/3 + expected := 1.0 / 3.0 + if b.gainPerLayer != expected { + t.Errorf("gainPerLayer = %v, want %v", b.gainPerLayer, expected) + } +} + +func TestNewBankCustomConfigNoClip(t *testing.T) { + cfgs := map[classify.TrafficClass]FreqConfig{ + classify.ClassICMP: ClassFreqConfigs[classify.ClassICMP], + classify.ClassDNS: ClassFreqConfigs[classify.ClassDNS], + } + b := NewBank(0.01, cfgs) + counts := map[classify.TrafficClass]int64{ + classify.ClassICMP: 1000, + classify.ClassDNS: 1000, + } + snap := classify.WindowSnapshot{Counts: counts, TotalPackets: 2000, WindowIndex: 0} + for i := 0; i < 10; i++ { + for _, frame := range b.RenderWindow(snap) { + if frame[0] > 1.0 || frame[0] < -1.0 || frame[1] > 1.0 || frame[1] < -1.0 { + t.Fatalf("clipped with 2-class config: L=%v R=%v", frame[0], frame[1]) + } + } + } +} + // windowRMS computes the root mean square amplitude across all stereo frames. func windowRMS(frames [][2]float64) float64 { var sum float64 diff --git a/synth/config_test.go b/synth/config_test.go index f1e822c..e969a7e 100644 --- a/synth/config_test.go +++ b/synth/config_test.go @@ -59,7 +59,8 @@ func TestClassFreqConfigsComplete(t *testing.T) { } func TestNumLayersMatchesAllClasses(t *testing.T) { - if synth.NumLayers != len(classify.AllClasses()) { - t.Errorf("NumLayers=%d but AllClasses() has %d entries", synth.NumLayers, len(classify.AllClasses())) + if len(synth.ClassFreqConfigs) != len(classify.AllClasses()) { + t.Errorf("ClassFreqConfigs has %d entries but AllClasses() has %d entries", + len(synth.ClassFreqConfigs), len(classify.AllClasses())) } }