Full Code of szpajder/RTLSDR-Airband for AI

main f8a17d7f0e5a cached
109 files
919.5 KB
382.4k tokens
313 symbols
1 requests
Download .txt
Showing preview only (960K chars total). Download the full file or copy to clipboard to get everything.
Repository: szpajder/RTLSDR-Airband
Branch: main
Commit: f8a17d7f0e5a
Files: 109
Total size: 919.5 KB

Directory structure:
gitextract_bz4e8om4/

├── .clang-format
├── .devcontainer/
│   ├── Dockerfile
│   ├── devcontainer.json
│   └── shell
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── install_dependencies
│   ├── platform_build
│   └── workflows/
│       ├── build_docker_containers.yml
│       ├── ci_build.yml
│       ├── code_formatting.yml
│       ├── platform_build.yml
│       └── version_bump.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode/
│   ├── c_cpp_properties.json
│   ├── launch.json
│   └── settings.json
├── CMakeLists.txt
├── Dockerfile
├── LICENSE
├── NEWS.md
├── README.md
├── config/
│   ├── basic_multichannel.conf
│   ├── basic_scanning.conf
│   ├── big_mixer.conf
│   ├── mixers.conf
│   ├── noaa.conf
│   └── two_dongles_multiple_outputs.conf
├── init.d/
│   ├── rtl_airband-debian.sh
│   ├── rtl_airband-freebsd.sh
│   ├── rtl_airband-gentoo.sh
│   └── rtl_airband.service
├── scripts/
│   ├── find_version
│   └── reformat_code
└── src/
    ├── .gitignore
    ├── CMakeLists.txt
    ├── CMakeModules/
    │   ├── FindBCM_VC.cmake
    │   ├── FindLame.cmake
    │   ├── FindMiriSDR.cmake
    │   ├── FindRTLSDR.cmake
    │   └── version.cmake
    ├── config.cpp
    ├── config.h.in
    ├── ctcss.cpp
    ├── ctcss.h
    ├── filters.cpp
    ├── filters.h
    ├── generate_signal.cpp
    ├── generate_signal.h
    ├── hello_fft/
    │   ├── CMakeLists.txt
    │   ├── gpu_fft.c
    │   ├── gpu_fft.h
    │   ├── gpu_fft.txt
    │   ├── gpu_fft_base.c
    │   ├── gpu_fft_shaders.c
    │   ├── gpu_fft_trans.h
    │   ├── gpu_fft_twiddles.c
    │   ├── hex/
    │   │   ├── shader_1024k.hex
    │   │   ├── shader_128k.hex
    │   │   ├── shader_16k.hex
    │   │   ├── shader_1k.hex
    │   │   ├── shader_2048k.hex
    │   │   ├── shader_256.hex
    │   │   ├── shader_256k.hex
    │   │   ├── shader_2k.hex
    │   │   ├── shader_32k.hex
    │   │   ├── shader_4k.hex
    │   │   ├── shader_512.hex
    │   │   ├── shader_512k.hex
    │   │   ├── shader_64k.hex
    │   │   ├── shader_8k.hex
    │   │   └── shader_trans.hex
    │   ├── mailbox.c
    │   └── mailbox.h
    ├── helper_functions.cpp
    ├── helper_functions.h
    ├── input-common.cpp
    ├── input-common.h
    ├── input-file.cpp
    ├── input-file.h
    ├── input-helpers.cpp
    ├── input-helpers.h
    ├── input-mirisdr.cpp
    ├── input-mirisdr.h
    ├── input-rtlsdr.cpp
    ├── input-rtlsdr.h
    ├── input-soapysdr.cpp
    ├── input-soapysdr.h
    ├── logging.cpp
    ├── logging.h
    ├── mixer.cpp
    ├── output.cpp
    ├── pulse.cpp
    ├── rtl_airband.cpp
    ├── rtl_airband.h
    ├── rtl_airband_neon.s
    ├── squelch.cpp
    ├── squelch.h
    ├── test_base_class.cpp
    ├── test_base_class.h
    ├── test_ctcss.cpp
    ├── test_filters.cpp
    ├── test_generate_signal.cpp
    ├── test_helper_functions.cpp
    ├── test_squelch.cpp
    ├── udp_stream.cpp
    └── util.cpp

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
---
BasedOnStyle: Chromium
IndentWidth: 4
ObjCBlockIndentWidth: 4
ColumnLimit: 200


================================================
FILE: .devcontainer/Dockerfile
================================================
FROM ubuntu:latest

RUN sed -i 's/^# \(.*export LS_OPTIONS.*$\)/\1/g' ~/.bashrc && \
    sed -i 's/^# \(.*alias ll.*$\)/\1/g' ~/.bashrc

RUN ln -fs /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
RUN DEBIAN_FRONTEND=noninteractive \
    apt-get update && \
    apt-get install -y \
        tzdata\
        git \
        sudo \
        gdb \
        clang-format-14 \
        python3-pip \
        pre-commit \
        vim

WORKDIR /app
COPY .github/install_dependencies /app/
RUN /app/install_dependencies

RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*


================================================
FILE: .devcontainer/devcontainer.json
================================================
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.177.0/containers/docker-existing-dockerfile
{
	"name": "Existing Dockerfile",

	// Sets the run context to one level up instead of the .devcontainer folder.
	"context": "..",

	"dockerFile": "Dockerfile",

	"updateContentCommand" : "apt-get install git",

	"postCreateCommand" : "cmake -B /app/build -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=true ; pre-commit install",

	// vs code extensions to install in the dev container
	"customizations": {
		"vscode": {
			"extensions": [
				"ms-vscode.cpptools",
				"ms-vscode.cmake-tools",
				"ms-vscode.cpptools-extension-pack",
				"twxs.cmake",
				"streetsidesoftware.code-spell-checker",
				"ms-azuretools.vscode-docker",
				"GitHub.vscode-github-actions",
				"xaver.clang-format"
			]
		}
	},

	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ]
}


================================================
FILE: .devcontainer/shell
================================================
#!/bin/bash -e

cd `dirname $0`/../

# build container
docker build -t rtl_airband-dev -f .devcontainer/Dockerfile .

# run bash in container
docker run --rm -v $(pwd):/app/ -it --entrypoint bash rtl_airband-dev


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Report a bug you found when using RTLSDR-Airband
title: "[BUG]"
labels: ''
assignees: ''

---

<!--
Please use this template to create your bug report. By providing as much info as possible you help understand the issue, reproduce it and resolve it for you quicker. Therefore take a couple of extra minutes to make sure you have provided all information needed.
-->

**Describe your environment**

  - RTLSDR-Airband version you are using (stable release number or branch/commit):
  - `make` options used to build the program:
  - Hardware platform (eg. x86_64, Raspberry Pi v4):
  - Operating system name and version:

**What happened?**

**What you expected to happen?**

**Steps to Reproduce**

**Additional context**
Add any other relevant information about the problem here.

**Your rtl_airband.conf file**
Remove passwords, server addresses and other private information.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Questions & Help
    url: https://github.com/rtl-airband/RTLSDR-Airband/discussions/categories/q-a
    about: Please ask and answer questions here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE]"
labels: ''
assignees: ''

---

**Is your feature request related to a problem? If so, please describe.**
A description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**

**Describe alternative solutions or features you've considered**

**Additional context**
Add any other relevant information about the feature request here.


================================================
FILE: .github/install_dependencies
================================================
#!/bin/bash

unameOut="$(uname -s)"

echo "Running on ${unameOut} as ${USER}"

case "${unameOut}" in
    Linux*)
        echo "Installing Linux dependencies"
        sudo apt-get update -y
        sudo apt-get install -y \
            build-essential \
            cmake \
            libmp3lame-dev \
            libshout3-dev \
            libconfig++-dev \
            libfftw3-dev \
            librtlsdr-dev \
            libsoapysdr-dev \
            libpulse-dev

        (
            git clone https://github.com/f4exb/libmirisdr-4
            cd libmirisdr-4
            mkdir build
            cd build
            cmake ../
            sudo make install
            sudo ldconfig
        )
        ;;

    Darwin*)
        echo "Installing MacOS dependencies"

        # detect when running in github workflow and skip `brew update` (relay on fresh OS image)
        if [ -n "${GITHUB_ACTION}" ] ; then
            echo "running in GitHub Workflow, skipping brew update"
            export HOMEBREW_NO_AUTO_UPDATE=1
            export HOMEBREW_NO_INSTALL_UPGRADE=1
            export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
            echo "running ${ImageOS} vsersion ${ImageVersion}"
        else
            brew update
        fi

        brew install \
            lame \
            libshout \
            libconfig \
            fftw \
            librtlsdr \
            soapysdr \
            pulseaudio \
            pkg-config

        ;;

    *)
        echo "Error: Machine not supported"
        exit -1
esac


================================================
FILE: .github/platform_build
================================================
#!/bin/bash -e

platform="${1}"

if [ -z "${platform}" ]; then
    echo "Error: platform not set"
    exit -1
fi

echo "running build for ${platform} on $(source /etc/os-release ; echo ${VERSION})"

case "${platform}" in
    rpi3b)
        CMAKE_ARGS="-DPLATFORM=rpiv2 -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE"
        ;;
    ubuntu-22.04-arm)
        CMAKE_ARGS="-DPLATFORM=native -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE"
        ;;

    *)
        echo "Error: Platform '${platform}' not supported"
        exit -1
esac

# make a build dir
rm -rf build || true ; mkdir build
cd build

# configure and build
cmake ${CMAKE_ARGS} ../
VERBOSE=1 make -j

# run unit tests
src/unittests

# run rtl_airband to get version string and exit
src/rtl_airband -v


================================================
FILE: .github/workflows/build_docker_containers.yml
================================================
name: Build and Publish Containers

on:
  push:
    branches: [main, unstable]
    tags: ['v*']
  pull_request:
  workflow_dispatch:
  schedule:
    - cron: '29 13 * * *' # run daily

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            platform: linux/amd64
          - os: ubuntu-latest
            platform: linux/386
          - os: ubuntu-24.04-arm
            platform: linux/arm64
          - os: ubuntu-24.04-arm
            platform: linux/arm/v6
          - os: ubuntu-24.04-arm
            platform: linux/arm/v7
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read
      packages: write
      attestations: write
      id-token: write
    steps:

    - name: Runner Info
      run: printenv | sort

    - name: Prepare
      id: prep
      run: |
        echo "platform_pair=${platform//\//-}" >> $GITHUB_OUTPUT
        echo "repo_lowercase=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
      env:
        platform: ${{ matrix.platform }}

    - name: Checkout
      uses: actions/checkout@v4
      with:
        fetch-depth: '0' # need full history to get version from git tag

    - name: Container metadata
      id: metadata
      uses: docker/metadata-action@v5
      with:
        images: ghcr.io/${{ steps.prep.outputs.repo_lowercase }}

    - name: Set up QEMU
      uses: docker/setup-qemu-action@v3

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Login to GitHub Container Registry
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Build and push by digest
      id: build
      uses: docker/build-push-action@v6
      with:
        platforms: ${{ matrix.platform }}
        cache-from: type=gha,scope=build-${{ steps.prep.outputs.platform_pair }}
        cache-to: type=gha,mode=max,scope=build-${{ steps.prep.outputs.platform_pair }}
        context: .
        outputs: type=image,name=ghcr.io/${{ steps.prep.outputs.repo_lowercase }},push-by-digest=true,name-canonical=true,push=true

    - name: Export digest
      run: |
        mkdir -p /tmp/digests
        digest="${{ steps.build.outputs.digest }}"
        touch "/tmp/digests/${digest#sha256:}"

    - name: Upload digest
      uses: actions/upload-artifact@v4
      with:
        name: digests-${{ steps.prep.outputs.platform_pair }}
        path: /tmp/digests/*
        if-no-files-found: error
        retention-days: 1

  merge:
    runs-on: ubuntu-latest
    needs: build
    permissions:
      contents: read
      packages: write
    steps:

    - name: Runner Info
      run: printenv | sort

    - name: Prepare
      id: prep
      run: |
        echo "repo_lowercase=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT

    - name: Download digests
      uses: actions/download-artifact@v4
      with:
        path: /tmp/digests
        pattern: digests-*
        merge-multiple: true

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Container metadata
      id: metadata
      uses: docker/metadata-action@v5
      with:
        images: ghcr.io/${{ steps.prep.outputs.repo_lowercase }}

    - name: Login to GitHub Container Registry
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Create manifest list and push
      working-directory: /tmp/digests
      run: |
        docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
          $(printf 'ghcr.io/${{ steps.prep.outputs.repo_lowercase }}@sha256:%s ' *)

    - name: Inspect image
      run: |
        docker buildx imagetools inspect ghcr.io/${{ steps.prep.outputs.repo_lowercase }}:${{ steps.metadata.outputs.version }}


================================================
FILE: .github/workflows/ci_build.yml
================================================
name: Run CI

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
  workflow_dispatch:
  schedule:
    - cron: '39 13 * * *' # run daily

jobs:
  ci_build:
    strategy:
      matrix:
        os: [ ubuntu-22.04, macos-14, ubuntu-22.04-arm ]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 35 # runtime across all OSs, runs can get queued
    steps:
    - name: Runner Info
      run: printenv | sort

    - name: Checkout
      uses: actions/checkout@v4
      with:
        fetch-depth: '0' # need full history to get version from git tag

    - name: Install packaged dependencies
      run: .github/install_dependencies

    - name: Configure
      run: |
        cmake -B ${{github.workspace}}/build_Debug -DCMAKE_BUILD_TYPE=Debug -DBUILD_UNITTESTS=TRUE
        cmake -B ${{github.workspace}}/build_Debug_NFM -DCMAKE_BUILD_TYPE=Debug -DNFM=TRUE -DBUILD_UNITTESTS=TRUE
        cmake -B ${{github.workspace}}/build_Release -DCMAKE_BUILD_TYPE=Release -DBUILD_UNITTESTS=TRUE
        cmake -B ${{github.workspace}}/build_Release_NFM -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE

    - name: Build
      run: |
        VERBOSE=1 cmake --build ${{github.workspace}}/build_Debug -j4
        VERBOSE=1 cmake --build ${{github.workspace}}/build_Debug_NFM -j4
        VERBOSE=1 cmake --build ${{github.workspace}}/build_Release -j4
        VERBOSE=1 cmake --build ${{github.workspace}}/build_Release_NFM -j4

    - name: Unit Tests
      run: |
        ${{github.workspace}}/build_Debug/src/unittests
        ${{github.workspace}}/build_Debug_NFM/src/unittests
        ${{github.workspace}}/build_Release/src/unittests
        ${{github.workspace}}/build_Release_NFM/src/unittests

    - name: Install
      run: sudo cmake --install ${{github.workspace}}/build_Release_NFM

    - name: Test run
      run: /usr/local/bin/rtl_airband -v


================================================
FILE: .github/workflows/code_formatting.yml
================================================
name: Code Formatting

on:
  pull_request:
  schedule:
    - cron: '39 13 * * *' # run daily

jobs:
  code_formatting:
    runs-on: ubuntu-latest
    steps:

    - name: Runner Info
      run: printenv | sort

    - name: Checkout
      uses: actions/checkout@v4

    - name: Install Clang Format
      run: sudo apt-get install clang-format-14

    - name: Run Clang Format
      run: |
        ./scripts/reformat_code
        git diff --exit-code


================================================
FILE: .github/workflows/platform_build.yml
================================================
name: Platform Build

on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
  workflow_dispatch:
  schedule:
    - cron: '39 13 * * *' # run daily

jobs:
  platform_build:
    strategy:
      matrix:
        # os: [ rpi3b ]
        os: [ ubuntu-22.04-arm ]
    runs-on: ${{ matrix.os }}
    timeout-minutes: 35 # runtime across all OSs, runs can get queued
    steps:
    - name: Runner Info
      run: printenv | sort

    - name: Checkout
      uses: actions/checkout@v4
      with:
        fetch-depth: '0' # need full history to get version from git tag

    - name: Install packaged dependencies
      run: .github/install_dependencies

    - name: Configure Build and Test
      run: .github/platform_build ${{ matrix.os }}


================================================
FILE: .github/workflows/version_bump.yml
================================================
name: Bump version
on:
  pull_request:
    types:
      - closed
    branches:
      - main
jobs:
  version_bump:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-22.04
    permissions:
      contents: write
      actions: write
    steps:
    - uses: actions/checkout@v4
      with:
        ref: ${{ github.event.pull_request.merge_commit_sha }}
        fetch-depth: '0'

    - name: Bump version and push tag
      id: tag
      uses: anothrNick/github-tag-action@1.64.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        WITH_V: true
        DEFAULT_BUMP: patch

    - name: Create release for ${{ steps.tag.outputs.new_tag }}
      if: steps.tag.outputs.part != 'patch'
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        tag: ${{ steps.tag.outputs.new_tag }}
      run: |
        gh release create "$tag" \
          --repo="$GITHUB_REPOSITORY" \
          --title="Version ${tag#v}" \
          --generate-notes

    - name: Run CI on ${{ steps.tag.outputs.new_tag }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        gh workflow run ci_build.yml --ref ${{ steps.tag.outputs.new_tag }}

    - name: Run Platform Build ${{ steps.tag.outputs.new_tag }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        gh workflow run platform_build.yml --ref ${{ steps.tag.outputs.new_tag }}

    - name: Build and Publish Containers for ${{ steps.tag.outputs.new_tag }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        gh workflow run build_docker_containers.yml --ref ${{ steps.tag.outputs.new_tag }}


================================================
FILE: .gitignore
================================================
build*/
.DS_Store
.cache
compile_commands.json
rtl_airband*.log


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
    - id: check-yaml
    - id: end-of-file-fixer
    - id: trailing-whitespace
    - id: check-shebang-scripts-are-executable

- repo: https://github.com/pre-commit/mirrors-clang-format
  rev: v14.0.6
  hooks:
  - id: clang-format
    files: src/.*\.cpp|src/.*\.h


================================================
FILE: .vscode/c_cpp_properties.json
================================================
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "${workspaceFolder}/build/_deps/googletest-src/googletest/include/",
                "${workspaceFolder}/build/src/"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-arm64",
            "configurationProvider": "ms-vscode.cmake-tools"
        }
    ],
    "version": 4
}


================================================
FILE: .vscode/launch.json
================================================
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch Unit Test",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build/src/unittests",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set Disassembly Flavor to Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        }

    ]
}


================================================
FILE: .vscode/settings.json
================================================
{
    "editor.formatOnPaste": true,
    "editor.formatOnSave": true,
    "editor.formatOnType": true,
    "editor.defaultFormatter": "xaver.clang-format",
    "clang-format.executable": "clang-format-14"
}


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required (VERSION 3.1...3.18 FATAL_ERROR)
project (RTLSDR-Airband CXX)

execute_process(COMMAND ${PROJECT_SOURCE_DIR}/scripts/find_version
   OUTPUT_VARIABLE RTL_AIRBAND_VERSION
   OUTPUT_STRIP_TRAILING_WHITESPACE
   ERROR_VARIABLE RTL_AIRBAND_VERSION_ERROR
   ERROR_STRIP_TRAILING_WHITESPACE)

string(COMPARE EQUAL "${RTL_AIRBAND_VERSION}" "" RTL_AIRBAND_VERSION_UNSET)

if(RTL_AIRBAND_VERSION_UNSET)
   message(FATAL_ERROR "Failed to detect RTL_AIRBAND_VERSION - \"${RTL_AIRBAND_VERSION_ERROR}\"")
endif()

set (CMAKE_CXX_STANDARD 11)
set (CXX_STANDARD_REQUIRED ON)
set (CMAKE_CXX_EXTENSIONS OFF)
set (CMAKE_COMPILE_WARNING_AS_ERROR ON)

if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE Release)
   message(STATUS "Build type not specified: defaulting to Release")
endif(NOT CMAKE_BUILD_TYPE)

# TODO: flags to add: -Wfloat-equal -Wconversion -Wstrict-overflow=5 -Waggregate-return -Wpedantic -Wcast-align
# TODO: these could be added except for gtest: -Wswitch-enum -Wundef -Wswitch-default
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wshadow -Wdate-time -Wpointer-arith -Wwrite-strings -Wcast-qual  -Wunreachable-code -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -DDEBUG")

if(DEBUG_SQUELCH)
   set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG_SQUELCH")
endif()

add_subdirectory (src)


================================================
FILE: Dockerfile
================================================
# build container
FROM debian:bookworm-slim AS build

# install build dependencies
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y --no-install-recommends \
      build-essential \
      cmake \
      libmp3lame-dev \
      libshout3-dev \
      libconfig++-dev \
      libfftw3-dev \
      libsoapysdr-dev \
      libpulse-dev \
      \
      git \
      ca-certificates \
      libusb-1.0-0-dev \
      debhelper \
      pkg-config \
      && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# set working dir for compiling dependencies
WORKDIR /build_dependencies

# compile / install rtl-sdr-blog version of rtl-sdr for v4 support
RUN git clone https://github.com/rtlsdrblog/rtl-sdr-blog && \
    cd rtl-sdr-blog/ && \
    dpkg-buildpackage -b --no-sign && \
    cd .. && \
    dpkg -i librtlsdr0_*.deb && \
    dpkg -i librtlsdr-dev_*.deb && \
    dpkg -i rtl-sdr_*.deb

# compile / install libmirisdr-4
RUN git clone https://github.com/f4exb/libmirisdr-4 && \
  cd libmirisdr-4 && \
  mkdir build && \
  cd build && \
  cmake ../ && \
  VERBOSE=1 make install && \
  ldconfig

# TODO: build anything from source?

# set working dir for project build
WORKDIR /rtl_airband_build

# copy in the rtl_airband source, coping in the full repo so find_version will be correct
COPY ./ .

# configure and build
# TODO: detect platforms
RUN cmake -B build_dir -DPLATFORM=generic -DCMAKE_BUILD_TYPE=Release -DNFM=TRUE -DBUILD_UNITTESTS=TRUE && \
    VERBOSE=1 cmake --build build_dir -j4

# make sure unit tests pass
RUN ./build_dir/src/unittests


# application container
FROM debian:bookworm-slim

# install runtime dependencies
RUN apt-get update && \
  apt-get upgrade -y && \
  apt-get install -y --no-install-recommends \
    tini \
    libc6 \
    libmp3lame0 \
    libshout3 \
    libconfig++9v5 \
    libfftw3-single3 \
    libsoapysdr0.8 \
    libpulse0 \
    libusb-1.0-0-dev \
    ca-certificates \
  && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# install (from build container) rtl-sdr-blog version of rtl-sdr for v4 support
