Программирование стратегических игр с DirectX 9.0

       

Архитектура DirectMusic


DirectX содержит два интерфейса, которые можно применять для воспроизведения звуковых файлов: DirectSound и DirectMusic. Главное различие между ними заключается в том, что DirectSound предоставляет низкоуровневый доступ к аппаратуре звуковой карты. Лично я предпочитаю использовать DirectMusic, поскольку он предоставляет более широкие возможности и достаточно быстро работает на современных процессорах. DirectMusic содержит следующие основные части:

Загрузчик

Исполнитель

Сегменты



Деструктор SoundSystem::~SoundSystem()


В следующем блоке кода представлен деструктор класса:

SoundSystem::~SoundSystem() { SAFE_RELEASE(m_pLoader); SAFE_RELEASE(m_pPerformance); }

Деструктор освобождает объекты загрузчика и исполнителя. Я использую вспомогательный макрос DirectX с именем SAFE_RELEASE. Он проверяет равно ли значение указателя NULL и, если нет, освобождает объект. Поэтому данная операция и называется безопасным освобождением. Раз в классе создаются объекты загрузчика и исполнителя, в деструкторе их следует освободить.



DirectShow


Первая вещь на которой следует остановиться — имя проекта. В отличие от первых двух проектов из этой главы, имя данного проекта начинается с префикса DShow. Я сделал это потому, что данная программа использует DirectShow а не DirectMusic. DirectShow представляет собой отдельный интерфейс DirectX предназначенный для работы с потоковой аудиовизуальной информацией в Windows. Он может применяться для воспроизведения различных форматов, в том числе AVI, MPEG, MP3 и даже WAV. Как видите, вы можете воспроизводить не только звук, но и видео, а также комбинировать оба этих способа. Это действительно замечательная возможность, открывающая дорогу к воспроизведению видеофрагментов в начале вашей игры и между уровнями.



Файл программы Main.cpp


Главная часть кода расположена в файле программы main.cpp. Пришло время открыть его. Как обычно, нашего внимания требует функция WinMain(). Она содержит стандартный код инициализации приложения Windows и несколько новых вызовов функций. Вот фрагмент кода, который должен заинтересовать вас:

// Инициализация Direct Sound bRet = bInitializeSoundSystem(hWnd); if(bRet == 0) { MessageBox(hWnd, "Initialization Failure", "Failed to initialize Direct Sound", MB_ICONEXCLAMATION | MB_OK); // Сбой в программе, выход exit(1); }

Я вызываю функцию bInitializeSoundSystem() сразу после того, как создано окно программы. Функция получает один параметр — дескриптор окна. Если функция возвращает 0, значит ее выполнение закончилось неудачно. В этом случае я вывожу на экран окно с сообщением об ошибке и завершаю работу программы после того, как пользователь щелкнет по кнопке OK. В противном случае все работает как предполагалось и выполнение кода продолжается.



Файл программы Main.cpp




Основной код программы располагается в файле main.cpp. Загрузите его сейчас и следуйте дальше. Найдите в коде функцию WinMain() и обратите внимание на следующий фрагмент:

// Воспроизведение музыки bRet = bPlayTitleMusic(); if(bRet == 0) { MessageBox(hWnd, "Initialization Failure", "Failed to initialize DirectShow", MB_ICONEXCLAMATION | MB_OK); // Сбой в программе, выход exit(1); }

В этом блоке кода вызывается функция bPlayTitleMusic(), являющаяся локальной для моей программы. Она отвечает за инициализацию DirectShow и воспроизведение файла MP3 из каталога с звуковыми файлами DirectX SDK. Давайте перейдем к этой функции.



Функция bInitializeSoundSystem()


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



Функция bPlayTitleMusic()


Эта функция в рассматриваемой программе выполняет большую часть работы. Она инициализирует COM, создает интерфейсы, загружает музыку, устанавливает темп воспроизведения и начинает проигрывание музыки. Вот как выглядит ее код:

