4. Проигрывание MIDI-файлов

В этой главе мы кратко расскажем вам о том, как приложения Windows могут проигрывать музыкальные файлы формата MIDI при помощи средств интерфейса MCI.

Стандарт MIDI (Musical Instrument Digital Interface - цифровой интерфейс музыкальных инструментов) был разработан давно, в 1982 году. В рамках этого стандарта определены электрические и логические спецификации (уровни сигналов, временные диаграммы и коммуникационный протокол) для подключения таких музыкальных инструментов, как синтезаторы и музыкальные клавиатуры друг к другу и к компьютеру.

Для электрического подключения используется последовательный интерфейс типа "токовая петля" со скоростью передачи данных 31250 бит в секунду. Данные передаются байтами с одним стартовым и одним стоповым битом. Подробная спецификация разъема приведена в документации на звуковой адаптер (если адаптер оснащен портом для подключения внешних устройств MIDI). Отметим, что устройства MIDI могут подключаться цепочкой, для чего на корпусе устройства обычно предусмотрены три разъема. Входной разъем обозначается MIDI In , выходной - MIDI Out . Разъем MIDI Thru предназначен для организации цепочки, он является выходным и дублирует сигналы с разъема MIDI In. Некоторые устройства, например, простые музыкальные клавиатуры, могут иметь только один входной или выходной разъем. В звуковом адаптере Sound Galaxy NX Pro предусмотрено подключение входного и выходного сигнала, причем оба они подключаются к одному разъему.

Как правило, все звуковые адаптеры оборудованы музыкальным синтезатором. Синтезаторы могут иметь базовый или расширенный уровень. Синтезатор базового уровня содержит в себе голоса трех мелодичных и трех ударных инструментов. Уровень полифонии такого синтезатора равен 6 для мелодичных и 3 для ударных инструментов. Это означает, что синтезатор базового уровня может играть одновременно 6 нот на мелодичных инструментах и 3 ноты на ударных. Синтезаторы расширенного уровня содержат в себе 9 мелодичных и 8 ударных инструментов при уровне полифонии, равном 16.

Хорошие синтезаторы содержат в себе цифровые записи (образцы) голосов музыкальных инструментов, которые модифицируются для получения нужной высоты тона и нужного уровня выходного сигнала. Более простые модели синтезируют звучание программным способом, что, однако, отдаляет качество звучания от идеала.

Для управления синтезатором, подключенным через порт MIDI, используется механизм сообщений MIDI. Сообщения могут иметь длину от одного до нескольких байт. Они передают такую информацию, как номер нажатой или отпущенной клавиши музыкальной клавиатуры или номер одного из 16 логических каналов, через которые осуществляется управление синтезатором.

Файлы в стандарте MIDI имеют расширение имени .mid и содержат заголовок и сообщения для музыкального синтезатора. Используется также стандарт RIFF. Файлы, содержащие сообщения MIDI и соответствующие стандарту RIFF, обычно создаются с расширением имени rmi. Если приложение Windows будет проигрывать такие файлы при помощи интерфейса MCI, ему не нужно знать внутренний формат файлов.

Но есть одна тонкость, связанная с использованием логических каналов и кодов инструментов.

В спецификации MIDI определены 16 логических каналов, предназначенных для адресации 16 логических синтезаторов. Каналы с номерами 13...16 используются синтезаторами базового уровня, каналы с номерами 1...10 - синтезаторами расширенного уровня. Каналы 11 и 12 не используются. Файлы MIDI содержат сообщения, предназначенные для разных каналов и для разных инструментов. Проблема заключается в том, что не все синтезаторы имеют одинаковое распределение каналов и инструментов. Самый простой способ уйти от трудностей, связанных с распределением каналов - приобретать mid-файлы, созданные специально для работы в среде Windows (авторизованные для Windows). В магазинах есть компакт-диски с многими сотнями таких файлов, причем в большинстве случаев вы можете свободно продавать эти файлы в составе своего программного обеспечения.

В нашей книге мы не будем рассматривать вопросы создания mid-файлов. Во-первых, есть много готовых, которые можно купить в магазине. Во-вторых, для создания mid-файлов требуется музыкальная клавиатура, соответствующее программное обеспечение и, разумеется, некоторые музыкальные способности. Кроме того, нам надо еще оставить место в книге для рассказа о новейшем направлении в мультимедиа - системе Microsoft Video for Windows.

