diff --git a/HPSDRNetwork.pas b/HPSDRNetwork.pas index 0bbd229..c4f5e9c 100644 --- a/HPSDRNetwork.pas +++ b/HPSDRNetwork.pas @@ -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; diff --git a/MainForm.pas b/MainForm.pas index bbfb1f1..81f508c 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -162,6 +162,9 @@ type FAudioIn: TAudioInput; FAudioOutDevName: string; // текущее имя выходного устройства FAudioInDevName: string; // текущее имя входного устройства (TX mic) + // Отправка RX-аудио в трансивер (встроенный динамик радио, DDC Audio port 1028). + // Само накопление/конвертация живёт в THPSDRNetwork.SendSpeakerAudio. + FSendAudioToRadio: Boolean; FTXSettings: TTXSettings; // полный TX-конфиг (загружается per-MAC) FAlexSettings: TAlexSettings; // per-band antenna/routing config (Alex board) FXvtrSettings: TXvtrSettings; // transverter slots (per-device) @@ -724,6 +727,7 @@ type procedure ApplyAudioDevice(DevIndex: Integer; const DevName: string); procedure ApplyAudioInputDevice(DevIndex: Integer; const DevName: string); procedure ApplyAudioBufferSize(BufferSize: Integer); + procedure ApplySendAudioToRadio(Enabled: Boolean); // TX settings: применить FTXSettings к WDSP и переслать DUC Specific // (mic-биты Boost/Bias/LineIn/PTT попадают в byte 50 DUCSpecific). procedure ApplyTXSettingsToDSP; @@ -961,6 +965,7 @@ begin // Audio device names Result.AudioOutDevice := FAudioOutDevName; Result.AudioInDevice := FAudioInDevName; + Result.SendAudioToRadio := FSendAudioToRadio; // CAT settings — carried from the last loaded/saved device config Result.CATSerialEnabled := FCATLastGlobal.CATSerialEnabled; Result.CATSerialPort := FCATLastGlobal.CATSerialPort; @@ -4091,6 +4096,7 @@ begin FWfNFEnabled := G_Settings.WfNFEnabled; FDitherEnabled := G_Settings.DitherEnabled; FRandomEnabled := G_Settings.RandomEnabled; + FSendAudioToRadio := G_Settings.SendAudioToRadio; FWfManualHigh := G_Settings.WfManualHigh; FWfManualLow := G_Settings.WfManualLow; FWfAGCOffset := G_Settings.WfAGCOffset; @@ -4239,6 +4245,8 @@ begin // DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах. // DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings. FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); + // Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1). + FNetwork.SetSpeakerAudio(FSendAudioToRadio); SendDUCSpecificFromSettings; // Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...). ApplyTXSettingsToDSP; @@ -7366,6 +7374,9 @@ var MonoBuf: array[0..1023] of Single; i: Integer; begin + // Дублируем RX-аудио во встроенный динамик трансивера (DDC Audio, port 1028). + if FSendAudioToRadio then + FNetwork.SendSpeakerAudio(Left, Right, Count); if Assigned(FWebServer) and FWebServer.WebClientActive then begin for i := 0 to Count - 1 do @@ -7513,6 +7524,16 @@ begin FSettings.Save; end; +procedure TMainForm.ApplySendAudioToRadio(Enabled: Boolean); +begin + FSendAudioToRadio := Enabled; + // Один переключатель: вкл = слать поток + снять мьют динамика, + // выкл = замьютить динамик (byte 1400 bit1) и не слать поток. + FNetwork.SetSpeakerAudio(Enabled); + if FDevConnected then + FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); +end; + procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill, SpectrumFill: Boolean); begin @@ -7577,6 +7598,7 @@ begin SF.OnAudioDevChange := ApplyAudioDevice; SF.OnAudioInDevChange := ApplyAudioInputDevice; SF.OnAudioBufferChange := ApplyAudioBufferSize; + SF.OnSendAudioToRadioChange := ApplySendAudioToRadio; SF.OnVisibilityChange := ApplyVisibility; SF.OnFPSChange := ApplyFPS; SF.OnPAChange := OnPASettingsChange; @@ -7619,6 +7641,7 @@ begin SF.LoadLightTheme(FLightTheme); SF.LoadFreqMhzDigits(FFreqMhzDigits); SF.LoadAudioBufferSize(FAudioOut.OutputBufferSize); + SF.LoadSendAudioToRadio(FSendAudioToRadio); // Загружаем текущие значения if FWDSPReady then SF.LoadValues( diff --git a/Settings.pas b/Settings.pas index ec8f571..ff35110 100644 --- a/Settings.pas +++ b/Settings.pas @@ -219,6 +219,7 @@ type AudioOutDevice: string; // PortAudio output device name ('' = default) AudioSampleRate: Integer; // PortAudio stream sample rate (e.g. 48000) AudioInDevice: string; // PortAudio input device name for TX mic ('' = none) + SendAudioToRadio: Boolean; // True = send RX audio stream to radio (built-in speaker) // --- Visibility settings --- ShowSpectrum: Boolean; // True = draw spectrum on main form ShowWaterfall: Boolean; // True = draw waterfall on main form @@ -399,6 +400,7 @@ begin G.AudioOutDevice := ''; G.AudioSampleRate := 48000; G.AudioInDevice := ''; + G.SendAudioToRadio := False; G.ShowSpectrum := True; G.ShowWaterfall := True; G.ShowWideband := False; @@ -618,6 +620,7 @@ begin G.AudioOutDevice := JS(GObj,'audio_out_device',''); G.AudioSampleRate := JI(GObj,'audio_sample_rate',48000); G.AudioInDevice := JS(GObj,'audio_in_device',''); + G.SendAudioToRadio := JB(GObj,'send_audio_to_radio',False); // Visibility settings G.ShowSpectrum := JB(GObj,'show_spectrum',True); G.ShowWaterfall := JB(GObj,'show_waterfall',True); @@ -706,6 +709,7 @@ begin JWS(O,'audio_out_device',G.AudioOutDevice); JW(O,'audio_sample_rate',G.AudioSampleRate); JWS(O,'audio_in_device',G.AudioInDevice); + JW(O,'send_audio_to_radio',G.SendAudioToRadio); // Visibility settings JW(O,'show_spectrum',G.ShowSpectrum); JW(O,'show_waterfall',G.ShowWaterfall); diff --git a/SettingsForm.pas b/SettingsForm.pas index aa5fddc..fd22279 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -49,6 +49,7 @@ type TOnAudioDevChange = procedure(DevIndex: Integer; const DevName: string) of object; TOnAudioInDevChange = procedure(DevIndex: Integer; const DevName: string) of object; TOnAudioBufferChange = procedure(BufferSize: Integer) of object; + TOnSendAudioToRadioChange = procedure(Enabled: Boolean) of object; TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill, SpectrumFill: Boolean) of object; TOnFPSChange = procedure(FPS: Integer) of object; @@ -99,6 +100,7 @@ type FTXDevIndices: array[0..63] of Integer; FTXDevCount: Integer; FCmbAudioBuffer: TFlatComboBox; + FChkSendAudioToRadio: TFlatCheckBox; // ---- RX1 sub-tab controls ---- FCmbFFTSize: TFlatComboBox; @@ -272,6 +274,7 @@ type FOnAudioDevChange: TOnAudioDevChange; FOnAudioInDevChange: TOnAudioInDevChange; FOnAudioBufferChange: TOnAudioBufferChange; + FOnSendAudioToRadioChange: TOnSendAudioToRadioChange; FOnVisibilityChange: TOnVisibilityChange; FOnFPSChange: TOnFPSChange; FOnThemeChange: TOnThemeChange; @@ -335,6 +338,7 @@ type procedure OnRXDevChange(Sender: TObject); procedure OnTXDevChange(Sender: TObject); procedure OnAudioBufferCmbChange(Sender: TObject); + procedure OnSendAudioToRadioChkChange(Sender: TObject); procedure OnVisibilityChkChange(Sender: TObject); procedure OnFPSCmbChange(Sender: TObject); procedure OnLightThemeChkChange(Sender: TObject); @@ -375,6 +379,7 @@ type procedure RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput); procedure LoadAudioBufferSize(BufferSize: Integer); + procedure LoadSendAudioToRadio(Enabled: Boolean); procedure LoadVisibility(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill, SpectrumFill: Boolean); procedure LoadFPS(FPS: Integer); @@ -406,6 +411,7 @@ type property OnAudioDevChange: TOnAudioDevChange read FOnAudioDevChange write FOnAudioDevChange; property OnAudioInDevChange: TOnAudioInDevChange read FOnAudioInDevChange write FOnAudioInDevChange; property OnAudioBufferChange: TOnAudioBufferChange read FOnAudioBufferChange write FOnAudioBufferChange; + property OnSendAudioToRadioChange: TOnSendAudioToRadioChange read FOnSendAudioToRadioChange write FOnSendAudioToRadioChange; property OnVisibilityChange: TOnVisibilityChange read FOnVisibilityChange write FOnVisibilityChange; property OnFPSChange: TOnFPSChange read FOnFPSChange write FOnFPSChange; property OnThemeChange: TOnThemeChange read FOnThemeChange write FOnThemeChange; @@ -765,6 +771,18 @@ begin FCmbAudioBuffer.Items.Add('256 frames'); FCmbAudioBuffer.Items.Add('512 frames'); FCmbAudioBuffer.ItemIndex := 0; + + Grp := MakeGroupPanel(FPageAudio, 'Transceiver Audio', MARGIN, 290, 650, 92); + + FChkSendAudioToRadio := TFlatCheckBox.Create(Self); + FChkSendAudioToRadio.Parent := Grp; + FChkSendAudioToRadio.Caption := 'Send audio stream to radio (built-in speaker)'; + FChkSendAudioToRadio.SetBounds(PAD, R1, 420, 22); + FChkSendAudioToRadio.Checked := False; + FChkSendAudioToRadio.Font.Color := CLR_TEXT; + FChkSendAudioToRadio.Font.Name := UI_FONT; + FChkSendAudioToRadio.Font.Size := 9; + FChkSendAudioToRadio.OnChange := OnSendAudioToRadioChkChange; end; // --------------------------------------------------------------------------- @@ -2050,6 +2068,16 @@ begin end; end; +procedure TSettingsForm.LoadSendAudioToRadio(Enabled: Boolean); +begin + FLoading := True; + try + FChkSendAudioToRadio.Checked := Enabled; + finally + FLoading := False; + end; +end; + // --------------------------------------------------------------------------- // RefreshAudioDevices // --------------------------------------------------------------------------- @@ -2338,6 +2366,13 @@ begin FOnAudioBufferChange(BufferSize); end; +procedure TSettingsForm.OnSendAudioToRadioChkChange(Sender: TObject); +begin + if FLoading then Exit; + if Assigned(FOnSendAudioToRadioChange) then + FOnSendAudioToRadioChange(FChkSendAudioToRadio.Checked); +end; + procedure TSettingsForm.LoadVisibility(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill, SpectrumFill: Boolean); begin