#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <mem.h>
#pragma hdrstop

#include "waveio.hpp"

BOOL WAVEPlay(HWND);

extern WAVEIOCB waveiocbOut;
extern WAVEIOCB waveiocbIn;
extern HWAVEOUT hWaveOut;
extern HWAVEIN  hWaveIn;
extern int  nMode;
extern int  nPosition;

//-----------------------------------------------------
// WAVELoad
// Загрузка wav-файла для проигрывания
//-----------------------------------------------------
BOOL WAVELoad(LPWAVEIOCB lpwiocb)
{
  BYTE szFileName[256];
  OPENFILENAME ofn;
  int rc;
  BYTE szBuf[256];

  // Проверяем наличие драйвера, способного выводить
  // звуковые файлы
  rc=waveOutGetNumDevs();
  if(!rc)
  {
    MessageBox(NULL,
    (LPSTR)"Нет устройств для вывода звуковых файлов",
    "Wave Error", MB_OK | MB_ICONHAND);
    return FALSE;
  }

  // Выбираем wav-файл
  if(!wioSelectFile(szFileName))
    return FALSE;

  // Открываем и загружаем в память выбранный файл
  rc = wioFileOpen(lpwiocb, (LPSTR)szFileName);

  if(rc == WIOERR_NOERROR)
    return TRUE;
  else if(rc == WIOERR_FILEERROR)
    lstrcpy(szBuf, "Ошибка при открытии файла");
  else if(rc == WIOERR_BADFORMAT)
    lstrcpy(szBuf, "Неправильный или неподдерживаемый формат файла");
  else if(rc == WIOERR_NOMEM)
    lstrcpy(szBuf, "Мало памяти");
  else if(rc == WIOERR_READERROR)
    lstrcpy(szBuf, "Ошибка при чтении");
  else
    lstrcpy(szBuf, "Неизвестная ошибка");

  MessageBox(NULL, (LPSTR)szBuf,
    "Wave Error", MB_OK | MB_ICONHAND);
  return FALSE;
}

//-----------------------------------------------------
// wioSelectFile
// Выбор wav-файла
//-----------------------------------------------------
BOOL wioSelectFile(LPSTR lpszFileName)
{
  OPENFILENAME ofn;

  char szFile[256];
  char szFileTitle[256];
  char szFilter[256] =
	 "Wave Files\0*.wav\0Any Files\0*.*\0";
  szFile[0] = '\0';
  memset(&ofn, 0, sizeof(OPENFILENAME));

  // Инициализируем нужные нам поля
  ofn.lStructSize       = sizeof(OPENFILENAME);
  ofn.hwndOwner         = NULL;
  ofn.lpstrFilter       = szFilter;
  ofn.nFilterIndex      = 1;
  ofn.lpstrFile         = szFile;
  ofn.nMaxFile          = sizeof(szFile);
  ofn.lpstrFileTitle    = szFileTitle;
  ofn.nMaxFileTitle     = sizeof(szFileTitle);
  ofn.lpstrInitialDir   = NULL;
  ofn.Flags =   OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
               | OFN_HIDEREADONLY;

  // Выбираем входной файл
  if (GetOpenFileName(&ofn))
  {
    // Копируем путь к выбранному файлу
    lstrcpy(lpszFileName, (LPSTR)szFile);
    return TRUE;
  }
  else
    return FALSE;
}

