# Technology Stack **Project:** NetSynth v1.1 — Custom Sound Mappings **Researched:** 2026-03-26 **Scope:** Additions/changes only. Existing stack (gopacket, go-pcap, go-lame, cobra) is validated and unchanged. --- ## Existing Stack (Do Not Re-research) | Technology | Version | Status | |------------|---------|--------| | `github.com/gopacket/gopacket` | v1.5.0 | Validated in v1.0, unchanged | | `github.com/packetcap/go-pcap` | v0.0.0-20251215 | Validated in v1.0, unchanged | | `github.com/sjzar/go-lame` | v0.0.9 | Validated in v1.0, unchanged | | `github.com/spf13/cobra` | v1.10.2 | Validated in v1.0, unchanged | | Hand-rolled sine oscillator + EMA | — | Validated in v1.0, extend in place | | Ordered `[]Rule` classifier | — | Validated in v1.0, extend in place | --- ## New Dependencies for v1.1 ### TOML Config Parsing | Technology | Version | Purpose | Why | |------------|---------|---------|-----| | `github.com/BurntSushi/toml` | v1.6.0 | Parse `netsynth.toml` config files | Single-function `toml.Decode()` into a struct. The `MetaData.Undecoded()` method catches unknown keys in user configs — surfacing typos like `frequncy` rather than silently ignoring them. This is the right behavior for a config file tool. v1.6.0 released December 2025, Go 1.18+ required. Zero indirect dependencies. | **Version confirmed:** v1.6.0, December 18, 2025, via pkg.go.dev and GitHub releases page. **Why not `pelletier/go-toml v2`:** go-toml v2.3.0 (March 2026) is faster but the performance difference is irrelevant — config is read once at startup. go-toml v2's `Strict` mode can detect unknown keys but requires more setup than BurntSushi's `MetaData.Undecoded()`. BurntSushi's API is simpler for this use case and has clearer error message patterns for user-facing config mistakes. ### Config Auto-Discovery No new dependency. Use Go stdlib only: ```go // Probe order: --config flag > ./netsynth.toml > ~/.config/netsynth/config.toml func findConfigPath(flagValue string) (string, bool) { if flagValue != "" { return flagValue, true } if _, err := os.Stat("./netsynth.toml"); err == nil { return "./netsynth.toml", true } if dir, err := os.UserConfigDir(); err == nil { p := filepath.Join(dir, "netsynth", "config.toml") if _, err := os.Stat(p); err == nil { return p, true } } return "", false } ``` `os.UserConfigDir()` returns `$XDG_CONFIG_HOME` if set, else `$HOME/.config` on Linux/macOS — confirmed against Go stdlib docs. No third-party XDG library needed. ### Additional Waveform Types No new dependency. Extend the existing `synth.Oscillator` in place. Square, sawtooth, and triangle are pure math — each is ~3 lines. The existing oscillator uses a phase accumulator (0.0–1.0 range), which is the right representation for all four waveforms: ```go // Waveform enum addition to synth package type Waveform int const ( WaveformSine Waveform = iota WaveformSquare WaveformSawtooth WaveformTriangle ) // Per-sample generation (replaces math.Sin call in Advance()) func sample(phase float64, w Waveform) float64 { switch w { case WaveformSquare: if phase < 0.5 { return 1.0 } return -1.0 case WaveformSawtooth: return 2*phase - 1.0 case WaveformTriangle: if phase < 0.5 { return 4*phase - 1.0 } return 3.0 - 4*phase default: // WaveformSine return math.Sin(2 * math.Pi * phase) } } ``` The `Oscillator` struct gains a `Waveform` field; `Advance()` dispatches to `sample()`. Harmonics still work the same way — each harmonic's phase is `phase * ratio`, which maps correctly for all waveform types. --- ## Installation Delta ```bash # Add only this new dependency go get github.com/BurntSushi/toml@v1.6.0 ``` No changes to build flags. `CGO_ENABLED=1` still required for go-lame. --- ## Integration Points ### Where Config Feeds Existing Code The TOML config needs to override two existing data structures: 1. **`synth.ClassFreqConfigs`** (map in `synth/config.go`) — user can override `BaseHz` and add a `Waveform` field per class 2. **`classify.DefaultRules`** (slice in `classify/rules.go`) — user can prepend custom rules before the defaults The config loader should apply overrides at startup before any other initialization. The cleanest integration is: ``` cmd/netsynth/main.go -> config.Load(path) // returns *AppConfig -> classify.MergeRules(cfg) // prepend user rules to DefaultRules -> synth.ApplyOverrides(cfg) // patch ClassFreqConfigs entries ``` Both `classify.DefaultRules` and `synth.ClassFreqConfigs` are currently package-level vars — they can be replaced or cloned at startup without changing the downstream pipeline. ### TOML Struct Shape The config schema maps naturally to the existing types: ```toml # netsynth.toml [[rules]] protocol = "tcp" dst_port = 8443 class = "my-https-alt" [sounds.my-https-alt] frequency = 195.0 waveform = "square" [sounds.ICMP] frequency = 80.0 # override built-in waveform = "triangle" ``` ```go type AppConfig struct { Rules []RuleConfig `toml:"rules"` Sounds map[string]SoundConfig `toml:"sounds"` } type RuleConfig struct { Protocol string `toml:"protocol"` DstPort uint16 `toml:"dst_port"` Class string `toml:"class"` } type SoundConfig struct { Frequency float64 `toml:"frequency"` Waveform string `toml:"waveform"` // "sine"|"square"|"sawtooth"|"triangle" } ``` Use `toml.Decode()` and check `meta.Undecoded()` to warn on unknown keys. --- ## What NOT to Add | Avoid | Why | What to Do Instead | |-------|-----|-------------------| | `adrg/xdg` or any XDG library | `os.UserConfigDir()` in stdlib already handles `$XDG_CONFIG_HOME` on Linux — confirmed | Use `os.UserConfigDir()` directly | | `pelletier/go-toml v2` | No advantage over BurntSushi for a single-file startup read; `MetaData.Undecoded()` in BurntSushi is more ergonomic for typo detection | `github.com/BurntSushi/toml` | | `spf13/viper` | Massive dependency (brings in 20+ transitive deps) for a use case that is one TOML file — Viper adds remote config, env var binding, hot reload, none of which are needed | `BurntSushi/toml` + manual flag override | | Any waveform/audio library | Square/sawtooth/triangle are 3 lines of math each; no library adds value | Extend `synth.Oscillator` in place | | `gopkg.in/yaml.v3` or JSON config | TOML is explicitly specified for this milestone and is the right format for user-editable config files (comments supported, less noisy than JSON) | TOML only | --- ## Version Compatibility | Package | Version | Compatible With | Notes | |---------|---------|-----------------|-------| | `BurntSushi/toml` | v1.6.0 | Go 1.18+ | No issues with Go 1.24 | | `os.UserConfigDir()` | stdlib | Go 1.13+ | Returns `$XDG_CONFIG_HOME` or `$HOME/.config` on Linux | --- ## Confidence Assessment | Area | Confidence | Source | |------|------------|--------| | BurntSushi/toml v1.6.0 version | HIGH | pkg.go.dev confirmed, GitHub releases confirmed | | `os.UserConfigDir()` XDG behavior | HIGH | Official Go stdlib docs at pkg.go.dev/os | | Waveform math (no library needed) | HIGH | Trivial math, Dylan Meeus Go audio blog confirms the same approach | | go-toml v2.3.0 version | HIGH | pkg.go.dev confirmed | | Recommendation of BurntSushi over go-toml v2 | MEDIUM | Based on API ergonomics for the specific `Undecoded()` use case; both would work | --- ## Sources - `pkg.go.dev/github.com/BurntSushi/toml` — v1.6.0 confirmed, December 18, 2025 - `github.com/BurntSushi/toml/releases` — v1.6.0 release notes, TOML 1.1 enabled by default - `pkg.go.dev/github.com/pelletier/go-toml/v2` — v2.3.0 confirmed, March 24, 2026 - `pkg.go.dev/os#UserConfigDir` — XDG_CONFIG_HOME behavior on Linux confirmed via official Go docs - `dylanmeeus.github.io/posts/audio-from-scratch-pt8/` — Go waveform synthesis from scratch, confirms no library needed - `github.com/golang/go/issues/76320` — UserConfigDir XDG_CONFIG_HOME discussion (Nov 2025), confirms existing stdlib support on Linux --- *Stack research for: NetSynth v1.1 — Custom Sound Mappings milestone* *Researched: 2026-03-26*