mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(slices): multi-slice receivers (FlexRadio-style) — soft fan-out + per-slice flag/audio
Independent RX slices sharing one panadapter: each has its own freq/mode/filter/AGC/NR-NB-SNB-ANF, S-meter, volume and sound card. VFO A = main receiver; B+ are soft fan-out WDSP channels within the captured span (ideal for Pluto/QO-100; HW DDC path is Stage 3). - WDSPEngine: soft fan-out (TSliceChan, ProcessSlices in DSP thread, per-slice NCO/mode/filter/AGC/NR/NB/SNB/ANF + S-meter, OnSliceAudio), FSliceLock serialises add/remove/set vs DSP; reopened on rate change. - RadioController: TCtrlSlice with its own TAudioOutput/device, Add/Remove/SetSlice*, RefreshSliceShifts keeps slices on target Hz across centre changes. Slice letters start at 'B' (A = main receiver). - UI: per-slice flags (VfoOverlay) with letter badge/close, filter-band markers in BOTH GL and CPU renderers, drag-retune, wheel; flags placed beside the filter band with flip, like the main flag. Ctrl+click adds. - Main flag (slice A) visible whenever the left panel is hidden OR any slice exists; synced with panel-driven DSP/mode/AGC changes. - Perf: S-meter decoupled from flag chrome cache (FMeterDirty + meter-only RefreshMeter via EnsureRendered); InvalidateVfoOverlay avoids global overlay-cache churn on meter ticks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+85
-5
@@ -70,6 +70,7 @@ type
|
||||
FADCOverloadVisible: Boolean;
|
||||
FSampleRateOverlay: TSampleRateOverlay;
|
||||
FVfoOverlay: TVfoOverlay;
|
||||
FSliceOverlays: TFPList; // доп. слайс-флаги (B+); не владеет (владелец MainForm)
|
||||
FBandPlanOverlay: TBandPlanOverlay;
|
||||
// ── Marker ────────────────────────────────────────────────────────────────
|
||||
FMarkerActive: Boolean;
|
||||
@@ -108,12 +109,15 @@ type
|
||||
procedure SetFillSpectrum(V: Boolean);
|
||||
procedure DrawSpectrumGradient(const SpPts: array of TPoint; W, H: Integer);
|
||||
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||
procedure DrawSliceFilterMarkers(C: TCanvas; W, H: Integer);
|
||||
procedure DrawBeaconMarkers(C: TCanvas; W, H: Integer);
|
||||
procedure BlendBand(X1, X2, H: Integer; R, G, B, Alpha: Byte);
|
||||
procedure CopyGridToSpectrum(W, H: Integer);
|
||||
procedure DrawADCOverloadOverlay(C: TCanvas; W, H: Integer);
|
||||
procedure CalcFilterBandX(VfoFreq: Double; W: Integer;
|
||||
out X1, X2, VfoX: Integer);
|
||||
procedure CalcFilterBandXFor(VfoFreq: Double; Mode, BW, W: Integer;
|
||||
out X1, X2, VfoX: Integer);
|
||||
function GetWaterfallDirty: Boolean;
|
||||
procedure SetWaterfallDirty(V: Boolean);
|
||||
// Waterfall sub-view delegating accessors
|
||||
@@ -218,6 +222,11 @@ type
|
||||
property PbSMeterRight: TPaintBox write SetPbSMeterRight;
|
||||
property SampleRateOverlay: TSampleRateOverlay read FSampleRateOverlay write FSampleRateOverlay;
|
||||
property VfoOverlay: TVfoOverlay read FVfoOverlay write FVfoOverlay;
|
||||
// Доп. слайс-флаги (B+): рисуются поверх спектра после главного оверлея.
|
||||
procedure AddSliceOverlay(O: TVfoOverlay);
|
||||
procedure RemoveSliceOverlay(O: TVfoOverlay);
|
||||
function SliceOverlayCount: Integer;
|
||||
function SliceOverlayAt(Index: Integer): TVfoOverlay;
|
||||
property BandPlanOverlay: TBandPlanOverlay read FBandPlanOverlay write FBandPlanOverlay;
|
||||
|
||||
// ── Данные от DSP ─────────────────────────────────────────────────────────
|
||||
@@ -244,6 +253,9 @@ type
|
||||
// ── Утилиты ───────────────────────────────────────────────────────────────
|
||||
procedure InvalidateGridCache; virtual;
|
||||
procedure InvalidateOverlayCache; virtual;
|
||||
// Инвалидация ТОЛЬКО текстуры главного VFO-флага (без band/samplerate/слайсов).
|
||||
// Нужна для дешёвого обновления S-метра главного флага в GL-вьюхе.
|
||||
procedure InvalidateVfoOverlay; virtual;
|
||||
procedure SetTheme(const T: TAppTheme); virtual;
|
||||
procedure InvalidateRulerCache; virtual;
|
||||
procedure ResetSpectrumBuf; virtual;
|
||||
@@ -329,6 +341,7 @@ begin
|
||||
FWaterfall := TWaterfallView.Create;
|
||||
FSMeter := TSMeterView.Create;
|
||||
FRuler := TRulerView.Create;
|
||||
FSliceOverlays := TFPList.Create;
|
||||
ResetSpectrumBuf;
|
||||
end;
|
||||
|
||||
@@ -339,9 +352,30 @@ begin
|
||||
FWaterfall.Free;
|
||||
FSMeter.Free;
|
||||
FRuler.Free;
|
||||
FSliceOverlays.Free; // не владеет флагами — только список
|
||||
inherited;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.AddSliceOverlay(O: TVfoOverlay);
|
||||
begin
|
||||
if (O <> nil) and (FSliceOverlays.IndexOf(O) < 0) then FSliceOverlays.Add(O);
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.RemoveSliceOverlay(O: TVfoOverlay);
|
||||
begin
|
||||
FSliceOverlays.Remove(O);
|
||||
end;
|
||||
|
||||
function TSpectrumView.SliceOverlayCount: Integer;
|
||||
begin
|
||||
Result := FSliceOverlays.Count;
|
||||
end;
|
||||
|
||||
function TSpectrumView.SliceOverlayAt(Index: Integer): TVfoOverlay;
|
||||
begin
|
||||
Result := TVfoOverlay(FSliceOverlays[Index]);
|
||||
end;
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// Setters with cascade to sub-views
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -583,6 +617,32 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Маркер фильтра каждого слайса (B+) на спектре — полупрозрачная полоса
|
||||
// пропускания + края + центральная несущая. Янтарный цвет, как в GL-вьюхе
|
||||
// (SpectrumViewOpengl.DrawSliceFilterMarkers). Рисуется под кривой спектра.
|
||||
procedure TSpectrumView.DrawSliceFilterMarkers(C: TCanvas; W, H: Integer);
|
||||
const
|
||||
SLICE_R = $F0; SLICE_G = $A8; SLICE_B = $10; // янтарный (совпадает с SLICE_CLR в GL)
|
||||
SLICE_CLR = TColor($0010A8F0);
|
||||
var
|
||||
i, SX1, SX2, SVfoX: Integer;
|
||||
O: TVfoOverlay;
|
||||
begin
|
||||
for i := 0 to FSliceOverlays.Count - 1 do
|
||||
begin
|
||||
O := TVfoOverlay(FSliceOverlays[i]);
|
||||
if (O = nil) or (not O.Visible) then Continue;
|
||||
CalcFilterBandXFor(O.DisplayVfoHz, O.DisplayMode, O.DisplayBW, W, SX1, SX2, SVfoX);
|
||||
if SX2 > SX1 then BlendBand(SX1, SX2, H, SLICE_R, SLICE_G, SLICE_B, 60);
|
||||
C.Pen.Color := SLICE_CLR; C.Pen.Width := 1; C.Pen.Style := psSolid;
|
||||
C.MoveTo(SX1, 0); C.LineTo(SX1, H);
|
||||
C.MoveTo(SX2, 0); C.LineTo(SX2, H);
|
||||
C.Pen.Width := 2;
|
||||
C.MoveTo(SVfoX, 0); C.LineTo(SVfoX, H - 12);
|
||||
C.Pen.Width := 1;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.CopyGridToSpectrum(W, H: Integer);
|
||||
var
|
||||
Y: Integer;
|
||||
@@ -620,15 +680,22 @@ end;
|
||||
|
||||
procedure TSpectrumView.CalcFilterBandX(VfoFreq: Double; W: Integer;
|
||||
out X1, X2, VfoX: Integer);
|
||||
begin
|
||||
CalcFilterBandXFor(VfoFreq, FMode, FFilterBW, W, X1, X2, VfoX);
|
||||
end;
|
||||
|
||||
// То же, но для произвольного режима/полосы (маркеры слайсов).
|
||||
procedure TSpectrumView.CalcFilterBandXFor(VfoFreq: Double; Mode, BW, W: Integer;
|
||||
out X1, X2, VfoX: Integer);
|
||||
var
|
||||
Lo_Hz, Hi_Hz, Half: Double;
|
||||
begin
|
||||
Half := FFilterBW / 2;
|
||||
Half := BW / 2;
|
||||
VfoX := Round((VfoFreq - FCenterFreq + FSpanHz / 2) / FSpanHz * W);
|
||||
case FMode of
|
||||
0: begin Lo_Hz := -FFilterBW; Hi_Hz := -100; end;
|
||||
1: begin Lo_Hz := 100; Hi_Hz := FFilterBW; end;
|
||||
else begin Lo_Hz := -Half; Hi_Hz := Half; end;
|
||||
case Mode of
|
||||
0: begin Lo_Hz := -BW; Hi_Hz := -100; end;
|
||||
1: begin Lo_Hz := 100; Hi_Hz := BW; end;
|
||||
else begin Lo_Hz := -Half; Hi_Hz := Half; end;
|
||||
end;
|
||||
X1 := Max(0, Min(W - 1, VfoX + Round(Lo_Hz / FSpanHz * W)));
|
||||
X2 := Max(0, Min(W - 1, VfoX + Round(Hi_Hz / FSpanHz * W)));
|
||||
@@ -737,6 +804,13 @@ begin
|
||||
FSpectrumDirty := True;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.InvalidateVfoOverlay;
|
||||
begin
|
||||
// База (CPU-путь) перерисовывает флаг по FCacheDirty/FMeterDirty оверлея — здесь
|
||||
// достаточно пометить спектр грязным. GL-подкласс переопределяет.
|
||||
FSpectrumDirty := True;
|
||||
end;
|
||||
|
||||
procedure TSpectrumView.SetTheme(const T: TAppTheme);
|
||||
begin
|
||||
FTheme := T;
|
||||
@@ -905,6 +979,9 @@ begin
|
||||
C.Pen.Style := psSolid;
|
||||
end;
|
||||
|
||||
// Полосы фильтра слайсов (B+) — под кривой спектра, как в GL-вьюхе.
|
||||
DrawSliceFilterMarkers(C, W, H);
|
||||
|
||||
// ── 3. AGC линии ──────────────────────────────────────────────────────────
|
||||
if FWDSPReady then
|
||||
begin
|
||||
@@ -1009,6 +1086,9 @@ begin
|
||||
FSampleRateOverlay.DrawOverlay(FSpectrumBitmap, C, W, H);
|
||||
if Assigned(FVfoOverlay) then
|
||||
FVfoOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||
// Доп. слайс-флаги (B+) поверх главного.
|
||||
for i := 0 to FSliceOverlays.Count - 1 do
|
||||
TVfoOverlay(FSliceOverlays[i]).DrawOverlay(FSpectrumBitmap, W, H);
|
||||
|
||||
// Восстановление непрозрачности. На Cocoa проверено, что проход не нужен
|
||||
// (Canvas-операции сохраняют альфу $FF), поэтому там его не делаем.
|
||||
|
||||
Reference in New Issue
Block a user