Confirmed on a real SunSDR: the stream decodes and the test recording plays back clean. So it can do the job a virtual audio cable was doing — this wires it to the QSO recorder, which already accepts a pushed source (the Icom network audio uses the same door). The conversion lives here rather than in internal/cat: the radio's job is to hand over what it sent, not to know that the recorder works in 16 kHz mono. Three samples are AVERAGED rather than two of them dropped — decimating by picking every third folds everything above 8 kHz back into the voice band, and on a receiver that is hiss, which a QSO recording has plenty of already. Off by default, and applied the moment it is switched: it replaces a sound card the operator has already wired up, and an option that needs a restart to take effect reads as an option that does not work.
48 lines
1.5 KiB
Go
48 lines
1.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
"hamlog/internal/audio"
|
|
)
|
|
|
|
// The stream is 48 kHz and the recorder works at 16 — three to one. A
|
|
// recording that keeps every sample plays back three times too fast, which is
|
|
// the fault that gets blamed on the decoding rather than on the rate.
|
|
func TestTheStreamIsResampledToTheRecorderRate(t *testing.T) {
|
|
const in = 48000
|
|
samples := make([]float32, in/10) // a tenth of a second
|
|
pcm := tciToRecorderPCM(in, samples)
|
|
want := (audio.RecorderSampleRate / 10) * 2 // 16-bit
|
|
if len(pcm) != want {
|
|
t.Fatalf("a tenth of a second produced %d bytes, want %d", len(pcm), want)
|
|
}
|
|
}
|
|
|
|
// Full scale must arrive as full scale: a conversion that quietly halves the
|
|
// level turns a recording into evidence of a fault that is not there.
|
|
func TestFullScaleSurvivesTheConversion(t *testing.T) {
|
|
samples := make([]float32, 12)
|
|
for i := range samples {
|
|
samples[i] = 1
|
|
}
|
|
pcm := tciToRecorderPCM(48000, samples)
|
|
if len(pcm) < 2 {
|
|
t.Fatal("no samples came out")
|
|
}
|
|
v := int16(binary.LittleEndian.Uint16(pcm[:2]))
|
|
if v < 32000 {
|
|
t.Fatalf("full scale came out at %d", v)
|
|
}
|
|
}
|
|
|
|
// A rate the recorder already works in is passed through rather than mangled by
|
|
// a division that would round to nothing.
|
|
func TestAStreamAtTheRecorderRateIsNotDecimated(t *testing.T) {
|
|
samples := make([]float32, 160)
|
|
if got, want := len(tciToRecorderPCM(audio.RecorderSampleRate, samples)), 160*2; got != want {
|
|
t.Fatalf("%d bytes, want %d", got, want)
|
|
}
|
|
}
|