feat(synth): add LFO modulation, ADSR envelopes, pentatonic tuning, and soft limiter

Replace static EMA-smoothed drones with an evolving ambient soundscape:
- ADSR envelope system with sustained (2s attack, 4s release) and bursty
  (30ms attack, no sustain) modes per protocol group
- LFO pitch wobble and amplitude tremolo with incommensurable rates per
  group (Eno technique) so modulation patterns never repeat
- C major pentatonic frequency tuning (just intonation) — any combination
  of active protocols sounds consonant
- tanh soft limiter on master output prevents clipping
- Sync all documentation: README, PROJECT.md, ARCHITECTURE.md, v1.2
  requirements traceability

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 19:38:58 +01:00
co-authored by Claude Opus 4.6
parent 494385b528
commit 6b2db48339
14 changed files with 824 additions and 255 deletions
+4 -4
View File
@@ -67,8 +67,8 @@ func TestLoadPartialOverrideWaveform(t *testing.T) {
t.Errorf("ICMP WaveformType: got %v, want WaveformSquare", cfgs[classify.ClassICMP].WaveformType)
}
// BaseHz should be unchanged (default is 65.0)
if cfgs[classify.ClassICMP].BaseHz != 65.0 {
t.Errorf("ICMP BaseHz: got %v, want 65.0 (default)", cfgs[classify.ClassICMP].BaseHz)
if cfgs[classify.ClassICMP].BaseHz != 65.4 {
t.Errorf("ICMP BaseHz: got %v, want 65.4 (default)", cfgs[classify.ClassICMP].BaseHz)
}
// Harmonics should be regenerated (non-empty)
if len(cfgs[classify.ClassICMP].Harmonics) == 0 {
@@ -121,8 +121,8 @@ func TestLoadNoConfig(t *testing.T) {
t.Errorf("result map size: got %d, want %d", len(cfgs), len(classify.AllClasses()))
}
// ICMP should be at its default BaseHz (65.0)
if cfgs[classify.ClassICMP].BaseHz != 65.0 {
t.Errorf("ICMP BaseHz: got %v, want 65.0 (default)", cfgs[classify.ClassICMP].BaseHz)
if cfgs[classify.ClassICMP].BaseHz != 65.4 {
t.Errorf("ICMP BaseHz: got %v, want 65.4 (default)", cfgs[classify.ClassICMP].BaseHz)
}
}