Using the Low Level Windows API
FINAL VERSION
In this tutorial we will build a sound recorder program with wave visualisation and hopefullly end with
something like this :-

I have used Microsoft Visual Studio 2010 for this build it will not build under MinGW/gcc
without a few alterations to the code although I have built this in CodeBlocks/MinGW.
If required I will release a gcc version of this program.
First of all we start with declaring a few headers and constants.
#include <CommDlg.h> #include <MMSystem.h> #include <fstream> #include <cstdlib> #include "resource.h" using namespace std; //Globals for sound wave visualistion int number, length, byte_samp, byte_sec, bit_samp; static int sampleRate = 11025; const int NUMPTS = 11025 * 10; bool mono = TRUE; bool PLAY = FALSE; errno_t wavfile; char * filename; int s_rate = 11025; double limit = 10000.0; FILE * stream; /* Declare Windows procedure */ LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM); /* Declare procedures */ int readSample(int number,bool leftchannel); void SaveWavFile(char *FileName, PWAVEHDR WaveHeader); void Wav(char *c, HWND hWnd); /* Make the class name into a global variable */ char szAppName[ ] = "Recorder";
next we define WinMain
// **********
// Windows Main Function.
// - Here starts our program
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */
/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szAppName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = "APP_MENU"; /*menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szAppName, /* Classname */
szAppName, /* Title Text */
WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
430, /* The programs width */
300, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /*use class menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);
/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
Next we have our callback function, this function processes messages sent to the window.
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND RecButton;
static HWND PlyButton;
static HWND StpButton;
static HMENU hMenu;
static HPEN hPen;
static BOOL bRecording, bPlaying,bEnding, bTerminating ;
static DWORD dwDataLength, dwRepetitions = 1 ;
static HWAVEIN hWaveIn ;
static HWAVEOUT hWaveOut ;
static PBYTE pBuffer1, pBuffer2, pSaveBuffer, pNewBuffer ;
static PWAVEHDR pWaveHdr1, pWaveHdr2 ;
static TCHAR szOpenError[] = TEXT ("Error opening waveform audio!");
static TCHAR szMemError [] = TEXT ("Error allocating memory!") ;
static WAVEFORMATEX waveform ;
hMenu = GetMenu (hwnd);
HDC hDC;
POINT pt [NUM];
BOOL fSuccess = FALSE;
switch (message) /* handle the messages */
{
case WM_CREATE:
RecButton = CreateWindow ( TEXT ("button"),"RECORD",WS_VISIBLE|WS_CHILD|ES_LEFT|1,7,175,100,25,hwnd,(HMENU) IDC_RECORD,((LPCREATESTRUCT) lParam) -> hInstance,NULL);
PlyButton = CreateWindow ( TEXT ("button"),"PLAY",WS_VISIBLE|WS_CHILD|ES_LEFT|1,157,175,100,25,hwnd,(HMENU) IDC_PLAY,((LPCREATESTRUCT) lParam) -> hInstance,NULL);
StpButton = CreateWindow ( TEXT ("button"),"STOP",WS_VISIBLE|WS_CHILD|ES_LEFT|1,314,175,100,25,hwnd,(HMENU) IDC_STOP,((LPCREATESTRUCT) lParam) -> hInstance,NULL);
EnableWindow (PlyButton, FALSE) ;
EnableWindow (StpButton, FALSE) ;
pWaveHdr1 = reinterpret_cast <PWAVEHDR> ( malloc (sizeof (WAVEHDR)) );
pWaveHdr2 = reinterpret_cast <PWAVEHDR> ( malloc (sizeof (WAVEHDR)) ) ;
// Allocate memory for save buffer
pSaveBuffer = reinterpret_cast <PBYTE> ( malloc (1) ) ;
return 0;
This creates a menu and a few buttons the play button is set to disabled or grayed out
and so is the stop button.
we also allocate memory for the save buffer and the Wave Headers pWaveHdr1 and pWaveHdr2 at this point.
next up we look at how we paint our window.
case WM_PAINT:
PAINTSTRUCT ps;
hDC = BeginPaint(hwnd, &ps);
if (hDC)
{
RECT rc;
rc.top = 35;
rc.left = 0;
rc.bottom = 145;
rc.right = 425;
FillRect(hDC,&rc,(HBRUSH)(COLOR_WINDOW+2));
if (PLAY==TRUE)
{
FillRect(hDC,&rc,(HBRUSH)(COLOR_WINDOW+2));
hPen = CreatePen(PS_SOLID,1,RGB(0,200,0));
SelectObject(hDC, hPen);
SetMapMode(hDC, MM_ISOTROPIC);
SetWindowExtEx(hDC, 400, 300, NULL);
SetViewportExtEx(hDC,200, 180, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);
int sample=0;
int i=1;
int num = 60000;
sample= readSample(i, TRUE);
// scale the sample
pt[i].x =i/20;
pt[i].y = (int) ((sample)*1.5);
MoveToEx (hDC,pt[i].x,pt[i].y,NULL);
while (i<num && sample!=(int)0xefffffff)
{
// scale the sample
pt[i].x = i/20;
pt[i].y = (int) ((sample)*1.5);
LineTo(hDC, pt[i].x,pt[i].y);
i++;
sample= readSample(i, TRUE);
}
}
DeleteObject(hPen);
DeleteDC(hDC);
EndPaint(hwnd, &ps);
}
return 0;
First we Use a FillRect command to create a black box
which will hold our wave visualisation.
If the variable PLAY is true we draw the wave visualisation graph,
with CreatePen we make a lime green pen to draw with.
Then we set the Map Mode to something useful.
Then we start to draw our sample which is only a few seconds long
through our readSample function which we will discuss next.
//start of wave visualisation process
int readSample(int number,bool leftchannel)
{
/*
Reads sample number, returns it as an int, if
this.mono==false we look at the leftchannel bool
to determine which to return.
number is in the range [0,length/byte_samp]
returns 0xefffffff on failure
*/
if (number>=0 && number<length/byte_samp)
{
// go to beginning of the file
rewind(stream);
// we start reading at sample_number * sample_size + header length
int offset = number * 1 + 44;
// unless this is a stereo file and the rightchannel is requested.
if (!mono && !leftchannel)
{
offset += byte_samp/2;
}
// read this many bytes;
int amount;
amount=byte_samp;
fseek(stream,offset,SEEK_CUR);
short sample = 0;
fread((void *)&sample,1,amount,stream);
return sample;
}
else
{
// return 0xefffffff if failed
return (int)0xefffffff;
}
}
This function returns an int through the variable sample
for success or if failure returns 0xefffffff.
We use rewind to position the file pointer at the beginning of 'stream'.
Then we begin reading at the sample number times (1+44)
this is because the data in the canonical wave format is held there at this point.
The Canonical Wave Format.

1. The characters "RIFF" indiciate the RIFF header. RIFF stands for Resource Interchange File Format.
2. The sizeof the file being read or recorded.
3. The "WAVE" characters indicate its a .wav file.
4. The "fmt " characters specify that this is the section of the file describing the format specifically.
5. The size of the WAVEFORMATEX data to follow
6. # WAVEFORMATEX (shown below)
a.wFormatTag, only PCM data is supported in this sample
b. nChannels, Number of channels in (1 for mono, 2 for stereo)
c. nSamplesPerSec, Sample rate of the waveform in samples per second
d. nAvgBytesPerSec, Average bytes per second which can be used to determine the
time-wise length of the audio
e. nBlockAlign, Specifies how each audio block must be aligned in bytes
f. wBitsPerSample, How many bits represent a single sample (typically 8 or 16)
7. The "data" characters specify that the audio data is next in the file.
8. The length of the data in bytes.
9. PCM DATA. The actual sound data.
readSample references values in Wav function.
which we will discuss next.
// Read the temporary wav file
void Wav(char *c, HWND hWnd)
{
filename = new char[strlen(c)+1];
strcpy_s(filename,strlen(c)+1,c);
// open filepointer readonly
wavfile = fopen_s(&stream,filename,"r");
if (stream==NULL)
{
MessageBox(hWnd, "Could not open " + (char)filename,"Error", MB_OK);
}
else
{
// declare a char buff to store some values in
char *buff = new char[5];
buff[4]='\0';
// read the first 4 bytes
fread((void *)buff,1,4,stream);
// the first four bytes should be 'RIFF'
if (strcmp((char *)buff,"RIFF")==0)
{
// read byte 8,9,10 and 11
fseek(stream,4,SEEK_CUR);
fread((void *)buff,1,4,stream);
// this should read "WAVE"
if (strcmp((char *)buff,"WAVE")==0)
{
// read byte 12,13,14,15
fread((void *)buff,1,4,stream);
// this should read "fmt "
if (strcmp((char *)buff,"fmt ")==0)
{
fseek(stream,20,SEEK_CUR);
// final one read byte 36,37,38,39
fread((void *)buff,1,4,stream);
if (strcmp((char *)buff,"data")==0)
{
// Now we know it is a wav file, rewind the stream
rewind(stream);
// now is it mono or stereo ?
fseek(stream,22,SEEK_CUR);
fread((void *)buff,1,2,stream);
if (buff[0]==0x02)
{
mono=false;
}
else
{
mono=true;
}
// read the sample rate
fread((void *)&s_rate,1,4,stream);
fread((void *)&byte_sec,1,4,stream);
byte_samp=0;
fread((void *)&byte_samp,1,2,stream);
bit_samp=0;
fread((void *)&bit_samp,1,2,stream);
fseek(stream,4,SEEK_CUR);
fread((void *)&length,1,4,stream);
}
}
}
}
delete buff;
}
}
hWnd and c are passed to the wav function
filename then holds the value of c.
a file is then opened.If this is NULL a Messagebox is displayed informing the user of the error.
if the file is not null.
it is checked for being a .wav file.
The things checked are :-
1. The "RIFF" characters
2. The "WAVE" characters
3. "fmt " characters
4. "data" characters
These have been discussed in the canonical wave format section.
next the sample rate, bytes per second,the byte sample, the bit sample and the length of the sample is read.
The byte sample and the length of the sample are used in the readSample function.
Next up is this message handler which is activated when a waveform-audio input device is opened.
case MM_WIM_OPEN:
// Shrink down the save buffer
pSaveBuffer = reinterpret_cast <PBYTE>(realloc (pSaveBuffer, 1)) ;
// Add the buffers
waveInAddBuffer (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
waveInAddBuffer (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
// Begin sampling
bRecording = TRUE ;
bEnding = FALSE ;
dwDataLength = 0 ;
waveInStart (hWaveIn) ;
return TRUE ;
first we shrink down the savebuffer to one byte.
then we add the Wave buffers pWaveHdr1 and pWaveHdr2 to hWaveIn which is the handle to the waveform-audio input device.
Then we start the sampling process
the flag bRecording is set to true.
bEnding is set to false
waveInStart is called with hWaveIn which is the handle to the waveform-audio input device,
this starts the recording process.
Next This message handleer is triggered when waveform-audio data is present in the input buffer and the buffer is being returned to the application.
case MM_WIM_DATA:
// Reallocate save buffer memory
pNewBuffer = reinterpret_cast <PBYTE> (realloc (pSaveBuffer, dwDataLength +
((PWAVEHDR) lParam)->dwBytesRecorded)) ;
if (pNewBuffer == NULL)
{
waveInClose (hWaveIn) ;
MessageBox (hwnd, szMemError, szAppName,
MB_ICONEXCLAMATION | MB_OK) ;
return TRUE ;
}
pSaveBuffer = pNewBuffer ;
CopyMemory (pSaveBuffer + dwDataLength, ((PWAVEHDR) lParam)->lpData,
((PWAVEHDR) lParam)->dwBytesRecorded) ;
dwDataLength += ((PWAVEHDR) lParam)->dwBytesRecorded ;
if (bEnding)
{
waveInClose (hWaveIn) ;
return TRUE ;
}
// Send out a new buffer
waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ;
return TRUE ;
basically reallocates memory for the save buffer and sends out a new buffer triggered by waveInReset function.
case MM_WIM_CLOSE:
// Free the buffer memory
waveInUnprepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
waveInUnprepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
free (pBuffer1) ;
free (pBuffer2) ;
// Enable and disable buttons
if (dwDataLength > 0)
{
EnableWindow (PlyButton, TRUE) ;
}
bRecording = FALSE ;
if (bTerminating)
SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L) ;
return TRUE
message triggered by waveInClose function or when the waveform-audio input device is closed.
case MM_WOM_OPEN:
// Set up header
pWaveHdr1->lpData = reinterpret_cast <CHAR*>(pSaveBuffer) ;
pWaveHdr1->dwBufferLength = dwDataLength ;
pWaveHdr1->dwBytesRecorded = 0 ;
pWaveHdr1->dwUser = 0 ;
pWaveHdr1->dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP ;
pWaveHdr1->dwLoops = dwRepetitions ;
pWaveHdr1->lpNext = NULL ;
pWaveHdr1->reserved = 0 ;
// Prepare and write
waveOutPrepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
waveOutWrite (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
bEnding = FALSE ;
bPlaying = TRUE ;
return TRUE ;
opposite of MM_WOM_CLOSE and sent when a .wav file is playing back, sets and prepares the wave header then writes the wave header to the audio output device.
case MM_WOM_DONE:
waveOutUnprepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
waveOutClose (hWaveOut) ;
EnableWindow (PlyButton, TRUE);
return TRUE ;
case MM_WOM_CLOSE:
dwRepetitions = 1 ;
bPlaying = FALSE ;
return TRUE ;
case MM_WOM_DONE Unprepares header closes the audio output device and resets the PLAY button to on.
case MM_WOM_CLOSE just concerns itself with the setting of flags.
Windows Message Commands
case WM_COMMAND:
switch (wParam)
{
case APP_SAVE:
{
char szFileName[MAX_PATH] = "";
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize= sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Wave Files(*.wav)\0*.wav\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = "wav";
if (GetSaveFileName(&ofn))
{
if (szFileName)
{
SaveWavFile(szFileName,pWaveHdr1);
}
}
}
break ;
This sets up a Common Dialog to save a .wav file, ofn.flags are set to OFN_EXPLORER | OFN_OVERWRITEPROMPT this asks the user if he/she wants to overwrite the new file if a file by that name already exists.
It calls the SaveWavFile function to save the actual wave file.
case APP_EXIT:
{
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
//cleanup before closing
_fcloseall();
fSuccess= DeleteFile(TEXT("temp.wav"));
}
break ;
This piece of code exits the app.
it closes all open streams and deletes the temporary .wav file "temp.wav"
case IDC_RECORD:
{
EnableWindow (RecButton, FALSE) ;
EnableWindow (PlyButton, FALSE) ;
EnableWindow (StpButton, TRUE) ;
waveOutReset (hWaveOut) ;
waveInReset (hWaveIn) ;
pBuffer1=reinterpret_cast <PBYTE> (malloc(INP_BUFFER_SIZE) );
pBuffer2= reinterpret_cast <PBYTE> ( malloc(INP_BUFFER_SIZE) );
if (!pBuffer1 || !pBuffer2)
{
if (pBuffer1) free (pBuffer1) ;
if (pBuffer2) free (pBuffer2) ;
MessageBox (hwnd, szMemError, szAppName,
MB_ICONEXCLAMATION | MB_OK) ;
return TRUE ;
}
// Open waveform audio for input
waveform.wFormatTag = WAVE_FORMAT_PCM ;
waveform.nChannels = 1 ;
waveform.nSamplesPerSec = 11025 ;
waveform.nAvgBytesPerSec = 11025 ;
waveform.nBlockAlign = 1 ;
waveform.wBitsPerSample = 8 ;
waveform.cbSize = 0 ;
if (waveInOpen (&hWaveIn, WAVE_MAPPER, &waveform,
(DWORD) hwnd, 0, CALLBACK_WINDOW))
{
free (pBuffer1) ;
free (pBuffer2) ;
}
// Set up headers and prepare them
pWaveHdr1->lpData =reinterpret_cast <CHAR*>( pBuffer1 ) ;
pWaveHdr1->dwBufferLength = INP_BUFFER_SIZE ;
pWaveHdr1->dwBytesRecorded = 0 ;
pWaveHdr1->dwUser = 0 ;
pWaveHdr1->dwFlags = 0 ;
pWaveHdr1->dwLoops = 1 ;
pWaveHdr1->lpNext = NULL ;
pWaveHdr1->reserved = 0 ;
waveInPrepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
pWaveHdr2->lpData = reinterpret_cast <CHAR*>(pBuffer2 ) ;
pWaveHdr2->dwBufferLength = INP_BUFFER_SIZE ;
pWaveHdr2->dwBytesRecorded = 0 ;
pWaveHdr2->dwUser = 0 ;
pWaveHdr2->dwFlags = 0 ;
pWaveHdr2->dwLoops = 1 ;
pWaveHdr2->lpNext = NULL ;
pWaveHdr2->reserved = 0 ;
waveInPrepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
}
break ;
The above is our code for the record button.
The RECORD button itself is disabled along with the PLAY button the STOP button is enabled.
Memory is allocated for pBuffer1 and pBuffer2.
If you have run out of memory or memory cannot be allocated a messagebox is displayed with the nature of the error.
Next we set up the wave headers pWaveHdr1 and pWaveHdr2 and call the function
waveInPrepareHeader to prepare for waveform audio input.
case IDC_STOP:
{
_fcloseall();
EnableWindow (RecButton, TRUE) ;
EnableWindow (StpButton, FALSE) ;
EnableWindow (PlyButton, TRUE);
bEnding = TRUE ;
SaveWavFile("temp.wav",pWaveHdr1);
}
This is the code for the STOP button first we close all streams that are opened.
enable the RECORD and PLAY buttons and disable the STOP button.
The Flag bEnding is set to true and then the temporary file "temp.wav" is saved
using the SaveWavFile Function.
case IDC_PLAY:
{
// play wav file
bPlaying = TRUE;
EnableWindow (PlyButton, FALSE);
waveform.wFormatTag = WAVE_FORMAT_PCM ;
waveform.nChannels = 1 ;
waveform.nSamplesPerSec = 11025 ;
waveform.nAvgBytesPerSec = 11025 ;
waveform.nBlockAlign = 1 ;
waveform.wBitsPerSample = 8 ;
waveform.cbSize = 0 ;
waveInReset (hWaveIn) ;
waveOutReset (hWaveOut) ;
if (waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveform,
(DWORD) hwnd, 0, CALLBACK_WINDOW))
{
MessageBox (hwnd, szOpenError, szAppName,
MB_ICONEXCLAMATION | MB_OK) ;
}
Wav("temp.wav", hwnd);
RECT rc;
GetClientRect(hwnd, &rc);
PLAY = TRUE;
InvalidateRect(hwnd,&rc,TRUE);
}
break ;
}
break ;
The above code sets the flag bPlaying to true.
It disables the PLAY button.
Prepares the waveform for playing
if it cant open the audio output device it displays a Messsage Box with the Error.
It then calls the Wav function which loads the temporary wave file "temp.wav".
The flag PLAY is set to true and a refresh of the window is forced by InvalidateRect.
void SaveWavFile(char *FileName, PWAVEHDR WaveHeader)
{
fstream myFile (FileName, fstream::out | fstream::binary);
int chunksize,pcmsize,NumSamples,subchunk1size;
int audioFormat = 1;
int numChannels = 1;
int bitsPerSample = 8;
NumSamples = ((long) (NUMPTS/sampleRate) * 1000);
pcmsize = sizeof(PCMWAVEFORMAT);
subchunk1size= 16;
int byteRate = sampleRate*numChannels*bitsPerSample/8;
int blockAlign = numChannels*bitsPerSample/8;
int subchunk2size = WaveHeader->dwBufferLength*numChannels;
chunksize = (36 + subchunk2size);
// write the wav file per the wav file format
myFile.seekp (0, ios::beg);
myFile.write ("RIFF", 4); // chunk id
myFile.write ((char*) &chunksize, 4); // chunk size (36 + SubChunk2Size))
myFile.write ("WAVE", 4); // format
myFile.write ("fmt ", 4); // subchunk1ID
myFile.write ((char*) &subchunk1size, 4); // subchunk1size (16 for PCM)
myFile.write ((char*) &audioFormat, 2); // AudioFormat (1 for PCM)
myFile.write ((char*) &numChannels, 2); // NumChannels
myFile.write ((char*) &sampleRate, 4); // sample rate
myFile.write ((char*) &byteRate, 4); // byte rate (SampleRate * NumChannels * BitsPerSample/8)
myFile.write ((char*) &blockAlign, 2); // block align (NumChannels * BitsPerSample/8)
myFile.write ((char*) &bitsPerSample, 2); // bits per sample
myFile.write ("data", 4); // subchunk2ID
myFile.write ((char*) &subchunk2size, 4); // subchunk2size (NumSamples * NumChannels * BitsPerSample/8)
myFile.write (WaveHeader->lpData,WaveHeader->dwBufferLength); // data
myFile.close();
}
Finally we have the SavWavFile Function this opens a fstream object called myFile
the file is saved as according to the table in the Canonical Wave format section.
First "RIFF" is written, then the chunksize is written this is (36+ Bufferlength*number of channels(in this case 1)) next "WAVE" is written to determine which type of RIFF file it is.
Then "fmt" is written next subchunk1size is written in this case it is 16 for PCM.
Audio format is written next in this case it is 1 for PCM, next numchannels is written in this case it 1 once again.
The sample rate is the next thing to be written in this case it is 11025.
The Byte rate is now written to the file which is the SampleRate * NumChannels * BitsPerSample/8
BitsPerSample can be either 16 or 8 in this case its 8.
The Block alignment is written next which is NumChannels * BitsPerSample/8.
BitsPerSample is now written which is '8' which I havepreviously mentioned.
"data" is now written to the file to mark the start of the data portion of the .wav file.
subchunk2size is now written which is (NumSamples * NumChannels * BitsPerSample/8).
Lastly the actual sample data is written to the .wav file and the file is closed.
For completeness I will now post the projects main files the .cpp the .h and the .rc files.
Then I will go into detail about which Libraries to include.
recorder.cpp
#include <CommDlg.h>
#include <MMSystem.h>
#include <fstream>
#include <cstdlib>
#include "resource.h"
using namespace std;
//Globals for sound wave visualistion
int number, length, byte_samp, byte_sec, bit_samp;
static int sampleRate = 11025;
const int NUMPTS = 11025 * 10;
bool mono = TRUE;
bool PLAY = FALSE;
errno_t wavfile;
char * filename;
int s_rate = 11025;
double limit = 10000.0;
FILE * stream;
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
/* Declare procedures */
int readSample(int number,bool leftchannel);
void SaveWavFile(char *FileName, PWAVEHDR WaveHeader);
void Wav(char *c, HWND hWnd);
/* Make the class name into a global variable */
char szAppName[ ] = "Recorder";
// **********
// Windows Main Function.
// - Here starts our program
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */
/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szAppName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = "APP_MENU"; /*menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szAppName, /* Classname */
szAppName, /* Title Text */
WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
430, /* The programs width */
300, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /*use class menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);
/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND RecButton;
static HWND PlyButton;
static HWND StpButton;
static HMENU hMenu;
static HPEN hPen;
static BOOL bRecording, bPlaying,bEnding, bTerminating ;
static DWORD dwDataLength, dwRepetitions = 1 ;
static HWAVEIN hWaveIn ;
static HWAVEOUT hWaveOut ;
static PBYTE pBuffer1, pBuffer2, pSaveBuffer, pNewBuffer ;
static PWAVEHDR pWaveHdr1, pWaveHdr2 ;
static TCHAR szOpenError[] = TEXT ("Error opening waveform audio!");
static TCHAR szMemError [] = TEXT ("Error allocating memory!") ;
static WAVEFORMATEX waveform ;
hMenu = GetMenu (hwnd);
HDC hDC;
POINT pt [NUM];
BOOL fSuccess = FALSE;
switch (message) /* handle the messages */
{
case WM_CREATE:
RecButton = CreateWindow ( TEXT ("button"),"RECORD",WS_VISIBLE|WS_CHILD|ES_LEFT|1,7,175,100,25,hwnd,(HMENU) IDC_RECORD,((LPCREATESTRUCT) lParam) -> hInstance,NULL);
PlyButton = CreateWindow ( TEXT ("button"),"PLAY",WS_VISIBLE|WS_CHILD|ES_LEFT|1,157,175,100,25,hwnd,(HMENU) IDC_PLAY,((LPCREATESTRUCT) lParam) -> hInstance,NULL);
StpButton = CreateWindow ( TEXT ("button"),"STOP",WS_VISIBLE|WS_CHILD|ES_LEFT|1,314,175,100,25,hwnd,(HMENU) IDC_STOP,((LPCREATESTRUCT) lParam) -> hInstance,NULL);
EnableWindow (PlyButton, FALSE) ;
EnableWindow (StpButton, FALSE) ;
pWaveHdr1 = reinterpret_cast <PWAVEHDR> ( malloc (sizeof (WAVEHDR)) );
pWaveHdr2 = reinterpret_cast <PWAVEHDR> ( malloc (sizeof (WAVEHDR)) ) ;
// Allocate memory for save buffer
pSaveBuffer = reinterpret_cast <PBYTE> ( malloc (1) ) ;
return 0;
case WM_PAINT:
PAINTSTRUCT ps;
hDC = BeginPaint(hwnd, &ps);
if (hDC)
{
RECT rc;
rc.top = 35;
rc.left = 0;
rc.bottom = 145;
rc.right = 425;
FillRect(hDC,&rc,(HBRUSH)(COLOR_WINDOW+2));
if (PLAY==TRUE)
{
FillRect(hDC,&rc,(HBRUSH)(COLOR_WINDOW+2));
hPen = CreatePen(PS_SOLID,1,RGB(0,200,0));
SelectObject(hDC, hPen);
SetMapMode(hDC, MM_ISOTROPIC);
SetWindowExtEx(hDC, 400, 300, NULL);
SetViewportExtEx(hDC,200, 180, NULL);
SetViewportOrgEx(hDC, 0, 0, NULL);
int sample=0;
int i=1;
int num = 60000;
sample= readSample(i, TRUE);
// scale the sample
pt[i].x =i/20;
pt[i].y = (int) ((sample)*1.5);
MoveToEx (hDC,pt[i].x,pt[i].y,NULL);
while (i<num && sample!=(int)0xefffffff)
{
// scale the sample
pt[i].x = i/20;
pt[i].y = (int) ((sample)*1.5);
LineTo(hDC, pt[i].x,pt[i].y);
i++;
sample= readSample(i, TRUE);
}
}
DeleteObject(hPen);
DeleteDC(hDC);
EndPaint(hwnd, &ps);
}
return 0;
case MM_WIM_OPEN:
// Shrink down the save buffer
pSaveBuffer = reinterpret_cast <PBYTE>(realloc (pSaveBuffer, 1)) ;
// Add the buffers
waveInAddBuffer (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
waveInAddBuffer (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
// Begin sampling
bRecording = TRUE ;
bEnding = FALSE ;
dwDataLength = 0 ;
waveInStart (hWaveIn) ;
return TRUE ;
case MM_WIM_DATA:
// Reallocate save buffer memory
pNewBuffer = reinterpret_cast <PBYTE> (realloc (pSaveBuffer, dwDataLength +
((PWAVEHDR) lParam)->dwBytesRecorded)) ;
if (pNewBuffer == NULL)
{
waveInClose (hWaveIn) ;
MessageBox (hwnd, szMemError, szAppName,
MB_ICONEXCLAMATION | MB_OK) ;
return TRUE ;
}
pSaveBuffer = pNewBuffer ;
CopyMemory (pSaveBuffer + dwDataLength, ((PWAVEHDR) lParam)->lpData,
((PWAVEHDR) lParam)->dwBytesRecorded) ;
dwDataLength += ((PWAVEHDR) lParam)->dwBytesRecorded ;
if (bEnding)
{
waveInClose (hWaveIn) ;
return TRUE ;
}
// Send out a new buffer
waveInAddBuffer (hWaveIn, (PWAVEHDR) lParam, sizeof (WAVEHDR)) ;
return TRUE ;
case MM_WIM_CLOSE:
// Free the buffer memory
waveInUnprepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
waveInUnprepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
free (pBuffer1) ;
free (pBuffer2) ;
// Enable and disable buttons
if (dwDataLength > 0)
{
EnableWindow (PlyButton, TRUE) ;
}
bRecording = FALSE ;
if (bTerminating)
SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L) ;
return TRUE ;
case MM_WOM_OPEN:
// Set up header
pWaveHdr1->lpData = reinterpret_cast <CHAR*>(pSaveBuffer) ;
pWaveHdr1->dwBufferLength = dwDataLength ;
pWaveHdr1->dwBytesRecorded = 0 ;
pWaveHdr1->dwUser = 0 ;
pWaveHdr1->dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP ;
pWaveHdr1->dwLoops = dwRepetitions ;
pWaveHdr1->lpNext = NULL ;
pWaveHdr1->reserved = 0 ;
// Prepare and write
waveOutPrepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
waveOutWrite (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
bEnding = FALSE ;
bPlaying = TRUE ;
return TRUE ;
case MM_WOM_DONE:
waveOutUnprepareHeader (hWaveOut, pWaveHdr1, sizeof (WAVEHDR)) ;
waveOutClose (hWaveOut) ;
EnableWindow (PlyButton, TRUE);
return TRUE ;
case MM_WOM_CLOSE:
dwRepetitions = 1 ;
bPlaying = FALSE ;
return TRUE ;
case WM_COMMAND:
switch (wParam)
{
case APP_SAVE:
{
char szFileName[MAX_PATH] = "";
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize= sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = "Wave Files(*.wav)\0*.wav\0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = "wav";
if (GetSaveFileName(&ofn))
{
if (szFileName)
{
SaveWavFile(szFileName,pWaveHdr1);
}
}
}
break ;
case APP_EXIT:
{
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
//cleanup before closing
_fcloseall();
fSuccess= DeleteFile(TEXT("temp.wav"));
}
break ;
case IDC_RECORD:
{
EnableWindow (RecButton, FALSE) ;
EnableWindow (PlyButton, FALSE) ;
EnableWindow (StpButton, TRUE) ;
waveOutReset (hWaveOut) ;
waveInReset (hWaveIn) ;
pBuffer1=reinterpret_cast <PBYTE> (malloc(INP_BUFFER_SIZE) );
pBuffer2= reinterpret_cast <PBYTE> ( malloc(INP_BUFFER_SIZE) );
if (!pBuffer1 || !pBuffer2)
{
if (pBuffer1) free (pBuffer1) ;
if (pBuffer2) free (pBuffer2) ;
MessageBox (hwnd, szMemError, szAppName,
MB_ICONEXCLAMATION | MB_OK) ;
return TRUE ;
}
// Open waveform audio for input
waveform.wFormatTag = WAVE_FORMAT_PCM ;
waveform.nChannels = 1 ;
waveform.nSamplesPerSec = 11025 ;
waveform.nAvgBytesPerSec = 11025 ;
waveform.nBlockAlign = 1 ;
waveform.wBitsPerSample = 8 ;
waveform.cbSize = 0 ;
if (waveInOpen (&hWaveIn, WAVE_MAPPER, &waveform,
(DWORD) hwnd, 0, CALLBACK_WINDOW))
{
free (pBuffer1) ;
free (pBuffer2) ;
}
// Set up headers and prepare them
pWaveHdr1->lpData =reinterpret_cast <CHAR*>( pBuffer1 ) ;
pWaveHdr1->dwBufferLength = INP_BUFFER_SIZE ;
pWaveHdr1->dwBytesRecorded = 0 ;
pWaveHdr1->dwUser = 0 ;
pWaveHdr1->dwFlags = 0 ;
pWaveHdr1->dwLoops = 1 ;
pWaveHdr1->lpNext = NULL ;
pWaveHdr1->reserved = 0 ;
waveInPrepareHeader (hWaveIn, pWaveHdr1, sizeof (WAVEHDR)) ;
pWaveHdr2->lpData = reinterpret_cast <CHAR*>(pBuffer2 ) ;
pWaveHdr2->dwBufferLength = INP_BUFFER_SIZE ;
pWaveHdr2->dwBytesRecorded = 0 ;
pWaveHdr2->dwUser = 0 ;
pWaveHdr2->dwFlags = 0 ;
pWaveHdr2->dwLoops = 1 ;
pWaveHdr2->lpNext = NULL ;
pWaveHdr2->reserved = 0 ;
waveInPrepareHeader (hWaveIn, pWaveHdr2, sizeof (WAVEHDR)) ;
}
break ;
case IDC_STOP:
{
_fcloseall();
EnableWindow (RecButton, TRUE) ;
EnableWindow (StpButton, FALSE) ;
EnableWindow (PlyButton, TRUE);
bEnding = TRUE ;
SaveWavFile("temp.wav",pWaveHdr1);
}
break ;
case IDC_PLAY:
{
// play wav file
bPlaying = TRUE;
EnableWindow (PlyButton, FALSE);
waveform.wFormatTag = WAVE_FORMAT_PCM ;
waveform.nChannels = 1 ;
waveform.nSamplesPerSec = 11025 ;
waveform.nAvgBytesPerSec = 11025 ;
waveform.nBlockAlign = 1 ;
waveform.wBitsPerSample = 8 ;
waveform.cbSize = 0 ;
waveInReset (hWaveIn) ;
waveOutReset (hWaveOut) ;
if (waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveform,
(DWORD) hwnd, 0, CALLBACK_WINDOW))
{
MessageBox (hwnd, szOpenError, szAppName,
MB_ICONEXCLAMATION | MB_OK) ;
}
Wav("temp.wav", hwnd);
RECT rc;
GetClientRect(hwnd, &rc);
PLAY = TRUE;
InvalidateRect(hwnd,&rc,TRUE);
}
break ;
}
break ;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
//cleanup before closing
_fcloseall();
fSuccess= DeleteFile(TEXT("temp.wav"));
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
//start of wave visualisation process
int readSample(int number,bool leftchannel)
{
/*
Reads sample number, returns it as an int, if
this.mono==false we look at the leftchannel bool
to determine which to return.
number is in the range [0,length/byte_samp]
returns 0xefffffff on failure
*/
if (number>=0 && number<length/byte_samp)
{
// go to beginning of the file
rewind(stream);
// we start reading at sample_number * sample_size + header length
int offset = number * 1 + 44;
// unless this is a stereo file and the rightchannel is requested.
if (!mono && !leftchannel)
{
offset += byte_samp/2;
}
// read this many bytes;
int amount;
amount=byte_samp;
fseek(stream,offset,SEEK_CUR);
short sample = 0;
fread((void *)&sample,1,amount,stream);
return sample;
}
else
{
// return 0xefffffff if failed
return (int)0xefffffff;
}
}
// Read the temporary wav file
void Wav(char *c, HWND hWnd)
{
filename = new char[strlen(c)+1];
strcpy_s(filename,strlen(c)+1,c);
// open filepointer readonly
wavfile = fopen_s(&stream,filename,"r");
if (stream==NULL)
{
MessageBox(hWnd, "Could not open " + (char)filename,"Error", MB_OK);
}
else
{
// declare a char buff to store some values in
char *buff = new char[5];
buff[4]='\0';
// read the first 4 bytes
fread((void *)buff,1,4,stream);
// the first four bytes should be 'RIFF'
if (strcmp((char *)buff,"RIFF")==0)
{
// read byte 8,9,10 and 11
fseek(stream,4,SEEK_CUR);
fread((void *)buff,1,4,stream);
// this should read "WAVE"
if (strcmp((char *)buff,"WAVE")==0)
{
// read byte 12,13,14,15
fread((void *)buff,1,4,stream);
// this should read "fmt "
if (strcmp((char *)buff,"fmt ")==0)
{
fseek(stream,20,SEEK_CUR);
// final one read byte 36,37,38,39
fread((void *)buff,1,4,stream);
if (strcmp((char *)buff,"data")==0)
{
// Now we know it is a wav file, rewind the stream
rewind(stream);
// now is it mono or stereo ?
fseek(stream,22,SEEK_CUR);
fread((void *)buff,1,2,stream);
if (buff[0]==0x02)
{
mono=false;
}
else
{
mono=true;
}
// read the sample rate
fread((void *)&s_rate,1,4,stream);
fread((void *)&byte_sec,1,4,stream);
byte_samp=0;
fread((void *)&byte_samp,1,2,stream);
bit_samp=0;
fread((void *)&bit_samp,1,2,stream);
fseek(stream,4,SEEK_CUR);
fread((void *)&length,1,4,stream);
}
}
}
}
delete buff;
}
}
void SaveWavFile(char *FileName, PWAVEHDR WaveHeader)
{
fstream myFile (FileName, fstream::out | fstream::binary);
int chunksize,pcmsize,NumSamples,subchunk1size;
int audioFormat = 1;
int numChannels = 1;
int bitsPerSample = 8;
NumSamples = ((long) (NUMPTS/sampleRate) * 1000);
pcmsize = sizeof(PCMWAVEFORMAT);
subchunk1size= 16;
int byteRate = sampleRate*numChannels*bitsPerSample/8;
int blockAlign = numChannels*bitsPerSample/8;
int subchunk2size = WaveHeader->dwBufferLength*numChannels;
chunksize = (36 + subchunk2size);
// write the wav file per the wav file format
myFile.seekp (0, ios::beg);
myFile.write ("RIFF", 4); // chunk id
myFile.write ((char*) &chunksize, 4); // chunk size (36 + SubChunk2Size))
myFile.write ("WAVE", 4); // format
myFile.write ("fmt ", 4); // subchunk1ID
myFile.write ((char*) &subchunk1size, 4); // subchunk1size (16 for PCM)
myFile.write ((char*) &audioFormat, 2); // AudioFormat (1 for PCM)
myFile.write ((char*) &numChannels, 2); // NumChannels
myFile.write ((char*) &sampleRate, 4); // sample rate
myFile.write ((char*) &byteRate, 4); // byte rate (SampleRate * NumChannels * BitsPerSample/8)
myFile.write ((char*) &blockAlign, 2); // block align (NumChannels * BitsPerSample/8)
myFile.write ((char*) &bitsPerSample, 2); // bits per sample
myFile.write ("data", 4); // subchunk2ID
myFile.write ((char*) &subchunk2size, 4); // subchunk2size (NumSamples * NumChannels * BitsPerSample/8)
myFile.write (WaveHeader->lpData,WaveHeader->dwBufferLength); // data
myFile.close();
}
resource.h
#ifndef RESOURCE_H_INCLUDED #define RESOURCE_H_INCLUDED #include <windows.h> #define INP_BUFFER_SIZE 16384 #define IDC_RECORD 1 #define IDC_PLAY 2 #define IDC_STOP 3 #define NUM 20000 //defines for menu #define APP_SAVE 1003 #define APP_EXIT 1004 #endif // RESOURCE_H_INCLUDED
resource.rc
//MENU
//CREATION
#include "resource.h"
#include "afxres.h"
APP_MENU MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Save", APP_SAVE
MENUITEM "&Exit", APP_EXIT
END
END
In Visual Studio 2010 to link to the Libraries :-
WinMM.Lib;
kernel32.lib;
user32.lib;
comdlg32.lib
which are required to build the project
GoTo Project -> Properties
(or press ALT + F7)
then GoTo Linker->Input -> Additional Dependancies
and then add the above libraries as shown.
References
Programming Windows Fifth Edition by Charles Petzold.
MSDN
(If anyone wants a MinGW/gcc friendly version please ask as I have one built)






MultiQuote





|