/* BASS spectrum analyser example Copyright (c) 2002-2014 Un4seen Developments Ltd. */ #include #include #include #include #include "bass.h" #define SPECWIDTH 368 // display width #define SPECHEIGHT 127 // height (changing requires palette adjustments too) HWND win = NULL; DWORD timer = 0; DWORD chan; HDC specdc = 0; HBITMAP specbmp = 0; BYTE *specbuf; int specmode = 0, specpos = 0; // spectrum mode (and marker pos for 3D mode) // display error messages void Error(const char *es) { char mes[200]; sprintf(mes, "%s\n(error code: %d)", es, BASS_ErrorGetCode()); MessageBox(win, mes, 0, 0); } // select a file to play, and play it BOOL PlayFile() { char file[MAX_PATH] = ""; OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = win; ofn.nMaxFile = MAX_PATH; ofn.lpstrFile = file; ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER; ofn.lpstrTitle = "Select a file to play"; ofn.lpstrFilter = "playable files\0*.mo3;*.xm;*.mod;*.s3m;*.it;*.mtm;*.umx;*.mp3;*.mp2;*.mp1;*.ogg;*.wav;*.aif\0All files\0*.*\0\0"; if (!GetOpenFileName(&ofn)) return FALSE; if (!(chan = BASS_StreamCreateFile(FALSE, file, 0, 0, BASS_SAMPLE_LOOP)) && !(chan = BASS_MusicLoad(FALSE, file, 0, 0, BASS_MUSIC_RAMP | BASS_SAMPLE_LOOP, 1))) { Error("Can't play file"); return FALSE; // Can't load the file } BASS_ChannelPlay(chan, FALSE); return TRUE; } // update the spectrum display - the interesting bit :) void CALLBACK UpdateSpectrum(UINT uTimerID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) { HDC dc; int x, y, y1; if (specmode == 3) { // waveform int c; float *buf; BASS_CHANNELINFO ci; memset(specbuf, 0, SPECWIDTH * SPECHEIGHT); BASS_ChannelGetInfo(chan, &ci); // get number of channels buf = alloca(ci.chans * SPECWIDTH * sizeof(float)); // allocate buffer for data BASS_ChannelGetData(chan, buf, (ci.chans * SPECWIDTH * sizeof(float)) | BASS_DATA_FLOAT); // get the sample data (floating-point to avoid 8 & 16 bit processing) for (c = 0; c < ci.chans; c++) { for (x = 0; x < SPECWIDTH; x++) { int v = (1 - buf[x * ci.chans + c]) * SPECHEIGHT / 2; // invert and scale to fit display if (v < 0) v = 0; else if (v >= SPECHEIGHT) v = SPECHEIGHT - 1; if (!x) y = v; do { // draw line from previous sample... if (y < v) y++; else if (y > v) y--; specbuf[y * SPECWIDTH + x] = c & 1 ? 127 : 1; // left=green, right=red (could add more colours to palette for more chans) } while (y != v); } } } else { float fft[1024]; BASS_ChannelGetData(chan, fft, BASS_DATA_FFT2048); // get the FFT data if (!specmode) { // "normal" FFT memset(specbuf, 0, SPECWIDTH * SPECHEIGHT); for (x = 0; x < SPECWIDTH / 2; x++) { #if 1 y = sqrt(fft[x + 1]) * 3 * SPECHEIGHT - 4; // scale it (sqrt to make low values more visible) #else y = fft[x + 1] * 10 * SPECHEIGHT; // scale it (linearly) #endif if (y > SPECHEIGHT) y = SPECHEIGHT; // cap it if (x && (y1 = (y + y1) / 2)) // interpolate from previous to make the display smoother while (--y1 >= 0) specbuf[y1 * SPECWIDTH + x * 2 - 1] = y1 + 1; y1 = y; while (--y >= 0) specbuf[y * SPECWIDTH + x * 2] = y + 1; // draw level } } else if (specmode == 1) { // logarithmic, combine bins int b0 = 0; memset(specbuf, 0, SPECWIDTH * SPECHEIGHT); #define BANDS 28 for (x = 0; x < BANDS; x++) { float peak = 0; int b1 = pow(2, x * 10.0 / (BANDS - 1)); if (b1 <= b0) b1 = b0 + 1; // make sure it uses at least 1 FFT bin if (b1 > 1023) b1 = 1023; for (; b0 < b1; b0++) if (peak < fft[1 + b0]) peak = fft[1 + b0]; y = sqrt(peak) * 3 * SPECHEIGHT - 4; // scale it (sqrt to make low values more visible) if (y > SPECHEIGHT) y = SPECHEIGHT; // cap it while (--y >= 0) memset(specbuf + y * SPECWIDTH + x * (SPECWIDTH / BANDS), y + 1, SPECWIDTH / BANDS - 2); // draw bar } } else { // "3D" for (x = 0; x < SPECHEIGHT; x++) { y = sqrt(fft[x + 1]) * 3 * 127; // scale it (sqrt to make low values more visible) if (y > 127) y = 127; // cap it specbuf[x * SPECWIDTH + specpos] = 128 + y; // plot it } // move marker onto next position specpos = (specpos + 1) % SPECWIDTH; for (x = 0; x < SPECHEIGHT; x++) specbuf[x * SPECWIDTH + specpos] = 255; } } // update the display dc = GetDC(win); BitBlt(dc, 0, 0, SPECWIDTH, SPECHEIGHT, specdc, 0, 0, SRCCOPY); ReleaseDC(win, dc); } // window procedure LRESULT CALLBACK SpectrumWindowProc(HWND h, UINT m, WPARAM w, LPARAM l) { switch (m) { case WM_PAINT: if (GetUpdateRect(h, 0, 0)) { PAINTSTRUCT p; HDC dc; if (!(dc = BeginPaint(h, &p))) return 0; BitBlt(dc, 0, 0, SPECWIDTH, SPECHEIGHT, specdc, 0, 0, SRCCOPY); EndPaint(h, &p); } return 0; case WM_LBUTTONUP: specmode = (specmode + 1) % 4; // change spectrum mode memset(specbuf, 0, SPECWIDTH * SPECHEIGHT); // clear display return 0; case WM_CREATE: win = h; // initialize BASS if (!BASS_Init(-1, 44100, 0, win, NULL)) { Error("Can't initialize device"); return -1; } if (!PlayFile()) { // start a file playing BASS_Free(); return -1; } { // create bitmap to draw spectrum in (8 bit for easy updating) BYTE data[2000] = { 0 }; BITMAPINFOHEADER *bh = (BITMAPINFOHEADER*)data; RGBQUAD *pal = (RGBQUAD*)(data + sizeof(*bh)); int a; bh->biSize = sizeof(*bh); bh->biWidth = SPECWIDTH; bh->biHeight = SPECHEIGHT; // upside down (line 0=bottom) bh->biPlanes = 1; bh->biBitCount = 8; bh->biClrUsed = bh->biClrImportant = 256; // setup palette for (a = 1; a < 128; a++) { pal[a].rgbGreen = 256 - 2 * a; pal[a].rgbRed = 2 * a; } for (a = 0; a < 32; a++) { pal[128 + a].rgbBlue = 8 * a; pal[128 + 32 + a].rgbBlue = 255; pal[128 + 32 + a].rgbRed = 8 * a; pal[128 + 64 + a].rgbRed = 255; pal[128 + 64 + a].rgbBlue = 8 * (31 - a); pal[128 + 64 + a].rgbGreen = 8 * a; pal[128 + 96 + a].rgbRed = 255; pal[128 + 96 + a].rgbGreen = 255; pal[128 + 96 + a].rgbBlue = 8 * a; } // create the bitmap specbmp = CreateDIBSection(0, (BITMAPINFO*)bh, DIB_RGB_COLORS, (void**)&specbuf, NULL, 0); specdc = CreateCompatibleDC(0); SelectObject(specdc, specbmp); } // start update timer (40hz) timer = timeSetEvent(25, 25, (LPTIMECALLBACK)&UpdateSpectrum, 0, TIME_PERIODIC); break; case WM_DESTROY: if (timer) timeKillEvent(timer); BASS_Free(); if (specdc) DeleteDC(specdc); if (specbmp) DeleteObject(specbmp); PostQuitMessage(0); break; } return DefWindowProc(h, m, w, l); } int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wc = { 0 }; MSG msg; // check the correct BASS was loaded if (HIWORD(BASS_GetVersion()) != BASSVERSION) { MessageBox(0, "An incorrect version of BASS.DLL was loaded", 0, MB_ICONERROR); return 0; } // register window class and create the window wc.lpfnWndProc = SpectrumWindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = "BASS-Spectrum"; if (!RegisterClass(&wc) || !CreateWindow("BASS-Spectrum", "BASS spectrum example (click to switch mode)", WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE, 200, 200, SPECWIDTH + 2 * GetSystemMetrics(SM_CXDLGFRAME), SPECHEIGHT + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYDLGFRAME), NULL, NULL, hInstance, NULL)) { Error("Can't create window"); return 0; } ShowWindow(win, SW_SHOWNORMAL); while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }