Мультимедиа для Windows© Александр Фролов, Григорий ФроловТом 15, М.: Диалог-МИФИ, 1994, 284 стр. 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 |