From 04d965d67a7264a1c7c211494aebda1953df7603 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Fri, 7 Aug 2026 18:25:13 +0000 Subject: Initial push --- stacks/crdop/scripts/crdopc | 171 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100755 stacks/crdop/scripts/crdopc (limited to 'stacks/crdop/scripts/crdopc') diff --git a/stacks/crdop/scripts/crdopc b/stacks/crdop/scripts/crdopc new file mode 100755 index 0000000..2fc82bd --- /dev/null +++ b/stacks/crdop/scripts/crdopc @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# CRDOP launcher — native MAX25-SoftModem (audio-dummyd / M25 host). +# ARDOP is a separate optional MAX25-Stack plugin — not part of CRDOP. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +MAX25_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +PREFIX_SHARE="${SCRIPT_DIR}/../share/crdop" + +if [[ -n "${CRDOP_INI:-}" ]]; then + INI="${CRDOP_INI}" +elif [[ -f "${HOME}/.config/crdop/crdop.ini" ]]; then + INI="${HOME}/.config/crdop/crdop.ini" +elif [[ -f "${PREFIX_SHARE}/crdop.ini.example" ]]; then + INI="${PREFIX_SHARE}/crdop.ini.example" +else + INI="${ROOT}/share/crdop.ini.example" +fi + +ini_get() { + local section="$1" key="$2" default="$3" file="$4" + [[ -f "${file}" ]] || { echo "${default}"; return; } + awk -F= -v sec="[${section}]" -v k="${key}" -v d="${default}" ' + $0 ~ /^[[:space:]]*[#;]/ { next } + $1 ~ /^\[/ { cur=$0; gsub(/[][]/, "", cur); next } + cur == substr(sec, 2, length(sec)-2) { + gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1) + if ($1 == k) { + v=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", v); gsub(/^"|"$/, "", v) + print v; found=1; exit + } + } + END { if (!found) print d } + ' "${file}" +} + +resolve_ini() { + if [[ ! -f "${INI}" && -f "${ROOT}/share/crdop.ini.example" ]]; then + INI="${ROOT}/share/crdop.ini.example" + fi +} + +normalize_profile() { + case "${1,,}" in + cb|dual|amateur) echo "${1,,}" ;; + *) + echo "warn: unknown radio_profile '${1}', using cb" >&2 + echo "cb" + ;; + esac +} + +normalize_duplex() { + case "${1,,}" in + full|half) echo "${1,,}" ;; + *) + echo "warn: unknown duplex '${1}', using half" >&2 + echo "half" + ;; + esac +} + +enforce_host_audio() { + local backend + backend="$(ini_get audio backend "" "${INI}")" + if [[ -z "${backend}" ]]; then + case "$(uname -s)" in + FreeBSD) backend="oss" ;; + *) backend="alsa" ;; + esac + fi + if [[ "${backend}" == "alsa" ]]; then + local no_pulse + no_pulse="$(ini_get audio no_pulse yes "${INI}")" + case "${no_pulse,,}" in + 1|yes|true|on) + unset PULSE_SERVER PULSE_COOKIE PIPEWIRE_RUNTIME_DIR 2>/dev/null || true + export PULSE_SERVER= + export ALSA_CARD="${ALSA_CARD:-}" + ;; + esac + fi +} + +audio_cli_args() { + local cap pb backend + backend="$(ini_get audio backend "" "${INI}")" + if [[ -z "${backend}" ]]; then + case "$(uname -s)" in + FreeBSD) backend="oss" ;; + *) backend="alsa" ;; + esac + fi + cap="$(ini_get audio capture "" "${INI}")" + pb="$(ini_get audio playback "" "${INI}")" + local args=() + [[ -n "${cap}" ]] && args+=("${cap}") + [[ -n "${pb}" ]] && args+=("${pb}") + if [[ ${#args[@]} -gt 0 ]]; then + if [[ "${backend}" == "alsa" ]]; then + for a in "${args[@]}"; do + if [[ "${a}" == *pulse* || "${a}" == *pipewire* || "${a}" == default ]]; then + echo "error: audio device '${a}' — use hw:/plughw: kernel ALSA" >&2 + exit 1 + fi + done + fi + printf '%s\n' "${args[@]}" + elif [[ "${backend}" == "oss" ]]; then + echo "/dev/dsp" + echo "/dev/dsp" + fi +} + +find_audio_dummyd() { + for candidate in \ + "${MAX25_ROOT}/build/bin/audio-dummyd" \ + "${MAX25_ROOT}/stacks/crdop/tools/audio-dummyd" \ + "$(command -v audio-dummyd 2>/dev/null || true)"; do + [[ -n "${candidate}" && -x "${candidate}" ]] || continue + echo "${candidate}" + return + done + return 1 +} + +resolve_ini + +if [[ -f "${PREFIX_SHARE}/VERSION" ]]; then + CRDOP_VERSION="$(tr -d '[:space:]' < "${PREFIX_SHARE}/VERSION")" +elif [[ -f "${ROOT}/VERSION" ]]; then + CRDOP_VERSION="$(tr -d '[:space:]' < "${ROOT}/VERSION")" +else + CRDOP_VERSION="CUR999" +fi + +PROFILE="$(normalize_profile "$(ini_get profile radio_profile cb "${INI}")")" +DUPLEX="$(normalize_duplex "$(ini_get modem duplex half "${INI}")")" +MYCALL="$(ini_get mycall call NOCALL-0 "${INI}")" +HOST_PORT="$(ini_get host port 8515 "${INI}")" +DATA_PORT=$((HOST_PORT + 1)) +SAMPLE_RATE="$(ini_get audio sample_rate 48000 "${INI}")" + +if [[ $# -ge 1 && "${1}" =~ ^[0-9]+$ ]]; then + HOST_PORT="$1" + DATA_PORT=$((HOST_PORT + 1)) + shift +fi + +mapfile -t AUDIO_ARGS < <(audio_cli_args || true) +enforce_host_audio + +DUMMY="$(find_audio_dummyd || true)" +if [[ -z "${DUMMY}" ]]; then + echo "error: audio-dummyd not found — build MAX25 or set path" >&2 + exit 1 +fi + +DUMMY_ARGS=(--ctrl-port "${HOST_PORT}" --data-port "${DATA_PORT}" -r "${SAMPLE_RATE}") +if [[ ${#AUDIO_ARGS[@]} -ge 1 ]]; then + DUMMY_ARGS+=(-D "${AUDIO_ARGS[0]}") +fi +if [[ ${#AUDIO_ARGS[@]} -ge 2 ]]; then + DUMMY_ARGS+=(-P "${AUDIO_ARGS[1]}") +fi + +AUDIO_BACKEND="$(ini_get audio backend "" "${INI}")" +[[ -z "${AUDIO_BACKEND}" ]] && case "$(uname -s)" in FreeBSD) AUDIO_BACKEND="oss" ;; *) AUDIO_BACKEND="alsa" ;; esac +echo "CRDOP ${CRDOP_VERSION} profile=${PROFILE} duplex=${DUPLEX} ctrl=:${HOST_PORT} data=:${DATA_PORT} call=${MYCALL} audio=${AUDIO_BACKEND} (native M25)" +exec "${DUMMY}" "${DUMMY_ARGS[@]}" "$@" -- cgit v1.3.1