216 lines
8.1 KiB
Markdown
216 lines
8.1 KiB
Markdown
# 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*
|