feat(03-01): extend TrafficClass to 14 classes with hash-bucketed unknowns
- Replace ClassUnknown with ClassUnknown1-4 (unknown-1 through unknown-4) - AllClasses() now returns 14 elements - Add hashBucket() function routing unrecognized traffic deterministically - Update synth/config.go: NumLayers=14, 4 dissonant unknown tone configs at 862-1047 Hz - Add TestAllClassesCount and TestHashBucketDistribution tests - Add TestClassFreqConfigsComplete and TestNumLayersMatchesAllClasses to config_test.go - Update TestNewBankHas14Layers, TestMixerNoClip for 14 classes - All classify and synth tests pass
This commit is contained in:
+5
-5
@@ -7,10 +7,10 @@ import (
|
||||
"github.com/netsynth/netsynth/classify"
|
||||
)
|
||||
|
||||
func TestNewBankHas11Layers(t *testing.T) {
|
||||
func TestNewBankHas14Layers(t *testing.T) {
|
||||
b := NewBank(1.0)
|
||||
if len(b.layers) != 11 {
|
||||
t.Errorf("NewBank() has %d layers, want 11", len(b.layers))
|
||||
if len(b.layers) != 14 {
|
||||
t.Errorf("NewBank() has %d layers, want 14", len(b.layers))
|
||||
}
|
||||
// Verify each class has exactly one layer
|
||||
for _, class := range classify.AllClasses() {
|
||||
@@ -76,13 +76,13 @@ func TestRenderWindowNonZeroWithTraffic(t *testing.T) {
|
||||
func TestMixerNoClip(t *testing.T) {
|
||||
b := NewBank(0.01) // fast EMA to quickly ramp up to near-max amplitude
|
||||
counts := make(map[classify.TrafficClass]int64)
|
||||
// All 11 classes at max count — worst-case mixing scenario
|
||||
// All 14 classes at max count — worst-case mixing scenario
|
||||
for _, class := range classify.AllClasses() {
|
||||
counts[class] = 1000
|
||||
}
|
||||
snap := classify.WindowSnapshot{
|
||||
Counts: counts,
|
||||
TotalPackets: 11000,
|
||||
TotalPackets: 14000,
|
||||
WindowIndex: 0,
|
||||
}
|
||||
// Render multiple windows to let EMA converge
|
||||
|
||||
+8
-5
@@ -6,8 +6,8 @@ const (
|
||||
SampleRate = 44100 // D-13: CD quality
|
||||
WindowMs = 500 // matches aggregate.DefaultWindowMs
|
||||
SamplesPerWindow = SampleRate * WindowMs / 1000 // 22050
|
||||
NumLayers = 11
|
||||
GainPerLayer = 1.0 / float64(NumLayers) // D-10: ~0.0909
|
||||
NumLayers = 14
|
||||
GainPerLayer = 1.0 / float64(NumLayers) // D-10: ~0.0714
|
||||
WhisperFloor = 0.03 // D-08/D-09: 3% of max amplitude
|
||||
)
|
||||
|
||||
@@ -38,7 +38,10 @@ var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{
|
||||
classify.ClassDHCP: {600.0, []HarmonicDef{{1, 1.0}, {2, 0.35}, {3, 0.15}}, -0.75},
|
||||
classify.ClassOtherTCP: {700.0, []HarmonicDef{{1, 1.0}, {2, 0.2}}, 0.75},
|
||||
classify.ClassOtherUDP: {780.0, []HarmonicDef{{1, 1.0}, {2, 0.2}}, -0.75},
|
||||
// D-04: 437 Hz is ~12 cents flat from A4 (440 Hz/SMTP).
|
||||
// Creates 3 Hz beating when SMTP is present = dissonant "doesn't belong" signal.
|
||||
classify.ClassUnknown: {437.0, []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, 0.0},
|
||||
// D-05: Unknown buckets in 850-1100 Hz dissonant range, detuned intervals
|
||||
// D-06: Same dissonant harmonic character {1,1.0},{2,0.8},{3,0.4} for all 4
|
||||
classify.ClassUnknown1: {862.0, []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, 0.6},
|
||||
classify.ClassUnknown2: {920.0, []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, -0.6},
|
||||
classify.ClassUnknown3: {981.0, []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, 0.9},
|
||||
classify.ClassUnknown4: {1047.0, []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, -0.9},
|
||||
}
|
||||
|
||||
+17
-2
@@ -17,8 +17,9 @@ func TestAllClassesHaveConfig(t *testing.T) {
|
||||
|
||||
func TestFrequenciesInRange(t *testing.T) {
|
||||
for class, cfg := range synth.ClassFreqConfigs {
|
||||
if cfg.BaseHz < 60 || cfg.BaseHz > 800 {
|
||||
t.Errorf("class %q BaseHz=%.1f is out of range [60, 800]", class, cfg.BaseHz)
|
||||
// D-05: unknown buckets use 850-1100 Hz dissonant range; known classes use 60-800 Hz
|
||||
if cfg.BaseHz < 60 || cfg.BaseHz > 1100 {
|
||||
t.Errorf("class %q BaseHz=%.1f is out of range [60, 1100]", class, cfg.BaseHz)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,3 +49,17 @@ func TestPanPositionsInRange(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassFreqConfigsComplete(t *testing.T) {
|
||||
for _, class := range classify.AllClasses() {
|
||||
if _, ok := synth.ClassFreqConfigs[class]; !ok {
|
||||
t.Errorf("ClassFreqConfigs missing entry for class %q", class)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user