diff --git a/synth/config.go b/synth/config.go index 67fcb2b..7636102 100644 --- a/synth/config.go +++ b/synth/config.go @@ -11,6 +11,53 @@ const ( WhisperFloor = 0.03 // D-08/D-09: 3% of max amplitude ) +// WaveformType selects the harmonic preset for a synthesis layer. +// The zero value WaveformCustom preserves existing hand-tuned harmonics in FreqConfig.Harmonics. +type WaveformType int + +const ( + WaveformCustom WaveformType = iota // zero value: use FreqConfig.Harmonics as-is + WaveformSine // pure fundamental, single harmonic + WaveformSquare // odd harmonics with 1/k amplitude (bandlimited) + WaveformSawtooth // all harmonics with 1/k amplitude (bandlimited) + WaveformTriangle // odd harmonics with alternating 1/k^2 amplitude (bandlimited) +) + +// WaveformPresetHarmonics returns the bandlimited harmonic series for the given waveform type +// at the given base frequency and sample rate. Returns nil for WaveformCustom. +// All returned harmonics are below Nyquist (sampleRate/2). +func WaveformPresetHarmonics(wt WaveformType, baseHz float64, sampleRate int) []HarmonicDef { + nyquist := float64(sampleRate) / 2.0 + switch wt { + case WaveformCustom: + return nil + case WaveformSine: + return []HarmonicDef{{Ratio: 1, Amplitude: 1.0}} + case WaveformSquare: + var harmonics []HarmonicDef + for k := 1; float64(k)*baseHz < nyquist; k += 2 { + harmonics = append(harmonics, HarmonicDef{Ratio: k, Amplitude: 1.0 / float64(k)}) + } + return harmonics + case WaveformSawtooth: + var harmonics []HarmonicDef + for k := 1; float64(k)*baseHz < nyquist; k++ { + harmonics = append(harmonics, HarmonicDef{Ratio: k, Amplitude: 1.0 / float64(k)}) + } + return harmonics + case WaveformTriangle: + var harmonics []HarmonicDef + sign := 1.0 + for k := 1; float64(k)*baseHz < nyquist; k += 2 { + harmonics = append(harmonics, HarmonicDef{Ratio: k, Amplitude: sign / float64(k*k)}) + sign = -sign + } + return harmonics + default: + return nil + } +} + // HarmonicDef defines one partial in an additive synthesizer. type HarmonicDef struct { Ratio int // harmonic number: 1=fundamental, 2=octave, 3=fifth+octave, etc. @@ -19,29 +66,30 @@ type HarmonicDef struct { // FreqConfig holds synthesis parameters for one traffic class. type FreqConfig struct { - BaseHz float64 - Harmonics []HarmonicDef - Pan float64 // [-1, 1]: -1=full left, 0=center, +1=full right + BaseHz float64 + Harmonics []HarmonicDef + Pan float64 // [-1, 1]: -1=full left, 0=center, +1=full right + WaveformType WaveformType // zero value WaveformCustom uses Harmonics as-is } // ClassFreqConfigs maps each traffic class to its synthesis parameters. // Frequencies use musical intervals per D-02/D-03. Harmonics per D-05/D-06. // Pan positions per D-12. var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{ - classify.ClassICMP: {65.0, []HarmonicDef{{1, 1.0}, {2, 0.4}, {3, 0.15}}, 0.0}, - classify.ClassDNS: {110.0, []HarmonicDef{{1, 1.0}, {2, 0.5}, {3, 0.25}}, -0.2}, - classify.ClassHTTPS: {175.0, []HarmonicDef{{1, 1.0}, {2, 0.6}, {3, 0.3}}, 0.2}, - classify.ClassHTTP: {220.0, []HarmonicDef{{1, 1.0}, {2, 0.5}, {4, 0.2}}, -0.35}, - classify.ClassSSH: {330.0, []HarmonicDef{{1, 1.0}, {3, 0.6}, {5, 0.3}}, 0.35}, - classify.ClassSMTP: {440.0, []HarmonicDef{{1, 1.0}, {2, 0.3}, {3, 0.1}}, -0.55}, - classify.ClassNTP: {520.0, []HarmonicDef{{1, 1.0}, {2, 0.25}}, 0.55}, - 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}, + classify.ClassICMP: {BaseHz: 65.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.4}, {3, 0.15}}, Pan: 0.0}, + classify.ClassDNS: {BaseHz: 110.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.5}, {3, 0.25}}, Pan: -0.2}, + classify.ClassHTTPS: {BaseHz: 175.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.6}, {3, 0.3}}, Pan: 0.2}, + classify.ClassHTTP: {BaseHz: 220.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.5}, {4, 0.2}}, Pan: -0.35}, + classify.ClassSSH: {BaseHz: 330.0, Harmonics: []HarmonicDef{{1, 1.0}, {3, 0.6}, {5, 0.3}}, Pan: 0.35}, + classify.ClassSMTP: {BaseHz: 440.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.3}, {3, 0.1}}, Pan: -0.55}, + classify.ClassNTP: {BaseHz: 520.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.25}}, Pan: 0.55}, + classify.ClassDHCP: {BaseHz: 600.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.35}, {3, 0.15}}, Pan: -0.75}, + classify.ClassOtherTCP: {BaseHz: 700.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.2}}, Pan: 0.75}, + classify.ClassOtherUDP: {BaseHz: 780.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.2}}, Pan: -0.75}, // 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}, + classify.ClassUnknown1: {BaseHz: 862.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: 0.6}, + classify.ClassUnknown2: {BaseHz: 920.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: -0.6}, + classify.ClassUnknown3: {BaseHz: 981.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: 0.9}, + classify.ClassUnknown4: {BaseHz: 1047.0, Harmonics: []HarmonicDef{{1, 1.0}, {2, 0.8}, {3, 0.4}}, Pan: -0.9}, }