- PanGains uses cos/sin constant-power pan law (D-11) - StereoFramesToInt16Bytes converts stereo frames to interleaved LE int16 bytes - clamp prevents int16 overflow for values outside [-1.0, 1.0] - All 7 mixer tests pass (pan law, byte conversion, clamping)
41 lines
1.2 KiB
Go
41 lines
1.2 KiB
Go
package synth
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math"
|
|
)
|
|
|
|
// PanGains returns left and right channel gains for a pan position p in [-1, 1].
|
|
// Uses constant-power (equal-power) pan law: cos/sin mapping.
|
|
// Per D-11: bass frequencies center, mid spread L/R, higher frequencies wider.
|
|
// At p=-1.0: gainL=1.0, gainR=0.0; at p=0.0: gainL=gainR=sqrt(2)/2; at p=1.0: gainL=0.0, gainR=1.0.
|
|
func PanGains(p float64) (gainL, gainR float64) {
|
|
angle := (p + 1.0) / 2.0 * math.Pi / 2.0
|
|
return math.Cos(angle), math.Sin(angle)
|
|
}
|
|
|
|
// StereoFramesToInt16Bytes converts [][2]float64 stereo frames to interleaved
|
|
// little-endian int16 bytes suitable for go-lame's Write method.
|
|
// Clamps values to [-1.0, 1.0] before conversion.
|
|
// Output format: [L0_lo, L0_hi, R0_lo, R0_hi, L1_lo, L1_hi, R1_lo, R1_hi, ...]
|
|
func StereoFramesToInt16Bytes(frames [][2]float64) []byte {
|
|
buf := make([]byte, len(frames)*4) // 2 channels * 2 bytes per sample
|
|
for i, frame := range frames {
|
|
l := clamp(frame[0])
|
|
r := clamp(frame[1])
|
|
binary.LittleEndian.PutUint16(buf[i*4:], uint16(int16(l*32767)))
|
|
binary.LittleEndian.PutUint16(buf[i*4+2:], uint16(int16(r*32767)))
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func clamp(v float64) float64 {
|
|
if v > 1.0 {
|
|
return 1.0
|
|
}
|
|
if v < -1.0 {
|
|
return -1.0
|
|
}
|
|
return v
|
|
}
|