summaryrefslogtreecommitdiff
path: root/stacks/crdop/scripts/crdopc
blob: 2fc82bd08bac34580aadbb7306fd08995cea80c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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