Offline IQ Signal Analyzer — Time · Spectrum · Waterfall
A professional spectrum analyzer with a modern desktop UI for playing back, visualizing, and processing recorded IQ files.
Three synchronized views, six independent traces, an intelligent channelizer, a real bandpass filter, and a colormap editor — all in one application.
Quick Demo · Features · Architecture · Algorithms · Build & Run
▶ Watch demo video (WebM) · Download demo.webm
Note: GitHub README does not embed repo-hosted
<video>tags. Click the preview or link above — GitHub opens its built-in player fordemo.webm(~70 MB, already in the repository).
| View | Description |
|---|---|
| Time Domain | IQ waveform over each sweep — time axis (ms) and I/Q amplitude |
| Frequency Spectrum | Power (dB) vs. frequency (Hz) with center/span axes |
| Waterfall | Scrolling spectrogram — spectrum history over time |
- Box zoom with dashed rubber band on all three plots
- Rescale from the right-click context menu to restore full range
- Synchronized X axis between spectrum and waterfall
- Time slider for offline seek plus Play / Pause / Restart
- Supports
float32,double64, anduint16interleaved I/Q files - Automatic WAV/RIFF header detection on input
- Configure sample rate, center frequency, and gain
- Auto repeat for looping playback
- Live parameters — change RBW, overlap, and sweep time without an Apply button
- Thread-safe design: file reading on a worker thread, FFT and rendering on the GUI thread
| Trace Mode | Behavior |
|---|---|
| Off | Disabled |
| Clear & Write | Overwrite every sweep |
| Max Hold | Retain maximum values |
| Min Hold | Retain minimum values |
| Min/Max Hold | Both min and max simultaneously |
| Average | Moving average with configurable count |
Markers / Peaks:
- Peak Search (Max) · Min Peak · Next Peak
- Peak Left / Peak Right — step through local extrema
- Manual marker — left-click on the spectrum with a diamond badge
- Disable All Peaks · per-trace color · Update / Hide toggles
- Automatic detection of occupied bands in the visible frequency span
- Automatic noise floor (lower PSD percentile) or manual threshold (click on spectrum)
- Green occupancy bands with fade-in / shimmer animation
- Horizontal noise-floor line on the plot
- Results table: Start · Stop · BW · Peak · Mean for each detected channel
- Select a frequency range with two clicks on the spectrum
- Live preview of boundary lines and shaded passband
- Real IQ bandpass filtering — not FFT bin masking:
- Frequency shift (mixer) → Kaiser lowpass FIR → FFT overlap-add
- Export to float32 / double64 / uint16 with a progress dialog
- Suitable for extracting a passband from wideband IQ for downstream analysis
- Editable colormap with a spline curve
- Presets: Night · Gamma 0.5 · Gamma 2.0 · Reset Linear · Invert
- Auto color scale · scale to reference level
- Delta marker on the waterfall
- Configurable history depth
- Decibelle-inspired layout with soft card shadows
- Collapsible panels: Source · Capture · Measurement Interval
- Animated splash screen and shimmer on panel titles
- Status bar: SIGNAL · TRACE · MARKER · READOUT
- Three-column layout: Measurement panel · plots · control panel
flowchart LR
subgraph Worker["⚙ OfflineWorker (Background Thread)"]
direction TB
READ[Read IQ Chunks]
RING[(ThreadSafe Ring Buffer<br/>complex float)]
READ --> RING
end
subgraph DSP["📐 GUI DSP"]
direction TB
FFT[FFTW Plan<br/>STFT / Averaging]
FRAME[Spectrum frame]
TR[SpectrumTraces<br/>6 traces + peaks]
CH[ChannelDetector]
BF[IQ Bandpass Filter<br/>on export]
FFT --> FRAME
FRAME --> TR
end
subgraph UI["🖥 MainWindow (GUI Thread)"]
direction TB
SRC[Source Panel<br/>fs · fc · gain · file type]
CAP[Capture Panel<br/>RBW · overlap · sweep time]
MP[Measurement Panel<br/>traces · peaks · channelizer · filter]
TP[Time Plot]
SP[Spectrum Plot]
WF[Waterfall Plot]
TBL[Channel Results Table]
end
FILE[(IQ File<br/>float32 · double64 · uint16)] --> READ
SRC -.-> READ
CAP -.-> READ
RING -->|IQ blocks| FFT
FRAME -->|time waveform| TP
FRAME -->|PSD| SP
FRAME -->|spectrogram row| WF
TR -->|trace overlay| SP
MP -.->|enable| CH
CH -->|occupancy bands| SP
CH -->|channel list| TBL
MP -.->|export range| BF
BF -->|filtered IQ| OUT[(Filtered IQ File)]
style UI fill:#f0f4f8,stroke:#3D7A9A
style Worker fill:#e8f5e9,stroke:#2e7d32
style DSP fill:#fff3e0,stroke:#ef6c00
flowchart TB
A[User presses Play] --> B[fillOfflineParamsFromUi]
B --> C[Worker: runOffline]
B --> I[Plot timer]
subgraph ReadLoop["Background — read loop"]
direction TB
D{Read loop}
E[Decode IQ<br/>float32 / double64 / uint16]
F[Push to ring buffer]
G{Auto repeat?}
H[finished]
D --> E --> F --> D
D -->|EOF| G
G -->|Yes| D
G -->|No| H
end
C -->|start worker| D
F -.->|ring buffer| J
subgraph PlotLoop["GUI — plot timer loop"]
direction TB
J[Pop one sweep block]
K[FFTW + overlap averaging]
L[Update time plot]
M[Update spectrum + traces]
N[Append waterfall row]
I --> J --> K
K --> L --> I
K --> M --> I
K --> N --> I
end
style A fill:#c8e6c9
style K fill:#ffe0b2
style ReadLoop fill:#e8f5e9,stroke:#2e7d32
style PlotLoop fill:#fff3e0,stroke:#ef6c00
Key parameters:
| Parameter | Computation |
|---|---|
| NFFT | Next power of two from fs / RBW (clamped by Max FFT) |
| Actual RBW | fs / NFFT — synced back to the UI |
| Hop | From overlap % or step length |
| Sweep block | fs × sweepTime samples |
flowchart TD
IQ[IQ block from ring] --> WIN[Hann window]
WIN --> FFTW[FFTW forward]
FFTW --> MAG["|X(k)|² → dB"]
MAG --> AVG{Overlap > 0?}
AVG -->|Yes| MEAN[Average multiple STFT frames]
AVG -->|No| OUT[Output spectrum]
MEAN --> OUT
OUT --> SPEC[Spectrum plot]
OUT --> WF[Waterfall row]
OUT --> TR[Trace engine]
flowchart TD
IN[PSD over ROI] --> SM[Moving average<br/>boxcar smoothing]
SM --> NF{Noise floor}
NF -->|Auto| PCT[20th percentile<br/>of lower bins]
NF -->|Manual| MAN[User click threshold]
PCT --> THR[Threshold = floor + margin]
MAN --> THR
THR --> HYST[Hysteresis tracking<br/>enter +6 dB · exit +3 dB]
HYST --> REG[Region extraction]
REG --> FILT[Filter minBins · minSNR]
FILT --> MERGE[Merge small gaps<br/>and shallow dips]
MERGE --> EDGE[Edge trim<br/>peak − 12 dB]
EDGE --> OUT["DetectedChannel[]<br/>Start · Stop · BW · Peak"]
Default channelizer parameters:
| Parameter | Value | Role |
|---|---|---|
enterMarginDb |
6 dB | Start a channel above noise |
exitMarginDb |
3 dB | End a channel |
noisePercentile |
0.20 | Noise-floor estimate |
smoothBins |
5 | Smoothing window |
minBins |
3 | Minimum channel width |
mergeGapBins |
24 | Merge nearby gaps |
edgeDropDb |
12 dB | Trim edges relative to peak |
flowchart LR
subgraph Input
F[Input IQ file]
end
subgraph Stage1["Frequency Translation"]
MIX["Mixer: exp(−j2π·fc·t)"]
end
subgraph Stage2["Lowpass"]
KAISER["Kaiser FIR<br/>β = 5.0"]
OLA["FFT overlap-add<br/>convolution"]
end
subgraph Output
SAVE[Output IQ file<br/>float32 · double64 · uint16]
end
F --> MIX
MIX --> KAISER
KAISER --> OLA
OLA --> SAVE
style Stage1 fill:#e3f2fd
style Stage2 fill:#fce4ec
Parameter derivation from absolute frequencies:
fc_mixer = (f_low + f_high) / 2 − center_freq
BW = f_high − f_low
cutoff = BW / 2 → Kaiser lowpass at baseband
Unlike FFT bin masking, this pipeline extracts a real passband IQ stream — suitable for downstream SDR/DSP workflows.
flowchart TD
FRAME[New spectrum frame] --> SEL{Trace mode?}
SEL -->|Clear & Write| CW[Overwrite buffer]
SEL -->|Max Hold| MX["max(buffer, frame)"]
SEL -->|Min Hold| MN["min(buffer, frame)"]
SEL -->|Average| AV[EMA with avgCount]
SEL -->|Min/Max| BOTH[Both buffers updated]
CW --> RENDER[Draw graph]
MX --> RENDER
MN --> RENDER
AV --> RENDER
BOTH --> RENDER
RENDER --> PEAK{Peak mode?}
PEAK -->|Max / Min| LIST[Build local extrema list]
PEAK -->|Manual| LOCK[Lock X — track Y]
LIST --> NAV[Next · Left · Right]
# Ubuntu / Debian
sudo apt install qt6-base-dev libfftw3-dev build-essential| Dependency | Version |
|---|---|
| Qt | 6.7+ (Widgets, Concurrent, PrintSupport) |
| C++ | 17 |
| FFTW3 | 3.x |
| QCustomPlot | Bundled in third_party/ |
git clone https://github.com/mohammadHaghpanah/SpectrumAnalyzer.git
cd SpectrumAnalyzer
qmake6 SpectrumAnalyzer.pro
make -j$(nproc)Or open SpectrumAnalyzer.pro in Qt Creator and build the Release configuration.
./build/Desktop_Qt_6_7_2-Release/SpectrumAnalyzer- File → Open — select an IQ recording
- In Source Settings: set sample rate, center frequency, and file sample type
- In Capture Settings: set RBW, sweep time, and overlap
- Press Play — inspect Time / Spectrum / Waterfall
- Run Channelizer to detect occupied bands
- Use Select Range → Filter & Save to extract a passband
SpectrumAnalyzer/
├── mainwindow.* # Main UI, orchestration, channelizer, band filter
├── offline_worker.* # Background IQ file reader
├── offline_params.h # Thread-safe shared parameters
├── threadsafe_buffer.h # Ring buffer
├── Measurement/
│ ├── spectrum_traces.* # 6 traces + peak manager
│ ├── channel_detector.* # Occupancy channelizer
│ ├── iq_bandpass_filter.* # Kaiser FIR + overlap-add
│ ├── measurement_panel.* # Traces / markers panel
│ └── channel_band_item.* # Channel overlay graphics
├── ColorMap_Editor/ # Waterfall colormap editor
├── third_party/qcustomplot.* # QCustomPlot
├── resources/ # Icons, splash screen, desktop entry
└── docs/assets/ # Screenshots and demo.webm
| Layer | Technology |
|---|---|
| UI framework | Qt 6 Widgets |
| Plotting | QCustomPlot |
| FFT | FFTW3 |
| DSP | Kaiser FIR · STFT · overlap-add |
| Concurrency | QThread + thread-safe ring buffer |
| Build | qmake / Qt Creator |
This project is licensed under GPL-3.0 — see LICENSE.
Bundled and linked dependencies are listed in THIRD_PARTY.md.
Spectrum Analyzer — Offline IQ Analysis · Qt 6 · FFTW3
Built for RF, DSP, and SDR engineers






