Skip to content

Run the Demos

gr-apollo ships with four demo scripts that exercise different parts of the TX/RX chain. They progress from a self-contained streaming loopback to a full mission simulation with crew voice, and finally to live integration with the Virtual AGC emulator.

DemoRequiresWhat It Does
loopback_demo.pyGNU RadioStreaming TX to RX round-trip
voice_subcarrier_demo.pyGNU Radio, scipyReal audio through 1.25 MHz FM
full_downlink_demo.pyGNU Radio, scipyPCM telemetry + crew voice on one carrier
agc_loopback_demo.pyyaAGC (no GR)Live AGC telemetry over TCP

Script: examples/loopback_demo.py Requires: GNU Radio

The loopback demo connects usb_signal_source directly to usb_downlink_receiver through the GNU Radio scheduler. It transmits PCM frames, receives them back, and displays sync word analysis for each recovered frame.

graph LR
    A["usb_signal_source\n(TX chain)"]:::rf --> B["head\n(sample limiter)"]:::timing --> C["usb_downlink_receiver\n(RX chain)"]:::rf
    C -->|"frames (PDU)"| D["message_debug\n(store)"]:::data

    classDef data fill:#2d5016,stroke:#4a8c2a,color:#fff
    classDef rf fill:#1a3a5c,stroke:#3a7abd,color:#fff
    classDef timing fill:#5c3a1a,stroke:#bd7a3a,color:#fff
Terminal window
uv run python examples/loopback_demo.py
uv run python examples/loopback_demo.py --voice # include voice subcarrier
uv run python examples/loopback_demo.py --snr 20 # add noise at 20 dB SNR
uv run python examples/loopback_demo.py --frames 20 # generate 20 frames
ArgumentDefaultDescription
--frames10Number of PCM frames to transmit
--snrNoneSNR in dB (None = clean, no noise)
--voiceoffEnable the 1.25 MHz FM voice subcarrier with 1 kHz test tone
============================================================
Apollo USB Loopback Demo
============================================================
Frames to transmit: 10
Samples per frame: 102,400
Total samples: 1,024,000
Duration: 0.200 s
SNR: clean (no noise)
Voice subcarrier: disabled
Building flowgraph...
Running flowgraph (TX -> RX)...
Recovered 8 frames from 10 transmitted
------------------------------------------------------------
Frame 1: ID= 3 (odd ), sync=0xAB31D403, 124 words [00 00 00 00 00 00 00 00 ...]
Frame 2: ID= 4 (even), sync=0xABCED404, 124 words [00 00 00 00 00 00 00 00 ...]
...
------------------------------------------------------------
Recovery rate: 8/10 (80%)

Script: examples/voice_subcarrier_demo.py Requires: GNU Radio, scipy

This demo takes a real audio file (such as actual Apollo 11 crew recordings), modulates it onto the 1.25 MHz FM voice subcarrier with +/-29 kHz deviation, then demodulates it back to audio. The round-trip exercises the same signal path the spacecraft and ground station used.

graph LR
    A["WAV file\n(any rate)"]:::data --> B["resample\nto 8 kHz"]:::timing --> C["upsample\nto 5.12 MHz"]:::timing
    C --> D["fm_voice_subcarrier_mod\n(audio_input=True)"]:::rf
    D --> E["voice_subcarrier_demod\n(8 kHz output)"]:::rf
    E --> F["recovered\nWAV file"]:::data

    classDef data fill:#2d5016,stroke:#4a8c2a,color:#fff
    classDef rf fill:#1a3a5c,stroke:#3a7abd,color:#fff
    classDef timing fill:#5c3a1a,stroke:#bd7a3a,color:#fff
Terminal window
uv run python examples/voice_subcarrier_demo.py examples/audio/apollo11_crew.wav
uv run python examples/voice_subcarrier_demo.py input.wav --output recovered.wav
uv run python examples/voice_subcarrier_demo.py input.wav --play
ArgumentDefaultDescription
input (positional)Input WAV file (any sample rate)
--output, -o<input>_recovered.wavOutput WAV file path
--playoffPlay recovered audio with aplay after processing
--sample-rate5,120,000Baseband sample rate in Hz
============================================================
Apollo Voice Subcarrier Demo
============================================================
Input: examples/audio/apollo11_crew.wav
Sample rate: 48000 Hz
Duration: 5.23 s
Samples: 251,136
Resampled to 8000 Hz: 41,856 samples
Upsampling 8000 Hz -> 5.12 MHz (ratio 640:1)...
Upsampled: 26,787,840 samples (2.1s)
Building flowgraph: FM mod (1.25 MHz) -> FM demod...
Running flowgraph...
Processed in 3.4s
Recovered: 41,790 samples at 8000 Hz
Duration: 5.22 s
Saved: examples/audio/apollo11_crew_recovered.wav
Peak amplitude: 0.8234
Play with: aplay examples/audio/apollo11_crew_recovered.wav
Done.

Script: examples/full_downlink_demo.py Requires: GNU Radio, scipy

The full downlink demo reconstructs the complete Apollo USB downlink: PCM telemetry frames on the 1.024 MHz BPSK subcarrier AND crew voice on the 1.25 MHz FM subcarrier, both phase-modulated onto a single complex carrier. The receiver splits the signal back into decoded frames and audio.