//---------------------------------------------------------
//  wioFileOpen
//  Открытие и загрузка wav-файла
//---------------------------------------------------------
int wioFileOpen(LPWAVEIOCB lpwiocb, LPSTR lpszFileName)
{
  HMMIO hmmio;
  MMCKINFO ckRIFF, ckFMT;
  DWORD dwFmtSize;

  // Открываем wav-файл
  hmmio = mmioOpen((LPSTR)lpszFileName, NULL, MMIO_READ | MMIO_ALLOCBUF);
  if(!hmmio)
    return WIOERR_FILEERROR;

  // Ищем фрагмент "WAVE"
  memset(&ckRIFF, 0, sizeof(MMCKINFO));
  ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');

  if(mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF))
  {
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Ищем фрагмент "fmt "
  memset(&ckFMT, 0, sizeof(MMCKINFO));
  ckFMT.ckid = mmioFOURCC('f', 'm', 't', ' ');

  if(mmioDescend(hmmio,
    &ckFMT, &ckRIFF, MMIO_FINDCHUNK))
  {
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  dwFmtSize = sizeof(PCMWAVEFORMAT);  //ckFMT.cksize;

  // Получаем память для загрузки формата звукового файла 
  lpwiocb->lpFmt = (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR, dwFmtSize);

  if(!lpwiocb->lpFmt)
  {
    mmioClose(hmmio,0);
    return WIOERR_NOMEM;
  }

  // Загружаем формат звукового файла
  if(mmioRead(hmmio, (HPSTR)lpwiocb->lpFmt, dwFmtSize) != (LONG)dwFmtSize)
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_READERROR;
  }

  // Проверяем формат звукового файла
  if(lpwiocb->lpFmt->wf.wFormatTag != WAVE_FORMAT_PCM)
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Проверяем способность драйвера работать с указанным форматом
  if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt,
       NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC))
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Вычисляем количество байт, необходимых для
  // хранения одной выборки звукового сигнала
  lpwiocb->wBitsPerSample = lpwiocb->lpFmt->wBitsPerSample;
  lpwiocb->wBytesPerSample =
    (waveiocbOut.wBitsPerSample/8) * waveiocbOut.lpFmt->wf.nChannels;

  // Ищем фрагмент "data"
  mmioAscend(hmmio, &ckFMT, 0);
  ckFMT.ckid = mmioFOURCC('d', 'a', 't', 'a');

  if(mmioDescend(hmmio, &ckFMT, &ckRIFF, MMIO_FINDCHUNK))
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Определяем размер фрагмента сегмента звуковых
  // данных и его смещение в wav-файле
  lpwiocb->dwDataSize   = ckFMT.cksize;
  lpwiocb->dwDataOffset = ckFMT.dwDataOffset;

  // Проверяем, что файл не пуст
  if(lpwiocb->dwDataSize == 0L)
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Получаем память для заголовка блока
  lpwiocb->lpWaveHdr =
    (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
  if(!lpwiocb->lpWaveHdr)
    return WIOERR_NOMEM;

  // Получаем память для звуковых данных 
  lpwiocb->lpData =
    (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, lpwiocb->dwDataSize);
  if(!lpwiocb->lpData)
    return WIOERR_NOMEM;

  // Позиционирование на начало звуковых данных
  mmioSeek(hmmio, SEEK_SET, lpwiocb->dwDataOffset);

  // Читаем звуковые данные
  mmioRead(hmmio, lpwiocb->lpData, lpwiocb->dwDataSize);

  // Закрываем wav-файл
  mmioClose(hmmio,0);

  return WIOERR_NOERROR;
}