А сейчас рассмотрим особенности использования интерфейса MCI для проигрывания файлов MIDI.

4.1. Интерфейс управляющих строк MCI

Для работы с музыкальным синтезатором, входящим в комплект звукового адаптера, используется драйвер mciseq.drv . Свое название он получил от слова sequencer (дословно можно перевести как "устройство задания последовательности"). Именно так в терминологии мультимедиа называется устройство, предназначенное для работы с файлами в стандарте MIDI.

При работе с mid-файлами на уровне управляющих строк MCI вы можете пользоваться практически всеми командами, рассмотренными нами в разделе, посвященном записи и воспроизведению звука при помощи устройства waveaudio (не поддерживаются команды resume, record и save). Например, следующая последовательность команд выполнит проигрывание файла canyon.mid, который поставляется в составе дистрибутива операционной системы Windows:

open g:\win\canyon.mid alias music wait
play music wait
close music

Если с помощью команды play будет предпринята попытка проиграть mid-файл, не авторизованный для Windows, на экране появится предупреждающее сообщение о том, что при стандартной конфигурации MIDI данный файл может проигрываться неправильно (рис. 4.1).

Рис. 4.1. Сообщение о попытке выполнить проигрывание файла MIDI, не авторизованного для Windows

Драйвер mciseq.drv не поддерживает следующие параметры команды set: audio all off, audio all on, audio left on, audio left off, audio right on, audio right off. Дополнительно вы можете использовать параметр time format song pointer, устанавливающий формат времени в единицах "одна шестнадцатая ноты", параметр tempo, позволяющий задать темп исполнения мелодии, и некоторые другие. Полный перечень вы сможете найти в документации, которая поставляется вместе с Microsoft SDK для Windows 3.1.

4.2. Интерфейс управляющих сообщений MCI

Использование интерфейса управляющих сообщений для проигрывания mid-файлов аналогично использованию этого интерфейса для проигрывания wav-файлов. Перечислим кратко допустимые коды управляющих сообщений и самые нужные параметры, специфические для драйвера mciseq.drv.

MCI_OPEN

Устройство sequencer открывается командой MCI_OPEN с использованием структуры MCI_OPEN_PARMS, определенной в файле mmsystem.h.

Поле lpstrDeviceType в этой структуре должно содержать указатель на строку имени устройства, или константный идентификатор устройства. Для устройства sequencer вы можете указать имя "sequencer " или константу MCI_DEVTYPE_SEQUENCER .

Через параметр lpstrElementName передается указатель на путь к проигрываемому mid-файлу.

MCI_CLOSE

Эта команда закрывает устройство. Ее необходимо выдавать после завершения работы с устройством.

MCI_PLAY

Команда MCI_PLAY предназначена для проигрывания. Для mid-файлов она используется точно таким же образом, что и для wav-файлов.

MCI_PAUSE

Команда MCI_PAUSE приостанавливает выполнение операции проигрывания.

MCI_RESUME

Эта команда не поддерживается драйвером mciseq.drv . Используйте вместо нее команду MCI_PLAY без указания позиции (для запуска проигрывания с текущей позиции).

MCI_STOP

Команда MCI_STOP останавливает выполнение операции проигрывания.

MCI_SEEK

Команда MCI_SEEK позволяет выполнять позиционирование в пределах mid-файла.

MCI_BREAK

С помощью команды MCI_BREAK указывается виртуальный код клавиши, с помощью которой можно прервать выполнение операции.

MCI_GETDEVCAPS

С помощью команды MCI_GETDEVCAPS можно определить возможности устройства.

MCI_INFO

С помощью этой команды можно получить информацию об устройстве в виде текстовой строки, такую как описание аппаратуры, имя файла, связанного с устройством.

MCI_SYSINFO

С помощью этой команды можно получить системную информацию об устройстве в виде текстовой строки.

MCI_STATUS

Команда MCI_STATUS используется для определения текущего состояния устройства.

MCI_SET

Команда MCI_SET предназначена для установки режима работы устройства.

MCI_COPY

Команда MCI_COPY предназначена для копирования данных в универсальный буфер обмена Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.

MCI_PASTE

Команда MCI_PASTE вставляет данные из Clipboard в текущий буфер устройства. Для нее, как и для команды MCI_COPY, используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.