graph TB
    subgraph TX ["TX (spacecraft)"]
        direction LR
        A["pcm_frame_source\n→ nrz → bpsk_mod"]:::data --> D["add_ff"]:::rf
        B["crew audio\n→ fm_voice_mod"]:::data --> C["× 0.764"]:::rf --> D
        D --> E["pm_mod"]:::rf
    end

    subgraph RX ["RX (ground station)"]
        direction LR
        F["pm_demod"]:::rf --> G["bpsk_demod\n→ frame_sync"]:::rf
        F --> H["voice_demod"]:::rf
        G --> I["PCM frames"]:::data
        H --> J["crew audio"]:::data
    end

    E --> F

    classDef data fill:#2d5016,stroke:#4a8c2a,color:#fff
    classDef rf fill:#1a3a5c,stroke:#3a7abd,color:#fff

This demo builds the TX chain manually (not using usb_signal_source) so it can inject external audio into the voice channel. It then runs the RX chain twice: once for PCM frame recovery, once for voice demodulation.

Terminal window
uv run python examples/full_downlink_demo.py examples/audio/apollo11_crew.wav
uv run python examples/full_downlink_demo.py input.wav --snr 25
uv run python examples/full_downlink_demo.py input.wav --play
ArgumentDefaultDescription
audio (positional)Input crew voice WAV file
--output, -o<input>_fullchain.wavOutput WAV path for recovered voice
--snrNoneAdd AWGN noise at this SNR in dB
--playoffPlay recovered voice with aplay
============================================================
Apollo Full Downlink Demo
PCM telemetry (1.024 MHz BPSK) + crew voice (1.25 MHz FM)
============================================================
Loading crew voice audio...
Source: examples/audio/apollo11_crew.wav (5.23s)
Upsampled: 26,787,840 samples at 5.12 MHz
PCM frames: ~263 at 50 fps
Signal: 26,787,840 samples (5.23s)
SNR: clean
TX: Building combined PCM + voice signal...
Generated 26,787,840 complex samples (4.2s)
PM envelope std: 0.000001 (should be ~0 for clean)
RX: Decoding PCM telemetry frames...
Recovered 260 PCM frames (6.1s)
Frame 1: ID= 3 (odd), 124 data words
Frame 2: ID= 4 (even), 124 data words
Frame 3: ID= 5 (odd), 124 data words
Frame 4: ID= 6 (even), 124 data words
Frame 5: ID= 7 (odd), 124 data words
... (255 more frames)
RX: Demodulating crew voice (1.25 MHz FM)...
Recovered 41,790 audio samples (3.8s)
Duration: 5.22s at 8000 Hz
Saved: examples/audio/apollo11_crew_fullchain.wav
============================================================
TX: 5.23s of combined PCM + voice
RX: 260 PCM frames + 5.22s crew voice
SNR: clean
============================================================
Play voice: aplay examples/audio/apollo11_crew_fullchain.wav

Script: examples/agc_loopback_demo.py Requires: yaAGC emulator (no GNU Radio needed)

This demo connects directly to a running Virtual AGC emulator over TCP, receives DNTM1/DNTM2 telemetry packets, decodes them into downlink list snapshots, and optionally sends DSKY commands.

graph LR
    A["yaAGC\n(Luminary099)"]:::timing -->|"TCP :19697"| B["AGCBridgeClient"]:::rf
    B --> C["DownlinkEngine\n(reassemble words)"]:::data
    C --> D["telemetry\nsnapshots"]:::data
    E["UplinkEncoder\n(V16N36E)"]:::data -->|"INLINK ch 045"| B

    classDef data fill:#2d5016,stroke:#4a8c2a,color:#fff
    classDef rf fill:#1a3a5c,stroke:#3a7abd,color:#fff
    classDef timing fill:#5c3a1a,stroke:#bd7a3a,color:#fff
  1. Install Virtual AGC from the project website. The key binary is yaAGC.

  2. Start the AGC emulator with a mission flight software image:

    Terminal window
    yaAGC --core=Luminary099.bin --port=19697
  3. Optionally start yaDSKY2 for a visual DSKY display:

    Terminal window
    yaDSKY2 --port=19698
Terminal window
uv run python examples/agc_loopback_demo.py
uv run python examples/agc_loopback_demo.py --host 192.168.1.100
uv run python examples/agc_loopback_demo.py --send-v16n36
uv run python examples/agc_loopback_demo.py --duration 30
ArgumentDefaultDescription
--hostlocalhostyaAGC hostname or IP
--port19697yaAGC TCP port
--duration10.0Collection duration in seconds
--send-v16n36offSend V16N36E (display mission elapsed time) to the AGC
============================================================
Apollo AGC Integration Demo
============================================================
Target: localhost:19697
Duration: 10.0 seconds
Connecting to yaAGC at localhost:19697...
Connection: connecting
Connection: connected
Sending V16N36E (display time)...
Sent 7 uplink words
Collecting telemetry for 10.0 seconds...
------------------------------------------------------------
Telemetry snapshot: CM Coast/Alignment (type 2), 400 words
[000] = 00002 (2)
[001] = 00000 (0)
[002] = 77777 (32767)
[003] = 00000 (0)
[004] = 00000 (0)
... (395 more words)
------------------------------------------------------------
Summary:
Total packets received: 1247
Telemetry words: 834
Telemetry snapshots: 2
Duration: 10.1 seconds
Done.

If you are new to gr-apollo:

  1. Start with the loopback demo. It has no external dependencies beyond GNU Radio and exercises the complete TX/RX round-trip in a self-contained flowgraph.

  2. Try the voice demo with an Apollo crew recording to hear the signal processing in action. Audio files in examples/audio/ are ready to use.

  3. Run the full downlink to see both PCM and voice working together on one carrier — the way it worked on the actual spacecraft.

  4. Connect to yaAGC when you are ready to interact with a running Apollo Guidance Computer.