mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(tx): заводские EQ-кривые ESSB, честная АЧХ в редакторе, фикс 3-полосного EQ
Ключ ко всему — семантика EQ в WDSP (wdsp/eq.c, eq_impulse + TXA.c): F[1..n] это УЗЛЫ ломаной АЧХ, между ними интерполяция по децибелам, а за крайними узлами при ctfmode=0 (так создаётся eqp у TXA, мы его не меняем) идёт кумулятивный скат (f/f0)^4 на каждый бин — фактически обрыв. Значит включённый EQ работает как второй полосовой фильтр, и верхний узел обязан лежать за верхней кромкой TX-фильтра. Settings: заводские ESSB несут свою кривую (AddEQ) — ESSB 3.5k узлы 100..3600, ESSB 5k узлы 100..5100, преамп в минус (EQ стоит до компрессора и ALC). EQ у Default/SSB DX/SSB Wide сбрасывается в заводской ноль (FlatEQ), а не наследует живой, иначе «сброс к заводским» не сбрасывал бы тембр. Сетка узлов вынесена в модульную DEF_EQ_FREQS. WDSPEngine: единая точка пуша EQ (PushTXEQProfile). Починен 3-полосный режим: раньше в WDSP уходили первые три узла 10-полосной сетки (100/200/400) и включённый EQ убивал весь голос выше 400 Гц; теперь legacy-раскладка самого WDSP (150/400/1500/6000, как SetTXAGrphEQ). EqualizerControl: кривая — точный порт eq_impulse вместо гауссовых горбов (сверено с C численно, расхождение 0.0 дБ), рисуется без клампа ±15, чтобы обрыв за крайними узлами был виден. В 3-полосном режиме ручки стоят на 150/1500/6000 и по частоте не таскаются, сетка профиля при этом цела. RadioController/SettingsForm/MainForm: кнопка Factory — пересборка заводского набора с применением Default. Без неё обновлённые заводские профили не доедут до тех, у кого секция tx_profiles уже записана: она читается, а не создаётся. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+34
-3
@@ -226,6 +226,7 @@ type
|
||||
FBtnTXProfNew: TFlatButton;
|
||||
FBtnTXProfRen: TFlatButton;
|
||||
FBtnTXProfDel: TFlatButton;
|
||||
FBtnTXProfFactory: TFlatButton;
|
||||
FLblTXProfDrive: TLabel; // «Drive профиля: N %» (ползунок — в главном окне)
|
||||
FCmbTXMicJack: TFlatComboBox; // "Mic In" / "Line In"
|
||||
FChkTXMicBoost: TFlatCheckBox; // +20 dB (только Mic In)
|
||||
@@ -438,6 +439,7 @@ type
|
||||
FOnTXProfileAdd: TOnTXProfileName;
|
||||
FOnTXProfileRename: TOnTXProfileIdxName;
|
||||
FOnTXProfileDelete: TOnTXProfileIdx;
|
||||
FOnTXProfileFactory: TNotifyEvent;
|
||||
|
||||
FLoading: Boolean;
|
||||
|
||||
@@ -496,6 +498,7 @@ type
|
||||
procedure BtnTXProfNewClick(Sender: TObject);
|
||||
procedure BtnTXProfRenClick(Sender: TObject);
|
||||
procedure BtnTXProfDelClick(Sender: TObject);
|
||||
procedure BtnTXProfFactoryClick(Sender: TObject);
|
||||
procedure ApplyTheme(const T: TAppTheme);
|
||||
procedure NavClick(Sender: TObject);
|
||||
procedure SelectPage(APage: TWinControl; ANav: TFlatButton);
|
||||
@@ -655,6 +658,7 @@ type
|
||||
property OnTXProfileAdd: TOnTXProfileName read FOnTXProfileAdd write FOnTXProfileAdd;
|
||||
property OnTXProfileRename: TOnTXProfileIdxName read FOnTXProfileRename write FOnTXProfileRename;
|
||||
property OnTXProfileDelete: TOnTXProfileIdx read FOnTXProfileDelete write FOnTXProfileDelete;
|
||||
property OnTXProfileFactory: TNotifyEvent read FOnTXProfileFactory write FOnTXProfileFactory;
|
||||
property OnWfAGCNFChange: TOnWfAGCNFChange read FOnWfAGCNFChange write FOnWfAGCNFChange;
|
||||
property OnADCChange: TOnADCChange read FOnADCChange write FOnADCChange;
|
||||
property OnWebSettingsChange: TOnWebSettingsChange read FOnWebSettingsChange write FOnWebSettingsChange;
|
||||
@@ -1514,10 +1518,12 @@ begin
|
||||
FBtnTXProfNew := MkBtn(Grp, PAD + 302, R1, 100, 'New...', BtnTXProfNewClick);
|
||||
FBtnTXProfRen := MkBtn(Grp, PAD + 408, R1, 100, 'Rename...', BtnTXProfRenClick);
|
||||
FBtnTXProfDel := MkBtn(Grp, PAD + 514, R1, 100, 'Delete', BtnTXProfDelClick);
|
||||
FBtnTXProfFactory := MkBtn(Grp, PAD + 620, R1, 100, 'Factory', BtnTXProfFactoryClick);
|
||||
|
||||
MakeLbl(Grp, 'Правки на вкладках Profile и EQ пишутся в активный профиль сразу — '
|
||||
+ 'отдельной кнопки «сохранить» нет.', PAD, R1 + 30, GRP_W - 2 * PAD);
|
||||
MakeLbl(Grp, 'New... создаёт копию текущего звука под новым именем.',
|
||||
MakeLbl(Grp, 'New... создаёт копию текущего звука под новым именем. '
|
||||
+ 'Factory — заводской набор заново, эквалайзер в ноль.',
|
||||
PAD, R1 + 48, GRP_W - 2 * PAD);
|
||||
|
||||
// ---- Сегменты подвкладок ------------------------------------------------
|
||||
@@ -1627,7 +1633,7 @@ begin
|
||||
// =========================================================================
|
||||
SubPage(1, SubTop);
|
||||
|
||||
Grp := MakeGroupPanel(FTXSubPage[1], 'TX Equalizer', MARGIN, 0, GRP_W, 390);
|
||||
Grp := MakeGroupPanel(FTXSubPage[1], 'TX Equalizer', MARGIN, 0, GRP_W, 434);
|
||||
|
||||
FTXEQControl := TEqualizerControl.Create(Self);
|
||||
FTXEQControl.Parent := Grp;
|
||||
@@ -1638,7 +1644,17 @@ begin
|
||||
TColor($00383838), CLR_TEXT, CLR_TEXTDIM, CLR_ACCENT, TColor($0055DDEE));
|
||||
FTXEQControl.OnChange := OnTXAnyChange;
|
||||
|
||||
FTXSubPage[1].Height := DpiScale(390 + 12);
|
||||
// Главное, что надо знать про этот EQ: точки — не «полосы», а узлы кривой,
|
||||
// и за крайними узлами WDSP обрывает АЧХ (см. WDSPEngine.PushTXEQProfile).
|
||||
MakeLbl(Grp, 'Точки — узлы кривой: между ними интерполяция по дБ, '
|
||||
+ 'за крайними точками АЧХ обрывается.', PAD, R1 + 332, GRP_W - 2 * PAD);
|
||||
MakeLbl(Grp, 'Нижняя точка должна быть не выше нижней кромки TX-фильтра, '
|
||||
+ 'верхняя — не ниже верхней, иначе EQ сам срежет полосу.',
|
||||
PAD, R1 + 352, GRP_W - 2 * PAD);
|
||||
MakeLbl(Grp, 'В режиме 3 полос частоты задаёт WDSP (150 / 1500 / 6000 Гц) — '
|
||||
+ 'двигается только усиление.', PAD, R1 + 372, GRP_W - 2 * PAD);
|
||||
|
||||
FTXSubPage[1].Height := DpiScale(434 + 12);
|
||||
|
||||
// =========================================================================
|
||||
// Подвкладка 2 — Hardware: свойства устройства, профилем не переключаются
|
||||
@@ -1953,6 +1969,21 @@ begin
|
||||
if Assigned(FOnTXProfileDelete) then FOnTXProfileDelete(Idx);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.BtnTXProfFactoryClick(Sender: TObject);
|
||||
// Пересоздать заводской набор. Нужен не только «на случай чего»: заводские
|
||||
// профили пишутся в конфиг один раз при первом подключении устройства, и без
|
||||
// этой кнопки обновлённые заводские настройки (например кривые EQ у ESSB)
|
||||
// не доехали бы до тех, у кого секция tx_profiles уже записана.
|
||||
begin
|
||||
if MessageDlg('TX Profile',
|
||||
'Заменить весь список профилей заводским набором?'#13#10
|
||||
+ 'Свои профили и правки заводских будут потеряны.'#13#10
|
||||
+ 'Активным станет «Default»: микрофон, полоса, динамика и мощность '
|
||||
+ 'останутся как сейчас, эквалайзер вернётся в ноль.',
|
||||
mtConfirmation, [mbYes, mbNo], 0) <> mrYes then Exit;
|
||||
if Assigned(FOnTXProfileFactory) then FOnTXProfileFactory(Self);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadTXProfiles(const Names: array of string;
|
||||
ActiveIdx, DrivePct: Integer);
|
||||
var
|
||||
|
||||
Reference in New Issue
Block a user