//go:build windows package audio import ( "os" "github.com/braheezy/shine-mp3/pkg/mp3" ) // mp3Rate is the encode sample rate. The capture pipeline is 16 kHz, but the // Shine encoder emits broken "free-format" frames at MPEG-2 rates (16/22/24 // kHz) that most players reject. Encoding at an MPEG-1 rate (we upsample ×2 to // 32 kHz) produces standard, universally-playable MP3s. const mp3Rate = sampleRate * 2 // 32000 // writeMP3 encodes 16 kHz mono 16-bit PCM to a standard MP3 file using the // pure-Go Shine encoder (no CGO). Two quirks are worked around: // - 16 kHz (MPEG-2) yields broken free-format frames → upsample ×2 to 32 kHz. // - Shine's Write only encodes half the samples for MONO input (its loop // advances by samples_per_pass*2). Feeding STEREO interleaved data (the // encoder reads samples_per_pass*channels per pass) encodes everything, so // we duplicate mono → L=R stereo. func writeMP3(path string, pcm []byte) error { f, err := os.Create(path) if err != nil { return err } defer f.Close() mono32 := upsample2(bytesToInt16(pcm)) // 16 kHz → 32 kHz mono stereo := make([]int16, len(mono32)*2) // L=R interleaved for i, v := range mono32 { stereo[2*i], stereo[2*i+1] = v, v } enc := mp3.NewEncoder(mp3Rate, 2) return enc.Write(f, stereo) } // upsample2 doubles the sample rate with linear interpolation (16 kHz → 32 kHz). func upsample2(in []int16) []int16 { if len(in) == 0 { return in } out := make([]int16, len(in)*2) for i := range in { out[2*i] = in[i] if i+1 < len(in) { out[2*i+1] = int16((int32(in[i]) + int32(in[i+1])) / 2) } else { out[2*i+1] = in[i] } } return out }