Untitled
4. Проигрывание MIDI-файлов
4.1. Интерфейс управляющих строк MCI
4.2. Интерфейс управляющих сообщений MCI
4.3. Приложение MIDIPL
В этой главе мы кратко расскажем вам о том, как приложения 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.
Для работы с музыкальным синтезатором, входящим в комплект звукового
адаптера, используется драйвер mciseq.drv . Свое название он получил
от слова sequencer (дословно можно перевести как "устройство
задания последовательности"). Именно так в терминологии мультимедиа
называется устройство, предназначенное для работы с файлами в
стандарте MIDI.
При работе с mid-файлами на уровне управляющих строк MCI вы можете
пользоваться практически всеми командами, рассмотренными нами
в разделе, посвященном записи и воспроизведению звука при помощи
устройства waveaudio (не поддерживаются команды resume, record
и save). Например, следующая последовательность команд выполнит
проигрывание файла canyon.mid, который поставляется в составе
дистрибутива операционной системы Windows:
open g:wincanyon.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.
Использование интерфейса управляющих сообщений для проигрывания
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 предназначена для загрузки файла.
Приложение 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.0n"
"(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 *.mid;*.rmi Any Files *.* ";
szFile[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
|