/* The original version of this program can be found at http://damb.dk */
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#define SAMPLE_RATE 22050

void CheckError(MMRESULT MMResult, const char *msg)
{
   if(MMResult != MMSYSERR_NOERROR)
   {
      char Text[256];
      waveOutGetErrorText(MMResult, Text, sizeof(Text));
      fprintf(stderr, "%s: %sn", msg, Text);
      exit(EXIT_FAILURE);
   }
}

BOOL KeyHit();
WORD GetChar();

bool Play(char *buffer, int NumSamples)
{
  HWAVEOUT WaveHandle;
  WAVEHDR WaveHeader;
  MMRESULT MMResult;
  WAVEFORMATEX WaveFormat;

  // Setup the WaveFormat structure and open the Output device
  WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
  WaveFormat.nChannels = 1;
  WaveFormat.nSamplesPerSec = SAMPLE_RATE;
  WaveFormat.nAvgBytesPerSec = SAMPLE_RATE;
  WaveFormat.nBlockAlign = 1;
  WaveFormat.wBitsPerSample = 8;
  WaveFormat.cbSize = 0;
  MMResult = waveOutOpen(&WaveHandle, WAVE_MAPPER, &WaveFormat, 0, 0, 0);
  CheckError(MMResult, "Failed to open wave out");

  // Setup the WaveHeader, whcih contaions info about the data to play
  memset(&WaveHeader, 0, sizeof(WaveHeader));
  WaveHeader.lpData = buffer;
  WaveHeader.dwBufferLength = NumSamples;
  WaveHeader.dwBytesRecorded = NumSamples;
  WaveHeader.dwLoops = 1;
  WaveHeader.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
  MMResult = waveOutPrepareHeader(WaveHandle, &WaveHeader, sizeof(WaveHeader));
  CheckError(MMResult, "Failed to prepare header");

  // "write" (ie play) the samples
  MMResult = waveOutWrite(WaveHandle, &WaveHeader, sizeof(WaveHeader));
  CheckError(MMResult, "Failed to write");

  // Wait until done
  printf("Playing");
  fflush(stdout);
  Sleep(1000*NumSamples/SAMPLE_RATE);
  while(!(WaveHeader.dwFlags & WHDR_DONE))
    Sleep(10);
  printf("n");

  // Gracefully shutdown
  MMResult = waveOutUnprepareHeader(WaveHandle, &WaveHeader, sizeof(WaveHeader));
  CheckError(MMResult, "Failed to unprepare header");
  MMResult = waveOutClose(WaveHandle);
  CheckError(MMResult, "Failed to close");

  return true;
}

bool Record(char *buffer, int &NumSamples)
{
   HWAVEIN WaveInHandle;
   WAVEFORMATEX WaveFormat;
   MMRESULT MMResult;
   WAVEHDR WaveHeader;
   MMTIME MMTime;

   // Setup the WaveFormat structure and open the Input device
   WaveFormat.wFormatTag = WAVE_FORMAT_PCM;
   WaveFormat.nChannels = 1;
   WaveFormat.nSamplesPerSec = SAMPLE_RATE;
   WaveFormat.nAvgBytesPerSec = SAMPLE_RATE;
   WaveFormat.nBlockAlign = 1;
   WaveFormat.wBitsPerSample = 8;
   WaveFormat.cbSize = 0;
   MMResult = waveInOpen(&WaveInHandle, WAVE_MAPPER, &WaveFormat, 0, 0, 0);
   CheckError(MMResult, "Failed to open input");

   // Setup the WaveHeader, whcih contaions info about the data to record
   memset(&WaveHeader, 0, sizeof(WaveHeader));
   WaveHeader.lpData = buffer;
   WaveHeader.dwBufferLength = NumSamples;
   MMResult = waveInPrepareHeader(WaveInHandle, &WaveHeader, sizeof(WaveHeader));
   CheckError(MMResult, "Failed to prepare header");

   // Tell the system to use our buffer
   MMResult = waveInAddBuffer(WaveInHandle, &WaveHeader, sizeof(WaveHeader));
   CheckError(MMResult, "Failed to add buffer");

   // Start recording
   MMResult = waveInStart(WaveInHandle);
   CheckError(MMResult, "Failed to Start");

   // Wait for user key or end of input
   printf("Recording, hit any key to stop: ");
   fflush(stdout);
   do
   {
     Sleep(100);
   }
   while(!KeyHit() && !(WaveHeader.dwFlags & WHDR_DONE));
   while(KeyHit())
     GetChar();
   printf("n");

   // Get the position at which the recording stopped
   MMTime.wType = TIME_BYTES;
   MMResult = waveInGetPosition(WaveInHandle, &MMTime, sizeof(MMTime));
   CheckError(MMResult, "Failed to get positionn");
   NumSamples = MMTime.u.cb;

   // Gracefully stop
   MMResult = waveInReset(WaveInHandle);
   CheckError(MMResult, "Failed to resetn");
   MMResult = waveInUnprepareHeader(WaveInHandle, &WaveHeader, sizeof(WaveHeader));
   CheckError(MMResult, "Failed to unprepare header");
   MMResult = waveInClose(WaveInHandle);
   CheckError(MMResult, "Failed to close input");

   return true;
}

