feat: added RIT and XIT for Icom

This commit is contained in:
2026-07-05 13:21:11 +02:00
parent e112de5967
commit 8cf53a0aa2
10 changed files with 430 additions and 64 deletions
+26 -4
View File
@@ -60,6 +60,7 @@ type Decoder struct {
state bool // true = mark (key down)
stateHops int
dotHops float64 // adaptive dot length, in hops
markCount int // marks seen since lock (fast WPM adaptation while small)
elem []byte // current "." / "-" run for the in-progress character
charEmitted bool
wordEmitted bool
@@ -145,6 +146,7 @@ func (d *Decoder) Reset() {
d.state = false
d.stateHops = 0
d.dotHops = 15
d.markCount = 0
d.elem = d.elem[:0]
d.charEmitted, d.wordEmitted = false, false
}
@@ -213,10 +215,11 @@ func (d *Decoder) analyze() {
// Tiered acquisition: a clearly strong tone locks on the FIRST hop (so we
// don't eat the first element of a strong signal), a marginal/weak tone
// locks after a couple of stable hops (so we don't lock onto pure noise).
if snr > d.strongSNR || (d.candHops >= 3 && snr > d.acqSNR) {
if snr > d.strongSNR || (d.candHops >= 2 && snr > d.acqSNR) {
d.lockIdx = maxIdx
d.peak, d.floor = maxMag, d.noise // seed the envelope to this bin
d.quietHops = 0
d.markCount = 0 // relearn WPM fast for this new signal
}
}
if d.lockIdx >= 0 {
@@ -267,6 +270,17 @@ func (d *Decoder) step() {
} else {
d.quietHops++
if d.quietHops > d.relockHops {
// End of the over: flush any pending character and drop a word
// space so the next transmission starts a fresh word (the word-gap
// timer above can't fire once the lock is gone).
if len(d.elem) > 0 && !d.charEmitted {
d.flushChar()
d.charEmitted = true
}
if !d.wordEmitted && d.onChar != nil {
d.onChar(" ")
d.wordEmitted = true
}
d.lockIdx, d.candIdx, d.candHops = -1, -1, 0
}
}
@@ -311,10 +325,18 @@ func (d *Decoder) endMark(hops int) {
}
// adaptDot nudges the dot-length estimate toward an observation (EMA, clamped
// to ~5100 WPM).
// to ~5100 WPM). The first marks of a new signal adapt FAST so the WPM (and
// therefore the character/word gap thresholds) converge within the first
// character or two — otherwise a wrong seed mis-times early gaps and runs
// characters/words together.
func (d *Decoder) adaptDot(obs float64) {
d.dotHops = d.dotHops*0.8 + obs*0.2 // slower: a few odd marks can't yank it
if d.dotHops < 5 { // 5 hops ≈ 60 WPM ceiling — never 100
alpha := 0.2
if d.markCount < 6 {
alpha = 0.5 // fast convergence on the opening marks
}
d.markCount++
d.dotHops = d.dotHops*(1-alpha) + obs*alpha
if d.dotHops < 5 { // 5 hops ≈ 60 WPM ceiling — never 100
d.dotHops = 5
}
if d.dotHops > 55 {