8.1 KiB
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:
// 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:
// 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
# 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:
synth.ClassFreqConfigs(map insynth/config.go) — user can overrideBaseHzand add aWaveformfield per classclassify.DefaultRules(slice inclassify/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:
# 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"
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, 2025github.com/BurntSushi/toml/releases— v1.6.0 release notes, TOML 1.1 enabled by defaultpkg.go.dev/github.com/pelletier/go-toml/v2— v2.3.0 confirmed, March 24, 2026pkg.go.dev/os#UserConfigDir— XDG_CONFIG_HOME behavior on Linux confirmed via official Go docsdylanmeeus.github.io/posts/audio-from-scratch-pt8/— Go waveform synthesis from scratch, confirms no library neededgithub.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