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, } }