void SaveFile(char *buffer, int NumSamples)
{ /* http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ */
  FILE *f = fopen("sample.wav", "wb");
  if(!f)
  {
     fprintf(stderr, "Failed to open output filen");
     return;
  }
  fwrite("RIFF", 4, 1, f);
  int size = NumSamples + 36;
  fwrite(&size, 4, 1, f);
  fwrite("WAVE", 4, 1, f);
  fwrite("fmt ", 4, 1, f);

  size = 16;
  fwrite(&size, 4, 1, f);
  size = 1;
  fwrite(&size, 2, 1, f);
  size = 1;
  fwrite(&size, 2, 1, f);
  size = SAMPLE_RATE;
  fwrite(&size, 4, 1, f);
  fwrite(&size, 4, 1, f);
  size = 1;
  fwrite(&size, 2, 1, f);
  size = 8;
  fwrite(&size, 2, 1, f);
  fwrite("data", 4, 1, f);
  fwrite(&NumSamples, 4, 1, f);
  fwrite(buffer, NumSamples, 1, f);

  fclose(f);

  printf("Recorded data written to sample.wavn");
}

int main(void)
{
   /* Run sndvol32.exe -r to adjust the recording properties */
   int NumSamples = 10*SAMPLE_RATE;
   printf("Hit any key to Record");
   fflush(stdout);
   GetChar();
   printf("n");

   char *Buffer = (char *)malloc(10*SAMPLE_RATE);

   if(Record(Buffer, NumSamples))
   {
      SaveFile(Buffer, NumSamples);
      printf("Hit any key to play");
      fflush(stdout);
      GetChar();
      printf("n");
      Play(Buffer, NumSamples);
   }
   free(Buffer);
   return 0;
}

// Some helper functions
BOOL KeyHit()
{
  DWORD NumEvents, NumEventsRead;
  INPUT_RECORD *InputRecord;
  DWORD i;
  GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE), &NumEvents);

  InputRecord = (INPUT_RECORD *)malloc(sizeof(INPUT_RECORD)*NumEvents);
  PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), InputRecord, NumEvents, &NumEventsRead);

  for(i = 0; i < NumEventsRead; i++)
  {
    if(InputRecord[i].EventType & KEY_EVENT && InputRecord[i].Event.KeyEvent.bKeyDown)
    {
      if(InputRecord[i].Event.KeyEvent.wVirtualKeyCode != VK_CONTROL &&
         InputRecord[i].Event.KeyEvent.wVirtualKeyCode != VK_MENU  &&
         InputRecord[i].Event.KeyEvent.wVirtualKeyCode != VK_SHIFT)
      {
        free(InputRecord);
        return TRUE;
      }
    }
  }
  free(InputRecord);
  return FALSE;
}

WORD GetChar()
{
  DWORD NumEventsRead;
  INPUT_RECORD InputRecord;

  while(1)
  {
    if(!ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &InputRecord, 1, &NumEventsRead))
      return 0;
    if(InputRecord.EventType & KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown)
    {
      if(InputRecord.Event.KeyEvent.wVirtualKeyCode != VK_CONTROL &&
         InputRecord.Event.KeyEvent.wVirtualKeyCode != VK_MENU  &&
         InputRecord.Event.KeyEvent.wVirtualKeyCode != VK_SHIFT)
      {
        return InputRecord.Event.KeyEvent.wVirtualKeyCode;
      }
    }
  }
}