MCI_CUT

Команда MCI_CUT удаляет данные из текущего буфера устройства и копирует их в универсальный буфер обмена Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.

MCI_DELETE

Команда MCI_DELETE удаляет данные из текущего буфера устройства без копирования их в Clipboard.

MCI_LOAD

Команда MCI_LOAD предназначена для загрузки файла.

4.3. Приложение MIDIPL

Приложение MIDIPL (рис. 4.2) демонстрирует способы использования некоторых функций MCI для проигрывания файлов MIDI.

Рис. 4.2. Приложение MIDIPL

По своей структуре оно напоминает приложение MCIWAWER, которое может проигрывать и записывать wav-файлы. Поэтому мы рассмотрим только отличия, специфические для работы с устройством sequencer.

Основной файл исходных текстов приложения MIDIPL приведен в листинге 4.1.

Листинг 4.1. Файл midipl/midipl.cpp

// ------------------------------------------------
// Приложение MIDIPL
// Проигрывание файлов MIDI
// с помощью интерфейса сообщений MCI
// ------------------------------------------------

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

#include "midipl.hpp"
#include "midiio.hpp"

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

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

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

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

// Глобальные переменные
int      nMode = MODE_STOP;
MMTIME   mmtimeOut;
BOOL     fFileLoaded = FALSE;
int      nPosition;
HWND     hScroll;
UINT     wOutDeviceID;
BYTE     szFileName[128];
DWORD    dwFileSize;

