Advertisement
Guest User

Untitled

a guest
May 9th, 2025
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.01 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. """
  3. Real-time FM audio demodulator for HackRF One.
  4.  
  5. * Centre frequency : 100.1 MHz (edit FREQ_HZ below if needed)
  6. * RF sample rate : 2 MS/s
  7. * Audio sample rate: 48 kS/s (pushed straight to your speakers)
  8.  
  9. Dependencies
  10. ------------
  11. pip install numpy scipy pyhackrf sounddevice
  12.  
  13. Tested with:
  14. * HackRF One firmware 2024.02.1
  15. * pyhackrf 0.6.7
  16. * NumPy 2.0, SciPy 1.13
  17. * sounddevice 0.4.6 (PortAudio backend)
  18.  
  19. Press Ctrl-C to quit.
  20. """
  21.  
  22. import numpy as np
  23. import scipy.signal
  24. import sounddevice as sd
  25. from math import gcd
  26. from hackrf import HackRf
  27.  
  28. # ------------------ editable constants ------------------ #
  29. FREQ_HZ = 100_100_000 # 100.1 MHz – change to your station
  30. RF_RATE = 2_000_000 # 2 MS/s I-Q
  31. AUDIO_RATE = 48_000 # 48 kS/s mono
  32. RF_GAIN_DB = 20 # front-end gain in dB
  33. BUF_SYMS = 262_144 # ≈131 ms at 2 MS/s
  34. DEEMPH_TC = 75e-6 # 75 µs FM de-emphasis
  35. # -------------------------------------------------------- #
  36.  
  37.  
  38. def fm_demodulate(iq: np.ndarray) -> np.ndarray:
  39. """Wide-band FM discriminator + de-emphasis + resample."""
  40. iq -= np.mean(iq) # DC-remove
  41. audio = np.diff(np.unwrap(np.angle(iq))) # phase diff
  42. audio = audio[::40] # decimate 2 MHz → 50 kHz
  43.  
  44. # 75 µs de-emphasis (simple IIR)
  45. alpha = np.exp(-1.0 / (AUDIO_RATE * DEEMPH_TC))
  46. b0, a1 = 1.0 - alpha, alpha
  47. for i in range(1, audio.size):
  48. audio[i] = b0 * audio[i] + a1 * audio[i-1]
  49.  
  50. audio = np.tanh(5 * audio) # soft-clip
  51.  
  52. fs_in = RF_RATE // 40 # 50 kHz
  53. if fs_in != AUDIO_RATE: # resample to 48 kHz
  54. g = gcd(fs_in, AUDIO_RATE)
  55. audio = scipy.signal.resample_poly(audio,
  56. AUDIO_RATE // g,
  57. fs_in // g)
  58. return audio.astype(np.float32)
  59.  
  60.  
  61. def main():
  62. hackrf = HackRf()
  63. hackrf.open()
  64. hackrf.set_sample_rate(RF_RATE)
  65. hackrf.set_freq(FREQ_HZ)
  66. hackrf.set_lna_gain(RF_GAIN_DB)
  67. hackrf.set_vga_gain(0)
  68. hackrf.set_amp_enable(False)
  69. hackrf.start_rx_mode()
  70.  
  71. print(f"► Receiving {FREQ_HZ/1e6:.3f} MHz – playing live audio…")
  72.  
  73. sd.default.samplerate = AUDIO_RATE
  74. sd.default.channels = 1
  75. stream = sd.OutputStream(dtype='float32')
  76. stream.start()
  77.  
  78. try:
  79. while True:
  80. raw = hackrf.receive(BUF_SYMS) # bytes: I0 Q0 I1 Q1…
  81. u8 = np.frombuffer(raw, dtype=np.uint8).astype(np.float32) - 128.0
  82. iq = (u8[::2] + 1j * u8[1::2]) / 128.0 # complex f32 −1…+1
  83. audio = fm_demodulate(iq)
  84. stream.write(audio)
  85. except KeyboardInterrupt:
  86. pass
  87. finally:
  88. stream.stop()
  89. hackrf.stop_rx_mode()
  90. hackrf.close()
  91.  
  92.  
  93. if __name__ == "__main__":
  94. main()
  95.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement