chore(tci): mark the transmit passes and count every frame type
The first transmit test came back with a log that said nothing, which is the one answer that cannot be read: either no transmit frames arrived, or they arrived and went unlogged. So each pass is now bounded by a line of its own, and every stream type is counted without limit. A pass that reports 'receive audio: 240, and nothing else' is a result — it says the radio sends no chrono unless something more is asked of it — where a log with no transmit lines was merely a silence. The forty-frame logging budget is also handed back to the transmit types on each pass, since it was always spent on receive audio long before anyone got round to keying. The start line says whether the receive stream is even open, because a radio with nothing streaming has no reason to send chrono, and that is the likeliest reason the first attempt saw nothing.
This commit is contained in:
+79
-12
@@ -33,6 +33,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -60,10 +61,10 @@ const tciAudioProbeMax = 40
|
||||
|
||||
// TCIAudioStatus is what the panel polls while testing the stream.
|
||||
type TCIAudioStatus struct {
|
||||
Running bool `json:"running"`
|
||||
SampleRate int `json:"sample_rate"`
|
||||
Frames int64 `json:"frames"` // binary frames accepted
|
||||
Samples int64 `json:"samples"` // audio samples decoded
|
||||
Running bool `json:"running"`
|
||||
SampleRate int `json:"sample_rate"`
|
||||
Frames int64 `json:"frames"` // binary frames accepted
|
||||
Samples int64 `json:"samples"` // audio samples decoded
|
||||
// PeakDB is the loudest sample of the last second, in dBFS: the one number
|
||||
// that says "audio is really arriving" rather than "a socket is open".
|
||||
PeakDB float64 `json:"peak_db"`
|
||||
@@ -73,15 +74,20 @@ type TCIAudioStatus struct {
|
||||
// tciAudio is the receive-side state, kept on the backend so it lives exactly
|
||||
// as long as the connection does.
|
||||
type tciAudio struct {
|
||||
mu sync.Mutex
|
||||
want bool // the host asked for audio
|
||||
rx int // which receiver
|
||||
rate int
|
||||
frames int64
|
||||
samples int64
|
||||
peak float64
|
||||
peakAt time.Time
|
||||
mu sync.Mutex
|
||||
want bool // the host asked for audio
|
||||
rx int // which receiver
|
||||
rate int
|
||||
frames int64
|
||||
samples int64
|
||||
peak float64
|
||||
peakAt time.Time
|
||||
probeByType map[int]int
|
||||
// countByType counts EVERY frame per stream type, capped by nothing.
|
||||
// The probe above stops logging after forty frames of a type; these keep
|
||||
// counting, so a transmission that produced no transmit frames at all can
|
||||
// be reported as a fact rather than inferred from an absence of lines.
|
||||
countByType map[int]int64
|
||||
lastErr string
|
||||
// widthLogged keeps the one-line note about the sample width to once a
|
||||
// session — it is a fact about the radio, not an event.
|
||||
@@ -90,6 +96,9 @@ type tciAudio struct {
|
||||
// audio_stream_channels). Its own declaration, and it arrives before the
|
||||
// first frame — the frame arithmetic below stays as the check on it rather
|
||||
// than as the only source.
|
||||
// txMark is the per-type frame count when transmission began, so the census
|
||||
// at the end reports the pass rather than the whole session.
|
||||
txMark map[int]int64
|
||||
declaredType string
|
||||
declaredChans int
|
||||
|
||||
@@ -192,6 +201,10 @@ func (t *TCI) handleBinary(data []byte) {
|
||||
if t.audio.probeByType == nil {
|
||||
t.audio.probeByType = map[int]int{}
|
||||
}
|
||||
if t.audio.countByType == nil {
|
||||
t.audio.countByType = map[int]int64{}
|
||||
}
|
||||
t.audio.countByType[stype]++
|
||||
probe := t.audio.probeByType[stype]
|
||||
if probe < tciAudioProbeMax {
|
||||
t.audio.probeByType[stype]++
|
||||
@@ -333,3 +346,57 @@ func (t *TCI) resumeAudio() {
|
||||
// ignore the message type entirely and split every frame on ';', which would
|
||||
// have fed audio bytes to the command parser the moment a stream was opened.
|
||||
func wsMessageIsBinary(mt int) bool { return mt == websocket.BinaryMessage }
|
||||
|
||||
// noteTXTransition reports what the stream did across a transmission.
|
||||
//
|
||||
// The voice keyer needs two numbers the documentation does not give: the size
|
||||
// and the cadence of the frames the radio expects while transmitting. They can
|
||||
// only be read off a real transmission — and the first attempt came back with a
|
||||
// log that said nothing at all, which is ambiguous: either no transmit frames
|
||||
// arrived, or they arrived and went unlogged.
|
||||
//
|
||||
// So the boundaries are marked and every stream type is counted. A pass that
|
||||
// produces "type 1: 240, and nothing else" is a RESULT — it says the radio
|
||||
// sends no chrono unless something more is asked of it — where a log with no
|
||||
// transmit lines in it was merely a silence.
|
||||
func (t *TCI) noteTXTransition(on bool) {
|
||||
t.audio.mu.Lock()
|
||||
if t.audio.countByType == nil {
|
||||
t.audio.countByType = map[int]int64{}
|
||||
}
|
||||
if on {
|
||||
// Let the transmit types speak again on every pass: forty frames is a
|
||||
// budget spent long before the operator gets round to keying.
|
||||
if t.audio.probeByType != nil {
|
||||
delete(t.audio.probeByType, tciStreamTXAudio)
|
||||
delete(t.audio.probeByType, tciStreamTXChrono)
|
||||
}
|
||||
t.audio.txMark = map[int]int64{}
|
||||
for k, v := range t.audio.countByType {
|
||||
t.audio.txMark[k] = v
|
||||
}
|
||||
streaming := t.audio.want
|
||||
t.audio.mu.Unlock()
|
||||
debugLog.Printf("TCI: TRANSMIT started — watching for transmit-audio (type %d) and chrono (type %d) frames; receive stream is %s",
|
||||
tciStreamTXAudio, tciStreamTXChrono, map[bool]string{true: "open", false: "CLOSED (tick the TCI recording option, or the radio has no reason to stream)"}[streaming])
|
||||
return
|
||||
}
|
||||
names := map[int]string{
|
||||
tciStreamIQ: "IQ",
|
||||
tciStreamRXAudio: "receive audio",
|
||||
tciStreamTXAudio: "transmit audio",
|
||||
tciStreamTXChrono: "transmit chrono",
|
||||
}
|
||||
var parts []string
|
||||
for _, k := range []int{tciStreamIQ, tciStreamRXAudio, tciStreamTXAudio, tciStreamTXChrono} {
|
||||
if n := t.audio.countByType[k] - t.audio.txMark[k]; n > 0 {
|
||||
parts = append(parts, fmt.Sprintf("%s (type %d): %d", names[k], k, n))
|
||||
}
|
||||
}
|
||||
t.audio.mu.Unlock()
|
||||
if len(parts) == 0 {
|
||||
debugLog.Printf("TCI: TRANSMIT ended — NO binary frames of any type arrived during it")
|
||||
return
|
||||
}
|
||||
debugLog.Printf("TCI: TRANSMIT ended — frames during the pass: %s", strings.Join(parts, ", "))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user