Add Audio tab toggle for transceiver built-in speaker

Adds a "Send audio stream to radio (built-in speaker)" checkbox on the
Settings → Audio tab. It is a single switch for the radio's built-in
speaker:

- ON  — RX audio is streamed to the radio via the DDC Audio packet
        (port 1028, 64 L/R pairs per packet) and the speaker is unmuted
        (High Priority byte 1400 bit1 = 0).
- OFF — speaker is muted (byte 1400 bit1 = 1, ANAN-8000DLE IO1 output)
        and no audio stream is sent.

The float->int16 conversion and 64-pair packet accumulation live in
THPSDRNetwork (SendSpeakerAudio/SetSpeakerAudio), next to SendDDCAudio;
MainForm only reads the setting and forwards the audio. State is
persisted per-MAC (send_audio_to_radio, default off) and re-applied on
each radio start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-02 08:55:36 +03:00
co-authored by Claude Opus 4.8
parent d8584323ac
commit aff17aa3a6
4 changed files with 122 additions and 0 deletions
+60
View File
@@ -112,6 +112,15 @@ type
FSeqAudio: LongWord;
FSeqDUCIQ: LongWord;
// Накопитель RX-аудио для встроенного динамика радио: DDC Audio пакет несёт
// ровно 64 L/R пары (128 SmallInt). Копим интерливом между вызовами, чтобы
// не вставлять тишину при некратном размере буфера WDSP.
FSpeakerBuf: array[0..127] of SmallInt;
FSpeakerCount: Integer;
// True = поток аудио шлётся И динамик радио включён (byte 1400 bit1 = 0);
// False = мьют динамика (bit1 = 1) и поток не накапливается.
FSpeakerAudioEnabled: Boolean;
FReceiveThread: TThread;
FKeepaliveThread: TThread;
FDUCIQThread: TThread;
@@ -233,6 +242,13 @@ type
// Не меняет FIsTransmitting — вызывать после UpdateState+SendFullHP.
procedure SendPTT(Active: Boolean);
procedure SendDDCAudio(const LeftRight: array of SmallInt);
// Принимает RX-аудио (float [-1..1]) из DSP, конвертирует в 16-bit и
// отправляет на радио пакетами по 64 L/R пары. Хвост копится между вызовами.
procedure SendSpeakerAudio(const Left, Right: array of Single; Count: Integer);
// Единый переключатель встроенного динамика: True = слать аудио и снять мьют,
// False = замьютить динамик (byte 1400 bit1) и не накапливать поток.
// Применяется немедленно (шлёт HP-пакет, если подключены).
procedure SetSpeakerAudio(AEnabled: Boolean);
procedure SendDUCIQ(const IData, QData: array of Integer);
procedure ClearDUCIQQueue;
@@ -1562,6 +1578,12 @@ begin
Buf[1400] := Buf[1400] or $01;
end;
// Audio mute (byte 1400 bit 1) — ANAN-8000DLE: IO1 output, 0=audio enabled,
// 1=mute. Когда отправка аудио в радио выключена, держим динамик в мьюте.
// На прочих платах бит «not assigned» и игнорируется.
if not FSpeakerAudioEnabled then
Buf[1400] := Buf[1400] or $02;
// Step attenuators ADC0/ADC1 (bytes 1443/1442)
// 0 dB = 0, max 31 dB
Buf[1443] := FStepAtten; // ADC0 attenuation
@@ -1610,6 +1632,44 @@ begin
DoSendTo(FSocket, Pkt, SizeOf(Pkt), FDevice.IPAddress, FPortDDCAudio);
end;
procedure THPSDRNetwork.SetSpeakerAudio(AEnabled: Boolean);
begin
FSpeakerAudioEnabled := AEnabled;
if not AEnabled then FSpeakerCount := 0; // сброс накопленного хвоста
if FConnected then SendFullHP; // применить мьют (byte 1400 bit1)
end;
procedure THPSDRNetwork.SendSpeakerAudio(const Left, Right: array of Single;
Count: Integer);
function ToS16(V: Single): SmallInt;
begin
if V > 1.0 then V := 1.0
else if V < -1.0 then V := -1.0;
Result := SmallInt(Round(V * 32767.0));
end;
var
i: Integer;
begin
if not (FSpeakerAudioEnabled and FConnected and FRunning) then
begin
FSpeakerCount := 0;
Exit;
end;
for i := 0 to Count - 1 do
begin
FSpeakerBuf[FSpeakerCount] := ToS16(Left[i]);
FSpeakerBuf[FSpeakerCount + 1] := ToS16(Right[i]);
Inc(FSpeakerCount, 2);
if FSpeakerCount >= Length(FSpeakerBuf) then
begin
SendDDCAudio(FSpeakerBuf);
FSpeakerCount := 0;
end;
end;
end;
procedure THPSDRNetwork.SendDUCIQ(const IData, QData: array of Integer);
var
Pkt: TDUCIQPacket;