|
- package dsp
-
- import (
- "github.com/jan/fm-rds-tx/internal/output"
- )
-
- // ResampleIQ resamples a CompositeFrame from its native sample rate to
- // the target device rate using linear interpolation. Returns a new frame.
- // If rates are equal (within 0.5 Hz), returns the original frame unchanged.
- func ResampleIQ(frame *output.CompositeFrame, targetRateHz float64) *output.CompositeFrame {
- if frame == nil || len(frame.Samples) == 0 {
- return frame
- }
-
- srcRate := frame.SampleRateHz
- if srcRate <= 0 || targetRateHz <= 0 {
- return frame
- }
-
- // No resampling needed if rates match
- ratio := targetRateHz / srcRate
- if ratio > 0.999 && ratio < 1.001 {
- return frame
- }
-
- srcLen := len(frame.Samples)
- dstLen := int(float64(srcLen) * ratio)
- if dstLen <= 0 {
- return frame
- }
-
- dst := make([]output.IQSample, dstLen)
- step := 1.0 / ratio // position step in source samples per output sample
-
- pos := 0.0
- for i := 0; i < dstLen; i++ {
- idx := int(pos)
- frac := float32(pos - float64(idx))
-
- if idx+1 < srcLen {
- s0 := frame.Samples[idx]
- s1 := frame.Samples[idx+1]
- dst[i] = output.IQSample{
- I: s0.I*(1-frac) + s1.I*frac,
- Q: s0.Q*(1-frac) + s1.Q*frac,
- }
- } else if idx < srcLen {
- dst[i] = frame.Samples[idx]
- }
- pos += step
- }
-
- return &output.CompositeFrame{
- Samples: dst,
- SampleRateHz: targetRateHz,
- Timestamp: frame.Timestamp,
- GeneratedAt: frame.GeneratedAt,
- Sequence: frame.Sequence,
- }
- }
|