// ----------------------------------------
// Приложение WAVE
// Проигрывание и запись wav-файлов на низком уровне
// ----------------------------------------

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

#include "wave.hpp"
#include "waveio.hpp"

// Идентификатор таймера
#define BEEP_TIMER 1

// Идентификатор полосы просмотра
#define ID_SCROLL 10

// Длина полосы просмотра
#define SCROLL_SIZE 400

// Прототипы функций
BOOL    InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    WAVELoad(LPWAVEIOCB lpwiocb);

// Глобальные переменные
HWAVEOUT hWaveOut;
WAVEIOCB waveiocbOut;
HWAVEIN  hWaveIn;
WAVEIOCB waveiocbIn;
int      nMode = MODE_STOP;
MMTIME   mmtimeIn, mmtimeOut;
BOOL     fNeedSave = FALSE;
BOOL     fFileLoaded = FALSE;
int      nPosition;
HWND     hScroll;
char const szClassName[]   = "WaveClass";
char const szWindowTitle[] = "Wave Player/Recorder";
HINSTANCE  hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  if(hPrevInstance)
    return FALSE;

  if(!InitApp(hInstance))
    return FALSE;

  hInst = hInstance;

  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // размеры и расположение окна
    CW_USEDEFAULT,       
    450, 120, 0, 0, hInstance, NULL);
                       
  if(!hwnd)
    return FALSE;

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации

  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APPICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  int rc;

  switch (msg)
  {

// ------------------------------------------------------------
// WM_CREATE
// Создание главного окна приложения
// ------------------------------------------------------------
    case WM_CREATE:
    {
       nMode       = MODE_STOP;
       fNeedSave   = FALSE;
       fFileLoaded = FALSE;
       hWaveIn     = NULL;
       hWaveOut    = NULL;

       // Создаем таймер
       SetTimer(hwnd, BEEP_TIMER, 100, NULL);

       // Создаем полосу просмотра
       hScroll = CreateWindow("scrollbar", NULL,
         WS_CHILD | WS_VISIBLE | SBS_HORZ,
         10, 40, SCROLL_SIZE, 15, hwnd,
         (HMENU) ID_SCROLL, hInst, NULL);

       // Устанавливаем текущую позицию
       nPosition = 0;

       // Устанавливаем минимальное и максимальное
       // значения для полосы просмотра
       SetScrollRange(hScroll, SB_CTL, 1, SCROLL_SIZE, TRUE);

       // Устанавливаем ползунок
       SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
       return 0;
    }

// ------------------------------------------------------------
// WM_PAINT
// Рисование в окне
// ------------------------------------------------------------
    case WM_PAINT:
    {
      // Получаем контекст отображения для
      // рисования во внутренней области окна 
      hdc = BeginPaint(hwnd, &ps);

      // Отображаем текущий режим работы
      if(nMode == MODE_STOP)
        TextOut(hdc, 10, 10, "Остановлено", 11);
      else if(nMode == MODE_RECORDING)
        TextOut(hdc, 10, 10, "Идет запись...", 14);
      else if(nMode == MODE_PLAYING)
        TextOut(hdc, 10, 10, "Идет проигрывание...", 20);
      else if(nMode == MODE_RECORDINGPAUSED)
        TextOut(hdc, 10, 10, "Запись остановлена", 18);
      else if(nMode == MODE_PLAYINGPAUSED)
        TextOut(hdc, 10, 10, "Проигрывание остановлено", 24);
      else
        TextOut(hdc, 10, 10, "Неправильный режим!", 19);

      // Освобождаем контекст отображения
      EndPaint(hwnd, &ps);
      return 0;
    }

// ------------------------------------------------------------
// WM_COMMAND
// Обработка сообщений от меню
// ------------------------------------------------------------
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // -------------------------------------------------
        // Строка "About" меню "Help"
        // -------------------------------------------------
        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "Wave, v.1.0\nWave Player/Recorder\n"
            "(C) Frolov A.V., 1994",
	    "About Wave", MB_OK | MB_ICONINFORMATION);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Open" меню "File"
        // -------------------------------------------------
        case CM_FILEOPEN:
        {
          // Если файл уже был загружен, возвращаем WAVE в
          // исходное состояние
          if(fFileLoaded)
          {
            if(hWaveOut)
            {
              // Останавливаем устройство вывода
              rc=waveOutReset(hWaveOut);
              if(rc) wioOutError(rc);

              // Закрываем устройство вывода
              rc=waveOutClose(hWaveOut);
              if(rc) wioOutError(rc);
            }

            // Освобождаем буфера, предназначенные для вывода
            GlobalFreePtr(waveiocbOut.lpWaveHdr);
            GlobalFreePtr(waveiocbOut.lpData);
            GlobalFreePtr(waveiocbOut.lpFmt);
          }

          // Загружаем новый файл
          if(!WAVELoad(&waveiocbOut))
            return 0;

          // Устанавливаем флаг загрузки файла
          fFileLoaded = TRUE;
          return 0;
        }

        // -------------------------------------------------
        // Строка "Play!"
        // Проигрывание загруженного wav-файла
        // -------------------------------------------------
        case CM_CTLPLAY:
        {
          if(nMode == MODE_STOP)
          {
            // Если файл загружен и не проигрывается,
            // запускаем проигрывание файла
            if((fFileLoaded == TRUE) && (nMode != MODE_PLAYING))
            {
              // Новый режим
              nMode = MODE_PLAYING;

              // Перерисовываем окно для отображения строки,
              // соответствующей новому режиму
              InvalidateRect(hwnd, NULL, TRUE);

              // Проигрываем файл
              rc=wioPlay(&waveiocbOut, hwnd);
              if(rc) wioOutError(rc);
            }
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Record!"
        // Запись wav-файла
        // -------------------------------------------------
        case CM_CTLRECORD:
        {
          // Запись возможна только из состояния останова
          if(nMode == MODE_STOP)
          {
            nMode = MODE_RECORDING;
            InvalidateRect(hwnd, NULL, TRUE);

            // Требуется сохранить записанный файл
            fNeedSave = TRUE;

            // Запись файла
            wioRecord(&waveiocbIn, hwnd);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Stop!"
        // Останов проигрывания или записи wav-файла
        // -------------------------------------------------
        case CM_CTLSTOP:
        {
          if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED)
          {
            if(hWaveIn)
            {
              // Останавливаем запись
              rc=waveInReset(hWaveIn);
              if(rc) wioInError(rc);

              // Закрываем устройство записи
              rc=waveInClose(hWaveIn);
              if(rc) wioInError(rc);
              hWaveIn = 0;
            }
          }

          else if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED)
          {
            if(hWaveOut)
            {
              // Останавливаем проигрывание
              rc=waveOutReset(hWaveOut);
              if(rc) wioOutError(rc);

              // Закрываем устройство вывода
              rc=waveOutClose(hWaveOut);
              if(rc) wioOutError(rc);
              hWaveOut = 0;
            }
          }

          // Устанавливаем движок в начало полосы просмотра
          nPosition = 0;
          SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

          // Новый режим
          nMode = MODE_STOP;
          InvalidateRect(hwnd, NULL, TRUE);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Pause!"
        // Временный останов проигрывания или
        // полный останов записи wav-файла
        // -------------------------------------------------
        case CM_CTLPAUSE:
        {
          if(nMode == MODE_RECORDING)
          {
            // Останов записи
            rc=waveInStop(hWaveIn);
            if(rc) wioInError(rc);
            nMode = MODE_STOP;
          }

          else if(nMode == MODE_PLAYING)
          {
            // Временный останов проигрывания
            rc=waveOutPause(hWaveOut);
            if(rc) wioOutError(rc);
            nMode = MODE_PLAYINGPAUSED;
          }

          InvalidateRect(hwnd, NULL, TRUE);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Resume!"
        // Продолжение проигрывания после останова
        // -------------------------------------------------
        case CM_CTLRESUME:
        {
          if(nMode == MODE_PLAYINGPAUSED)
          {
            rc=waveOutRestart(hWaveOut);
            if(rc) wioOutError(rc);
            nMode = MODE_PLAYING;
            InvalidateRect(hwnd, NULL, TRUE);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Exit" меню "File"
        // Завершение работы приложения
        // -------------------------------------------------
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }

        default:
          return 0;
      }
    }

// ------------------------------------------------------------
// MM_WOM_DONE
// Завершение проигрывания блока
// ------------------------------------------------------------
    case MM_WOM_DONE:
    {
      nMode = MODE_STOP;
      nPosition = 0;
      SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      InvalidateRect(hwnd, NULL, TRUE);

      // Удаляем блок из очереди проигрывания
      waveOutUnprepareHeader((HWAVEOUT)wParam,
       (LPWAVEHDR)lParam, sizeof(WAVEHDR));

      // Останавливаем и закрываем устройство вывода
      waveOutReset((HWAVEOUT)wParam);
      waveOutClose((HWAVEOUT)wParam);
      hWaveOut = 0;
      return 0;
    }

// ------------------------------------------------------------
// MM_WIM_DATA
// Завершение записи блока
// ------------------------------------------------------------
    case MM_WIM_DATA:
    {
      nMode = MODE_STOP;
      nPosition = 0;
      SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      InvalidateRect(hwnd, NULL, TRUE);

      // Удаляем блок из очереди записи
      waveInUnprepareHeader((HWAVEIN)wParam,
       (LPWAVEHDR)lParam, sizeof(WAVEHDR));

      // Сохраняем записанный блок в файле
      wioFileSave("RECORDED.WAV");
      fNeedSave = FALSE;

      // Освобождаем буфера, связанные с блоком
      GlobalFreePtr(waveiocbIn.lpWaveHdr);
      GlobalFreePtr(waveiocbIn.lpData);

      // Останавливаем и закрываем устройство ввода
      waveInReset((HWAVEIN)wParam);
      waveInClose((HWAVEIN)wParam);
      hWaveIn = 0;
      return 0;
    }

// ------------------------------------------------------------
// WM_TIMER
// Сообщение от таймера
// ------------------------------------------------------------
    case WM_TIMER:
    {
      if(nMode == MODE_RECORDING)
      {
        // Определяем текущую позицию внутри проигрываемого блока
        mmtimeIn.wType = TIME_SAMPLES;
        waveInGetPosition(hWaveIn,
          (LPMMTIME)&mmtimeIn, sizeof(MMTIME));

        // Вычисляем новое положение движка полосы просмотра
        nPosition =
          ((DWORD)SCROLL_SIZE * mmtimeIn.u.sample) / MAXSAMPLES;

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 1 до SCROLL_SIZE
        if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE;
        if(nPosition < 1) nPosition = 1;

        // Устанавливаем ползунок полосы просмотра
        // в соответствии с новым значением текущей позиции
        SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      }

      else if(nMode == MODE_PLAYING)
      {
        // Определяем текущую позицию внутри записываемого блока
        mmtimeOut.wType = TIME_SAMPLES;
        waveOutGetPosition(hWaveOut,
        (LPMMTIME)&mmtimeOut, sizeof(MMTIME));

        // Вычисляем новое положение движка полосы просмотра
        nPosition =
          ((DWORD)SCROLL_SIZE * mmtimeOut.u.sample) /
           (waveiocbOut.dwDataSize / waveiocbOut.wBytesPerSample);

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 1 до SCROLL_SIZE
        if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE;
        if(nPosition < 1) nPosition = 1;

        // Устанавливаем ползунок полосы просмотра
        // в соответствии с новым значением текущей позиции
        SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      }
      return 0;
    }

// ------------------------------------------------------------
// WM_DESTROY
// Уничтожение главного окна приложения
// ------------------------------------------------------------
    case WM_DESTROY:
    {
      // Удаляем таймер и полосу просмотра
      KillTimer(hwnd, BEEP_TIMER);
      DestroyWindow(hScroll);

      // Если находимся в режиме записи, останавливаем
      // запись и закрываем устройство ввода
      if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED)
      {
        if(hWaveIn)
        {
          rc=waveInReset(hWaveIn);
            if(rc) wioInError(rc);
          rc=waveInClose(hWaveIn);
            if(rc) wioInError(rc);
        }
      }

      // Если запись началась, но еще не закончилась, после
      // остановки записи удаляем буфера
      if(fNeedSave)
      {
        GlobalFreePtr(waveiocbIn.lpWaveHdr);
        GlobalFreePtr(waveiocbIn.lpData);
      }

      else if(fFileLoaded)
      {
      // Если находимся в режиме проигрывания, останавливаем
      // запись и закрываем устройство вывода
        if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED)
        {
          if(hWaveOut)
          {
            rc=waveOutReset(hWaveOut);
            if(rc) wioOutError(rc);

            rc=waveOutUnprepareHeader(hWaveOut, waveiocbOut.lpWaveHdr, sizeof(WAVEHDR));
            if(rc) wioOutError(rc);

            rc=waveOutClose(hWaveOut);
            if(rc) wioOutError(rc);
          }
        }

        nMode = MODE_STOP;

        // Освобождаем буфера
        GlobalFreePtr(waveiocbOut.lpWaveHdr);
        GlobalFreePtr(waveiocbOut.lpData);
        GlobalFreePtr(waveiocbOut.lpFmt);
      }
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

