267 lines
8.4 KiB
C
267 lines
8.4 KiB
C
/*
|
|
BASS custom looping example
|
|
Copyright (c) 2004-2014 Un4seen Developments Ltd.
|
|
*/
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <process.h>
|
|
#include "bass.h"
|
|
|
|
#define WIDTH 600 // display width
|
|
#define HEIGHT 201 // height (odd number for centre line)
|
|
|
|
HWND win = NULL;
|
|
DWORD scanthread = 0;
|
|
BOOL killscan = FALSE;
|
|
|
|
DWORD chan;
|
|
DWORD bpp; // bytes per pixel
|
|
QWORD loop[2] = { 0 }; // loop start & end
|
|
HSYNC lsync; // looping sync
|
|
|
|
HDC wavedc = 0;
|
|
HBITMAP wavebmp = 0;
|
|
BYTE *wavebuf;
|
|
|
|
// 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);
|
|
}
|
|
|
|
void CALLBACK LoopSyncProc(HSYNC handle, DWORD channel, DWORD data, void *user)
|
|
{
|
|
if (!BASS_ChannelSetPosition(channel, loop[0], BASS_POS_BYTE)) // try seeking to loop start
|
|
BASS_ChannelSetPosition(channel, 0, BASS_POS_BYTE); // failed, go to start of file instead
|
|
}
|
|
|
|
void SetLoopStart(QWORD pos)
|
|
{
|
|
loop[0] = pos;
|
|
}
|
|
|
|
void SetLoopEnd(QWORD pos)
|
|
{
|
|
loop[1] = pos;
|
|
BASS_ChannelRemoveSync(chan, lsync); // remove old sync
|
|
lsync = BASS_ChannelSetSync(chan, BASS_SYNC_POS | BASS_SYNC_MIXTIME, loop[1], LoopSyncProc, 0); // set new sync
|
|
}
|
|
|
|
// scan the peaks
|
|
void __cdecl ScanPeaks(void *p)
|
|
{
|
|
DWORD decoder = (DWORD)p;
|
|
DWORD pos = 0;
|
|
float spp = BASS_ChannelBytes2Seconds(decoder, bpp); // seconds per pixel
|
|
while (!killscan) {
|
|
float peak[2];
|
|
if (spp > 1) { // more than 1 second per pixel, break it down...
|
|
float todo = spp;
|
|
peak[1] = peak[0] = 0;
|
|
do {
|
|
float level[2], step = (todo < 1 ? todo : 1);
|
|
BASS_ChannelGetLevelEx(decoder, level, step, BASS_LEVEL_STEREO); // scan peaks
|
|
if (peak[0] < level[0]) peak[0] = level[0];
|
|
if (peak[1] < level[1]) peak[1] = level[1];
|
|
todo -= step;
|
|
} while (todo > 0);
|
|
} else
|
|
BASS_ChannelGetLevelEx(decoder, peak, spp, BASS_LEVEL_STEREO); // scan peaks
|
|
{
|
|
DWORD a;
|
|
for (a = 0; a < peak[0] * (HEIGHT / 2); a++)
|
|
wavebuf[(HEIGHT / 2 - 1 - a) * WIDTH + pos] = 1 + a; // draw left peak
|
|
for (a = 0; a < peak[1] * (HEIGHT / 2); a++)
|
|
wavebuf[(HEIGHT / 2 + 1 + a) * WIDTH + pos] = 1 + a; // draw right peak
|
|
}
|
|
pos++;
|
|
if (pos >= WIDTH) break; // reached end of display
|
|
if (!BASS_ChannelIsActive(decoder)) break; // reached end of channel
|
|
}
|
|
if (!killscan) {
|
|
DWORD size;
|
|
BASS_ChannelSetPosition(decoder, (QWORD)-1, BASS_POS_BYTE | BASS_POS_SCAN); // build seek table (scan to end)
|
|
size = BASS_ChannelGetAttributeEx(decoder, BASS_ATTRIB_SCANINFO, 0, 0); // get seek table size
|
|
if (size) { // got it
|
|
void *info = malloc(size); // allocate a buffer
|
|
BASS_ChannelGetAttributeEx(decoder, BASS_ATTRIB_SCANINFO, info, size); // get the seek table
|
|
BASS_ChannelSetAttributeEx(chan, BASS_ATTRIB_SCANINFO, info, size); // apply it to the playback channel
|
|
free(info);
|
|
}
|
|
}
|
|
BASS_StreamFree(decoder); // free the decoder
|
|
scanthread = 0;
|
|
}
|
|
|
|
// select a file to play, and start scanning 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*.mp3;*.mp2;*.mp1;*.ogg;*.wav;*.aif;*.mo3;*.it;*.xm;*.s3m;*.mtm;*.mod;*.umx\0All files\0*.*\0\0";
|
|
if (!GetOpenFileName(&ofn)) return FALSE;
|
|
|
|
if (!(chan = BASS_StreamCreateFile(FALSE, file, 0, 0, 0))
|
|
&& !(chan = BASS_MusicLoad(FALSE, file, 0, 0, BASS_MUSIC_RAMPS | BASS_MUSIC_POSRESET | BASS_MUSIC_PRESCAN, 1))) {
|
|
Error("Can't play file");
|
|
return FALSE; // Can't load the file
|
|
}
|
|
{
|
|
BYTE data[2000] = { 0 };
|
|
BITMAPINFOHEADER *bh = (BITMAPINFOHEADER*)data;
|
|
RGBQUAD *pal = (RGBQUAD*)(data + sizeof(*bh));
|
|
int a;
|
|
bh->biSize = sizeof(*bh);
|
|
bh->biWidth = WIDTH;
|
|
bh->biHeight = -HEIGHT;
|
|
bh->biPlanes = 1;
|
|
bh->biBitCount = 8;
|
|
bh->biClrUsed = bh->biClrImportant = HEIGHT / 2 + 1;
|
|
// setup palette
|
|
for (a = 1; a <= HEIGHT / 2; a++) {
|
|
pal[a].rgbRed = (255 * a) / (HEIGHT / 2);
|
|
pal[a].rgbGreen = 255 - pal[a].rgbRed;
|
|
}
|
|
// create the bitmap
|
|
wavebmp = CreateDIBSection(0, (BITMAPINFO*)bh, DIB_RGB_COLORS, (void**)&wavebuf, NULL, 0);
|
|
wavedc = CreateCompatibleDC(0);
|
|
SelectObject(wavedc, wavebmp);
|
|
}
|
|
bpp = BASS_ChannelGetLength(chan, BASS_POS_BYTE) / WIDTH; // bytes per pixel
|
|
{
|
|
DWORD bpp1 = BASS_ChannelSeconds2Bytes(chan, 0.001); // minimum 1ms per pixel
|
|
if (bpp < bpp1) bpp = bpp1;
|
|
}
|
|
BASS_ChannelSetSync(chan, BASS_SYNC_END | BASS_SYNC_MIXTIME, 0, LoopSyncProc, 0); // set sync to loop at end
|
|
BASS_ChannelPlay(chan, FALSE); // start playing
|
|
{ // create another channel to scan
|
|
DWORD chan2 = BASS_StreamCreateFile(FALSE, file, 0, 0, BASS_STREAM_DECODE);
|
|
if (!chan2) chan2 = BASS_MusicLoad(FALSE, file, 0, 0, BASS_MUSIC_DECODE, 1);
|
|
scanthread = _beginthread(ScanPeaks, 0, (void*)chan2); // start scanning in a new thread
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void DrawTimeLine(HDC dc, QWORD pos, DWORD col, DWORD y)
|
|
{
|
|
HPEN pen = CreatePen(PS_SOLID, 0, col), oldpen;
|
|
DWORD wpos = pos / bpp;
|
|
DWORD time = BASS_ChannelBytes2Seconds(chan, pos) * 1000; // position in milliseconds
|
|
char text[16];
|
|
sprintf(text, "%u:%02u.%03u", time / 60000, (time / 1000) % 60, time % 1000);
|
|
oldpen = (HPEN)SelectObject(dc, pen);
|
|
MoveToEx(dc, wpos, 0, NULL);
|
|
LineTo(dc, wpos, HEIGHT);
|
|
SetTextColor(dc, col);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextAlign(dc, wpos >= WIDTH / 2 ? TA_RIGHT : TA_LEFT);
|
|
TextOut(dc, wpos, y, text, strlen(text));
|
|
SelectObject(dc, oldpen);
|
|
DeleteObject(pen);
|
|
}
|
|
|
|
// window procedure
|
|
LRESULT CALLBACK SpectrumWindowProc(HWND h, UINT m, WPARAM w, LPARAM l)
|
|
{
|
|
switch (m) {
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_MOUSEMOVE:
|
|
if (w & MK_LBUTTON) SetLoopStart(LOWORD(l) * bpp); // set loop start
|
|
if (w & MK_RBUTTON) SetLoopEnd(LOWORD(l) * bpp); // set loop end
|
|
return 0;
|
|
|
|
case WM_MBUTTONDOWN:
|
|
BASS_ChannelSetPosition(chan, LOWORD(l) * bpp, BASS_POS_BYTE); // set current pos
|
|
return 0;
|
|
|
|
case WM_TIMER:
|
|
InvalidateRect(h, 0, 0); // refresh window
|
|
return 0;
|
|
|
|
case WM_PAINT:
|
|
if (GetUpdateRect(h, 0, 0)) {
|
|
PAINTSTRUCT p;
|
|
HDC dc;
|
|
if (!(dc = BeginPaint(h, &p))) return 0;
|
|
BitBlt(dc, 0, 0, WIDTH, HEIGHT, wavedc, 0, 0, SRCCOPY); // draw peak waveform
|
|
DrawTimeLine(dc, loop[0], 0xffff00, 12); // loop start
|
|
DrawTimeLine(dc, loop[1], 0x00ffff, 24); // loop end
|
|
DrawTimeLine(dc, BASS_ChannelGetPosition(chan, BASS_POS_BYTE), 0xffffff, 0); // current pos
|
|
EndPaint(h, &p);
|
|
}
|
|
return 0;
|
|
|
|
case WM_CREATE:
|
|
win = h;
|
|
// initialize output
|
|
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;
|
|
}
|
|
SetTimer(h, 0, 100, 0); // set update timer (10hz)
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
KillTimer(h, 0);
|
|
if (scanthread) { // still scanning
|
|
killscan = TRUE;
|
|
WaitForSingleObject((HANDLE)scanthread, 1000); // wait for the thread
|
|
}
|
|
BASS_Free();
|
|
if (wavedc) DeleteDC(wavedc);
|
|
if (wavebmp) DeleteObject(wavebmp);
|
|
PostQuitMessage(0);
|
|
break;
|
|
}
|
|
return DefWindowProc(h, m, w, l);
|
|
}
|
|
|
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
|
{
|
|
WNDCLASS wc;
|
|
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
|
|
memset(&wc, 0, sizeof(wc));
|
|
wc.lpfnWndProc = SpectrumWindowProc;
|
|
wc.hInstance = hInstance;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.lpszClassName = "BASS-CustLoop";
|
|
if (!RegisterClass(&wc) || !CreateWindow("BASS-CustLoop",
|
|
"BASS custom looping example (left-click to set loop start, right-click to set end)",
|
|
WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE, 100, 100,
|
|
WIDTH + 2 * GetSystemMetrics(SM_CXDLGFRAME),
|
|
HEIGHT + 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;
|
|
}
|