COPY --from=build /build_dependencies/librtlsdr0_*.deb /build_dependencies/librtlsdr-dev_*.deb /build_dependencies/rtl-sdr_*.deb /tmp/
RUN dpkg -i /tmp/librtlsdr0_*.deb && \
    dpkg -i /tmp/librtlsdr-dev_*.deb && \
    dpkg -i /tmp/rtl-sdr_*.deb && \
    rm -rf /tmp/*.deb && \
    echo '' | tee --append /etc/modprobe.d/rtl_sdr.conf && \
    echo 'blacklist dvb_usb_rtl28xxun' | tee --append /etc/modprobe.d/rtl_sdr.conf && \
    echo 'blacklist rtl2832' | tee --append /etc/modprobe.d/rtl_sdr.conf && \
    echo 'blacklist rtl2830' | tee --append /etc/modprobe.d/rtl_sdr.conf

# copy (from build container) libmirisdr-4 library
COPY --from=build /usr/local/lib/libmirisdr.so.4 /usr/local/lib/

# Copy rtl_airband from the build container
COPY LICENSE /app/
COPY --from=build /rtl_airband_build/build_dir/src/unittests /app/
COPY --from=build /rtl_airband_build/build_dir/src/rtl_airband /app/
RUN chmod a+x /app/unittests /app/rtl_airband

# make sure unit tests pass
RUN /app/unittests

# Use tini as init and run rtl_airband from /app/
ENTRYPOINT ["/usr/bin/tini", "--"]
WORKDIR /app/
CMD ["/app/rtl_airband", "-F", "-e", "-c", "/app/rtl_airband.conf"]


================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS


================================================
FILE: NEWS.md
================================================
# NEWS

This file will no longer be updated with each release, for changes between releases, see PRs merged to the repo

Version 5.0.0 (Jan 21, 2024):

* NOTE: Going forward a release tag will be automatically created on each merge to `main`, and changes will not be reflected in this file.  For changes between versions see the repo's [release history](https://github.com/rtl-airband/RTLSDR-Airband/releases).
* NOTE: Going forward PRs will be opened directly against `main` and the `unstable` branch will no longer be used.
* NOTE: This repo has significantly diverged from the original project [microtony/RTLSDR-Airband](https://github.com/microtony/RTLSDR-Airband) so it has been been detached (ie no longer a fork).

* Changes in this release, see [#444](https://github.com/rtl-airband/RTLSDR-Airband/pull/444):
  * build and publish docker containers
  * changes to supported `cmake` platforms:
    * depreciate `rpiv1`, `armv7-generic`, and `armv8-generic` and build platforms
    * change default build platform to `native`
    * rename `default` to `generic`
  * enable a series of compile warnings and cleanup code
  * remove `SSE` specific code - let the compiler "do the right thing"
  * remove some no longer supported windows `ifdef`'s
  * fix CTCSS bug that could miss a tone when multiple tones have the same power (happens with less accurate floating point operations, ie i386)

Version 4.2.0 (Oct 13, 2023):

* Changes in this release:
  * Add support for building with libshout v2.4.6, see [#382](https://github.com/rtl-airband/RTLSDR-Airband/pull/382) and [#422](https://github.com/rtl-airband/RTLSDR-Airband/pull/422)
  * Add error checking for lowpass <= highpass, see [#399](https://github.com/rtl-airband/RTLSDR-Airband/pull/399) and [#412](https://github.com/rtl-airband/RTLSDR-Airband/pull/412)
  * Remove limit on count of mixer inputs (thanks @cdknox), see [#408](https://github.com/rtl-airband/RTLSDR-Airband/pull/408)
  * Add `dated_subdirectories` config option for output files (thanks, @marcin-osowski), see [#413](https://github.com/rtl-airband/RTLSDR-Airband/pull/413)

Version 4.1.1 (May 1, 2023):

* Changes in this release:
  * Fix build issues when using VideoCore GPU, see [#378](https://github.com/rtl-airband/RTLSDR-Airband/pull/378)

Version 4.1.0 (April 23, 2023):

* Changes in this release:
  * Add `channel_dbfs_noise_level` and `channel_dbfs_signal_level` to the stats file, see [#355](https://github.com/rtl-airband/RTLSDR-Airband/pull/355)
  * Add squelch support for CTCSS, add `channel_ctcss_counter` and `channel_no_ctcss_counter` to the stats file, see [#368](https://github.com/rtl-airband/RTLSDR-Airband/pull/368)
  * Support `ampfactor` on a per-channel basis (in addition to mixer inputs), see [#369](https://github.com/rtl-airband/RTLSDR-Airband/pull/369)
  * Fix config error messages, see [#371](https://github.com/rtl-airband/RTLSDR-Airband/pull/371)
  * Multiple CI / workflow improvements, including:
    * Addition of Dockerfiles and shell scripts for multiple build environments
    * Addition of vscode devcontainer configuration
    * Addition of gtest, code refactoring, addition of unit tests, running unit tests on each pull request
    * Running more combinations of OSs, build types, and build options on each pull request

Version 4.0.3 (Jan 10, 2023):

* Changes in this release:
  * Add `channel_squelch_level` to stats file, see [#332](https://github.com/rtl-airband/RTLSDR-Airband/pull/332)
  * Support "default" values in lists for `squelch_snr_threshold` and `notch_q`,
   see [#334](https://github.com/rtl-airband/RTLSDR-Airband/pull/334)
  * Set cmake `ENABLE_EXPORTS` property, see [#339](https://github.com/rtl-airband/RTLSDR-Airband/pull/339)

* Other items to note:
  * Repo maintainer has changed, see [#342](https://github.com/rtl-airband/RTLSDR-Airband/discussions/342)
  * Repo URL has moved to https://github.com/rtl-airband/RTLSDR-Airband
  * Default branch / Top of Tree has been renamed to `main`

Version 4.0.2 (Dec 26, 2021):

* Added a new `PLATFORM` value `default` (which, as the name says, is the new
  default). It results in a portable binary without any architecture-specific
  optimizations. This also allows the program to be built with compilers that
  do not support `-march=native` option (notably Clang on Apple M1) (#303).

Version 4.0.1 (Nov 14, 2021):

* Fixed compilation error on RaspberryPi OS 11 (Bullseye)

Version 4.0.0 (Oct 19, 2021):

* RTLSDR-Airband is now built with CMake. Refer to the wiki for updated
  compilation instructions.
* When compiling the program, a new `PLATFORM` value `native` can now be
  specified. It enables `-march=native -mtune=native` compilation options. This
  causes the compiler to apply the most appropriate optimizations for the
  hardware on which the app is being built (thx @charlie-foxtrot).
* BACKWARDS-INCOMPATIBLE CHANGE: Signal level and noise level estimates
  displayed in the textual waterfalls are now expressed in dBFS (decibels
  related to the full scale of the analog-to-digital converter). The main
  benefit of the new approach is that these values do not depend on the
  `fft_size` value(thx @charlie-foxtrot).
* BACKWARDS-INCOMPATIBLE CHANGE: Improved squelch algorithm with new
  configuration parameters. `squelch` keyword has been replaced with
  `squelch_threshold` which takes an absolute signal value in dBFS as an
  argument. Alternatively, a minimum signal-to-noise ratio (in dB) that should
  trigger the squelch might be configured using `squelch_snr_threshold` option
  (thx @charlie-foxtrot).
* BACKWARDS-INCOMPATIBLE CHANGE: `include_freq` config option for file outputs
  now causes the frequency to be appended after the timestamp rather than
  before it. This feature now works correctly in scan mode, when
  `split_on_transmission` feature is enabled. (thx @charlie-foxtrot).
* BACKWARDS-INCOMPATIBLE CHANGE: sample format in files produced by `rawfile`
  outputs has been changed from CS16 to CF32. File name suffix is now `.cf32`.
* Improved squelch indicator in the textual waterfalls. In addition to the `*`
  character indicating that the squelch is open, there is also a `~` character
  indicating that the channel has a signal that is being suppressed because it
  is outside the band of the channel filter (thx @charlie-foxtrot).
* New output type `udp_stream` for sending uncompressed audio to another host
  via UDP/IP (thx @charlie-foxtrot).
* Added `multiple_output_threads` global option. When set to `true`, a separate
  output thread is spawned for each device (thx @charlie-foxtrot).
* Modulation in scan mode is now configurable per channel (thx
  @charlie-foxtrot).
* SoapySDR errors like TIMEOUT or OVERFLOW are no longer treated as fatal. They
  often appear intermittently, especially when the CPU usage is high. There is
  no point in failing the input in this case.
* Added `.tmp` suffix to the names of the output files currently being written
  to. The suffix is removed when the file is closed. External applications that
  consume recorded files can now figure out which files are not yet complete.
* Added logging and statistics for output thread overruns and mixer
  input/output overruns (thx @charlie-foxtrot).
* The program can now be built on MacOS.
* Miscellaneous bug fixes and code cleanups.

Version 3.2.1 (Nov 13, 2020):

* Fixed a compile error when using libshout older than 2.4.0

Version 3.2.0 (Nov 08, 2020):

* Added `split_on_transmission` output file option which allows creating
  a new file for every transmission on the channel (thx @charlie-foxtrot).
* Added `include_freq` output file option, which causes the channel frequency
  to be appended to the file name (thx @charlie-foxtrot).
* Added support for notch filters for eliminating narrowband interference,
  like CTCSS tones (thx @charlie-foxtrot).
* Added `bandwidth` channel option which causes the channelized I/Q signal
  to be lowpass-filtered before demodulation. This might help in situations
  where neighboring channels are closely spaced and interfere with the channel
  of interest. It also reduces the bandwidth of the resulting audio signal,
  and thus eliminates the high-frequency noise (thx @charlie-foxtrot).
* Added support for multithreaded demodulation. Each device can now have its
  own demodulation thread. This allows spreading the demodulation work across
  multiple CPU cores. Enable with `multiple_demod_threads` global option
  (thx @charlie-foxtrot).
* Added support for highpass/lowpass MP3 filters for mixers (thx @charlie-foxtrot)
* Added support for frequency usage statistics (thx @charlie-foxtrot).
* Workaround for Fitipower tuner problem of not honoring the first gain
  setting when the device is first used (thx @eshaz).
* Finalize the MP3 file properly before opening a new one (thx @jratke).
* Close the RTL device properly on program exit (thx @jratke).
* Updated the SoapySDR input driver to reflect changes in SoapySDR library API.
* Minor cleanups.

Version 3.1.0 (Jan 19, 2020):

* SoapySDR: added support for complex float 32-bit samples
* SoapySDR: allow using AGC if the device supports it. Gain setting for
  soapy devices is now optional - if it's not specified, the program will
  try to enable AGC.
* Use lowpass/highpass filters provided by LAME library to improve audio
  quality of MP3 streams. Filter cutoff frequencies may be configured per
  output, using `highpass` and `lowpass` config options. Credit: clydebarrow.
* Added `log_scan_activity` global config option. When set to `true`, a
  log message is written whenever a squelch opens on a scanned channel,
  effectively producing a channel activity log. Credit: clam-i-am.
* Improved squelch behaviour in some corner cases.
* Fix for incorrect naming of pulseaudio context. Name set in the config
  was not used as it should. Credit: Darryl Pogue.
* Don't fail when the configured gain value is negative. Some SDRs support
  this (eg. FC0012-based dongles).
* Fix a bug which in some cases could prevent the icecast output from
  reconnecting with the Icecast server after the connection has failed.

Version 3.0.1 (Feb 16, 2018):

* Fix for squelch staying constantly open when configured manually
  with NFM=off (#84)

Version 3.0.0 (Feb 10, 2018):

* Major overhaul of the SDR input code - now it's modular and
  hardware-agnostic (no longer tightly coupled with librtlsdr).
* Support for SoapySDR vendor-neutral SDR library - any SDR which has
  a plugin for SoapySDR shall now work in RTLSDR-Airband.
* Support for Mirics DVB-T dongles via libmirisdr-4 library.
* Support for RTLSDR is now optional and can be disabled at compilation
  stage.
* Removed the 8-channels-per-device limit in multichannel mode.
* Configurable per-device sampling rate.
* Configurable FFT size.
* Support for multibyte input samples.
* Support for rawfile outputs (ie. writing raw I/Q data from a
  narrowband channel to a file for processing with other programs,
  line GNUradio or csdr).
* INCOMPATIBLE CHANGE: removed `rtlsdr_buffers` global configuration
  option; buffer count can now be adjusted with a per-device
  "buffers" option.
* INCOMPATIBLE CHANGE: removed `syslog` global configuration option;
  syslog logging is now enabled by default, both in foreground and
  background mode. To force logging to standard error, use -e command
  line option.
* Added -F command line option for better cooperation with systemd.
  Runs the program in foreground, but without textual waterfalls.
  Together with -e it allows running rtl_airband as a service of type
  "simple" under systemd. Example rtl_airband.service file has been
  adjusted to reflect this change.
* Added `type` device configuration option. It sets the device type
  (ie. the input driver which shall be used to talk to the device).
  "rtlsdr" is assumed as a default type for backward compatibility.
  If RTLSDR support has been disabled at compilation stage, then
  there is no default type - it must be set manually, or the program
  will throw an error on startup.
* Frequencies in the config can now be expressed in Hz, kHz, MHz or GHz
  for improved readability.
* Lots of bugfixes.
* Rewritten documentation on [Github Wiki](https://github.com/rtl-airband/RTLSDR-Airband/wiki).

Version 2.4.0 (Oct 15, 2017):

* Support for PulseAudio output via new output type `pulse`. With this
  feature you can eg. play the sound via the soundcard of the Raspberry
  Pi you run RTLSDR-Airband on (you need to install and run pulseaudio
  daemon on it, though). Or you can stream the audio from a Pi located
  near the antenna (eg. in the attic) to speakers connected to the desktop
  PC you are sitting at, without launching a local Icecast server,
  as before. Because the audio stream is sent uncompressed, it is
  not recommended to run it across the Internet - jitter or packet loss
  will easily cause the audio to become choppy. However in a local network
  PulseAudio is a good choice. And it gives much lower latency as compared
  to Icecast (typically under 0.5 seconds). Thanks to Marcus Ströbel
  for the idea and initial implementation.
* Support for referring to RTL devices by their serial numbers in the
  config file. Instead of `index = <dongle_index>` parameter, use `serial =
  <dongle_serial_number>` to get consistent behavior across reboots
  and hardware reconfigurations.
* Set RTL gain to the nearest gain value supported by the device. This is
  required for E4000 tuners, which do not round the given gain value to
  the nearest supported setting, which causes the gain setting operation
  to fail.
* Improved squelch operation in scan mode. All squelch-related variables
  (noise floor, AGC coefficients, etc) are now calculated and stored
  separately for each scanned channel. Earlier their values were common
  to all channels, which caused squelch problems in case when noise floor
  varied considerably between channels. Thanks to @strix-technica.
* Added build target for FreeBSD on x86. Use `PLATFORM=x86-freebsd` to
  compile and `PLATFORM=x86-freebsd gmake install` to install. Thanks
  to @nyammy.
* Display squelch setting in waterfall in place of noise floor value when
  squelch is set manually.
* Bug fixes, performance improvements.
* Decluttered and more understandable documentation.

Version 2.3.0 (Jan 2, 2017):

* Added support for mixers. It is now possible to produce audio streams
  combined from several input channels. Both mono and stereo mixing is
  supported. Usage example is provided in config/mixers.conf. All
  mixer-related parameters are documented in config/reference.conf.
* Added build options for 64-bit ARM architectures, like Odroid C2.
  Please use PLATFORM=armv8-generic when compiling.
* Fixed a long-standing bug in RTL sample processing, which caused some
  samples to be processed twice. If you were annoyed by these regular
  clicks in NFM audio every 125 ms, they are now gone.
* Reduced CPU usage on x86
* Some code restructuring and cleanups
* Added several configuration file examples for typical real-life
  scenarios. They are placed in config/ subdirectory. rtl_airband.conf.example
  file has been moved to config/reference.conf. It is meant to be a reference
  for all supported config knobs together with their description. This is
  still an interim solution before some more readable and understandable
  documentation gets written.

Version 2.2.0 (Oct 8, 2016):

* Support for Icecast stream metadata updates in scanning mode. When enabled,
  every time the scanner stops on a channel, current frequency is written into
  Icecast song title, which in turn is displayed in the player. Alternatively,
  textual labels can be configured for each frequency. It is possible
  to configure the amount of delay between the stream and metadata updates to
  synchronize them with the audio. There are some caveats however - read
  comments in rtl_airband.conf.example for details.
* Added global option 'localtime'. When enabled, rtl_airband uses local time
  instead of UTC time for output file names. (Credit: ScanOC).
* Auto gain feature removed. RTL auto gain does not work well for narrowband
  channels. Most often it sets the gain too high which causes problems for
  auto squelch and audio bleeding between adjacent channels. Gain must be
  configured manually from now on.
* Dropped unmaintained Windows build.
* Reverted to power level calculation algorithm from version 2.0.2. The new
  algo didn't really do much to sensitivity, but introduced annoying clicks
  on squelch open/close.
* Improved DC offset estimator for AM mode. This one hardly ever clicks
  on squelch opening.
* Boosted AM audio volume.
* Reduced squelch flapping in NFM mode.

Version 2.1.0 (Aug 11, 2016):

* Narrowband FM demodulation support
* Automatic Frequency Control
* Append mode for recording (enabled by default)
* Dongles, channels and outputs can be individually enabled and disabled
  by a simple config flag (no need to comment out or delete large
  configuration sections)
* Use VBR for MP3 encoding
* Modified power level calculation algorithm (better sensitivity)
* Support for manual squelch setting
* Bug fixes

Version 2.0.2 (Mar 26, 2016):

* Fixed a problem with running three dongles or more, simultaneously

Version 2.0.1 (Jan 24, 2016):

* Fixed crash on output initialization

Version 2.0.0 (Dec 27, 2015):

* util/convert_cfg: can be used to convert old-style config.txt to the new format
* Syslog logging (enabled by default)
* Daemon mode
* Reworked makefiles, added install rule
* /dev/vcio is now used to access GPU on RPi; creating char_dev no longer necessary
* Startup scripts for Debian and Gentoo
* Support for auto gain setting
* Support for multiple outputs per channel
* Support for recording streams to local MP3 files
* Support for ARMv7-based platforms other than RPi (eg. Cubieboard)
* Updated documentation
* Numerous bugfixes and stability improvements

Version 1.0.0 (May 12, 2015):

* Linux x86/x86_64 support (Windows build is currently unmaintained and might not work)
* Raspberry Pi V2 support
* Bundled hello_fft code (v2.0)
* More robust interaction with Icecast servers
* Important stability fixes


================================================
FILE: README.md
================================================
# RTLSDR-Airband

![main](https://github.com/rtl-airband/RTLSDR-Airband/actions/workflows/ci_build.yml/badge.svg?branch=main)
![main](https://github.com/rtl-airband/RTLSDR-Airband/actions/workflows/platform_build.yml/badge.svg?branch=main)
![main](https://github.com/rtl-airband/RTLSDR-Airband/actions/workflows/build_docker_containers.yml/badge.svg?branch=main)
![main](https://github.com/rtl-airband/RTLSDR-Airband/actions/workflows/code_formatting.yml/badge.svg?branch=main)

Changes as of v5.1.0:
 - License is now GPLv2 [#503](https://github.com/rtl-airband/RTLSDR-Airband/discussions/503)

NOTE: Repo URL has moved to https://github.com/rtl-airband/RTLSDR-Airband see [#502](https://github.com/rtl-airband/RTLSDR-Airband/discussions/502) for info

Changes as of v5.0.0:
 - PRs will be opened directly against `main` and the `unstable` branch will no longer be used
 - Version tags will be automatically created on each merge to `main`
 - A release will be created on each `major` or `minor` version tag but not `minor` tags
 - Checking out `main` is recommended over using a release artifact to stay on the latest version
 - This repo has significantly diverged from the original project [microtony/RTLSDR-Airband](https://github.com/microtony/RTLSDR-Airband) so it has been been detached (ie no longer a fork).
 - Specific build support for `rpiv1`, `armv7-generic`, and `armv8-generic` have been deprecated for the new default `native`, see [#447](https://github.com/rtl-airband/RTLSDR-Airband/discussions/447)


## Overview

RTLSDR-Airband receives analog radio voice channels and produces
audio streams which can be routed to various outputs, such as online
streaming services like LiveATC.net. Originally the only SDR type
supported by the program was Realtek DVB-T dongle (hence the project's
name). However, thanks to SoapySDR vendor-neutral SDR library, other
radios are now supported as well.

## Documentation

User's manual is now on the [wiki](https://github.com/rtl-airband/RTLSDR-Airband/wiki).

## Credits and thanks

I hereby express my gratitude to everybody who helped with the development and testing
of RTLSDR-Airband. Special thanks go to:

* Dave Pascoe
* SDR Guru
* Marcus Ströbel
* strix-technica
* charlie-foxtrot

## License

Copyright (C) 2022-2025 charlie-foxtrot

Copyright (C) 2015-2022 Tomasz Lemiech <szpajder@gmail.com>

Based on original work by Wong Man Hang <microtony@gmail.com>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.

## Open Source Licenses of bundled code

### gpu_fft

BCM2835 "GPU_FFT" release 2.0
Copyright (c) 2014, Andrew Holme.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the
  names of its contributors may be used to endorse or promote products
  derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

### rtl-sdr

* Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>
* Copyright (C) 2015 by Kyle Keen <keenerd@gmail.com>
* GNU General Public License Version 2


================================================
FILE: config/basic_multichannel.conf
================================================
# This is a minimalistic configuration file for RTLSDR-Airband.
# Just a single RTL dongle with two AM channels in multichannel mode.
# Each channel is sent to a single Icecast output.
# Refer to https://github.com/rtl-airband/RTLSDR-Airband/wiki
# for description of keywords and config syntax.

devices:
({
  type = "rtlsdr";
  index = 0;
  gain = 25;
  centerfreq = 120.0;
  correction = 80;
  channels:
  (
    {
      freq = 119.5;
      outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "TWR.mp3";
          name = "Tower";
          genre = "ATC";
          username = "source";
          password = "mypassword";
        }
      );
    },
    {
      freq = 120.225;
      outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "GND.mp3";
          name = "Ground";
          genre = "ATC";
          description = "My local airport - ground feed";
          username = "source";
          password = "mypassword";
        }
      );
    }
  );
 }
);


================================================
FILE: config/basic_scanning.conf
================================================
# Scanning mode example
# Single dongle, three frequencies, output to Icecast server and to a file.
# Refer to https://github.com/rtl-airband/RTLSDR-Airband/wiki
# for description of keywords and config syntax.

devices:
({
  type = "rtlsdr";
  index = 0;
  gain = 25;
  correction = 80;
  mode = "scan";
  channels:
  (
    {
      freqs = ( 118.15, 124.7, 132.1 );
      labels = ( "Tower", "Ground", "Approach" );
      outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "stream.mp3";
          name = "Tower + Ground + Approach";
          genre = "ATC";
          description = "My local airport - aggregated feed";
          username = "source";
          password = "mypassword";
          send_scan_freq_tags = false;
        },
        {
          type = "file";
          directory = "/home/pi/recordings";
          filename_template = "TWR+GND+APP";
        }
      );
    }
  );
 }
);


================================================
FILE: config/big_mixer.conf
================================================
mixers: {
  big_mixer: {
    outputs: (
      {
        type = "file";
        directory = "./";
        filename_template = "big_mixer";
      }
    );
  }
};

devices:
({
  type = "rtlsdr";
  index = 0;
  gain = 25;
  centerfreq = 156.7375;
  channels:
  (
    {
      freq = 156.050;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.175;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.250;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.275;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.300;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.325;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.350;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.375;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.400;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.425;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.450;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.475;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.500;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.525;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.550;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.575;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.600;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.625;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.650;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.675;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.700;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.725;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.750;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.800;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.850;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.875;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.900;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.925;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.950;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 156.975;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.000;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.025;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.050;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.075;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.100;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.125;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.150;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.175;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.200;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.225;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.250;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.275;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.300;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.325;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.350;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.375;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.400;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    },
    {
      freq = 157.425;
      outputs: (
        {
          type = "mixer";
          name = "big_mixer";
        }
      );
    }
  )
});


================================================
FILE: config/mixers.conf
================================================
# This config file demonstrates the usage of mixers.
# First, two mixers are defined:
#
# - mixer1: sends the mixed stream to Icecast and saves it to a file
# - mixer2: sends the mixed stream to Icecast
#
# Two dongles are used, both in AM, multichannel mode:
#
# - dongle 1: 3 channels:
#   - channel 1 goes to mixer1 (center, volume decreased to 30%)
#   - channel 2 goes to mixer1 (full left)
#   - channel 3 goes to mixer2 (85% right)
#
# - dongle 2: 2 channels:
#   - channel 1 goes to mixer1 (full right)
#   - channel 2 goes to mixer2 (85% left, volume set to 200%)
#
# Refer to https://github.com/rtl-airband/RTLSDR-Airband/wiki
# for description of keywords and config syntax.
mixers: {
  mixer1: {
    outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "mixer1.mp3";
          name = "VOLMET + Approach + Director"
          genre = "ATC";
          username = "source";
          password = "mypassword";
        },
        {
          type = "file";
          directory = "/home/pi/recordings";
          filename_template = "mixer1";
        }
    );
  },
  mixer2: {
    outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "mixer2.mp3";
          name = "Ground + Delivery"
          genre = "ATC";
          username = "source";
          password = "mypassword";
        }
    );
  }
};

devices:
({
  type = "rtlsdr";
  index = 0;
  gain = 25;
  centerfreq = 121.2;
  correction = 81;
  channels:
  (
# VOLMET
    {
      freq = 120.875;
# VOLMET/ATIS/AWOS channels often transmit continuously.
# Auto squelch does not perform well in such cases, so it's best to set the
# squelch threshold manually. squelch_threshold defines an absolute signal
# level (in dBFS).
      squelch_threshold = -40;
      lowpass = 5;
      highpass = 5;
      outputs: (
        {
          type = "mixer";
          name = "mixer1";
          ampfactor = 0.3;
        }
      );
    },
# Approach
    {
      freq = 121.8;
      outputs: (
        {
          type = "mixer";
          name = "mixer1";
          balance = -1.0;
        }
      );
    },
# Director
    {
      freq = 121.925;
      outputs: (
        {
          type = "mixer";
          name = "mixer2";
          balance = 0.85;
        }
      );
    }
  );
 },
 {
  type = "rtlsdr";
  index = 1;
  gain = 33;
  centerfreq = 131.2;
  correction = 48;
  channels:
  (
# Ground
    {
      freq = 130.925;
# Another way of tweaking the squelch is to specify custom SNR threshold (in dB)
      squelch_snr_threshold = 5.0;
      outputs: (
        {
          type = "mixer";
          name = "mixer1";
          balance = 1.0;
        }
      );
    },
# Delivery
    {
      freq = 131.4;
      outputs: (
        {
          type = "mixer";
          name = "mixer2";
          balance = -0.85;
          ampfactor = 2.0;
        }
      );
    }
  );
 }
);


================================================
FILE: config/noaa.conf
================================================
fft_size = 1024;
localtime = true;
multiple_demod_threads = true;
multiple_output_threads = true;
devices:
(
  {
    type = "rtlsdr";
    index = 0;
    gain = 19.7;
    centerfreq = 162.48200;
    correction = 0;
    sample_rate = 2.40;
    channels:
    (
      {
        freq = 162.40000;
        label = "NOAA 162.400";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.400";
          }
        );
      },
      {
        freq = 162.42500;
        label = "NOAA 162.425";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.425";
          }
        );
      },
      {
        freq = 162.45000;
        label = "NOAA 162.450";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.450";
          }
        );
      },
      {
        freq = 162.47500;
        label = "NOAA 162.475";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.475";
          }
        );
      },
      {
        freq = 162.50000;
        label = "NOAA 162.500";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.500";
          }
        );
      },
      {
        freq = 162.52500;
        label = "NOAA 162.525";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.525";
          }
        );
      },
      {
        freq = 162.55000;
        label = "NOAA 162.550";
        modulation = "nfm";
        lowpass = -1;
        highpass = -1;
        bandwidth = 5000;
        ampfactor = 2.00;
        squelch_snr_threshold = 0.00;
        outputs:
        (
          {
            type = "file";
            directory = "/recordings";
            filename_template = "NOAA_162.550";
          }
        );
      }
    );
  }
);


================================================
FILE: config/two_dongles_multiple_outputs.conf
================================================
# Example configuration file for 2 dongles.
# First dongle - scanning mode, NFM modulation, three frequencies,
# output to Icecast stream, to a file and to PulseAudio server
# on a local network.
# Second dongle - multichannel mode, three channels:
#
# - channel 1: AM, goes to Icecast stream
# - channel 2: AM, goes to two Icecast streams
# - channel 3: NFM, goes to two files
#
# Dongles are specified with their serial numbers instead of
# indexes, because the latter can change when devices are
# reconnected into different USB ports.
#
# Refer to https://github.com/rtl-airband/RTLSDR-Airband/wiki
# for description of keywords and config syntax.

devices:
({
  type = "rtlsdr";
  serial = "777755221";
  gain = 25;
  correction = 80;
  mode = "scan";
  channels:
  (
    {
      modulation = "nfm";
      freqs = ( 152.1, 168.25, 168.375 );
      outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "utility.mp3";
          name = "Utility channels";
          username = "source";
          password = "mypassword";
        },
        {
          type = "file";
          directory = "/home/pi/recordings";
          filename_template = "utility";
        },
        {
          type = "pulse";
          server = "192.168.11.10";
          stream_name = "Utility channels";
          continuous = false;
        }
      );
    }
  );
 },
 {
  type = "rtlsdr";
  serial = "33433123";
  gain = 20;
  centerfreq = 118.5;
  correction = 43;
  mode = "multichannel";
  channels:
  (
    {
      freq = 118.15;
      outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "TWR.mp3";
          name = "Tower";
          genre = "ATC";
          username = "source";
          password = "mypassword";
        }
      );
    },
    {
      freq = 119.425;
      outputs: (
        {
          type = "icecast";
          server = "icecast.server.example.org";
          port = 8080;
          mountpoint = "ACC.mp3";
          name = "Radar";
          genre = "ATC";
          username = "source";
          password = "mypassword";
        },
        {
          type = "icecast";
          server = "other.server.example.org";
          port = 9999;
          mountpoint = "feed.mp3";
          username = "user";
          password = "secretpass";
        }
      );
    },
    {
      freq = 119.6;
      modulation = "nfm";
      outputs: (
        {
          type = "file";
          directory = "/home/pi/recordings";
          filename_template = "somechannel";
        },
        {
          type = "file";
          directory = "/home/pi/recordings";
          filename_template = "somechannel_full";
          continuous = true;
        }
      );
    }
  );
 }
);


================================================
FILE: init.d/rtl_airband-debian.sh
================================================
#! /bin/sh
### BEGIN INIT INFO
# Provides:          rtl_airband
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: rtl_airband initscript
### END INIT INFO

# Author: Tomasz Lemiech <szpajder@gmail.com>

PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
DESC="RTLSDR airband receiver"
NAME=rtl_airband
DAEMON=/usr/local/bin/$NAME
DAEMON_ARGS=""
PIDFILE=/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
	# Return
	#   0 if daemon has been started
	#   1 if daemon was already running
	#   2 if daemon could not be started
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
		|| return 1
	start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
		$DAEMON_ARGS \
		|| return 2
	# on this one.  As a last resort, sleep for some time.
}

do_stop()
{
	# Return
	#   0 if daemon has been stopped
	#   1 if daemon was already stopped
	#   2 if daemon could not be stopped
	#   other if a failure occurred
	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
	RETVAL="$?"
	[ "$RETVAL" = 2 ] && return 2
	# Wait for children to finish too if this is a daemon that forks
	# and if the daemon is only ever run from this initscript.
	# If the above conditions are not satisfied then add some other code
	# that waits for the process to drop all resources that could be
	# needed by services started subsequently.  A last resort is to
	# sleep for some time.
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
	[ "$?" = 2 ] && return 2
	rm -f $PIDFILE
	return "$RETVAL"
}

case "$1" in
  start)
	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
	do_start
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  status)
	status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
	;;
  restart|force-reload)
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
		# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
	exit 3
	;;
esac

:


================================================
FILE: init.d/rtl_airband-freebsd.sh
================================================
#!/bin/sh

# PROVIDE: rtl_airband
# REQUIRE: DAEMON
# BEFORE: LOGIN
# KEYWORD: nojail shutdown

. /etc/rc.subr

name=rtl_airband
rcvar=rtl_airband_enable

command="/usr/local/bin/rtl_airband"

load_rc_config ${name}
run_rc_command "$1"


================================================
FILE: init.d/rtl_airband-gentoo.sh
================================================
#!/sbin/runscript
# rtl_airband Gentoo startup script
# (c) 2015 Tomasz Lemiech <szpajder@gmail.com>

RTLAIRBAND_CONFDIR=${RTLAIRBAND_CONFDIR:-/usr/local/etc}
RTLAIRBAND_CONFIG=${RTLAIRBAND_CONFIG:-${RTLAIRBAND_CONFDIR}/rtl_airband.conf}
RTLAIRBAND_PIDFILE=${RTLAIRBAND_PIDFILE:-/run/${SVCNAME}.pid}
RTLAIRBAND_BINARY=${RTLAIRBAND_BINARY:-/usr/local/bin/rtl_airband}

depend() {
	use logger dns
}

checkconfig() {
	if [ ! -e "${RTLAIRBAND_CONFIG}" ] ; then
		eerror "You need an ${RTLAIRBAND_CONFIG} file to run rtl_airband"
		return 1
	fi
}

start() {
	checkconfig || return 1

	ebegin "Starting ${SVCNAME}"
	start-stop-daemon --start --exec "${RTLAIRBAND_BINARY}" \
	    --pidfile "${RTLAIRBAND_PIDFILE}" \
	    -- ${RTLAIRBAND_OPTS}
	eend $?
}

stop() {
	if [ "${RC_CMD}" = "restart" ] ; then
		checkconfig || return 1
	fi

	ebegin "Stopping ${SVCNAME}"
	start-stop-daemon --stop --exec "${RTLAIRBAND_BINARY}" \
	    --pidfile "${RTLAIRBAND_PIDFILE}" --quiet
	eend $?
}


================================================
FILE: init.d/rtl_airband.service
================================================
[Unit]
Description=SDR AM/NFM demodulator
Documentation=https://github.com/rtl-airband/RTLSDR-Airband/wiki
Wants=network.target  # NOTE: `network-online.target` may be better for some use cases
After=network.target  # NOTE: `network-online.target` may be better for some use cases

[Service]
Type=simple
ExecStart=/usr/local/bin/rtl_airband -Fe
# The program may exit only due to startup failure (eg. misconfiguration)
# or due to failure of all SDR devices (eg. disconnection). In either case,
# there is no point to restart it, because it would fail once again.
Restart=no

[Install]
WantedBy=multi-user.target


================================================
FILE: scripts/find_version
================================================
#!/bin/bash

PROJECT_ROOT_PATH="$(cd $(dirname "$0")/../ ; pwd)"
PROJECT_GIT_DIR_PATH="${PROJECT_ROOT_PATH}/.git"
PROJECT_DIR_NAME="$(basename ${PROJECT_ROOT_PATH})"

# if there is a .git directory at the project root then rely on git for the version string
if [ -r "${PROJECT_GIT_DIR_PATH}" ] ; then
    git describe --tags --abbrev --dirty --always
    exit 0
fi

# if the proejct root directory matches the naming convetion of an extracted archive then
# get the version number out of that
if [[ "${PROJECT_DIR_NAME}" =~ ^RTLSDR-Airband-[0-9]*\.[0-9]*\.[0-9]*$ ]]; then
    echo ${PROJECT_DIR_NAME} | cut -d '-' -f 3
    exit 0
fi

# print an error string to stderr (any output to stdout is considered success)
>&2 echo "did not find a git root directory at ${PROJECT_GIT_DIR_PATH} and failed to extract a version from ${PROJECT_DIR_NAME}"


================================================
FILE: scripts/reformat_code
================================================
#!/bin/bash

find src/*.h src/*.cpp src/hello_fft/*.h src/hello_fft/*.c | xargs clang-format-14 -i


================================================
FILE: src/.gitignore
================================================
config.h


================================================
FILE: src/CMakeLists.txt
================================================
include(CheckCXXCompilerFlag)
include(CheckCXXSymbolExists)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)

if(UNIX OR MINGW)
	add_definitions(-D_FILE_OFFSET_BITS=64)
	# isnormal()
	add_definitions(-D_POSIX_C_SOURCE=200112L)
endif()

CHECK_CXX_COMPILER_FLAG(-pthread CXX_HAS_PTHREAD)
if(CXX_HAS_PTHREAD)
    add_compile_options(-pthread)
endif()

CHECK_CXX_COMPILER_FLAG(-ffast-math CXX_HAS_FFAST_MATH)
if(CXX_HAS_FFAST_MATH)
    add_compile_options(-ffast-math)
endif()

# asprintf on MacOS
if(APPLE)
	add_definitions(-D_DARWIN_C_SOURCE)
endif()

# sincosf on linux vs __sincosf on MacOS
set(CMAKE_REQUIRED_DEFINITIONS_ORIG ${CMAKE_REQUIRED_DEFINITIONS})
list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE")
set(CMAKE_REQUIRED_LIBRARIES_ORIG ${CMAKE_REQUIRED_LIBRARIES})
list(APPEND CMAKE_REQUIRED_LIBRARIES m)
CHECK_SYMBOL_EXISTS(sincosf math.h HAVE_SINCOSF)
if(HAVE_SINCOSF)
	set(SINCOSF "sincosf")
else()
	CHECK_SYMBOL_EXISTS(__sincosf math.h HAVE___SINCOSF)
	if(HAVE___SINCOSF)
		set(SINCOSF "__sincosf")
	endif()
endif()
if(NOT HAVE_SINCOSF AND NOT HAVE___SINCOSF)
	message(FATAL_ERROR "Required function sincosf() is unavailable")
endif()
set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS_ORIG})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_ORIG})

find_library(LIBM m REQUIRED)
find_library(LIBDL dl REQUIRED)
find_library(LIBPTHREAD pthread REQUIRED)

find_package(PkgConfig REQUIRED)

pkg_check_modules(CONFIG REQUIRED libconfig++)
list(APPEND rtl_airband_extra_libs ${CONFIG_LIBRARIES})
list(APPEND rtl_airband_include_dirs ${CONFIG_INCLUDE_DIRS})
list(APPEND link_dirs ${CONFIG_LIBRARY_DIRS})

# Can't use pkg_check_modules here, as some distros do not install lame.pc file
find_package(Lame REQUIRED)
list(APPEND rtl_airband_extra_libs ${LAME_LIBRARIES})
list(APPEND rtl_airband_include_dirs ${LAME_INCLUDE_DIR})

pkg_check_modules(SHOUT REQUIRED shout)
list(APPEND rtl_airband_extra_libs ${SHOUT_LIBRARIES})
list(APPEND rtl_airband_include_dirs ${SHOUT_INCLUDE_DIRS})
list(APPEND link_dirs ${SHOUT_LIBRARY_DIRS})

set(CMAKE_REQUIRED_INCLUDES_SAVE ${CMAKE_REQUIRED_INCLUDES})
set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES})
set(CMAKE_REQUIRED_LINK_OPTIONS_SAVE ${CMAKE_REQUIRED_LINK_OPTIONS})
set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES} ${SHOUT_INCLUDE_DIRS}")
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${SHOUT_LIBRARIES}")
if ( NOT "${SHOUT_LIBRARY_DIRS}" STREQUAL "" )
	set(CMAKE_REQUIRED_LINK_OPTIONS "-L${SHOUT_LIBRARY_DIRS}")
endif()
set(LIBSHOUT_HEADER "shout/shout.h")
CHECK_CXX_SYMBOL_EXISTS("SHOUT_TLS_AUTO" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_TLS_AUTO)
CHECK_CXX_SYMBOL_EXISTS("SHOUT_TLS_AUTO_NO_PLAIN" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_TLS_AUTO_NO_PLAIN)
CHECK_CXX_SYMBOL_EXISTS("SHOUT_TLS_RFC2818" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_TLS_RFC2818)
CHECK_CXX_SYMBOL_EXISTS("SHOUT_TLS_RFC2817" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_TLS_RFC2817)
CHECK_CXX_SYMBOL_EXISTS("SHOUT_TLS_DISABLED" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_TLS_DISABLED)
CHECK_CXX_SYMBOL_EXISTS("shout_set_tls" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_SET_TLS)
CHECK_CXX_SYMBOL_EXISTS("shout_set_content_format" ${LIBSHOUT_HEADER}
	LIBSHOUT_HAS_CONTENT_FORMAT)

if(HAVE_SHOUT_TLS_AUTO AND HAVE_SHOUT_TLS_AUTO_NO_PLAIN AND
		HAVE_SHOUT_TLS_RFC2818 AND HAVE_SHOUT_TLS_RFC2817 AND
		HAVE_SHOUT_TLS_DISABLED AND HAVE_SHOUT_SET_TLS)
	set(LIBSHOUT_HAS_TLS TRUE)
else()
	set(LIBSHOUT_HAS_TLS FALSE)
endif()

# check for shout_set_metadata_utf8() - introduced in libshout v2.4.6
CHECK_CXX_SYMBOL_EXISTS("shout_set_metadata_utf8" ${LIBSHOUT_HEADER}
	HAVE_SHOUT_SET_METADATA_UTF8)
if(HAVE_SHOUT_SET_METADATA_UTF8)
	set(SHOUT_SET_METADATA "shout_set_metadata_utf8")
else()
	set(SHOUT_SET_METADATA "shout_set_metadata")
endif()

set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES_SAVE})
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_SAVE})
set(CMAKE_REQUIRED_LINK_OPTIONS ${CMAKE_REQUIRED_LINK_OPTIONS_SAVE})

option(NFM "Enable support for narrow FM channels" OFF)

set(PLATFORM "native" CACHE STRING "Optimize the build for the given hardware platform")

option(RTLSDR "Enable RTL-SDR support" ON)
set(WITH_RTLSDR FALSE)

option(MIRISDR "Enable Mirics support" ON)
set(WITH_MIRISDR FALSE)

option(SOAPYSDR "Enable SoapySDR support" ON)
set(WITH_SOAPYSDR FALSE)

option(PULSEAUDIO "Enable PulseAudio support" ON)
set(WITH_PULSEAUDIO FALSE)

option(PROFILING "Enable profiling with gperftools")
set(WITH_PROFILING FALSE)

if(RTLSDR)
	find_package(RTLSDR)
	if(RTLSDR_FOUND)
		list(APPEND rtl_airband_extra_sources input-rtlsdr.cpp)
		list(APPEND rtl_airband_extra_libs ${RTLSDR_LIBRARIES})
		list(APPEND rtl_airband_include_dirs ${RTLSDR_INCLUDE_DIRS})
		list(APPEND link_dirs ${RTLSDR_LIBRARY_DIRS})
		set(WITH_RTLSDR TRUE)
	endif()
endif()

if(MIRISDR)
	find_package(MiriSDR)
	if(MIRISDR_FOUND)
		set(WITH_MIRISDR TRUE)
		list(APPEND rtl_airband_extra_sources input-mirisdr.cpp)
		list(APPEND rtl_airband_extra_libs ${MIRISDR_LIBRARIES})
		list(APPEND rtl_airband_include_dirs ${MIRISDR_INCLUDE_DIRS})
		list(APPEND link_dirs ${MIRISDR_LIBRARY_DIRS})
	endif()
endif()

if(SOAPYSDR)
	message(STATUS "Checking for SoapySDR")
	find_package(SoapySDR NO_MODULE)
	if(SoapySDR_FOUND)
		list(APPEND rtl_airband_extra_sources input-soapysdr.cpp)
		message(STATUS "  SoapySDR found, ${SoapySDR_INCLUDE_DIRS}, ${SoapySDR_LIBRARIES}")
		list(APPEND rtl_airband_extra_libs ${SoapySDR_LIBRARIES})
		list(APPEND rtl_airband_include_dirs ${SoapySDR_INCLUDE_DIRS})
		set(WITH_SOAPYSDR TRUE)
	else()
		message(STATUS "  SoapySDR not found")
	endif()
endif()

if(PULSEAUDIO)
	pkg_check_modules(PULSEAUDIO libpulse)
	if(PULSEAUDIO_FOUND)
		list(APPEND rtl_airband_extra_sources pulse.cpp)
		list(APPEND rtl_airband_extra_libs ${PULSEAUDIO_LIBRARIES})
		list(APPEND rtl_airband_include_dirs ${PULSEAUDIO_INCLUDE_DIRS})
		list(APPEND link_dirs ${PULSEAUDIO_LIBRARY_DIRS})
		set(WITH_PULSEAUDIO TRUE)
	endif()
endif()

if(PROFILING)
	pkg_check_modules(PROFILING libprofiler)
	if(PROFILING_FOUND)
		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
		list(APPEND rtl_airband_extra_libs ${PROFILING_LIBRARIES})
		list(APPEND rtl_airband_include_dirs ${PROFILING_INCLUDE_DIRS})
		list(APPEND link_dirs ${PROFILING_LIBRARY_DIRS})
		set(WITH_PROFILING TRUE)
	endif()
endif()


option(BCM_VC "Enable Broadcom Videocore 3 support" OFF)
set(WITH_BCM_VC FALSE)

# error out on depricated PLATFORM values
if(PLATFORM STREQUAL "rpiv1" OR PLATFORM STREQUAL "armv7-generic" OR PLATFORM STREQUAL "armv8-generic")
	message(FATAL_ERROR "platform '${PLATFORM}' has been deprecated, see https://github.com/rtl-airband/RTLSDR-Airband/discussions/447")
# rpiv2 - Raspberry Pi 2 or Raspberry Pi 3 using Broadcom VideoCore IV GPU for FFT
# NOTE: use 'native' to not use the GPU for FFT
elseif(PLATFORM STREQUAL "rpiv2")
	set(BCM_VC ON)
	add_compile_options(-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard)
	enable_language(ASM)
	list(APPEND rtl_airband_extra_sources rtl_airband_neon.s)
# native - let the complier optimize to run on local hardware (default)
elseif(PLATFORM STREQUAL "native")
	CHECK_CXX_COMPILER_FLAG(-march=native CXX_HAS_MARCH_NATIVE)
	if(CXX_HAS_MARCH_NATIVE)
		add_compile_options(-march=native)
	else()
		message(FATAL_ERROR "Cannot build with PLATFORM=native: the compiler does not support -march=native option")
	endif()
# generic - dont add any hardware related flags, used to build a "portable" binary
elseif(PLATFORM STREQUAL "generic")
	# NO-OP
# error out on unrecongnnized PLATFORM value
else()
	message(FATAL_ERROR "Unknown platform '${PLATFORM}'. Valid options are: rpiv2, native, and generic")
endif()

# Try using VC GPU if enabled. Fallback to fftw3f if disabled or if VC lib not found
if(BCM_VC)
	find_package(BCM_VC)
	if(BCM_VC_FOUND)
		add_subdirectory(hello_fft)
		list(APPEND rtl_airband_obj_files $<TARGET_OBJECTS:hello_fft>)
		list(APPEND rtl_airband_extra_libs ${BCM_VC_LIBRARIES})
		set(WITH_BCM_VC TRUE)
	endif()
endif()
if(NOT BCM_VC_FOUND)
	pkg_check_modules(FFTW3F REQUIRED fftw3f)
	if(FFTW3F_FOUND)
		list(APPEND rtl_airband_extra_libs ${FFTW3F_LIBRARIES})
		list(APPEND rtl_airband_include_dirs ${FFTW3F_INCLUDE_DIRS})
		list(APPEND link_dirs ${FFTW3F_LIBRARY_DIRS})
	endif()
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
	list(APPEND rtl_airband_extra_libs c++)
endif()

if(BUILD_UNITTESTS)
	set(BUILD_UNITTESTS TRUE)
else()
	set(BUILD_UNITTESTS FALSE)
endif()

message(STATUS "RTLSDR-Airband configuration summary:\n")
message(STATUS "- Version string:\t\t${RTL_AIRBAND_VERSION}")
message(STATUS "- Build type:\t\t${CMAKE_BUILD_TYPE}")
message(STATUS "- Operating system:\t\t${CMAKE_SYSTEM_NAME}")
message(STATUS "- SDR drivers:")
message(STATUS "  - librtlsdr:\t\trequested: ${RTLSDR}, enabled: ${WITH_RTLSDR}")
message(STATUS "  - mirisdr:\t\t\trequested: ${MIRISDR}, enabled: ${WITH_MIRISDR}")
message(STATUS "  - soapysdr:\t\trequested: ${SOAPYSDR}, enabled: ${WITH_SOAPYSDR}")
message(STATUS "- Other options:")
message(STATUS "  - Platform:\t\t${PLATFORM}")
message(STATUS "  - Build Unit Tests:\t${BUILD_UNITTESTS}")
message(STATUS "  - Broadcom VideoCore GPU:\t${WITH_BCM_VC}")
message(STATUS "  - NFM support:\t\t${NFM}")
message(STATUS "  - PulseAudio:\t\trequested: ${PULSEAUDIO}, enabled: ${WITH_PULSEAUDIO}")
message(STATUS "  - Profiling:\t\trequested: ${PROFILING}, enabled: ${WITH_PROFILING}")
message(STATUS "  - Icecast TLS support:\t${LIBSHOUT_HAS_TLS}")

if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config.h)
	message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR}/config.h nolonger used, delete before continuing")
endif()

configure_file(
	"${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
	"${CMAKE_CURRENT_BINARY_DIR}/config.h"
	@ONLY
)

add_custom_command(
	OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.cpp
		${CMAKE_CURRENT_BINARY_DIR}/_version.cpp
	COMMAND ${CMAKE_COMMAND} -DRTL_AIRBAND_VERSION=${RTL_AIRBAND_VERSION} -P
	${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules/version.cmake
)

add_library (rtl_airband_base OBJECT
	config.cpp
	input-common.cpp
	input-file.cpp
	input-helpers.cpp
	mixer.cpp
	output.cpp
	rtl_airband.cpp
	squelch.cpp
	ctcss.cpp
	util.cpp
	udp_stream.cpp
	logging.cpp
	filters.cpp
	helper_functions.cpp
	${CMAKE_CURRENT_BINARY_DIR}/version.cpp
	${rtl_airband_extra_sources}
	)

target_include_directories (rtl_airband_base PUBLIC
	${CMAKE_CURRENT_BINARY_DIR} # needed for config.h
	${rtl_airband_include_dirs}
)

# can't do this per target with cmake <3.13
link_directories(${link_dirs})

list(APPEND rtl_airband_obj_files $<TARGET_OBJECTS:rtl_airband_base>)

add_executable (rtl_airband ${rtl_airband_obj_files})
set_property(TARGET rtl_airband PROPERTY ENABLE_EXPORTS 1)

# add include for config.h
target_include_directories (rtl_airband PUBLIC
	${CMAKE_CURRENT_BINARY_DIR}
)

target_link_libraries (rtl_airband
	dl
	m
	pthread
	${rtl_airband_extra_libs}
)

install(TARGETS rtl_airband
	RUNTIME DESTINATION bin
)

# TODO: install config if not present


if(BUILD_UNITTESTS)
	cmake_minimum_required(VERSION 3.1...3.18 FATAL_ERROR)

	# GoogleTest requires at least C++14
	set(CMAKE_CXX_STANDARD 14)

	# set timestamps of URL extracted files to the extraction time
	if(POLICY CMP0135)
		cmake_policy(SET CMP0135 NEW)
	endif()

	# pull in GoogleTest as a dependency
	include(FetchContent)
	FetchContent_Declare(
		googletest
		URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
	)
	FetchContent_MakeAvailable(googletest)

	enable_testing()

	file(GLOB_RECURSE TEST_FILES "test_*.cpp")
	list(APPEND TEST_FILES
		squelch.cpp
		logging.cpp
		filters.cpp
		ctcss.cpp
		generate_signal.cpp
		helper_functions.cpp
	)

	add_executable(
		unittests
		${TEST_FILES}
	)
	target_link_libraries(
		unittests
		GTest::gtest_main
		dl
		${rtl_airband_extra_libs}
	)

	# add include for config.h
	target_include_directories (unittests PUBLIC
		${CMAKE_CURRENT_BINARY_DIR}
	)

	include(GoogleTest)
	gtest_discover_tests(unittests)

endif()


================================================
FILE: src/CMakeModules/FindBCM_VC.cmake
================================================
if(NOT BCM_VC_FOUND)

	set(BCM_VC_PATH "/opt/vc" CACHE STRING "List of paths to search for Broadcom VideoCore library")

	find_path(BCM_VC_INCLUDE_DIR bcm_host.h PATHS ${BCM_VC_PATH}/include)
	find_library(BCM_VC_LIBRARY NAMES bcm_host PATHS ${BCM_VC_PATH}/lib)

	set(BCM_VC_LIBRARIES ${BCM_VC_LIBRARY} )
	set(BCM_VC_INCLUDE_DIRS ${BCM_VC_INCLUDE_DIR} )

	include(FindPackageHandleStandardArgs)
	# handle the QUIETLY and REQUIRED arguments and set BCM_VC_FOUND to TRUE
	# if all listed variables are TRUE
	find_package_handle_standard_args(BCM_VC DEFAULT_MSG
					  BCM_VC_LIBRARY BCM_VC_INCLUDE_DIR)

	mark_as_advanced(BCM_VC_INCLUDE_DIR BCM_VC_LIBRARY)

endif()


================================================
FILE: src/CMakeModules/FindLame.cmake
================================================
FIND_PATH(LAME_INCLUDE_DIR lame/lame.h)
FIND_LIBRARY(LAME_LIBRARIES NAMES mp3lame)

IF(LAME_INCLUDE_DIR AND LAME_LIBRARIES)
	SET(LAME_FOUND TRUE)
ENDIF(LAME_INCLUDE_DIR AND LAME_LIBRARIES)

IF(LAME_FOUND)
	IF (NOT Lame_FIND_QUIETLY)
		MESSAGE(STATUS "Found lame includes:	${LAME_INCLUDE_DIR}/lame/lame.h")
		MESSAGE(STATUS "Found lame library: ${LAME_LIBRARIES}")
	ENDIF (NOT Lame_FIND_QUIETLY)
ELSE(LAME_FOUND)
	IF (Lame_FIND_REQUIRED)
		MESSAGE(FATAL_ERROR "lame library required but not found")
	ENDIF (Lame_FIND_REQUIRED)
ENDIF(LAME_FOUND)


================================================
FILE: src/CMakeModules/FindMiriSDR.cmake
================================================
# - Try to find mirisdr - the hardware driver for Mirics chip in the dvb receivers
# Once done this will define
#  MIRISDR_FOUND - System has mirisdr
#  MIRISDR_LIBRARIES - The mirisdr libraries
#  MIRISDR_INCLUDE_DIRS - The mirisdr include directories
#  MIRISDR_LIB_DIRS - The mirisdr library directories

if(NOT MIRISDR_FOUND)

    find_package(PkgConfig)
    pkg_check_modules (MIRISDR_PKG libmirisdr)
    set(MIRISDR_DEFINITIONS ${PC_MIRISDR_CFLAGS_OTHER})

    find_path(MIRISDR_INCLUDE_DIR
                NAMES mirisdr.h
                HINTS ${MIRISDR_PKG_INCLUDE_DIRS} $ENV{MIRISDR_DIR}/include
                PATHS /usr/local/include /usr/include /opt/include /opt/local/include)

    find_library(MIRISDR_LIBRARY
                NAMES mirisdr
                HINTS ${MIRISDR_PKG_LIBRARY_DIRS} $ENV{MIRISDR_DIR}/include
                PATHS /usr/local/lib /usr/lib /opt/lib /opt/local/lib)

    set(MIRISDR_LIBRARIES ${MIRISDR_LIBRARY} )
    set(MIRISDR_INCLUDE_DIRS ${MIRISDR_INCLUDE_DIR} )

    include(FindPackageHandleStandardArgs)
    # handle the QUIETLY and REQUIRED arguments and set LibMIRISDR_FOUND to TRUE
    # if all listed variables are TRUE
    find_package_handle_standard_args(MiriSDR  DEFAULT_MSG
                                      MIRISDR_LIBRARY MIRISDR_INCLUDE_DIR)

    mark_as_advanced(MIRISDR_INCLUDE_DIR MIRISDR_LIBRARY)

endif(NOT MIRISDR_FOUND)


================================================
FILE: src/CMakeModules/FindRTLSDR.cmake
================================================
#
# Copyright 2012-2013 The Iris Project Developers. See the
# COPYRIGHT file at the top-level directory of this distribution
# and at http://www.softwareradiosystems.com/iris/copyright.html.
#
# This file is part of the Iris Project.
#
# Iris is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# Iris is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# A copy of the GNU Lesser General Public License can be found in
# the LICENSE file in the top-level directory of this distribution
# and at http://www.gnu.org/licenses/.
#

# - Try to find rtlsdr - the hardware driver for the realtek chip in the dvb receivers
# Once done this will define
#  RTLSDR_FOUND - System has rtlsdr
#  RTLSDR_LIBRARIES - The rtlsdr libraries
#  RTLSDR_INCLUDE_DIRS - The rtlsdr include directories
#  RTLSDR_LIB_DIRS - The rtlsdr library directories

if(NOT RTLSDR_FOUND)

    find_package(PkgConfig)
    pkg_check_modules (RTLSDR_PKG librtlsdr)
    set(RTLSDR_DEFINITIONS ${PC_RTLSDR_CFLAGS_OTHER})

    find_path(RTLSDR_INCLUDE_DIR
                NAMES rtl-sdr.h
                HINTS ${RTLSDR_PKG_INCLUDE_DIRS} $ENV{RTLSDR_DIR}/include
                PATHS /usr/local/include /usr/include /opt/include /opt/local/include)

    find_library(RTLSDR_LIBRARY
                NAMES rtlsdr
                HINTS ${RTLSDR_PKG_LIBRARY_DIRS} $ENV{RTLSDR_DIR}/include
                PATHS /usr/local/lib /usr/lib /opt/lib /opt/local/lib)

    set(RTLSDR_LIBRARIES ${RTLSDR_LIBRARY} )
    set(RTLSDR_INCLUDE_DIRS ${RTLSDR_INCLUDE_DIR} )

    include(FindPackageHandleStandardArgs)
    # handle the QUIETLY and REQUIRED arguments and set LibRTLSDR_FOUND to TRUE
    # if all listed variables are TRUE
    find_package_handle_standard_args(RTLSDR  DEFAULT_MSG
                                      RTLSDR_LIBRARY RTLSDR_INCLUDE_DIR)

    mark_as_advanced(RTLSDR_INCLUDE_DIR RTLSDR_LIBRARY)

endif(NOT RTLSDR_FOUND)


================================================
FILE: src/CMakeModules/version.cmake
================================================
set (VERSION "char const *RTL_AIRBAND_VERSION=\"${RTL_AIRBAND_VERSION}\";\n")

if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/version.cpp)
	file(READ ${CMAKE_CURRENT_BINARY_DIR}/version.cpp VERSION_)
else()
	set(VERSION_ "")
endif()

if (NOT "${VERSION}" STREQUAL "${VERSION_}")
	file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.cpp "${VERSION}")
endif()


================================================
FILE: src/config.cpp
================================================
/*
 * config.cpp
 * Configuration parsing routines
 *
 * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdint.h>  // uint32_t
#include <syslog.h>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <libconfig.h++>
#include "input-common.h"  // input_t
#include "rtl_airband.h"

using namespace std;

static int parse_outputs(libconfig::Setting& outs, channel_t* channel, int i, int j, bool parsing_mixers) {
    int oo = 0;
    for (int o = 0; o < channel->output_count; o++) {
        channel->outputs[oo].has_mp3_output = false;
        channel->outputs[oo].lame = NULL;
        channel->outputs[oo].lamebuf = NULL;

        if (outs[o].exists("disable") && (bool)outs[o]["disable"] == true) {
            continue;
        }
        if (!strncmp(outs[o]["type"], "icecast", 7)) {
            channel->outputs[oo].data = XCALLOC(1, sizeof(struct icecast_data));
            channel->outputs[oo].type = O_ICECAST;
            icecast_data* idata = (icecast_data*)(channel->outputs[oo].data);
            idata->hostname = strdup(outs[o]["server"]);
            idata->port = outs[o]["port"];
            idata->mountpoint = strdup(outs[o]["mountpoint"]);
            idata->username = strdup(outs[o]["username"]);
            idata->password = strdup(outs[o]["password"]);
            if (outs[o].exists("name"))
                idata->name = strdup(outs[o]["name"]);
            if (outs[o].exists("genre"))
                idata->genre = strdup(outs[o]["genre"]);
            if (outs[o].exists("description"))
                idata->description = strdup(outs[o]["description"]);
            if (outs[o].exists("send_scan_freq_tags"))
                idata->send_scan_freq_tags = (bool)outs[o]["send_scan_freq_tags"];
            else
                idata->send_scan_freq_tags = 0;
#ifdef LIBSHOUT_HAS_TLS
            if (outs[o].exists("tls")) {
                if (outs[o]["tls"].getType() == libconfig::Setting::TypeString) {
                    if (!strcmp(outs[o]["tls"], "auto")) {
                        idata->tls_mode = SHOUT_TLS_AUTO;
                    } else if (!strcmp(outs[o]["tls"], "auto_no_plain")) {
                        idata->tls_mode = SHOUT_TLS_AUTO_NO_PLAIN;
                    } else if (!strcmp(outs[o]["tls"], "transport")) {
                        idata->tls_mode = SHOUT_TLS_RFC2818;
                    } else if (!strcmp(outs[o]["tls"], "upgrade")) {
                        idata->tls_mode = SHOUT_TLS_RFC2817;
                    } else if (!strcmp(outs[o]["tls"], "disabled")) {
                        idata->tls_mode = SHOUT_TLS_DISABLED;
                    } else {
                        if (parsing_mixers) {
                            cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: ";
                        } else {
                            cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: ";
                        }
                        cerr << "invalid value for tls; must be one of: auto, auto_no_plain, transport, upgrade, disabled\n";
                        error();
                    }
                } else {
                    if (parsing_mixers) {
                        cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: ";
                    } else {
                        cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: ";
                    }
                    cerr << "tls value must be a string\n";
                    error();
                }
            } else {
                idata->tls_mode = SHOUT_TLS_DISABLED;
            }
#endif /* LIBSHOUT_HAS_TLS */

            channel->outputs[oo].has_mp3_output = true;
        } else if (!strncmp(outs[o]["type"], "file", 4)) {
            channel->outputs[oo].data = XCALLOC(1, sizeof(struct file_data));
            channel->outputs[oo].type = O_FILE;
            file_data* fdata = (file_data*)(channel->outputs[oo].data);

            fdata->type = O_FILE;
            if (!outs[o].exists("directory") || !outs[o].exists("filename_template")) {
                if (parsing_mixers) {
                    cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: ";
                } else {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: ";
                }
                cerr << "both directory and filename_template required for file\n";
                error();
            }
            fdata->basedir = outs[o]["directory"].c_str();
            fdata->basename = outs[o]["filename_template"].c_str();
            fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ? (bool)(outs[o]["dated_subdirectories"]) : false;
            fdata->suffix = ".mp3";

            fdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false;
            fdata->append = (!outs[o].exists("append")) || (bool)(outs[o]["append"]);
            fdata->split_on_transmission = outs[o].exists("split_on_transmission") ? (bool)(outs[o]["split_on_transmission"]) : false;
            fdata->include_freq = outs[o].exists("include_freq") ? (bool)(outs[o]["include_freq"]) : false;

            channel->outputs[oo].has_mp3_output = true;

            if (fdata->split_on_transmission) {
                if (parsing_mixers) {
                    cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: split_on_transmission is not allowed for mixers\n";
                    error();
                }
                if (fdata->continuous) {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: can't have both continuous and split_on_transmission\n";
                    error();
                }
            }

        } else if (!strncmp(outs[o]["type"], "rawfile", 7)) {
            if (parsing_mixers) {  // rawfile outputs not allowed for mixers
                cerr << "Configuration error: mixers.[" << i << "] outputs[" << o << "]: rawfile output is not allowed for mixers\n";
                error();
            }
            channel->outputs[oo].data = XCALLOC(1, sizeof(struct file_data));
            channel->outputs[oo].type = O_RAWFILE;
            file_data* fdata = (file_data*)(channel->outputs[oo].data);

            fdata->type = O_RAWFILE;
            if (!outs[o].exists("directory") || !outs[o].exists("filename_template")) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: both directory and filename_template required for file\n";
                error();
            }

            fdata->basedir = outs[o]["directory"].c_str();
            fdata->basename = outs[o]["filename_template"].c_str();
            fdata->dated_subdirectories = outs[o].exists("dated_subdirectories") ? (bool)(outs[o]["dated_subdirectories"]) : false;
            fdata->suffix = ".cf32";

            fdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false;
            fdata->append = (!outs[o].exists("append")) || (bool)(outs[o]["append"]);
            fdata->split_on_transmission = outs[o].exists("split_on_transmission") ? (bool)(outs[o]["split_on_transmission"]) : false;
            fdata->include_freq = outs[o].exists("include_freq") ? (bool)(outs[o]["include_freq"]) : false;
            channel->needs_raw_iq = channel->has_iq_outputs = 1;

            if (fdata->continuous && fdata->split_on_transmission) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: can't have both continuous and split_on_transmission\n";
                error();
            }
        } else if (!strncmp(outs[o]["type"], "mixer", 5)) {
            if (parsing_mixers) {  // mixer outputs not allowed for mixers
                cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: mixer output is not allowed for mixers\n";
                error();
            }
            channel->outputs[oo].data = XCALLOC(1, sizeof(struct mixer_data));
            channel->outputs[oo].type = O_MIXER;
            mixer_data* mdata = (mixer_data*)(channel->outputs[oo].data);
            const char* name = (const char*)outs[o]["name"];
            if ((mdata->mixer = getmixerbyname(name)) == NULL) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: unknown mixer \"" << name << "\"\n";
                error();
            }
            float ampfactor = outs[o].exists("ampfactor") ? (float)outs[o]["ampfactor"] : 1.0f;
            float balance = outs[o].exists("balance") ? (float)outs[o]["balance"] : 0.0f;
            if (balance < -1.0f || balance > 1.0f) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: balance out of allowed range <-1.0;1.0>\n";
                error();
            }
            if ((mdata->input = mixer_connect_input(mdata->mixer, ampfactor, balance)) < 0) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o
                     << "]: "
                        "could not connect to mixer "
                     << name << ": " << mixer_get_error() << "\n";
                error();
            }
            debug_print("dev[%d].chan[%d].out[%d] connected to mixer %s as input %d (ampfactor=%.1f balance=%.1f)\n", i, j, o, name, mdata->input, ampfactor, balance);
        } else if (!strncmp(outs[o]["type"], "udp_stream", 6)) {
            channel->outputs[oo].data = XCALLOC(1, sizeof(struct udp_stream_data));
            channel->outputs[oo].type = O_UDP_STREAM;

            udp_stream_data* sdata = (udp_stream_data*)channel->outputs[oo].data;

            sdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false;

            if (outs[o].exists("dest_address")) {
                sdata->dest_address = strdup(outs[o]["dest_address"]);
            } else {
                if (parsing_mixers) {
                    cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: ";
                } else {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: ";
                }
                cerr << "missing dest_address\n";
                error();
            }

            if (outs[o].exists("dest_port")) {
                if (outs[o]["dest_port"].getType() == libconfig::Setting::TypeInt) {
                    char buffer[12];
                    sprintf(buffer, "%d", (int)outs[o]["dest_port"]);
                    sdata->dest_port = strdup(buffer);
                } else {
                    sdata->dest_port = strdup(outs[o]["dest_port"]);
                }
            } else {
                if (parsing_mixers) {
                    cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: ";
                } else {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: ";
                }
                cerr << "missing dest_port\n";
                error();
            }
#ifdef WITH_PULSEAUDIO
        } else if (!strncmp(outs[o]["type"], "pulse", 5)) {
            channel->outputs[oo].data = XCALLOC(1, sizeof(struct pulse_data));
            channel->outputs[oo].type = O_PULSE;

            pulse_data* pdata = (pulse_data*)(channel->outputs[oo].data);
            pdata->continuous = outs[o].exists("continuous") ? (bool)(outs[o]["continuous"]) : false;
            pdata->server = outs[o].exists("server") ? strdup(outs[o]["server"]) : NULL;
            pdata->name = outs[o].exists("name") ? strdup(outs[o]["name"]) : "rtl_airband";
            pdata->sink = outs[o].exists("sink") ? strdup(outs[o]["sink"]) : NULL;

            if (outs[o].exists("stream_name")) {
                pdata->stream_name = strdup(outs[o]["stream_name"]);
            } else {
                if (parsing_mixers) {
                    cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: PulseAudio outputs of mixers must have stream_name defined\n";
                    error();
                }
                char buf[1024];
                snprintf(buf, sizeof(buf), "%.3f MHz", (float)channel->freqlist[0].frequency / 1000000.0f);
                pdata->stream_name = strdup(buf);
            }
#endif /* WITH_PULSEAUDIO */
        } else {
            if (parsing_mixers) {
                cerr << "Configuration error: mixers.[" << i << "] outputs.[" << o << "]: ";
            } else {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] outputs.[" << o << "]: ";
            }
            cerr << "unknown output type\n";
            error();
        }
        channel->outputs[oo].enabled = true;
        channel->outputs[oo].active = false;
        oo++;
    }
    return oo;
}

