470 lines
13 KiB
ObjectPascal
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.
|