mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
Add wideband display pane
This commit is contained in:
@@ -0,0 +1,752 @@
|
||||
unit WidebandView;
|
||||
|
||||
{
|
||||
WidebandView.pas - raw ADC wideband spectrum pane.
|
||||
|
||||
The network layer feeds 16-bit ADC samples collected from Protocol V4
|
||||
wideband packets. This view windows the frame, runs an in-process real FFT
|
||||
and renders a compact Thetis-style wideband panadapter. CPU paint uses a
|
||||
bitmap; when MainForm creates a TOpenGLControl the same spectrum is rendered
|
||||
as GL primitives.
|
||||
}
|
||||
|
||||
{$IFDEF FPC}
|
||||
{$MODE Delphi}
|
||||
{$ENDIF}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, Math, Graphics, Controls, ExtCtrls,
|
||||
OpenGLContext, GL, AppTheme;
|
||||
|
||||
type
|
||||
TWidebandView = class
|
||||
private
|
||||
FBitmap: TBitmap;
|
||||
FRulerBitmap: TBitmap;
|
||||
FTheme: TAppTheme;
|
||||
FData: array of Single;
|
||||
FPoints: array of TPoint;
|
||||
FGLTex: GLuint;
|
||||
FDirty: Boolean;
|
||||
FSampleRateHz: Double;
|
||||
FRefLevel: Double;
|
||||
FRange: Double;
|
||||
FCalOffset: Double;
|
||||
FViewStartHz: Double;
|
||||
FViewEndHz: Double;
|
||||
FSourceStartHz: Double;
|
||||
FSourceEndHz: Double;
|
||||
FMarkerHz: Double;
|
||||
procedure EnsureBitmap(W, H: Integer);
|
||||
procedure ComputeSpectrum(const Samples: array of SmallInt; Count: Integer);
|
||||
procedure DrawCPU(W, H: Integer);
|
||||
procedure PaintCPU(C: TCanvas);
|
||||
procedure PaintGL(C: TOpenGLControl);
|
||||
procedure ColorToGL(AColor: TColor; out R, G, B: GLFloat);
|
||||
function BinToMHz(Bin, BinCount: Integer): Double;
|
||||
function FreqToX(FreqHz: Double; W: Integer): Integer;
|
||||
function GridStepHz(W: Integer): Double;
|
||||
function RulerGridStepHz(C: TCanvas; PlotW: Integer): Double;
|
||||
procedure PaintVerticalGradient(C: TCanvas; W, H: Integer; TopColor, BottomColor: TColor);
|
||||
procedure AlphaFillRect(const R: TRect; AColor: TColor; Alpha: Byte);
|
||||
procedure DrawHamBands(C: TCanvas; PlotW, H: Integer);
|
||||
procedure DrawHamBand(C: TCanvas; PlotW, H: Integer; F1, F2: Double;
|
||||
const Name: string; AColor: TColor);
|
||||
procedure DrawFrequencyMarker(C: TCanvas; PlotW, H: Integer);
|
||||
procedure UploadBitmapToGL;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
procedure SetTheme(const T: TAppTheme);
|
||||
procedure SetSampleRateHz(AHz: Double);
|
||||
function SetFrequencyView(ViewStartHz, ViewEndHz, SourceStartHz,
|
||||
SourceEndHz: Double): Boolean;
|
||||
function SetMarkerHz(AHz: Double): Boolean;
|
||||
function TryPixelToFrequency(PixelX, PanelWidth: Integer;
|
||||
out FreqHz: Double): Boolean;
|
||||
procedure SetSamples(const Samples: array of SmallInt; Count: Integer);
|
||||
procedure SetBitmapSize(W, H: Integer);
|
||||
procedure SetRulerSize(W, H: Integer);
|
||||
procedure Draw;
|
||||
procedure DrawRuler;
|
||||
procedure Paint(Sender: TObject);
|
||||
procedure PaintRuler(Sender: TObject);
|
||||
property Dirty: Boolean read FDirty write FDirty;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
WB_DB_SCALE_W = 34;
|
||||
|
||||
constructor TWidebandView.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
FBitmap := TBitmap.Create;
|
||||
FBitmap.PixelFormat := pf32bit;
|
||||
FRulerBitmap := TBitmap.Create;
|
||||
FRulerBitmap.PixelFormat := pf32bit;
|
||||
FTheme := DarkTheme;
|
||||
FSampleRateHz := 122880000.0;
|
||||
FRefLevel := -50.0;
|
||||
FRange := 120.0;
|
||||
FCalOffset := -40.1;
|
||||
FViewStartHz := 0.0;
|
||||
FViewEndHz := FSampleRateHz * 0.5;
|
||||
FSourceStartHz := 0.0;
|
||||
FSourceEndHz := FSampleRateHz * 0.5;
|
||||
FMarkerHz := 0.0;
|
||||
FGLTex := 0;
|
||||
FDirty := True;
|
||||
end;
|
||||
|
||||
destructor TWidebandView.Destroy;
|
||||
begin
|
||||
if FGLTex <> 0 then
|
||||
glDeleteTextures(1, @FGLTex);
|
||||
FRulerBitmap.Free;
|
||||
FBitmap.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.SetTheme(const T: TAppTheme);
|
||||
begin
|
||||
FTheme := T;
|
||||
FDirty := True;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.SetSampleRateHz(AHz: Double);
|
||||
begin
|
||||
if AHz > 0 then
|
||||
begin
|
||||
FSampleRateHz := AHz;
|
||||
SetFrequencyView(0.0, FSampleRateHz * 0.5, 0.0, FSampleRateHz * 0.5);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWidebandView.SetFrequencyView(ViewStartHz, ViewEndHz, SourceStartHz,
|
||||
SourceEndHz: Double): Boolean;
|
||||
var
|
||||
Nyq: Double;
|
||||
T: Double;
|
||||
begin
|
||||
Result := False;
|
||||
Nyq := FSampleRateHz * 0.5;
|
||||
if Nyq <= 0.0 then Nyq := 61440000.0;
|
||||
if ViewEndHz <= ViewStartHz then
|
||||
begin
|
||||
ViewStartHz := 0.0;
|
||||
ViewEndHz := Nyq;
|
||||
end;
|
||||
if SourceEndHz < SourceStartHz then
|
||||
begin
|
||||
T := SourceStartHz;
|
||||
SourceStartHz := SourceEndHz;
|
||||
SourceEndHz := T;
|
||||
end;
|
||||
if (Abs(FViewStartHz - ViewStartHz) < 0.5) and
|
||||
(Abs(FViewEndHz - ViewEndHz) < 0.5) and
|
||||
(Abs(FSourceStartHz - SourceStartHz) < 0.5) and
|
||||
(Abs(FSourceEndHz - SourceEndHz) < 0.5) then Exit;
|
||||
FViewStartHz := ViewStartHz;
|
||||
FViewEndHz := ViewEndHz;
|
||||
FSourceStartHz := SourceStartHz;
|
||||
FSourceEndHz := SourceEndHz;
|
||||
FDirty := True;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TWidebandView.SetMarkerHz(AHz: Double): Boolean;
|
||||
begin
|
||||
Result := False;
|
||||
if Abs(FMarkerHz - AHz) < 0.5 then Exit;
|
||||
FMarkerHz := AHz;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function TWidebandView.TryPixelToFrequency(PixelX, PanelWidth: Integer;
|
||||
out FreqHz: Double): Boolean;
|
||||
var
|
||||
PlotW: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
FreqHz := 0.0;
|
||||
PlotW := Max(1, PanelWidth - WB_DB_SCALE_W);
|
||||
if (PanelWidth <= 0) or (PixelX < 0) or (PixelX >= PlotW) then Exit;
|
||||
if FViewEndHz <= FViewStartHz then Exit;
|
||||
FreqHz := FViewStartHz +
|
||||
PixelX / Max(1, PlotW - 1) * (FViewEndHz - FViewStartHz);
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.EnsureBitmap(W, H: Integer);
|
||||
begin
|
||||
W := Max(1, W);
|
||||
H := Max(1, H);
|
||||
if (FBitmap.Width <> W) or (FBitmap.Height <> H) then
|
||||
FBitmap.SetSize(W, H);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.SetBitmapSize(W, H: Integer);
|
||||
begin
|
||||
EnsureBitmap(W, H);
|
||||
FDirty := True;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.SetRulerSize(W, H: Integer);
|
||||
begin
|
||||
W := Max(1, W);
|
||||
H := Max(1, H);
|
||||
if (FRulerBitmap.Width <> W) or (FRulerBitmap.Height <> H) then
|
||||
FRulerBitmap.SetSize(W, H);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.ComputeSpectrum(const Samples: array of SmallInt;
|
||||
Count: Integer);
|
||||
var
|
||||
N, Half, I, J, K, M, Step: Integer;
|
||||
Wr, Wi, Ur, Ui, Tr, Ti, Ang, Re, Im, Mag, Win, WinSum: Double;
|
||||
RealBuf, ImagBuf: array of Double;
|
||||
begin
|
||||
if Count < 256 then Exit;
|
||||
N := 1;
|
||||
while (N shl 1 <= Count) and (N shl 1 <= 16384) do
|
||||
N := N shl 1;
|
||||
Half := N div 2;
|
||||
SetLength(RealBuf, N);
|
||||
SetLength(ImagBuf, N);
|
||||
WinSum := 0.0;
|
||||
for I := 0 to N - 1 do
|
||||
begin
|
||||
Win := 0.35875 - 0.48829 * Cos(2 * Pi * I / (N - 1)) +
|
||||
0.14128 * Cos(4 * Pi * I / (N - 1)) -
|
||||
0.01168 * Cos(6 * Pi * I / (N - 1));
|
||||
WinSum := WinSum + Win;
|
||||
RealBuf[I] := Samples[I] / 32768.0 * Win;
|
||||
ImagBuf[I] := 0.0;
|
||||
end;
|
||||
if WinSum <= 0.0 then WinSum := N;
|
||||
|
||||
J := 0;
|
||||
for I := 1 to N - 2 do
|
||||
begin
|
||||
K := N shr 1;
|
||||
while J >= K do
|
||||
begin
|
||||
Dec(J, K);
|
||||
K := K shr 1;
|
||||
end;
|
||||
Inc(J, K);
|
||||
if I < J then
|
||||
begin
|
||||
Re := RealBuf[I]; RealBuf[I] := RealBuf[J]; RealBuf[J] := Re;
|
||||
Im := ImagBuf[I]; ImagBuf[I] := ImagBuf[J]; ImagBuf[J] := Im;
|
||||
end;
|
||||
end;
|
||||
|
||||
M := 2;
|
||||
while M <= N do
|
||||
begin
|
||||
Ang := -2 * Pi / M;
|
||||
Wr := Cos(Ang);
|
||||
Wi := Sin(Ang);
|
||||
Step := M div 2;
|
||||
K := 0;
|
||||
while K < N do
|
||||
begin
|
||||
Ur := 1.0;
|
||||
Ui := 0.0;
|
||||
for J := 0 to Step - 1 do
|
||||
begin
|
||||
I := K + J;
|
||||
Tr := Ur * RealBuf[I + Step] - Ui * ImagBuf[I + Step];
|
||||
Ti := Ur * ImagBuf[I + Step] + Ui * RealBuf[I + Step];
|
||||
RealBuf[I + Step] := RealBuf[I] - Tr;
|
||||
ImagBuf[I + Step] := ImagBuf[I] - Ti;
|
||||
RealBuf[I] := RealBuf[I] + Tr;
|
||||
ImagBuf[I] := ImagBuf[I] + Ti;
|
||||
Re := Ur * Wr - Ui * Wi;
|
||||
Ui := Ur * Wi + Ui * Wr;
|
||||
Ur := Re;
|
||||
end;
|
||||
Inc(K, M);
|
||||
end;
|
||||
M := M shl 1;
|
||||
end;
|
||||
|
||||
SetLength(FData, Half);
|
||||
for I := 0 to Half - 1 do
|
||||
begin
|
||||
Mag := Sqr(RealBuf[I]) + Sqr(ImagBuf[I]);
|
||||
// Convert FFT bin amplitude to dBFS, then apply Thetis-style display
|
||||
// calibration offset. The previous unnormalised FFT power depended on N
|
||||
// and could not line up with the dB scale.
|
||||
FData[I] := 20.0 * Log10((2.0 * Sqrt(Mag) / WinSum) + 1.0E-20) + FCalOffset;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.SetSamples(const Samples: array of SmallInt; Count: Integer);
|
||||
begin
|
||||
ComputeSpectrum(Samples, Count);
|
||||
FDirty := True;
|
||||
end;
|
||||
|
||||
function TWidebandView.BinToMHz(Bin, BinCount: Integer): Double;
|
||||
begin
|
||||
if BinCount <= 1 then Result := 0
|
||||
else Result := (Bin / (BinCount - 1)) * (FSampleRateHz * 0.5) / 1000000.0;
|
||||
end;
|
||||
|
||||
function TWidebandView.FreqToX(FreqHz: Double; W: Integer): Integer;
|
||||
var
|
||||
SpanHz: Double;
|
||||
begin
|
||||
SpanHz := FViewEndHz - FViewStartHz;
|
||||
if SpanHz <= 0.0 then SpanHz := Max(1.0, FSampleRateHz * 0.5);
|
||||
Result := Round((FreqHz - FViewStartHz) / SpanHz * Max(1, W - 1));
|
||||
end;
|
||||
|
||||
function TWidebandView.GridStepHz(W: Integer): Double;
|
||||
var
|
||||
PixPerMHz, SpanMHz: Double;
|
||||
begin
|
||||
SpanMHz := Max(0.001, (FViewEndHz - FViewStartHz) / 1000000.0);
|
||||
PixPerMHz := W / SpanMHz;
|
||||
if PixPerMHz >= 44.0 then
|
||||
Result := 500000.0
|
||||
else if PixPerMHz >= 18.0 then
|
||||
Result := 1000000.0
|
||||
else if PixPerMHz >= 10.0 then
|
||||
Result := 2000000.0
|
||||
else if PixPerMHz >= 5.0 then
|
||||
Result := 5000000.0
|
||||
else
|
||||
Result := 10000000.0;
|
||||
end;
|
||||
|
||||
function TWidebandView.RulerGridStepHz(C: TCanvas; PlotW: Integer): Double;
|
||||
var
|
||||
BaseStep, PixPerStep: Double;
|
||||
LabelMult: Integer;
|
||||
begin
|
||||
BaseStep := GridStepHz(PlotW);
|
||||
PixPerStep := PlotW * BaseStep / Max(1.0, FViewEndHz - FViewStartHz);
|
||||
if PixPerStep >= 1.0 then
|
||||
LabelMult := Max(1, Ceil((C.TextWidth('000.0') + 8) / PixPerStep))
|
||||
else
|
||||
LabelMult := MaxInt;
|
||||
Result := BaseStep * LabelMult;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.PaintVerticalGradient(C: TCanvas; W, H: Integer;
|
||||
TopColor, BottomColor: TColor);
|
||||
var
|
||||
Y, B1, G1, R1, B2, G2, R2, B, G, R: Integer;
|
||||
T: Double;
|
||||
begin
|
||||
if (W <= 0) or (H <= 0) then Exit;
|
||||
B1 := (TopColor shr 16) and $FF;
|
||||
G1 := (TopColor shr 8) and $FF;
|
||||
R1 := TopColor and $FF;
|
||||
B2 := (BottomColor shr 16) and $FF;
|
||||
G2 := (BottomColor shr 8) and $FF;
|
||||
R2 := BottomColor and $FF;
|
||||
C.Pen.Style := psClear;
|
||||
C.Brush.Style := bsSolid;
|
||||
for Y := 0 to H - 1 do
|
||||
begin
|
||||
T := Y / Max(1, H - 1);
|
||||
B := Round(B1 + (B2 - B1) * T);
|
||||
G := Round(G1 + (G2 - G1) * T);
|
||||
R := Round(R1 + (R2 - R1) * T);
|
||||
C.Brush.Color := TColor((B shl 16) or (G shl 8) or R);
|
||||
C.FillRect(Rect(0, Y, W, Y + 1));
|
||||
end;
|
||||
C.Pen.Style := psSolid;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.AlphaFillRect(const R: TRect; AColor: TColor; Alpha: Byte);
|
||||
var
|
||||
X, Y: Integer;
|
||||
Row: PByte;
|
||||
BR, BG, BB: Integer;
|
||||
SR, SG, SB: Integer;
|
||||
RR: TRect;
|
||||
begin
|
||||
if Alpha = 0 then Exit;
|
||||
RR := Rect(
|
||||
EnsureRange(R.Left, 0, FBitmap.Width),
|
||||
EnsureRange(R.Top, 0, FBitmap.Height),
|
||||
EnsureRange(R.Right, 0, FBitmap.Width),
|
||||
EnsureRange(R.Bottom, 0, FBitmap.Height));
|
||||
if (RR.Right <= RR.Left) or (RR.Bottom <= RR.Top) then Exit;
|
||||
|
||||
SR := AColor and $FF;
|
||||
SG := (AColor shr 8) and $FF;
|
||||
SB := (AColor shr 16) and $FF;
|
||||
FBitmap.BeginUpdate(False);
|
||||
try
|
||||
for Y := RR.Top to RR.Bottom - 1 do
|
||||
begin
|
||||
Row := PByte(FBitmap.ScanLine[Y]);
|
||||
Inc(Row, RR.Left * 4);
|
||||
for X := RR.Left to RR.Right - 1 do
|
||||
begin
|
||||
BB := Row[0];
|
||||
BG := Row[1];
|
||||
BR := Row[2];
|
||||
Row[0] := Byte((SB * Alpha + BB * (255 - Alpha)) div 255);
|
||||
Row[1] := Byte((SG * Alpha + BG * (255 - Alpha)) div 255);
|
||||
Row[2] := Byte((SR * Alpha + BR * (255 - Alpha)) div 255);
|
||||
Inc(Row, 4);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
FBitmap.EndUpdate(False);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.DrawHamBand(C: TCanvas; PlotW, H: Integer;
|
||||
F1, F2: Double; const Name: string; AColor: TColor);
|
||||
var
|
||||
X1, X2, LabelX, LabelY, TW, TH: Integer;
|
||||
R: TRect;
|
||||
begin
|
||||
X1 := FreqToX(F1, PlotW);
|
||||
X2 := FreqToX(F2, PlotW);
|
||||
if (X2 <= 0) or (X1 >= PlotW) then Exit;
|
||||
X1 := EnsureRange(X1, 0, PlotW - 1);
|
||||
X2 := EnsureRange(X2, 0, PlotW - 1);
|
||||
if X2 <= X1 then Exit;
|
||||
|
||||
R := Rect(X1, 0, X2 + 1, H);
|
||||
AlphaFillRect(R, AColor, 86);
|
||||
AlphaFillRect(Rect(X1, 0, X2 + 1, Min(H, 15)), AColor, 118);
|
||||
|
||||
C.Font.Name := 'Sans';
|
||||
C.Font.Size := 7;
|
||||
TW := C.TextWidth(Name);
|
||||
TH := C.TextHeight(Name);
|
||||
if X2 - X1 >= 2 then
|
||||
begin
|
||||
LabelX := EnsureRange(((X1 + X2) div 2) - TW div 2, 2, Max(2, PlotW - TW - 5));
|
||||
LabelY := Max(1, Min(H - TH - 1, 2));
|
||||
C.Font.Color := TColor($00EAF7D7);
|
||||
C.TextOut(LabelX, LabelY, Name);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.DrawHamBands(C: TCanvas; PlotW, H: Integer);
|
||||
begin
|
||||
DrawHamBand(C, PlotW, H, 1810000, 2000000, '160m', TColor($003B8F5A));
|
||||
DrawHamBand(C, PlotW, H, 3500000, 3800000, '80m', TColor($004D8F3B));
|
||||
DrawHamBand(C, PlotW, H, 5258500, 5403500, '60m', TColor($00688F3B));
|
||||
DrawHamBand(C, PlotW, H, 7000000, 7300000, '40m', TColor($00808D34));
|
||||
DrawHamBand(C, PlotW, H, 10100000, 10150000, '30m', TColor($008F7834));
|
||||
DrawHamBand(C, PlotW, H, 14000000, 14350000, '20m', TColor($008F5D34));
|
||||
DrawHamBand(C, PlotW, H, 18068000, 18168000, '17m', TColor($008F4934));
|
||||
DrawHamBand(C, PlotW, H, 21000000, 21450000, '15m', TColor($008C3E58));
|
||||
DrawHamBand(C, PlotW, H, 24890000, 24990000, '12m', TColor($007A3E8C));
|
||||
DrawHamBand(C, PlotW, H, 28000000, 29700000, '10m', TColor($005B4B9A));
|
||||
DrawHamBand(C, PlotW, H, 50000000, 51990000, '6m', TColor($003C729A));
|
||||
end;
|
||||
|
||||
procedure TWidebandView.DrawFrequencyMarker(C: TCanvas; PlotW, H: Integer);
|
||||
var
|
||||
X: Integer;
|
||||
begin
|
||||
if (FMarkerHz < FViewStartHz) or (FMarkerHz > FViewEndHz) then Exit;
|
||||
X := EnsureRange(FreqToX(FMarkerHz, PlotW), 0, PlotW - 1);
|
||||
C.Pen.Width := 1;
|
||||
C.Pen.Color := TColor($006DA6FF);
|
||||
C.MoveTo(Max(0, X - 2), 0);
|
||||
C.LineTo(Min(PlotW - 1, X + 2), 0);
|
||||
C.Pen.Color := TColor($00FFF3C0);
|
||||
C.MoveTo(X, 0);
|
||||
C.LineTo(X, H);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.DrawCPU(W, H: Integer);
|
||||
var
|
||||
C: TCanvas;
|
||||
X, Y, Bin, N, TopH, PlotW, ScaleX: Integer;
|
||||
DB, DBMin, InvRange, StepHz, FreqHz, SrcHz, Nyq: Double;
|
||||
begin
|
||||
EnsureBitmap(W, H);
|
||||
C := FBitmap.Canvas;
|
||||
C.Brush.Color := FTheme.BG;
|
||||
C.FillRect(0, 0, W, H);
|
||||
PlotW := Max(1, W - WB_DB_SCALE_W);
|
||||
ScaleX := PlotW;
|
||||
DBMin := FRefLevel - FRange;
|
||||
InvRange := 1.0 / Max(1.0, FRange);
|
||||
|
||||
C.Pen.Color := FTheme.SpecGrid;
|
||||
C.Font.Color := FTheme.TextDim;
|
||||
C.Font.Size := 8;
|
||||
StepHz := RulerGridStepHz(C, PlotW);
|
||||
FreqHz := Ceil(FViewStartHz / StepHz) * StepHz;
|
||||
while FreqHz <= FViewEndHz + 0.5 do
|
||||
begin
|
||||
X := FreqToX(FreqHz, PlotW);
|
||||
C.Line(X, 0, X, H);
|
||||
FreqHz := FreqHz + StepHz;
|
||||
end;
|
||||
TopH := Max(1, H - 1);
|
||||
DB := FRefLevel - 20.0;
|
||||
while DB >= DBMin do
|
||||
begin
|
||||
Y := Round((FRefLevel - DB) * InvRange * TopH);
|
||||
C.Line(0, Y, W, Y);
|
||||
DB := DB - 20.0;
|
||||
end;
|
||||
|
||||
C.Pen.Style := psClear;
|
||||
C.Brush.Style := bsSolid;
|
||||
C.Brush.Color := FTheme.SpecLabelBand;
|
||||
C.FillRect(Rect(ScaleX, 0, W, H));
|
||||
C.Pen.Style := psSolid;
|
||||
C.Pen.Color := FTheme.SpecGrid;
|
||||
C.MoveTo(ScaleX, 0); C.LineTo(ScaleX, H);
|
||||
C.Font.Name := 'Courier New';
|
||||
C.Font.Size := 7;
|
||||
C.Font.Color := FTheme.SpecLabelText;
|
||||
C.Brush.Style := bsClear;
|
||||
DB := FRefLevel - 20.0;
|
||||
while DB >= DBMin do
|
||||
begin
|
||||
Y := Round((FRefLevel - DB) * InvRange * TopH);
|
||||
C.TextOut(ScaleX + 2, Y - 9, Format('%4.0f', [DB]));
|
||||
DB := DB - 20.0;
|
||||
end;
|
||||
|
||||
N := Length(FData);
|
||||
if N > 1 then
|
||||
begin
|
||||
SetLength(FPoints, PlotW);
|
||||
Nyq := FSampleRateHz * 0.5;
|
||||
if Nyq <= 0.0 then Nyq := 61440000.0;
|
||||
for X := 0 to PlotW - 1 do
|
||||
begin
|
||||
SrcHz := FSourceStartHz +
|
||||
X / Max(1, PlotW - 1) * (FSourceEndHz - FSourceStartHz);
|
||||
if (SrcHz < 0.0) or (SrcHz > Nyq) then
|
||||
DB := -200.0
|
||||
else
|
||||
begin
|
||||
Bin := EnsureRange(Round(SrcHz / Nyq * (N - 1)), 0, N - 1);
|
||||
DB := FData[Bin];
|
||||
end;
|
||||
Y := Round((FRefLevel - DB) * InvRange * TopH);
|
||||
Y := EnsureRange(Y, 1, TopH);
|
||||
FPoints[X] := Point(X, Y);
|
||||
end;
|
||||
C.Pen.Color := TColor($0040FF80);
|
||||
C.Polyline(FPoints);
|
||||
end;
|
||||
|
||||
DrawHamBands(C, PlotW, H);
|
||||
|
||||
C.Font.Color := FTheme.Text;
|
||||
C.TextOut(6, 4, 'Wideband');
|
||||
FDirty := False;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.DrawRuler;
|
||||
var
|
||||
C: TCanvas;
|
||||
W, H, X, TW, PlotW: Integer;
|
||||
StepHz, FreqHz: Double;
|
||||
Lbl: string;
|
||||
begin
|
||||
W := FRulerBitmap.Width;
|
||||
H := FRulerBitmap.Height;
|
||||
if (W <= 0) or (H <= 0) then Exit;
|
||||
C := FRulerBitmap.Canvas;
|
||||
PaintVerticalGradient(C, W, H, FTheme.RulerGradTop, FTheme.RulerGradBot);
|
||||
C.Pen.Color := FTheme.RulerBorder;
|
||||
C.Pen.Width := 1;
|
||||
C.MoveTo(0, 0); C.LineTo(W, 0);
|
||||
C.MoveTo(0, H - 1); C.LineTo(W, H - 1);
|
||||
C.Font.Name := 'Courier New';
|
||||
C.Font.Size := 7;
|
||||
C.Brush.Style := bsClear;
|
||||
C.Font.Color := FTheme.RulerText;
|
||||
|
||||
PlotW := Max(1, W - WB_DB_SCALE_W);
|
||||
StepHz := RulerGridStepHz(C, PlotW);
|
||||
|
||||
FreqHz := Ceil(FViewStartHz / StepHz) * StepHz;
|
||||
while FreqHz <= FViewEndHz + 0.5 do
|
||||
begin
|
||||
X := FreqToX(FreqHz, PlotW);
|
||||
C.Pen.Color := FTheme.RulerBorder;
|
||||
C.MoveTo(X, 0); C.LineTo(X, H div 2);
|
||||
if FreqHz >= 100000000.0 then
|
||||
Lbl := FormatFloat('0.###', FreqHz / 1000000.0)
|
||||
else
|
||||
Lbl := FormatFloat('0.#', FreqHz / 1000000.0);
|
||||
TW := C.TextWidth(Lbl);
|
||||
C.TextOut(EnsureRange(X - TW div 2, 2, Max(2, PlotW - TW - 2)), H div 2 - 1, Lbl);
|
||||
FreqHz := FreqHz + StepHz;
|
||||
end;
|
||||
C.Pen.Color := FTheme.RulerBorder;
|
||||
C.MoveTo(PlotW, 0); C.LineTo(PlotW, H);
|
||||
C.Brush.Style := bsSolid;
|
||||
C.Brush.Color := FTheme.SpecLabelBand;
|
||||
C.FillRect(Rect(PlotW + 1, 1, W, H - 1));
|
||||
C.Brush.Style := bsClear;
|
||||
C.Font.Color := FTheme.TextDim;
|
||||
Lbl := 'MHz';
|
||||
C.TextOut(Max(2, PlotW - C.TextWidth(Lbl) - 4), H div 2 - 1, Lbl);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.Draw;
|
||||
begin
|
||||
DrawCPU(FBitmap.Width, FBitmap.Height);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.PaintCPU(C: TCanvas);
|
||||
begin
|
||||
if FDirty then DrawCPU(FBitmap.Width, FBitmap.Height);
|
||||
C.Draw(0, 0, FBitmap);
|
||||
DrawFrequencyMarker(C, Max(1, FBitmap.Width - WB_DB_SCALE_W), FBitmap.Height);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.ColorToGL(AColor: TColor; out R, G, B: GLFloat);
|
||||
begin
|
||||
R := (AColor and $FF) / 255.0;
|
||||
G := ((AColor shr 8) and $FF) / 255.0;
|
||||
B := ((AColor shr 16) and $FF) / 255.0;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.UploadBitmapToGL;
|
||||
var
|
||||
X, Y, I: Integer;
|
||||
Src: PByte;
|
||||
Buf: array of Byte;
|
||||
begin
|
||||
if (FBitmap.Width <= 0) or (FBitmap.Height <= 0) then Exit;
|
||||
if FGLTex = 0 then
|
||||
glGenTextures(1, @FGLTex);
|
||||
SetLength(Buf, FBitmap.Width * FBitmap.Height * 4);
|
||||
FBitmap.BeginUpdate(False);
|
||||
try
|
||||
I := 0;
|
||||
for Y := 0 to FBitmap.Height - 1 do
|
||||
begin
|
||||
Src := PByte(FBitmap.ScanLine[Y]);
|
||||
for X := 0 to FBitmap.Width - 1 do
|
||||
begin
|
||||
Buf[I] := Src[2];
|
||||
Buf[I + 1] := Src[1];
|
||||
Buf[I + 2] := Src[0];
|
||||
Buf[I + 3] := $FF;
|
||||
Inc(Src, 4);
|
||||
Inc(I, 4);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
FBitmap.EndUpdate(False);
|
||||
end;
|
||||
glBindTexture(GL_TEXTURE_2D, FGLTex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FBitmap.Width, FBitmap.Height, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, @Buf[0]);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.PaintGL(C: TOpenGLControl);
|
||||
var
|
||||
W, H, PlotW, X: Integer;
|
||||
NeedUpload: Boolean;
|
||||
begin
|
||||
if C = nil then Exit;
|
||||
W := Max(1, C.Width);
|
||||
H := Max(1, C.Height);
|
||||
if not C.MakeCurrent then Exit;
|
||||
NeedUpload := False;
|
||||
if (FBitmap.Width <> W) or (FBitmap.Height <> H) then
|
||||
begin
|
||||
SetBitmapSize(W, H);
|
||||
NeedUpload := True;
|
||||
end;
|
||||
if FDirty then
|
||||
begin
|
||||
DrawCPU(W, H);
|
||||
NeedUpload := True;
|
||||
end;
|
||||
if FGLTex = 0 then
|
||||
NeedUpload := True;
|
||||
if NeedUpload then
|
||||
UploadBitmapToGL;
|
||||
glViewport(0, 0, W, H);
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity;
|
||||
glOrtho(0, W, 0, H, -1, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity;
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClearColor(0, 0, 0, 1.0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, FGLTex);
|
||||
glColor3f(1, 1, 1);
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0, 1); glVertex2f(0, 0);
|
||||
glTexCoord2f(1, 1); glVertex2f(W, 0);
|
||||
glTexCoord2f(1, 0); glVertex2f(W, H);
|
||||
glTexCoord2f(0, 0); glVertex2f(0, H);
|
||||
glEnd;
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
if (FMarkerHz >= FViewStartHz) and (FMarkerHz <= FViewEndHz) then
|
||||
begin
|
||||
PlotW := Max(1, W - WB_DB_SCALE_W);
|
||||
X := EnsureRange(FreqToX(FMarkerHz, PlotW), 0, PlotW - 1);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glColor4f(1.0, 0.64, 0.36, 0.32);
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(Max(0, X - 2), 0);
|
||||
glVertex2f(Min(PlotW - 1, X + 2), 0);
|
||||
glVertex2f(Min(PlotW - 1, X + 2), H);
|
||||
glVertex2f(Max(0, X - 2), H);
|
||||
glEnd;
|
||||
glColor4f(1.0, 0.95, 0.75, 0.90);
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(X, 0);
|
||||
glVertex2f(X, H);
|
||||
glEnd;
|
||||
glDisable(GL_BLEND);
|
||||
end;
|
||||
|
||||
C.SwapBuffers;
|
||||
FDirty := False;
|
||||
end;
|
||||
|
||||
procedure TWidebandView.Paint(Sender: TObject);
|
||||
begin
|
||||
if Sender is TOpenGLControl then
|
||||
PaintGL(TOpenGLControl(Sender))
|
||||
else if Sender is TPaintBox then
|
||||
PaintCPU(TPaintBox(Sender).Canvas);
|
||||
end;
|
||||
|
||||
procedure TWidebandView.PaintRuler(Sender: TObject);
|
||||
var
|
||||
PB: TPaintBox;
|
||||
begin
|
||||
if not (Sender is TPaintBox) then Exit;
|
||||
PB := TPaintBox(Sender);
|
||||
if (FRulerBitmap.Width <> PB.Width) or (FRulerBitmap.Height <> PB.Height) then
|
||||
SetRulerSize(PB.Width, PB.Height);
|
||||
DrawRuler;
|
||||
if (FRulerBitmap.Width > 0) and (FRulerBitmap.Height > 0) then
|
||||
PB.Canvas.Draw(0, 0, FRulerBitmap);
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user