Skip to content

Quindar tone option for MOX/PTT trigger sources #2262

@ten9876

Description

@ten9876

Background

Add classic Apollo-era "Quindar" tones (2525 Hz intro, 2475 Hz outro, ~250 ms each) to MOX/PTT engagement on phone modes — fun cosmetic feature for SSB ops who want NASA mission-control aesthetic.

Two flavors selectable per-user:

Style Intro Outro Notes
Tone (default) 2525 Hz sine, 250 ms 2475 Hz sine, 250 ms Apollo Quindar
Morse K at 45 WPM (≈ 240 ms) BK at 45 WPM (≈ 560 ms) Ham-radio convention: K = "go ahead", BK = "back to you"

Morse style uses the active CW pitch (or a configurable Quindar pitch — TBD) as the carrier, generated with the same envelope as the sine version. Outro BK is intentionally longer than the 250 ms intro K because the signoff convention warrants the extra emphasis.

Ships disabled by default; opt-in per-user.

Trigger surface — single coordinator

Add TransmitModel::requestPttOn(source) / requestPttOff(source) as the entry point for all "user wants to key/unkey" intents (where source ∈ {Mox, TciHardware, TciDax, …}). Existing setMox() / setTransmit() stay as low-level emitters; the coordinator runs the tone state machine before/after them.

Caller Today After
src/gui/TxApplet.cpp:294 — MOX button setMox(on) requestPttOn/Off(Mox)
src/core/TciServer.cpp:590 — radio-direct (e.g. Elgato footswitch via streamcontroller plugin) setTransmit(true) requestPttOn(TciHardware)
src/core/TciServer.cpp:587 — DAX path (WSJT-X etc.) startTxChrono() unchanged (digital, not phone)

State machine

Idle ──pttOn──▶ Engaging  (intro tone, 250 ms)
                   │
                   ▼ tone done
                Live  (mic audio)
                   │
              pttOff request
                   ▼
              Disengaging  (outro tone, 250 ms; xmit 1 still set)
                   │
                   ▼ tone done → emit xmit 0
                Idle

Engaging/Disengaging drive an atomic QuindarPhase read by AudioEngine on the audio thread.

DSP — ClientQuindarTone

New stage in src/core/ClientQuindarTone.{h,cpp}, lock-free atomic-driven (mirrors ClientGate / ClientTube shape):

  • Tone style: 2525 Hz (intro) / 2475 Hz (outro) sine, 5 ms cos² ramp envelope to avoid clicks.
  • Morse style: pre-rendered K (intro) / BK (outro) at 45 WPM, single carrier frequency (default 750 Hz, configurable). Element timing: dot = 1200/45 ≈ 26.67 ms, dash = 80 ms, intra-char gap = 26.67 ms, inter-letter gap = 80 ms. Each element shaped with the same 5 ms cos² envelope.
    • K = dah-dit-dah → 9 units → ~240 ms total
    • BK = dah-dit-dit-dit + inter-letter + dah-dit-dah → 21 units → ~560 ms total
  • Replaces mic samples wholesale during Engaging/Disengaging (simpler than ducking).
  • Inserted after PooDoo chain in the TX path so the tone/morse is pure (no comp/EQ coloration).
  • Injects directly into the DAX TX byte stream AudioEngine already emits.

MOX-off defer

requestPttOff(source):

  1. If Quindar enabled + phone mode + currently Live → audio->startQuindarOutro() + QTimer::singleShot(outroDurationMs, …, sendPttOff).
  2. Else → sendPttOff() immediately (current behavior).

outroDurationMs is style-dependent: 250 ms for Tone, ~560 ms for Morse BK. Asymmetric outro durations are fine — the timer length is computed at request time from the active style.

The MOX button visibly stays lit during the outro because m_mox doesn't flip until the timer fires — the "stickiness" is the outro tone duration, which is the correct UX (user sees what's happening).

Phone-mode gate

isPhoneMode() = mode ∈ {USB, LSB, AM, FM, NFM, DIGU, DIGL} checked at request time on the active slice. CW / FT8 / DIGITAL → bypass Quindar entirely.

Settings (AppSettings keys)

  • QuindarEnabled (default False)
  • QuindarStyle (default Tone; values Tone | Morse)
  • QuindarLevelDb (default -6)
  • Tone style:
    • QuindarIntroFreqHz (default 2525)
    • QuindarOutroFreqHz (default 2475)
    • QuindarDurationMs (default 250, range 100–500)
  • Morse style:
    • QuindarMorseWpm (default 45, range 20–60)
    • QuindarMorsePitchHz (default 750, range 400–1200)

UI

Right-click MOX → "Quindar..." popover with:

  • Enable toggle
  • Style segmented control: [ Tone ] [ Morse ]
  • Level slider (-20 … 0 dB)
  • Style-conditional fields:
    • Tone: intro freq, outro freq, duration spinner
    • Morse: WPM spinner (20–60), pitch slider (400–1200 Hz)

No menu-bar entry — keep it tucked under the right-click.

Files touched

File Change
src/core/ClientQuindarTone.{h,cpp} NEW — DSP stage + envelope
src/core/AudioEngine.{h,cpp} Instantiate, plumb into TX-after-PooDoo path, startQuindarIntro/Outro()
src/models/TransmitModel.{h,cpp} requestPttOn/Off, defer-xmit-0 timer, phase atomic
src/gui/TxApplet.cpp MOX button → requestPttOn/Off; right-click popover
src/core/TciServer.cpp Hardware-style trx:requestPttOn/Off(TciHardware)
tests/client_quindar_test.cpp NEW — frequency, duration, envelope, phase transitions

Edge cases

  • Rapid MOX toggle within outro window — coalesce: re-engaging during outro skips intro and resumes Live (otherwise the user feels a dead zone equal to outro + intro = up to ~800 ms in Morse style).
  • Mode change mid-transmission — keep current phase (don't truncate or restart).
  • Other client keys our radio (multi-Flex) — observed-only; doesn't trigger our tone (m_transmitting rising via radio-side status ≠ our PTT request).
  • Hardware PTT via radio-side serial PTT — out of scope. Document as MOX-only.
  • Disconnect mid-outro — drop pending xmit-0 timer; radio handles unkey via session teardown.

Open UX question

The "MOX button stays lit ~250 ms after release" feels correct, but worth validating on actual footswitch hardware — the streamcontroller plugin reflects MOX state back to the Stream Deck button, so there's a visible delay on the physical button too. If that delay feels off, alternative is to flip MOX visually immediately and only defer the actual xmit 0 (slight UI dishonesty but maybe worth it).

Risks

  • Niche feature — many ham contest ops will find Quindar annoying or unprofessional. Ships disabled by default.
  • Doesn't compose with hardware serial PTT (radio-side keying happens before AetherSDR sees it). Footswitch via TCI is fine; serial PTT footswitch is not. Document explicitly.

73, Jeremy KK7GWY & Claude (AI dev partner)

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceNew FeatureNew feature requestaudioAudio engine and streamingenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priority

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions