Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📡 Spectrum Analyzer

Offline IQ Signal Analyzer — Time · Spectrum · Waterfall

Qt C++ FFTW3 License: GPL v3 Platform

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


🎬 Quick Demo

Spectrum Analyzer demo preview — click to watch video

▶ 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 for demo.webm (~70 MB, already in the repository).


✨ Features

📊 Tri-View Display

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

📁 Offline IQ Playback

  • Supports float32, double64, and uint16 interleaved 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

📈 Traces & Measurements (6 Independent Traces)

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

🔍 Channelizer

  • 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

🎛 Band Filter (Extract & Save)

  • 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

🌈 Waterfall & ColorMap Editor

  • 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

🎨 User Interface

  • 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

🖼 Screenshots

Full interface — three views + panels
Time · Spectrum · Waterfall
Channelizer — 7 channels + table
Channelizer overlays on spectrum
Band filter range selection
IQ filtering — 65% progress
Manual marker on spectrum
ColorMap editor

🏗 Architecture & Workflow

System Overview

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
Loading

Offline Playback Loop

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
Loading

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

🧮 Algorithms

1. STFT & Spectrum Averaging

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]
Loading

2. Channelizer — Occupancy Detection

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"]
Loading

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

3. IQ Bandpass Filter

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
Loading

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.


4. Trace Engine & Peak Navigation

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]
Loading

🛠 Build & Run

Requirements

# 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/

Build

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.

Run

./build/Desktop_Qt_6_7_2-Release/SpectrumAnalyzer

Quick Start

  1. File → Open — select an IQ recording
  2. In Source Settings: set sample rate, center frequency, and file sample type
  3. In Capture Settings: set RBW, sweep time, and overlap
  4. Press Play — inspect Time / Spectrum / Waterfall
  5. Run Channelizer to detect occupied bands
  6. Use Select Range → Filter & Save to extract a passband

📂 Project Layout

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

🔧 Tech Stack

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

📄 License

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

About

Offline IQ spectrum analyzer — Qt 6 desktop app with time/spectrum/waterfall views, STFT, channelizer, and IQ bandpass export (Linux, C++17, FFTW3)

Topics

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages