package synth import "math" // LFO is a low-frequency oscillator for modulating synthesis parameters. // Uses sine waveform. Rate is in Hz (typically 0.01-0.5 Hz for ambient feel). type LFO struct { phase float64 rate float64 // Hz depth float64 // modulation depth (interpretation depends on usage) sr float64 } // NewLFO creates an LFO at the given rate (Hz) and depth. func NewLFO(rate, depth float64, sampleRate int) *LFO { return &LFO{ rate: rate, depth: depth, sr: float64(sampleRate), } } // Advance returns the current LFO value in [-depth, +depth] and advances the phase. func (l *LFO) Advance() float64 { val := math.Sin(2 * math.Pi * l.phase) l.phase += l.rate / l.sr if l.phase >= 1.0 { l.phase -= math.Floor(l.phase) } return val * l.depth } // PitchLFOFreq returns a pitch-modulated frequency given a base frequency // and an LFO value in semitones. For example, lfoVal=0.1 shifts pitch up by 0.1 semitones. func PitchLFOFreq(baseHz, lfoSemitones float64) float64 { return baseHz * math.Pow(2.0, lfoSemitones/12.0) } // LFOConfig holds LFO parameters for a synthesis layer. // Each protocol gets unique, incommensurable rates so modulation patterns never repeat. type LFOConfig struct { PitchRate float64 // Hz, typically 0.02-0.08 PitchDepth float64 // semitones, typically 0.05-0.15 TremoloRate float64 // Hz, typically 0.05-0.3 TremoloDepth float64 // amplitude fraction, typically 0.05-0.2 } // layerLFOConfigs provides unique incommensurable LFO rates per protocol group. // Rates chosen as non-integer ratios to avoid periodic sync (Eno technique). var groupLFOConfigs = map[string]LFOConfig{ "Infrastructure": {PitchRate: 0.031, PitchDepth: 0.08, TremoloRate: 0.053, TremoloDepth: 0.12}, "Web": {PitchRate: 0.043, PitchDepth: 0.10, TremoloRate: 0.071, TremoloDepth: 0.15}, "Mail": {PitchRate: 0.037, PitchDepth: 0.07, TremoloRate: 0.059, TremoloDepth: 0.10}, "Remote Access": {PitchRate: 0.029, PitchDepth: 0.12, TremoloRate: 0.047, TremoloDepth: 0.18}, "File Transfer": {PitchRate: 0.041, PitchDepth: 0.09, TremoloRate: 0.067, TremoloDepth: 0.13}, "Database": {PitchRate: 0.023, PitchDepth: 0.06, TremoloRate: 0.083, TremoloDepth: 0.10}, "VoIP": {PitchRate: 0.019, PitchDepth: 0.05, TremoloRate: 0.091, TremoloDepth: 0.08}, "Unknown": {PitchRate: 0.053, PitchDepth: 0.15, TremoloRate: 0.037, TremoloDepth: 0.20}, } // DefaultLFOConfig is the fallback for groups not in the map. var DefaultLFOConfig = LFOConfig{ PitchRate: 0.033, PitchDepth: 0.10, TremoloRate: 0.057, TremoloDepth: 0.15, } // LFOConfigForGroup returns the LFO config for a protocol group. func LFOConfigForGroup(group string) LFOConfig { if cfg, ok := groupLFOConfigs[group]; ok { return cfg } return DefaultLFOConfig }