Skip to main content

A Python library for extracting scanner radio tones from scanner audio.

Project description

icad_tone_detection

PyPI version License: MIT

Detect Two-Tone (Quick Call), Pulsed Single-Tone (On/Off Beeps), Long Tones, Hi–Low warble tones, MDC1200 / FleetSync, and DTMF in scanner-audio recordings.
The heavy DSP is performed by a bundled native binary (icad_decode), while the Python wrapper handles audio I/O and STFT-based frequency extraction.


Features

  • Single function: tone_detect() or CLI tool icad-tone-detect
  • Tone types:
    • Pulsed single-tone
    • Two-Tone / Quick Call
    • Long tones
    • Hi–Low warble tones
    • MDC1200 / FleetSync
    • DTMF
  • Flexible inputs: File path, URL, bytes, BytesIO, or pydub.AudioSegment
  • Automatic resample to 16 kHz mono PCM via FFmpeg
  • Fully configurable: All thresholds & detection params exposed as keyword args or CLI flags
  • Binaries included for:
    • Linux (x86_64, arm64, armv7)
    • macOS (x86_64, arm64)
    • Windows (x86_64)

Installation

pip install icad_tone_detection

Requires: Python 3.9+ and ffmpeg in PATH.


Quick start — Python

from icad_tone_detection import tone_detect

result = tone_detect("my_scanner_recording.wav")
print(result.pulsed_result)
print(result.two_tone_result)

Defaults work for most recordings

The built-in defaults are intentionally neutral and should work well on typical scanner audio (8–16 kHz mono, moderate noise). In many cases you can just call tone detection with no extra knobs and get solid results. All detectors are enabled by default, and the frontend (STFT + grouping) aims to avoid over- or under-segmenting tones.

Use the simplest call first:

^^^python from icad_tone_detection import tone_detect

result = tone_detect("my_scanner_recording.wav") print(result.pulsed_result) print(result.two_tone_result) ^^^

Or via CLI:

^^^bash icad-tone-detect my_scanner_recording.wav --debug ^^^

Only tweak parameters if you have a specific problem (e.g., very drifty tones, very brisk pulses, or unusually noisy/quiet recordings).

When to tweak (quick guide):

  • Very fast pulses → lower time_resolution_ms from 50 to 25.
  • Tone drift gets merged (e.g., 992/950 collapsing) → set fe_abs_cap_hz=20–24, and optionally fe_force_split_step_hz≈18 with fe_split_lookahead_frames=2.
  • Too many weak frames pass (noisy audio) → make gating stricter by raising fe_silence_below_global_db (e.g., -24 dB) or increasing fe_snr_above_noise_db (e.g., 8 dB).
  • Real tones get gated out (very quiet recording) → make gating looser by lowering fe_silence_below_global_db (e.g., -32 dB) or decreasing fe_snr_above_noise_db (e.g., 4 dB).
  • Two-tone pairs too wide/tight → adjust two_tone_bw_hz (typical 20–30 Hz) and ensure two_tone_min_pair_separation_hz (default 40 Hz) fits your system.

Quick start — CLI

# Show help
icad-tone-detect --help

# Analyze a file with MDC disabled
icad-tone-detect my.wav --detect_mdc false --debug

Boolean flags accepted: true/false, yes/no, or 1/0.


Full CLI example

icad-tone-detect my_scanner_recording.wav \
  --detect_pulsed true \
  --pulsed_bw_hz 25 \
  --pulsed_min_cycles 6 \
  --pulsed_min_on_ms 120  --pulsed_max_on_ms 900 \
  --pulsed_min_off_ms 25  --pulsed_max_off_ms 350 \
  --pulsed_auto_center_band 200,3000 \
  --pulsed_mode_bin_hz 5 \
  \
  --detect_two_tone true \
  --tone_a_min_length 0.85 --tone_b_min_length 2.6 \
  --two_tone_bw_hz 25 --two_tone_min_pair_separation_hz 40 \
  \
  --detect_hi_low true \
  --hi_low_interval 0.2 --hi_low_min_alternations 6 \
  --hi_low_tone_bw_hz 25 --hi_low_min_pair_separation_hz 40 \
  \
  --detect_long true \
  --long_tone_min_length 3.8 --long_tone_bw_hz 25 \
  \
  --detect_mdc false --detect_dtmf true \
  \
  --time_resolution_ms 50 --matching_threshold 2.5 \
  --fe_freq_band 200,3000 \
  --fe_merge_short_gaps_ms 0 \
  --fe_silence_below_global_db -28 --fe_snr_above_noise_db 6 \
  \
  # FE refinements to keep stepped pairs from merging:
  --fe_abs_cap_hz 30 \
  --fe_force_split_step_hz 18 \
  --fe_split_lookahead_frames 2 \
  \
  --debug

Example output (debug mode)

############################################################
ICAD Tone Detection: DEBUG - v2.8.3
------------------------------------------------------------
Decode binary path:        /opt/icad/bin/linux_x86_64/icad_decode
Analyzing audio at:        /captures/2025-08-09_call_173.wav

