summaryrefslogtreecommitdiff
path: root/stacks/crdop/scripts/crdopc
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:23:28 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:23:28 +0000
commitfa05a5f8238e9e1417235711656e5062ccc843a7 (patch)
tree46fbbd18de54492b59307c16efcc7f3152723238 /stacks/crdop/scripts/crdopc
Initial push
Diffstat (limited to 'stacks/crdop/scripts/crdopc')
-rwxr-xr-xstacks/crdop/scripts/crdopc171
1 files changed, 171 insertions, 0 deletions
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[@]}" "$@"
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com