static struct freq_t* mk_freqlist(int n) {
    if (n < 1) {
        cerr << "mk_freqlist: invalid list length " << n << "\n";
        error();
    }
    struct freq_t* fl = (struct freq_t*)XCALLOC(n, sizeof(struct freq_t));
    for (int i = 0; i < n; i++) {
        fl[i].frequency = 0;
        fl[i].label = NULL;
        fl[i].agcavgfast = 0.5f;
        fl[i].ampfactor = 1.0f;
        fl[i].squelch = Squelch();
        fl[i].active_counter = 0;
        fl[i].modulation = MOD_AM;
    }
    return fl;
}

static void warn_if_freq_not_in_range(int devidx, int chanidx, int freq, int centerfreq, int sample_rate) {
    static const float soft_bw_threshold = 0.9f;
    float bw_limit = (float)sample_rate / 2.f * soft_bw_threshold;
    if ((float)abs(freq - centerfreq) >= bw_limit) {
        log(LOG_WARNING, "Warning: dev[%d].channel[%d]: frequency %.3f MHz is outside of SDR operating bandwidth (%.3f-%.3f MHz)\n", devidx, chanidx, (double)freq / 1e6,
            (double)(centerfreq - bw_limit) / 1e6, (double)(centerfreq + bw_limit) / 1e6);
    }
}