Matching Threshold:        2.5%
Time Resolution (ms):      50

-- Frequency Extraction (frontend) --
  Search band (Hz):        200.0..3000.0
  Merge short gaps ≤ ms:   0
  Silence below global:    -28.0 dB
  SNR above noise floor:   +6.0 dB
  Abs cap (Hz):            30.0
  Force-split step (Hz):   18.0
  Split lookahead frames:  2

-- Two-Tone (Quick Call) --
  A min length (s):        0.85
  B min length (s):        2.60
  Max A→B gap (s):         0.35
  Intra-band width (Hz):   25.0
  Min A/B separation (Hz): 40.0

-- Long Tone --
  Min length (s):          3.80
  Intra-band width (Hz):   25.0

-- Hi–Low Warble --
  Interval (s):            0.20
  Min alternations:        6
  Intra-band width (Hz):   25.0
  Min pair separation (Hz):40.0

-- Pulsed Single Tone (auto-centered) --
  Band (Hz):               200.0..3000.0
  Mode bin width (Hz):     5.0
  BW around center (Hz):   ±25.0
  Min cycles:              6
  ON range (ms):           120..900
  OFF range (ms):          25..350

-- External Decoders --
  MDC/FleetSync:           disabled (hp=200 Hz, lp=4000 Hz)
  DTMF:                    enabled

Total Duration (s):        39.42
Sample Rate (Hz):          16000

Matched Frequencies (8 groups):
  1) Start=2.31s  | End=5.24s  | Dur=2.93s
       Freqs: [1009.9, 1010.4, 1010.2, 1010.1, 1010.1, 1010.0, 1010.1, 1009.8, 1010.3, 1010.2]
  2) Start=5.24s  | End=5.31s  | Dur=0.07s
       Freqs: [0.0, 0.0]                       # OFF
  3) Start=5.31s  | End=8.18s  | Dur=2.87s
       Freqs: [473.3, 473.2, 473.2, 473.4, 473.2, 473.3, 473.2, 473.3, 473.2]
  4) Start=8.18s  | End=8.22s  | Dur=0.04s
       Freqs: [0.0]                            # OFF
  5) Start=8.22s  | End=11.10s | Dur=2.88s
       Freqs: [809.9, 810.0, 810.1, 809.9, 810.0, 810.0, 810.1, 810.0, 810.0]
  6) Start=12.00s | End=12.08s | Dur=0.08s
       Freqs: [770.1, 770.0, 770.1, 769.9]
  7) Start=12.35s | End=12.43s | Dur=0.08s
       Freqs: [1206.9, 1207.1, 1207.0, 1206.8]
  8) Start=20.52s | End=23.34s | Dur=2.82s
       Freqs: [954.7, 954.9, 955.1, 954.8, 955.0, 954.9, 954.8, 954.9, 954.9]

############################################################
Masked intervals
  Pulsed windows:          [2.31–5.24], [20.52–23.34]
  Two-tone B windows:      [8.22–11.10]
  Long-tone windows:       (none)
############################################################

------------------------------------------------------------
DETECTION SUMMARY
------------------------------------------------------------
Two-Tone (Quick Call): 1
Long Tones:            0
Hi-Low Warble:         0
Pulsed Single Tone:    2
MDC1200/FleetSync:     0   (disabled)
DTMF:                  2
------------------------------------------------------------

{
  "pulsed": [
    {
      "tone_id": "pl_1",
      "detected": 1010.1,
      "start": 2.31, "end": 5.24, "length": 2.93,
      "cycles": 8,
      "on_ms_median": 180, "off_ms_median": 92
    },
    {
      "tone_id": "pl_2",
      "detected": 955.0,
      "start": 20.52, "end": 23.34, "length": 2.82,
      "cycles": 7,
      "on_ms_median": 160, "off_ms_median": 85
    }
  ],
  "two_tone": [
    {
      "tone_id": "qc_1",
      "detected": [473.3, 810.0],
      "tone_a_length": 0.91, "tone_b_length": 2.88,
      "start": 5.31, "end": 11.10
    }
  ],
  "long_tone": [],
  "hi_low": [],
  "mdc": [],
  "dtmf": [
    { "digit": "5", "start": 12.00, "end": 12.08 },
    { "digit": "9", "start": 12.35, "end": 12.43 }
  ]
}

Python API — tone_detect() signature