//---------------------------------------------------------
// wioPlay
// Проигрывание загруженного блока звуковых данных
//---------------------------------------------------------
int wioPlay(LPWAVEIOCB lpwiocb, HWND hwnd)
{
  WORD rc;

  // Открываем устройство вывода
  rc=waveOutOpen(&hWaveOut, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt,
                 (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);
  if(rc) return rc;

  // Заполняем заголовок блока данных
  lpwiocb->lpWaveHdr->lpData          = (LPSTR)lpwiocb->lpData;
  lpwiocb->lpWaveHdr->dwBufferLength  = lpwiocb->dwDataSize;
  lpwiocb->lpWaveHdr->dwBytesRecorded = 0;
  lpwiocb->lpWaveHdr->dwFlags         = 0;
  lpwiocb->lpWaveHdr->dwLoops         = 0;
  lpwiocb->lpWaveHdr->dwUser          = 0;
  lpwiocb->lpWaveHdr->lpNext          = 0;
  lpwiocb->lpWaveHdr->reserved        = 0;

  // Подготалвиваем заголовок для вывода
  rc=waveOutPrepareHeader(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
  if(rc)
  {
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    return rc;
  }

  // Запускаем проигрывание блока
  rc = waveOutWrite(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
  if(rc)
  {
    waveOutUnprepareHeader(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    return rc;
  }

  return 0;
}

//---------------------------------------------------------
//  wioRecord
//  Запись звуковых данных
//---------------------------------------------------------
int wioRecord(LPWAVEIOCB lpwiocb, HWND hwnd)
{
  int rc;

  // Проверяем наличие драйвера, способного
  // выполнять запись звука 
  rc=waveInGetNumDevs();
  if(!rc)
  {
    MessageBox(NULL,
    (LPSTR)"Нет устройств для записи звуковых файлов",
    "Wave Error", MB_OK | MB_ICONHAND);
    return WIOERR_NODEVICE;
  }

  // Максимальный размер блока в байтах
  lpwiocb->dwDataSize = MAXSAMPLES;

  // Получаем память для заголовка блока
  lpwiocb->lpWaveHdr =
    (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
  if(!lpwiocb->lpWaveHdr)
    return WIOERR_NOMEM;

  // Получаем память для блока звуковых данных
  lpwiocb->lpData =
    (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, lpwiocb->dwDataSize);
  if(!lpwiocb->lpData)
    return WIOERR_NOMEM;

  // Получаем память для блока формата 
  lpwiocb->lpFmt =
    (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR, sizeof(WAVEFORMAT));

  if(!lpwiocb->lpFmt)
  {
    return WIOERR_NOMEM;
  }

  // Заполняем блок формата. Наше приложение способно
  // записывать монофонические файлы в формате WAVE_FORMAT_PCM
  // с частотой дискретизации 22,05 КГц
  lpwiocb->lpFmt->wf.wFormatTag      = WAVE_FORMAT_PCM;
  lpwiocb->lpFmt->wf.nChannels       = 1;
  lpwiocb->lpFmt->wf.nSamplesPerSec  = 22050;
  lpwiocb->lpFmt->wf.nAvgBytesPerSec = 22050;
  lpwiocb->lpFmt->wf.nBlockAlign     = 1;

  // Открываем устройство записи
  rc=waveInOpen(&hWaveIn, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt,
                (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);
  if(rc)
  {
    wioInError(rc);
    return WIOERR_BADFORMAT;
  }

  // Заполняем заголовок блока
  lpwiocb->lpWaveHdr->lpData         = (LPSTR)lpwiocb->lpData;
  lpwiocb->lpWaveHdr->dwBufferLength = lpwiocb->dwDataSize;
  lpwiocb->lpWaveHdr->dwFlags        = 0L;
  lpwiocb->lpWaveHdr->dwLoops        = 0L;
  lpwiocb->lpWaveHdr->dwUser         = 0L;

  // Подготавливаем блок для записи
  rc = waveInPrepareHeader(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
  if(rc)
  {
    wioInError(rc);
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    return WIOERR_BADFORMAT;
  }

  // Передаем блок устройству записи
  rc = waveInAddBuffer(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
  if(rc)
  {
    wioInError(rc);
    waveInUnprepareHeader(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    GlobalFreePtr(lpwiocb->lpFmt);
    GlobalFreePtr(lpwiocb->lpData);
    return WIOERR_ERROR;
  }

  // Запускаем запись
  rc = waveInStart(hWaveIn);
  if(rc)  wioInError(rc);

  return TRUE;
}

//---------------------------------------------------------
//  wioFileSave
//  Сохранение записанного звука в wav-файле
//---------------------------------------------------------
BOOL wioFileSave(LPSTR szFileName)
{
   DWORD dwDataSize;
   char szdata[] = "data";
   HMMIO hFile;
   MMCKINFO ck;
   WORD wBitsPerSample = 8;
   char szfmt[] = "fmt ";
   DWORD dwFmtSize = sizeof(PCMWAVEFORMAT);

   // Создаем новый файл или перезаписываем существующий
   hFile = mmioOpen(szFileName, NULL, MMIO_CREATE | MMIO_READWRITE);
   if(hFile != NULL)
   {
     // Создаем заголовок wav-файла 
     ck.ckid = MMIO_CREATERIFF;
     ck.cksize = waveiocbIn.lpWaveHdr->dwBytesRecorded
                 + sizeof(PCMWAVEFORMAT) + 20;
     ck.fccType = mmioFOURCC('W', 'A', 'V', 'E');
     mmioCreateChunk(hFile, (LPMMCKINFO)&ck, MMIO_CREATERIFF);

     // Записываем фрагмент "fmt "
     mmioWrite(hFile, (HPSTR)szfmt, 4);
     mmioWrite(hFile, (HPSTR)&dwFmtSize, sizeof(DWORD));

     mmioWrite(hFile, (HPSTR)waveiocbIn.lpFmt, sizeof(WAVEFORMAT));
     mmioWrite(hFile, (HPSTR)&wBitsPerSample, sizeof(WORD));

     mmioWrite(hFile, (HPSTR)szdata, 4);
     dwDataSize = waveiocbIn.lpWaveHdr->dwBytesRecorded;
     mmioWrite(hFile, (HPSTR)&dwDataSize, sizeof(DWORD));

     // Записываем данные
     mmioWrite(hFile, (HPSTR)waveiocbIn.lpData, waveiocbIn.lpWaveHdr->dwBytesRecorded);

     // Закрываем файл
     mmioClose(hFile, 0);
     return TRUE;
   }
   return FALSE;
}

//---------------------------------------------------------
//  wioOutError
//  Вывод сообщения об ошибке в процессе проигрывания
//---------------------------------------------------------
void wioOutError(int rc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(waveOutGetErrorText(rc, (LPSTR)szBuf, MAXERRORLENGTH) ==
    MMSYSERR_BADERRNUM)
  {
    lstrcpy(szBuf, "Unknown Error");
  }
  MessageBox(NULL, (LPSTR)szBuf,
    "Wave Error", MB_OK | MB_ICONHAND);
}

//---------------------------------------------------------
//  wioOutError
//  Вывод сообщения об ошибке в процессе записи
//---------------------------------------------------------
void wioInError(int rc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(waveInGetErrorText(rc, (LPSTR)szBuf, MAXERRORLENGTH) ==
    MMSYSERR_BADERRNUM)
  {
    lstrcpy(szBuf, "Unknown Error");
  }
  MessageBox(NULL, (LPSTR)szBuf,
    "Wave Error", MB_OK | MB_ICONHAND);
}