char const szClassName[]   = "MCIMIDIClass";
char const szWindowTitle[] = "MIDI Player";
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;
       fFileLoaded  = FALSE;
       wOutDeviceID = 0;

       // Создаем таймер
       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_PLAYING)
        TextOut(hdc, 10, 10, "Идет проигрывание...", 20);
      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,
            "MIDI Player, v.1.0\n"
            "(C) Frolov A.V., 1994",
            "About MIDIPL", MB_OK | MB_ICONINFORMATION);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Open" меню "File"
        // -------------------------------------------------
        case CM_FILEOPEN:
        {
          char szTitle[256];

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

          // Отображаем в заголовке окна путь к файлу
          lstrcpy(szTitle, szWindowTitle);
          lstrcat(szTitle, " - ");
          lstrcat(szTitle, szFileName);
          SetWindowText(hwnd, szTitle);

          // Если было запущено воспроизведение,
          // останавливаем его и закрываем устройство вывода
          if(wOutDeviceID)
          {
            mcimidiStop(wOutDeviceID);
            mcimidiClose(wOutDeviceID);
            wOutDeviceID = 0;

            // Новый режим
            nMode = MODE_STOP;


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

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

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

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

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

            // Если устройство не было открыто раньше,
            // открываем его
            if(!wOutDeviceID)
              wOutDeviceID = mcimidiOpen((LPSTR)szFileName);

            // Проигрываем файл
            mcimidiPlay(hwnd, wOutDeviceID);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Stop!"
        // Останов проигрывания или записи файла MIDI
        // -------------------------------------------------
        case CM_CTLSTOP:
        {
          if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED)
          {
            // Останавливаем проигрывание
            mcimidiStop(wOutDeviceID);
          }

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

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

        // -------------------------------------------------
        // Строка "Pause!"
        // Временный останов проигрывания
        // -------------------------------------------------
        case CM_CTLPAUSE:
        {
          if(nMode == MODE_PLAYING)
          {
            // Временный останов проигрывания
            mcimidiPause(wOutDeviceID);
            nMode = MODE_PLAYINGPAUSED;
          }

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

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

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

// ------------------------------------------------------------
// MM_MCINOTIFY
// ------------------------------------------------------------
    case MM_MCINOTIFY:
    {
      // Если находились в режиме воспроизведения, останавливаем
      // и закрываем устройство вывода
      if(nMode == MODE_PLAYING)
      {
        if(wOutDeviceID)
        {
          mcimidiStop(wOutDeviceID);
          mcimidiClose(wOutDeviceID);
          wOutDeviceID=0;
          nMode = MODE_STOP;
          InvalidateRect(hwnd, NULL, TRUE);
        }
      }
      return 0;
    }

// ------------------------------------------------------------
// WM_TIMER
// Сообщение от таймера
// ------------------------------------------------------------
    case WM_TIMER:
    {
      MCI_STATUS_PARMS mciStatus;
      DWORD dwPos;

      // Режим воспроизведения
      if(nMode == MODE_PLAYING)
      {
        // Определяем текущую позицию внутри блока
        mciStatus.dwItem = MCI_STATUS_POSITION;
        mciSendCommand(wOutDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus);
        dwPos = mciStatus.dwReturn;

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

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 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(fFileLoaded)
      {
        if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED)
        {
          mcimidiStop(wOutDeviceID);
          mcimidiClose(wOutDeviceID);
        }
        nMode = MODE_STOP;
      }
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Обратим ваше внимание на то, как приложение MIDIPL выполняет продолжение проигрывания после временного останова. Так как драйвер mciseq.drv не поддерживает команду MCI_RESUME, для продолжения проигрывания используется команда MCI_PLAY без указания начальной позиции. Эта команда выдается функцией mcimidiPlayCurrent, вызываемой для продолжения проигрывания с текущего места.

Определения констант для приложения MIDIPL находятся в файле midipl.hpp (листинг 4.2).

Листинг 4.2. Файл midipl/midipl.hpp

#define CM_HELPABOUT   301
#define CM_FILEEXIT    302
#define CM_FILEOPEN    303
#define CM_CTLPLAY     401
#define CM_CTLRESUME   402
#define CM_CTLPAUSE    403
#define CM_CTLSTOP     404

Файл midiio.cpp содержит определение функций, предназначенных для работы с интерфейсом MCI (листинг 4.3).

Листинг 4.3. Файл midipl/midiio.cpp

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

#include "midiio.hpp"

// Глобальные переменные
extern int  nMode;
extern int  nPosition;
extern DWORD dwFileSize;

//-----------------------------------------------------
// mcimidiOpen
// Открытие устройства вывода
//-----------------------------------------------------

UINT mcimidiOpen(LPSTR szFileName)
{
  MCI_OPEN_PARMS mciOpen;
  MCI_STATUS_PARMS mciStatus;
  DWORD dwrc;
  DWORD dwFlags;

  // Готовим блок параметров
  mciOpen.lpstrDeviceType= (LPSTR)"sequencer";
  mciOpen.lpstrElementName = (LPSTR)szFileName;
  mciOpen.dwCallback = 0;
  mciOpen.wDeviceID = 0;
  mciOpen.wReserved0 = 0;
  mciOpen.lpstrAlias = NULL;

  // Устанавливаем флаги
  dwFlags = MCI_OPEN_TYPE | 
    MCI_OPEN_ELEMENT | MCI_WAIT;

  // Открываем устройство
  dwrc = mciSendCommand(0, MCI_OPEN,
    dwFlags, (DWORD)(LPVOID)&mciOpen);
  if(dwrc)
  {
    mcimidiError(dwrc);
    return 0;
  }

  // Если устройство открыто успешно, определяем
  // длительность звучания в миллисекундах 
  else
  {
    mciStatus.dwItem = MCI_STATUS_LENGTH;
    dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_STATUS,
      MCI_STATUS_ITEM | MCI_WAIT,
      (DWORD)(LPVOID)&mciStatus);
    if(dwrc)
    {
      mcimidiError(dwrc);
      return 0;
    }

    // Сохраняем длительность звучания в глобальной
    // переменной и возвращаем идентификатор устройства вывода
    dwFileSize = mciStatus.dwReturn;
    return mciOpen.wDeviceID;
  }
}

//-----------------------------------------------------
// mcimidiPlay
// Проигрывание загруженного файла MIDI
//-----------------------------------------------------
DWORD mcimidiPlay(HWND hwnd, UINT wDeviceID)
{
  MCI_PLAY_PARMS mciPlayParms;
  DWORD dwrc;

  // Позиционирование на начало фрагмента
  dwrc = mciSendCommand(wDeviceID, MCI_SEEK,
    MCI_WAIT | MCI_SEEK_TO_START, NULL);

  // Идентификатор окна, функция которого получит
  // сообщение MM_MCINOTIFY
  mciPlayParms.dwCallback = (DWORD)hwnd;

  // Запускаем проигрывание
  dwrc=mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY,
    (DWORD)(LPVOID)&mciPlayParms);

  return dwrc;
}

//-----------------------------------------------------
// mcimidiPlayCurrent
// Проигрывание загруженного файла MIDI
// с текущей позиции
//-----------------------------------------------------
DWORD mcimidiPlayCurrent(HWND hwnd, UINT wDeviceID)
{
  MCI_PLAY_PARMS mciPlayParms;
  DWORD dwrc;

  // Идентификатор окна, функция которого получит
  // сообщение MM_MCINOTIFY
  mciPlayParms.dwCallback = (DWORD)hwnd;

  // Запускаем проигрывание
  dwrc=mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY,
    (DWORD)(LPVOID)&mciPlayParms);

  return dwrc;
}

//-----------------------------------------------------
// mcimidiStop
// Останов проигрывания загруженного файла MIDI
//-----------------------------------------------------
DWORD mcimidiStop(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_STOP, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mcimidiError(dwrc);
  }

  return dwrc;
}

