Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

84 líneas
4.0KB

  1. fix: 13 bugs from systematic codebase review (fix25)
  2. === SIGNAL PATH (fixes audible/measurable on HW) ===
  3. [CRITICAL] stereo: fix 38kHz subcarrier 1-sample phase offset
  4. Encode() called Sample() before capturing Phase(), so the 38kHz
  5. subcarrier used phase_{n+1} while pilot used phase_n — a constant
  6. 60° phase error at 228kHz, degrading stereo separation to ~50%.
  7. Now captures phase BEFORE tick; stores lastPhase for coherent
  8. RDS carrier derivation.
  9. [HIGH] rds: phase-lock 57kHz carrier to pilot via StereoEncoder
  10. RDS encoder had its own free-running 57kHz oscillator instead of
  11. deriving from 3×pilot. Two independent float64 oscillators drift
  12. apart over hours. Added NextSampleWithCarrier(carrier float64),
  13. generator now passes stereoEncoder.RDSCarrier() (sin(3·pilotPhase))
  14. so all subcarriers share one phase reference. NextSample() remains
  15. as backward-compat fallback with internal carrier.
  16. [MEDIUM] dsp/fmmod: fix phase wrapping for negative phase
  17. Wrap condition only triggered on phase > pi. Negative phase from
  18. negative-biased composite could grow unbounded, losing float64
  19. precision after extended runtime. Now wraps on |phase| > pi.
  20. [MEDIUM] dsp/oscillator: phase wrapping handles both directions
  21. Same pattern as fmmod — only wrapped positive overflow. Now wraps
  22. on phase >= 1 OR phase < 0 for defensive robustness.
  23. === DAUERLAUF / SBC STABILITY ===
  24. [HIGH] offline/generator: eliminate per-chunk allocation (912KB @ 2.28MHz)
  25. GenerateFrame() allocated a new []IQSample every call. At Pluto
  26. rate (2.28MHz, 50ms chunks) that's 114k*8=912KB * 20/sec = 18MB/s
  27. garbage, causing GC pauses and hardware underruns on Raspi.
  28. Now pre-allocates frameBuf, reuses across calls. Grow-only policy,
  29. never shrinks. Safe because driver.Write() is blocking.
  30. [MEDIUM] output/file: batch-write entire frame in one syscall
  31. Was writing 8 bytes per sample = 114k syscalls per chunk on Pluto.
  32. Now serializes into a reusable byte buffer, single file.Write().
  33. Orders of magnitude faster on Raspi SD-card I/O.
  34. [MEDIUM] app/engine: add error backoff in TX loop
  35. run() tight-looped at 100% CPU on persistent driver errors
  36. (hardware disconnect, USB reset). Now backs off by chunkDuration
  37. per error using select with ctx.Done() for clean cancellation.
  38. [MEDIUM] app/engine: replace time.Sleep shutdown with sync.WaitGroup
  39. Stop() used time.Sleep(2*chunkDuration) hoping run() would exit.
  40. Race condition if hardware Write() stalls. Now uses wg.Wait() for
  41. deterministic goroutine join before Flush/Stop.
  42. === CORRECTNESS / HYGIENE ===
  43. [LOW] rds/normalize: filter to ASCII, prevent mid-rune truncation
  44. normalizePS truncated at 8 bytes, not 8 characters. UTF-8 input
  45. (e.g. umlauts) could split mid-rune producing corrupt RDS bitstream.
  46. Now replaces non-ASCII with space before truncation.
  47. [LOW] offline/generator: increment frame sequence counter
  48. Sequence was hardcoded to 1 on every frame, useless for debugging.
  49. Now increments per GenerateFrame() call.
  50. [LOW] control: document that config PATCH doesn't reach running engine
  51. Added TODO noting that POST /config updates server's copy only;
  52. running Engine/Generator holds its own snapshot.
  53. [COSMETIC] dsp/preemphasis: remove dead fields y1, a1
  54. PreEmphasis.y1 and .a1 were never read in Process(). Removed from
  55. struct, constructor, and Reset(). Fixed misleading doc comment on
  56. PreEmphasizedSource (claims audio-rate, actually composite-rate).
  57. Files changed:
  58. internal/stereo/encoder.go - phase coherence fix
  59. internal/rds/encoder.go - pilot-locked carrier API
  60. internal/rds/normalize.go - ASCII safety
  61. internal/offline/generator.go - buffer reuse + sequence + doc
  62. internal/dsp/fmmod.go - bidirectional phase wrap
  63. internal/dsp/oscillator.go - bidirectional phase wrap
  64. internal/dsp/preemphasis.go - dead field removal
  65. internal/output/file.go - batch write
  66. internal/app/engine.go - WaitGroup + backoff
  67. internal/control/control.go - TODO config propagation