bool bPlayTitleMusic(void) { HRESULT hr;

// Инициализация COM CoInitialize(NULL); // Создание графа CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&g_pGraph); // Запрос интерфейсов объекта g_pGraph->QueryInterface( IID_IMediaControl, (void **)&g_pMediaControl); g_pGraph->QueryInterface( IID_IMediaEvent, (void **)&g_pEvent); g_pGraph->QueryInterface( IID_IMediaSeeking, (void **)&g_pSeeking);

// Загрузка песни (вставьте имя своего файла) hr = g_pGraph->RenderFile( L"c:\\dxsdk\\samples\\media\\track3.mp3", NULL); if(hr != S_OK) { return(0); } // Установка темпа воспроизведения g_pSeeking->SetRate(1); // Воспроизведение музыки g_pMediaControl->Run(); // Установка флага воспроизведения g_bBackgroundMusicActive = 1; return(1); }



Функция SoundSystem::hrInitSoundSystem()

Первая действительно важная функция— это функция инициализации. Вот ее код:

HRESULT SoundSystem::hrInitSoundSystem(void) { HRESULT hResult; IDirectMusicAudioPath8 *path;

// Инициализация COM CoInitialize(NULL);

// Создание загрузчика if(FAILED(hResult = CoCreateInstance( CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**)&m_pLoader))) { return(SOUNDERROR_MUSICLOADER); }

// Создание исполнителя if(FAILED(hResult = CoCreateInstance( CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**)&m_pPerformance))) { return(SOUNDERROR_MUSICPERFORMANCE); }

// Инициализация аудиосистемы if(FAILED(hResult = m_pPerformance->InitAudio( NULL, NULL, m_hWnd, DMUS_APATH_DYNAMIC_STEREO, 4, DMUS_AUDIOF_ALL, NULL ))) { return(SOUNDERROR_INITAUDIO); }

// Получение пути по умолчанию if(FAILED(m_pPerformance->GetDefaultAudioPath(&path))) return(SOUNDERROR_PATH);

// Установка уровня громкости if(FAILED(path->SetVolume(0, 0))) return(SOUNDERROR_VOLUME);

return(S_OK); }

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

ПРИМЕЧАНИЕ

Вы не обязаны вызывать здесь функцию установки уровня громкости, но я предпочитаю при инициализации аудиосистемы задать и уровень громкости по умолчанию. Кроме того, вы можете удалить код инициализации COM, если планируете инициализировать COM где-то в другом месте вашей программы.

Функция SoundSystem::hrLoadSound()


Следующая функция в списке загружает звуковые данные. Вот как выглядит ее код:

HRESULT SoundSystem::hrLoadSound(char *szname,GameSound *gs) { WCHAR szWideFileName[512];

// Проверяем инициализирована ли аудиосистема if(!m_pLoader) return(SOUNDERROR_MUSICLOADER);

if(!m_pPerformance) return(SOUNDERROR_MUSICPERFORMANCE);

// Очищаем звуковые данные, если они существуют if(gs->m_pSound) { gs->m_pSound->Unload(m_pPerformance); gs->m_pSound->Release(); gs->m_pSound = NULL; }

// Копируем имя файла DXUtil_ConvertGenericStringToWideCch( szWideFileName, szname, 512);

// Загружаем звуковые данные из файла if (FAILED(m_pLoader->LoadObjectFromFile ( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8, szWideFileName, (LPVOID*) &gs->m_pSound ))) { return(SOUNDERROR_LOAD); }

// Устанавливаем указатель на исполнителя в объекте звукового фрагмента gs->m_pPerformance = m_pPerformance;

// Загружаем данные if (FAILED (gs->m_pSound->Download(m_pPerformance))) { return(SOUNDERROR_DOWNLOAD); }

return(S_OK); }

В первой части кода функции выполняется проверка того, равны ли значения указателей на интерфейсы загрузчика и исполнителя NULL или нет. Если хотя бы один из указателей равен NULL, функция возвращает соответствующий код ошибки, сообщая о возникщей проблеме.

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

Санитарные проверки завершены и пора заняться чем-нибудь более существенным. Далее в коде вызывается вспомогательная функция DirectX с именем DXUtil_ConvertGenericStringToWideCch(). Поскольку выполняющие загрузку звуковых данных функции DirectX требуют, чтобы имя файлов было представлено строкой 16-разрядных символов, необходимо преобразовать переданную в параметре строку с именем файла в строку 16-разрядных символов. Вызов этой функции прямолинеен, так что я полагаю, что здесь код говорит сам за себя.

После того, как имя файла преобразовано, можно спокойно переходить к функции загрузки. Чтобы загрузить реальные звуковые данные я вызываю функцию LoadObjectFromFile(). Как помните, ранее в этой главе я говорил, что данная функция загружает звуковые данные из файла и сохраняет их в объекте звукового сегмента. В данном случае я сохраняю данные сегмента в сегменте на который указывает член данных объекта звукового фрагмента m_pSound.

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

И, наконец, я загружаю данные в только что установленный объект исполнителя. Теперь звуковые данные готовы для воспроизведения.



Функция SoundSystem::hrPlaySound()


Раз уж мы заговорили о воспроизведении, пришла пора показать вам код функции воспроизведения звука.

HRESULT SoundSystem::hrPlaySound(GameSound *gs) { // Проверяем наличие объекта исполнителя if(!m_pPerformance) return(SOUNDERROR_MUSICPERFORMANCE);

// Проверяем наличие звукового сегмента if(!gs->m_pSound) return(SOUNDERROR_NOSEGMENT);

// Воспроизводим звуковой сегмент if(FAILED (m_pPerformance->PlaySegmentEx( gs->m_pSound, NULL, NULL, DMUS_SEGF_DEFAULT | DMUS_SEGF_SECONDARY, 0, NULL, NULL, NULL ))) return(SOUNDERROR_PLAYFAIL);

return(S_OK); }

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

Затем я вызываю функцию PlaySegmentEx(), которая выполняет всю черновую работу по воспроизведению звуковых данных. Поскольку функция требует, чтобы ей указали воспроизводимый звуковой сегмент, я передаю ей указатель на сегмент из объекта звукового фрагмента.



Функция vCheckMusicStatus()


Функция vCheckMusicStatus() проверяет завершено ли фоновое воспроизведение музыки. Если да, музыка перематывается к началу и воспроизведение запускается по новой. Вот как выглядит код функции:

void vCheckMusicStatus(void) { long evCode;

// Проверка кода события g_pEvent->WaitForCompletion(0, &evCode); // Если музыка закончилась, запустить ее заново if(evCode == EC_COMPLETE) { // Устанавливаем начальную позицию в 0 LONGLONG lStartPos = 0; // Останавливаем музыку g_pMediaControl->Stop(); // Устанавливаем позиции g_pSeeking->SetPositions( &lStartPos, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); // Запускаем музыку g_pMediaControl->Run(); } }



Функция vPlaySound()


Пример программы обрабатывает события левой кнопки мыши и вызывает при каждом событии функцию vPlaySound(). Обработкой событий занимается функция fnMessageProcessor(), но представляющий интерес код выполняется в функции воспроизведения звука. Давайте взглянем на код этой функции:

void vPlaySound(void) { // Воспроизведение звукового сегмента g_pPerformance->PlaySegmentEx( g_pSound, NULL, NULL, DMUS_SEGF_DEFAULT | DMUS_SEGF_SECONDARY, 0, NULL, NULL, NULL ); }

Функция vPlaySound() очень проста. Фактически она состоит из единственного вызова функции IDirectMusicPerformance8::PlaySegmentEx().



Граф фильтров


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

Чтение данных из файла.

Декодирование потоковых данных.

Захват видео.

Передача данных системной аппаратуре.

Например, граф фильтров может читать файл MP3 и вормировать звук для его вывода аудиооборудованием. Эти действия показаны на рис. 7.5.


Рис. 7.5. Граф фильтров MP3

Как видно на рис. 7.5, граф фильтров читает данные из файла MP3, декодирует их, а затем отправляет аудиоаппаратуре для воспроизведения. Рабочей лошадкой индустрии фильтров в DirectShow является интерфейс IGraphBuilder. В таблице 7.6 перечислены входящие в этот интерфейс функции.

Таблица 7.6. Методы интерфейса IGraphBuilder

Метод Описание
Abort Сообщает графу о необходимости прекратить текущую операцию.
AddSourceFilter Добавляет фильтр источника.
Connect Соединяет два контакта.
Render Добавляет фильтр к выходному контакту.
RenderFile Загружает файл для воспроизведения. Я использую этот метод в своем примере для загрузки MP3-файла.
SetLogFile Устанавливает обработчик для файла журналирования выходной информации.
ShouldOperationContinue Сообщает должна ли продолжаться операция. Это очень странная функция, которую вам никогда не придется вызывать.



Инициализация аудиосистемы


В отличие от загрузчика, исполнитель требует, чтобы после успешного создания интерфейса была выполнена его инициализация. Эта задача осуществляется функцией IDirectMusicPerformance8::InitAudio(), которая инициализирует исполнителя и устанавливает для него аудио-путь. Вот как выглядит прототип функции:

HRESULT InitAudio( IDirectMusic** ppDirectMusic, IDirectSound** ppDirectSound, HWND hWnd, DWORD dwDefaultPathType, DWORD dwPChannelCount, DWORD dwFlags, DMUS_AUDIOPARAMS *pParams );

Первый параметр, ppDirectMusic, позволяет функции возвратить созданный интерфейс DirectMusic. Если значение этого параметра равно NULL, то интерфейс DirectMusic создается и используется внутри объекта исполнителя. Я предпочитаю использовать NULL, поскольку это упрощает код.

Второй параметр предназначен для тех же целей, что и первый, за исключением того, что от хранит или возвращает интерфейс DirectSound. Для этого параметра я также предпочитаю использовать значение NULL.

Третий параметр, hWnd, предназначен для передачи дескриптора того окна для которого создается интерфейс DirectSound. Если значение параметра равно NULL, используется фоновое окно. Я предпочитаю использовать здесь NULL.

Четвертый параметр, dwDefaultPathType, задает тип аудио-пути по умолчанию. Возможные значения перечислены в таблице7.4.

Таблица 7.4. Типы аудио-пути

Значение Описание
DMUS_APATH_DYNAMIC_3D Трехмерный звук
DMUS_APATH_DYNAMIC_MONO Монофонический звук
DMUS_APATH_DYNAMIC_STEREO Стереофонический звук
DMUS_APATH_SHARED_STEREOPLUSREVERB Стереофонический звук с эхом

Из доступных типов я обычно использую DMUS_APATH_DYNAMIC_STEREO поскольку он предоставляет возможности, необходимые для стереофонического звукового сопровождения.

Пятый параметр, dwPChannelCount, задает количество используемых в аудио пути каналов исполнителя. В рассматриваемом примере я использую четыре канала.

Шестой параметр, dwFlags, позволяет вам задать набор функциональных возможностей, которые вы хотите видеть в объекте исполнителя. Доступные флаги и их назначение описаны в таблице 7.5.


Таблица 7.5. Флаги функциональных возможностей исполнителя

Значение Описание
DMUS_AUDIOF_3D Трехмерные буферы
DMUS_AUDIOF_ALL Все возможности
DMUS_AUDIOF_BUFFERS Множественные буферы
DMUS_AUDIOF_DMOS Дополнительные DMO (DirectX Media Object)
DMUS_AUDIOF_EAX Эффекты EAX
DMUS_AUDIOF_ENVIRON Моделирование среды
DMUS_AUDIOF_STREAMING Изменяющиеся формы волн
В рассматриваемом примере я использую флаг DMUS_AUDIOF_ALL. Он указывает объекту исполнителя использовать любые доступные возможности. Это удобный вариант, позволяющий пользователю отключить отдельные возможности, которые могут привести к снижению быстродействия.

Седьмой параметр, pParams, позволяет задать желаемые аудио параметры в виде структуры данных DMUS_AUDIOPARAMS. Я обычно использую значения параметров по умолчанию и указываю здесь NULL.

Функция инициализации аудиосистемы выполнила свою работу. Последний шаг, необходимый для инициализации интерфейса исполнителя — вызов функции IDirectMusicPerformance8::GetDefaultAudioPath(), возвращающей аудио-путь по умолчанию, созданный функцией инициализации аудиосистемы. Аудио-путь требуется для установки уровня громкости. Если вы не желаете связываться с регулировкой громкости, можно пропустить этот этап.

Вот как выглядит код, описываемый в приведенном выше тексте:

// Инициализация аудиосистемы if(FAILED(hResult = g_pPerformance->InitAudio( NULL, NULL, hWnd, DMUS_APATH_DYNAMIC_STEREO, 4, DMUS_AUDIOF_ALL, NULL ))) { return(0); } // Получение пути по умолчанию if(FAILED(g_pPerformance->GetDefaultAudioPath(&dmAudioPath))) return(0);


Инициализация DirectShow


Первая вещь, которую делает функция,— инициализация COM. Это необходимый этап, поскольку DirectShow использует COM-интерфейсы.

Следующий шаг к небесам DirectShow — создание объекта графа. Я выполняю это с помощью вызова функции CoCreateInstance(). Интерфейс IGraphBuilder использует CLSID CLSID_FilterGraph и идентификатор интерфейса IID_IGraphBuilder. Указатель на интерфейс сохраняется в глобальной переменной с именем g_pGraph.

Теперь, когда у вас есть интерфейс построителя графов, вы можете создать другие интерфейсы, отправив запрос построителю графов. Это делается с помощью функции IGraphBuilder::QueryInterface(). Мы создаем три интерфейса: IID_IMediaControl, IID_IMediaEvent и IID_IMediaSeeking. После создания указатель на каждый из интерфейсов сохраняется в глобальных переменных, о которых я упоминал ранее.

К данному моменту вы инициализировали COM, создали объект графа и необходимые для программы вспомогательные интерфейсы. Это основные действия инициализации, необходимые для воспроизведения файлов MP3. Осталось только загрузить файл с музыкой, установить темп воспроизведения и начать воспроизведение. Перед тем, как идти дальше, взгляните на рис. 7.6.


Рис. 7.6. Этапы инициализации DirectShow

На рис. 7.6 показана взаимосвязь между интерфейсом управления аудиовизуальным потоком, интерфейсом событий аудиовизуального потока, интерфейсом позиционирования аудиовизуального потока и интерфейсом графа фильтров. Также видна связь между функциями воспроизведения и интерфейсом управления аудиовизуальным потоком и между функцией установки темпа и интерфейсом позиционирования аудиовизуального потока. Позднее вы увидите как в эту большую картину вписывается интерфейс событий аудиовизуального потока.



Инициализация класса звуковой системы


Не будем тратить бумагу и рубить больше деревьев, чем требуется, так что без лишних проволочек перейдем к коду функции WinMain() в файле main.cpp. Там вы увидите вызов функции с именем bInitializeSoundSystem(), которая написана специально для данного примера. Вот ее код:

bool bInitializeSoundSystem(void) { HRESULT hr;

// Выделение памяти для звуковых фрагментов g_sndButton = new GameSound; g_sndButtonOver = new GameSound;

// Инициализация звуковой системы g_SoundSys.hrInitSoundSystem();

// Загрузка звуковых фрагментов hr = g_SoundSys.hrLoadSound("button.wav", g_sndButton); if(hr == SOUNDERROR_LOAD) { return(0); }

hr = g_SoundSys.hrLoadSound( "button_over.wav", g_sndButtonOver); if(hr == SOUNDERROR_LOAD) { return(0); }

// Успешное завершение return(1); }

В программе выполняется загрузка двух звуковых файлов: button_over.wav и button.wav. Поскольку у нас два звуковых файла, нам необходимо два объекта GameSound. Они присутствуют в виде переменных g_sndButton и g_sndButtonOver. Каждая из них является объектом GameSound и объявлена в заголовочном файле main.h. Первым действием является выделение памяти для двух объектов звуковых фрагментов. Я делаю это с помощью оператора new.

Объекты звуковых фрагментов не слишком полезны без звуковой системы, поэтому я объявляю в заголовочном файле main.h указатель на объект звуковой системы с именем g_SoundSys. Чтобы этим объектом можно было воспользоваться, его следует инициализировать, для чего я вызываю метод hrInitSoundSystem(). Данный вызов инициализирует DirectSound и подготавливает систему к воспроизведению звука.

Затем я дважды вызываю метод класса звуковой системы hrLoadSound(). В параметрах этих вызовов я передаю два объекта звуковых фрагментов, чтобы они были заполнены данными из указанных файлов WAV. Вы можете спокойно заменить загружаемые здесь WAV-файлы на свои собственные.

Как только работа функций загрузки звуковых данных успешно завершена, программе возвращается значение 1, свидетельствующее об успешном завершении инициализации. Если в ходе инициализации произошел сбой, возвращается 0, что говорит о наличии ошибки.

Итак, мы разобрали этапы успешной инициализации звуковой системы.



Исполнитель DirectMusic


Подобно тому, как музыкант исполняет произведение, интерфейс исполнителя в DirectMusic отвечает за исполнение музыки. Он управляет воспроизведением, работой с сообщениями, назначением каналов и контролем времени. Если говорить в двух словах, вы только создаете в вашей игре один из этих интерфейсов.

Вся работа исполнителя поддерживается единственным интерфейсом с именем IDirectMusicPerformance8. Он является рабочей лошадкой DirectMusic; поэтому в нем присутствуют десятки функций. Перечисление функций приведено в таблице 7.2.

Таблица 7.2. Методы интерфейса IDirectMusicPerformance8

Метод Описание
AddNotificationType Добавляет тип уведомления.
AddPort Назначает исполнителю порт.
AdjustTime Смещает время исполнителя вперед или назад.
AllocPMsg Выделяет память для сообщения.
AssignPChannel Назначает канал исполнителя.
AssignPChannelBlock Назначает блок из 16 каналов.
ClonePMsg Копирует сообщение исполнителя.
CloseDown Закрывает объект исполнителя.
CreateAudioPath Создает аудио-путь (audio path).
CreateStandardAudioPath Создает аудио-путь со стандартными параметрами.
DownloadInstrument Загружает инструмент DLS.
FreePMsg Освобождает занятую сообщением память.
GetBumperLength Возвращает время между помещением сообщения в буфер и началом его обработки.
GetDefaultAudioPath Возвращает аудио-путь по умолчанию.
GetGlobalParam Возвращает глобальные значения исполнителя.
GetGraph Возвращает инструментальный граф (toolgraph).
GetLatencyTime Возвращает время, необходимое исполнителю на обработку звука и вывод его на динамики.
GetNotificationPMsg Возвращает сообщение уведомления.
GetParam Возвращает параметры дорожки.
GetParamEx Возвращает параметры дорожки. Поддерживает саморегулируемые сегменты.
GetPrepareTime Возвращает латентность дорожки.
GetQueueTime Возвращает время, когда сообщения могут быть вытеснены.
GetResolvedTime Преобразует время в предел.
GetSegmentState Возвращает состояние текущего сегмента.
GetTime Возвращает время исполнителя.
InitAudio Инициализирует исполнителя.
Invalidate Вытесняет все сообщения.
IsPlaying Проверяет, воспроизводится ли текущий сегмент.
MIDIToMusic Преобразует значение ноты MIDI в значение DirectMusic.
MusicToMIDI Преобразует значение DirectMusic в значение MIDI.
MusicToReferenceTime Преобразует MUSIC_TIME в REFERENCE_TIME.
PChannelInfo Возвращает информацию о канале.
PlaySegment Воспроизводит сегмент.
PlaySegmentEx Воспроизводит сегмент с дополнительными параметрами.
ReferenceToMusicTime Преобразует REFERENCE_TIME в MUSIC_TIME.
RemoveNotificationType Удаляет тип уведомления.
RemovePort Удаляет порт.
RhythmToTime Преобразует время ритма во время музыки.
SendPMsg Отправляет сообщение.
SetBumperLength Устанавливает интервал между помещением сообщения в буфер и его обработкой.
SetDefaultAudioPath Устанавливает аудио-путь по умолчанию. Установленный путь становится активным.
SetGlobalParam Устанавливает глобальные значения.
SetGraph Устанавливает инструментальный граф.
SetNotificationHandle Устанавливает обработчик события.
SetParam Устанавливает данные дорожки.
SetPrepareTime Устанавливает время между отправкой сообщения и воспроизведением звука.
Stop Останавливает воспроизведение сегмента.
StopEx Останавливает сегмент или аудио-путь.
TimeToRhythm Преобразует время музыки во время ритма.

Ничего себе. Какой огромный список функций. Он не должен подавлять вас; я использую в своих программах лишь несколько из перечисленных функций. Представьте себе, вся эта функциональность доступна вам абсолютно бесплатно!



Использование класса звуковой системы в меню


Вы помните программу с меню игры Battle Armor, которую я описывал в 6 главе? Пришло время добавить к ней звуки и музыку. Откройте находящийся на компакт-диске проект с именем D3D_MenuSounds и следуйте за мной. Вместо того, чтобы вываливать на вас новые курганы кода, я просто проведу обзор внесенных изменений с высоты птичьего полета.

В файл Main.h я добавил директиву включения заголовочного файла класса звуковой системы. Кроме того, я создал глобальный объект класса звуковой системы и несколько объектов звуковых фрагментов. Все это показано на рис. 7.13.


Рис. 7.13. Структура реализации звуковой системы в заголовочном файле проекта D3D_MenuSounds

В файл Main.cpp были добавлены вызовы функций для инициализации звуковой системы, загрузки звуковых файлов и их воспроизведения. Данные изменения иллюстрирует рис. 7.14.


Рис. 7.14. Структура реализации звуковой системы в главном файле проекта D3D_MenuSoundsSound

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

Чтобы добавить воспроизведение файлов MP3 я просто скопировал в программу работы с меню функции bPlayTitleMusic(), vStopTitleMusic() и vCheckMusicStatus(). Эти действия и добавление вызова, начинающего воспроизведение музыки в код инициализации и составляют весь секрет трюка.

Если вы еще не сделали это, запустите программу D3D_MenuSounds и пощелкайте по разным кнопкам меню. Музыка MP3 воспроизводится в фоновом режиме, а при щелчке по некоторым кнопкам меню воспроизводится WAV-файл. Обратите внимание, что звук в WAV-файле достаточно тихий и вам, возможно, придется прислушаться, чтобы расслышать его на фоне музыки. Я советую вам попробовать поместить в программу свои собственные музыку и звуки (ха, вы можете добавить даже несколько звуков, чтобы закрепить полученные навыки).

Вот и все, что я собирался рассказать вам о реализации звукового сопровождения и воспроизведении MP3-файлов в программах. Конечно, есть еще масса вещей, которые придется учесть, например, определить какой звук воспроизводить и когда. Но об этом мы поговорим в другой раз.

netlib.narod.ru< Назад | Оглавление | Далее >



Как инициализировать COM


Первая вещь, которую должен сделать код,— инициализация COM. DirectX использует COM-интерфейсы, так что это неизбежное зло (или добро). Если вы не знакомы с COM, я рекомендую вам пойти в любимый книжный магазин и посмотреть несколько книг, посвященных этой технологии. Вызов для инициализации выглядит следующим образом:

CoInitialize(NULL);

Исключительно просто, правда? К счастью, это все, что требуется сделать для инициализации COM. После этого вы можете создавать интерфейсы DirectX.



Как регулировать громкость


Как я намекал раньше, интерфейс IDirectMusicAudioPath8 позволяет вам регулировать уровень громкости. Для этого предназначена функция SetVolume(), прототип которой выглядит следующим образом:

HRESULT SetVolume( long lVolume, DWORD dwDuration );

Первый параметр, lVolume, устанавливает желаемый уровень громкости в сотнях децибел. Допустимы значения от –9600 до 0. Значение 0 соответствует максимальной громкости.

Второй параметр, dwDuration, задает период времени за который осуществляется изменение громкости. Если его значение равно 0, система изменит громкость как только это будет возможно.

Вот как выглядит используемый в примере код:

// Установка громкости if(FAILED(dmAudioPath->SetVolume(0,0))) return(0);



Как воспроизвести файл MIDI


Вы когда-нибудь хотели воспроизводить в вашей игре MIDI-файлы? Если да, то вы попали по правильному адресу. К счастью, воспроизводить MIDI-файлы очень просто. Фактически, если вы читали предыдущий раздел, то уже знаете все, что необходимо! Абсолютно верно — воспроизведение MIDI-файлов осуществляется точно также, как воспроизведение WAV-файлов. Я пошел дальше и создал включенный в сопроводительные файлы проект с именем DMusic_PlayMIDI. Загрузите его, скомпилируйте и выполните. Вы увидите окно, изображенное на рис. 7.3.


Рис. 7.3. Окно программы DMusic_PlayMIDI

Спорим, что программа выглядит знакомо? Я сделал лишь несколько косметических изменений в программе воспроизведения WAV-файлов. Например, я изменил имя воспроизводимого файла на c:\dxsdk\samples\media\canyon.mid. Если у вас DirectX SDK располагается в другой папке, вам необходимо изменить путь к файлу и заново откомпилировать программу. Вы также можете указать имя файла и путь для любого вашего MIDI-файла. (Я не включил никаких MIDI-файлов в сопроводительные файлы к книге потому что у меня нет лицензионного программного обеспечения для их создания. Может быть в следующий раз.)

netlib.narod.ru< Назад | Оглавление | Далее >



в вашей игре было реализовано


Хотите ли вы, чтобы в вашей игре было реализовано воспроизведение файлов MP3? Если да, этот раздел для вас. К сожалению, воспроизведение файлов MP3 требует полностью нового набора интерфейсов и функций. Разве не удивляет такой быстрый переход от радости к сожалениям? Такова жизнь, мой друг.

На тот случай, если последние несколько лет вы провели на необитаемом острове, сообщу, что MP3— это формат для хранения звуковых данных с высокой степенью сжатия. Большинство людей использует его для песен, но он также подходит и для речи и звуковых эффектов. Единственный недостаток формата MP3 — большая, по сравнению с другими форматами, нагрузка на процессор при воспроизведении. Учитывая производительность современных процессоров, эта особенность не представляет большой проблемы, но ее все же следует иметь в виду.

В сопроводительные файлы к этой книге включен проект с названием DShow_PlayMP3. На рис. 7.4 показано окно, выводимое этой программой.



Рис. 7.4. Окно программы DShow_PlayMP3

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

Теперь загрузите проект DShow_PlayMP3, чтобы иметь возможность идти дальше. Я рекомендую вам скомпилировать и запустить программу, чтобы услышать результат ее работы. Если вы ничего не услышали, проверьте строку, в которой указано имя файла c:\dxsdk\samples\media\track.mp3. Если в указанном каталоге у вас нет MP3-файла, скорректируйте путь, чтобы он указывал на любой существующий в вашей системе файл MP3. Несколько пригодных для воспроизведения файлов входят в DirectX SDK.


Как воспроизвести файл WAV


Я не идиот— по крайней мере, моя жена не называет так меня в открытую, — так что держу пари, что вы главным образом хотите узнать о том, как воспроизводить файлы WAV. Я не могу придумать лучшего способа обучения, чем замечательный мир исходных кодов. Так что загружайте проект DMusic_PlaySound из сопроводительных файлов к книге.

Программа DMusic_PlaySound демонстрирует как инициализировать интерфейсы исполнителя и загрузчика, загрузить сегмент и воспроизвести его.



Как загрузить сегмент


Теперь вы должны загрузить сегмент в объект исполнителя. Эту задачу выполняет функция IDirectMusicSegment8::Download(), прототип которой выглядит так:

HRESULT Download( IUnknown* pAudioPath );

Как приятно и просто. Всего один параметр, который является указателем на интерфейс, в который загружается сегмент. Я в данном параметре передаю указатель на объект исполнителя g_pPerformance.

Взгляните на рис.7.2, чтобы увидеть описанные к данному моменту этапы.


Рис. 7.2. Этапы инициализации DirectMusic

На рис. 7.2 видно, как вы инициализировали COM, создали загрузчик, создали исполнителя, инициализировали аудиосистему, получили аудио-путь по умолчанию, установили уровень громкости, загрузили файл и, наконец, загрузили сегмент WAV. Этот набор шагов будет повторяться каждый раз, когда вы будете использовать в своих программах DirectMusic, так что запомните его (или по крайней мере, заложите эту страницу).



Класс звуковой системы


Сперва я опишу структуру класса звуковой системы, которая показана на рис.7.8.


Рис. 7.8. Структура класса звуковой системы

Обратите внимание, что на рисунку показаны три главных метода класса и два главных члена данных. В классе также есть обычные конструктор и деструктор, но на рисунке они не показаны, поскольку присутствуют в любом классе.

Три метода называются hrInitSoundSystem(), hrLoadSound() и hrPlaySound(). Достаточно прямолинейно, правда? Функция инициализации вызывается один раз для каждого экземпляра игры. Поскольку у вас должен быть только один экземпляр, это означает, что вы один раз вызываете функцию и она делает всю необходимую работу. Функция загрузки звука должна вызываться один раз для каждого звукового файла. Нет никакой необходимости загружать один и тот же звук несколько раз, если только вы действительно не хотите этого по каким-то причинам. Функция воспроизведения звука может и, возможно, будет, вызываться несколько раз для одного и того же звука. Нет никаких ограничений того, сколько раз можно воспроизводить звук.

Два основных члена данных, m_pLoader (IDirectMusicLoader8) и m_pPerformance (IDirectMusicPerformance8), предоставляют классу необходимые интерфейсы объекта. Как вы, возможно, помните, загрузчик отвечает за загрузку звуковых файлов, а объект исполнителя осуществляет воспроизведение звука.



Определение класса звукового фрагмента


Я создал класс GameSound потому, что вам требуется только один объект исполнителя и один объект загрузчика, но несколько сегментов. Данный класс содержит реальные данные звукового сегмента для отдельного звукового фрагмента. Этот класс ни что иное, как простое хранилище звуковой информации. Вот как выглядит заголовок класса:

class GameSound { public: IDirectMusicSegment8 *m_pSound; IDirectMusicPerformance8 *m_pPerformance; ~GameSound(); GameSound(); };

Не беспокойтесь по поводу указателя m_pPerformance. Он всего лишь указывает на интерфейс исполнителя в классе звуковой системы. Реально используется только один член данных, m_pSound, в котором сразу после загрузки сохраняются звуковые данные.

Взаимосвязь двух классов показана на рис.7.9.


Рис. 7.9. Взаимодействие класса звуковой системы и класса звукового фрагмента

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



Определение класса звуковой системы


Итак, вы познакомились со структурой класса; теперь пришло время посмотреть на реальный код. А вот и он во всей славе:

class SoundSystem { private:

public: HWND m_hWnd; // Звуковая система IDirectMusicLoader8 *m_pLoader; IDirectMusicPerformance8 *m_pPerformance;

// Функции SoundSystem(); ~SoundSystem(); HRESULT hrInitSoundSystem(void); HRESULT hrLoadSound(char *szname,GameSound *gs); HRESULT hrPlaySound(GameSound *gs); };

Код определения класса достаточно короток, но не слишком, если его сравнивать с самим классом. Ваше внимание может привлечь одна вещь — тип данных GameSound. Что же это такое?



Остановка музыки


Следующий фрагмент кода подразумевая, что песня подошла к концу, останавливает музыку, перематывает ее к началу и заново начинает воспроизведение. Остановка музыки осуществляется вызовом функции IMediaControl::Stop(). Параметров у функции нет.

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



Перехват фоновых событий


Теперь, когда все необходимые части проинициализированы и запущены, код присваивает флагу g_bBackgroundMusicActive значение 1. Благодаря этому цикл сообщений функции WinMain() узнает, что надо проверять состояние музыки. Вернитесь назад к функции WinMain() и взгляните на следующий фрагмент кода:

if(g_bBackgroundMusicActive) { vCheckMusicStatus(); }

Эй, я не говорил, что будет много кода! Так или иначе, главный цикл проверяет состояние музыки вызывая мою функцию vCheckMusicStatus().



Перемотка музыки


Теперь, когда музыка остановлена, вам необходимо перемотать ее к началу. Конечно же никакой реальной перемотки не выполняется; вы просто снова устанавливаете указатель позиции на начало песни. Это выполняет функция IMediaSeeking::SetPositions(). Вот как выглядит ее прототип:

HRESULT SetPositions( LONGLONG *pCurrent, DWORD dwCurrentFlags, LONGLONG *pStop, DWORD dwStopFlags );

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

Следующий параметр, dwCurrentFlags, является комбинацией битовых флагов, относящихся к устанавливаемой позиции. Существует два типа флагов: флаги позиционирования и модификаторы. Здесь я использую флаг AM_SEEKING_AbsolutePositioning, чтобы сообщить системе, что позиция 0 является абсолютной, а не относительной. Названия трех других флагов описывают их назначение: AM_SEEKING_NoPositioning, AM_SEEKING_RelativePositioning и AM_SEEKING_IncrementalPositioning.

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

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



Позиционирование аудиовизуального потока


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

Таблица 7.8. Методы интерфейса IMediaSeeking

Метод Описание
CheckCapabilities Проверяет, обладает ли поток указанными возможностями.
ConvertTimeFormat Преобразует из одного формата в другой.
GetAvailable Возвращает доступный диапазон значений времени для позиционирования.
GetCapabilities Возвращает возможности аудиовизуального потока.
GetCurrentPosition Возвращает текущую позицию в потоке.
GetDuration Возвращает длину потока.
GetPositions Возвращает текущую и конечную позиции.
GetPreroll Возвращает размер аудиовизуального потока, расположенного перед начальной позицией.
GetRate Возвращает темп воспроизведения.
GetStopPosition Возвращает конечную позицию. Она сообщает вам, когда воспроизведение потока будет завершено.
GetTimeFormat Возвращает используемый в данный момент формат времени.
IsFormatSupported Проверяет поддерживается ли указанный формат времени.
IsUsingTimeFormat Проверяет используется ли в данный момент указанный формат времени.
QueryPreferredFormat Возвращает предпочтительный формат времени.
SetPositions Устанавливает текущую и завершающую позиции.
SetRate Устанавливает темп воспроизведения.
SetTimeFormat Устанавливает формат времени.

В описываемой программе я использую функции SetRate() и SetPositions().



Пример использования класса звуковой системы


Как насчет примера программы, которая использует рассмотренный только что класс? Загрузите с компакт-диска проект с именем DSound_SoundSystem и следуйте за мной дальше.

Рассматриваемый пример состоит из пяти основных файлов: main.cpp, main.h, SoundSystem.cpp, SoundSystem.h и dxutil.cpp. Файлы main.cpp и main.h содержат основной код программы, а в файлах SoundSystem.cpp и SoundSystem.h находится код класса звуковой системы. Файл dxutil.cpp содержит код полезных вспомогательных функций DirectX.

Для компиляции приложения потребуются несколько библиотек: dxguid.lib, comctl32.lib, winmm.lib и dsound.lib. Список должен выглядеть знакомо, поскольку те же самые библиотеки использовались в первом примере программы из этой главы.

На рис. 7.11 показано окно, выводимое данной программой.


Рис. 7.11. Окно программы DSound_SoundSystem

Полагаю, что окно на рис. 7.11 нельзя сравнивать с экранами из игры Warcraft II, но тем не менее это настоящая программа! Фактически в программе нет никаких изображений для разглядывания. Но тем не менее, тут есть что послушать! Запустите программу и нажмите на левую кнопку мыши, а затем на правую. Вы услышите воспроизведение двух разных звуков. Более того, вы можете нажимать на кнопки мыши снова и снова и звук будет воспроизодиться несколько раз.



Проект DMusic_PlaySound


Программа содержит несколько исходных файлов: main.cpp, main.h и DXUtil.cpp. Все они являются уникальными для данного проекта, за исключением файла DXUtil.cpp, который является частью набора вспомогательных файлов DirectX SDK.

Проект использует следующие библиотеки: dxguid.lib, winmm.lib и dsound.lib. Вы можете спросить, почему нет библиотеки с именем dmusic.lib. Я не знаю, что вам ответить. Microsoft решила поместить функции DirectMusic в библиотеку DirectSound. Я предполагаю, что это вызвано тем, что они большей частью совместно используют одну и ту же логику.

Что-то я давно не показывал вам новых иллюстраций. Взгляните на рис. 7.1, где показан основной поток выполнения примера программы.


Рис. 7.1. Поток выполнения программы DMusic_PlaySound

На рис. 7.1 видно, что WinMain() вызывает функцию bInitializeSoundSystem(). Эта функция инициализации выполняет несколько вызовов функций DirectX для инициализации звуковой системы. Затем программа ожидает события мыши и воспроизводит файл WAV с помощью функции vPlaySound(). Если вы еще этого не сделали, запустите программу и щелкните по ее окну левой кнопкой мыши чтобы воспроизвести файл WAV. Разве вам не понравился этот замечательный тестовый файл WAV? Эй, я знаю, что он не производит особого впечатления. Если вы хотите поэкспериментировать, замените файл testsound.wav одним из ваших собственных звуковых файлов. Программа должна работать с любым WAV-файлом.



Все исходные файлы, за исключением


Программа содержит несколько файлов с исходным кодом: main.cpp, main.h и DXUtil.cpp. Все исходные файлы, за исключением DXUtil.cpp, являются уникальными для данного проекта. Кроме того, в проекте используются следующие библиотеки: dxguid.lib, winmm.lib и Strmiids.lib. Файл Strmiids.lib необходим для работы с DirectShow.


Проверка кода события


Пришло время использовать старый верный интерфейс IMediaEvent. Этот интерфейс позволяет увидеть состояние музыки. Я вызываю функцию IMediaEvent::WaitForCompletion() чтобы получить самый верхний код события.

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

Если код события равен EC_COMPLETE, я знаю, что воспроизведение песни закончилось. Это явная подсказка, что пора перемотать песню к началу и снова запустить ее воспроизведение. Если возвращен код EC_ERRORABORT или EC_USERABORT, я знаю, что что-то пошло наперекосяк и воспроизведение музыки прекращено.



Реализация класса звукового фрагмента


Класс звукового фрагмента является очень простым и содержит только конструктор и деструктор. Вот как выглядит реализация данного класса:

// Конструктор GameSound::GameSound() { m_pSound = NULL; m_pPerformance = NULL; } // Деструктор GameSound::~GameSound() { if(m_pSound) { if(m_pPerformance) m_pSound->Unload(m_pPerformance); } SAFE_RELEASE(m_pSound); }

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

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


Рис. 7.10. Взаимодействие объектов классов звуковой системы и звукового фрагмента

На рис. 7.10 показано взаимодействие объектов и взаимоотношения между классом звуковой системы и классом звукового фрагмента. В верхней части рисунка показано как интерфейсы зхагрузчика и исполнителя используются в функции инициализации. В центре рисунка видно как интерфейс загрузчика применяется в функции загрузки звука. В функцию загрузки передается объект звукового фрагмента и там вызывается его метод загрузки данных. В нижней части рисунка показано как объект звукового фрагмента передается в функцию возпроизведения, чтобы началось воспроизведение звука. Чтобы воспроизвести реальный звук объект исполнителя из класса звуковой системы использует данные сегмента из объекта звукового фрагмента. У-ух. Понятно?



Реализация класса звуковой системы


Обзор закончен, так как насчет нескольких фрагментов кода реализации класса, чтобы поддержать разговор? Ниже представлен код конструктора класса:

SoundSystem::SoundSystem() { m_pLoader = NULL; m_pPerformance = NULL; }

Как видите, в конструкторе я присваиваю значение NULL указателям на интерфейсы загрузчика и исполнителя. Это делается для того чтобы иметь возможность проверить существуют ли объекты загрузчика и исполнителя. Если значение указателя равно NULL, объект не готов. Если же значение отличается от NULL, я знаю, что могу использовать данный объект. Если хотите, можете назвать это санитарной проверкой.



Реализация классов звуковой системы


Теперь в вашем распоряжении достаточно знаний, чтобы вы смогли добавить звук в свои игры. Но в своем нынешнем состоянии код недостаточно гибок и его сложно интегрировать в проекты. Для того, чтобы упростить интеграцию и использование кода необходим класс объекта звуковой системы. В этом разделе я покажу вам как создать класс звуковой системы, выполняющий следующие действия:

Инициализация DirectSound

Загрузка звуковых файлов

Воспроизведение звуковых файлов



Сегменты DirectMusic


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

Вся функциональность сегментов сосредоточена в интерфейсе IDirectMusicSegment8. Так как он может содержать данные объектов различных типов, в нем достаточно много функций. Все они перечислены в таблице 7.3.

Таблица 7.3. Методы интерфейса IDirectMusicSegment8

Метод Описание
AddNotificationType Добавляет тип события.
Clone Копирует сегмент.
Compose Составляет дорожку.
Download Копирует данные в объект исполнителя.
GetAudioPathConfig Возвращает конфигурацию аудио-пути.
GetDefaultResolution Возвращает разрешение времени для сегмента.
GetGraph Возвращает инструментальный граф.
GetLength Возвращает длину сегмента.
GetLoopPoints Возвращает точки начала и конца цикла.
GetParam Возвращает параметры дорожки.
GetRepeats Возвращает количество цикличских повторений сегмента.
GetStartPoint Возвращает начальную точку.
GetTrack Возвращает дорожку, соответствующую заданным условиям поиска.
GetTrackGroup Возвращает группу битов дорожки.
InitPlay Инициализирует состояние воспроизведения.
InsertTrack Вставляет дорожку.
RemoveNotificationType Удаляет тип события.
RemoveTrack Удаляет дорожку.
SetDefaultResolution Устанавливает разрешение по умолчанию.
SetGraph Устанавливает инструментальный граф.
SetLength Устанавливает длину.
SetLoopPoints Устанавливает начальную и конечную точки цикла.
SetParam Устанавливает параметры дорожки.
SetPChannelsUsed Устанавливает используемый канал исполнителя.
SetRepeats Устанавливает количество циклических повторов сегмента.
SetStartPoint Устанавливает начальную точку.
SetTrackConfig Конфигурирует дорожку.
Unload Удаляет данные из объекта исполнителя.

Как видно из таблицы 7.3, интерфейс сегмента предоставляет много полезных функций. Функциональные возможности DirectMusic позволяют вам управлять практически каждым параметром сегмента. Вся красота в том, что вы можете использовать столько функциональных возможностей, сколько пожелаете. Перейдем к демонстрации!

netlib.narod.ru< Назад | Оглавление | Далее >



События аудиовизуального потока


Следующим в заголовочном файле расположен указатель на интерфейс IMediaEvent. Этот тип интерфейса используется для коммуникации с графом фильтров. Он будет информировать вас о текущем состоянии воспроизводимого аудиовизуального потока. В рассматриваемом примере программы я использую данный интерфейс, чтобы получить сообщение о завершении воспроизведения музыки. Методы интерфейса перечислены в таблице 7.8.

Таблица 7.8. Методы интерфейса IMediaEvent

Метод Описание
CancelDefaultHandling Отменяет установленную по умолчанию обработку события фильтром.
FreeEventParams Освобождает связанные с параметром ресурсы.
GetEvent Возвращает следующее событие из очереди.
GetEventHandle Возвращает дескриптор следующего сообщения в очереди.
RestoreDefaultHandling Восстанавливает обработчик по умолчанию.
WaitForCompletion Ожидает пока граф фильтров не завершит воспроизведение аудиовизуального потока. Я использую эту функцию в примере программы чтобы проверить завершено ли воспроизведение музыки.



Создание интерфейса исполнителя


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

if(FAILED(hResult = CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**) &g_pPerformance))) { return(0); }

И снова для создания интерфейса я применяю функцию CoCreateInstance(). Интерфейс IDirectPerformance8 использует CLSID CLSID_DirectMusicPerformance и идентификатор интерфейса IID_DirectMusicPerformance8. Указатель на интерфейс я сохраняю в глобальной переменной g_pPerformance.



Создание интерфейса загрузчика


Следующий фрагмент кода создает интерфейс загрузчика. Как вы, возможно, помните, интерфейс загрузчика отвечает за загрузку звуковых данных, таких как файлы WAV и файлы MIDI. Вот как выглядит соответствующий код:

if(FAILED(hResult = CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**) &g_pLoader))) { return(0); }

Для получения интерфейса загрузчика я вызываю функцию CoCreateInstance(). Интерфейс IDirectMusicLoader8 использует CLSID CLSID_DirectMusicLoader и идентификатор интерфейса IID_IDirectMusicLoader8. Указатель на интерфейс сохраняется в глобальной переменной g_pLoader.

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



Управление аудиовизуальным потоком


В следующей строке кода заголовочного файла я создаю указатель на интерфейс IMediaControl с именем g_pMediaControl. Интерфейс управления аудиовизуальным потоком предназначен для контроля проходящих через граф фильтров данных. Этот интерфейс позволяет запустить, закончить и даже временно приостановить прохождение данных через граф. Вы можете представлять его как пульт дистанционного упроавления видеомагнитофона.

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

Таблица 7.7. Методы интерфейса IMediaControl

Метод Описание
GetState Возвращает состояние графа.
Pause Приостанавливает воспроизводимый в данный момент аудиовизуальный поток.
Run Запускает аудиовизуальный поток. Это аналог кнопки Play на пульте дистанционного управления видеомагнитофона.
Stop Завершает воспроизведение аудиовизуального потока.
StopWhenReady Более мягкая остановка.



Установка темпа воспроизведения


Давайте двигаться дальше. Следующий фрагмент кода задает темп песни. Темп музыки определяет насколько быстро (или медленно) она воспроизводится. Данный параметр может использоваться для того, чтобы голос актера звучал похоже на белку или на Дарта Вейдера. Все это делает функция IMediaSeeking::SetRate(). Она получает единственный параметр— темп воспроизведения. Если вы хотите, чтобы для воспроизведения песни использовался ее темп по умолчанию, укажите здесь значение 1. Чтобы услышать, как артист поет с удвоенной скоростью, укажите значение 2. Кроме того, вы можете использовать различные промежуточные значения. Например, я люблю воспроизводить музыку группы Metallica с темпом 1.25, чтобы получить чистый скоростной металл. Вы почти можете видеть, как руки Ларса дымятся от этого!



Воспроизведение музыки


После всей выполненной работы воспроизведение MP3 осуществляется исключительно просто. Достаточно лишь вызвать функцию IMediaControl::Run(). Если функция возвращает S_OK, вы знаете, что воспроизведение успешно началось.



Воспроизведение звука


Функция PlaySegmentEx() начинает воспроизведение загруженного сегмента. У нее есть несколько параметров, но как видите, в коде моего примера значения многих из них равны NULL. Вот как выглядит прототип функции:

HRESULT PlaySegmentEx( IUnknown* pSource, WCHAR *pwzSegmentName, IUnknown* pTransition, DWORD dwFlags, __int64 i64StartTime, IDirectMusicSegmentState** ppSegmentState, IUnknown* pFrom, IUnknown* pAudioPath );

В первом параметре, pSource, передается указатель на интерфейс воспроизводимого объекта. В рассматриваемой программе я использую глобальный указатель на сегмент, загрузка которого была выполнена раньше.

Второй параметр, pwzSegmentName, в DirectX 9.0 не используется.

Третий параметр, pTransition, позволяет задать модуляцию для сегмента. Я передаю в этом параметре NULL.

Четвертый параметр, dwFlags, позволяет вам указать набор флагов, определяющих различные параметры воспроизведения. В рассматриваемом примере для этого параметра я использую флаги DMUS_SEGF_DEFAULT и DMUS_SEGF_SECONDARY. Эти флаги указывают, что сегмент воспроизводится в его границах по умолчанию и что сегмент воспроизводится как вторичный звук. Доступно еще много других флагов, и я рекомендую вам посмотреть их описание в документации DirectX SDK.

Пятый параметр, i64StartTime, задает начальное время для сегмента. Я передаю в этом параметре NULL чтобы воспроизведение звука началось немедленно.

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

Седьмой параметр, pFrom, позволяет указать интерфейс для остановки воспроизведения когда стартует новый сегмент. Здесь я также передаю NULL.

Восьмой параметр, pAudioPath, сообщает системе какой аудио-путь используется для воспроизведения сегмента. Я присваиваю этому параметру значение NULL чтобы для воспроизведения использовался аудио-путь по умолчанию.

Теперь запустите программу и щелкните несколько раз по ее окну левой кнопкой мыши. Важная особенность программы заключается в том, что она может одновременно воспроизводить несколько звуков. Это может не произвести на вас большого впечатления, если вы никогда раньше не занимались воспроизведением звука, но поверьте мне — это очень круто. Замечательно то, что DirectMusic за вас выполняет всю необходимую буферизацию.

netlib.narod.ru< Назад | Оглавление | Далее >



Воспроизведение звуковых фрагментов

Вернемся к циклу обработки сообщений Windows. В нем находится код, ожидающий пока не будет нажата левая или правая кнопки мыши. Как только произойдет одно из указанных событий, будет воспроизведен соответствующий звук. Вот как выглядит код:

switch(msg) { case WM_LBUTTONDOWN: // Воспроизведение звука g_SoundSys.hrPlaySound(g_sndButtonOver); break; case WM_RBUTTONDOWN: // Воспроизведение другого звука g_SoundSys.hrPlaySound(g_sndButton); break; case WM_DESTROY: PostQuitMessage(0); return 0; default: break; } return DefWindowProc(hWnd, msg, wParam, lParam);

В блоке логики оператора switch видно, как система реагирует на события кнопок мыши вызывая функцию класса звуковой системы hrPlaySound(). Если нажата левая кнопка мыши, воспроизводится звук из объекта g_sndButtonOver. Если нажата правая кнопка мыши, воспроизводится звук из объекта g_sndButton.

ПРИМЕЧАНИЕ

Сообщения WM_LBUTTONDOWN и WM_RBUTTONDOWN являются частью системы событий Microsoft Windows. Существуют сотни различных событий и для получения более подробной информации о них я рекомендую обратиться к документации VisualC++.

На рис. 7.12 показан ход выполнения программы с момента запуска до завершения.


Рис. 7.12. Работа программы, использующей класс звуковой системы

На рис. 7.12 видно, что первой вызывается функция инициализации звуковой системы. Внутри этой функции программа вызывает функцию инициализации из класса SoundSystem. Затем для загрузки двух звуковых файлов вызывается функция загрузки звукового фрагмента из того же класса. После того, как инициализация завершена, программа обрабатывает сообщения, пока не получит сигнал о том, что нажата левая или правая кнопка мыши. Как только это произойдет, программа вызывает метод воспроизведения звука из класса звуковой системы и воспроизводит соответствующий звуковой фрагмент.



Заголовочный файл Main.h

Откройте заголовочный файл main.h и следуйте за мной. Заголовочный файл в этом примере очень простой и короткий. Вот он целиком:

#ifndef MAIN_H #define MAIN_H #define STRICT #include <windows.h> #include <stdio.h> #include <D3DX9.h> #include <dmusici.h> #include <dsound.h> #include <dshow.h> #include <dxutil.h>

// Прототипы функций LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void vCleanup(void); bool bInitializeSoundSystem(HWND hWnd); void vPlaySound(void);

// Глобальные звуковые данные IDirectMusicLoader8 *g_pLoader; IDirectMusicPerformance8 *g_pPerformance; IDirectMusicSegment8 *g_pSound; #endif

Список включаемых файлов не должен преподнести вам много сюрпризов. В основном это обычные включаемые файлы для Windows-программы. Единственный файл, добавляемый специально для DirectMusic— это dmusici.h.

В разделе прототипов функций появились две новые функции: bInitializeSoundSystem() и vPlaySound(). Назначение обоих очевидно из их названий. (Разве вы не любите самодокументируемый код?)

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

ПРИМЕЧАНИЕ

Я вовсе не поощряю вас использовать глобальные переменные в коде. Я применяю их в примерах только для того, чтобы сделать код как можно проще.

Заголовочный файл Main.h


Заголовочный файл main.h содержит обычный набор объявлений глобальных переменных и директив включения файлов, необходимых для примера. Вот как выглядит код секции с директивами включения файлов:

#include <windows.h> #include <stdio.h> #include <D3DX9.h> #include <dxutil.h> #include <dshow.h>

Новым в этом блоке является включение файла dshow.h. Он необходим для вызова интерфейсов и функций DirectShow. Если вы планируете использовать функциональность DirectShow, убедитесь, что этот файл есть в списке включаемых.

В следующем блоке кода находятся прототипы функций. Вот как он выглядит:

LRESULT WINAPI fnMessageProcessor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void vCleanup(void); bool bPlayTitleMusic(void); void vStopTitleMusic(void); void vCheckMusicStatus(void);

Функция fnMessageProcessor()— это обычный обработчик сообщений Windows. Здесь нет ничего нового — все та же старая чепуха.

Функция vCleanup() вызывается перед выходом из программы. Она выполняет освобождение интерфейсов.

Функция bPlayTitleMusic() вызывается один раз в начале программы. Она инициализирует DirectShow, загружает файл MP3 и начинает его воспроизведение.

Функция vStopTitleMusic() останавливает воспроизведение музыки перед завершением работы программы.

Функция vCheckMusicStatus() проверяет не завершилось ли воспроизведенеи музыкального сегмента. Если да, воспроизведение музыки повторяется с начала.

Далее в заголовочном файле расположены глобальные переменные. Я создаю несколько указателей на интерфейсы и одну логическую переменную, как показано ниже:

bool g_bBackgroundMusicActive = 0; IGraphBuilder *g_pGraph; IMediaControl *g_pMediaControl; IMediaEvent *g_pEvent; IMediaSeeking *g_pSeeking;

Переменная g_bBackgroundMusicActive используется для отслеживания состояния музыки. Если музыка воспроизводится, ее значение равно 1. Если нет, значение равно 0.

Переменная g_pGraph является указателем на интерфейс IGraphBuilder. Эй, эй, что это за граф?



Загрузчик DirectMusic


Загрузчик в DirectMusic одвечает за загрузку аудио-содержимого. С его помощью вы можете загрузить файлы MIDI, файлы WAV, коллекции DLS и файлы сегментов DirectMusic. Как только вы сообщите, какой именно аудио-ресурс вам требуется загрузить, загрузчик выполнит всю необходимую работу и начнет потоковое чтение ресурса. Вам остается только выполнить воспроизведение аудиоданных!

Загрузчик использует единственный интерфейс с именем IDirectMusicLoader8. Возможно, вы удивляетесь почему в имени интерфейса DirectX9.0 не стоит цифра 9? Дело в том, что по сравнению с 8 версией в DirectMusic не было сделано никаких существенных изменений. Большинство изменений были сделаны для увеличения быстродействия кода.

Интерфейс загрузчика содержит несколько методов, перечисленных в таблице 7.1.

Таблица 7.1. Методы интерфейса IDirectMusicLoader8

Метод Описание
CacheObject Увеличивает счетчик ссылок объекта. Полезно использовать для предотвращения многократной загрузки объекта.
ClearCache Очищает счетчик ссылок для объекта указанного типа.
CollectGarbage Очищает неиспользуемые ссылки.
EnableCache Включает автоматическое кэширование. Может также применяться для выключения автоматического кэширования.
EnumObject Перечисляет объекты заданного типа.
GetObject Возвращает объект.
LoadObjectFromFile Загружает объект из файла. Это наиболее часто используемый метод, поскольку он отвечает за загрузку файлов WAV.
ReleaseObjectByUnknown Освобождает ссылку на объект.
ReleaseObject Освобождает ссылку на объект.
ScanDirectory Выполняет поиск в каталоге файлов указанного типа. Кэширует результаты для перечисления.
SetObject Позволяет установить атрибуты некорректного объекта.
SetSearchDirectory Устанавливает путь к каталогу, в котором будет выполняться поиск аудиофайлов.

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



Загрузка музыкального файла


Функция IGraphBuilder::RenderFile() выполняет всю работу, необходимую для загрузки файла. Она получает два параметра: первый содержит строку с именем файла, а второй не используется. Как видно из кода в рассматриваемой программе я загружаю файл c:\dxsdk\samples\media\track3.mp3. Если у вас DirectX SDK установлен в другом каталоге, убедитесь что путь соответствующим образом скорректирован. Вы можете скорректировать имя файла, чтобы воспроизводить любую из имеющихся на вашем компьютере песен в формате MP3. Лично я указал песню Amish Paradise, которую исполняет Weird Al Yankovic. Поскольку у меня нет прав на распространение этой песни, пришлось указать файл, который, скорее всего, будет на диске вашего компьютера!

Если загрузка файла прошла успешно, функция возвращает значение S_OK. Если же при загрузке произошел сбой, вам может понадобиться консультация с документацией DirectX SDK для получения информации о возвращаемом коде ошибки.



Загрузка звукового файла


Вы помните интерфейс загрузчика, который создали минуту назад? Пора снова воспользоваться им для загрузки тестового файла WAV, включенного в сопроводительные файлы. Чтобы сделать это, обратимся к функции LoadObjectFromFile(). Перед тем, как я покажу вам ее прототип, взгляните на код из программы:

// Загрузка звукового файла if (FAILED(g_pLoader->LoadObjectFromFile( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8, L"testsound.wav", (LPVOID*) &g_pSound ))) { return(0); }

// Загрузка данных if (FAILED (g_pSound->Download(g_pPerformance))) { return(0); }

В коде я использую функцию загрузки файла, чтобы загрузить WAV-файл с жесткого диска. Затем я загружаю сегмент в объект исполнителя для воспроизведения. Вот что видно с высоты птичьего полета.

Прототип функции LoadObjectFromFile() выглядит следующим образом:

HRESULT LoadObjectFromFile( REFGUID rguidClassID, REFIID iidInterfaceID, WCHAR *pwzFilePath, void ** ppObject );

В первом параметре, rguidClassID, должен быть передан уникальный идентификатор класса. Для загрузки в сегмент используйте идентификатор класса CLSID_DirectMusicSegment.

Второй параметр, iidInterfaceID, должен быть уникальным идентификатором интерфейса. Для загрузки в сегмент используйте IID_IDirectMusicSegment8.

Третий параметр, pwzFilePath, является именем загружаемого файла. В рассматриваемом примере я использую файл testsound.wav. Вы можете заметить букву L перед строкой с именем файла. Я поместил ее потому, что в данном параметре должно передаваться имя в кодировке Unicode (где для представления одного символа используются два байта).

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



Запуск музыки


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

Взгляните на рис. 7.7, чтобы увидеть все, что выполняет наша программа.


Рис. 7.7. Поток исполнения программы воспроизведения MP3

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

netlib.narod.ru< Назад | Оглавление | Далее >



Звуковые API


Существует несколько способов воспроизведения звука в компьютерной игре, включая использование различных свободно распространяемых API, таких как OpenAL и DirectX. Кроме того, доступны различные коммерческие библиотеки, но их стоимость может вас неприятно удивить.

На данный момент я предпочитаю пользоваться DirectX API. Вы можете спросить, почему? Девятая версия DirectX предоставляет сотни различных возможностей. Она также поддерживается практически каждым производителем оборудования. А больше всего мне нравится, что за ее использование не надо платить.

OpenAL тоже распространяется свободно, но на данный момент не может похвалиться такой широкой поддержкой производителей аппаратуры. Это вызывает проблемы с драйверами. Если ваша игра поддерживает только OpenAL, пользователи не имеющие драйверов OpenAL для своих звуковых карт скорее всего возвратят вашу игру даже не попытавшись сыграть в нее. Пока OpenAL не стал таким же распространенным, как OpenGL, я рекомендую воздержаться от его использования. Не поймите меня неправильно. Если вы действительно хотите использовать OpenAL, просто сделайте так, чтобы ваша игра могла работать как с DirectX так и с OpenAL. Тогда пользователь сможет выбирать то, что он хочет, и все останутся в выигрыше.

netlib.narod.ru< Назад | Оглавление | Далее >