//-----------------------------------------------------
// mcimidiPause
// Временный останов проигрывания загруженного файла MIDI
//-----------------------------------------------------
DWORD mcimidiPause(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mcimidiError(dwrc);
  }

  return dwrc;
}

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

  char szFile[256];
  char szFileTitle[256];
  char szFilter[256] =
         "MIDI Files\0*.mid;*.rmi\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;
}

//-----------------------------------------------------
// mcimidiClose
// Закрытие устройства вывода
//-----------------------------------------------------
void mcimidiClose(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mcimidiError(dwrc);
    return;
  }
}

//-----------------------------------------------------
// mcimidiError
// Обработка ошибок
//-----------------------------------------------------
void mcimidiError(DWORD dwrc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH))
    MessageBox(NULL, szBuf,
      "MIDIPL Error", MB_ICONEXCLAMATION);
  else
    MessageBox(NULL, "Неизвестная ошибка",
      "MIDIPL Error", MB_ICONEXCLAMATION);
}

Функция mcimidiOpen предназначена для открытия устройства sequencer. При подготовке блока параметров в поле lpstrDeviceType структуры mciOpen указано имя устройства:

mciOpen.lpstrDeviceType= (LPSTR)"sequencer";

Функция mcimidiPlayCurrent предназначена для проигрывания с текущей позиции. В отличие от функции mcimidiPlay в ней не выполняется позиционирование на начало.

Файл midiio.hpp (листинг 4.4) содержит определения констант и прототипы функций для файла midiio.cpp.

Листинг 4.4. Файл midipl/midiio.hpp

#include <windows.h>
#include <mmsystem.h>

#define MODE_STOP            0
#define MODE_PLAYING         1
#define MODE_PLAYINGPAUSED   2

UINT  mcimidiOpen(LPSTR szFileName);
BOOL  mcimidiSelectFile(LPSTR lpszFileName);
void  mcimidiClose(UINT wDeviceID);
DWORD mcimidiPlay(HWND hwnd, UINT wDeviceID);
DWORD mcimidiPlayCurrent(HWND hwnd, UINT wDeviceID);
void  mcimidiError(DWORD dwrc);
DWORD mcimidiStop(UINT wDeviceID);
DWORD mcimidiPause(UINT wDeviceID);
DWORD mcimidiResume(UINT wDeviceID);

Файл определения ресурсов midipl.rc приведен в листинге 4.5.

Листинг 4.5. Файл midipl/midipl.rc

#include "midipl.hpp"
APPICON ICON "midipl.ico"
APP_MENU MENU 
BEGIN
  POPUP "&File"
    BEGIN
      MENUITEM "&Open...", CM_FILEOPEN
      MENUITEM SEPARATOR
      MENUITEM "E&xit",    CM_FILEEXIT
    END

  MENUITEM "&Play!",     CM_CTLPLAY
  MENUITEM "&Stop!",     CM_CTLSTOP
  MENUITEM "Resu&me!",   CM_CTLRESUME
  MENUITEM "P&ause!",    CM_CTLPAUSE

  POPUP "&Help"
    BEGIN
      MENUITEM "&About...", CM_HELPABOUT
    END
END

Файл определения модуля для приложения MIDIPL приведен в листинге 4.6.

Листинг 4.6. Файл midipl/midipl.def

NAME        MIDIPL
DESCRIPTION 'Приложение MIDIPL, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   10240
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple