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:
+76
-13
@@ -1536,6 +1536,14 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
const
|
||||
// Заводская 10-полосная голосовая сетка EQ (Hz). [0]=слот преампа (частота
|
||||
// не используется), [1..10]=узлы кривой. Подобрано под голос, а не под
|
||||
// музыку: больше точек в 100..3500 Hz. Нужна и DefaultTX, и заводским
|
||||
// профилям (сброс тембра в ноль), поэтому живёт на уровне модуля.
|
||||
DEF_EQ_FREQS: array[0..10] of Double = (
|
||||
0, 100, 200, 400, 700, 1100, 1500, 2000, 2500, 3000, 3500);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TX settings (per-device, JSON-секция "tx" под MAC)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1543,12 +1551,6 @@ end;
|
||||
class procedure TSettingsManager.DefaultTX(out T: TTXSettings);
|
||||
var
|
||||
i: Integer;
|
||||
const
|
||||
// 10-полосный голосовой EQ (Hz). [0]=preamp slot, [1..10]=bands.
|
||||
// Подобрано под голос, а не под музыку: больше точек в 100..3500 Hz.
|
||||
DefaultEQFreqs: array[0..10] of Double = (
|
||||
0, // [0] preamp slot — частота не используется
|
||||
100, 200, 400, 700, 1100, 1500, 2000, 2500, 3000, 3500);
|
||||
begin
|
||||
FillChar(T, SizeOf(T), 0);
|
||||
T.MicLineIn := False;
|
||||
@@ -1590,7 +1592,7 @@ begin
|
||||
for i := 0 to 10 do
|
||||
begin
|
||||
T.EQGains[i] := 0.0;
|
||||
T.EQFreqs[i] := DefaultEQFreqs[i];
|
||||
T.EQFreqs[i] := DEF_EQ_FREQS[i];
|
||||
end;
|
||||
// AM
|
||||
T.AMCarrierLevel := 0.5;
|
||||
@@ -1862,6 +1864,36 @@ end;
|
||||
|
||||
procedure DefaultTXProfiles(const Base: TTXSettings; DrivePct: Integer;
|
||||
out L: TTXProfileList);
|
||||
const
|
||||
// Кривые EQ для заводских ESSB. Значения — УЗЛЫ ломаной АЧХ WDSP (eq.c),
|
||||
// а не «полосы»: между узлами интерполяция по дБ, а ЗА крайними узлами
|
||||
// ctfmode=0 обрывает АЧХ на нет. Отсюда железное правило, по которому
|
||||
// подобраны частоты: нижний узел ≤ FilterLow, верхний ≥ FilterHigh, иначе
|
||||
// включённый EQ сам срежет полосу, ради которой ESSB и включают.
|
||||
// Форма: срез бубнения снизу, ровная середина, мягкий подъём присутствия
|
||||
// к верху. Преамп в минус — EQ стоит ДО компрессора и ALC, и плюсовые
|
||||
// гейны иначе просто загоняют ALC в ограничение.
|
||||
ESSB35_F: array[1..10] of Double = (100, 160, 250, 400, 700, 1200, 1800, 2500, 3100, 3600);
|
||||
ESSB35_G: array[1..10] of Double = ( -6, -4, -2, 0, 0, 1, 2, 3, 4, 4);
|
||||
ESSB35_PRE = -2.0;
|
||||
ESSB50_F: array[1..10] of Double = (100, 180, 300, 550, 950, 1600, 2400, 3300, 4200, 5100);
|
||||
ESSB50_G: array[1..10] of Double = ( -5, -3, -1, 0, 0, 1, 2, 3.5, 5, 5);
|
||||
ESSB50_PRE = -3.0;
|
||||
|
||||
// Тембр у заводского профиля — заводской: EQ выключен, все усиления и
|
||||
// преамп в нуле, сетка узлов штатная. Наследовать сюда живой EQ нельзя —
|
||||
// иначе «сброс к заводским» не сбрасывал бы то, ради чего его жмут.
|
||||
procedure FlatEQ(var P: TTXProfile);
|
||||
var i: Integer;
|
||||
begin
|
||||
P.EQOn := False;
|
||||
P.EQNumBands := 10;
|
||||
for i := 0 to 10 do
|
||||
begin
|
||||
P.EQGains[i] := 0.0;
|
||||
P.EQFreqs[i] := DEF_EQ_FREQS[i];
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure Add(const AName: string; ALow, AHigh: Integer;
|
||||
AComp: Boolean; ACompGain: Double);
|
||||
@@ -1873,24 +1905,55 @@ procedure DefaultTXProfiles(const Base: TTXSettings; DrivePct: Integer;
|
||||
L.Items[L.Count].FilterHigh := AHigh;
|
||||
L.Items[L.Count].CompressorOn := AComp;
|
||||
L.Items[L.Count].CompressorGain := ACompGain;
|
||||
FlatEQ(L.Items[L.Count]);
|
||||
Inc(L.Count);
|
||||
end;
|
||||
|
||||
// То же плюс своя кривая EQ: у ESSB тембр — часть профиля, наследовать
|
||||
// сетку 100..3500 от текущих настроек здесь нельзя (см. правило выше).
|
||||
procedure AddEQ(const AName: string; ALow, AHigh: Integer;
|
||||
AComp: Boolean; ACompGain: Double;
|
||||
const AF, AG: array of Double; APreamp: Double);
|
||||
var
|
||||
i, Before: Integer;
|
||||
begin
|
||||
Before := L.Count;
|
||||
Add(AName, ALow, AHigh, AComp, ACompGain);
|
||||
if L.Count = Before then Exit; // упёрлись в TXPROF_MAX — ничего не добавлено
|
||||
with L.Items[L.Count - 1] do
|
||||
begin
|
||||
EQOn := True;
|
||||
EQNumBands := 10;
|
||||
EQFreqs[0] := 0.0; // слот преампа — частота не используется
|
||||
EQGains[0] := APreamp;
|
||||
for i := 1 to 10 do
|
||||
begin
|
||||
EQFreqs[i] := AF[i - 1];
|
||||
EQGains[i] := AG[i - 1];
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
begin
|
||||
FillChar(L, SizeOf(L), 0);
|
||||
L.Count := 0;
|
||||
L.ActiveIdx := 0;
|
||||
// «Default» — ровно текущее состояние устройства: после миграции старого
|
||||
// конфига звук в эфире не меняется ни на йоту.
|
||||
// «Default» — текущее состояние устройства (микрофонный вход, полоса,
|
||||
// динамика, мощность), но с заводским EQ: после миграции старого конфига
|
||||
// звук в эфире не меняется, а сброс к заводским возвращает ровный тембр.
|
||||
TXProfileCapture(Base, DrivePct, L.Items[0]);
|
||||
L.Items[0].Name := 'Default';
|
||||
FlatEQ(L.Items[0]);
|
||||
L.Count := 1;
|
||||
// Остальные заводские отличаются от Default ТОЛЬКО полосой и компрессором;
|
||||
// микрофонный вход, EQ и мощность наследуются (см. коммент к DefaultTXProfiles).
|
||||
// SSB-профили отличаются от Default только полосой и компрессором;
|
||||
// микрофонный вход и мощность наследуются, EQ у всех троих заводской-ровный.
|
||||
// ESSB несут ещё и свою кривую: без неё «широкий» профиль звучит тем же
|
||||
// телефонным тембром, а с чужой сеткой (верхний узел 3500) EQ обрезал бы
|
||||
// верх ESSB 5k.
|
||||
Add('SSB DX', 300, 2700, True, 8.0);
|
||||
Add('SSB Wide', 200, 3100, False, 5.0);
|
||||
Add('ESSB 3.5k', 100, 3500, False, 5.0);
|
||||
Add('ESSB 5k', 100, 5000, False, 5.0);
|
||||
AddEQ('ESSB 3.5k', 100, 3500, False, 5.0, ESSB35_F, ESSB35_G, ESSB35_PRE);
|
||||
AddEQ('ESSB 5k', 100, 5000, False, 5.0, ESSB50_F, ESSB50_G, ESSB50_PRE);
|
||||
end;
|
||||
|
||||
function TSettingsManager.LoadTXProfiles(const MAC: array of Byte;
|
||||
|
||||
Reference in New Issue
Block a user