Code:
/* gcc main.c -lwinmm -std=c99 -pedantic */
#include <windows.h>
#include <stdbool.h>
#include <stdio.h>
#define MAXBUFFSIZE 44100
#define WM_PLYBCKERR (WM_USER+1)
#define WM_ENDPLAY (WM_USER+2)
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
void ErrorBox(HWND hwnd, const char *msg)
{
MessageBox(hwnd, msg, "Error", MB_OK | MB_ICONSTOP);
}
typedef struct _wave_struct
{
WAVEFORMATEX wsFormat;
PBYTE FileData;
LPWAVEHDR Hdrs;
unsigned int HdrC;
} WAVE_STRUCT, *PWAVE_STRUCT;
typedef struct _wave_file_hdr
{
char wfhSign[4];
unsigned int wfhChnkSize;
char wfhFormat[4];
char wfhSubChnk1ID[4];
unsigned int wfhSubChnk1Size;
unsigned short wfhAudFormat;
unsigned short wfhChannels;
unsigned int wfhSampPerSec;
unsigned int wfhAvgBytesPerSec;
unsigned short wfhBlockAlign;
unsigned short wfhBitsPerSamp;
char wfhSubChnk2Sign[4];
unsigned int wfhSubChnk2Size;
} WAVE_FILE_HDR, *PWAVE_FILE_HDR;
typedef enum
{
PlayState_Exit,
PlayState_Pause,
PlayState_Play,
PlayState_RewindPlay,
} PlayState;
typedef struct _thr_data
{
HANDLE Thr;
WAVE_STRUCT ws;
HWAVEOUT wOut;
char *wLoc;
HWND hwndPar;
ULONG ID;
PlayState ps;
CRITICAL_SECTION csps;
HANDLE evPlayStateChange,
evData;
} THREAD_DATA, *PTHREAD_DATA;
bool LoadWaveFile(PWAVE_STRUCT ws, const char *Loc)
{
if (!ws || !Loc)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
WAVE_FILE_HDR Head = {0};
DWORD Read = 0, i = 0;
PBYTE wPoint = 0;
HANDLE wFile = CreateFile(Loc, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (wFile == INVALID_HANDLE_VALUE)
return false;
if (!ReadFile(wFile, &Head, sizeof(Head), &Read, NULL))
{
CloseHandle(wFile);
return false;
}
if (Head.wfhSign[0] != 'R' || Head.wfhSign[1] != 'I' ||
Head.wfhSign[2] != 'F' || Head.wfhSign[3] != 'F')
{
CloseHandle(wFile);
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
if (Head.wfhFormat[0] != 'W' || Head.wfhFormat[1] != 'A' ||
Head.wfhFormat[2] != 'V' || Head.wfhFormat[3] != 'E')
{
CloseHandle(wFile);
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
if (Head.wfhAudFormat != WAVE_FORMAT_PCM)
{
CloseHandle(wFile);
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
Read = 1 + (Head.wfhChnkSize-36) / MAXBUFFSIZE;
ws->Hdrs = (WAVEHDR*)malloc(Read * sizeof(WAVEHDR) +
(Head.wfhChnkSize - 36));
if (!ws->Hdrs)
{
CloseHandle(wFile);
SetLastError(ERROR_OUTOFMEMORY);
return false;
}
ZeroMemory(ws->Hdrs, Read * sizeof(WAVEHDR) + (Head.wfhChnkSize - 36));
ws->FileData = (PBYTE)ws->Hdrs+Read*sizeof(WAVEHDR);
ws->HdrC = Read;
ReadFile(wFile, ws->FileData, Head.wfhChnkSize - 36, &Read, NULL);
if (Read != (Head.wfhChnkSize - 36))
{
free(ws->Hdrs);
CloseHandle(wFile);
free(ws->FileData);
return false;
}
ws->wsFormat.wFormatTag = WAVE_FORMAT_PCM;
ws->wsFormat.nChannels = Head.wfhChannels;
ws->wsFormat.nSamplesPerSec = Head.wfhSampPerSec;
ws->wsFormat.nAvgBytesPerSec = Head.wfhAvgBytesPerSec;
ws->wsFormat.nBlockAlign = Head.wfhBlockAlign;
ws->wsFormat.wBitsPerSample = Head.wfhBitsPerSamp;
ws->wsFormat.cbSize = 0;
CloseHandle(wFile);
wPoint = ws->FileData;
for (i = 0; i < ws->HdrC - 1; ++i)
{
ws->Hdrs[i].lpData = (LPSTR)wPoint;
ws->Hdrs[i].dwBufferLength = MAXBUFFSIZE;
wPoint += MAXBUFFSIZE;
}
ws->Hdrs[ws->HdrC-1].lpData = (LPSTR)wPoint;
ws->Hdrs[ws->HdrC-1].dwBufferLength = (Head.wfhChnkSize-36) % MAXBUFFSIZE;
SetLastError(ERROR_SUCCESS);
return true;
}
void CALLBACK waveyProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance,
DWORD Param1, DWORD Param2)
{
if (uMsg == WOM_DONE)
{
PTHREAD_DATA pThrDat = (PTHREAD_DATA)dwInstance;
SetEvent(pThrDat->evData);
}
return;
}
DWORD WINAPI PlayThr(void *param)
{
PTHREAD_DATA pThrDat = (PTHREAD_DATA)param;
MMRESULT mRes = MMSYSERR_NOERROR;
unsigned int Pos = 0;
bool bPaused = false;
if (!LoadWaveFile(&pThrDat->ws, pThrDat->wLoc))
{
PostMessage(pThrDat->hwndPar, WM_PLYBCKERR, 0, GetLastError());
return 0;
}
mRes = waveOutOpen(&pThrDat->wOut, WAVE_MAPPER, &pThrDat->ws.wsFormat,
(DWORD_PTR)waveyProc, (DWORD_PTR)pThrDat,
CALLBACK_FUNCTION);
if (mRes != MMSYSERR_NOERROR)
{
PostMessage(pThrDat->hwndPar, WM_PLYBCKERR, 0, mRes);
free(pThrDat->ws.Hdrs);
return 0;
}
for (Pos = 0; Pos < pThrDat->ws.HdrC; ++Pos)
waveOutPrepareHeader(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
# define numWaitObjects 2
const HANDLE waitObjects[numWaitObjects] =
{
pThrDat->evPlayStateChange,
pThrDat->evData,
};
enum
{
WO_PLAYSTATE_CHANGE = WAIT_OBJECT_0 + 0,
WO_MORE_DATA = WAIT_OBJECT_0 + 1,
};
Pos = 0;
SetEvent(pThrDat->evData);
for (;;)
{
DWORD status = WaitForMultipleObjects(numWaitObjects, waitObjects,
FALSE, INFINITE);
if (status == WO_MORE_DATA)
{
if (!bPaused && Pos < pThrDat->ws.HdrC)
{
waveOutWrite(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
++Pos;
}
}
else if (status == WO_PLAYSTATE_CHANGE)
{
PlayState nextState;
EnterCriticalSection(&pThrDat->csps);
nextState = pThrDat->ps;
LeaveCriticalSection(&pThrDat->csps);
if (nextState == PlayState_Exit)
{
break;
}
else if (nextState == PlayState_Pause)
{
bPaused = true;
waveOutPause(pThrDat->wOut);
}
else if (nextState == PlayState_Play ||
nextState == PlayState_RewindPlay)
{
if (bPaused)
waveOutRestart(pThrDat->wOut);
bPaused = false;
if (nextState == PlayState_RewindPlay)
{
waveOutReset(pThrDat->wOut);
Pos = 0;
SetEvent(pThrDat->evData);
}
}
}
}
for (Pos = 0; Pos < pThrDat->ws.HdrC; ++Pos)
waveOutUnprepareHeader(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
waveOutClose(pThrDat->wOut);
free(pThrDat->ws.Hdrs);
PostMessage(pThrDat->hwndPar, WM_ENDPLAY, 0, 0);
return 0;
}
THREAD_DATA ThrData = {0};
char szClassName[ ] = "WavePlayer";
char *wLoc = NULL;
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nFunsterStil)
{
HWND hwnd;
MSG messages;
WNDCLASSEX wincl;
if (!lstrlen(lpszArgument))
{
ErrorBox(NULL,"No sound file specified!");
return 0;
}
else
{
wLoc = lpszArgument;
}
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure;
wincl.style = CS_DBLCLKS;
wincl.cbSize = sizeof(WNDCLASSEX);
wincl.hIcon = LoadIcon (NULL,IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL,IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL,IDC_ARROW);
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
if (!RegisterClassEx(&wincl))
return 0;
hwnd = CreateWindowEx(0, szClassName, "Wavey",
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 400,
NULL, NULL, hThisInstance, NULL);
while (GetMessage(&messages, NULL, 0, 0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
ThrData.hwndPar = hwnd;
ThrData.wLoc = wLoc;
ThrData.ps = PlayState_Play;
InitializeCriticalSection(&ThrData.csps);
ThrData.evPlayStateChange = CreateEvent(0, FALSE, FALSE, 0);
ThrData.evData = CreateEvent(0, FALSE, FALSE, 0);
ThrData.Thr = CreateThread(NULL, 0, PlayThr, &ThrData, 0, &ThrData.ID);
if (!ThrData.Thr)
{
ErrorBox(hwnd,"Unable to initialize playback thread!");
PostQuitMessage(0);
}
break;
case WM_LBUTTONDOWN:
EnterCriticalSection(&ThrData.csps);
if (ThrData.ps != PlayState_Pause)
ThrData.ps = PlayState_Pause;
else
ThrData.ps = PlayState_Play;
LeaveCriticalSection(&ThrData.csps);
SetEvent(ThrData.evPlayStateChange);
break;
case WM_RBUTTONDOWN:
EnterCriticalSection(&ThrData.csps);
ThrData.ps = PlayState_RewindPlay;
LeaveCriticalSection(&ThrData.csps);
SetEvent(ThrData.evPlayStateChange);
break;
case WM_PLYBCKERR:
{
char err[512];
sprintf(err, "Playback Error:\n(0x%X)", lParam);
ErrorBox(hwnd, err);
CloseHandle(ThrData.Thr);
break;
}
case WM_ENDPLAY:
CloseWindow(hwnd);
break;
case WM_DESTROY:
EnterCriticalSection(&ThrData.csps);
ThrData.ps = PlayState_Exit;
LeaveCriticalSection(&ThrData.csps);
SetEvent(ThrData.evPlayStateChange);
WaitForSingleObject(ThrData.Thr, INFINITE);
CloseHandle(ThrData.Thr);
CloseHandle(ThrData.evData);
CloseHandle(ThrData.evPlayStateChange);
DeleteCriticalSection(&ThrData.csps);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return ERROR_SUCCESS;
}