mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
Фильтр главного приёмника перестал быть одним числом. Теперь это таблица (Name, Low, High) на режим: 10 пресетов + VAR1/VAR2, знаковые кромки (у LSB своя строка, у USB своя), паритет с Thetis FilterForm. Слайсы независимые кромки умели и раньше — главный приёмник настраивался только пресетами. Модель (Settings/RadioController): - TFilterSlot/TFilterSet/TFilterTable + заводские дефолты из прежних FILT_*_BW и FILT_*_NAMES (переехали в Settings — из них строится таблица). - FFilterLo/FFilterHi = истина, FFilterBW производная (FilterBWFromEdges), поэтому все прежние читатели ширины (web, зум, band-cache, CAT) не тронуты. - ApplyFilterSlot — единственная точка «слот → состояние тракта». - SetFilterEdges: произвольные кромки уходят в VAR1 с автопереключением, пресеты нельзя испортить случайным движением мыши или энкодера. - Персист: секция "filters" под MAC, пишутся только режимы, отличные от заводских; секция режима сносится целиком, иначе возврат слота к заводскому оставлял бы старый ключ. - Диапазон помнит только выбранный слот: кромки живут в таблице, второго источника истины у VAR нет. UI: - 12 кнопок фильтра (2 ряда по 6), подписи из живой таблицы. - FilterPopup — поповер редактора по ПКМ на кнопке фильтра, по образцу PureSignalPopup (дочерний контрол формы, не top-level: Wayland). Слева слоты, справа Name/Low/High/Width, внизу форма полосы на знаковой оси. - Перетаскивание края полосы на спектре: ±5px, снап 10 Гц, Alt = симметрично (не Ctrl — он создаёт слайс). Отказ на передаче и когда полоса на экране уже 12px, иначе захват съедал бы клик-тюн. Отрисовка: - Полоса главного VFO рисуется по РЕАЛЬНЫМ кромкам приёмника, слайса — по его кромкам; вывод из режима и ширины остался фолбэком. Пара→ширина→пара было преобразованием с потерями: несимметричный фильтр из слайс-CAT (ZZFL/ZZFH) рисовался не там, где звучал. - На передаче полоса считается по TX-фильтру (TXSignedEdges), а не по приёмной ширине — TX-полоса от RX не зависит (модель Thetis). - CalcFilterBandXFor больше не держит свою копию таблицы знака боковой: единственная таблица теперь FilterEdgesFromBW. Управление: - CAT ZZFL/ZZFH заработали на основном порту (были заглушками). - Андромеда: энкодеры #4/#5 — независимые Filter High/Low, как на железе G2 (было: #4 ширина, #5 не реализован). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
180 lines
7.8 KiB
ObjectPascal
180 lines
7.8 KiB
ObjectPascal
unit AndromedaMap;
|
||
|
||
{
|
||
AndromedaMap.pas — таблицы раскладки передней панели Apache Labs Andromeda / G2.
|
||
|
||
Только ДАННЫЕ: перечисления действий и дефолтные таблицы
|
||
«номер кнопки → действие», «номер энкодера → параметр»,
|
||
«индикатор → номер LED (ZZZI)». Без логики и без зависимостей от
|
||
контроллера — этим занимается AndromedaPanel.pas.
|
||
|
||
Соответствие протоколу Thetis:
|
||
• действия кнопок/энкодеров — подмножество EButtonBarActions / EEncoderActions;
|
||
• номера LED-индикаторов (ZZZI nn) зеркалят дефолтную таблицу Thetis
|
||
(MakeNewAndromedaDataset): 1=MOX, 3=Tune, 7=CTUN и т.д. Свободные слоты
|
||
12..17 заняты функциями ewsdr (Split/NB/NR/SNB/ANF/Squelch).
|
||
}
|
||
|
||
{$mode objfpc}{$H+}
|
||
|
||
interface
|
||
|
||
type
|
||
// Действие кнопки панели (подмножество, поддержанное ewsdr).
|
||
TAndButton = (
|
||
abNone, abMOX, abTune, abBandUp, abBandDown, abModeUp, abModeDown,
|
||
abMute, abNB, abNR, abSNB, abANF, abVFOSplit, abVFOSwap, abCTUN,
|
||
abCTCSS, abSquelch, abFilterUp, abFilterDown,
|
||
abVFOAtoB, // копировать VFO A → B (Thetis eBBVFOAtoB)
|
||
abVFOBtoA, // копировать VFO B → A (Thetis eBBVFOBtoA)
|
||
abVFOLock, // блокировка перестройки VFO (Thetis eBBVFOLockOnOff)
|
||
abStartStop, // старт/стоп SDR (Thetis eBBStartStop)
|
||
abShift); // модификатор band-keypad (Thetis eBBShift) — обрабатывается в панели
|
||
|
||
// Параметр, управляемый поворотным энкодером панели.
|
||
TAndEncoder = (
|
||
aeNone, aeAFGain, aeAGCLevel, aeDrive, aeSquelch,
|
||
aeFilterWidth, aeFilterHigh, aeFilterLow, aeMulti);
|
||
|
||
// Индикатор (LED) панели. Порядок зеркалит EIndicatorActions Thetis.
|
||
TAndIndicator = (
|
||
aiNone, aiMOX, aiTune, aiRIT, aiXIT, aiSplit, aiCTune, aiVFOSync,
|
||
aiVFOAB, aiVFOLock, aiNB, aiNR, aiSNB, aiANF, aiSquelch,
|
||
aiCompander, aiPuresignal, aiDiversity, aiATUReady, aiShift);
|
||
|
||
const
|
||
// Размеры дефолтных таблиц (кнопки/энкодеры нумеруются с 0 после разбора ZZZP/ZZZE).
|
||
AND_MAX_BUTTONS = 48;
|
||
AND_MAX_ENCODERS = 20;
|
||
|
||
// Раскладка кнопок: индекс = номер кнопки (0-based), значение = действие.
|
||
// Нумерация совпадает с дефолтом Thetis (MakeNewAndromedaDataset): наш 0-based
|
||
// индекс = «Pushbutton Number» Thetis − 1. Проверено на реальной панели
|
||
// (см. andromeda_test/AndDecode.pas). Неназначенные / отсутствующий тракт → abNone.
|
||
AND_BUTTON_MAP: array[0..AND_MAX_BUTTONS-1] of TAndButton = (
|
||
abMute, // 0 RX1 Mute
|
||
abNone, // 1
|
||
abNone, // 2 RX2 Mute — нет второго приёмника
|
||
abNone, // 3
|
||
abNone, // 4
|
||
abNone, // 5
|
||
abNone, // 6
|
||
abNone, // 7
|
||
abNone, // 8
|
||
abNone, // 9
|
||
abNone, // 10
|
||
abNone, // 11
|
||
abNone, // 12
|
||
abNone, // 13
|
||
abNone, // 14
|
||
abNone, // 15
|
||
abNone, // 16
|
||
abNone, // 17
|
||
abNone, // 18
|
||
abNone, // 19
|
||
abNone, // 20
|
||
abNone, // 21
|
||
abNone, // 22
|
||
abNone, // 23
|
||
abNone, // 24
|
||
abNone, // 25
|
||
abNone, // 26
|
||
abNone, // 27
|
||
abShift, // 28 Shift (band-keypad модификатор)
|
||
abBandUp, // 29 Band + (под shift → 160m)
|
||
abModeUp, // 30 Mode + (под shift → 80m)
|
||
abFilterUp, // 31 Filter + (под shift → 60m)
|
||
abBandDown, // 32 Band - (под shift → 40m)
|
||
abModeDown, // 33 Mode - (под shift → 30m)
|
||
abFilterDown, // 34 Filter - (под shift → 20m)
|
||
abVFOAtoB, // 35 A>B (под shift → 17m)
|
||
abVFOBtoA, // 36 B>A (под shift → 15m)
|
||
abVFOSplit, // 37 Split (под shift → 12m)
|
||
abNone, // 38 (под shift → 10m)
|
||
abNone, // 39 (под shift → 6m)
|
||
abNone, // 40 (под shift → GEN, нет в ewsdr)
|
||
abNone, // 41 RIT/XIT toggle — нет RIT/XIT
|
||
abVFOSwap, // 42 Toggle A/B
|
||
abVFOLock, // 43 VFO Lock
|
||
abCTUN, // 44 CTUNE
|
||
abStartStop, // 45 SDR ON (start/stop)
|
||
abMOX, // 46 MOX
|
||
abTune); // 47 TUN
|
||
|
||
// Раскладка энкодеров: индекс = номер энкодера (0-based), значение = параметр.
|
||
// Нумерация = «Encoder Number» Thetis − 1 (дефолт MakeNewAndromedaDataset).
|
||
// Энкодер #1 в Thetis — AGC Level (слайдер RF), поэтому aeAGCLevel.
|
||
// #4/#5 — Filter High/Low: настоящие независимые края (как на железе G2).
|
||
// aeFilterWidth оставлен в перечислении для раскладок, где нужен один
|
||
// энкодер «ширина» — в дефолтной раскладке не используется.
|
||
AND_ENCODER_MAP: array[0..AND_MAX_ENCODERS-1] of TAndEncoder = (
|
||
aeAFGain, // 0 RX1 AF Gain
|
||
aeAGCLevel, // 1 RX1 AGC Level (Thetis «RF»)
|
||
aeNone, // 2 RX2 AF — нет второго приёмника
|
||
aeNone, // 3 RX2 AGC
|
||
aeFilterHigh, // 4 Filter High — верхний край
|
||
aeFilterLow, // 5 Filter Low — нижний край
|
||
aeNone, // 6 Diversity gain
|
||
aeNone, // 7 Diversity phase
|
||
aeNone, // 8 RIT
|
||
aeNone, // 9 XIT
|
||
aeMulti, // 10 Multifunction
|
||
aeDrive, // 11 TX Drive
|
||
aeNone, aeNone, aeNone, aeNone, aeNone, aeNone, aeNone, aeNone);
|
||
|
||
// Номер LED (ZZZI nn) для каждого индикатора; 0 = панель этот индикатор не имеет.
|
||
// 1..11 — дефолт Thetis; 12..17 — функции ewsdr на свободных слотах.
|
||
AND_INDICATOR_NUM: array[TAndIndicator] of Integer = (
|
||
{aiNone} 0,
|
||
{aiMOX} 1,
|
||
{aiTune} 3,
|
||
{aiRIT} 8,
|
||
{aiXIT} 9,
|
||
{aiSplit} 12,
|
||
{aiCTune} 7,
|
||
{aiVFOSync} 0,
|
||
{aiVFOAB} 10,
|
||
{aiVFOLock} 11,
|
||
{aiNB} 13,
|
||
{aiNR} 14,
|
||
{aiSNB} 15,
|
||
{aiANF} 16,
|
||
{aiSquelch} 17,
|
||
{aiCompander} 0,
|
||
{aiPuresignal} 0,
|
||
{aiDiversity} 0,
|
||
{aiATUReady} 2,
|
||
{aiShift} 6);
|
||
|
||
{ Возвращает действие для номера кнопки (0-based); abNone вне диапазона. }
|
||
function ButtonAction(BtnIndex: Integer): TAndButton;
|
||
{ Возвращает параметр для номера энкодера (0-based); aeNone вне диапазона. }
|
||
function EncoderParam(EncIndex: Integer): TAndEncoder;
|
||
{ Номер ZZZI для индикатора; 0 если панель его не отображает. }
|
||
function IndicatorNum(Ind: TAndIndicator): Integer;
|
||
|
||
implementation
|
||
|
||
function ButtonAction(BtnIndex: Integer): TAndButton;
|
||
begin
|
||
if (BtnIndex >= 0) and (BtnIndex < AND_MAX_BUTTONS) then
|
||
Result := AND_BUTTON_MAP[BtnIndex]
|
||
else
|
||
Result := abNone;
|
||
end;
|
||
|
||
function EncoderParam(EncIndex: Integer): TAndEncoder;
|
||
begin
|
||
if (EncIndex >= 0) and (EncIndex < AND_MAX_ENCODERS) then
|
||
Result := AND_ENCODER_MAP[EncIndex]
|
||
else
|
||
Result := aeNone;
|
||
end;
|
||
|
||
function IndicatorNum(Ind: TAndIndicator): Integer;
|
||
begin
|
||
Result := AND_INDICATOR_NUM[Ind];
|
||
end;
|
||
|
||
end.
|