via:"RunSynthesis calls bank.RenderWindow for each snapshot"
pattern:"bank\\.RenderWindow"
- from:"encode/mp3.go"
to:"synth/mixer.go"
via:"Uses StereoFramesToInt16Bytes for PCM conversion"
pattern:"StereoFramesToInt16Bytes"
- from:"cmd/netsynth/main.go"
to:"encode/mp3.go"
via:"Will wire RunSynthesis in Phase 3; -o flag defined now"
pattern:"outputPath"
---
<objective>
Build the MP3 encoder package and wire the -o output flag into the CLI. This completes OUT-01 (output path flag), OUT-02 (valid MP3 encoding), and OUT-03 (zero-packet guard). An integration test synthesizes from synthetic snapshots and validates the MP3 with ffprobe.
Purpose: The encoder is the final stage of the audio pipeline. After this plan, the complete synthesis chain (WindowSnapshot -> OscillatorBank -> stereo PCM -> MP3 file) is validated end-to-end against synthetic data.
Output: `encode/mp3.go`, `encode/mp3_test.go`, updated `cmd/netsynth/main.go` with -o flag.
- TestMP3Valid: Create 3 synthetic WindowSnapshots with varied traffic (ICMP=50, DNS=30 in snap1; HTTPS=200 in snap2; SSH=10, HTTP=80 in snap3). Run RunSynthesis into a temp file. Assert: file exists, file size > 0, `ffprobe -v error -show_entries format=format_name,duration,nb_streams -of csv=p=0 <file>` outputs "mp3" in format_name, duration > 0, nb_streams=1.
- TestZeroPacketError: Pass empty slice of snapshots (or snapshots where all TotalPackets=0). Assert: returns non-nil error containing "no packets", output file does NOT exist on disk.
- TestEncodeMP3DirectBytes: Call EncodeMP3 with known [][2]float64 frames (e.g., 44100 frames of 440Hz sine). Assert: output file > 0 bytes, ffprobe validates it as MP3.
</behavior>
<action>
**encode/mp3.go** — MP3 encoding and synthesis orchestrator:
```go
packageencode
import(
"fmt"
"os"
lame"github.com/sjzar/go-lame"
"github.com/netsynth/netsynth/classify"
"github.com/netsynth/netsynth/synth"
)
// EncodeMP3 writes stereo PCM frames to an MP3 file at the given path.
// sampleRate=44100, bitrate=128 kbps, stereo, quality=5. Per D-13, D-14.
// CRITICAL: InitParams() must be called after all Set* calls (Pitfall 1).
-`RunSynthesis` takes a `[]classify.WindowSnapshot` slice (not a channel). The caller (main.go in Phase 3) collects from the channel, then passes the slice. This enables the zero-packet check before file creation.
-`EncodeMP3` is a separate function so it can be tested independently with raw frames.
- PCM bytes are written in a single `Write` call (batch output, not streaming — per project constraints).
Write tests FIRST, using `os.CreateTemp` for output files and `exec.Command("ffprobe", ...)` for validation. Clean up temp files in test teardown.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && CGO_ENABLED=1 go test ./encode/... -v -count=1</automated>
- encode/mp3_test.go uses `ffprobe` to validate MP3 output
-`CGO_ENABLED=1 go test ./encode/...` exits 0
</acceptance_criteria>
<done>EncodeMP3 produces a valid MP3 file validated by ffprobe. RunSynthesis renders synthetic snapshots through the full pipeline. Zero-packet input returns error without creating a file. All encode tests pass.</done>
- .planning/phases/02-audio-synthesis-engine/02-RESEARCH.md (output flag wiring example)
</read_first>
<action>
Add the `-o` / `--output` flag to main.go. Per D-15: defaults to `netsynth-<timestamp>.mp3` when omitted. The flag is defined now but NOT wired to the synthesis pipeline yet — that happens in Phase 3 when capture and synthesis are integrated.
1. Add a package-level var:
```go
var outputPath string
```
2. Add the flag registration in `main()` after the existing flags:
5. Add a snapshot slice accumulator alongside the existing snapshot consumption loop, and a TODO for Phase 3 wiring. Replace the current `for snap := range snapshots` block to also collect snapshots into a slice:
Do NOT import the encode package yet — Phase 3 handles that. The flag must be functional (parseable, shows in --help) even though the synthesis pipeline isn't wired.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go build -o /dev/null ./cmd/netsynth/ && go run ./cmd/netsynth/ --help 2>&1 | grep -q "\-o.*output" && echo "OK"</automated>
<done>The -o/--output flag is registered in Cobra, defaults to netsynth-<timestamp>.mp3, visible in --help. Snapshot slice accumulator stub is in place for Phase 3 wiring. Build succeeds.</done>