result = tone_detect(
  audio_path="my.wav",          # path / URL / BytesIO / AudioSegment

  # STFT & grouping
  matching_threshold=2.5,       # % tolerance for grouping frames
  time_resolution_ms=50,        # STFT hop size in ms

  # Frequency-extraction (frontend)
  fe_freq_band=(200.0, 3000.0),         # Hz band to search for peaks
  fe_merge_short_gaps_ms=0,             # merge groups separated by ≤ this gap (ms)
  fe_silence_below_global_db=-28.0,     # OFF if frame is this many dB below global peak
  fe_snr_above_noise_db=6.0,            # require SNR above simple noise floor
  fe_abs_cap_hz=None,                   # cap dynamic tolerance (e.g., 30.0) or None
  fe_force_split_step_hz=None,          # force split if step > Hz (e.g., 18.0) or None
  fe_split_lookahead_frames=0,          # confirm forced split with lookahead

  # Quick Call (two-tone)
  tone_a_min_length=0.85,               # sec – min A-tone
  tone_b_min_length=2.6,                # sec – min B-tone
  two_tone_max_gap_between_a_b=0.35,    # sec – max gap A→B
  two_tone_bw_hz=25.0,                  # Hz – intra-group stability
  two_tone_min_pair_separation_hz=40.0, # Hz – min A/B separation

  # Hi/Low warble
  hi_low_interval=0.2,                  # sec – max gap between alternations
  hi_low_min_alternations=6,            # min alternations
  hi_low_tone_bw_hz=25.0,               # Hz – intra-group stability
  hi_low_min_pair_separation_hz=40.0,   # Hz – min separation between tones

  # Long tone
  long_tone_min_length=3.8,             # sec – min duration
  long_tone_bw_hz=25.0,                 # Hz – intra-group stability

  # Pulsed single tone (auto-centered)
  pulsed_bw_hz=25.0,                    # Hz ± deviation counted as ON
  pulsed_min_cycles=6,                  # min ON→OFF cycles
  pulsed_min_on_ms=120,                 # ms – ON min
  pulsed_max_on_ms=900,                 # ms – ON max
  pulsed_min_off_ms=25,                 # ms – OFF min
  pulsed_max_off_ms=350,                # ms – OFF max
  pulsed_auto_center_band=(200.0, 3000.0),  # Hz band to auto-estimate center
  pulsed_mode_bin_hz=5.0,               # Hz bin width for robust center mode

  # Detector toggles
  detect_pulsed=True,
  detect_two_tone=True,
  detect_long=True,
  detect_hi_low=True,

  # External decoders
  detect_mdc=True,                      # MDC1200 / FleetSync
  mdc_high_pass=200,                    # Hz
  mdc_low_pass=4000,                    # Hz
  detect_dtmf=True,

  debug=False,
)

Result object

tone_detect() returns a ToneDetectionResult dataclass with:

  • pulsed_result — Pulsed single-tone hits: tone_id, detected, start, end, length, cycles, on_ms_median, off_ms_median
  • two_tone_result — Quick-Call matches: tone_id, detected [A,B], tone_a_length, tone_b_length, start, end
  • long_result — Long tone hits: tone_id, detected, length, start, end
  • hi_low_result — Warble sequences: tone_id, detected [low,high], alternations, length, start, end
  • mdc_result — Decoded frames from external MDC/FleetSync decoder (if enabled)
  • dtmf_result — Decoded DTMF presses (if enabled)

Platforms & binaries

OS Architectures Wheel folder name
Linux x86_64, arm64, armv7 linux_x86_64, linux_arm64, linux_armv7
macOS x86_64, arm64 macos_x86_64, macos_arm64
Windows x86_64 windows_x86_64

Example audio

Sample WAV files are in examples/example_audio/.


Contributing

Issues and pull requests welcome: GitHub repo


License

MIT © TheGreatCodeholio • Version 2.8.3 • Python 3.9+

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

icad_tone_detection-2.8.3.tar.gz (2.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

icad_tone_detection-2.8.3-py3-none-any.whl (2.9 MB view details)

Uploaded Python 3

File details

Details for the file icad_tone_detection-2.8.3.tar.gz.

File metadata

  • Download URL: icad_tone_detection-2.8.3.tar.gz
  • Upload date:
  • Size: 2.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for icad_tone_detection-2.8.3.tar.gz
Algorithm Hash digest
SHA256 f737cb5933d777f342be8822a7ebdfdaebda3658e8e2eea6d04e5995a769d3b6
MD5 4fbe85e4ea9d289ea664e80c8cd41ca7
BLAKE2b-256 d01571b1d6f40600de834405a3e6d98ab726370dff4f171ecd60637d42810af8

See more details on using hashes here.

Provenance

The following attestation bundles were made for icad_tone_detection-2.8.3.tar.gz:

Publisher: python-package.yml on TheGreatCodeholio/icad_tone_detection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file icad_tone_detection-2.8.3-py3-none-any.whl.

File metadata

File hashes

Hashes for icad_tone_detection-2.8.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c361be5a8ae5652ae1da258d2c423d92d9ddb8be97f06ee8b3f7aa31f50d244f
MD5 f972489be21030331f8e3937ad52a434
BLAKE2b-256 9497bea28aa5a59534b1c8a479388dc7c89d26b94848b64fda3beeb67f61efd0

See more details on using hashes here.

Provenance

The following attestation bundles were made for icad_tone_detection-2.8.3-py3-none-any.whl:

Publisher: python-package.yml on TheGreatCodeholio/icad_tone_detection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page