336 lines
9.1 KiB
C
336 lines
9.1 KiB
C
|
/*
|
||
|
BASS recording example
|
||
|
Copyright (c) 2002-2019 Un4seen Developments Ltd.
|
||
|
*/
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <commctrl.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include "bass.h"
|
||
|
|
||
|
HWND win = NULL;
|
||
|
|
||
|
#define FREQ 44100
|
||
|
#define CHANS 2
|
||
|
#define BUFSTEP 200000 // memory allocation unit
|
||
|
|
||
|
int input; // current input source
|
||
|
BYTE *recbuf = NULL; // recording buffer
|
||
|
DWORD reclen; // recording length
|
||
|
HRECORD rchan = 0; // recording channel
|
||
|
HSTREAM chan = 0; // playback channel
|
||
|
|
||
|
void StopRecording();
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
// messaging macros
|
||
|
#define MESS(id,m,w,l) SendDlgItemMessage(win,id,m,(WPARAM)(w),(LPARAM)(l))
|
||
|
#define DLGITEM(id) GetDlgItem(win,id)
|
||
|
|
||
|
// buffer the recorded data
|
||
|
BOOL CALLBACK RecordingCallback(HRECORD handle, const void *buffer, DWORD length, void *user)
|
||
|
{
|
||
|
// increase buffer size if needed
|
||
|
if ((reclen % BUFSTEP) + length >= BUFSTEP) {
|
||
|
recbuf = realloc(recbuf, ((reclen + length) / BUFSTEP + 1) * BUFSTEP);
|
||
|
if (!recbuf) {
|
||
|
rchan = 0;
|
||
|
Error("Out of memory!");
|
||
|
MESS(10, WM_SETTEXT, 0, "Record");
|
||
|
return FALSE; // stop recording
|
||
|
}
|
||
|
}
|
||
|
// buffer the data
|
||
|
memcpy(recbuf + reclen, buffer, length);
|
||
|
reclen += length;
|
||
|
return TRUE; // continue recording
|
||
|
}
|
||
|
|
||
|
void StartRecording()
|
||
|
{
|
||
|
WAVEFORMATEX *wf;
|
||
|
if (recbuf) { // free old recording
|
||
|
BASS_StreamFree(chan);
|
||
|
chan = 0;
|
||
|
free(recbuf);
|
||
|
recbuf = NULL;
|
||
|
EnableWindow(DLGITEM(11), FALSE);
|
||
|
EnableWindow(DLGITEM(12), FALSE);
|
||
|
}
|
||
|
// allocate initial buffer and make space for WAVE header
|
||
|
recbuf = malloc(BUFSTEP);
|
||
|
reclen = 44;
|
||
|
// fill the WAVE header
|
||
|
memcpy(recbuf, "RIFF\0\0\0\0WAVEfmt \20\0\0\0", 20);
|
||
|
memcpy(recbuf + 36, "data\0\0\0\0", 8);
|
||
|
wf = (WAVEFORMATEX*)(recbuf + 20);
|
||
|
wf->wFormatTag = 1;
|
||
|
wf->nChannels = CHANS;
|
||
|
wf->wBitsPerSample = 16;
|
||
|
wf->nSamplesPerSec = FREQ;
|
||
|
wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample / 8;
|
||
|
wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
|
||
|
// start recording
|
||
|
rchan = BASS_RecordStart(FREQ, CHANS, 0, RecordingCallback, 0);
|
||
|
if (!rchan) {
|
||
|
Error("Can't start recording");
|
||
|
free(recbuf);
|
||
|
recbuf = 0;
|
||
|
return;
|
||
|
}
|
||
|
MESS(10, WM_SETTEXT, 0, "Stop");
|
||
|
}
|
||
|
|
||
|
void StopRecording()
|
||
|
{
|
||
|
BASS_ChannelStop(rchan);
|
||
|
rchan = 0;
|
||
|
MESS(10, WM_SETTEXT, 0, "Record");
|
||
|
// complete the WAVE header
|
||
|
*(DWORD*)(recbuf + 4) = reclen - 8;
|
||
|
*(DWORD*)(recbuf + 40) = reclen - 44;
|
||
|
// enable "save" button
|
||
|
EnableWindow(DLGITEM(12), TRUE);
|
||
|
// create a stream from the recording
|
||
|
if (chan = BASS_StreamCreateFile(TRUE, recbuf, 0, reclen, 0))
|
||
|
EnableWindow(DLGITEM(11), TRUE); // enable "play" button
|
||
|
}
|
||
|
|
||
|
// write the recorded data to disk
|
||
|
void WriteToDisk()
|
||
|
{
|
||
|
FILE *fp;
|
||
|
char file[MAX_PATH] = "";
|
||
|
OPENFILENAME ofn = { 0 };
|
||
|
ofn.lStructSize = sizeof(ofn);
|
||
|
ofn.hwndOwner = win;
|
||
|
ofn.nMaxFile = MAX_PATH;
|
||
|
ofn.lpstrFile = file;
|
||
|
ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER;
|
||
|
ofn.lpstrFilter = "WAV files\0*.wav\0All files\0*.*\0\0";
|
||
|
ofn.lpstrDefExt = "wav";
|
||
|
if (!GetSaveFileName(&ofn)) return;
|
||
|
if (!(fp = fopen(file, "wb"))) {
|
||
|
Error("Can't create the file");
|
||
|
return;
|
||
|
}
|
||
|
fwrite(recbuf, reclen, 1, fp);
|
||
|
fclose(fp);
|
||
|
}
|
||
|
|
||
|
void UpdateInputInfo()
|
||
|
{
|
||
|
char *type;
|
||
|
float level;
|
||
|
int it = BASS_RecordGetInput(input, &level); // get info on the input
|
||
|
if (it == -1 || level < 0) { // failed to get level
|
||
|
it = BASS_RecordGetInput(-1, &level); // try master input instead
|
||
|
if (it == -1 || level < 0) { // that failed too
|
||
|
level = 1; // just display 100%
|
||
|
EnableWindow(DLGITEM(14), FALSE);
|
||
|
} else
|
||
|
EnableWindow(DLGITEM(14), TRUE);
|
||
|
} else
|
||
|
EnableWindow(DLGITEM(14), TRUE);
|
||
|
MESS(14, TBM_SETPOS, TRUE, level * 100); // set the level slider
|
||
|
switch (it & BASS_INPUT_TYPE_MASK) {
|
||
|
case BASS_INPUT_TYPE_DIGITAL:
|
||
|
type = "digital";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_LINE:
|
||
|
type = "line-in";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_MIC:
|
||
|
type = "microphone";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_SYNTH:
|
||
|
type = "midi synth";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_CD:
|
||
|
type = "analog cd";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_PHONE:
|
||
|
type = "telephone";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_SPEAKER:
|
||
|
type = "pc speaker";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_WAVE:
|
||
|
type = "wave/pcm";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_AUX:
|
||
|
type = "aux";
|
||
|
break;
|
||
|
case BASS_INPUT_TYPE_ANALOG:
|
||
|
type = "analog";
|
||
|
break;
|
||
|
default:
|
||
|
type = "undefined";
|
||
|
{
|
||
|
// check if it's a loopback device
|
||
|
BASS_DEVICEINFO info;
|
||
|
BASS_RecordGetDeviceInfo(BASS_RecordGetDevice(), &info);
|
||
|
if (info.flags & BASS_DEVICE_LOOPBACK) type = "loopback";
|
||
|
}
|
||
|
}
|
||
|
MESS(15, WM_SETTEXT, 0, type); // display the type
|
||
|
}
|
||
|
|
||
|
BOOL InitDevice(int device)
|
||
|
{
|
||
|
BASS_RecordFree(); // free current device (and recording channel) if there is one
|
||
|
// initalize new device
|
||
|
if (!BASS_RecordInit(device)) {
|
||
|
Error("Can't initialize recording device");
|
||
|
return FALSE;
|
||
|
}
|
||
|
{ // get list of inputs
|
||
|
int c;
|
||
|
const char *i;
|
||
|
MESS(13, CB_RESETCONTENT, 0, 0);
|
||
|
input = 0;
|
||
|
for (c = 0; i = BASS_RecordGetInputName(c); c++) {
|
||
|
MESS(13, CB_ADDSTRING, 0, i);
|
||
|
if (!(BASS_RecordGetInput(c, NULL) & BASS_INPUT_OFF)) { // this one is currently "on"
|
||
|
input = c;
|
||
|
MESS(13, CB_SETCURSEL, input, 0);
|
||
|
}
|
||
|
}
|
||
|
UpdateInputInfo();
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
INT_PTR CALLBACK dialogproc(HWND h, UINT m, WPARAM w, LPARAM l)
|
||
|
{
|
||
|
switch (m) {
|
||
|
case WM_TIMER:
|
||
|
{ // update the recording/playback counter
|
||
|
char text[30] = "";
|
||
|
if (rchan) { // recording
|
||
|
if (rchan != 1 && !BASS_ChannelIsActive(rchan)) { // the recording has stopped, eg. unplugged device
|
||
|
StopRecording();
|
||
|
Error("The recording stopped");
|
||
|
break;
|
||
|
}
|
||
|
sprintf(text, "%d", reclen - 44);
|
||
|
} else if (chan) {
|
||
|
if (BASS_ChannelIsActive(chan)) // playing
|
||
|
sprintf(text, "%I64d / %I64d", BASS_ChannelGetPosition(chan, BASS_POS_BYTE), BASS_ChannelGetLength(chan, BASS_POS_BYTE));
|
||
|
else
|
||
|
sprintf(text, "%I64d", BASS_ChannelGetLength(chan, BASS_POS_BYTE));
|
||
|
}
|
||
|
MESS(20, WM_SETTEXT, 0, text);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_COMMAND:
|
||
|
switch (LOWORD(w)) {
|
||
|
case IDCANCEL:
|
||
|
EndDialog(h, 0);
|
||
|
break;
|
||
|
case 10:
|
||
|
if (!rchan)
|
||
|
StartRecording();
|
||
|
else
|
||
|
StopRecording();
|
||
|
break;
|
||
|
case 11:
|
||
|
BASS_ChannelPlay(chan, TRUE); // play the recorded data
|
||
|
break;
|
||
|
case 12:
|
||
|
WriteToDisk();
|
||
|
break;
|
||
|
case 13:
|
||
|
if (HIWORD(w) == CBN_SELCHANGE) { // input selection changed
|
||
|
int i;
|
||
|
input = MESS(13, CB_GETCURSEL, 0, 0); // get the selection
|
||
|
// enable the selected input
|
||
|
for (i = 0; BASS_RecordSetInput(i, BASS_INPUT_OFF, -1); i++); // 1st disable all inputs, then...
|
||
|
BASS_RecordSetInput(input, BASS_INPUT_ON, -1); // enable the selected
|
||
|
UpdateInputInfo();
|
||
|
}
|
||
|
break;
|
||
|
case 16:
|
||
|
if (HIWORD(w) == CBN_SELCHANGE) { // device selection changed
|
||
|
int i = MESS(16, CB_GETCURSEL, 0, 0); // get the selection
|
||
|
if (rchan) rchan = 1; // special handle (real handles always have highest bit set) to prevent timer ending the recording
|
||
|
// initialize the selected device
|
||
|
if (InitDevice(i)) {
|
||
|
if (rchan) { // continue recording on the new device...
|
||
|
HRECORD newrchan = BASS_RecordStart(FREQ, CHANS, 0, RecordingCallback, NULL);
|
||
|
if (!newrchan)
|
||
|
Error("Couldn't start recording");
|
||
|
else
|
||
|
rchan = newrchan;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_HSCROLL:
|
||
|
if (l) { // set input source level
|
||
|
float level = SendMessage((HWND)l, TBM_GETPOS, 0, 0) / 100.f;
|
||
|
if (!BASS_RecordSetInput(input, 0, level)) // failed to set input level
|
||
|
BASS_RecordSetInput(-1, 0, level); // try master level instead
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_INITDIALOG:
|
||
|
win = h;
|
||
|
MESS(14, TBM_SETRANGE, FALSE, MAKELONG(0, 100));
|
||
|
{ // get list of recording devices
|
||
|
int c, def;
|
||
|
BASS_DEVICEINFO di;
|
||
|
for (c = 0; BASS_RecordGetDeviceInfo(c, &di); c++) {
|
||
|
MESS(16, CB_ADDSTRING, 0, di.name);
|
||
|
if (di.flags & BASS_DEVICE_DEFAULT) { // got the default device
|
||
|
MESS(16, CB_SETCURSEL, c, 0);
|
||
|
def = c;
|
||
|
}
|
||
|
}
|
||
|
InitDevice(def); // initialize default recording device
|
||
|
}
|
||
|
// initialize default output device
|
||
|
if (!BASS_Init(-1, FREQ, 0, win, NULL))
|
||
|
Error("Can't initialize output device");
|
||
|
SetTimer(h, 0, 200, 0); // timer to update the position display
|
||
|
return 1;
|
||
|
|
||
|
case WM_DESTROY:
|
||
|
// release all BASS stuff
|
||
|
BASS_RecordFree();
|
||
|
BASS_Free();
|
||
|
break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||
|
{
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
{ // enable trackbar support (for the level control)
|
||
|
INITCOMMONCONTROLSEX cc = { sizeof(cc),ICC_BAR_CLASSES };
|
||
|
InitCommonControlsEx(&cc);
|
||
|
}
|
||
|
|
||
|
DialogBox(hInstance, MAKEINTRESOURCE(1000), NULL, dialogproc);
|
||
|
|
||
|
return 0;
|
||
|
}
|