editor/bass-sys/win/bass24/delphi/livespec/livespec.dpr
2021-01-07 21:37:50 -06:00

470 lines
13 KiB
ObjectPascal

{
BASS "live" spectrum analyser example
Copyright (c) 2002-2008 Un4seen Developments Ltd.
C++ to Delphi with use API adapted by Evgeny Melnikov
Required Delphi 5 or above
http://dsoft1961.narod.ru
mail angvelem@gmail.com
}
program livespec;
uses
Windows, Messages, MMSystem, Bass in '..\bass.pas';
const
SPECWIDTH = 368; // display width
SPECHEIGHT = 127; // height (changing requires palette adjustments too)
BANDS = 28;
szAppName = 'BASS-Spectrum';
var
Window : HWND = 0;
WndGlobal : HWND = 0;
Msg : TMsg;
WndClass : TWndClassEX;
PosX, PosY : Integer;
SizeX, SizeY : Integer;
Timer : DWORD = 0;
Channel : HRECORD; // recording channel
SpecDC : HDC = 0;
SpecBmp : HBITMAP = 0;
SpecBuf : Pointer;
SpecMode : Integer = 0;
SpecPos : Integer = 0; // spectrum mode (and marker pos for 2nd mode)
quietcount : DWORD = 0;
BI : PBITMAPINFO;
pal : array[Byte] of TRGBQUAD;
{$DEFINE ScaleSqrt}
//------------------ Auxiliary functions -------------------
function Format(const Format : String; const Args : array of const ) : String;
var
I : Integer;
FormatBuffer : array[0..High(Word)] of Char;
Arr, Arr1 : PDWORD;
PP : PDWORD;
begin
Arr := NIL;
if High(Args) >= 0 then
GetMem(Arr, (High(Args) + 1) * SizeOf(Pointer));
Arr1 := Arr;
for I := 0 to High(Args) do
begin
PP := @Args[I];
PP := Pointer(PP^);
Arr1^ := DWORD(PP);
inc(Arr1);
end;
I := wvsprintf(@FormatBuffer[0], PChar(Format), PChar(Arr));
SetLength(Result, I);
Result := FormatBuffer;
if Arr <> NIL then
FreeMem(Arr);
end;
//---------------------------------------------------------
function IntPower(const Base : Extended; const Exponent : Integer) : Extended;
asm
mov ecx, eax
cdq
fld1 { Result := 1 }
xor eax, edx
sub eax, edx { eax := Abs(Exponent) }
jz @@3
fld Base
jmp @@2
@@1: fmul ST, ST { X := Base * Base }
@@2: shr eax,1
jnc @@1
fmul ST(1),ST { Result := Result * X }
jnz @@1
fstp st { pop X from FPU stack }
cmp ecx, 0
jge @@3
fld1
fdivrp { Result := 1 / Result }
@@3:
fwait
end;
//---------------------------------------------------------
function Power(const Base, Exponent : Extended) : Extended;
begin
if Exponent = 0.0 then
Result := 1.0 { n**0 = 1 }
else if (Base = 0.0) and (Exponent > 0.0) then
Result := 0.0 { 0**n = 0, n > 0 }
else if (Frac(Exponent) = 0.0) and (Abs(Exponent) <= MaxInt) then
Result := IntPower(Base, Integer(Trunc(Exponent)))
else
Result := Exp(Exponent * Ln(Base))
end;
//---------------------------------------------------------
// Log.10(X) := Log.2(X) * Log.10(2)
function Log10(const X : Extended) : Extended;
asm
FLDLG2 { Log base ten of 2 }
FLD X
FYL2X
FWAIT
end;
//==========================================================
//==========================================================
// display error messages
procedure Error(const es : String);
begin
MessageBox(WndGlobal, PChar(Format('%s' + #13#10 + '(error code: %d)', [es, BASS_ErrorGetCode])), 'Error', MB_OK or MB_ICONERROR);
end;
//---------------------------------------------------------
// update the spectrum display - the interesting bit :)
procedure UpdateSpectrum(uTimerID, uMsg, dwUser, dw1, dw2 : Integer); stdcall;
type
TSpecBuf = array[0..0] of Byte;
TBuf = array of SmallInt;
var
DC : HDC;
X, Y, Y1,
V, B0, B1,
SC : Integer;
Sum : Single;
aRect : TRect;
fft : array[0..1023] of Single;
Buf : TBuf;
SBuf : ^TSpecBuf absolute SpecBuf;
begin
if SpecMode = 3 then // waveform
begin
FillChar(SpecBuf^, SPECWIDTH * SPECHEIGHT, 0);
SetLength(Buf, SPECWIDTH);
BASS_ChannelGetData(Channel, Buf, SizeOf(SmallInt) * SPECWIDTH); // get the sample data
Y := 0;
for X := 0 to SPECWIDTH - 1 do
begin
V := (32767 - Buf[X]) * SPECHEIGHT div 65536; // invert and scale to fit display
if X = 0 then
Y := V;
repeat // draw line from previous sample...
if Y < V then
inc(Y)
else if Y > V then
dec(Y);
SBuf[Y * SPECWIDTH + X] := abs(Y - SPECHEIGHT div 2) * 2 + 1;
until Y = V;
end;
end
else
begin
BASS_ChannelGetData(Channel, @fft, BASS_DATA_FFT2048); // get the FFT data
case SpecMode of
0 : // "normal" FFT
begin
FillChar(SpecBuf^, SPECWIDTH * SPECHEIGHT, 0);
Y1 := 0;
for X := 0 to (SPECWIDTH div 2) - 1 do
begin
{$IFDEF ScaleSqrt}
Y := Trunc(sqrt(fft[X + 1]) * 3 * SPECHEIGHT - 4); // scale it (sqrt to make low values more visible)
{$ELSE}
Y := Trunc(fft[x + 1] * 10 * SPECHEIGHT); // scale it (linearly)
{$ENDIF}
if Y > SPECHEIGHT then
Y := SPECHEIGHT; // cap it
Y1 := (Y + Y1) div 2;
if (X > 0) and (Y1 > 0) then // interpolate from previous to make the display smoother
while (Y1 >= 0) do
begin
SBuf[Y1 * SPECWIDTH + X * 2 - 1] := Y1 + 1;
dec(Y1);
end;
Y1 := Y;
while (Y >= 0) do
begin
SBuf[Y * SPECWIDTH + X * 2] := Y + 1; // draw level
dec(Y);
end;
end;
end;
1 : // logarithmic, acumulate & average bins
begin
B0 := 0;
FillChar(SpecBuf^, SPECWIDTH * SPECHEIGHT, 0);
for X := 0 to BANDS - 1 do
begin
Sum := 0;
B1 := Trunc(Power(2, X * 10.0 / (BANDS - 1)));
if B1 > 1023 then
B1 := 1023;
if B1 <= B0 then
B1 := B0 + 1; // make sure it uses at least 1 FFT bin
SC := 10 + B1 - B0;
while B0 < B1 do
begin
Sum := Sum + fft[1 + B0];
inc(B0);
end;
Y := Trunc((sqrt(Sum / log10(SC)) * 1.7 * SPECHEIGHT) - 4); // scale it
if Y > SPECHEIGHT then
Y := SPECHEIGHT; // cap it
while (Y >= 0) do
begin
FillChar(SBuf[Y * SPECWIDTH + X * (SPECWIDTH div BANDS)], Trunc(0.9 * (SPECWIDTH / BANDS)), Y + 1); // draw bar
dec(Y);
end;
end;
end;
2 : // "3D"
begin
for X := 0 to SPECHEIGHT - 1 do
begin
Y := Trunc(sqrt(fft[X + 1]) * 3 * 127); // scale it (sqrt to make low values more visible)
if Y > 127 then
Y := 127; // cap it
SBuf[X * SPECWIDTH + SpecPos] := 128 + Y; // plot it
end;
// move marker onto next position
SpecPos := (SpecPos + 1) mod SPECWIDTH;
for X := 0 to SPECHEIGHT - 1 do
SBuf[X * SPECWIDTH + SpecPos] := 255;
end;
end;
end;
// update the display
DC := GetDC(WndGlobal);
try
BitBlt(DC, 0, 0, SPECWIDTH, SPECHEIGHT, SpecDC, 0, 0, SRCCOPY);
if LOWORD(BASS_ChannelGetLevel(Channel)) < 500 then
begin // check if it's quiet
inc(QuietCount);
if (QuietCount > 40) and (QuietCount and 16 > 0) then
begin // it's been quiet for over a second
SetRect(aRect, 0, 0, SPECWIDTH, SPECHEIGHT);
SetTextColor(DC, $ffffff);
SetBkMode(DC, TRANSPARENT);
DrawText(DC, 'make some noise!', -1, aRect, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
end;
end
else
QuietCount := 0; // not quiet
finally
ReleaseDC(Window, DC);
end;
end;
//---------------------------------------------------------
// Recording callback - not doing anything with the data
function DuffRecording(handle : HRECORD; const Buffer : Pointer; Length : DWORD; user : Pointer) : Boolean; stdcall;
begin
Result := True; // continue recording
end;
//---------------------------------------------------------
procedure InitApp(Wnd : HWND);
var
I : Integer;
begin
WndGlobal := Wnd;
// initialize BASS recording (default device)
if not BASS_RecordInit(-1) then
begin
Error('Can''t initialize device');
Halt;
end;
// start recording (44100hz mono 16-bit)
Channel := BASS_RecordStart(44100, 1, 0, @DuffRecording, NIL);
if Channel = 0 then
begin
Error('Can''t start recording');
Halt;
end;
// create bitmap to draw spectrum in - 8 bit for easy updating :)
GetMem(BI, SizeOf(TBitmapInfo) + 256 * SizeOf(TRGBQuad));
try
with BI^.bmiHeader do // fill structure with parameter bitmap
begin
biSize := SizeOf(BI.bmiHeader);
biWidth := SPECWIDTH;
biHeight := SPECHEIGHT; // upside down (line 0=bottom)
biPlanes := 1;
biBitCount := 8;
biClrImportant := 256;
biClrUsed := 256;
end;
// setup palette
for I := 1 to 127 do
begin
pal[I].rgbGreen := 256 - 2 * I;
pal[I].rgbRed := 2 * I;
end;
for I := 0 to 31 do
begin
pal[128 + I].rgbBlue := 8 * I;
pal[128 + 32 + I].rgbBlue := 255;
pal[128 + 32 + I].rgbRed := 8 * I;
pal[128 + 64 + I].rgbRed := 255;
pal[128 + 64 + I].rgbBlue := 8 * (31 - I);
pal[128 + 64 + I].rgbGreen := 8 * I;
pal[128 + 96 + I].rgbRed := 255;
pal[128 + 96 + I].rgbGreen := 255;
pal[128 + 96 + I].rgbBlue := 8 * I;
end;
// Move palette in BI
Move(Pal, BI^.bmiColors, 256 * SizeOf(TRGBQuad));
// create the bitmap
SpecBmp := CreateDIBSection(0, BI^, DIB_RGB_COLORS, SpecBuf, 0, 0);
SpecDC := CreateCompatibleDC(0);
SelectObject(SpecDC, SpecBmp);
finally
FreeMem(BI);
end;
// setup update timer (40hz)
timer := timeSetEvent(25, 25, @UpdateSpectrum, 0, TIME_PERIODIC);
end;
//---------------------------------------------------------
// window procedure
function SpectrumWindowProc(Wnd : HWND; Msg : Integer; wParam, lParam : Longint) : Integer; stdcall;
var
Ps : TPAINTSTRUCT;
DC : HDC;
begin
Result := 0;
case Msg of
WM_CREATE : InitApp(Wnd);
WM_PAINT :
if GetUpdateRect(Wnd, PRect(NIL)^, False) then
begin
DC := BeginPaint(Wnd, Ps);
if DC = 0 then
begin
Result := 0;
Exit;
end;
BitBlt(DC, 0, 0, SPECWIDTH, SPECHEIGHT, SpecDC, 0, 0, SRCCOPY);
EndPaint(Wnd, Ps);
Result := 0;
Exit;
end;
WM_LBUTTONUP :
begin
SpecMode := (SpecMode + 1) mod 4; // swap spectrum mode
if SpecMode = 2 then
SpecPos := 0;
FillChar(SpecBuf^, SPECWIDTH * SPECHEIGHT, 0); // clear display
Result := 0;
Exit;
end;
WM_CLOSE :
begin
DestroyWindow(Wnd);
end;
WM_DESTROY :
begin
if timer <> 0 then
timeKillEvent(timer);
BASS_RecordFree;
if SpecDC <> 0 then
DeleteDC(SpecDC);
if SpecBmp <> 0 then
DeleteObject(specbmp);
PostQuitMessage(0);
Exit;
end;
end;
Result := DefWindowProc(Wnd, Msg, wParam, lParam);
end;
//---------------------------------------------------------
begin
Window := FindWindow(szAppName, NIL);
if Window <> 0 then
begin
if IsIconic(Window) then
ShowWindow(Window, SW_RESTORE);
SetForegroundWindow(Window);
Halt(254);
end;
// check the correct BASS was loaded
if HIWORD(BASS_GetVersion) <> BASSVERSION then
begin
MessageBox(0, 'An incorrect version of BASS.DLL was loaded', '', MB_ICONERROR);
Exit;
end;
// register window class and create the window
FillChar(WndClass, SizeOf(TWndClassEx), 0);
WndClass.cbSize := SizeOf(TWndClassEx);
WndClass.style := CS_HREDRAW or CS_VREDRAW;
WndClass.lpfnWndProc := @SpectrumWindowProc;
WndClass.cbClsExtra := 0;
WndClass.cbWndExtra := 0;
WndClass.hInstance := hInstance;
WndClass.hCursor := LoadCursor(0, IDC_ARROW);
WndClass.hbrBackGround := GetSysColorBrush(COLOR_BTNFACE);
WndClass.lpszClassName := szAppName;
if RegisterClassEx(WndClass) = 0 then
Halt(255);
SizeX := SPECWIDTH + 2 * GetSystemMetrics(SM_CXDLGFRAME);
SizeY := SPECHEIGHT + 2 * GetSystemMetrics(SM_CYDLGFRAME) + GetSystemMetrics(SM_CYCAPTION);
PosX := (GetSystemMetrics(SM_CXSCREEN) - SizeX) div 2;
PosY := (GetSystemMetrics(SM_CYSCREEN) - SizeY) div 2;
Window := CreateWindowEx(0, szAppName, 'BASS "live" spectrum (click to toggle mode)',
WS_POPUPWINDOW or WS_CAPTION,
PosX, PosY, SizeX, SizeY, 0, 0, hInstance, NIL);
ShowWindow(Window, SW_SHOWNORMAL);
while (GetMessage(Msg, 0, 0, 0)) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
Halt(Msg.wParam);
end.