static int parse_anynum2int(libconfig::Setting& f) {
    int ret = 0;
    if (f.getType() == libconfig::Setting::TypeInt) {
        ret = (int)f;
    } else if (f.getType() == libconfig::Setting::TypeFloat) {
        ret = (int)((double)f * 1e6);
    } else if (f.getType() == libconfig::Setting::TypeString) {
        char* s = strdup((char const*)f);
        ret = (int)atofs(s);
        free(s);
    }
    return ret;
}

static int parse_channels(libconfig::Setting& chans, device_t* dev, int i) {
    int jj = 0;
    for (int j = 0; j < chans.getLength(); j++) {
        if (chans[j].exists("disable") && (bool)chans[j]["disable"] == true) {
            continue;
        }
        channel_t* channel = dev->channels + jj;
        for (int k = 0; k < AGC_EXTRA; k++) {
            channel->wavein[k] = 20;
            channel->waveout[k] = 0.5;
        }
        channel->axcindicate = NO_SIGNAL;
        channel->mode = MM_MONO;
        channel->freq_count = 1;
        channel->freq_idx = 0;
        channel->highpass = chans[j].exists("highpass") ? (int)chans[j]["highpass"] : 100;
        channel->lowpass = chans[j].exists("lowpass") ? (int)chans[j]["lowpass"] : 2500;
#ifdef NFM
        channel->pr = 0;
        channel->pj = 0;
        channel->prev_waveout = 0.5;
        channel->alpha = dev->alpha;
#endif /* NFM */

        // Make sure lowpass / highpass aren't flipped.
        // If lowpass is enabled (greater than zero) it must be larger than highpass
        if (channel->lowpass > 0 && channel->lowpass < channel->highpass) {
            cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: lowpass (" << channel->lowpass << ") must be greater than or equal to highpass (" << channel->highpass << ")\n";
            error();
        }

        modulations channel_modulation = MOD_AM;
        if (chans[j].exists("modulation")) {
#ifdef NFM
            if (strncmp(chans[j]["modulation"], "nfm", 3) == 0) {
                channel_modulation = MOD_NFM;
            } else
#endif /* NFM */
                if (strncmp(chans[j]["modulation"], "am", 2) != 0) {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: unknown modulation\n";
                    error();
                }
        }
        channel->afc = chans[j].exists("afc") ? (unsigned char)(unsigned int)chans[j]["afc"] : 0;
        if (dev->mode == R_MULTICHANNEL) {
            channel->freqlist = mk_freqlist(1);
            channel->freqlist[0].frequency = parse_anynum2int(chans[j]["freq"]);
            warn_if_freq_not_in_range(i, j, channel->freqlist[0].frequency, dev->input->centerfreq, dev->input->sample_rate);
            if (chans[j].exists("label")) {
                channel->freqlist[0].label = strdup(chans[j]["label"]);
            }
            channel->freqlist[0].modulation = channel_modulation;
        } else { /* R_SCAN */
            channel->freq_count = chans[j]["freqs"].getLength();
            if (channel->freq_count < 1) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: freqs should be a list with at least one element\n";
                error();
            }
            channel->freqlist = mk_freqlist(channel->freq_count);
            if (chans[j].exists("labels") && chans[j]["labels"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: labels should be a list with at least " << channel->freq_count << " elements\n";
                error();
            }
            if (chans[j].exists("squelch_threshold") && libconfig::Setting::TypeList == chans[j]["squelch_threshold"].getType() && chans[j]["squelch_threshold"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_threshold should be an int or a list of ints with at least " << channel->freq_count
                     << " elements\n";
                error();
            }
            if (chans[j].exists("squelch_snr_threshold") && libconfig::Setting::TypeList == chans[j]["squelch_snr_threshold"].getType() &&
                chans[j]["squelch_snr_threshold"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j
                     << "]: squelch_snr_threshold should be an int, a float or a list of "
                        "ints or floats with at least "
                     << channel->freq_count << " elements\n";
                error();
            }
            if (chans[j].exists("notch") && libconfig::Setting::TypeList == chans[j]["notch"].getType() && chans[j]["notch"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch should be an float or a list of floats with at least " << channel->freq_count << " elements\n";
                error();
            }
            if (chans[j].exists("notch_q") && libconfig::Setting::TypeList == chans[j]["notch_q"].getType() && chans[j]["notch_q"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch_q should be a float or a list of floats with at least " << channel->freq_count << " elements\n";
                error();
            }
            if (chans[j].exists("ctcss") && libconfig::Setting::TypeList == chans[j]["ctcss"].getType() && chans[j]["ctcss"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: ctcss should be an float or a list of floats with at least " << channel->freq_count << " elements\n";
                error();
            }
            if (chans[j].exists("modulation") && chans[j].exists("modulations")) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: can't set both modulation and modulations\n";
                error();
            }
            if (chans[j].exists("modulations") && chans[j]["modulations"].getLength() < channel->freq_count) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: modulations should be a list with at least " << channel->freq_count << " elements\n";
                error();
            }

            for (int f = 0; f < channel->freq_count; f++) {
                channel->freqlist[f].frequency = parse_anynum2int((chans[j]["freqs"][f]));
                if (chans[j].exists("labels")) {
                    channel->freqlist[f].label = strdup(chans[j]["labels"][f]);
                }
                if (chans[j].exists("modulations")) {
#ifdef NFM
                    if (strncmp(chans[j]["modulations"][f], "nfm", 3) == 0) {
                        channel->freqlist[f].modulation = MOD_NFM;
                    } else
#endif /* NFM */
                        if (strncmp(chans[j]["modulations"][f], "am", 2) == 0) {
                            channel->freqlist[f].modulation = MOD_AM;
                        } else {
                            cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] modulations.[" << f << "]: unknown modulation\n";
                            error();
                        }
                } else {
                    channel->freqlist[f].modulation = channel_modulation;
                }
            }
            // Set initial frequency for scanning
            // We tune 20 FFT bins higher to avoid DC spike
            dev->input->centerfreq = channel->freqlist[0].frequency + 20 * (double)(dev->input->sample_rate / fft_size);
        }
        if (chans[j].exists("squelch")) {
            cerr << "Warning: 'squelch' no longer supported and will be ignored, use 'squelch_threshold' or 'squelch_snr_threshold' instead\n";
        }
        if (chans[j].exists("squelch_threshold") && chans[j].exists("squelch_snr_threshold")) {
            cerr << "Warning: Both 'squelch_threshold' and 'squelch_snr_threshold' are set and may conflict\n";
        }
        if (chans[j].exists("squelch_threshold")) {
            // Value is dBFS, zero disables manual threshold (ie use auto squelch), negative is valid, positive is invalid
            if (libconfig::Setting::TypeList == chans[j]["squelch_threshold"].getType()) {
                // New-style array of per-frequency squelch settings
                for (int f = 0; f < channel->freq_count; f++) {
                    int threshold_dBFS = (int)chans[j]["squelch_threshold"][f];
                    if (threshold_dBFS > 0) {
                        cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_threshold must be less than or equal to 0\n";
                        error();
                    } else if (threshold_dBFS == 0) {
                        channel->freqlist[f].squelch.set_squelch_level_threshold(0);
                    } else {
                        channel->freqlist[f].squelch.set_squelch_level_threshold(dBFS_to_level(threshold_dBFS));
                    }
                }
            } else if (libconfig::Setting::TypeInt == chans[j]["squelch_threshold"].getType()) {
                // Legacy (single squelch for all frequencies)
                int threshold_dBFS = (int)chans[j]["squelch_threshold"];
                float level;
                if (threshold_dBFS > 0) {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_threshold must be less than or equal to 0\n";
                    error();
                } else if (threshold_dBFS == 0) {
                    level = 0;
                } else {
                    level = dBFS_to_level(threshold_dBFS);
                }

                for (int f = 0; f < channel->freq_count; f++) {
                    channel->freqlist[f].squelch.set_squelch_level_threshold(level);
                }
            } else {
                cerr << "Invalid value for squelch_threshold (should be int or list - use parentheses)\n";
                error();
            }
        }
        if (chans[j].exists("squelch_snr_threshold")) {
            // Value is SNR in dB, zero disables squelch (ie always open), -1 uses default value, positive is valid, other negative values are invalid
            if (libconfig::Setting::TypeList == chans[j]["squelch_snr_threshold"].getType()) {
                // New-style array of per-frequency squelch settings
                for (int f = 0; f < channel->freq_count; f++) {
                    float snr = 0.f;
                    if (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"][f].getType()) {
                        snr = (float)chans[j]["squelch_snr_threshold"][f];
                    } else if (libconfig::Setting::TypeInt == chans[j]["squelch_snr_threshold"][f].getType()) {
                        snr = (int)chans[j]["squelch_snr_threshold"][f];
                    } else {
                        cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_snr_threshold list must be of int or float\n";
                        error();
                    }

                    if (snr == -1.0) {
                        continue;  // "disable" for this channel in list
                    } else if (snr < 0) {
                        cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_snr_threshold must be greater than or equal to 0\n";
                        error();
                    } else {
                        channel->freqlist[f].squelch.set_squelch_snr_threshold(snr);
                    }
                }
            } else if (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"].getType() || libconfig::Setting::TypeInt == chans[j]["squelch_snr_threshold"].getType()) {
                // Legacy (single squelch for all frequencies)
                float snr = (libconfig::Setting::TypeFloat == chans[j]["squelch_snr_threshold"].getType()) ? (float)chans[j]["squelch_snr_threshold"] : (int)chans[j]["squelch_snr_threshold"];

                if (snr == -1.0) {
                    continue;  // "disable" so use the default without error message
                } else if (snr < 0) {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: squelch_snr_threshold must be greater than or equal to 0\n";
                    error();
                }

                for (int f = 0; f < channel->freq_count; f++) {
                    channel->freqlist[f].squelch.set_squelch_snr_threshold(snr);
                }
            } else {
                cerr << "Invalid value for squelch_snr_threshold (should be float, int, or list of int/float - use parentheses)\n";
                error();
            }
        }
        if (chans[j].exists("notch")) {
            static const float default_q = 10.0;

            if (chans[j].exists("notch_q") && chans[j]["notch"].getType() != chans[j]["notch_q"].getType()) {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch_q (if set) must be the same type as notch - "
                     << "float or a list of floats with at least " << channel->freq_count << " elements\n";
                error();
            }
            if (libconfig::Setting::TypeList == chans[j]["notch"].getType()) {
                for (int f = 0; f < channel->freq_count; f++) {
                    float freq = (float)chans[j]["notch"][f];
                    float q = chans[j].exists("notch_q") ? (float)chans[j]["notch_q"][f] : default_q;

                    if (q == 0.0) {
                        q = default_q;
                    } else if (q <= 0.0) {
                        cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: invalid value for notch_q: " << q << " (must be greater than 0.0)\n";
                        error();
                    }

                    if (freq == 0) {
                        continue;  // "disable" for this channel in list
                    } else if (freq < 0) {
                        cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: invalid value for notch: " << freq << ", ignoring\n";
                    } else {
                        channel->freqlist[f].notch_filter = NotchFilter(freq, WAVE_RATE, q);
                    }
                }
            } else if (libconfig::Setting::TypeFloat == chans[j]["notch"].getType()) {
                float freq = (float)chans[j]["notch"];
                float q = chans[j].exists("notch_q") ? (float)chans[j]["notch_q"] : default_q;
                if (q <= 0.0) {
                    cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: invalid value for notch_q: " << q << " (must be greater than 0.0)\n";
                    error();
                }
                for (int f = 0; f < channel->freq_count; f++) {
                    if (freq == 0) {
                        continue;  // "disable" is default so ignore without error message
                    } else if (freq < 0) {
                        cerr << "devices.[" << i << "] channels.[" << j << "]: notch value '" << freq << "' invalid, ignoring\n";
                    } else {
                        channel->freqlist[f].notch_filter = NotchFilter(freq, WAVE_RATE, q);
                    }
                }
            } else {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: notch should be an float or a list of floats with at least " << channel->freq_count << " elements\n";
                error();
            }
        }
        if (chans[j].exists("ctcss")) {
            if (libconfig::Setting::TypeList == chans[j]["ctcss"].getType()) {
                for (int f = 0; f < channel->freq_count; f++) {
                    float freq = (float)chans[j]["ctcss"][f];

                    if (freq == 0) {
                        continue;  // "disable" for this channel in list
                    } else if (freq < 0) {
                        cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: invalid value for ctcss: " << freq << ", ignoring\n";
                    } else {
                        channel->freqlist[f].squelch.set_ctcss_freq(freq, WAVE_RATE);
                    }
                }
            } else if (libconfig::Setting::TypeFloat == chans[j]["ctcss"].getType()) {
                float freq = (float)chans[j]["ctcss"];
                for (int f = 0; f < channel->freq_count; f++) {
                    if (freq <= 0) {
                        cerr << "devices.[" << i << "] channels.[" << j << "]: ctcss value '" << freq << "' invalid, ignoring\n";
                    } else {
                        channel->freqlist[f].squelch.set_ctcss_freq(freq, WAVE_RATE);
                    }
                }
            } else {
                cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: ctcss should be an float or a list of floats with at least " << channel->freq_count << " elements\n";
                error();
            }
        }
        if (chans[j].exists("bandwidth")) {
            channel->needs_raw_iq = 1;

            if (libconfig::Setting::TypeList == chans[j]["bandwidth"].getType()) {
                for (int f = 0; f < channel->freq_count; f++) {
                    int bandwidth = parse_anynum2int(chans[j]["bandwidth"][f]);

                    if (bandwidth == 0) {
                        continue;  // "disable" for this channel in list
                    } else if (bandwidth < 0) {
                        cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: bandwidth value '" << bandwidth << "' invalid, ignoring\n";
                    } else {
                        channel->freqlist[f].lowpass_filter = LowpassFilter((float)bandwidth / 2, WAVE_RATE);
                    }
                }
            } else {
                int bandwidth = parse_anynum2int(chans[j]["bandwidth"]);
                if (bandwidth == 0) {
                    continue;  // "disable" is default so ignore without error message
                } else if (bandwidth < 0) {
                    cerr << "devices.[" << i << "] channels.[" << j << "]: bandwidth value '" << bandwidth << "' invalid, ignoring\n";
                } else {
                    for (int f = 0; f < channel->freq_count; f++) {
                        channel->freqlist[f].lowpass_filter = LowpassFilter((float)bandwidth / 2, WAVE_RATE);
                    }
                }
            }
        }
        if (chans[j].exists("ampfactor")) {
            if (libconfig::Setting::TypeList == chans[j]["ampfactor"].getType()) {
                for (int f = 0; f < channel->freq_count; f++) {
                    float ampfactor = (float)chans[j]["ampfactor"][f];

                    if (ampfactor < 0) {
                        cerr << "devices.[" << i << "] channels.[" << j << "] freq.[" << f << "]: ampfactor '" << ampfactor << "' must not be negative\n";
                        error();
                    }

                    channel->freqlist[f].ampfactor = ampfactor;
                }
            } else {
                float ampfactor = (float)chans[j]["ampfactor"];

                if (ampfactor < 0) {
                    cerr << "devices.[" << i << "] channels.[" << j << "]: ampfactor '" << ampfactor << "' must not be negative\n";
                    error();
                }

                for (int f = 0; f < channel->freq_count; f++) {
                    channel->freqlist[f].ampfactor = ampfactor;
                }
            }
        }

#ifdef NFM
        if (chans[j].exists("tau")) {
            channel->alpha = ((int)chans[j]["tau"] == 0 ? 0.0f : exp(-1.0f / (WAVE_RATE * 1e-6 * (int)chans[j]["tau"])));
        }
#endif /* NFM */
        libconfig::Setting& outputs = chans[j]["outputs"];
        channel->output_count = outputs.getLength();
        if (channel->output_count < 1) {
            cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: no outputs defined\n";
            error();
        }
        channel->outputs = (output_t*)XCALLOC(channel->output_count, sizeof(struct output_t));
        int outputs_enabled = parse_outputs(outputs, channel, i, j, false);
        if (outputs_enabled < 1) {
            cerr << "Configuration error: devices.[" << i << "] channels.[" << j << "]: no outputs defined\n";
            error();
        }
        channel->outputs = (output_t*)XREALLOC(channel->outputs, outputs_enabled * sizeof(struct output_t));
        channel->output_count = outputs_enabled;

        dev->base_bins[jj] = dev->bins[jj] =
            (size_t)ceil((channel->freqlist[0].frequency + dev->input->sample_rate - dev->input->centerfreq) / (double)(dev->input->sample_rate / fft_size) - 1.0) % fft_size;
        debug_print("bins[%d]: %zu\n", jj, dev->bins[jj]);

#ifdef NFM
        for (int f = 0; f < channel->freq_count; f++) {
            if (channel->freqlist[f].modulation == MOD_NFM) {
                channel->needs_raw_iq = 1;
                break;
            }
        }
#endif /* NFM */

        if (channel->needs_raw_iq) {
            // Downmixing is done only for NFM and raw IQ outputs. It's not critical to have some residual
            // freq offset in AM, as it doesn't affect sound quality significantly.
            double dm_dphi = (double)(channel->freqlist[0].frequency - dev->input->centerfreq);  // downmix freq in Hz

            // In general, sample_rate is not required to be an integer multiple of WAVE_RATE.
            // However the FFT window may only slide by an integer number of input samples. A non-zero rounding error
            // introduces additional phase rotation which we have to compensate in order to shift the channel of interest
            // to the center of the spectrum of the output I/Q stream. This is important for correct NFM demodulation.
            // The error value (in Hz):
            // - has an absolute value 0..WAVE_RATE/2
            // - is linear with the error introduced by rounding the value of sample_rate/WAVE_RATE to the nearest integer
            //   (range of -0.5..0.5)
            // - is linear with the distance between center frequency and the channel frequency, normalized to 0..1
            double decimation_factor = ((double)dev->input->sample_rate / (double)WAVE_RATE);
            double dm_dphi_correction = (double)WAVE_RATE / 2.0;
            dm_dphi_correction *= (decimation_factor - round(decimation_factor));
            dm_dphi_correction *= (double)(channel->freqlist[0].frequency - dev->input->centerfreq) / ((double)dev->input->sample_rate / 2.0);

            debug_print("dev[%d].chan[%d]: dm_dphi: %f Hz dm_dphi_correction: %f Hz\n", i, jj, dm_dphi, dm_dphi_correction);
            dm_dphi -= dm_dphi_correction;
            debug_print("dev[%d].chan[%d]: dm_dphi_corrected: %f Hz\n", i, jj, dm_dphi);
            // Normalize
            dm_dphi /= (double)WAVE_RATE;
            // Unalias it, to prevent overflow of int during cast
            dm_dphi -= trunc(dm_dphi);
            debug_print("dev[%d].chan[%d]: dm_dphi_normalized=%f\n", i, jj, dm_dphi);
            // Translate this to uint32_t range 0x00000000-0x00ffffff
            dm_dphi *= 256.0 * 65536.0;
            // Cast it to signed int first, because casting negative float to uint is not portable
            channel->dm_dphi = (uint32_t)((int)dm_dphi);
            debug_print("dev[%d].chan[%d]: dm_dphi_scaled=%f cast=0x%x\n", i, jj, dm_dphi, channel->dm_dphi);
            channel->dm_phi = 0.f;
        }

#ifdef DEBUG_SQUELCH
        // Setup squelch debug file, if enabled
        char tmp_filepath[1024];
        for (int f = 0; f < channel->freq_count; f++) {
            snprintf(tmp_filepath, sizeof(tmp_filepath), "./squelch_debug-%d-%d.dat", j, f);
            channel->freqlist[f].squelch.set_debug_file(tmp_filepath);
        }
#endif /* DEBUG_SQUELCH */

        jj++;
    }
    return jj;
}

int parse_devices(libconfig::Setting& devs) {
    int devcnt = 0;
    for (int i = 0; i < devs.getLength(); i++) {
        if (devs[i].exists("disable") && (bool)devs[i]["disable"] == true)
            continue;
        device_t* dev = devices + devcnt;
        if (devs[i].exists("type")) {
            dev->input = input_new(devs[i]["type"]);
            if (dev->input == NULL) {
                cerr << "Configuration error: devices.[" << i << "]: unsupported device type\n";
                error();
            }
        } else {
#ifdef WITH_RTLSDR
            cerr << "Warning: devices.[" << i << "]: assuming device type \"rtlsdr\", please set \"type\" in the device section.\n";
            dev->input = input_new("rtlsdr");
#else
            cerr << "Configuration error: devices.[" << i << "]: mandatory parameter missing: type\n";
            error();
#endif /* WITH_RTLSDR */
        }
        assert(dev->input != NULL);
        if (devs[i].exists("sample_rate")) {
            int sample_rate = parse_anynum2int(devs[i]["sample_rate"]);
            if (sample_rate < WAVE_RATE) {
                cerr << "Configuration error: devices.[" << i << "]: sample_rate must be greater than " << WAVE_RATE << "\n";
                error();
            }
            dev->input->sample_rate = sample_rate;
        }
        if (devs[i].exists("mode")) {
            if (!strncmp(devs[i]["mode"], "multichannel", 12)) {
                dev->mode = R_MULTICHANNEL;
            } else if (!strncmp(devs[i]["mode"], "scan", 4)) {
                dev->mode = R_SCAN;
            } else {
                cerr << "Configuration error: devices.[" << i << "]: invalid mode (must be one of: \"scan\", \"multichannel\")\n";
                error();
            }
        } else {
            dev->mode = R_MULTICHANNEL;
        }
        if (dev->mode == R_MULTICHANNEL) {
            dev->input->centerfreq = parse_anynum2int(devs[i]["centerfreq"]);
        }  // centerfreq for R_SCAN will be set by parse_channels() after frequency list has been read
#ifdef NFM
        if (devs[i].exists("tau")) {
            dev->alpha = ((int)devs[i]["tau"] == 0 ? 0.0f : exp(-1.0f / (WAVE_RATE * 1e-6 * (int)devs[i]["tau"])));
        } else {
            dev->alpha = alpha;
        }
#endif /* NFM */

        // Parse hardware-dependent configuration parameters
        if (input_parse_config(dev->input, devs[i]) < 0) {
            // FIXME: get and display error string from input_parse_config
            // Right now it exits the program on failure.
        }
        // Some basic sanity checks for crucial parameters which have to be set
        // (or can be modified) by the input driver
        assert(dev->input->sfmt != SFMT_UNDEF);
        assert(dev->input->fullscale > 0);
        assert(dev->input->bytes_per_sample > 0);
        assert(dev->input->sample_rate > WAVE_RATE);

        // For the input buffer size use a base value and round it up to the nearest multiple
        // of FFT_BATCH blocks of input samples.
        // ceil is required here because sample rate is not guaranteed to be an integer multiple of WAVE_RATE.
        size_t fft_batch_len = FFT_BATCH * (2 * dev->input->bytes_per_sample * (size_t)ceil((double)dev->input->sample_rate / (double)WAVE_RATE));
        dev->input->buf_size = MIN_BUF_SIZE;
        if (dev->input->buf_size % fft_batch_len != 0)
            dev->input->buf_size += fft_batch_len - dev->input->buf_size % fft_batch_len;
        debug_print("dev->input->buf_size: %zu\n", dev->input->buf_size);
        dev->input->buffer = (unsigned char*)XCALLOC(sizeof(unsigned char), dev->input->buf_size + 2 * dev->input->bytes_per_sample * fft_size);
        dev->input->bufs = dev->input->bufe = 0;
        dev->input->overflow_count = 0;
        dev->output_overrun_count = 0;
        dev->waveend = dev->waveavail = dev->row = dev->tq_head = dev->tq_tail = 0;
        dev->last_frequency = -1;

        libconfig::Setting& chans = devs[i]["channels"];
        if (chans.getLength() < 1) {
            cerr << "Configuration error: devices.[" << i << "]: no channels configured\n";
            error();
        }
        dev->channels = (channel_t*)XCALLOC(chans.getLength(), sizeof(channel_t));
        dev->bins = (size_t*)XCALLOC(chans.getLength(), sizeof(size_t));
        dev->base_bins = (size_t*)XCALLOC(chans.getLength(), sizeof(size_t));
        dev->channel_count = 0;
        int channel_count = parse_channels(chans, dev, i);
        if (channel_count < 1) {
            cerr << "Configuration error: devices.[" << i << "]: no channels enabled\n";
            error();
        }
        if (dev->mode == R_SCAN && channel_count > 1) {
            cerr << "Configuration error: devices.[" << i << "]: only one channel is allowed in scan mode\n";
            error();
        }
        dev->channels = (channel_t*)XREALLOC(dev->channels, channel_count * sizeof(channel_t));
        dev->bins = (size_t*)XREALLOC(dev->bins, channel_count * sizeof(size_t));
        dev->base_bins = (size_t*)XREALLOC(dev->base_bins, channel_count * sizeof(size_t));
        dev->channel_count = channel_count;
        devcnt++;
    }
    return devcnt;
}

int parse_mixers(libconfig::Setting& mx) {
    const char* name;
    int mm = 0;
    for (int i = 0; i < mx.getLength(); i++) {
        if (mx[i].exists("disable") && (bool)mx[i]["disable"] == true)
            continue;
        if ((name = mx[i].getName()) == NULL) {
            cerr << "Configuration error: mixers.[" << i << "]: undefined mixer name\n";
            error();
        }
        debug_print("mm=%d name=%s\n", mm, name);
        mixer_t* mixer = &mixers[mm];
        mixer->name = strdup(name);
        mixer->enabled = false;
        mixer->interval = MIX_DIVISOR;
        mixer->output_overrun_count = 0;
        mixer->input_count = 0;
        mixer->inputs = NULL;
        mixer->inputs_todo = NULL;
        mixer->input_mask = NULL;
        channel_t* channel = &mixer->channel;
        channel->highpass = mx[i].exists("highpass") ? (int)mx[i]["highpass"] : 100;
        channel->lowpass = mx[i].exists("lowpass") ? (int)mx[i]["lowpass"] : 2500;
        channel->mode = MM_MONO;

        // Make sure lowpass / highpass aren't flipped.
        // If lowpass is enabled (greater than zero) it must be larger than highpass
        if (channel->lowpass > 0 && channel->lowpass < channel->highpass) {
            cerr << "Configuration error: mixers.[" << i << "]: lowpass (" << channel->lowpass << ") must be greater than or equal to highpass (" << channel->highpass << ")\n";
            error();
        }

        libconfig::Setting& outputs = mx[i]["outputs"];
        channel->output_count = outputs.getLength();
        if (channel->output_count < 1) {
            cerr << "Configuration error: mixers.[" << i << "]: no outputs defined\n";
            error();
        }
        channel->outputs = (output_t*)XCALLOC(channel->output_count, sizeof(struct output_t));
        int outputs_enabled = parse_outputs(outputs, channel, i, 0, true);
        if (outputs_enabled < 1) {
            cerr << "Configuration error: mixers.[" << i << "]: no outputs defined\n";
            error();
        }
        channel->outputs = (output_t*)XREALLOC(channel->outputs, outputs_enabled * sizeof(struct output_t));
        channel->output_count = outputs_enabled;
        mm++;
    }
    return mm;
}

// vim: ts=4


================================================
FILE: src/config.h.in
================================================
/*
 * config.h.in
 * Template for cmake-generated config.h
 *
 * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#ifndef _CONFIG_H
#define _CONFIG_H
#cmakedefine WITH_RTLSDR
#cmakedefine WITH_MIRISDR
#cmakedefine WITH_SOAPYSDR
#cmakedefine WITH_PROFILING
#cmakedefine WITH_PULSEAUDIO
#cmakedefine NFM
#cmakedefine WITH_BCM_VC
#cmakedefine LIBSHOUT_HAS_TLS
#cmakedefine LIBSHOUT_HAS_CONTENT_FORMAT
#define SINCOSF @SINCOSF@

#define SHOUT_SET_METADATA @SHOUT_SET_METADATA@

#endif /* _CONFIG_H */


================================================
FILE: src/ctcss.cpp
================================================
/*
 * ctcss.h
 *
 * Copyright (C) 2022-2023 charlie-foxtrot
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <math.h>     // M_PI
#include <algorithm>  // sort

#include "logging.h"  // debug_print()

#include "ctcss.h"

using namespace std;

// Implementation of https://www.embedded.com/detecting-ctcss-tones-with-goertzels-algorithm/
// also https://www.embedded.com/the-goertzel-algorithm/
ToneDetector::ToneDetector(float tone_freq, float sample_rate, int window_size) {
    tone_freq_ = tone_freq;
    magnitude_ = 0.0;

    window_size_ = window_size;

    int k = (0.5 + window_size * tone_freq / sample_rate);
    float omega = (2.0 * M_PI * k) / window_size;
    coeff_ = 2.0 * cos(omega);

    reset();
}

void ToneDetector::process_sample(const float& sample) {
    q0_ = coeff_ * q1_ - q2_ + sample;
    q2_ = q1_;
    q1_ = q0_;

    count_++;
    if (count_ == window_size_) {
        magnitude_ = q1_ * q1_ + q2_ * q2_ - q1_ * q2_ * coeff_;
        count_ = 0;
    }
}

void ToneDetector::reset(void) {
    count_ = 0;
    q0_ = q1_ = q2_ = 0.0;
}

bool ToneDetectorSet::add(const float& tone_freq, const float& sample_rate, int window_size) {
    ToneDetector new_tone = ToneDetector(tone_freq, sample_rate, window_size);

    for (const auto tone : tones_) {
        if (new_tone.coefficient() == tone.coefficient()) {
            debug_print("Skipping tone %f, too close to other tones\n", tone_freq);
            return false;
        }
    }

    tones_.push_back(new_tone);
    return true;
}

void ToneDetectorSet::process_sample(const float& sample) {
    for (vector<ToneDetector>::iterator it = tones_.begin(); it != tones_.end(); ++it) {
        it->process_sample(sample);
    }
}

void ToneDetectorSet::reset(void) {
    for (vector<ToneDetector>::iterator it = tones_.begin(); it != tones_.end(); ++it) {
        it->reset();
    }
}

float ToneDetectorSet::sorted_powers(vector<ToneDetectorSet::PowerIndex>& powers) {
    powers.clear();

    float total_power = 0.0;
    for (size_t i = 0; i < tones_.size(); ++i) {
        powers.push_back({tones_[i].relative_power(), tones_[i].freq()});
        total_power += tones_[i].relative_power();
    }

    sort(powers.begin(), powers.end(), [](PowerIndex a, PowerIndex b) { return a.power > b.power; });

    return total_power / tones_.size();
}

vector<float> CTCSS::standard_tones = {67.0,  69.3,  71.9,  74.4,  77.0,  79.7,  82.5,  85.4,  88.5,  91.5,  94.8,  97.4,  100.0, 103.5, 107.2, 110.9, 114.8,
                                       118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 150.0, 151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8, 177.3,
                                       179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, 250.3, 254.1};

CTCSS::CTCSS(const float& ctcss_freq, const float& sample_rate, int window_size) : enabled_(true), ctcss_freq_(ctcss_freq), window_size_(window_size), found_count_(0), not_found_count_(0) {
    debug_print("Adding CTCSS detector for %f Hz with a sample rate of %f and window %d\n", ctcss_freq, sample_rate, window_size_);

    // Add the target CTCSS frequency first followed by the other "standard tones", except those
    // within +/- 5 Hz
    powers_.add(ctcss_freq, sample_rate, window_size_);

    for (const auto tone : standard_tones) {
        if (abs(ctcss_freq - tone) < 5) {
            debug_print("Skipping tone %f, too close to other tones\n", tone);
            continue;
        }
        powers_.add(tone, sample_rate, window_size_);
    }

    // clear all values to start NOTE: has_tone_ will be true until the first window count of samples are processed
    reset();
}

void CTCSS::process_audio_sample(const float& sample) {
    if (!enabled_) {
        return;
    }

    powers_.process_sample(sample);

    sample_count_++;
    if (sample_count_ < window_size_) {
        return;
    }

    enough_samples_ = true;

    // if this is sample fills out the window then check if one of the "strongest"
    // tones is the CTCSS tone we are looking for.  NOTE: there can be multiple "strongest"
    // tones based on floating point math
    vector<ToneDetectorSet::PowerIndex> tone_powers;
    float avg_power = powers_.sorted_powers(tone_powers);
    float ctcss_tone_power = 0.0;
    for (const auto i : tone_powers) {
        if (i.freq == ctcss_freq_) {
            ctcss_tone_power = i.power;
            break;
        }
    }
    if (ctcss_tone_power == tone_powers[0].power && ctcss_tone_power > avg_power) {
        debug_print("CTCSS tone of %f Hz detected\n", ctcss_freq_);
        has_tone_ = true;
        found_count_++;
    } else {
        debug_print("CTCSS tone of %f Hz not detected - highest power was %f Hz at %f vs %f\n", ctcss_freq_, tone_powers[0].freq, tone_powers[0].power, ctcss_tone_power);
        has_tone_ = false;
        not_found_count_++;
    }

    // reset everything for the next window's worth of samples
    powers_.reset();
    sample_count_ = 0;
}

void CTCSS::reset(void) {
    if (enabled_) {
        powers_.reset();
        enough_samples_ = false;
        sample_count_ = 0;
        has_tone_ = false;
    }
}


================================================
FILE: src/ctcss.h
================================================
/*
 * ctcss.h
 *
 * Copyright (C) 2022-2023 charlie-foxtrot
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#ifndef _CTCSS_H
#define _CTCSS_H 1

#include <cstddef>  // size_t
#include <vector>

class ToneDetector {
   public:
    ToneDetector(float tone_freq, float sample_freq, int window_size);
    void process_sample(const float& sample);
    void reset(void);

    const float& relative_power(void) const { return magnitude_; }
    const float& freq(void) const { return tone_freq_; }
    const float& coefficient(void) const { return coeff_; }

   private:
    float tone_freq_;
    float magnitude_;

    int window_size_;
    float coeff_;

    int count_;
    float q0_;
    float q1_;
    float q2_;
};

class ToneDetectorSet {
   public:
    struct PowerIndex {
        float power;
        float freq;
    };

    ToneDetectorSet() {}

    bool add(const float& tone_freq, const float& sample_freq, int window_size);
    void process_sample(const float& sample);
    void reset(void);

    float sorted_powers(std::vector<PowerIndex>& powers);

   private:
    std::vector<ToneDetector> tones_;
};

class CTCSS {
   public:
    CTCSS(void) : enabled_(false), found_count_(0), not_found_count_(0) {}
    CTCSS(const float& ctcss_freq, const float& sample_rate, int window_size);
    void process_audio_sample(const float& sample);
    void reset(void);

    const size_t& found_count(void) const { return found_count_; }
    const size_t& not_found_count(void) const { return not_found_count_; }

    bool is_enabled(void) const { return enabled_; }
    bool enough_samples(void) const { return enough_samples_; }
    bool has_tone(void) const { return !enabled_ || has_tone_; }

    static std::vector<float> standard_tones;

   private:
    bool enabled_;
    float ctcss_freq_;
    int window_size_;
    size_t found_count_;
    size_t not_found_count_;

    ToneDetectorSet powers_;

    bool enough_samples_;
    int sample_count_;
    bool has_tone_;
};

#endif /* _CTCSS_H */


================================================
FILE: src/filters.cpp
================================================
/*
 * filters.cpp
 *
 * Copyright (C) 2022-2023 charlie-foxtrot
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include "logging.h"  // debug_print()

#include "filters.h"

using namespace std;

// Default constructor is no filter
NotchFilter::NotchFilter(void) : enabled_(false) {}

// Notch Filter based on https://www.dsprelated.com/showcode/173.php
NotchFilter::NotchFilter(float notch_freq, float sample_freq, float q) : enabled_(true), x{0.0}, y{0.0} {
    if (notch_freq <= 0.0) {
        debug_print("Invalid frequency %f Hz, disabling notch filter\n", notch_freq);
        enabled_ = false;
        return;
    }

    debug_print("Adding notch filter for %f Hz with parameters {%f, %f}\n", notch_freq, sample_freq, q);

    float wo = 2 * M_PI * (notch_freq / sample_freq);

    e = 1 / (1 + tan(wo / (q * 2)));
    p = cos(wo);
    d[0] = e;
    d[1] = 2 * e * p;
    d[2] = (2 * e - 1);

    debug_print("wo:%f e:%f p:%f d:{%f,%f,%f}\n", wo, e, p, d[0], d[1], d[2]);
}

void NotchFilter::apply(float& value) {
    if (!enabled_) {
        return;
    }

    x[0] = x[1];
    x[1] = x[2];
    x[2] = value;

    y[0] = y[1];
    y[1] = y[2];
    y[2] = d[0] * x[2] - d[1] * x[1] + d[0] * x[0] + d[1] * y[1] - d[2] * y[0];

    value = y[2];
}

// Default constructor is no filter
LowpassFilter::LowpassFilter(void) : enabled_(false) {}

// 2nd order lowpass Bessel filter, based entirely on a simplification of https://www-users.cs.york.ac.uk/~fisher/mkfilter/
LowpassFilter::LowpassFilter(float freq, float sample_freq) : enabled_(true) {
    if (freq <= 0.0) {
        debug_print("Invalid frequency %f Hz, disabling lowpass filter\n", freq);
        enabled_ = false;
        return;
    }

    debug_print("Adding lowpass filter at %f Hz with a sample rate of %f\n", freq, sample_freq);

    double raw_alpha = (double)freq / sample_freq;
    double warped_alpha = tan(M_PI * raw_alpha) / M_PI;

    complex<double> zeros[2] = {-1.0, -1.0};
    complex<double> poles[2];
    poles[0] = blt(M_PI * 2 * warped_alpha * complex<double>(-1.10160133059e+00, 6.36009824757e-01));
    poles[1] = blt(M_PI * 2 * warped_alpha * conj(complex<double>(-1.10160133059e+00, 6.36009824757e-01)));

    complex<double> topcoeffs[3];
    complex<double> botcoeffs[3];
    expand(zeros, 2, topcoeffs);
    expand(poles, 2, botcoeffs);
    complex<double> gain_complex = evaluate(topcoeffs, 2, botcoeffs, 2, 1.0);
    gain = hypot(gain_complex.imag(), gain_complex.real());

    for (int i = 0; i <= 2; i++) {
        ycoeffs[i] = -(botcoeffs[i].real() / botcoeffs[2].real());
    }

    debug_print("gain: %f, ycoeffs: {%f, %f}\n", gain, ycoeffs[0], ycoeffs[1]);
}

complex<double> LowpassFilter::blt(complex<double> pz) {
    return (2.0 + pz) / (2.0 - pz);
}

/* evaluate response, substituting for z */
complex<double> LowpassFilter::evaluate(complex<double> topco[], int nz, complex<double> botco[], int np, complex<double> z) {
    return eval(topco, nz, z) / eval(botco, np, z);
}

/* evaluate polynomial in z, substituting for z */
complex<double> LowpassFilter::eval(complex<double> coeffs[], int npz, complex<double> z) {
    complex<double> sum(0.0);
    for (int i = npz; i >= 0; i--) {
        sum = (sum * z) + coeffs[i];
    }
    return sum;
}

/* compute product of poles or zeros as a polynomial of z */
void LowpassFilter::expand(complex<double> pz[], int npz, complex<double> coeffs[]) {
    coeffs[0] = 1.0;
    for (int i = 0; i < npz; i++) {
        coeffs[i + 1] = 0.0;
    }
    for (int i = 0; i < npz; i++) {
        multin(pz[i], npz, coeffs);
    }
    /* check computed coeffs of z^k are all real */
    for (int i = 0; i < npz + 1; i++) {
        if (fabs(coeffs[i].imag()) > 1e-10) {
            log(LOG_ERR, "coeff of z^%d is not real; poles/zeros are not complex conjugates\n", i);
            error();
        }
    }
}

void LowpassFilter::multin(complex<double> w, int npz, complex<double> coeffs[]) {
    /* multiply factor (z-w) into coeffs */
    complex<double> nw = -w;
    for (int i = npz; i >= 1; i--) {
        coeffs[i] = (nw * coeffs[i]) + coeffs[i - 1];
    }
    coeffs[0] = nw * coeffs[0];
}

void LowpassFilter::apply(float& r, float& j) {
    if (!enabled_) {
        return;
    }

    complex<float> input(r, j);

    xv[0] = xv[1];
    xv[1] = xv[2];
    xv[2] = input / gain;

    yv[0] = yv[1];
    yv[1] = yv[2];
    yv[2] = (xv[0] + xv[2]) + (2.0f * xv[1]) + (ycoeffs[0] * yv[0]) + (ycoeffs[1] * yv[1]);

    r = yv[2].real();
    j = yv[2].imag();
}


================================================
FILE: src/filters.h
================================================
/*
 * filters.h
 *
 * Copyright (C) 2022-2023 charlie-foxtrot
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#ifndef _FILTERS_H
#define _FILTERS_H 1

#include <complex>

class NotchFilter {
   public:
    NotchFilter(void);
    NotchFilter(float notch_freq, float sample_freq, float q);
    void apply(float& value);
    bool enabled(void) { return enabled_; }

   private:
    bool enabled_;
    float e;
    float p;
    float d[3];
    float x[3];
    float y[3];
};

class LowpassFilter {
   public:
    LowpassFilter(void);
    LowpassFilter(float freq, float sample_freq);
    void apply(float& r, float& j);
    bool enabled(void) const { return enabled_; }

   private:
    static std::complex<double> blt(std::complex<double> pz);
    static void expand(std::complex<double> pz[], int npz, std::complex<double> coeffs[]);
    static void multin(std::complex<double> w, int npz, std::complex<double> coeffs[]);
    static std::complex<double> evaluate(std::complex<double> topco[], int nz, std::complex<double> botco[], int np, std::complex<double> z);
    static std::complex<double> eval(std::complex<double> coeffs[], int npz, std::complex<double> z);

    bool enabled_;
    float ycoeffs[3];
    float gain;

    std::complex<float> xv[3];
    std::complex<float> yv[3];
};

#endif /* _FILTERS_H */


================================================
FILE: src/generate_signal.cpp
================================================
/*
 * generate_signal.cpp
 *
 * Copyright (C) 2023 charlie-foxtrot
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include <cmath>

#include "generate_signal.h"

using namespace std;

float Tone::WEAK = 0.05;
float Tone::NORMAL = 0.2;
float Tone::STRONG = 0.4;

Tone::Tone(int sample_rate, const float& freq, const float& ampl) : sample_rate_(sample_rate), freq_(freq), ampl_(ampl), sample_count_(0) {}

float Tone::get_sample(void) {
    sample_count_++;
    return ampl_ * sin(2 * M_PI * sample_count_ * freq_ / sample_rate_);
}

float Noise::WEAK = 0.05;
float Noise::NORMAL = 0.2;
float Noise::STRONG = 0.5;

Noise::Noise(const float& ampl) : ampl_(ampl) {
    // create a seeded generator
    std::random_device r;
    std::seed_seq s{r(), r(), r(), r(), r(), r(), r(), r()};
    generator = std::mt19937(s);

    // centered at 0.0, standard deviation of 0.1
    distribution = normal_distribution<float>(0.0, 0.1);
}
float Noise::get_sample(void) {
    return ampl_ * distribution(generator);
}

GenerateSignal::GenerateSignal(int sample_rate) : sample_rate_(sample_rate) {}

void GenerateSignal::add_tone(const float& freq, const float& ampl) {
    tones_.push_back(Tone(sample_rate_, freq, ampl));
}

void GenerateSignal::add_noise(const float& ampl) {
    noises_.push_back(Noise(ampl));
}

float GenerateSignal::get_sample(void) {
    float value = 0.0;

    for (vector<Tone>::iterator tone = tones_.begin(); tone != tones_.end(); ++tone) {
        value += tone->get_sample();
    }

    for (vector<Noise>::iterator noise = noises_.begin(); noise != noises_.end(); ++noise) {
        value += noise->get_sample();
    }

    return value;
}

void GenerateSignal::write_file(const string& filepath, const float& seconds) {
    FILE* fp = fopen(filepath.c_str(), "wb");

    for (int i = 0; i < sample_rate_ * seconds; ++i) {
        float sample = get_sample();
        fwrite(&sample, sizeof(float), 1, fp);
    }
    fclose(fp);
}


================================================
FILE: src/generate_signal.h
================================================
/*
 * generate_signal.h
 *
 * Copyright (C) 2023 charlie-foxtrot
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#ifndef _GENERATE_SIGNAL_H
#define _GENERATE_SIGNAL_H

#include <random>
#include <string>
#include <vector>

class Tone {
   public:
    static float WEAK;
    static float NORMAL;
    static float STRONG;

    Tone(int sample_rate, const float& freq, const float& ampl);
    float get_sample(void);

   private:
    int sample_rate_;
    float freq_;
    float ampl_;
    size_t sample_count_;
};

class Noise {
   public:
    static float WEAK;
    static float NORMAL;
    static float STRONG;

    Noise(const float& ampl);
    float get_sample(void);

   private:
    float ampl_;
    std::mt19937 generator;
    std::normal_distribution<float> distribution;
};

class GenerateSignal {
   public:
    GenerateSignal(int sample_rate);

    void add_tone(const float& freq, const float& ampl);
    void add_noise(const float& ampl);

    float get_sample(void);

    void write_file(const std::string& filepath, const float& seconds);

   private:
    int sample_rate_;
    std::vector<Tone> tones_;
    std::vector<Noise> noises_;
};

#endif /* _GENERATE_SIGNAL_H */


================================================
FILE: src/hello_fft/CMakeLists.txt
================================================
set(hello_fft_source_files
	mailbox.c
	gpu_fft.c
	gpu_fft_twiddles.c
	gpu_fft_shaders.c
	gpu_fft_base.c
)
# Temp hack due to the fact that mailbox.c includes ../rtl_airband.h which
# is a C++ header.
SET_SOURCE_FILES_PROPERTIES(${hello_fft_source_files} PROPERTIES LANGUAGE CXX )
add_library(hello_fft OBJECT
	${hello_fft_source_files}
)
target_include_directories(hello_fft PUBLIC
	".." # needed for rtl_airband.h
	"${CMAKE_CURRENT_BINARY_DIR}/.." # needed for config.h
	${BCM_VC_INCLUDE_DIRS}
)

# disable -Wcast-qual for this folder
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-cast-qual")


================================================
FILE: src/hello_fft/gpu_fft.c
================================================
/*
BCM2835 "GPU_FFT" release 2.0
Copyright (c) 2014, Andrew Holme.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <string.h>

#include "gpu_fft.h"

#define GPU_FFT_BUSY_WAIT_LIMIT (5 << 12)  // ~1ms

typedef struct GPU_FFT_COMPLEX COMPLEX;

int gpu_fft_prepare(int mb,         // mailbox file_desc
                    int log2_N,     // log2(FFT_length) = 8...20
                    int direction,  // GPU_FFT_FWD: fft(); GPU_FFT_REV: ifft()
                    int jobs,       // number of transforms in batch
                    struct GPU_FFT** fft) {
    unsigned info_bytes, twid_bytes, data_bytes, code_bytes, unif_bytes, mail_bytes;
    unsigned size, *uptr, vc_tw, vc_data;
    int i, q, shared, unique, passes, ret;

    struct GPU_FFT_BASE* base;
    struct GPU_FFT_PTR ptr;
    struct GPU_FFT* info;

    if (gpu_fft_twiddle_size(log2_N, &shared, &unique, &passes))
        return -2;

    info_bytes = 4096;
    data_bytes = (1 + ((sizeof(COMPLEX) << log2_N) | 4095));
    code_bytes = gpu_fft_shader_size(log2_N);
    twid_bytes = sizeof(COMPLEX) * 16 * (shared + GPU_FFT_QPUS * unique);
    unif_bytes = sizeof(int) * GPU_FFT_QPUS * (5 + jobs * 2);
    mail_bytes = sizeof(int) * GPU_FFT_QPUS * 2;

    size = info_bytes +             // header
           data_bytes * jobs * 2 +  // ping-pong data, aligned
           code_bytes +             // shader, aligned
           twid_bytes +             // twiddles
           unif_bytes +             // uniforms
           mail_bytes;              // mailbox message

    ret = gpu_fft_alloc(mb, size, &ptr);
    if (ret)
        return ret;

    // Header
    info = (struct GPU_FFT*)ptr.arm.vptr;
    base = (struct GPU_FFT_BASE*)info;
    gpu_fft_ptr_inc(&ptr, info_bytes);

    // For transpose
    info->x = 1 << log2_N;
    info->y = jobs;

    // Ping-pong buffers leave results in or out of place
    info->in = info->out = ptr.arm.cptr;
    info->step = data_bytes / sizeof(COMPLEX);
    if (passes & 1)
        info->out += info->step * jobs;  // odd => out of place
    vc_data = gpu_fft_ptr_inc(&ptr, data_bytes * jobs * 2);

    // Shader code
    memcpy(ptr.arm.vptr, gpu_fft_shader_code(log2_N), code_bytes);
    base->vc_code = gpu_fft_ptr_inc(&ptr, code_bytes);

    // Twiddles
    gpu_fft_twiddle_data(log2_N, direction, ptr.arm.fptr);
    vc_tw = gpu_fft_ptr_inc(&ptr, twid_bytes);

    uptr = ptr.arm.uptr;

    // Uniforms
    for (q = 0; q < GPU_FFT_QPUS; q++) {
        *uptr++ = vc_tw;
        *uptr++ = vc_tw + sizeof(COMPLEX) * 16 * (shared + q * unique);
        *uptr++ = q;
        for (i = 0; i < jobs; i++) {
            *uptr++ = vc_data + data_bytes * i;
            *uptr++ = vc_data + data_bytes * i + data_bytes * jobs;
        }
        *uptr++ = 0;
        *uptr++ = (q == 0);  // For mailbox: IRQ enable, master only

        base->vc_unifs[q] = gpu_fft_ptr_inc(&ptr, sizeof(int) * (5 + jobs * 2));
    }

    if ((jobs << log2_N) <= GPU_FFT_BUSY_WAIT_LIMIT) {
        // Direct register poking with busy wait
        base->vc_msg = 0;
    } else {
        // Mailbox message
        for (q = 0; q < GPU_FFT_QPUS; q++) {
            *uptr++ = base->vc_unifs[q];
            *uptr++ = base->vc_code;
        }

        base->vc_msg = ptr.vc;
    }

    *fft = info;
    return 0;
}

unsigned gpu_fft_execute(struct GPU_FFT* info) {
    return gpu_fft_base_exec(&info->base, GPU_FFT_QPUS);
}

void gpu_fft_release(struct GPU_FFT* info) {
    gpu_fft_base_release(&info->base);
}


================================================
FILE: src/hello_fft/gpu_fft.h
================================================
/*
BCM2835 "GPU_FFT" release 2.0
Copyright (c) 2014, Andrew Holme.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef __GPU_FFT__
#define __GPU_FFT__

#define GPU_FFT_QPUS 8

#define GPU_FFT_PI 3.14159265358979323846

#define GPU_FFT_FWD 0  // forward FFT
#define GPU_FFT_REV 1  // inverse FFT

struct GPU_FFT_COMPLEX {
    float re, im;
};

struct GPU_FFT_PTR {
    unsigned vc;
    union {
        struct GPU_FFT_COMPLEX* cptr;
        void* vptr;
        char* bptr;
        float* fptr;
        unsigned* uptr;
    } arm;
};

struct GPU_FFT_BASE {
    int mb;
    unsigned handle, size, vc_msg, vc_code, vc_unifs[GPU_FFT_QPUS];
    volatile unsigned* peri;
};

struct GPU_FFT {
    struct GPU_FFT_BASE base;
    struct GPU_FFT_COMPLEX *in, *out;
    int x, y, step;
};

int gpu_fft_prepare(int mb,         // mailbox file_desc
                    int log2_N,     // log2(FFT_length) = 8...20
                    int direction,  // GPU_FFT_FWD: fft(); GPU_FFT_REV: ifft()
                    int jobs,       // number of transforms in batch
                    struct GPU_FFT** fft);

unsigned gpu_fft_execute(struct GPU_FFT* info);

void gpu_fft_release(struct GPU_FFT* info);

// private
int gpu_fft_twiddle_size(int, int*, int*, int*);
void gpu_fft_twiddle_data(int, int, float*);
unsigned int gpu_fft_shader_size(int);
unsigned int* gpu_fft_shader_code(int);

// gpu_fft_base:

unsigned gpu_fft_base_exec(struct GPU_FFT_BASE* base, unsigned num_qpus);

int gpu_fft_alloc(int mb, unsigned size, struct GPU_FFT_PTR* ptr);

void gpu_fft_base_release(struct GPU_FFT_BASE* base);

unsigned gpu_fft_ptr_inc(struct GPU_FFT_PTR* ptr, int bytes);

#endif  // __GPU_FFT__


================================================
FILE: src/hello_fft/gpu_fft.txt
================================================
BCM2835 "GPU_FFT" release 2.0 by Andrew Holme, 2014.

GPU_FFT is an FFT library for the Raspberry Pi which exploits the BCM2835 SoC
3D hardware to deliver ten times more data throughput than is possible on the
700 MHz ARM.  Kernels are provided for all power-of-2 FFT lengths between 256
and 2,097,152 points inclusive.  A transpose function, which also uses the 3D
hardware, is provided to support 2-dimensional transforms.


*** Accuracy ***

GPU_FFT uses single-precision floats for data and twiddle factors.  The output
is not scaled.  The relative root-mean-square (rms) error in parts-per-million
(ppm) for different transform lengths (N) is typically:

log2(N) |  8    | 9    | 10   |  11   |  12  |  13  |  14  |  15  |  16 |  17
ppm rms |  0.27 | 0.42 | 0.50 |  0.70 |  2.3 |  4.4 |  7.6 |  9.2 |  18 |  70

log2(N) |  18 |  19 |  20 |  21 |                8...17 batch of 10
ppm rms | 100 | 180 | 360 | 720 |               18...21 batch of  1


*** Throughput ***

GPU_FFT 1.0 had to be invoked through a "mailbox" which added a 100us overhead
on every call.  To mitigate this, batches of transforms could be submitted via
a single call.  GPU_FFT 2.0 avoids this 100us overhead by poking GPU registers
directly from the ARM if total batch runtime will be short; but still uses the
mailbox for longer jobs to avoid busy waiting at 100% CPU for too long.

Typical per-transform runtimes for batch sizes of 1 and 10; and comparative
figures for FFTW (FFTW_MEASURE mode) are:

log2(N) |   8   |   9   |  10   |  11   |  12  |  13  |  14  |  15  |
      1 | 0.036 | 0.051 | 0.070 | 0.11  | 0.24 | 0.58 |  1.2 |  3.3 |
     10 | 0.016 | 0.027 | 0.045 | 0.095 | 0.25 | 0.61 |  1.2 |  3.2 |
   FFTW | 0.092 | 0.22  | 0.48  | 0.95  | 3.0  | 5.1  | 12   | 31   |

log2(N) |  16  |  17 |  18 |  19 |   20 |   21 |       All times in
      1 |  6.8 |  16 |  42 |  95 |  190 |  380 |       milliseconds
   FFTW | 83   | 180 | 560 | 670 | 1600 | 3400 |       2 sig. figs.


*** API functions ***

    gpu_fft_prepare()       Call once to allocate memory and initialise data
                            structures.  Returns 0 for success.

    gpu_fft_execute()       Call one or more times to execute a previously
                            prepared FFT batch.  Returns 0 for success.

    gpu_fft_release()       Call once to release resources after use.
                            GPU memory is permanently lost if not freed.


*** Parameters ***

    int mb          Mailbox file descriptor obtained by calling mbox_open()

    int log2_N      log2(FFT length) = 8 to 21

    int direction   FFT direction:  GPU_FFT_FWD for forward FFT
                                    GPU_FFT_REV for inverse FFT

    int jobs        Number of transforms in batch = 1 or more

    GPU_FFT **      Output parameter from prepare: control structure.
    GPU_FFT *       Input parameter to execute and release


*** Data format ***

Complex data arrays are stored as alternate real and imaginary parts:

    struct GPU_FFT_COMPLEX {
        float re, im;
    };

The GPU_FFT struct created by gpu_fft_prepare() contains pointers to the input
and output arrays:

    struct GPU_FFT {
       struct GPU_FFT_COMPLEX *in, *out;

When executing a batch of transforms, buffer pointers are obtained as follows:

    struct GPU_FFT *fft = gpu_fft_prepare( ... , jobs);
    for (int j=0; j<jobs; j++) {
       struct GPU_FFT_COMPLEX *in  = fft->in  + j*fft->step;
       struct GPU_FFT_COMPLEX *out = fft->out + j*fft->step;

GPU_FFT.step is greater than FFT length because a guard space is left between
buffers for caching and alignment reasons.

GPU_FFT performs multiple passes between ping-pong buffers.  The final output
lands in the same buffer as input after an even number of passes.  Transforms
where log2_N=12...16 use an odd number of passes and the final result is left
out-of-place.  The input data is never preserved.


*** Example program ***

The code that produced the above accuracy and performance figures is included
as a demo with the latest Raspbian distro.  Build and run it as follows:

cd /opt/vc/src/hello_pi/hello_fft
make
sudo mknod char_dev c 100 0
sudo ./hello_fft.bin 12

It accepts three optional command-line arguments: <log2_N> <batch> <loops>

The special character device is required for the ioctl mailbox through which
the ARM communicates with the Videocore GPU.


*** With Open GL ***

GPU_FFT and Open GL will run concurrently if the GPU_FFT_MEM_* defines in
file gpu_fft.c are changed as follows:

#define GPU_FFT_MEM_FLG 0x4        // cached=0xC; direct=0x4
#define GPU_FFT_MEM_MAP 0x20000000 // cached=0x0; direct=0x20000000

Overall performance will probably be higher if GPU_FFT and Open GL take turns
at using the 3D hardware.  Since eglSwapBuffers() returns immediately without
waiting for rendering, call glFlush() and glFinish() afterwards as follows:

    for (;;) {
        ....
        eglSwapBuffers(....); // non-blocking call returns immediately
        glFlush();
        glFinish(); // wait until V3D hardware is idle
        ....
        gpu_fft_execute(....); // blocking call
        ....
    }


*** 2-dimensional FFT ***

Please study the hello_fft_2d demo source, which is built and executed thus:

make hello_fft_2d.bin
sudo ./hello_fft_2d.bin

This generates a Windows BMP file: "hello_fft_2d.bmp"

The demo uses a square 512x512 array; however, rectangular arrays are allowed.
The following lines in gpu_fft_trans.c will do what is safe:

    ptr.arm.uptr[6] = src->x < dst->y? src->x : dst->y;
    ptr.arm.uptr[7] = src->y < dst->x? src->y : dst->x;

One may transpose the output from the second FFT pass back into the first pass
input buffer, by preparing and executing a second transposition; however, this
is probably unnecessary.  It depends on how the final output will be accessed.


================================================
FILE: src/hello_fft/gpu_fft_base.c
================================================
/*
BCM2835 "GPU_FFT" release 2.0
Copyright (c) 2014, Andrew Holme.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "bcm_host.h"
#include "gpu_fft.h"
#include "mailbox.h"

#define BUS_TO_PHYS(x) ((x) & ~0xC0000000)

// V3D spec: http://www.broadcom.com/docs/support/videocore/VideoCoreIV-AG100-R.pdf
#define V3D_L2CACTL (0xC00020 >> 2)
#define V3D_SLCACTL (0xC00024 >> 2)
#define V3D_SRQPC (0xC00430 >> 2)
#define V3D_SRQUA (0xC00434 >> 2)
#define V3D_SRQCS (0xC0043c >> 2)
#define V3D_DBCFG (0xC00e00 >> 2)
#define V3D_DBQITE (0xC00e2c >> 2)
#define V3D_DBQITC (0xC00e30 >> 2)

#define GPU_FFT_MEM_MAP 0x0  // cached=0x0; direct=0x20000000

#define GPU_FFT_NO_FLUSH 1
#define GPU_FFT_TIMEOUT 2000  // ms

unsigned gpu_fft_base_exec_direct(struct GPU_FFT_BASE* base, unsigned num_qpus) {
    unsigned q;

    base->peri[V3D_DBCFG] = 0;    // Disallow IRQ
    base->peri[V3D_DBQITE] = 0;   // Disable IRQ
    base->peri[V3D_DBQITC] = -1;  // Resets IRQ flags

    base->peri[V3D_L2CACTL] = 1 << 2;  // Clear L2 cache
    base->peri[V3D_SLCACTL] = -1;      // Clear other caches

    base->peri[V3D_SRQCS] = (1 << 7) | (1 << 8) | (1 << 16);  // Reset error bit and counts

    for (q = 0; q < num_qpus; q++) {  // Launch shader(s)
        base->peri[V3D_SRQUA] = base->vc_unifs[q];
        base->peri[V3D_SRQPC] = base->vc_code;
    }

    // Busy wait polling
    for (;;) {
        if (((base->peri[V3D_SRQCS] >> 16) & 0xff) == num_qpus)
            break;  // All done?
    }

    return 0;
}

unsigned gpu_fft_base_exec(struct GPU_FFT_BASE* base, unsigned num_qpus) {
    if (base->vc_msg) {
        // Use mailbox
        // Returns: 0x0 for success; 0x80000000 for timeout
        return execute_qpu(base->mb, num_qpus, base->vc_msg, GPU_FFT_NO_FLUSH, GPU_FFT_TIMEOUT);
    } else {
        // Direct register poking
        return gpu_fft_base_exec_direct(base, num_qpus);
    }
}

int gpu_fft_alloc(int mb, unsigned size, struct GPU_FFT_PTR* ptr) {
    struct GPU_FFT_BASE* base;
    volatile unsigned* peri;
    unsigned handle;

    if (qpu_enable(mb, 1))
        return -1;

    // Shared memory : cached=0xC; direct=0x4
    unsigned mem_flg = bcm_host_get_sdram_address() == 0x40000000 ? 0xC : 0x4;
    handle = mem_alloc(mb, size, 4096, mem_flg);
    if (!handle) {
        qpu_enable(mb, 0);
        return -3;
    }

    peri = (volatile unsigned*)mapmem(bcm_host_get_peripheral_address(), bcm_host_get_peripheral_size());
    if (!peri) {
        mem_free(mb, handle);
        qpu_enable(mb, 0);
        return -4;
    }

    ptr->vc = mem_lock(mb, handle);
    ptr->arm.vptr = mapmem(BUS_TO_PHYS(ptr->vc + GPU_FFT_MEM_MAP), size);

    base = (struct GPU_FFT_BASE*)ptr->arm.vptr;
    base->peri = peri;
    base->mb = mb;
    base->handle = handle;
    base->size = size;

    return 0;
}

void gpu_fft_base_release(struct GPU_FFT_BASE* base) {
    int mb = base->mb;
    unsigned handle = base->handle, size = base->size;
    unmapmem((void*)base->peri, bcm_host_get_peripheral_size());
    unmapmem((void*)base, size);
    mem_unlock(mb, handle);
    mem_free(mb, handle);
    qpu_enable(mb, 0);
}

unsigned gpu_fft_ptr_inc(struct GPU_FFT_PTR* ptr, int bytes) {
    unsigned vc = ptr->vc;
    ptr->vc += bytes;
    ptr->arm.bptr += bytes;
    return vc;
}


================================================
FILE: src/hello_fft/gpu_fft_shaders.c
================================================
/*
BCM2835 "GPU_FFT" release 2.0
Copyright (c) 2014, Andrew Holme.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

static unsigned int shader_256[] = {
#include "hex/shader_256.hex"
};
static unsigned int shader_512[] = {
#include "hex/shader_512.hex"
};
static unsigned int shader_1k[] = {
#include "hex/shader_1k.hex"
};
static unsigned int shader_2k[] = {
#include "hex/shader_2k.hex"
};
static unsigned int shader_4k[] = {
#include "hex/shader_4k.hex"
};
static unsigned int shader_8k[] = {
#include "hex/shader_8k.hex"
};
static unsigned int shader_16k[] = {
#include "hex/shader_16k.hex"
};
static unsigned int shader_32k[] = {
#include "hex/shader_32k.hex"
};
static unsigned int shader_64k[] = {
#include "hex/shader_64k.hex"
};
static unsigned int shader_128k[] = {
#include "hex/shader_128k.hex"
};
static unsigned int shader_256k[] = {
#include "hex/shader_256k.hex"
};
static unsigned int shader_512k[] = {
#include "hex/shader_512k.hex"
};
static unsigned int shader_1024k[] = {
#include "hex/shader_1024k.hex"
};
static unsigned int shader_2048k[] = {
#include "hex/shader_2048k.hex"
};

static struct {
    unsigned int size, *code;
} shaders[] = {{sizeof(shader_256), shader_256},   {sizeof(shader_512), shader_512},   {sizeof(shader_1k), shader_1k},       {sizeof(shader_2k), shader_2k},      {sizeof(shader_4k), shader_4k},
               {sizeof(shader_8k), shader_8k},     {sizeof(shader_16k), shader_16k},   {sizeof(shader_32k), shader_32k},     {sizeof(shader_64k), shader_64k},    {sizeof(shader_128k), shader_128k},
               {sizeof(shader_256k), shader_256k}, {sizeof(shader_512k), shader_512k}, {sizeof(shader_1024k), shader_1024k}, {sizeof(shader_2048k), shader_2048k}};

unsigned int gpu_fft_shader_size(int log2_N) {
    return shaders[log2_N - 8].size;
}

unsigned int* gpu_fft_shader_code(int log2_N) {
    return shaders[log2_N - 8].code;
}


================================================
FILE: src/hello_fft/gpu_fft_trans.h
================================================
/*
BCM2835 "GPU_FFT" release 2.0
Copyright (c) 2014, Andrew Holme.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "gpu_fft.h"

struct GPU_FFT_TRANS {
    struct GPU_FFT_BASE base;
};

int gpu_fft_trans_prepare(int mb, struct GPU_FFT* src, struct GPU_FFT* dst, struct GPU_FFT_TRANS** out);

unsigned gpu_fft_trans_execute(  // src->out ==> T ==> dst->in
    struct GPU_FFT_TRANS* info);

void gpu_fft_trans_release(struct GPU_FFT_TRANS* info);


================================================
FILE: src/hello_fft/gpu_fft_twiddles.c
================================================
/*
BCM2835 "GPU_FFT" release 2.0
C
Download .txt
gitextract_bz4e8om4/

├── .clang-format
├── .devcontainer/
│   ├── Dockerfile
│   ├── devcontainer.json
│   └── shell
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   └── feature_request.md
│   ├── install_dependencies
│   ├── platform_build
│   └── workflows/
│       ├── build_docker_containers.yml
│       ├── ci_build.yml
│       ├── code_formatting.yml
│       ├── platform_build.yml
│       └── version_bump.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .vscode/
│   ├── c_cpp_properties.json
│   ├── launch.json
│   └── settings.json
├── CMakeLists.txt
├── Dockerfile
├── LICENSE
├── NEWS.md
├── README.md
├── config/
│   ├── basic_multichannel.conf
│   ├── basic_scanning.conf
│   ├── big_mixer.conf
│   ├── mixers.conf
│   ├── noaa.conf
│   └── two_dongles_multiple_outputs.conf
├── init.d/
│   ├── rtl_airband-debian.sh
│   ├── rtl_airband-freebsd.sh
│   ├── rtl_airband-gentoo.sh
│   └── rtl_airband.service
├── scripts/
│   ├── find_version
│   └── reformat_code
└── src/
    ├── .gitignore
    ├── CMakeLists.txt
    ├── CMakeModules/
    │   ├── FindBCM_VC.cmake
    │   ├── FindLame.cmake
    │   ├── FindMiriSDR.cmake
    │   ├── FindRTLSDR.cmake
    │   └── version.cmake
    ├── config.cpp
    ├── config.h.in
    ├── ctcss.cpp
    ├── ctcss.h
    ├── filters.cpp
    ├── filters.h
    ├── generate_signal.cpp
    ├── generate_signal.h
    ├── hello_fft/
    │   ├── CMakeLists.txt
    │   ├── gpu_fft.c
    │   ├── gpu_fft.h
    │   ├── gpu_fft.txt
    │   ├── gpu_fft_base.c
    │   ├── gpu_fft_shaders.c
    │   ├── gpu_fft_trans.h
    │   ├── gpu_fft_twiddles.c
    │   ├── hex/
    │   │   ├── shader_1024k.hex
    │   │   ├── shader_128k.hex
    │   │   ├── shader_16k.hex
    │   │   ├── shader_1k.hex
    │   │   ├── shader_2048k.hex
    │   │   ├── shader_256.hex
    │   │   ├── shader_256k.hex
    │   │   ├── shader_2k.hex
    │   │   ├── shader_32k.hex
    │   │   ├── shader_4k.hex
    │   │   ├── shader_512.hex
    │   │   ├── shader_512k.hex
    │   │   ├── shader_64k.hex
    │   │   ├── shader_8k.hex
    │   │   └── shader_trans.hex
    │   ├── mailbox.c
    │   └── mailbox.h
    ├── helper_functions.cpp
    ├── helper_functions.h
    ├── input-common.cpp
    ├── input-common.h
    ├── input-file.cpp
    ├── input-file.h
    ├── input-helpers.cpp
    ├── input-helpers.h
    ├── input-mirisdr.cpp
    ├── input-mirisdr.h
    ├── input-rtlsdr.cpp
    ├── input-rtlsdr.h
    ├── input-soapysdr.cpp
    ├── input-soapysdr.h
    ├── logging.cpp
    ├── logging.h
    ├── mixer.cpp
    ├── output.cpp
    ├── pulse.cpp
    ├── rtl_airband.cpp
    ├── rtl_airband.h
    ├── rtl_airband_neon.s
    ├── squelch.cpp
    ├── squelch.h
    ├── test_base_class.cpp
    ├── test_base_class.h
    ├── test_ctcss.cpp
    ├── test_filters.cpp
    ├── test_generate_signal.cpp
    ├── test_helper_functions.cpp
    ├── test_squelch.cpp
    ├── udp_stream.cpp
    └── util.cpp
Download .txt
SYMBOL INDEX (313 symbols across 41 files)

FILE: src/config.cpp
  function parse_outputs (line 34) | static int parse_outputs(libconfig::Setting& outs, channel_t* channel, i...
  type freq_t (line 271) | struct freq_t
  type freq_t (line 276) | struct freq_t
  type freq_t (line 276) | struct freq_t
  type freq_t (line 276) | struct freq_t
  function warn_if_freq_not_in_range (line 289) | static void warn_if_freq_not_in_range(int devidx, int chanidx, int freq,...
  function parse_anynum2int (line 298) | static int parse_anynum2int(libconfig::Setting& f) {
  function parse_channels (line 312) | static int parse_channels(libconfig::Setting& chans, device_t* dev, int ...
  function parse_devices (line 731) | int parse_devices(libconfig::Setting& devs) {
  function parse_mixers (line 838) | int parse_mixers(libconfig::Setting& mx) {

FILE: src/ctcss.h
  function class (line 26) | class ToneDetector {
  function class (line 49) | class ToneDetectorSet {
  function class (line 68) | class CTCSS {

FILE: src/filters.h
  function class (line 25) | class NotchFilter {
  function class (line 41) | class LowpassFilter {

FILE: src/generate_signal.h
  function class (line 27) | class Tone {
  function class (line 43) | class Noise {
  function class (line 58) | class GenerateSignal {

FILE: src/hello_fft/gpu_fft.c
  type COMPLEX (line 35) | typedef struct GPU_FFT_COMPLEX COMPLEX;
  function gpu_fft_prepare (line 37) | int gpu_fft_prepare(int mb,         // mailbox file_desc
  function gpu_fft_execute (line 129) | unsigned gpu_fft_execute(struct GPU_FFT* info) {
  function gpu_fft_release (line 133) | void gpu_fft_release(struct GPU_FFT* info) {

FILE: src/hello_fft/gpu_fft.h
  type GPU_FFT_COMPLEX (line 39) | struct GPU_FFT_COMPLEX {
  type GPU_FFT_PTR (line 43) | struct GPU_FFT_PTR {
  type GPU_FFT_BASE (line 54) | struct GPU_FFT_BASE {
  type GPU_FFT (line 60) | struct GPU_FFT {
  type GPU_FFT (line 70) | struct GPU_FFT
  type GPU_FFT (line 72) | struct GPU_FFT
  type GPU_FFT (line 74) | struct GPU_FFT
  type GPU_FFT_BASE (line 84) | struct GPU_FFT_BASE
  type GPU_FFT_PTR (line 86) | struct GPU_FFT_PTR
  type GPU_FFT_BASE (line 88) | struct GPU_FFT_BASE
  type GPU_FFT_PTR (line 90) | struct GPU_FFT_PTR

FILE: src/hello_fft/gpu_fft_base.c
  function gpu_fft_base_exec_direct (line 50) | unsigned gpu_fft_base_exec_direct(struct GPU_FFT_BASE* base, unsigned nu...
  function gpu_fft_base_exec (line 76) | unsigned gpu_fft_base_exec(struct GPU_FFT_BASE* base, unsigned num_qpus) {
  function gpu_fft_alloc (line 87) | int gpu_fft_alloc(int mb, unsigned size, struct GPU_FFT_PTR* ptr) {
  function gpu_fft_base_release (line 122) | void gpu_fft_base_release(struct GPU_FFT_BASE* base) {
  function gpu_fft_ptr_inc (line 132) | unsigned gpu_fft_ptr_inc(struct GPU_FFT_PTR* ptr, int bytes) {

FILE: src/hello_fft/gpu_fft_shaders.c
  function gpu_fft_shader_size (line 78) | unsigned int gpu_fft_shader_size(int log2_N) {

FILE: src/hello_fft/gpu_fft_trans.h
  type GPU_FFT_TRANS (line 31) | struct GPU_FFT_TRANS {
  type GPU_FFT (line 35) | struct GPU_FFT
  type GPU_FFT (line 35) | struct GPU_FFT
  type GPU_FFT_TRANS (line 35) | struct GPU_FFT_TRANS
  type GPU_FFT_TRANS (line 38) | struct GPU_FFT_TRANS
  type GPU_FFT_TRANS (line 40) | struct GPU_FFT_TRANS

FILE: src/hello_fft/gpu_fft_twiddles.c
  function twiddles_256 (line 90) | static void twiddles_256(double two_pi, float* out) {
  function twiddles_512 (line 101) | static void twiddles_512(double two_pi, float* out) {
  function twiddles_1k (line 112) | static void twiddles_1k(double two_pi, float* out) {
  function twiddles_2k (line 123) | static void twiddles_2k(double two_pi, float* out) {
  function twiddles_4k (line 134) | static void twiddles_4k(double two_pi, float* out) {
  function twiddles_8k (line 146) | static void twiddles_8k(double two_pi, float* out) {
  function twiddles_16k (line 158) | static void twiddles_16k(double two_pi, float* out) {
  function twiddles_32k (line 170) | static void twiddles_32k(double two_pi, float* out) {
  function twiddles_64k (line 182) | static void twiddles_64k(double two_pi, float* out) {
  function twiddles_128k (line 194) | static void twiddles_128k(double two_pi, float* out) {
  function twiddles_256k (line 207) | static void twiddles_256k(double two_pi, float* out) {
  function twiddles_512k (line 220) | static void twiddles_512k(double two_pi, float* out) {
  function twiddles_1024k (line 233) | static void twiddles_1024k(double two_pi, float* out) {
  function twiddles_2048k (line 246) | static void twiddles_2048k(double two_pi, float* out) {
  function gpu_fft_twiddle_size (line 267) | int gpu_fft_twiddle_size(int log2_N, int* shared, int* unique, int* pass...
  function gpu_fft_twiddle_data (line 276) | void gpu_fft_twiddle_data(int log2_N, int direction, float* out) {

FILE: src/hello_fft/mailbox.c
  function unmapmem (line 67) | void unmapmem(void* addr, unsigned size) {
  function mbox_property (line 79) | static int mbox_property(int file_desc, void* buf) {
  function mem_alloc (line 96) | unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigne...
  function mem_free (line 116) | unsigned mem_free(int file_desc, unsigned handle) {
  function mem_lock (line 134) | unsigned mem_lock(int file_desc, unsigned handle) {
  function mem_unlock (line 152) | unsigned mem_unlock(int file_desc, unsigned handle) {
  function execute_code (line 170) | unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigne...
  function qpu_enable (line 194) | unsigned qpu_enable(int file_desc, unsigned enable) {
  function execute_qpu (line 213) | unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control,...
  function mbox_open (line 234) | int mbox_open() {
  function mbox_close (line 246) | void mbox_close(int file_desc) {

FILE: src/helper_functions.cpp
  function dir_exists (line 29) | bool dir_exists(const string& dir_path) {
  function file_exists (line 34) | bool file_exists(const string& file_path) {
  function make_dir (line 39) | bool make_dir(const string& dir_path) {
  function make_subdirs (line 51) | bool make_subdirs(const string& basedir, const string& subdirs) {
  function string (line 73) | string make_dated_subdirs(const string& basedir, const struct tm* time) {

FILE: src/helper_functions.h
  type tm (line 30) | struct tm

FILE: src/input-common.cpp
  function input_t (line 35) | input_t* input_new(char const* const type) {
  function input_init (line 56) | int input_init(input_t* const input) {
  function input_start (line 74) | int input_start(input_t* const input) {
  function input_parse_config (line 86) | int input_parse_config(input_t* const input, libconfig::Setting& cfg) {
  function input_stop (line 97) | int input_stop(input_t* const input) {
  function input_set_centerfreq (line 118) | int input_set_centerfreq(input_t* const input, int const centerfreq) {

FILE: src/input-common.h
  type sample_format_t (line 31) | typedef enum { SFMT_UNDEF = 0, SFMT_U8, SFMT_S8, SFMT_S16, SFMT_F32 } sa...
  type input_state_t (line 34) | typedef enum { INPUT_UNKNOWN = 0, INPUT_INITIALIZED, INPUT_RUNNING, INPU...
  type input_t (line 37) | typedef struct input_t input_t;
  type input_t (line 39) | struct input_t {

FILE: src/input-file.cpp
  function file_parse_config (line 35) | int file_parse_config(input_t* const input, libconfig::Setting& cfg) {
  function file_init (line 67) | int file_init(input_t* const input) {
  function file_set_centerfreq (line 149) | int file_set_centerfreq(input_t* const /*input*/, int const /*centerfreq...
  function file_stop (line 153) | int file_stop(input_t* const input) {
  function MODULE_EXPORT (line 162) | MODULE_EXPORT input_t* file_input_new() {

FILE: src/input-file.h
  type file_dev_data_t (line 25) | typedef struct {

FILE: src/input-helpers.cpp
  function circbuffer_append (line 37) | void circbuffer_append(input_t* const input, unsigned char* buf, size_t ...

FILE: src/input-mirisdr.cpp
  function mirisdr_callback (line 38) | static void mirisdr_callback(unsigned char* buf, uint32_t len, void* ctx) {
  function mirisdr_nearest_gain (line 46) | static bool mirisdr_nearest_gain(mirisdr_dev_t* dev, int target_gain, in...
  function mirisdr_find_device_by_serial (line 72) | static int mirisdr_find_device_by_serial(char const* const s) {
  function mirisdr_init (line 88) | int mirisdr_init(input_t* const input) {
  function mirisdr_stop (line 159) | int mirisdr_stop(input_t* const input) {
  function mirisdr_set_centerfreq (line 169) | int mirisdr_set_centerfreq(input_t* const input, int const centerfreq) {
  function mirisdr_parse_config (line 181) | int mirisdr_parse_config(input_t* const input, libconfig::Setting& cfg) {
  function MODULE_EXPORT (line 210) | MODULE_EXPORT input_t* mirisdr_input_new() {

FILE: src/input-mirisdr.h
  type mirisdr_dev_data_t (line 25) | typedef struct {

FILE: src/input-rtlsdr.cpp
  function rtlsdr_callback (line 38) | static void rtlsdr_callback(unsigned char* buf, uint32_t len, void* ctx) {
  function rtlsdr_nearest_gain (line 46) | static bool rtlsdr_nearest_gain(rtlsdr_dev_t* dev, int target_gain, int*...
  function rtlsdr_find_device_by_serial (line 72) | static int rtlsdr_find_device_by_serial(char const* const s) {
  function rtlsdr_init (line 88) | int rtlsdr_init(input_t* const input) {
  function rtlsdr_stop (line 170) | int rtlsdr_stop(input_t* const input) {
  function rtlsdr_set_centerfreq (line 180) | int rtlsdr_set_centerfreq(input_t* const input, int const centerfreq) {
  function rtlsdr_parse_config (line 192) | int rtlsdr_parse_config(input_t* const input, libconfig::Setting& cfg) {
  function MODULE_EXPORT (line 225) | MODULE_EXPORT input_t* rtlsdr_input_new() {

FILE: src/input-rtlsdr.h
  type rtlsdr_dev_data_t (line 25) | typedef struct {

FILE: src/input-soapysdr.cpp
  function soapysdr_match_sfmt (line 45) | static bool soapysdr_match_sfmt(input_t* const input, char const* const ...
  function soapysdr_choose_sample_format (line 76) | static bool soapysdr_choose_sample_format(SoapySDRDevice* const sdr, inp...
  function sdrplay_get_nearest_sample_rate (line 111) | static int sdrplay_get_nearest_sample_rate(SoapySDRDevice* sdr, int chan...
  function soapysdr_parse_config (line 148) | int soapysdr_parse_config(input_t* const input, libconfig::Setting& cfg) {
  function soapysdr_init (line 217) | int soapysdr_init(input_t* const input) {
  function soapysdr_set_centerfreq (line 322) | int soapysdr_set_centerfreq(input_t* const input, int const centerfreq) {
  function MODULE_EXPORT (line 333) | MODULE_EXPORT input_t* soapysdr_input_new() {

FILE: src/input-soapysdr.h
  type soapysdr_dev_data_t (line 26) | typedef struct {

FILE: src/logging.cpp
  function error (line 31) | void error() {
  function init_debug (line 36) | void init_debug(const char* file) {
  function close_debug (line 49) | void close_debug() {
  function log (line 57) | void log(int priority, const char* format, ...) {

FILE: src/logging.h
  type LogDestination (line 48) | enum LogDestination { SYSLOG, STDERR, NONE }

FILE: src/mixer.cpp
  function mixer_set_error (line 33) | static inline void mixer_set_error(const char* msg) {
  function mixer_t (line 41) | mixer_t* getmixerbyname(const char* name) {
  function mixer_disable (line 52) | void mixer_disable(mixer_t* mixer) {
  function mixer_connect_input (line 57) | int mixer_connect_input(mixer_t* mixer, float ampfactor, float balance) {
  function mixer_disable_input (line 96) | void mixer_disable_input(mixer_t* mixer, int input_idx) {
  function mixer_put_samples (line 114) | void mixer_put_samples(mixer_t* mixer, int input_idx, const float* sampl...
  function mix_waveforms (line 133) | void mix_waveforms(float* sum, const float* in, float mult, int size) {
  type timeval (line 167) | struct timeval

FILE: src/output.cpp
  function lame_t (line 147) | lame_t airlame_init(mix_modes mixmode, int highpass, int lowpass) {
  class LameTone (line 173) | class LameTone {
    method LameTone (line 178) | LameTone(mix_modes mixmode, int msec, unsigned int hz = 0) : _data(NUL...
    method write (line 220) | int write(FILE* f) {
  function rename_if_exists (line 233) | int rename_if_exists(char const* oldpath, char const* newpath) {
  function open_file (line 250) | static int open_file(file_data* fdata, mix_modes mixmode, int is_audio) {
  function close_file (line 314) | static void close_file(output_t* output) {
  function close_if_necessary (line 353) | static void close_if_necessary(output_t* output) {
  function output_file_ready (line 404) | static bool output_file_ready(channel_t* channel, output_t* output) {
  function process_outputs (line 466) | void process_outputs(channel_t* channel, int cur_scan_freq) {
  function disable_channel_outputs (line 590) | void disable_channel_outputs(channel_t* channel) {
  function disable_device_outputs (line 619) | void disable_device_outputs(device_t* dev) {
  function print_channel_metric (line 626) | static void print_channel_metric(FILE* f, char const* name, float freq, ...
  function output_channel_noise_levels (line 634) | static void output_channel_noise_levels(FILE* f) {
  function output_channel_dbfs_noise_levels (line 652) | static void output_channel_dbfs_noise_levels(FILE* f) {
  function output_channel_signal_levels (line 670) | static void output_channel_signal_levels(FILE* f) {
  function output_channel_dbfs_signal_levels (line 688) | static void output_channel_dbfs_signal_levels(FILE* f) {
  function output_channel_squelch_levels (line 706) | static void output_channel_squelch_levels(FILE* f) {
  function output_channel_squelch_counter (line 724) | static void output_channel_squelch_counter(FILE* f) {
  function output_channel_flappy_counter (line 742) | static void output_channel_flappy_counter(FILE* f) {
  function output_channel_ctcss_counter (line 760) | static void output_channel_ctcss_counter(FILE* f) {
  function output_channel_no_ctcss_counter (line 778) | static void output_channel_no_ctcss_counter(FILE* f) {
  function output_channel_activity_counters (line 796) | static void output_channel_activity_counters(FILE* f) {
  function output_device_buffer_overflows (line 814) | static void output_device_buffer_overflows(FILE* f) {
  function output_output_overruns (line 826) | static void output_output_overruns(FILE* f) {
  function output_input_overruns (line 842) | static void output_input_overruns(FILE* f) {
  function write_stats_file (line 861) | void write_stats_file(timeval* last_stats_write) {
  type freq_tag (line 902) | struct freq_tag
  type timeval (line 903) | struct timeval

FILE: src/pulse.cpp
  function pulse_shutdown (line 40) | void pulse_shutdown(pulse_data* pdata) {
  function pulse_stream_underflow_cb (line 62) | static void pulse_stream_underflow_cb(pa_stream*, void* userdata) {
  function pulse_stream_overflow_cb (line 68) | static void pulse_stream_overflow_cb(pa_stream*, void* userdata) {
  function stream_state_cb (line 73) | static void stream_state_cb(pa_stream* stream, void* userdata) {
  function pa_stream (line 94) | static pa_stream* pulse_setup_stream(pulse_data* pdata, const pa_sample_...
  function pulse_setup_streams (line 121) | static void pulse_setup_streams(pulse_data* pdata) {
  function pulse_ctx_state_cb (line 148) | static void pulse_ctx_state_cb(pa_context* c, void* userdata) {
  function pulse_init (line 170) | void pulse_init() {
  function pulse_setup (line 177) | int pulse_setup(pulse_data* pdata, mix_modes mixmode) {
  function pulse_start (line 196) | void pulse_start() {
  function pulse_write_single_stream (line 204) | static int pulse_write_single_stream(pa_stream* stream, pulse_data* pdat...
  function pulse_write_stream (line 235) | void pulse_write_stream(pulse_data* pdata, mix_modes mode, const float* ...

FILE: src/rtl_airband.cpp
  type fm_demod_algo (line 88) | enum fm_demod_algo { FM_FAST_ATAN2, FM_QUADRI_DEMOD }
  type fm_demod_algo (line 89) | enum fm_demod_algo
  function sighandler (line 96) | void sighandler(int sig) {
  type timeval (line 106) | struct timeval
  function multiply (line 141) | void multiply(float ar, float aj, float br, float bj, float* cr, float* ...
  function fast_atan2 (line 147) | float fast_atan2(float y, float x) {
  function polar_disc_fast (line 168) | float polar_disc_fast(float ar, float aj, float br, float bj) {
  function fm_quadri_demod (line 174) | float fm_quadri_demod(float ar, float aj, float br, float bj) {
  class AFC (line 180) | class AFC {
    method square (line 184) | float square(const GPU_FFT_COMPLEX* fft_results, size_t index) {
    method square (line 188) | float square(const fftwf_complex* fft_results, size_t index) {
    method check (line 194) | size_t check(const FFT_RESULTS* fft_results, const size_t base, const ...
    method AFC (line 222) | AFC(device_t* dev, int index) : _prev_axcindicate(dev->channels[index]...
    method finalize (line 225) | void finalize(device_t* dev, int index, const FFT_RESULTS* fft_results) {
  function init_demod (line 253) | void init_demod(demod_params_t* params, Signal* signal, int device_start...
  function init_output (line 268) | bool init_output(channel_t* channel, output_t* output) {
  function init_output_params (line 290) | void init_output_params(output_params_t* params, int device_start, int d...
  function next_device (line 300) | int next_device(demod_params_t* params, int current) {
  type GPU_FFT (line 317) | struct GPU_FFT
  type timeval (line 376) | struct timeval
  type GPU_FFT_COMPLEX (line 427) | struct GPU_FFT_COMPLEX
  type GPU_FFT_COMPLEX (line 445) | struct GPU_FFT_COMPLEX
  type GPU_FFT_COMPLEX (line 496) | struct GPU_FFT_COMPLEX
  function usage (line 696) | void usage() {
  function count_devices_running (line 713) | static int count_devices_running() {
  function main (line 723) | int main(int argc, char* argv[]) {

FILE: src/rtl_airband.h
  type sample_fft_arg (line 88) | struct sample_fft_arg {
  type status (line 101) | enum status { NO_SIGNAL = ' ', SIGNAL = '*', AFC_UP = '<', AFC_DOWN = '>' }
  type ch_states (line 102) | enum ch_states { CH_DIRTY, CH_WORKING, CH_READY }
  type mix_modes (line 103) | enum mix_modes { MM_MONO, MM_STEREO }
  type output_type (line 104) | enum output_type {
  type icecast_data (line 116) | struct icecast_data {
  type file_data (line 132) | struct file_data {
  type udp_stream_data (line 149) | struct udp_stream_data {
  type pulse_data (line 163) | struct pulse_data {
  type mixer_data (line 176) | struct mixer_data {
  type output_t (line 181) | struct output_t {
  type freq_tag (line 197) | struct freq_tag {
  type modulations (line 202) | enum modulations {
  function class (line 210) | class Signal {
  type freq_t (line 232) | struct freq_t {
  type channel_t (line 243) | struct channel_t {
  type rec_modes (line 271) | enum rec_modes { R_MULTICHANNEL, R_SCAN }
  type device_t (line 272) | struct device_t {
  type mixinput_t (line 294) | struct mixinput_t {
  type mixer_t (line 304) | struct mixer_t {
  type demod_params_t (line 316) | struct demod_params_t {
  type output_params_t (line 328) | struct output_params_t {
  type timeval (line 367) | struct timeval
  type freq_tag (line 368) | struct freq_tag

FILE: src/squelch.h
  function class (line 69) | class Squelch {

FILE: src/test_base_class.cpp
  function delete_directory (line 28) | void delete_directory(const string& root) {
  function string (line 65) | string make_temp_dir(void) {
  function TEST (line 93) | TEST(TestHelpers, make_temp_dir) {
  function TEST (line 108) | TEST(TestHelpers, delete_directory) {

FILE: src/test_base_class.h
  function class (line 27) | class TestBaseClass : public ::testing::Test {

FILE: src/test_ctcss.cpp
  class CTCSSTest (line 27) | class CTCSSTest : public TestBaseClass {
    method SetUp (line 33) | void SetUp(void) {
    method write_file (line 40) | void write_file(const vector<float>& samples, const string& filepath) {
    method load_from_file (line 51) | void load_from_file(CTCSS& ctcss, const string& filepath) {
    method test_all_tones (line 66) | void test_all_tones(GenerateSignal& signal, const float& tone = 0) {
    method run_signal (line 112) | void run_signal(CTCSS& ctcss, GenerateSignal& signal, vector<float>& s...
  function TEST_F (line 122) | TEST_F(CTCSSTest, creation) {
  function TEST_F (line 127) | TEST_F(CTCSSTest, no_signal) {
  function TEST_F (line 132) | TEST_F(CTCSSTest, has_tone) {
  function TEST_F (line 140) | TEST_F(CTCSSTest, has_non_standard_tone) {
  function TEST_F (line 148) | TEST_F(CTCSSTest, has_each_standard_tone) {

FILE: src/test_filters.cpp
  class FiltersTest (line 26) | class FiltersTest : public TestBaseClass {
    method SetUp (line 28) | void SetUp(void) { TestBaseClass::SetUp(); }
    method TearDown (line 30) | void TearDown(void) { TestBaseClass::TearDown(); }
  function TEST_F (line 33) | TEST_F(FiltersTest, default_notch) {
  function TEST_F (line 38) | TEST_F(FiltersTest, default_lowpass) {

FILE: src/test_generate_signal.cpp
  class ToneTest (line 28) | class ToneTest : public TestBaseClass {}
  function TEST_F (line 30) | TEST_F(ToneTest, simple_object) {
  function TEST_F (line 102) | TEST_F(ToneTest, strengths) {
  class NoiseTest (line 129) | class NoiseTest : public TestBaseClass {}
  function TEST_F (line 131) | TEST_F(NoiseTest, simple_object) {
  function TEST_F (line 153) | TEST_F(NoiseTest, strengths) {
  class GenerateSignalTest (line 172) | class GenerateSignalTest : public TestBaseClass {
    method SetUp (line 175) | void SetUp(void) {
  function TEST_F (line 181) | TEST_F(GenerateSignalTest, default_object) {
  function TEST_F (line 186) | TEST_F(GenerateSignalTest, generate_file) {
  function TEST_F (line 200) | TEST_F(GenerateSignalTest, get_sample_no_signals) {
  function TEST_F (line 207) | TEST_F(GenerateSignalTest, get_sample_single_tone_only) {
  function TEST_F (line 219) | TEST_F(GenerateSignalTest, get_sample_two_tones) {
  function TEST_F (line 235) | TEST_F(GenerateSignalTest, get_sample_only_noise) {
  function TEST_F (line 254) | TEST_F(GenerateSignalTest, get_sample_two_tones_and_noise) {

FILE: src/test_helper_functions.cpp
  class HelperFunctionsTest (line 26) | class HelperFunctionsTest : public TestBaseClass {
    method SetUp (line 28) | void SetUp(void) { TestBaseClass::SetUp(); }
    method create_file (line 30) | void create_file(const string& filepath) {
  function TEST_F (line 36) | TEST_F(HelperFunctionsTest, dir_exists_true) {
  function TEST_F (line 40) | TEST_F(HelperFunctionsTest, dir_exists_false) {
  function TEST_F (line 44) | TEST_F(HelperFunctionsTest, dir_exists_not_dir) {
  function TEST_F (line 50) | TEST_F(HelperFunctionsTest, file_exists_true) {
  function TEST_F (line 56) | TEST_F(HelperFunctionsTest, file_exists_false) {
  function TEST_F (line 60) | TEST_F(HelperFunctionsTest, file_exists_not_file) {
  function TEST_F (line 65) | TEST_F(HelperFunctionsTest, make_dir_normal) {
  function TEST_F (line 72) | TEST_F(HelperFunctionsTest, make_dir_exists) {
  function TEST_F (line 78) | TEST_F(HelperFunctionsTest, make_dir_empty) {
  function TEST_F (line 82) | TEST_F(HelperFunctionsTest, make_dir_fail) {
  function TEST_F (line 86) | TEST_F(HelperFunctionsTest, make_dir_file_in_the_way) {
  function TEST_F (line 92) | TEST_F(HelperFunctionsTest, make_subdirs_exists) {
  function TEST_F (line 98) | TEST_F(HelperFunctionsTest, make_subdirs_one_subdir) {
  function TEST_F (line 105) | TEST_F(HelperFunctionsTest, make_subdirs_multiple_subdir) {
  function TEST_F (line 112) | TEST_F(HelperFunctionsTest, make_subdirs_file_in_the_way) {
  function TEST_F (line 121) | TEST_F(HelperFunctionsTest, make_subdirs_create_base) {
  function TEST_F (line 127) | TEST_F(HelperFunctionsTest, make_subdirs_extra_slashes) {
  function TEST_F (line 133) | TEST_F(HelperFunctionsTest, make_dated_subdirs_normal) {
  function TEST_F (line 145) | TEST_F(HelperFunctionsTest, make_dated_subdirs_fail) {
  function TEST_F (line 153) | TEST_F(HelperFunctionsTest, make_dated_subdirs_some_exist) {

FILE: src/test_squelch.cpp
  class SquelchTest (line 27) | class SquelchTest : public TestBaseClass {
    method SetUp (line 29) | void SetUp(void) {
    method TearDown (line 36) | void TearDown(void) { TestBaseClass::TearDown(); }
    method send_samples_for_noise_floor (line 39) | void send_samples_for_noise_floor(Squelch& squelch) {
  function TEST_F (line 51) | TEST_F(SquelchTest, default_object) {
  function TEST_F (line 56) | TEST_F(SquelchTest, noise_floor) {
  function TEST_F (line 81) | TEST_F(SquelchTest, normal_operation) {
  function TEST_F (line 111) | TEST_F(SquelchTest, dead_spot) {
  function TEST_F (line 145) | TEST_F(SquelchTest, should_process_audio) {
  function TEST_F (line 167) | TEST_F(SquelchTest, good_ctcss) {
  function TEST_F (line 205) | TEST_F(SquelchTest, wrong_ctcss) {
  function TEST_F (line 236) | TEST_F(SquelchTest, close_ctcss) {

FILE: src/udp_stream.cpp
  function udp_stream_init (line 29) | bool udp_stream_init(udp_stream_data* sdata, mix_modes mode, size_t len) {
  function udp_stream_write (line 86) | void udp_stream_write(udp_stream_data* sdata, const float* data, size_t ...
  function udp_stream_write (line 93) | void udp_stream_write(udp_stream_data* sdata, const float* data_left, co...
  function udp_stream_shutdown (line 104) | void udp_stream_shutdown(udp_stream_data* sdata) {

FILE: src/util.cpp
  function atomic_inc (line 35) | int atomic_inc(volatile int* pv) {
  function atomic_dec (line 39) | int atomic_dec(volatile int* pv) {
  function atomic_get (line 43) | int atomic_get(volatile int* pv) {
  function tag_queue_put (line 47) | void tag_queue_put(device_t* dev, int freq, struct timeval tv) {
  function tag_queue_get (line 60) | void tag_queue_get(device_t* dev, struct freq_tag* tag) {
  function tag_queue_advance (line 78) | void tag_queue_advance(device_t* dev) {
  function sincosf_lut_init (line 105) | void sincosf_lut_init() {
  function sincosf_lut (line 113) | void sincosf_lut(uint32_t phi, float* sine, float* cosine) {
  function atofs (line 130) | double atofs(char* s) {
  function delta_sec (line 157) | double delta_sec(const timeval* start, const timeval* stop) {
  function dBFS_to_level (line 174) | float dBFS_to_level(const float& dBFS) {
  function level_to_dBFS (line 178) | float level_to_dBFS(const float& level) {
Condensed preview — 109 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (973K chars).
[
  {
    "path": ".clang-format",
    "chars": 83,
    "preview": "---\nBasedOnStyle: Chromium\nIndentWidth: 4\nObjCBlockIndentWidth: 4\nColumnLimit: 200\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "chars": 581,
    "preview": "FROM ubuntu:latest\n\nRUN sed -i 's/^# \\(.*export LS_OPTIONS.*$\\)/\\1/g' ~/.bashrc && \\\n    sed -i 's/^# \\(.*alias ll.*$\\)/"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 1116,
    "preview": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:\n// https://github.co"
  },
  {
    "path": ".devcontainer/shell",
    "chars": 212,
    "preview": "#!/bin/bash -e\n\ncd `dirname $0`/../\n\n# build container\ndocker build -t rtl_airband-dev -f .devcontainer/Dockerfile .\n\n# "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 906,
    "preview": "---\nname: Bug report\nabout: Report a bug you found when using RTLSDR-Airband\ntitle: \"[BUG]\"\nlabels: ''\nassignees: ''\n\n--"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 201,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions & Help\n    url: https://github.com/rtl-airband/RTLSDR-Air"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 460,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE]\"\nlabels: ''\nassignees: ''\n\n---\n\n**Is"
  },
  {
    "path": ".github/install_dependencies",
    "chars": 1537,
    "preview": "#!/bin/bash\n\nunameOut=\"$(uname -s)\"\n\necho \"Running on ${unameOut} as ${USER}\"\n\ncase \"${unameOut}\" in\n    Linux*)\n       "
  },
  {
    "path": ".github/platform_build",
    "chars": 793,
    "preview": "#!/bin/bash -e\n\nplatform=\"${1}\"\n\nif [ -z \"${platform}\" ]; then\n    echo \"Error: platform not set\"\n    exit -1\nfi\n\necho \""
  },
  {
    "path": ".github/workflows/build_docker_containers.yml",
    "chars": 4011,
    "preview": "name: Build and Publish Containers\n\non:\n  push:\n    branches: [main, unstable]\n    tags: ['v*']\n  pull_request:\n  workfl"
  },
  {
    "path": ".github/workflows/ci_build.yml",
    "chars": 1862,
    "preview": "name: Run CI\n\non:\n  push:\n    branches: [main]\n    tags: ['v*']\n  pull_request:\n  workflow_dispatch:\n  schedule:\n    - c"
  },
  {
    "path": ".github/workflows/code_formatting.yml",
    "chars": 449,
    "preview": "name: Code Formatting\n\non:\n  pull_request:\n  schedule:\n    - cron: '39 13 * * *' # run daily\n\njobs:\n  code_formatting:\n "
  },
  {
    "path": ".github/workflows/platform_build.yml",
    "chars": 742,
    "preview": "name: Platform Build\n\non:\n  push:\n    branches: [main]\n    tags: ['v*']\n  pull_request:\n  workflow_dispatch:\n  schedule:"
  },
  {
    "path": ".github/workflows/version_bump.yml",
    "chars": 1644,
    "preview": "name: Bump version\non:\n  pull_request:\n    types:\n      - closed\n    branches:\n      - main\njobs:\n  version_bump:\n    if"
  },
  {
    "path": ".gitignore",
    "chars": 64,
    "preview": "build*/\n.DS_Store\n.cache\ncompile_commands.json\nrtl_airband*.log\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 355,
    "preview": "repos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.5.0\n    hooks:\n    - id: check-yaml\n    - id"
  },
  {
    "path": ".vscode/c_cpp_properties.json",
    "chars": 572,
    "preview": "{\n    \"configurations\": [\n        {\n            \"name\": \"Linux\",\n            \"includePath\": [\n                \"${workspa"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1110,
    "preview": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 206,
    "preview": "{\n    \"editor.formatOnPaste\": true,\n    \"editor.formatOnSave\": true,\n    \"editor.formatOnType\": true,\n    \"editor.defaul"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 1347,
    "preview": "cmake_minimum_required (VERSION 3.1...3.18 FATAL_ERROR)\nproject (RTLSDR-Airband CXX)\n\nexecute_process(COMMAND ${PROJECT_"
  },
  {
    "path": "Dockerfile",
    "chars": 3274,
    "preview": "# build container\nFROM debian:bookworm-slim AS build\n\n# install build dependencies\nRUN apt-get update && \\\n    apt-get u"
  },
  {
    "path": "LICENSE",
    "chars": 14800,
    "preview": "GNU GENERAL PUBLIC LICENSE\n\nVersion 2, June 1991\n\nCopyright (C) 1989, 1991 Free Software Foundation, Inc.\n<https://fsf.o"
  },
  {
    "path": "NEWS.md",
    "chars": 18173,
    "preview": "# NEWS\n\nThis file will no longer be updated with each release, for changes between releases, see PRs merged to the repo\n"
  },
  {
    "path": "README.md",
    "chars": 4774,
    "preview": "# RTLSDR-Airband\n\n![main](https://github.com/rtl-airband/RTLSDR-Airband/actions/workflows/ci_build.yml/badge.svg?branch="
  },
  {
    "path": "config/basic_multichannel.conf",
    "chars": 1142,
    "preview": "# This is a minimalistic configuration file for RTLSDR-Airband.\n# Just a single RTL dongle with two AM channels in multi"
  },
  {
    "path": "config/basic_scanning.conf",
    "chars": 991,
    "preview": "# Scanning mode example\n# Single dongle, three frequencies, output to Icecast server and to a file.\n# Refer to https://g"
  },
  {
    "path": "config/big_mixer.conf",
    "chars": 6842,
    "preview": "mixers: {\n  big_mixer: {\n    outputs: (\n      {\n        type = \"file\";\n        directory = \"./\";\n        filename_templa"
  },
  {
    "path": "config/mixers.conf",
    "chars": 3014,
    "preview": "# This config file demonstrates the usage of mixers.\n# First, two mixers are defined:\n#\n# - mixer1: sends the mixed stre"
  },
  {
    "path": "config/noaa.conf",
    "chars": 3169,
    "preview": "fft_size = 1024;\nlocaltime = true;\nmultiple_demod_threads = true;\nmultiple_output_threads = true;\ndevices:\n(\n  {\n    typ"
  },
  {
    "path": "config/two_dongles_multiple_outputs.conf",
    "chars": 2859,
    "preview": "# Example configuration file for 2 dongles.\n# First dongle - scanning mode, NFM modulation, three frequencies,\n# output "
  },
  {
    "path": "init.d/rtl_airband-debian.sh",
    "chars": 3031,
    "preview": "#! /bin/sh\n### BEGIN INIT INFO\n# Provides:          rtl_airband\n# Required-Start:    $remote_fs $syslog\n# Required-Stop:"
  },
  {
    "path": "init.d/rtl_airband-freebsd.sh",
    "chars": 236,
    "preview": "#!/bin/sh\n\n# PROVIDE: rtl_airband\n# REQUIRE: DAEMON\n# BEFORE: LOGIN\n# KEYWORD: nojail shutdown\n\n. /etc/rc.subr\n\nname=rtl"
  },
  {
    "path": "init.d/rtl_airband-gentoo.sh",
    "chars": 973,
    "preview": "#!/sbin/runscript\n# rtl_airband Gentoo startup script\n# (c) 2015 Tomasz Lemiech <szpajder@gmail.com>\n\nRTLAIRBAND_CONFDIR"
  },
  {
    "path": "init.d/rtl_airband.service",
    "chars": 613,
    "preview": "[Unit]\nDescription=SDR AM/NFM demodulator\nDocumentation=https://github.com/rtl-airband/RTLSDR-Airband/wiki\nWants=network"
  },
  {
    "path": "scripts/find_version",
    "chars": 843,
    "preview": "#!/bin/bash\n\nPROJECT_ROOT_PATH=\"$(cd $(dirname \"$0\")/../ ; pwd)\"\nPROJECT_GIT_DIR_PATH=\"${PROJECT_ROOT_PATH}/.git\"\nPROJEC"
  },
  {
    "path": "scripts/reformat_code",
    "chars": 99,
    "preview": "#!/bin/bash\n\nfind src/*.h src/*.cpp src/hello_fft/*.h src/hello_fft/*.c | xargs clang-format-14 -i\n"
  },
  {
    "path": "src/.gitignore",
    "chars": 9,
    "preview": "config.h\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "chars": 11987,
    "preview": "include(CheckCXXCompilerFlag)\ninclude(CheckCXXSymbolExists)\n\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_SOURCE_DIR}/"
  },
  {
    "path": "src/CMakeModules/FindBCM_VC.cmake",
    "chars": 664,
    "preview": "if(NOT BCM_VC_FOUND)\n\n\tset(BCM_VC_PATH \"/opt/vc\" CACHE STRING \"List of paths to search for Broadcom VideoCore library\")\n"
  },
  {
    "path": "src/CMakeModules/FindLame.cmake",
    "chars": 544,
    "preview": "FIND_PATH(LAME_INCLUDE_DIR lame/lame.h)\nFIND_LIBRARY(LAME_LIBRARIES NAMES mp3lame)\n\nIF(LAME_INCLUDE_DIR AND LAME_LIBRARI"
  },
  {
    "path": "src/CMakeModules/FindMiriSDR.cmake",
    "chars": 1388,
    "preview": "# - Try to find mirisdr - the hardware driver for Mirics chip in the dvb receivers\n# Once done this will define\n#  MIRIS"
  },
  {
    "path": "src/CMakeModules/FindRTLSDR.cmake",
    "chars": 2265,
    "preview": "#\n# Copyright 2012-2013 The Iris Project Developers. See the\n# COPYRIGHT file at the top-level directory of this distrib"
  },
  {
    "path": "src/CMakeModules/version.cmake",
    "chars": 344,
    "preview": "set (VERSION \"char const *RTL_AIRBAND_VERSION=\\\"${RTL_AIRBAND_VERSION}\\\";\\n\")\n\nif(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/ver"
  },
  {
    "path": "src/config.cpp",
    "chars": 46613,
    "preview": "/*\n * config.cpp\n * Configuration parsing routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n"
  },
  {
    "path": "src/config.h.in",
    "chars": 1162,
    "preview": "/*\n * config.h.in\n * Template for cmake-generated config.h\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail."
  },
  {
    "path": "src/ctcss.cpp",
    "chars": 5794,
    "preview": "/*\n * ctcss.h\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n *\n * This program is free software; you can redistribute it"
  },
  {
    "path": "src/ctcss.h",
    "chars": 2600,
    "preview": "/*\n * ctcss.h\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n *\n * This program is free software; you can redistribute it"
  },
  {
    "path": "src/filters.cpp",
    "chars": 5112,
    "preview": "/*\n * filters.cpp\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n *\n * This program is free software; you can redistribut"
  },
  {
    "path": "src/filters.h",
    "chars": 1919,
    "preview": "/*\n * filters.h\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n *\n * This program is free software; you can redistribute "
  },
  {
    "path": "src/generate_signal.cpp",
    "chars": 2553,
    "preview": "/*\n * generate_signal.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistri"
  },
  {
    "path": "src/generate_signal.h",
    "chars": 1791,
    "preview": "/*\n * generate_signal.h\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistribu"
  },
  {
    "path": "src/hello_fft/CMakeLists.txt",
    "chars": 593,
    "preview": "set(hello_fft_source_files\n\tmailbox.c\n\tgpu_fft.c\n\tgpu_fft_twiddles.c\n\tgpu_fft_shaders.c\n\tgpu_fft_base.c\n)\n# Temp hack du"
  },
  {
    "path": "src/hello_fft/gpu_fft.c",
    "chars": 4930,
    "preview": "/*\nBCM2835 \"GPU_FFT\" release 2.0\nCopyright (c) 2014, Andrew Holme.\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "src/hello_fft/gpu_fft.h",
    "chars": 3116,
    "preview": "/*\nBCM2835 \"GPU_FFT\" release 2.0\nCopyright (c) 2014, Andrew Holme.\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "src/hello_fft/gpu_fft.txt",
    "chars": 5830,
    "preview": "BCM2835 \"GPU_FFT\" release 2.0 by Andrew Holme, 2014.\n\nGPU_FFT is an FFT library for the Raspberry Pi which exploits the "
  },
  {
    "path": "src/hello_fft/gpu_fft_base.c",
    "chars": 4723,
    "preview": "/*\nBCM2835 \"GPU_FFT\" release 2.0\nCopyright (c) 2014, Andrew Holme.\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "src/hello_fft/gpu_fft_shaders.c",
    "chars": 3321,
    "preview": "/*\nBCM2835 \"GPU_FFT\" release 2.0\nCopyright (c) 2014, Andrew Holme.\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "src/hello_fft/gpu_fft_trans.h",
    "chars": 1895,
    "preview": "/*\nBCM2835 \"GPU_FFT\" release 2.0\nCopyright (c) 2014, Andrew Holme.\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "src/hello_fft/gpu_fft_twiddles.c",
    "chars": 9449,
    "preview": "/*\nBCM2835 \"GPU_FFT\" release 2.0\nCopyright (c) 2014, Andrew Holme.\nAll rights reserved.\n\nRedistribution and use in sourc"
  },
  {
    "path": "src/hello_fft/hex/shader_1024k.hex",
    "chars": 39845,
    "preview": "0x00000014, 0xe0021227, // mov rb_STAGES,  STAGES\n0x00000010, 0xe00216e7, // mov rb_0x10,    0x10\n0x00000040, 0xe0021727"
  },
  {
    "path": "src/hello_fft/hex/shader_128k.hex",
    "chars": 33149,
    "preview": "0x00000011, 0xe0021227, // mov rb_STAGES,  STAGES\n0x00000010, 0xe00216a7, // mov rb_0x10,    0x10\n0x00000040, 0xe00216e7"
  },
  {
    "path": "src/hello_fft/hex/shader_16k.hex",
    "chars": 30772,
    "preview": "0x00000010, 0xe00216e7, // mov rb_0x10,    0x10\n0x00000040, 0xe0021727, // mov rb_0x40,    0x40\n0x00000080, 0xe0021767, "
  },
  {
    "path": "src/hello_fft/hex/shader_1k.hex",
    "chars": 24437,
    "preview": "0x00000010, 0xe00216e7, // mov rb_0x10,    0x10\n0x00000040, 0xe0021727, // mov rb_0x40,    0x40\n0x000000f0, 0xe0021767, "
  },
  {
    "path": "src/hello_fft/hex/shader_2048k.hex",
    "chars": 59955,
    "preview": "0x00000010, 0xe0021227, // mov rb_0x10,    0x10\n0x000001d0, 0xe0021967, // mov r5rep,      0x1D0\n0x00000080, 0xe00208e7,"
  },
  {
    "path": "src/hello_fft/hex/shader_256.hex",
    "chars": 17416,
    "preview": "0x00000040, 0xe00217a7, // mov rb_0x40,    0x40\n0x00000080, 0xe00217e7, // mov rb_0x80,    0x80\n0x00005555, 0xe0020767, "
  },
  {
    "path": "src/hello_fft/hex/shader_256k.hex",
    "chars": 38480,
    "preview": "0x00000012, 0xe0021227, // mov rb_STAGES,  STAGES\n0x00000010, 0xe00216a7, // mov rb_0x10,    0x10\n0x00000040, 0xe00216e7"
  },
  {
    "path": "src/hello_fft/hex/shader_2k.hex",
    "chars": 35556,
    "preview": "0x00000010, 0xe0021727, // mov rb_0x10,    0x10\n0x00000040, 0xe0021767, // mov rb_0x40,    0x40\n0x000000f0, 0xe00217a7, "
  },
  {
    "path": "src/hello_fft/hex/shader_32k.hex",
    "chars": 29901,
    "preview": "0x00000010, 0xe00216e7, // mov rb_0x10,    0x10\n0x00000040, 0xe0021727, // mov rb_0x40,    0x40\n0x00000080, 0xe0021767, "
  },
  {
    "path": "src/hello_fft/hex/shader_4k.hex",
    "chars": 24505,
    "preview": "0x00000020, 0xe0021767, // mov rb_0x20,    0x20\n0x00000040, 0xe00217a7, // mov rb_0x40,    0x40\n0x00000080, 0xe00217e7, "
  },
  {
    "path": "src/hello_fft/hex/shader_512.hex",
    "chars": 23907,
    "preview": "0x00000010, 0xe0021727, // mov rb_0x10,    0x10\n0x00000040, 0xe0021767, // mov rb_0x40,    0x40\n0x00000080, 0xe00217a7, "
  },
  {
    "path": "src/hello_fft/hex/shader_512k.hex",
    "chars": 43438,
    "preview": "0x00000013, 0xe0021227, // mov rb_STAGES,  STAGES\n0x00000010, 0xe00216e7, // mov rb_0x10,    0x10\n0x00000040, 0xe0021727"
  },
  {
    "path": "src/hello_fft/hex/shader_64k.hex",
    "chars": 41120,
    "preview": "0x00000010, 0xe0021227, // mov rb_0x10,    0x10\n0x000001d0, 0xe0021967, // mov r5rep,      0x1D0\n0x00005555, 0xe00207a7,"
  },
  {
    "path": "src/hello_fft/hex/shader_8k.hex",
    "chars": 28018,
    "preview": "0x00000010, 0xe00216e7, // mov rb_0x10,    0x10\n0x00000040, 0xe0021727, // mov rb_0x40,    0x40\n0x00000080, 0xe0021767, "
  },
  {
    "path": "src/hello_fft/hex/shader_trans.hex",
    "chars": 6005,
    "preview": "0x15827d80, 0x10020e27, // mov t0s, unif\n0x009e7000, 0xa00009e7, // ldtmu0\n0x0c9cc9c0, 0xd0020e27, // add t0s, r4, 3*4\n0"
  },
  {
    "path": "src/hello_fft/mailbox.c",
    "chars": 7224,
    "preview": "/*\nCopyright (c) 2012, Broadcom Europe Ltd.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, wit"
  },
  {
    "path": "src/hello_fft/mailbox.h",
    "chars": 2380,
    "preview": "/*\nCopyright (c) 2012, Broadcom Europe Ltd.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, wit"
  },
  {
    "path": "src/helper_functions.cpp",
    "chars": 2684,
    "preview": "/*\n * helper_functions.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistr"
  },
  {
    "path": "src/helper_functions.h",
    "chars": 1157,
    "preview": "/*\n * helper_functions.h\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistrib"
  },
  {
    "path": "src/input-common.cpp",
    "chars": 3730,
    "preview": "/*\n * input-common.cpp\n * common input handling routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.co"
  },
  {
    "path": "src/input-common.h",
    "chars": 2256,
    "preview": "/*\n * input-common.h\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n * This program is free softwa"
  },
  {
    "path": "src/input-file.cpp",
    "chars": 6161,
    "preview": "/*\n * input-file.cpp\n * binary file specific routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n"
  },
  {
    "path": "src/input-file.h",
    "chars": 936,
    "preview": "/*\n *  input-file.h\n *  RTLSDR-specific declarations\n *\n *  Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n"
  },
  {
    "path": "src/input-helpers.cpp",
    "chars": 2936,
    "preview": "/*\n * input-helpers.cpp\n * Convenience functions to be called by input drivers\n *\n * Copyright (c) 2015-2021 Tomasz Lemi"
  },
  {
    "path": "src/input-helpers.h",
    "chars": 935,
    "preview": "/*\n * input-helpers.h\n * Convenience functions to be called by input drivers\n *\n * Copyright (c) 2015-2021 Tomasz Lemiec"
  },
  {
    "path": "src/input-mirisdr.cpp",
    "chars": 8393,
    "preview": "/*\n *  input-mirisdr.cpp\n *  MiriSDR-specific routines\n *\n *  Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com"
  },
  {
    "path": "src/input-mirisdr.h",
    "chars": 1315,
    "preview": "/*\n *  input-mirisdr.h\n *  MiriSDR-specific declarations\n *\n *  Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.c"
  },
  {
    "path": "src/input-rtlsdr.cpp",
    "chars": 9078,
    "preview": "/*\n * input-rtlsdr.cpp\n * RTLSDR-specific routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n"
  },
  {
    "path": "src/input-rtlsdr.h",
    "chars": 1258,
    "preview": "/*\n *  input-rtlsdr.h\n *  RTLSDR-specific declarations\n *\n *  Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com"
  },
  {
    "path": "src/input-soapysdr.cpp",
    "chars": 17517,
    "preview": "/*\n *  input-soapysdr.cpp\n *  SoapySDR-specific routines\n *\n *  Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.c"
  },
  {
    "path": "src/input-soapysdr.h",
    "chars": 1521,
    "preview": "/*\n *  input-soapysdr.h\n *  SoapySDR-specific declarations\n *\n *  Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail"
  },
  {
    "path": "src/logging.cpp",
    "chars": 1821,
    "preview": "/*\n * logging.cpp\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n * Copyright (c) 2015-2022 Tomasz Lemiech <szpajder@gmai"
  },
  {
    "path": "src/logging.h",
    "chars": 1848,
    "preview": "/*\n * logging.h\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n * Copyright (c) 2015-2022 Tomasz Lemiech <szpajder@gmail."
  },
  {
    "path": "src/mixer.cpp",
    "chars": 10280,
    "preview": "/*\n * mixer.cpp\n * Mixer related routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n * This p"
  },
  {
    "path": "src/output.cpp",
    "chars": 40114,
    "preview": "/*\n * output.cpp\n * Output related routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n * This"
  },
  {
    "path": "src/pulse.cpp",
    "chars": 9860,
    "preview": "/*\n * pulse.cpp\n * PulseAudio output routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n * Th"
  },
  {
    "path": "src/rtl_airband.cpp",
    "chars": 43321,
    "preview": "/*\n * RTLSDR AM/NFM demodulator, mixer, streamer and recorder\n *\n * Copyright (c) 2014 Wong Man Hang <microtony@gmail.co"
  },
  {
    "path": "src/rtl_airband.h",
    "chars": 11126,
    "preview": "/*\n * rtl_airband.h\n * Global declarations\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n * This "
  },
  {
    "path": "src/rtl_airband_neon.s",
    "chars": 1841,
    "preview": "#\n# RTLSDR AM demodulator and streaming\n#\n# Copyright (c) 2014 Wong Man Hang <microtony@gmail.com>\n#\n# Updates for NEON "
  },
  {
    "path": "src/squelch.cpp",
    "chars": 21995,
    "preview": "/*\n * squelch.cpp\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n *\n * This program is free software; you can redistribut"
  },
  {
    "path": "src/squelch.h",
    "chars": 7605,
    "preview": "/*\n * squelch.h\n *\n * Copyright (C) 2022-2023 charlie-foxtrot\n *\n * This program is free software; you can redistribute "
  },
  {
    "path": "src/test_base_class.cpp",
    "chars": 3783,
    "preview": "/*\n * test_base_class.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistri"
  },
  {
    "path": "src/test_base_class.h",
    "chars": 990,
    "preview": "/*\n * test_base_class.h\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistribu"
  },
  {
    "path": "src/test_ctcss.cpp",
    "chars": 5010,
    "preview": "/*\n * test_ctcss.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistribute "
  },
  {
    "path": "src/test_filters.cpp",
    "chars": 1161,
    "preview": "/*\n * test_filters.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistribut"
  },
  {
    "path": "src/test_generate_signal.cpp",
    "chars": 8940,
    "preview": "/*\n * test_generate_signal.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can red"
  },
  {
    "path": "src/test_helper_functions.cpp",
    "chars": 5200,
    "preview": "/*\n * test_output.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistribute"
  },
  {
    "path": "src/test_squelch.cpp",
    "chars": 9762,
    "preview": "/*\n * test_squelch.cpp\n *\n * Copyright (C) 2023 charlie-foxtrot\n *\n * This program is free software; you can redistribut"
  },
  {
    "path": "src/udp_stream.cpp",
    "chars": 4024,
    "preview": "/*\n * udp_stream.cpp\n *\n * Copyright (C) 2024 charlie-foxtrot\n *\n * This program is free software; you can redistribute "
  },
  {
    "path": "src/util.cpp",
    "chars": 5253,
    "preview": "/*\n * util.cpp\n * Miscellaneous routines\n *\n * Copyright (c) 2015-2021 Tomasz Lemiech <szpajder@gmail.com>\n *\n * This pr"
  }
]

About this extraction

This page contains the full source code of the szpajder/RTLSDR-Airband GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 109 files (919.5 KB), approximately 382.4k tokens, and a symbol index with 313 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!