package synth // EnvelopeState tracks the current phase of an ADSR envelope. type EnvelopeState int const ( EnvIdle EnvelopeState = iota // silent, waiting for trigger EnvAttack // ramping up to peak EnvDecay // falling from peak to sustain level EnvSustain // holding at sustain level (modulated by traffic rate) EnvRelease // fading out after traffic stops ) // Envelope is an ADSR envelope generator with exponential curves. // It wraps the traffic-rate amplitude: the envelope shapes onset/offset, // while the traffic rate modulates within the sustain phase. type Envelope struct { state EnvelopeState level float64 // current envelope level [0, 1] attackRate float64 // per-sample (exponential approach) decayRate float64 // per-sample sustainLevel float64 // target level during sustain [0, 1] releaseRate float64 // per-sample trafficAmp float64 // EMA-smoothed traffic amplitude [0, 1] emaAlpha float64 // EMA coefficient for traffic smoothing } // EnvelopeParams configures ADSR timing. type EnvelopeParams struct { AttackSec float64 // seconds to reach peak DecaySec float64 // seconds from peak to sustain level SustainLevel float64 // sustain amplitude [0, 1] ReleaseSec float64 // seconds to fade to silence } // Sustained flow envelope: slow, ambient feel. var SustainedEnvParams = EnvelopeParams{ AttackSec: 2.0, DecaySec: 1.0, SustainLevel: 0.85, ReleaseSec: 4.0, } // Bursty protocol envelope: percussive, event-like. var BurstyEnvParams = EnvelopeParams{ AttackSec: 0.03, DecaySec: 0.3, SustainLevel: 0.0, ReleaseSec: 1.5, } // NewEnvelope creates an ADSR envelope for the given params and sample rate. // tau is the EMA time constant for traffic amplitude smoothing (seconds). func NewEnvelope(params EnvelopeParams, sampleRate int, tau float64) *Envelope { sr := float64(sampleRate) return &Envelope{ state: EnvIdle, attackRate: 1.0 / (params.AttackSec * sr), decayRate: 1.0 / (params.DecaySec * sr), sustainLevel: params.SustainLevel, releaseRate: 1.0 / (params.ReleaseSec * sr), emaAlpha: EMAAlpha(tau, sampleRate), } } // Trigger starts the envelope (called when traffic first appears for this class). func (e *Envelope) Trigger() { if e.state == EnvIdle || e.state == EnvRelease { e.state = EnvAttack } } // Release begins the release phase (called when traffic stops). func (e *Envelope) Release() { if e.state != EnvIdle { e.state = EnvRelease } } // SetTrafficRate updates the EMA-smoothed traffic amplitude target. // rate should be normalized [0, 1] (count / maxCount). func (e *Envelope) SetTrafficRate(rate float64) { e.trafficAmp += e.emaAlpha * (rate - e.trafficAmp) } // Advance processes one sample and returns the envelope amplitude [0, 1]. func (e *Envelope) Advance() float64 { switch e.state { case EnvIdle: return 0 case EnvAttack: e.level += e.attackRate * (1.05 - e.level) // overshoot target slightly for exponential feel if e.level >= 1.0 { e.level = 1.0 e.state = EnvDecay } case EnvDecay: target := e.sustainLevel e.level += e.decayRate * (target - e.level) if e.level-target < 0.001 { e.level = target if target > 0 { e.state = EnvSustain } else { // Bursty: sustain=0, go to release e.state = EnvRelease } } case EnvSustain: // Modulate sustain level by traffic rate target := e.sustainLevel * (WhisperFloor + (1.0-WhisperFloor)*e.trafficAmp) e.level += e.emaAlpha * (target - e.level) case EnvRelease: e.level -= e.releaseRate * e.level if e.level < 0.001 { e.level = 0 e.state = EnvIdle } } return e.level } // State returns the current envelope state (for testing). func (e *Envelope) State() EnvelopeState { return e.state } // Level returns the current envelope level (for testing). func (e *Envelope) Level() float64 { return e.level }