{ BASS multiple output example Copyright (c) 2001-2008 Un4seen Developments Ltd. C++ to Delphi with use API adapted by Evgeny Melnikov Required Delphi 7 or above } program Multi; uses Windows, Messages, CommCtrl, CommDlg, Bass in '..\Bass.pas'; const szFiles = 'streamable files'#0'*.mp3;*.mp2;*.mp1;*.ogg;*.wav;*.aif'#0 + 'All files'#0'*.*'#0#0; szOpen = 'Select a file to play'; var win : HWND; ofn : OPENFILENAME; FileName : array[0..MAX_PATH - 1] of Char; outdev : array[0..1] of DWORD; // output devices latency : array[0..1] of DWORD; // latencies chan : array[0..1] of HSTREAM; // the streams {$R 'multi.res' 'multi.rc'} //------------------ 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; //--------------------------------------------------------- // display error messages procedure Error(const es : String); begin MessageBox(win, PChar(Format('%s' + #13#10 + '(error code: %d)', [es, BASS_ErrorGetCode])), 'Error', MB_OK or MB_ICONERROR); end; //--------------------------------------------------------- // Cloning DSP function procedure CloneDSP(Handle : HDSP; channel : DWORD; buffer : Pointer; Length : DWORD; user : Pointer); stdcall; begin BASS_StreamPutData(HSTREAM(user), buffer, Length); // user = clone end; //--------------------------------------------------------- procedure InitApp(Wnd : HWND); var info : BASS_INFO; di : BASS_DEVICEINFO; St : AnsiString; begin win := Wnd; // get a playable file FillChar(ofn, SizeOf(OPENFILENAME), 0); FillChar(FileName, SizeOf(FileName), 0); ofn.lStructSize := SizeOf(OPENFILENAME); ofn.hwndOwner := Wnd; ofn.hInstance := hInstance; ofn.lpstrTitle := szOpen; ofn.lpstrFile := FileName; ofn.lpstrFilter := szFiles; ofn.nMaxFile := MAX_PATH; ofn.Flags := OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_EXPLORER; // initialize output devices if not BASS_Init(outdev[0], 44100, BASS_DEVICE_LATENCY, Wnd, NIL) then begin Error('Can''t initialize device 1'); DestroyWindow(Wnd); end; BASS_GetInfo(info); latency[0] := info.latency; if not BASS_Init(outdev[1], 44100, BASS_DEVICE_LATENCY, Wnd, NIL) then begin Error('Can''t initialize device 2'); DestroyWindow(Wnd); end; BASS_GetInfo(info); latency[1] := info.latency; BASS_GetDeviceInfo(outdev[0], di); St := di.name; SendDlgItemMessage(Wnd, 20, WM_SETTEXT, 0, Integer(PChar(String(St)))); BASS_GetDeviceInfo(outdev[1], di); St := di.name; SendDlgItemMessage(Wnd, 21, WM_SETTEXT, 0, Integer(PChar(String(St)))); end; //--------------------------------------------------------- function DialogProc(hWndDlg : HWND; Msg, wParam, lParam : Longint) : Integer; stdcall; type TBuf = array of Byte; var info : BASS_CHANNELINFO; devn, devnx : Integer; c, d : DWORD; temp : HSTREAM; St : String; Buf : TBuf; Buf1 : array[0..MAX_PATH - 1] of AnsiChar; Buf2 : array[0..MAX_PATH - 1] of AnsiChar; begin Result := 0; case Msg of WM_INITDIALOG : begin InitApp(hWndDlg); Result := 1; Exit; end; WM_COMMAND : case LOWORD(wParam) of 10, 11 : // open a file to play on device #1 or device #2 begin devn := LOWORD(wParam) - 10; if GetOpenFileName(ofn) then begin St := FileName; BASS_StreamFree(chan[devn]); // free old stream BASS_SetDevice(outdev[devn]); // set the device to create stream on chan[devn] := BASS_StreamCreateFile(False, PChar(St), 0, 0, BASS_SAMPLE_LOOP {$IFDEF UNICODE} or BASS_UNICODE {$ENDIF}); if chan[devn] = 0 then begin SendDlgItemMessage(hWndDlg, 10 + devn, WM_SETTEXT, 0, Integer(PChar('click here to open a file...'))); Error('Can''t play the file'); Exit; end; BASS_ChannelPlay(chan[devn], False); // play new stream SendDlgItemMessage(hWndDlg, 10 + devn, WM_SETTEXT, 0, Integer(PChar(St))); end; end; 15, 16 : // clone on device #1 or device #2 begin devn := LOWORD(wParam) - 15; devnx := devn xor 1; if not BASS_ChannelGetInfo(chan[devnx], info) then begin Error('Nothing to clone'); Exit; end; BASS_StreamFree(chan[devn]); // free old stream BASS_SetDevice(outdev[devn]); // set the device to create stream on chan[devn] := BASS_StreamCreate(info.freq, info.chans, info.flags, STREAMPROC_PUSH, NIL); if chan[devn] = 0 then begin // create a "push" stream SendDlgItemMessage(hWndDlg, 10 + devn, WM_SETTEXT, 0, Integer(PChar('click here to open a file...'))); Error('Can''t create clone'); Exit; end; BASS_ChannelLock(chan[devnx], True); // lock source stream to synchonise buffer contents BASS_ChannelSetDSP(chan[devnx], @CloneDSP, Pointer(chan[devn]), 0); // set DSP to feed data to clone // copy buffered data to clone d := BASS_ChannelSeconds2Bytes(chan[devn], latency[devn] / 1000); // playback delay c := BASS_ChannelGetData(chan[devnx], NIL, BASS_DATA_AVAILABLE); SetLength(Buf, c); c := BASS_ChannelGetData(chan[devnx], buf, c); if c > d then BASS_StreamPutData(chan[devn], Pointer(DWORD(buf) + d), c - d); BASS_ChannelLock(chan[devnx], False); // unlock source stream BASS_ChannelPlay(chan[devn], False); // play clone SendDlgItemMessage(hWndDlg, 10 + devn, WM_SETTEXT, 0, Integer(PChar('clone'))); end; 30 : // swap channel devices begin // swap handles temp := chan[0]; chan[0] := chan[1]; chan[1] := temp; // swap text SendDlgItemMessage(hWndDlg, 10, WM_GETTEXT, SizeOf(Buf1), Integer(@Buf1[0])); SendDlgItemMessage(hWndDlg, 11, WM_GETTEXT, SizeOf(Buf2), Integer(@Buf2[0])); SendDlgItemMessage(hWndDlg, 10, WM_SETTEXT, 0, Integer(@Buf2[0])); SendDlgItemMessage(hWndDlg, 11, WM_SETTEXT, 0, Integer(@Buf1[0])); // update the channel devices BASS_ChannelSetDevice(chan[0], outdev[0]); BASS_ChannelSetDevice(chan[1], outdev[1]); end; IDCANCEL : DestroyWindow(hWndDlg); end; WM_CLOSE : DestroyWindow(hWndDlg); WM_DESTROY : begin // release both devices BASS_SetDevice(outdev[0]); BASS_Free; BASS_SetDevice(outdev[1]); BASS_Free; end; end; end; //--------------------------------------------------------- // Simple device selector dialog stuff begins here function DeviceDialogProc(hWndDlg : HWND; Msg, wParam, lParam : Longint) : Integer; stdcall; var i, idx, Device : Integer; di : BASS_DEVICEINFO; St : String; St1 : AnsiString; begin Result := 0; case Msg of WM_INITDIALOG : begin St := Format('Select output device #%d', [lParam]); SetWindowText(hWndDlg, PChar(St)); // device 1 = 1st real device i := 1; while BASS_GetDeviceInfo(I, di) do begin if di.flags and BASS_DEVICE_ENABLED = BASS_DEVICE_ENABLED then // enabled, so add it... begin St1 := di.name; idx := SendDlgItemMessage(hWndDlg, 10, LB_ADDSTRING, 0, Integer(PChar(String(St1)))); SendDlgItemMessage(hWndDlg, 10, LB_SETITEMDATA, idx, i); // store device # end; inc(i); end; SendDlgItemMessage(hWndDlg, 10, LB_SETCURSEL, 0, 0); Result := 1; Exit; end; WM_COMMAND : case LOWORD(wParam) of 10 : if HIWORD(wParam) <> LBN_DBLCLK then Exit; IDOK : begin Device := SendDlgItemMessage(hWndDlg, 10, LB_GETCURSEL, 0, 0); Device := SendDlgItemMessage(hWndDlg, 10, LB_GETITEMDATA, Device, 0); // get device # EndDialog(hWndDlg, Device); end; end; end; end; // Device selector stuff ends here //--------------------------------------------------------- begin // check the correct BASS was loaded if HIWORD(BASS_GetVersion) <> BASSVERSION then begin MessageBox(0, 'An incorrect version of BASS.DLL was loaded', 'Error', MB_OK or MB_ICONERROR); Halt(BASS_ERROR_VERSION); end; // Let the user choose the output devices outdev[0] := DialogBoxParam(hInstance, MakeIntResource(2000), 0, @DeviceDialogproc, 1); outdev[1] := DialogBoxParam(hInstance, MakeIntResource(2000), 0, @DeviceDialogproc, 2); // display the window DialogBox(hInstance, MakeIntResource(1000), 0, @DialogProc); end.