feat(cw): show tone-shift markers on the waterfall spectrogram
When the tone-shift feature moves a tone into the model's 400-1200 Hz window, the waterfall now shows two visual markers so the operator can see what is happening: a green dashed line at the target (800 Hz) and an orange frequency label at the top-left showing the original pitch. The waterfall draws the RAW audio, not the shifted audio, so a 1500 Hz tone was always invisible regardless of the shift setting. The markers close the gap: the operator can now see that a tone was detected and where it was moved, even when the original pitch is outside the visible band. activeShiftHz is now a StateFlow exposed through ICwDecoder so the UI can observe it without polling.
This commit is contained in:
1 parent
50a644f417
commit
fa73328936
4 files changed
+98
-12
No files matched your search
@@ -106,6 +106,9 @@ class CwDeepDecoder(
|
||||
private val _estimatedPitch = MutableStateFlow<Float?>(null)
|
||||
override val estimatedPitch: StateFlow<Float?> = _estimatedPitch.asStateFlow()
|
||||
|
||||
private val _activeShiftHz = MutableStateFlow(0f)
|
||||
override val activeShiftHz: StateFlow<Float> = _activeShiftHz.asStateFlow()
|
||||
|
||||
private val _signalStrength = MutableStateFlow(0f)
|
||||
override val signalStrength: StateFlow<Float> = _signalStrength.asStateFlow()
|
||||
|
||||
@@ -130,9 +133,6 @@ class CwDeepDecoder(
|
||||
/** Held while inference runs so slow devices skip work instead of queuing it. */
|
||||
private val inferenceLock = Mutex()
|
||||
|
||||
/** Shift currently applied to incoming audio; 0 when the tone needs no move. */
|
||||
private var activeShiftHz = 0f
|
||||
|
||||
/** Decides what shift to apply from successive tone estimates. */
|
||||
private val shiftDecider = CwShiftDecider(SHIFT_HYSTERESIS_HZ)
|
||||
|
||||
@@ -304,7 +304,7 @@ class CwDeepDecoder(
|
||||
*
|
||||
* The detection scan is a bin-by-bin DFT, so it runs at most every
|
||||
* [DETECT_INTERVAL_MS] rather than on every ~100 ms capture chunk; the decision it
|
||||
* produces is cached in [activeShiftHz] and applied to the chunks in between. A
|
||||
* produces is cached in [_activeShiftHz] and applied to the chunks in between. A
|
||||
* tone already inside the window yields a zero shift, and then this returns the
|
||||
* caller's array untouched.
|
||||
*
|
||||
@@ -323,7 +323,7 @@ class CwDeepDecoder(
|
||||
if (enabled != previousEnabled) {
|
||||
Log.i(TAG, "toneShift: setting changed to $enabled, dropping buffered audio")
|
||||
dropBufferedAudio()
|
||||
activeShiftHz = 0f
|
||||
_activeShiftHz.value = 0f
|
||||
shiftDecider.reset()
|
||||
lastDetectAtMs = 0L
|
||||
detectionPool.clear()
|
||||
@@ -343,7 +343,7 @@ class CwDeepDecoder(
|
||||
|
||||
// Streaming keeps the Hilbert filter history and mixer phase across chunks;
|
||||
// shifting each chunk in isolation distorted the 62 samples at its edges.
|
||||
return streamingShifter.process(resampled, activeShiftHz, CwDeepSpectrogram.SAMPLE_RATE)
|
||||
return streamingShifter.process(resampled, _activeShiftHz.value, CwDeepSpectrogram.SAMPLE_RATE)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +368,7 @@ class CwDeepDecoder(
|
||||
private fun runDetection(sample: FloatArray) {
|
||||
val analysis = CwToneShifter.analyse(sample, CwDeepSpectrogram.SAMPLE_RATE)
|
||||
val decision = shiftDecider.accept(analysis)
|
||||
activeShiftHz = decision.shiftHz
|
||||
_activeShiftHz.value = decision.shiftHz
|
||||
|
||||
when (decision.outcome) {
|
||||
CwShiftDecider.Outcome.NO_TONE -> Log.d(
|
||||
@@ -493,7 +493,7 @@ class CwDeepDecoder(
|
||||
val absoluteBin = 32 + bestBin
|
||||
// Undo the shift before reporting: the spectrogram sees the moved tone, but
|
||||
// the readout must show the pitch the operator actually hears on the radio.
|
||||
_estimatedPitch.value = (absoluteBin * binHz - activeShiftHz).toFloat()
|
||||
_estimatedPitch.value = (absoluteBin * binHz - _activeShiftHz.value).toFloat()
|
||||
|
||||
val mean = total / count
|
||||
_signalStrength.value = ((bestValue - mean) / bestValue).coerceIn(0f, 1f)
|
||||
@@ -508,7 +508,7 @@ class CwDeepDecoder(
|
||||
_signalStrength.value = 0f
|
||||
_lastInferenceMs.value = 0
|
||||
// Re-detect from scratch: the operator may have retuned before resetting.
|
||||
activeShiftHz = 0f
|
||||
_activeShiftHz.value = 0f
|
||||
shiftDecider.reset()
|
||||
lastDetectAtMs = 0L
|
||||
detectionPool.clear()
|
||||
|
||||
Reference in new issue
Block a user