в помощь р а з р а б о т ч и к у игр
CD-ROM
технологии КУДИЦ-ОБРАЗ
Меисон П а к п а с к и ^LfWf^' - -^W
^Ьв
«•
(CEO Xtreme Games LLC)/ j \
И эта книга для моей жены она из тех о ком слагают песни
THE PREMIER PRESS GAME DEVELOPMENT SERIES
beginning
GflFTlE programming
MASON MCCUSKEY ANDRE LAMOTHE CEO ХТЙЕМЕ GAMES LLC
В ПОМОЩЬ РАЗРАБОТЧИКУ ИГР
ЗВУК
играх
технологии ПРОГРАММИРОВАНИЯ
КУЛИ11-ОБРАЗ
Глава 6. Воспроизведение MIDI
109
Глава 7. Воспроизведение МРЗ и WMA
127
Глава 8. Воспроизведение Одд Vorbis
140
Глава 9. Воспроизведение аудио koMnakm-guckoB
149
Глава 10. Воспроизведение треКерной музыКи
167
Часть II. Продвинутые Возможности зВуКоВого gBujkka
173
Глава 11. Динамичес1сая музыКа
174
Глава 12. Ckpunmbi и эффеКты
197
Глава 13. 3D звуК, используя DirectX Audio
228
ББК 32.973.26-018.2 Мейсон МакКаски. Звук в играх: технологии программирования./Пер. с англ. - М: КУДИЦ-ОБРАЗ, 2004.-368 с. Идеальная книга для знакомства с программированием звука с нуля. Шаг за шагом вы будете разрабатывать собственный звуковой движок, а попутно научитесь воспроизводить midi, wav, mp3, wma, ogg файлы, аудио компакт диски, трекерную музыку. Основной упор делается на работу с DirectMusic, так же вы познакомитесь с OpenAL и различными вспомогательными библиотеками обработки и воспроизведения звука. Изучаются вопросы создания динамического музыкального сопровождения и его визуализации, трехмерного звука, звуковых спецэффектов, голосового общения в Интернет. Если вы еще не решили с чего начать знакомство с миром компьютерного звука и музыки - начните его с этой книги.
ISBN 1-59200-029-0 ISBN 5-9579-0024-9 Мейсон МакКаски. Звук в играх: технологии программирования, Учебно-справочное издание Переводчик: Б. В. Кучин Научный педакупр: И. В. Кошечкин .
Kpamkoe содержание
Глава 14. 3D sByk, используя OpenAL
246
Глава 15. Продвинутые Возможности 3D sByka
260
Глава 16. DirectPlay Voice
272
Глава 17. Визуализация зВуКа
292
3akAK>4CHue
306
Приложение А. Создание динамически музьиЧи
307
Приложение Б. Советы по созданию игрового аудио
339
Предметный указатель
347
Благодарности Я хочу поблагодарить нескольких людей, которые помогли мне при написании этой книги. Прежде всего я хочу поблагодарить Андре ЛаМот, а также Дженни Дэвидсон, моего редактора, и Хизера Холанда, моего 'технического редактора, которые помогли выглядеть мне более логичным и последовательным, чем я есть на самом деле. Также я хочу поблагодарить Джереми Мартина за часть мелодий предоставленных для CD. И, конечно, мою семью и друзей, которые мне очень сильно помогли при написании книги!
Об аВторе
\
Мейсон МакКаски - разработчик программного обеспечения с 8 годами опыта коммерческого программирования. Он успешно управляет собственной игровой компанией с 1990 года. В 2000 году его компания, Spin Studios, стала одним из 9 финалистов среди компаний со всего мира на конференции разработчиков игр 2nd Annual Independent Games Festival. Он также написал десятки статей для gamedev.net и других сайтов. Мейсон является автором книги Special Effects Game Programming with DirectX, изданной Premier Press.
Обращение от pegakmopa серии ЕСЛИ вы на меня похожи, то вы, наверняка, цените музыку и звуковые эффекты в играх, но не особенно о них заботитесь во время разработки. Вы создаете список необходимых звуковых эффектов и музыкальных сегментов, устанавливаете требования к ним, вроде 16-битного стерео, 44 кГц, нанимаете когонибудь и на этом заканчиваете. Потом в игре вы, может быть, используете панорамирование для создания эффекта «3D» в самолете, или используете эффект Доплера, или изменяете высоту звука в тоннеле - но в большей или меньшей степени вы вряд ли станете много заниматься звуком в игро. Если вы тоже подходите под это описание, то мы оба делаем большую ошибку! Аудио невероятно «выросло» за последние 5—7 лет. Встроенные возможности DirectAudio на платформе Windows, технологический прогресс в моделировании 3D звука аппаратным способом и множество качественных схем сжатия звука с потерями и без дали звуковым движкам безграничные возможности для создания по-настоящему притягательных игровых звуковых окружений. Я думаю, что мы забыли о том, что звук гораздо важнее, чем внешний вид. На.самом деле, звук может создать напряжение, волнение, чувство злости и т. д. Попробуйте поиграть в вашу любимую игру без звука; совсем не то, правда? Здесь нет ничего непонятного, звук - это прямой и незамедлительный способ повлиять на ваш мозг; он успокаивает или раздражает на подсознательном .уровне. В любом случае, звук мы больше не можем игнорировать. Раньше непонятной была 30-графика, которая теперь стала почти обязательным требованием. Теперь настало время разобраться со звуком - изучить, реализовать и достичь вершин мастерства. «Программирование игрового звука для начинающих» - как раз та книга, которая для этого нужна. Вы изучите все, что нужно знать, чтобы стать «экспертом» программирования звука вне зависимости от того, играете вы или работаете. Эта книга не сильно затрагивает технические вопросы - это значит, что вам не придется читать про обработку цифровых сигналов, не придется глубоко вникать в суть преобразования Фурье и импульсных характеристик и следствий этого. Тем не менее, книга даст вам твердое понимание основ и предоставит пути для развития всем, кто в этом заин-
тересован. Прочитав эту книгу, вы научитесь использовать такие возможности DirectAudio, как воспроизведение цифровых аудио файлов, DirectMusic и аудио скриптинг. Кроме того, вы узнаете, как проигрывать .МРЗ файлы и бесплатные «форматы будущего», такие, как Ogg Vorbis (похож на .МРЗ, но лучше!). Конечно, затронуты и такие моменты, как воспроизведение CD, микширование звуков, проблемы со временем и многое другое. Более того, рассмотрены продвинутые методики, такие, как моделирование ЗО-звука и использование OpenAL. Окончание книги посвящено обсуждению визуализации звука и инструментов для создания звуков и музыки. И, наконец, последними рассматриваются создание содержания, лицензирование и приемы для разработки завершенного набора звуковых эффектов и музыки. В итоге, если вы хотите больше знать про программирование звука, как разработчик игр, звуковой дизайнер или просто любитель, который хочет написать свой .МРЗ-проигрыватель, то эта книга для вас. Автор, Мейсон МакКаски (Mason McCuskey), потратил свое время не даром и сумел создать эту книгу веселой, информативной и увлекательной - не говоря о тех замечательных звуковых эффектах, которые он подобрал! Прочитав все это, сделайте выбор сами! Искренне ваш,
Андрё ЛаМот (Andre LaMothe), редактор серии Premier Press Game Development
Введение На игровой звук часто не обращают внимания, когда разрабатывают и программируют хорошую игру. Разработчики тратят чересчур много времени на работу с другими аспектами игры, к примеру графикой и ИИ, так что они забывают про музыку и звуки до тех пор, пока не становится слишком поздно. Так делать нельзя, потому что хорошее игровое аудио и игровая музыка имеют как минимум такую же, если не большую, способность мгновенно погрузить игрока в игровой мир и распоряжаться его эмоциональным состоянием, Даже лучшая графика в мире будет казаться блеклой, если для нее нет хорошего звука. Исторически сложилось, что книги по игровому аудио выходили редко, так как программирование игрового аудио было совсем несложным. Как заставить динамик ПК выдать звуковой сигнал или проиграть простой звук в моно, для эры звуковых карт 1990 годов было практически очевидно. Тем не менее, сегодня такие вещи, как 3D звук, моделирование окружения, воспроизведение МРЗ и CD аудио, представляют собой достаточно сложные технические препятствия даже для примитивной игры. Эта книга является введением в некоторые проблемы, с которыми предстоит столкнуться программисту при реализации аудио в их играх. Она написана, чтобы дать понятие об основах игрового аудио. Первая часть книги посвящена тому, как преодолеть трудности связанные с простыми задачами: воспроизведение звуковых эффектов, загрузка и изучение WAV файлов и воспроизведение музыки в различных форматах (MIDI, МРЗ и трекерная музыка). Вторая часть этой книги рассказывает про более продвинутые методики игрового аудио. Вы узнаете о том, как создается динамическая музыка и как реализовать ее в вашей игре. Также вы узнаете основы программирования 3D звука, создания окружений, аудио скриптинга, обработки эффектов и визуализации звука. Также одна глава посвящена использованию DirectPlay Voice для реализации общения голосом в текущем времени между вашими игроками! Большая часть книги использует DirectX и DirectX Audio, хотя есть и главы, которые используют OpenAL Audio или общие Win32 Audio API. Все написано на C++ и использует стандартные низкоуровневые структуры данных Standard Template Library (векторы, отображения и т. д.).
Введение
И
Чтобы извлечь максимум пользы из этой книги вы должны уверенно обращаться с C++, хотя и необязательно быть экспертом. Тем не менее, для начала вы должны понимать, как работает Windows программа. Вы должны уверено себя чувствовать, работая с программированием, основанным на обработке событий и постоянном использовании Win32 API, включая написание кода для кнопок, полей редактирования, окон списка и т. д. Также требуется хотя бы поверхностное знание DirectX. Вы должны знать, как получать и освобождать интерфейсы для различных компонентов DirectX, и должны понимать, что DirectX делает, почему он полезен и куда обратиться за помощью в случае необходимости. Если вы удовлетворяете всем этим требованиям... переворачивайте страницу и удачи вам!
ГлаВа 1
Основы основ Вам нужно где-то начинать, и для изучения игрового звука «где-то» это здесь. Здесь пока нет никаких примеров программного кода, только большое количество концепций и терминов, чтобы не было проблем с прочтением следующих глав. Если вы уже имели дело с компьютерной музыкой, то вы знаете большую часть всего этого. В этой главе вы узнаете основные принципы цифрового звука: как ваш компьютер слышит и поет. Также вы узнаете краткую историю звука в видеоиграх с небольшим тестом в конце (шутка). И в заклчение я продемонстрирую несколько способов избежать написания своего звукового движка путем приобретения лицензии на один из уже существующих или скачивания одного из бесплатных. Я решил привести список звуковых движков в начале, чтобы вы не могли потратить несколько недель за этой книгой, дойти до конца и спросить: «Так я могу скачать звуковой движок?!».
Принципы и термины Компьютерного aByka Для игрового звука все заключается в одном байтовом потоке. Это может звучать глупо, но смысл этой книги, да и всего игрового звука в целом, — это создание огромного одномерного байтового массива. Тем не менее, иногда простая идея может быть спрятана за большим количеством технических терминов. Чтобы не было полного непонимания, далее приводится описание некоторых базовых аудио терминов.
В сущности, любой звуковой движок представляет собой набор высокоуровневых функций для работы со звуком. Зачастую звуковой движок является частью игрового движка. - Примеч. науч. ред.
ОсноВы основ
13
Частота guckpemu3au,uu (Sample Rate) и Качество BbiBopku (Sample Quality) При программировании звука постоянно приходится сталкиваться с такими понятиями, как частота дискретизации и качество выборки. Чтобы разобраться с ним, вам нужно в первую очередь понять, как цифровой звук попадает на ваш жесткий диск (рис. 1.1). Предположим, вы делаете игру-экшн и вам требуется жуткий крик, от которого кровь стынет в жилах. Вы нанимаете своего друга/родственника/помощника и уговариваете его сесть перед микрофоном. Вы нажимаете клавишу записи в программе, работающей со звуком, и подаете сигнал, чтобы он начинал кричать. Как только вы нажали клавишу записи, программа сообщает звуковому аппаратному обеспечению, что нужно начинать записывать звук. В основном, когда система записывает звук, она опирается на работу специальной микросхемы на звуковой карте, которая называется ADC или analog-to-digital converter (АЦП - аналого-цифровой преобразователь). Время от времени АЦП измеряет электрический ток, идущий от микрофона, чтобы определить, насколько громок поступающий туда звук. После этого это число отправляется к драйверу звуковой карты и через Windows к вашей аудио программе, которая сохраняет его в памяти или записывает на жесткий диск. Эти данные называются выборкой (sample). (Обратите внимание, что часто целую запись, или подборку выборок, называют sample, так что нужно обращаться к контексту, в котором было употреблено слово, чтобы понять, о чем идет речь.) Частотой дискретизации называется число выборок, записанных за секунду. Частоты измеряются в герцах, т. е. число раз в секунду. Таким образом, 8000Гц соответствует 8000 выборкам в секунду. Аналогично тому, как для байтов есть килобайты, для герц есть килогерцы ; 22кГц означает, что что-то случается 22000 раз в секунду. 8000Гц - это то же самое, что и 8кГц. Звуковой фрагмент. Прослушайте ,музыкальные фрагменты 1.1 и 1.2, чтобы понять разницу между частотой дискретизации 44.1КГц и 8000Гц.
В некотором смысле, частота дискретизации для звука - это то же самое, что и разрешение для изображения. Так же, как большее количество пикселей означает более качественное видео, более высокая частота дискретизации означает более качественный звук. Обычно вы будете записывать звуковые эффекты на частоте
2
1 килобайт = 1024 байта, но 1 килогерц = 1000 герц. - Примеч. неуч. ред.
Глава!
14
Звуковые волны, улавливаемые микрофоном
Это число читается с помощью драйвера звуковой карты и через Windows отправляется программе редактирования звука
Звуковые волны преобразуются АЦП в число, показывающее, насколько громок звук от устройства [иполной сигнал)
Программа редактирования звука или сохраняет число в память, или записывает его прямо на диск (этот процесс называется стриминг)
Память
Жесткий диск или высокоскоростное устройство хранения информации
Рис. 1.1. Как ваш компьютер записывает звук
11205Гц, 22050Гц или 44100Гц. 44100Гц (44,1 кГц) - это частота дискретизации для CD аудио; если вы слушаете трек с CD, то какой-то компьютер где-то измерял громкость звука в воздухе 44100 раз в секунду. Это много данных. Когда нужно воспроизвести звук, компьютер просто читает числа и отправляет их на звуковую карту (рис. 1.2).
Почему 44100Гц ? Все-таки, почему CD записываются с частотой 44.100Гц? Это число кажется несколько странным. Для того чтобы ответить на этот вопрос, мы должны углубиться в историю звука. До CD цифровой звук записывался на видеопленку, при прослушивании которой слышались шумы в виде шипения или сухого кашля. Пленки были сделаны так, чтобы записывать и воспроизводить 60 кадров видеоинформации в секунду. В каждом кадре было 245 линий, и в каждой линии три выборки (для красного, зеленого и синего). Получается 245x3x60, или Я4100 выборок.
Основы основ
15
Память
^^\ ^ Жесткий диск или высокоскоростное устройство хранения информации
^5" 7
^ Программа воспроизведения 3№ л
^
->\
>
Windows ОС
h^
Числа (еыбо pen/sample] читаются из памяти и воспроизводятся или проигрываются прямо с устройства хранения (процесс называется стримииг]
Драйвер звуковой карты
£>
5
Колонки
Программа воспроизведения отправляет аыборку/sarnpfc в Windows ОС, которая отправляет его в драйвер. Драйвер переда : т еговЦАП
Цифроа налога вый преобразователь |ЦАП)
9 Наушники
Электрический импульс отправляется на ваши наушники или колонки
'
ДЛ создает электрически импульс, с амплитудой, пропорциональной входным данным
Рис. 1.2. Как ваш компьютер воспроизводит звук
С другой стороны, существует такое понятие, как качество выборки. Качество выборки (также называемое битами на выборку или иногда просто битами) показывает, насколько аккуратно измеряется выборка. Обычно используют одну из двух величин: 8 или 16 бит. Очевидно, что 16 бит лучше, чем 8. С 8 битами вы можете выделить только 256 различных уровней громкости, а с 16 битами вы получаете 65536 уникальных уровней, которые делают звук более насыщенным. Если у вас есть только 1 бит, то вы можете обозначить только «громко» или «тихо»,что совсем не способствует созданию хорошей выборки. Если частота дискретизации похожа на разрешение видео, то биты на выборку - это глубина цвета: чем больше у вас бит, тем большую точность вы получаете. Звуковой фрагмент. Прослушайте музыкальные фрагменты 1.3 и 1.4, чтобы понять разнш{у между 8- и 16-битными выборками. Слушайте внимательно; разница уже не столь заметна, как в случае с частотой дискретизации.
16
Глава 1
Стерео aByk До сих пор в этой главе рассматривался только одноканальный звук, называемый также монофонический (monaural}, сокращенно моно (mono). Для того чтобы записать стереозвук, вы используете два микрофона в двух разных местах (один слева от источника звука, а второй справа). Тем не менее, большую часть времени вы записываете стереозвук другим образом: вы используете микширующий блок или микширующую программу, позволяющую вам разрывать монозвук на два моноканала и контролирующую, из каких они будут слышны. Независимо от того, какой из двух способов вы избрали, в итоге вы получаете двухканальный стереозвук. CD качество - это 16-битный стереозвук с частотой дискретизации 44,100Гц. На каждую секунду звука CD качества вы получаете 44,100 выборок по 16 бит (2 байта) каждая - 88,200 байт на канал за секунду. Стереозвук содержит два канала, поэтому вы получаете 88,200X2, или 176,400 (около 176Кб) байт на каждую секунду звука. Вот почему компакт-диск, вмещающий 650 Мб данных, может хранить всего-навсего 74 минуты звука CD качества.
История CD Мой тесть, музыкант, рассказал, что причина, по которой CD содержит 74 минуты музыки, в том, что сильные мира сего хотели слушать Девятую симфонию Бетховена целиком, не прерываясь. Инженеры, всегда стремящиеся угодить, определили ее длину в 74 минуты и разработали физическую спецификацию для диска, который мог содержать столько информации. Мне показалось удивительным, что композитор, умерший несколько сотен лет назад, оказал столь большое влияние на формирование одной из наиболее вездесущих частей аудиотехнологии.
ЗВуКоВые форматы, okamue u kogeku Конечно, мир был бы унылым местом, если бы единственным способом, которым можно было сохранить звук CD качества, было бы потратить на секунду 176Кб. На обычную 3-минутную песню уходило бы 31,680Кб (где-то 30Мб)! Записывающие студии были бы счастливы, потому что им не пришлось бы иметь дело с людьми, обменивающимися песнями по сети. Даже при очень хорошем соединении 30Мб — это чересчур много для передачи трех минут звука. Тем не менее, умные люди нашли обходные пути. Появилось несколько алгоритмов сжатия звука, Эти алгоритмы представляют те же самые данные в более компактной форме, давая вам возможность записать ту же информацию меньшим количеством байт. Например, у вас есть моно WAV файл с двумя секундами молчания.
Основы основ
17
Обычно, если бы вы использовали CD качество, вы записали бы 88,200 байт нулями. Однако, вы можете сжать это место, говоря что-то вроде: «ОК, следующие 88,200 байт - это нули», и не записывать их вовсе. Вот, что делает обычный алгоритм сжатия, называемый RLE (run-length encoding - групповое кодирование). RLE - это очень простой алгоритм (см. рис. 1.3). Один из более сложных алгоритмов носит название MPEG Layer 3. В то время когда формат имен файлов был 8.3, звуковые файлы, сжатые с использованием MPEG Layer 3 алгоритма, получили трехбуквенное расширение МРЗ, а остальное принадлежит истории. MPEG - это аббревиатура Moving Pictures Experts Group (Экспертная группа по движущимся изображениям). Если вы хотите больше узнать об истории формата МРЗ и людей, которые его создали (Фраунгофер IIS-A), то можно посмотреть это здесь http://www.iis.fhg.de/amm/techinf71ayer3/. Исходный поток данных Исходный поток данных с выделенными одинаковыми величинами'
ш шиш ш виз шшш ш ш ш Ш1
Данные после RLE сжатия
= 10 байт данных [сэкономили 4 байта)
При FILE сжатии мы как будто говорим >3 нуля, 3 четверки, 2 восьмерки» и т. д.
Рис. 1.3. Алгоритм простейшей формы RLE сжатия
МРЗ является алгоритмом с потерями, т. е. некоторая информация теряется, когда вы сжимаете WAV в МРЗ. МРЗ работает по принципу обеспечения того, чтобы вы могли обойтись без этой потерянной информации. Например, пропадают многие участки высокой частоты, такие, как удары тарелок или произнесение шипящей «с» или твердой «к». Обычно эти потери незначительны для среднего слушателя, но с хорошими динамиками и тонким слухом вы сможете услышать разницу. Попробуйте это как-нибудь: сходите в тихое место и послушайте свою любимую песню на CD, а затем в МРЗ. Если у вас хорошие колонки, а вы молоды или хорошо ухаживали за своими ушами, вы сможете почувствовать разницу.
Глава!
18
Другие форматы сжатия перечислены здесь: ADPCM, аббревиатура от Advanced 3 Differential Pulse Cock Modulation , Ogg Vorbis (открытый ресурс, свободное от патентов сжатие звука, алгоритм, быстро набирающий популярность) и еще несколько менее известных форматов. Куски кода, обеспечивающие выполнение этих алгоритмов, называются кодеками, сокращенно от слова компрессор/декомпрессор (неужели вам не нравятся все эти сокращения?). В WAV файл включается название кодека, которым он был сжат; Windows предоставляет несколько самых общепринятых кодеков и автоматически использует нужный, основываясь на указании в WAV файле. Если вы попробуете проиграть WAV файл, содержащий указание на кодек, который не установлен на вашем компьютере, вы не сможете этого сделать, пока не разыщете кодек, которым он был сделан. К счастью, это не частое явление: 99 % WAV файлов - это РСМ или ADCPM.
MukuiupoBamie (mixing) aByka Естественно, хорошее звуковое сопровождение игры — это не только простое проигрывание WAV файлов. Часто вам понадобится воспроизвести одновременно несколько звуковых эффектов, и тогда становится необходимым микширование звука. Самый простой способ воспроизвести одновременно два звуковых эффекта просто сложить их выборки (рис. 1.4).
Выборки звука визга
0
0
0
4
4
4
8
8
2
Выборки зауш рычания f o l Г 2 I I 6 I I 1 I I 3 I \2
Визг + Рычание
I О I
б
3
7
7
7
7
0
0
0
2
7
7
7
9
7
8
Рис. 1.4. Алгоритм простейшей формы RLE сжатия
3
Автор ошибается, ADPCM - Adaptive Differential Pulse Code Modulation, что переводится как адаптивная дифференциальная импульсно-кодовая модуляция. - Примеч. науч. ред.
Основы основ *
19
Таким образом, складывая соответствующие выборки визга и рычания, вы услышите визг и рычание одновременно. Не кажется, что это будет работать, но это действительно работает. Конечно, получение хорошего результата требует гораздо большего, нежели простое сложение звуков. Вашей следующей мыслью будет: «А как насчет их усреднения?» Проблема в том, что это делает каждый конкретный звук тише. Если вы проигрываете одновременно три звука, то каждый будет звучать в три раза тише, чем он звучал отдельно. Есть несколько методов решения этой проблемы, некоторые из которых используют таблицы логарифмов и другое сложное математическое содержание. Однако, если вы не эксперт, работающий над чем-то очень продвинутым, вам никогда не придется беспокоиться о самостоятельном сложении выборок, это делает операционная система Windows. Самое большое, что вам придется сказать: это «Эй, DirectX Audio (или Эй, Windows!), смешай эти выборки с такой громкостью и те выборки с другой громкостью», - а потом с помощью какой-то черной магии оно сработает.
КратКая история зВуКа В Видеоиграх Со временем звук в играх претерпел значительные изменения. Когда методика изготовления жестяных консервов казалась сложной, а Рас-Man был высокотехнологичной игрой, играм было еще слишком трудно использовать выборки как звуковые эффекты. Самые мощные игровые машины 80-х обладали памятью от 8 до 128Кб, а обычный звуковой эффект, даже в плохом качестве, был от 30 до 50 Кб. Ранние компьютерные звуки основывались на так называемом частотно-модулированном синтезе, или FM синтезе. Я помню первый Sound Blaster, который я к\ттил для моего 386 компьютера, хвастливо уверяющий, что он поддерживает FM синтез. Звуковой фрагмент. Прослушайте музыкальный фрагмент 1.5, звучала ранняя музыка, созданная с помощью FM синтеза.
чтобы понять,
как
FM синтез использует алгоритмы воссоздания звуковых волн на лету. Вместо сохранения фактической звуковой волны, технология FM синтеза приближает эту волну математически воссозданными волнами различных форм, частот и громкостей. Например, FM синтезатор (здесь - синтезатор) говорит примерно следующее: «Если я создам вот такую волну одной частоты и объединю ее с волной другой частоты, то получится звук, который будет почти такой же, как звук трубы».
20
Глава!
К сожалению, «почти такой же» звук звучал не слишком хорошо, особенно для таких инструментов, как трубы или малый барабан. Флейты и другие инструменты, звучание которых - почти синусоидальная волна, воспроизводились нормально, а синтезированные инструменты замечательно, но все остальные довольно плохо, как дешевая электронная клавиатура. Буквально через пару лет произошла революция, когда Amiga мгновенно сделала популярным формат MOD. Музыка в формате MOD звучала значительно лучше, чем FM синтезированная, потому что MOD использовал реальные выборки, а не математическую аппроксимацию. Музыкальный формат MOD первоначально поддерживал 4 звуковые дорожки, и в любое заданное время любая из них могла воспроизводить одну выборку на любом шаге. Сейчас компьютеры обладают достаточной памятью для того, чтобы сохранить выборки инструментов, и достаточной памятью для обработки, чтобы манипулировать высотой тона этих инструментов в реальном времени. Звуковое качество не было великолепным - большая часть выборок записывались с частотой 11 кГц или 22кГц, но это было гораздо лучше, чем FM синтезатор. Особенно барабаны звучали гораздо лучше, потому что теперь это были записи реальных барабанов. Звуковой фрагмент. Прослушайте музыкальный фрагмент 1.6, чтобы понять, как звучала ранняя трекерная MOD музыка, и музыкальный фрагмент 1.7 с более поздней музыкой.
Эта технология была примитивной формой того, что теперь называется таблично-волновой (wave table) синтез. Таблично-волновой синтез берет свое название из того факта, что создание музыки основано на таблице записанных волн. Посмотрите рис. 1.5 для сравнения между FM и Wave Table синтезами. Спорный вопрос, какой формат трекерной (tracked) музыки был первым, но, как только эта идея появилась, возникли десятки «трекеров»: StarTrekker, ProTracker, NoiseTracker, Oktalyzer и много других. Демо-группа4, называющая себя Future Crew (команда будущего), создала и популяризовала музыкальные форматы STM (Scream Tracker) и, позднее, S3M (Scream Tracker 3), которые позволяли использовать большее количество каналов, лучшие эффекты и большую аппаратную частоту дискретизации. Impulse Tracker продвинул формат трекерной музыки даже дальше, введя более продвинутые возможности работы с выборками для видеоигр. С тех пор до середины девяностых такие музыкальные форматы были очень популярны. Широко распространенное в конце 80-х начале 90-х годов движение демо-групп занималось созданием демо-программ, которые представляли из себя набор аудиовизуальных спецэффектов. Подробнее смотри http:// www.scene.org и http://www.hugi.de . - Примеч. неуч. ред.
Основы основ FM синтез
Рис. 1.5. Сравнение FM синтеза и Wave Table синтеза
Совет. Потомки звукового формата MOD используются до сих пор. Нередко игры, распространяемые по сети, используют какую-нибудь форму трекерной музыки, так как ее размер намного меньше, чем размер музыки, записанной с помощью выборок. Обычный размер для файла трекерной музыки 200 — 300Кб, а для аналогичного произведения в формате МРЗ размер измеряется мегабайтами. Трекерная музыка имеет очень своеобразное звучание. Игровые разработчики частенько используют ее для создания песен в стиле техно. Я включил в CD небольшое количество трекерной музыки, среди которой есть песни, созданные мной ранее. Трекерная музыка до сих пор в некоторой степени популярна, так что я добавил еще пару ссылок на соответствующие сайты.
22
Глава!
MIDI Параллельно FM синтезу и развитию форматов трекерной музыки происходила другая революция - на этот раз в мире MIDI. MIDI - это аббревиатура от Musical Instrument Digital Interface (Цифровой Интерфейс Музыкальных Инструментов). MIDI версии 1.0 появился в 1983 и был быстро принят профессиональными цифровыми музыкантами. Когда цена MIDI синтезаторов упала, любители и просто широкая публика начали использовать MIDI. MIDI очень похож на трекерную музыку, только с незначительным отличием принципа работы. Формат трекерной музыки создавался как закрытая система центральный процессор делал все: сохранял инструменты, ноты, использовал эффекты и проигрывал конечный результат через собственные динамики. Наоборот, принцип MIDI заключается в нескольких машинах, работающих вместе для создания великолепного звука. Типичный профессиональный MIDI набор включал несколько устройств, каждое для своей цели. Страстный поклонник MIDI имел барабанную установку, синтезатор, множество инструментальных банков , компьютер и несколько процессоров эффектов. Все эти приборы должны были взаимодействовать друг с другом, и MIDI был устроен так, чтобы они могли это делать. По своей сущности, MIDI -это специальный сетевой протокол. Он определяет, как записываются ноты и как они передаются по проводам к специальным приборам. В частности, MIDI определял набор из 128 различных инструментоввсе от флейт до скрипок и специальных эффектов выстрелов и вертолетов. Эти 128 различных инструментов назывались Общий MIDI Звуковой Набор 1 Уровня (General MIDI Level 1 Sound Set), или сокращенно звуковой набор GM. Таким образом, когда MIDI-синтезатор получал сообщение «Сыграй звук пятой октавы си-бемоль на такой-то громкости на инструменте №47», он знал, что за инструмент имел номер 47, и мог воссоздать соответствующий тембр. Звуковой набор GM был чем-то вроде набора символов ASCII для MIDI. Важно заметить, однако, что, хотя описание MIDI диктовало, какой инструмент идет под каким номером, оно ничего не говорило о том, как на самом деле играют эти инструменты. (Представьте себе данные MIDI как небольшое музыкальное произведение для пианино. Данные .для исполнения - музыкальное произведение - это последовательность команд нажатия/отжатия клавиш, но насколько хорошо это прозвучит, напрямую зависит от квалификации пианиста, который будет играть.) Звук 5 Библиотеки звучаний различных инструментов и спецэффектов, которые можно загрузить в синтезатор. Примеч. науч. ред.
Основы основ
23
инструментов зависел от производителя оборудования MIDI. Большинство профессиональных производителей аппаратуры записывали реальные инструменты и сохраняли выборки в ROM. Большая часть музыкальной аппаратуры потребительского уровня использовала FM синтезатор для воссоздания звукового набора GM, потому что сохранение реальных выборок в ROM делало карты слишком дорогими для среднего покупателя (вспомните, это происходило 8 то время, когда пара мегабайт памяти стоила столько же, сколько сейчас половина гигабайта). Гораздо эффективнее было сохранять математические рецепты создания «достаточно точных» приближений инструментов, нежели сохранять сами разбитые на выборки волны этих инструментов. Звуковой набор GM, разбитый на категории, показан в табл. 1.1. Таблица 1.1. Категории Общего MIDI Звукового Набора Семейство
Номера
1-8
Фортепиано
9-16
Хроматическая перкуссия
17-24
Органные инструменты
25-32
Гитары
33-40
Басовые
41-48
Струнные
49-56
Ансамбли
57-64
Медные духовые
65-72
Дудочки
73-80
Флейты
81-88
Ведущие синтетические тембры
89-96
Синтетические подложки
97-104
Синтетические эффекты
105-112
Этнические инструменты
113-120
Нехроматическая перкуссия
121-128
Звуковые эффекты и специальные эффекты
24
Глава!
DLS1 Таким образом., в девяностых все эти разные подходы к игровой музыке существовали вместе. MIDI и трекерная музыка были самыми популярными, и оба они имели свои преимущества и недостатки. MIDI был замечателен, потому что MIDI файлы обычно были очень малы (около 100Кб), так как инструменты, которыми исполнялась песня, не записывались вместе с ней. К сожалению, это означало, что хорошее исполнение MIDI файла существенно зависело от качества инструментов, Профессионалы обладали библиотекой инструментов с высококачественными выборками из 128 общих MIDI инструментов, но все остальные (в частности люди, играющие в видеоигры) пользовались дешевыми приближениями этих инструментов, часто использующими FM синтезатор вместо записанного звука. Это означало, что MIDI файл звучал великолепно, исполняемый на профессиональном наборе композитора, и ужасно, воспроизводимый на обычном потребительском (игровом) оборудовании. Альтернативой была трекерная музыка. Она была замечательна тем, что все инструменты содержались прямо в мелодии. Это означало, что мелодии звучали одинаково независимо от аппаратуры, на которой они воспроизводились. Поскольку трекерная музыка использовала реальные выборки мелодий., то в отличие от мелодии, созданной FM синтезом, качество было выше и музыканты могли создавать свои собственные инструменты, не будучи ограничены 128 инструментами MIDI. Иногда создавался даже вокал путем разработки специальных инструментов для каждого слова или фразы в песне! Недостаток был в том, что эти мелодии были большими (вспомните, тогда 100 - 200Кб - это было очень много, почти четверть емкости 3,5 - дюймовой дискеты, и все это ради одной песни). Они также требовали больше мощности центрального процессора, потому что процессор должен был высчитывать нужный шаг выборки и соединять все данные вместе самостоятельно. Со временем прогресс спас положение. Увеличение скорости компьютерной аппаратуры и объема памяти сделало возможным соединение трекерной музыки и MIDI в один необоримый су пер формат. В 1997 ММА (MIDI Manufacturer's Association - Ассоциация Производителей MIDI) приняла и утвердила спецификацию Downloadable Sounds Level 1 (загружаемые звуки 1 уровня). DLS1 предоставлял потребителям настоящий синтезатор на основе волновых таблиц (т. е. синтезирование инструментов путем использования таблицы волновых выборок для представления их нот).
Основы осиоВ
25
Фактически, DLS1 зашел на шаг дальше., чем MIDI, давая возможность каждому инструменту иметь многочисленные выборки. Выборки были разбиты на диапазоны: могла быть одна «низкая» выборка для низких нот фортепиано и другая «высокая» для верхних октав. В сущности, у вас могло быть до 16 различных диапазонов для каждого инструмента. Для того чтобы сделать то же самое в трекерной музыке, требовалась некоторая изворотливость. DLS1 сильно продвинул популярность MIDI в качестве формата игровой музыки. Спецификация DLS включала директивы, как должны звучать инструменты, и, неожиданно, MIDI файлы, воспроизводимые на аппаратуре потребителей, стали звучать весьма похоже (а иногда даже очень похоже) на свои профессиональные эквиваленты. Теперь композиторы могли писать MIDI зная, что то, что слышат они, будут практически таким же, как то, что услышат игроки. Более того, музыканты больше не были ограничены 128 GM инструментами - они могли записывать все что угодно и включать свою собственные инструменты в MIDI мелодию, как это могли делать форматы трекерной музыки. Microsoft еще больше упрочил господство DLS1 включением в качестве части DirectX GM звукового набора записанных инструментов, созданного Roland (высокоценимой фирмой-производителем музыкальных инструментов, MIDI клавиатур и MIDI аппаратуры). Теперь, пока разработчики игр использовали эту программу DirectX синтезатор, то, что слышали они, слышали и игроки.
DLS2 ММА знали, что, если у них есть хит, как, очевидно, это было с DLS, то нужно делать продолжение. В 1999 году они утвердили спецификацию DLS уровень 2. В DLS2 были исправлены некоторые технические недостатки спецификации DLS1. Я не стану перечислять их все, достаточно сказать, что DLS2 лучше, чем DLS1, и это хорошо. DirectMusic 8 первым стал поддерживать DLS уровень 2. Синтезатор Microsoft, включенный в DirectMusic, удовлетворяет DLS2 и требует очень мало процессорного времени, даже на медленных компьютерах.
CD 3Byk (Redbook Audio) Я пока не говорил про CD аудио, решив оставить самое лучшее напоследок. Для коммерческих игр (распространяемых на CD) CD звук был наиболее предпочтительным способом хранения музыки.
26
Глава!
CD аудио позволило играм, выпускаемым на CD, пропустить всю неразбериху с MlDI/трекерной музыкой. Песни на CD звучали одинаково вне зависимости от того, на чем они проигрывались. CD-ROM привод производил большую часть работы, связанной с воспроизведением звуковой дорожки, так что процессор использовался несильно, a CD содержал настолько большое количество информации (650Мб), что место для хранения звука не было проблемой. Совет. СО звук обычно называют Redbook Audio (Redbook - букв, красная книга) по той причине, что оригинальная спецификация пришла от Sony в красном переплете. Именно в этой спецификации записываются все аудио CD. Если у вас есть игра на CD и вы можете слушать музыку из нее в проигрывателе CD, который установлен в вашем автомобиле, то эта игра использует CD звук в качестве своего звукового формата.
Redbook audio: неу&ели у&е Все? Первый CD прототип был разработан фирмой Philips в 1979 году, а первый CD проигрыватель был создан Sony в 1983 году (и стоил $1000). Первый альбом, который вы могли на нем послушать, был «52nd Street» Билли Джоела (Billy Joel). В 1983 году в США было 30000 проигрывателей и 80000 дисков. Через 7 лет9,2 миллиона проигрывателей и 288 миллионов CD. Огромная популярность CD, вкупе с казавшимся почти неограниченным размером, который стал доступен для ПК, обеспечило CD репутацию лучшего способа хранения звукового содержимого для видеоигр. Тем не менее, в наши дни CD аудио становится менее привлекательным для разработчиков игр. 650Мбэто совсем не так много, как это было когда-то, и для чего тогда использовать несжатый CD звук, когда можно (теоретически) достичь такого же качества, используя МРЗ и заняв при этом в 10 раз меньше места. Также у CD музыки нет никаких шансов быть динамической. A MIDI синтез, или даже отдельные выборки, могут служить кирпичиками для построения динамических звуковых дорожек, которые будут меняться в зависимости от происходящего в игре. Для многих игр CD звук по-прежнему является замечательным выбором, но в последнее время этому формату пришлось сильно потесниться из-за сжатой и динамической музыки. То, что когда-то сделало CD звук великим, сейчас медленно уходит в прошлое, и неизвестно, останется ли CD звук популярным форматом для видеоигр6.
' В настоящее время на смену CD аудио приходит DVD аудио. - Примеч. науч. ред.
Основы основ
27
И koega Ясе это Все уйдет на nokou? Из технологий, о которых я рассказал, три - трекерная музыка, MIDl/DLS/DirectMusic, и CD аудио - до сих пор используются. FM синтез последовал за динозаврами вместе с MS-DOS и Windows 3.11. Одно из самых важных решений, касающихся вашего игрового звука, - это решение о том, какой звуковой формат использовать. Вот руководство к тому, как выбрать правильный формат звука. Помните, что термины я использую в общем; например, не стесняйтесь подставлять ваш любимый формат сжатия звука вместо МРЗ. Когда я ссылаюсь на DirectAudio, я говорю про музыку, созданную с помощью DirectMusic7. Сначала решите; какая вам нужна музыка, динамическая или статическая. Если вам нужна динамическая музыка, то DirectMusic - это ваш выбор. Конечно, можно модифицировать или увеличить мощность старого движка трекерной музыки и превратить его в движок динамической музыки. Это подойдет, если вам нужен простой движок, но знайте, что для того чтобы создать движок, конкурентоспособный движку, созданному с помощью DirectMusic, нужно проделать очень много работы. Также вы может построить собственную систему для создания динамической музыки, основанную на выборках; например, вы можете записать несколько различных вариантов одной и той же части песни, а после написать код, который будет выбирать один из них случайно или в зависимости от игровой ситуации. Если вам нужна музыка статическая, то можно выбрать практически любой формат. Если вы собираетесь продавать вашу игру через Internet, я настоятельно рекомендую вам использовать трекерную музыку из-за ее небольших размеров. CD аудио и сжатая музыка, основанная на выборках, является мощными инструментами в том случае, если у вас есть деньги на создание действительно качественных звуковых дорожек профессионального уровня или если у вас уже есть написанные специализированные музыка и эффекты, и вы хотите использовать их. Если вашей целью является старое аппаратное обеспечение, то CD аудио немного лучше по той причине, что, теоретически, он не использует никаких ресурсов процессора. Иначе я бы посоветовал использовать какой-либо из кодеков, таких, как МРЗ, WMA, Ogg Vorbis и т. д. Также хочу заметить, что для независимого разработчика использование CD аудио является проблемой, так как для того чтобы слышать вашу музыку в игре, вы должны ввести некоторый отвлеченный слой в игру и «подделывать» CD звуковую дорожку, используя другой метод (что добавляет сложности в вашу систему), или вам нужно записывать новый CD-R (или, что нем ногим лучше, CD-RW) каждый раз, когда ваша музыка меняется. 7 Составная часть DirectX наряду с Direct3D, DirectSound, Directlnpuf, DjrectPlay, DirectShow. Отвечает за работу с музыкой. - Примеч. науч. ред.
28
Глава!
Нужно знать о том, что если вы используете МРЗ, то могут возникнуть проблемы с лицензированием. Обычно вы можете закодировать вещи так, что вам не придется платить за лицензию (вы рассчитываете на уже лицензированный декодер пользователя), но подобная операция использует обнуление бита контроля (см. более подробно Главу 6 «Воспроизведение MIDI/DirectMusic»). Напоследок хочу сказать, что для коммерческих игр все обычно сводится к выбору между МРЗ (для статических звуковых дорожек) и DirectMusic или вашей собственной звуковой системой для динамической музыки. Для сетевых игр выбор стоит обычно между МРЗ и трекерной музыкой. В любом случае, я надеюсь, что вам понравился этот головокружительный обзор игровой музыки. Ваш CD содержит дополнительные ссылки на страницы, которые могут дополнить то, о чем рассказывал вам я, - посмотрите их!
flokynka или создание зВуКоВого gBiukka Основная цель этой книги - это научить вас тому, как создать звуковой движок. В последующих главах вы воплотите основные и усложненные концепции в звуковой движок. И к концу книги вы будете знать, как работает звуковой движок, а также у вас уже будет один полностью работающий, который вы можете использовать в своих собственных играх. Конечно, не всем нужно писать свой звуковой движок. Триумф капитализма втом, что вы можете обменять деньги на время и купить существующий звуковой движок. Вот список коммерческих, условно-бесплатных (shareware) и свободно распространяемых (freeware) звуковых движков, которые вы можете использовать, если не хотите писать весь этот код самостоятельно. Все перечислено в алфавитном порядке - никаких предпочтений. Также вы можете найти демонстрационные версии нескольких из этих движков на вашем CD. Совет. Обратите внимание, что эта информация на начало 2003 года, какие-то мелочи могли измениться. Обращайтесь к сайту продавца за наиболее свежей информацией.
BASS Нет платы за свободно распространяемую версию 100$ за условно-бесплатную версию $995/$2750 за коммерческую версию http://www.un4seen.com/music/
Основы осноВ
29
BASS - весьма полный звуковой движок для Windows. Он поддерживает все звуковые форматы, используемые на сегодняшний день (WAV, MP3, WMA и Ogg Vorbis), требует только DirectX 3 интерфейс (так что вы можете использовать ее в NT), понимает ЕАХ и 3D звук и поддерживает наиболее популярные форматы трекерной музыки (MOD, XM, IT, S3M). Другая сильная сторона BASS - это поддержка большого количества языков. Конечно, среди них присутствуют C/C++ API, но также поддерживаются API для Visual Basic, Delphi, MASM и ТМТ Pascal. Звуковой движок BASS широко используется в настоящее время, в основном независимыми разработчиками.
MikMod Распространяется свободно http://www.mikmod.org MikMod - свободно распространяемая, многоплатформенная музыкальная библиотека, написанная Жаном-Полем Миккерсом (Jean-Paul Mikkers - MikMak) и поддерживаемая ныне Миодом Валлатом (Miod Vallat). Несколько других разработчиков также внесли свой вклад в этот проект. Движок поддерживает все популярные форматы и может запускаться множеством различных операционных систем - MacOs, BeOS, Java, Unix и конечно, Windows.
FMOD Нет штаты за свободно распространяемую версию 100$ за условно-бесплатную версию $1250 для коммерческих игр http://www.fmod.org/ FMOD - весьма полный звуковой движок, теоретически доступный на любой платформе, включая PS2, ХЬох и GameCube. Он полностью поддерживает разнообразные C++ компиляторы, как и компиляторы для Visual Basic, и включает передовые возможности, такие, как ЕАХ8 и эффекты окклюзии/обструкции9 (occlusion/obstruction). 8 ЕАХ - Environmental Audio Extensions, высокоуровневый интерфейс для учета влияния окружающей срады на звук. - Примеч. науч. ред. 9 Окклюзия - звук проходит через препятствия, обструкция - звуки задерживается препятствием. - Примеч. науч. ред.
ЗО
ГлаВа!
ModPlug Бесплатно http://www.modplug.com/ Если все, что вам нужно, - это простой и легкий способ воспроизведения трекерной музыки, то ModPtug может вам помочь, ModPlug имеет очень простой интерфейс с мощным движком воспроизведения, который поддерживает все форматы трекерной музыки, используемые на сегодняшний день. Поддерживает интерфейсы для C++, Visual Basic и Delphi, доступно управление ActiveX.
Miles Sound System $4,000 за лицензию на использование в 1 игре $12,000 за лицензию на 1 компанию-разработчика (без ограничения количества создаваемых игр) $22,000 за кросс-платформенную лицензию на 1 компанию-разработчика (без ограничения количества создаваемых игр) http ://www. radgametoo Is. com Miles Sound System, продукция RAD Game Tools, - это наиболее полная из доступных в настоящее время звуковых библиотек. Выпущенная Джоном Майлзом (John Miles) летом 1991 года, она используется более чем в 3000 (это не опечатка три тысячи) играх. Она делает все, что вы захотите. Повторяя бессмертные слова Ферриса Брюллера (Ferris Bueller), «Если у вас есть средства, то я крайне рекомендую вам ее приобрести».
Заключение Итак, вы изучили термины и информацию о происхождении, которые вам необходимы перед попыткой создать звуковой движок. Как и всегда, за большим количеством информации обращайтесь к ссылкам, которые находятся на вашем CD - они предоставят вам доступ к большому количеству интересных страниц, посвященных темам, затронутым здесь.
Часть I
Основы aBykoBoao gBudtka В этой части книги вы начнете создавать полноценный звуковой движок. Он будет написан на C++ с использованием Standard Template Library. Движок будет работать с Microsoft DirectSound и DirectMusic. В каждой из глав первой части вы узнаете, как реализуются одна или две важных функции звукового движка. Вы начнете с добавления в звуковой движок поддержки звуковых эффектов. Сначала вы познакомитесь с цифровым звуком на низком уровне и узнаете низкоуровневые функции DirectX, которые позволяют вам создавать звуковые эффекты, отправляя байты в звуковой буфер. Далее, вы узнаете, как читать и изучать WAV файлы, а также как проигрывать и зацикливать их. После того, как звуковые эффекты добавлены и настроены, вы обратите свое внимание в сторону игровой музыки, чтобы изучить, как читать и воспроизводить ряд форматов музыки. Конечно, вы узнаете, как проигрывать МРЗ, но вы также узнаеге, как читать и воспроизводить MIDI, Ogg Vorbis, и ряд форматов трекерной музыки (S3M, IT XM и так далее). К концу первой части у вас будет звуковой движок, пригодный для использования в ваших играх. Он не будет делать ничего слишком сложного -это все относится ко второй части - но если вам нужны лишь простейшие выполняемые функции, то он определенно подойдет вам для воспроизведения звуковых эффектов и музыки. Итак, приступим!
ГлаВа 2
Введение В DirectAudio Как самый длинный поход начинается с единственного шага, так и величайший звуковой движок начинается с единственной ноты, а именно, с 440Гц ноты «ля», которую вас научит создавать пример программы в этой главе. Эта глава - введение в DirectX Audio. Если вы и раньше имели дело с DirectX, многое из этого не покажется вам новым: важнейшие навыки в создании аудио интерфейса, настройке совместного режима (cooperative mode) и т. д. очень похожи на свои аналоги в DirectSD. Самые интересные части находятся ближе к концу, где я объясняю основную математику генерирования нот.
Основы DirectX Audio Я понимаю, что вы изнемогаете от нетерпения добраться, наконец, до примера кода, но сначала вы должны узнать некоторые концептуально необходимые вещи. Об этом рассказывается в первом разделе. В следующем разделе вы приметесь за код.
DirectMusic, DirectSound или DirectX Audio? Сначала был DirectSound (звук). Затем - DirectMusic (музыка). А потом, когда уже думалось, что все так и останется, Microsoft (в DirectX 8) представил новое имя - DirectX Audio (аудио). Печально то, что два из этих трех терминов неверны. Вы можете воспроизводить звуковые эффекты, пользуясь DirectMusic, и вы можете воспроизводить музыку, используя DirectSound. Забавно, правда? Вы дольше останетесь в здравом рассудке, если будете думать об этих интерфейсах не в смысле: «ОК, этот для звуковых эффектов, а этот для музыки», - а скорее, в терминах API низкого и высокого уровня. Табл. 2.1 объясняет различия между этими тремя терминами.
Введение В DirectAudio
33
Гигантская невидимая рука Microsoft заставляет нас всех пользоваться DirectMusic как единственным стоящим аудио API. Что касается DirectX 9, то о нем все еще можно говорить как об API низкого уровня (DirectSound); хотя очень часто мы используем только стандартные функции DirectMusic и не беспокоимся о низкоуровневом DirectSound API (кроме тех5- случаев, когда вы хотите получить низкоуровневый контроль над вашими WAV файлами). Я не удивлюсь, если в будущем Microsoft уберет DirectSound API и сделает DirectMusic API единственно доступным интерфейсом в DirectX Audio. Таблица 2.1. Определения терминов DirectX Audio Термин
Значение
DirectSound
Низкоуровневый аудио API для Windows. Этот API работает с деталями ввода и вывода «сырых» звуковых байт в звуковую карту и из нее
DirectMusic
Высокоуровневый аудио API. Этот API позволяет вам говорить: «ОК, я не уверен, что это файл WAV, MIDI или SGT (динамический музыкальный фрагмент), но я хочу, чтобы ты загрузил и воспроизвел его»
DirectX Audio
Название Microsoft для аудио API в целом - «дом», в котором живут DirectSound и DirectMusic
Однако остальная часть главы будет сосредоточена на DirectSound не потому, что он проще, чем DirectMusic, или с него лучше начинать изучение, а просто потому, что DirectSound позволяет вам снять все слои (layers), надстроенные Microsoft, и иметь дело только с пересылкой байт к звуковой карте. Для новичков в программировании игрового аудио это самый простой из возможных примеров, а для опытных в игровом аудио людей - это, вероятно, самый знакомый метод.
Первичный и Вторичный буферы По своей сущности, DirectSound опирается на концепцию первичных и вторичн ых буферов . Буфер в данном случае - это одномерный массив байт, который со временем станет звуком (как, объясняется в последней главе). У каждого приложения есть в точности один первичный буфер DirectSound (Primary DirectSound buffer). To, что находится в первичном буфере, это то, что периодически выходит из динамиков. 1
Я бы выделил три концепции: первичный буфер, вторичный буфер и микшер. - Примеч. науч. ред.
2-1490
Глава 2
34
Каждому приложению необходим как минимум один вторичный (Secondary) буфер DirectSound, но их можно сделать сколько угодно, а затем смикшировать вместе в первичном буфере. Вторичный буфер может содержать отдельный звук или поток аудио данных. Обычно DirectSound сам управляет этим процессом микширования, т. е. вам не приходится, как ни странно, самим иметь дело с первичным буфером. Однако вы можете получить доступ к нему, но помните, что если вы хотите прямой доступ к первичному буферу (для создания собственного микшера2), то вы не можете создавать никаких вторичных буферов. Но если вы не захотите сделать что-то очень уж странное, вам это и не понадобится. Вы заполняете данными вторичные буферы
DirectX Audio сводит вторичные буферы в один первичный буфер
Посыпает первичный буфер из звуковой карты к колонкам
Рис. 2.1. DirectX Audio соединяет вторичные буферы в первичный буфер, потом ведет звук в колонки Следует сказать, что буферы могут быть созданы на аппаратном или на программном уровне. В первом случае буфер находится в памяти звуковой карты, а во втором в системной памяти. Совет, Внастотнщее время нет никакой разницы между аппаратным и программным буферами. Давно, в девяностых, PC обладали очень медленными ISA шинами. Настолько медленными, что звуковой карте было проще хранить обычно используемые звуки в своей собственной памяти, которая находилась на самой карте. Таким образом, когда аудио чип должен был соединять звуки, ему не нужно было переносить их все через медленную ISA шину. Современные РСЛшины гораздо быстрее, до такой степени, что звуковым картам уже не имеет смысча иметь собственную память - все хранится в памяти системы и переносится через дьявольски быструю шину, когда это необходимо (хотя шины до сих пор Микшер - объект DirectSound, отвечающий за сведение аудио данных из вторичных буферов в единое целое и последующую пересылку этих данных в первичный буфер. - Примеч. науч. ред.
ВВедеаие В DirectAudio
35
еще слишком медленны для графических данных, поэтому видео карты снабжены собственной памятью). Я сильно упростил изложение всего вышесказанного; если вам нужно более техническое объяснение, прочитайте статью MSDN, «Sound Cards, Voice Management, and Driver Models» Брайана Шмидта, доступную на http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaudio/html/ntk_sc.asp.
Стриминг Теперь вы знаете, что звуковые эффекты помещаются во вторичные буферы. Но что происходит, если вторичный буфер слишком мал, чтобы вместить звуковой эффект? Например, если вы собираетесь играть цифровую музыку в течение 15-минутноЙ сцены в игре, вы не можете просто загрузить все эти 15 минут аудио данных в память за раз (т. е, вы, конечно, можете, но это заняло бы слишком много памяти), Лучшим решением было бы загрузить часть аудио данных, которая вам нужна, прямо перед тем, как она понадобится, другими словами, заставить информацию «течь» с диска. Стриминг (потоковая передача данных) - это достаточно обычная технология, которая очень хорошо работает (главным образом потому, что жесткий диск или CD-ROM может считывать байты гораздо быстрее, чем колонки их воспроизводят). Вы ближе познакомитесь с ней по мере прочтения этой книги, а сейчас просто примите это понятие так, как показано на рис. 2.2. Когда вы таким образом передаете аудио данные, вместо загрузки всех данных в память одномоментно, вы рассматриваете ваш вторичный буфер как окно, прокручивающее ваши звуковые данные, и загружаете данные по мере необходимости. Например, на рис. 2.2 вы видите, что десять секунд аудио данных (начало 3-минутной песни) загружены в буфер. Затем код заставляет буфер начать воспроизведение, и песня начинает играть. Пока она играет, компьютер читает новые данные с диска и помещает их в буфер прямо позади указателя воспроизведения, записывая их сверху на те, которые только что были сыграны. В конце концов, указатель воспроизведения перемещается снова в начало буфера, и начинают воспроизводиться новые данные. Все идет так, пока не будет воспроизведены все данные. Очевидно, весь этот процесс зависит от способности считывать данные быстрее, чем они воспроизводятся. К счастью, сейчас жесткий диск может считывать байты быстрее, чем их могут играть колонки.
Глава 2
36 Все начинается с загрузки первых 10 секунд звуковых данных в буфер и начала их воспроизведения Текущее воспроизводимое место
О sees
10 sees
Пока воспроизводится песня, новые данные загружаются в буфер прямо позади воспроизводимого места
О sees
10 sees
В конце концов, указатель воспроизведения перемещается снова в начало буфера и начинают воспроизводиться новые данные
11 sees
20 sees
Рис. 2.2. Стримннг большого звукового файла требует загрузки фрагментов непосредственно перед тем, как они понадобятся
Kak настроить Ваш Компилятор для DirectX Audio Прежде чем делать что-то дальше, вы должны узнать, как должным образом конфигурировать ваш компилятор, чтобы создавать приложения, использующие DirectX Audio. Два самых важных действия, которые вам необходимо совершить: вы должны сообщить вашему компилятору, где он может найти заголовочные файлы DirectX и где он может найти DirectX библиотеки. Этот раздел научит вас,
Введение В DirectAudio
37
как это сделать, при использовании Visual C++ версии 6. Если вы используете другой компилятор или работаете в Visual Studio .NET, известном также как Visual Studio версии 7, общий смысл будет тот же, но кнопки и меню, к которым вы должны будете обращаться, будут другими.
Глобальная или легальная Конфигурация npoekma В DevStudio существуют две различные конфигурационные установки. Есть глобальная конфигурация, которая применяется ко всем проектам, которыми вы занимаетесь на этом компьютере, и хранится внутри системного реестра, и есть конфигурация локальная (на текущий проект), которая применяется только к тому проекту, которым вы занимаетесь сейчас (хранится внутри DSP файла проекта). Локальная конфигурация занимает более высокое положение по сравнению с глобальной конфигурацией (т. е. DevStudio сначала ищет нужное ему, используя локальные настройки проекта, а затем уже, если не может найти, использует глобальные настройки).
Построение примеров в этой Книге Все примерные программы в этой книге предполагают использование вами путей DirectX, настроенных в вашей глобальной конфигурации (рис. 2.3). При установке DirectX обратите внимание на опцию, позволяющую автоматически добавлять соответствующие пути в конфигурацию DevStudio. Если вы не вьщелили эту опцию, выполняйте следующие шаги, чтобы добавить пути.
xt
•=
C:\Pn4MW FinVM ютиИ Vm* Siu*s«CSM. CWDE С:\Ргад.*ч Ffc^MiHOBUt Vma StutoWCMWFCSINClLDE CVPi oqi дп ПИЩ.ЕГОИ" Vmnl StuUn\4^a\ttT LMM O. U PC
Рис. 2.З. Глобальная конфигурация включает настройки пути
1. Выберите Options в разворачивающемся меню Tools; затем выберите ярлык Directories из появляющегося диалога (может быть, вам придется прокрутить меню, используя панель скроллинга справа).
38 2.
Глава 2 Выберите Include Files в ниспадающем окне Show Directories For...
3. Щелкните по кнопке слева от красной X, чтобы создать новый пункт, и введите полный путь к DirectX директории включаемых (include) файлов. DirectX SDK по умолчанию устанавливается в C:\dxsdk, поэтому, если вы не предписали иного местонахождения, вы должны ввести C:\dxsdk\include. Сделайте то же самое, чтобы ввести путь к директории включаемых файлов для выборок, т. е. C:\dxsdk\samples\Multimedia\Common\include. 4.
Перетащите эти две папки вверх к началу списка, заставляя DevStudio проверять эти два местоположения прежде всех других. Это важно, потому что установка DevStudio по умолчанию включает файлы «include» для старого DirectX SDK, и использование их вместо необходимых закончится ошибками типа: «LPDIRECTSOUND8 doesn't exist » (не существует).
5.
Выделите Library Files из ниспадающего меню Show Directories For и сделайте аналогичную процедуру, чтобы добавить полный путь к местоположению вашей DirectX библиотеки (обычно C:\dxsdk\lib).
6. Щелкните ОК, затем выйдите из DevStudio и перезапустите его. DevStudio сохраняет перед выходом свою глобальную конфигурацию, поэтому, если у вас войдет в привычку закрывать программу после изменения глобальной конфигурации, вы защитите себя от необходимости повторять все, что вы сделали, если (а мы все знаем, что это редкое явление) DevStudio зависнет, прежде чем вы выйдете. Мои поздравления, теперь все настроено. Если до сих пор у вас появляются неисправимые ошибки (вроде «could not open include file dsound.h: no such file or director}'») или какие-то похожие ошибки соединения, значит, вы, должно быть, неправильно создали пути. Совет. То, что приведено в этом совете, не нужно, чтобы заставить выборки воспроизводиться, но если вам для ваших собственных проектов захочется изменить локальные конфигурационные настройки проекта, вы сможете добраться до них, выделяя Settings из меню Project. Подключаемые директории могут быть обнаружены во вкладке C/C++ в пункте Preprocessor выпадающего меню, а директории библиотек — во вкладке Link в пункте Input выпадающего меню.
Создание Ваших собственных npoekmoB Когда вы работаете с DirectX, всегда есть несколько шагов, которые необходимо осуществить, для того чтобы получить основу для будущего приложения. Далее я перечислил шаги, необходимые для создания проекта Chlpl JToneGenerator. Совет. Файлы проекта CH2pI_ToneGenerator включены в ваш CD-ROM. Я привожу эти шаги лишь для справки, когда вы будете создавать уже свои собственные проекты.
Введение В DirectAudio
39
1. Выберите New из меню File, ярлык Projects и проект, который вам нужен (например, Win32 Console Application, Win32 Application или, если вы в MFC, MFC AppWizard EXE). Также настройте название проекта и его местоположение. 2.
Далее работайте с появившимся мастером настроек. Тут нет никаких особенных настроек, которые необходимы, — выбирайте, что вам нравится. Для ToneGeneratorlnput я выбрал консольное приложение «Hello World».
3. Когда закончите работать с мастером настроек, у вас будет программа, которая компилируется и работает без ошибок. Если же нет, то у вас большие проблемы - обратитесь в Microsoft и сообщите им, что их типовой исходный код не компилируется. 4.
Выберите Settings из меню Project. С левой стороны диалогового окна, в ниспадающем меню Settings, выберите All configurations.
5. Я люблю переходить во вкладку C/C++ и выбирать из выпадающего меню пункт Precompiled Headers и выбирать либо Not using precompiled headers, либо Automatic use of precompiled headers. Я думаю, для небольших проектов вроде этого, использование предварительно скомпилированных заголовков в файлах stdafx.h и stdafx.cpp есть ничто иное, как лишняя головная боль. 6.
Теперь перейдите к вкладке Link и добавьте dxerrS.lib winmm.Iib dsound.lib dxguid.lib в начало поля редактирования Object/Library modules. Эти библиотеки нужны для большинства примеров программ и большинства приложений DirectX Audio, которых вы создадите.
7.
Перейдите в категорию Input и добавьте соответствующий путь в ваши библиотеки DX в поле редактирования Library Path.
8.
Нажмите OK, чтобы убрать диалог Project Settings.
9.
Теперь перейдите в просмотр файла (fileview) и уберите stdafx.cpp и stdafx.h из проекта. Также во всех исходных файлах (на этой стадии ваш проект должен включать один срр файл) уберите строчку, включающую stdafx.h. Так как вы отключили предварительно откомпилированные заголовки в пятом пункте, эти файлы больше не нужны.
10. Stdafx.h содержал некоторые важные строчки, которые определяли WIN32JLEAN__AND_MEAN и включали stdio.h. Если они вам нужны, вам нужно добавить их в главный C++ файл или в один из ваших глобальных заголовочных файлов. 11. Удалите stdafx.h и stdafx.cpp со своего диска. 12. Убедитесь, что все до сих пор компилируется. Если же нет, то вы, должно быть, сделали какую-то ошибку или происходит что-то очень странное.
4О
Глава 2 Совет. Вы мажете легко создать приложения DirectX, выбирая DirectX AppWizard из списка проектов. Этот AppWizard покажет вам создание приложения DirectX, аналогичного, например, шаблонному DirectGraphics, включенного в ваш SDK. He используйте его для создания своей игры, его стоит использовать, только если вы хотите быстро получить каркас приложения, чтобы проверить что-либо. Даже после того как вы удалили все следы stdafa.h, он может сохраниться в папке External Dependencies. Эта папка обновляется, когда компиляция осуществляется без ошибок, поэтому просто откомпилируйте свой проект, и stdafa.h исчезнет.
ToneGenerator: простой пример DirectX Audio Теперь, когда ваш компилятор настроен, вы, наконец, можете браться за первый пример программы DirectX Audio! Если у вас есть желание, то читайте далее, загрузив Ch2pI_ToneGenerator в вашу IDE . Звуковой фрагмент. Проиграйте музыкальный фрагмент 2.1, чтобы услышать результат примерной программы этой главы.
Что makoe ToneGenerator? ToneGenerator - это простое консольное приложение, сделанное для того, чтобы показать основы инициализации и размещения звука с помощью DirectX Audio. Задача ToneGenerator - научить вас основам использования DirectX Audio, а также, на базовом уровне, сущности поведения звука. Это не так уж сложно и не так уж нужно, но я думаю, это помогает отбросить все условности и показать вам, на начальном уровне, как создается цифровой звук. ToneGenerator создает звук определенной частоты. Он делает это путем подсчитывания байт для синусоидальной волны и помещения их во вторичный буфер, который затем соединяется DirectX Audio с первичным буфером и посылается к динамикам. Это консольное приложение - никакого GUI кода не нужно. ToneGenerator не делает ничего необычного, вроде пилообразных или квадратных волн, хотя, если вы разберетесь в математике, которая за этим стоит, я помогу вам добавить их.Посмотрите на рис. 2.4, если вы не уверены, что знаете, как выглядит синусоидальная волна и что значат термины «амплитуда», «период» и «частота» (на рисунке показан размах (двойная амплитуда). - Примеч. науч. ред.) Несколько лет назад, для какого-нибудь хакера эта программа была бы неправдоподобно полезна , но я не собираюсь объяснять вам, как и почему. Также, чтобы они не натравили на меня законников, я должен был обязательно упомя3
4
IDE (Integrated Development Environment) - интегрированная среда разработки. - Примеч. перев.
Очевидно чтобы дурачить системы телефонных компаний путем генерирования ложного ответа на запрос определителя номера АТС. На западе, как известно, используется тоновый набор. - Примеч. науч. ред.
Введение В DirectAudio
41
Период Период = 1/частота Частота 5 Гц = Период 1/5 секунды
Рис. 2.4. Синусоидальная волна нуть, что я не потворствую причинению ущерба национальной телефонной системе или попыткам обмануть телефонную компанию, даже если это самые неумело управляемые организации, которые вы когда-либо видели. Пожалуйста, используйте эту скромную программу для добра, а не для зла. Я готов поспорить, что сейчас у вас кружится голова от волнения. Программа с таким «дисклаймером» должна быть классной, не правда ли? А вот теперь - как она работает!
Инициализация DirectSound Это первый и самый важный шаг. Инициализация состоит из трех главных частей: создание интерфейса, настройка уровня кооперации (cooperative level) и настройка структуры первичного буфера.
5
6
Disclaimer - официальный отказ от несения ответственности. - Примеч. перев.
Уровень кооперации кооперации показывает, как программа использует ресурсы: делясь ими с другими приложениями или только сама. - Примеч. науч. ред.
42
Глава 2 Совет. Когда вы вызываете SetCooperativeLevel, один из требуемых параметров — это дескриптор главного окна вашего приложения. Так как генератор звука запускается, как консольное окно, он использует некоторую черную магию Windows, чтобы получить свой HWND. Используйте функцию GetConsoleWindowHandle в примерной программе Ch2pl_ToneGenerator. cpp. Эта функция спрашивает у Windows его название (заголовок окна), а затем находит первый дескриптор окна, который имеет такой заголовок. Это отчасти окольный путь к получению дескриптора, но только он работает, если есть только одни запущенный экземпляр приложения. Как бы то ни было, с целью создания примерной программы, это работает как заклинание.
Первые два шага очень просты, вы можете пропустить всю канитель перечисления (enumeration) звуковых карт и выбора одной из них и объявить NULL первым параметром DirectSoundCreateS (или посмотрите Главу 4, чтобы узнать сложные подробности перечисления устройств). Настройка уровня кооперации аналогично проста: для большинства игр достаточно использовать DSSCL_PRIORITY, который позволит вам настроить формат первичного буфера. Если вы пишете профессиональное аудио приложение, вроде Winamp или ACiD, вам лучше использовать DSSCL_NORMAL, который не позволяет вам менять формат первичного буфера, но существенно облегчает быстрое переключение между приложениями и в целом гораздо лучше для системы, чем DSSCL_PRIORITY. Вот, в общем-то, то, что касалось задачи выбора формата первичного буфера. Это немного сложно, потому что вам нужно использовать интерфейс первичного буфера, как показано в следующем коде. HRESULT hr; LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
// Получаем первичный буфер
DSBUFFERDESC dsbd; 2eroMemory( Sdsbd, sizeof{DSBUFFERDESC) ) ; dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
// "создаем его" (Или получаем его, если он уже существует, что и есть на самом деле)
i f ( F A I L E D ( h r = g_pDS~>CreateSoundBuffer(Sdsbd, SpDSBPrimary, N U L L ) ) ) { cerr « "CreateSoundBuffer failed:" « DXGetErrorStringS (hr) « endl; return(false);
} // Устанавливаем его формат WAVEFORMATEX wfx; ZeroMemory( Swfx, sizeof(WAVEFORMATEX) ); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = (WORD)2; wfx.nSamplesPerSec = 22050; wfx.wBitsPerSarnple = (WORD) 16; wfx.nBlockAlign = '(WORD)(wfx.wBitsPerSample/8*wfx.nCharmels); wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
Введение В DirectAudio
43
if(FAILED(hr - pDSBPriraary->SetFormat(bwfx))) ( cerr « "pSDBPrimary->SetFormat() failed: " « DXGetErrorStringS(hr) « endl; return(false);
} // Мы закончили, поэтому освобождаем буфер! SAFE_RELEASE( pDSBPrimary );
Здесь вы видите код, выполняющий три действия. Во-первых, он получает первичный буфер. Метод, который он использует для этого, называется CreateSoundBuffer. CreateSoundBuffer берет структуру и, в зависимости от флагов в этой структуре, или создает вторичный буфер и возвращает его интерфейс, или возвращает интерфейс первичного буфера. В этом случае мы получаем интерфейс первичного буфера и сохраняем его в pDSBPrimary. Теперь нужно заполнить структуру WAVEFORMATEX свойствами аудио формата, которые вам нужны. В нашем случае это 2 канала (стерео), 22,050Гц (половина CD качества!), с 16 битами на выборку. Это значит, что на каждую секунду звука у нас получается 22,050 выборок, в каждой из них по 16 бит (2 байта), поэтому в итоге мы тратим 44,100 байт на секунду звука. 22,050Гц-это частота «среднего» качества; для сравнения, музыка на CD записывается с частотой выборки 44,100Hz. Ознакомьтесь с самыми распространенными частотами дискретизации с помощью табл. 2.2. После того как wfx правильно заполнен, код обращается к методу интерфейса первичного буфера. Он превращает первичный буфер в то, что установлено в wfx. Таблица 2.2. Стандартные звуковые частоты выборки Название Windows
Частота выборки
Биты
Каналы
N/A
8,000Гц
8
1 (моно)
Телефонное качество
1 1 ,025Гц
8
1 (моно)
Радио качество
22,050Гц
8
1 (моно)
CD качество
44,010Гц
16
2 (стерео)
В заключение обратите внимание на то, что код освобождает интерфейс буфера вызовом макроса SAFE_RELEASE. Обратите внимание, что это действие, на самом деле, не удаляет первичный буфер, а просто освобождает интерфейс, созданный нами вызовом CreateSoundBuffer. Объект DirectX (и, вообще-то, любой объект
44
ГлаВаЗ
СОМ) удаляется, только если все его интерфейсы освобождены, а поскольку DirectX Audio сохраняет внутренний интерфейс для первичного буфера, это не происходит до тех пор, пока код сам не деинициализирует DirectSound. Однако если этот код не освободит интерфейс, то теоретически первичный буфер никогда не будет удален и произойдет утечка памяти (memory leak). В действительности, DirectX Audio достаточно разумен для того, чтобы зафиксировать, если такое произошло, и привести все в порядок, но, не взирая на это, так делать неправильно; никогда не забывайте освобождать первичный буфер после того, как вы закончите работать с ним.
Создание Вторичного буфера Когда DirectSound проинициализирован, нужно создать вторичный буфер. Напомню, что вторичный буфер - это место фактического хранения аудио данных, DirectSound использует первичный буфер для соединения всех вторичных буферов вместе, перед тем как они отправляются к звуковой карте. Приятно, наверное, будет узнать, что, так как вызывается та же самая функция, вы уже знаете 90 % того, что нужно знать о создании вторичного буфера. Для того чтобы его создать, вызовите метод CreateSoundBufFer, как это показано в следующем коде. int CreateSecondaryBuffer(LPDIRECTSOUNDBUFFER *ppBu£fer, int channels, int sees, int samplerate, int bitspersample, DWORD flags} { HRESULT hr; stringstream err; DSBUFFERDESC dsbd; WAVEFORMATEX wfx; Z,eroMemory( Swfx, sizeof (WAVEFORMATEX) ); wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nChannels = channels; wfx.nSamplesPerSec = samplerate; wfx.wBitsPerSample = bitspersample; wfx.nBlockAlign = (WORD) (wfx.wBitsPerSample / 8 * wfx.nChannels); wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) ); dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = flags; dsbd.dwBufferBytes = samplerate * bitspersample / 8 * channels * sees; dsbd.guid3DAlgorithm = GUID_NULL; dsbd.IpwfxFormat = Swfx; if(FAILED(hr = g pDS->CreateSoundBuffer(fidsbd, ppBuffer, NULL))]
ВВедемие В DirectAudlo
{
45
err « "CreateSecondaryBuffar: CreateSoundBuffer failed: " « DXGetErrorStringS{hr} « endl; throw(err.str());;
} return(dsbd.dwBufferBytes); // возвращаем размер буфера }
Большая часть этого кода описывает заполнение структуры DSBUFFERDESC, которая конкретно объясняет DirectSound, что вы хотите создать. Назначение большинства членов структуры DSBUFFERDESC очевидно, но если что-то вам непонятно, наберите DSBUFFERDESC в индексе справочного файла DirectX SDK, и вы окажетесь на страничке, где все будет подробно объяснено. Код создает буфер, используя флаг WAVE__FORMAT_PCM. Этот флаг говорит DirectSound, что формат данных в буфере будет в модуляции импульсного кода (pulse code modulation (PCM)). Когда вы создаете обычный звуковой эффект, без всякого сжатия и забавных кодеков, вы подразумеваете РСМ. Создание буфера РСМ требует некоторых вычислений. Например, посмотрите на выравнивание блоков, nBlockAlign, структуры волнового формата. Вероятно, оно выглядит довольно странно, но оно правильно, потому что в документации говорится, что для буфера с форматом РСМ nBlockAlign должен быть равен произведению nChannels и wBitsPerSample, разделенному на 8. Тут вы пожмете плечами и скажете: «Ну, это было просто». Какой-то части кода DirectSound это необходимо, поэтому вам действительно проще дать ей то, что ей нужно, нежели разбираться, почему это так. Совет. Обратите внимание на обработку ошибок, включенную в код создания буфера. Вам следует всегда использовать макрос FAILED для проверки возвращаемого значения интерфейсных методов DirectSound. Если что-то будет неправильно, макрос возвратит true, давая вам возможность предпринять соответствующее действие. Большей частью, вы захотите использовать функцию DXGetErrorStringS для того, чтобы ошибочный код, полученный вами, всегда сопровождался наглядным сообщением с описанием ошибки. Двигаясь далее, посмотрите вычисления для члена dwBufferBytes структуры описания буфера. Обычно, когда вы имеете дело с WAV файлом, который вы загружаете с диска, вы знаете, сколько байт вам потребуется, но этот пример программы генерирует звуковые данные самостоятельно, поэтому она должна вычислить это количество. Переменные samplerate, bitspersample, channels и sees являются аргументами функции Create Secondary Buffer. Функция перемножает их, чтобы определить общее количество байт, необходимых выборке, затем помещает это число в dwBufFer-
46
Глава 2
Bytes. За каждую секунду звука, получаем samplerate выборок, поэтому получаем sees * samplerate. Количество каналов выборки сохраняется в channels, и на каждый из этих каналов есть bitspersample бит, поэтому получаем channels * bitspersample / 8 байт на каждую выборку. А теперь напишите все это вместе, и вы получите длинную цепь перемножений, показывающую, почему все-таки так полезно МРЗ сжатие; без него, стерео (двухканальная), CD качества (16 бит на выборку, 44,010Гц частота) трехминутная песня занимает 44,010X16/8x2x180, или 31,687,200 байт места. Это около 31 мегабайта. Совет. Будьте осторожны, когда производите вычисления, включающие количество бит па выборку. Обычно все забывают поделить на 8 при переводе из «бит на выборку» в «байт на выборку». Если вы забудете поделить на восемь при подсчете размера буфера, вы рискуете зря потратить память, выделив ее на этот буфер в восемь раз более необходимого. Если же вы забудете поделить на восемь в других местах, могут случиться гораздо более неприятные вещи. Защитите себя от себя самого, давая своим переменным длинные, информативные имена. Переменная, называющаяся bitspersample, говорит вам, что нужно разделить на восемь. Переменная, названная bps, гораздо более опасна, потому что вы не знаете, имеются ли в виду под «Ь» биты или байты.
Возвращаясь к коду, после того как структура DSBUPFERDESC заполнена, код вызывает CreateSoundBuffer метод IDirectSoundS, который создает буфер и помещает его интерфейс в ppBuffer. Обратите внимание на двойное перенаправление. ppBuffer это указатель на указатель на IDirectSoundBuffer (Помните, LPDTRECTSOUNDBUFFER - это на самом деле IDirectSoundBuffer*, поэтому LPDIRECTSOUNDBUFFER*- это на самом деле IDirectSoundBuffer**. LP происходит из венгерской нотации и означает Long Pointer, поэтому LPDIRECTSOUNDBUFFER это просто длинный указатель.)
Создание шума После того как вы создали вторичный буфер, вы должны заполнить его данными. Для того чтобы это сделать, прежде всего, надо «запереть» буфер (вызовом Lock метода из IdirectSoundBuffer). Lock сообщает DirectSound, что вы готовы начать модифицировать содержимое буфера. Когда вы вызываете Lock, вы получаете указатель на сам буфер. Если bitspersample равен восьми, вы должны привести указатель, который вам выдаст DirectSound, к unsigned char*. Теперь можете заполнять буфер байтами. После того как вы это сделаете, вы должны будете «открыть» буфер (посредством вызова Unlock). Вот как выглядит код этого процесса.
Введение В DirectAudio
47
void FillBuffer(LPDIRECTSOUNDBUFFER pBuffer, float frequency, int buffersize, int samplerate) { HRESULT hr; stringstrearn err; unsigned char *pBufferBytes; DWORD lockedsize; // Запираем буфер if
pBuffer->Unlock( pBufferBytes, lockedsize, NULL, 0 ) ;
}
Код начинается с замыкания буфера посредством вызова Lock метода из IdirectSoundBuffer. Он передает первый байт, который он хочет модифицировать (zero), и общее число байт, которое он хочет модифицировать (т. е. buffersize, потому что он воздействует на весь буфер). Возможно, вас удивит, что, когда вы запираете что-то с помощью Lock, вы получаете не один, а целых два указателя. Это должно помочь вам при стриминге (рис. 2.5). DirectSound позволяет вам задать начальный байт и длину таким образом, чтобы она превысила длину вашего буфера. Например, вы вполне можете определить 100байтовый буфер, а потом запросить 50 байт, начиная с 75. Вы, должно быть, думаете, что это что-то неправильное, потому что, если мы начнем с 75 байта, у нас остается только 25, но, вместо этого, DirectSound «прокручивает» буфер. Если вы запрашиваете 50 байт, начиная с 75 байта в 100-байтовом буфере, вы получите от DirectSound два указателя. Первый указатель указывает на область с 75 по 99 байт. Второй указатель указывает на область с 0 до 39 байта.
Глава 2
48
Не правда ли, замечательно? Автоматически прокручивая для вас буфер, DirectSound освобождает вас от необходимости создавать два цикла замыкание/заполнение/размыкание. Запрос на 50 байт, начиная с 75 байта
\
Байт О
Байт 0, дп. 25
Байт 99
Байт 75, дл. 25
Байт О
Байт 99
A DirectX возвращает вам два указателя, один для 75-99 байт, а второй для 0-24 байт. Рис. 2.5. Когда вы что-либо запираете с помощью Lock, вы должны получить два указателя
Если то, что вы запираете, не будет прокручиваться, вы можете просто обнулить второй указатель и использовать только первый. Именно это и делает пример программы генератора звуков. Как бы там ни было, когда вы будете использовать стриминг звука, вы захотите передать Lock методу два буфера и два размера. Обращайте внимание, что он вам возвращает, чтобы вы легко могли управлять ситуацией с прокручиванием. Совет. Метод Lock допускает использование пары полезных флагов. DSBLOCK_ENT!REBUFFER говорит, что нужно запереть целый буфер. Я не использовал этот флаг в примере программы, потому что хотел подробно показать вам, как запирать байты, но в обычном приложении вам нужно будет его использовать вместо выставления в ноль первого байта и размера буфера в качестве длины. Второй флаг - это DSBLOCK_FROMWRITECURSOR, который сообщает DirectSound, что вы хотите начать писать с того места, где в прошлый раз закончили. Это похоже на запись в файл и очень практично при стриминге.
Введение В DirectAudio После того как буфер заперт, начинается настоящее веселье. Для того чтобы получить звук, этот пример программы заполняет буфер данными, генерированными с помощью функции синуса. Если вы уже давно работали с тригонометрией, то могу напомнить, что функция синус возвращает значение от -1 до 1, совершая при этом колебание. Довольно сложно правильно получить частоту волны. Для того чтобы это сделать, существует небольшой алгоритм. В первую очередь в этом алгоритме мы определяем, на каком волновом цикле мы находимся, т. е. сколько полных периодов прошло с начала/ Если pos - это полное число, то это начало нового периода. Взгляните на рис. 2.6, чтобы это представить визуально. pos - целая часть от pos = 0,54 *
pos=0
pos=1
pos=2
>
pos=2,54
Рис. 2.6. Использование дробной части роз для определения, где мы находимся
В этом коде выражение pos - floor(pos) равно «остатку» pos, т. е. числу, показывающему, в каком месте конкретной волны мы находимся (всегда между нулем и единицей). Например, если pos равно 2,54 (что значит, что вы прошли уже две полных волны и чуть больше половины третьей), тогда floor(pos) равно 2, а г (reminder) равен 0,54. Обратите внимание, что вне зависимости от того, насколько длинна или коротка данная волна, вы всегда получите остаток между нулем и единицей. И не нужно ничего делать с частотой, потому что вы ее уже учли, когда определяли pos в цикле. В этом красота этого алгоритма. Начиная отсюда, все просто: подставьте полученное значение г в функцию синус, чтобы получить высоту волны этой точки между -1 и 1. Далее нужно умножить это число на 127, делая волну — высокой максимально. Вспомните, что
5О
Глава 2
высота волны - это громкость звука, поэтому, умножая на 127, мы даем звуку максимальную громкость. Если вы хотите сделать его в два раза тише, то умножать следовало на 64. Теперь высота где-то между -17 и 127 (или от -64 до 64, если вы уменьшили громкость). Последним шагом нужно сместить волну, чтобы она попала в промежуток для unsigned char (от 0 до 255) Для этого мы просто прибавляем 127. Совет. Звуковой генератор мог использовать косинус вместо синуса при вычислении для буфера. Я предпочел использовать синус, потому что он начинается в нуле. Далее, если вы заканчиваете делать нормальный звуковой генератор, а не простой пример программы, всегда делайте размер буфера кратным волновой частоте, которую вы генерируете. Таким образом, вы гарантируете видимость того, что последний байт буфера точно совпадает с первым. В этом примере, из-за выбранной > частоты, буфер не может так образовывать цикл, чтобы это было незаметно, поэтому вы услышите небольшой клик или щелчок, когда буфер будет проходить через конец цикла. Итак, код делает это для каждого байта вторичного буфера. После того как программа пройдет цикл, она отпирает буфер вызовом метода Unlock, используя указатели и размеры, переданные методом Lock. Нет никаких доступных флагов Unlock просто отпирает то, что раньше вы закрыли, Совет. Пример программы ToneGenerator не поддерживает переключение заданий, но, если вы хотите, чтобы ваша программа поддерживала, не забывайте, что необходимо восстанавливать буферы, когда пользовательское задание переключается снова на игру. Это выполняется с помощью метода Restore, принадлежащего интерфейсу IdirectSoundBuffer. За примерам, как восстанавливать «красиво», обратитесь к примерам программ DirectX SDK, DirectSound.
Проигрывание буфера После того как мы пробились через все сложности создания и заполнения буфера, вам будет приятно узнать, что заставить вторичный буфер смикшироваться в первичный и ифать через колонки очень просто. Все, что вам нужно сделать, это вызвать метод Play буфера. void PlayBuffer(LPDIRECTSOUNDBUFFER pBuffer) {
pBuffer->Play(Q, 0, DSBPLAY LOOPING);
}
Первый параметр Play зарезервирован для использования в будущих версиях DirectSound и должен быть выставлен в ноль. Второй параметр - это значение приоритета, но он требует специального типа буфера (созданного с флагом DSBCAPS_LOCDEFER). Так
Введение В DirectAudio
51
как вы создали вторичный буфер без этого флага, вам не нужно определять приоритет (он нормальный) - просто введите ноль. Третий параметр - это вездесущий параметр флагов. И снова, если у вас есть DSBCAPS_LOCDEFER буфер, должно присутствовать множество всевозможных флагов, из которых можно выбирать (обращайтесь к помощи DirectX SDK за полным списком), но так как используется простой буфер, то единственным доступным флагом является DSBPLAYJL0OPING, который сообщает DirectSound команду прокручивать буфер бесконечно. Чтобы остановить воспроизведение звука, используйте метод Sto]4. Stop не требует никаких аргументов, он просто заставляет замолчать буфер, который воспроизводится.
Освобождение памяти Как только пользователь услышал звук и нажал клавишу, чтобы остановить его, код начинает освобождать ту память, которую мы предварительно заняли, // освобождаем вторичный буфер
cout « "Releasing sound buffer..." « endl; SAFE_RELEASE(soundbuf); // деинициализируем DirectAudio cout « "Un-Initializing Audio..." « endl; SAFE_RELEASE(g_pDS);
Программа выделила память только под две вещи: вторичный буфер и собственно интерфейс DirectSound (который также содержит.первичный буфер). Если вы никогда раньше его не видели, то вот как примерно выглядит макрос SAFE_RELEASE (он объявлен в dxutil.h). #define SAFE_RELEASE(p)
{ if(p) { (р)->Release{); !p)=NULL; } )
SAFE_RELEASE смотрит на данный ему интерфейс и, если он не NULL, освобождает его (вызывая метод Release) и после этого выставляет его в NULL. Таким образом, если кто-то попробует использовать его снова, ваша программа вызовет нарушение доступа (access violation) и у вас будет возможность увидеть, что идет не так. Это стандартный прием при использовании DirectX, и вам следует использовать SAFE_RELEASE вместо того, чтобы напрямую обращаться к методу Release. Также убедитесь, что вы освобождаете память в правильном порядке. Вы должны освободить все вторичные буферы до того, как вы освободите основной интерфейс DirectSound. Если вы сделаете не так, то DirectSound предупредит вас об этом (через отладочный вывод (debug output)), так как он считает, что если буфер не освобожден на момент завершения работы интерфейса, то про него забыли.
52
Глава 2
Заключение Я надеюсь, вам понравился тот беглый обзор DirectSound, который предоставила вам программа генерации звука. Не забывайте, что, вероятнее всего, вы никогда не будете использовать эту программу в настоящей игре. Как вы узнаете в последующих разделах, DirectX предоставляет вам гораздо более высокоуровневые элементы управления, чем этот - вам никогда не нужно будет создавать ваши собственные звуки на конкретных частотах, чтобы делать музыку. Если бы вы создавали игру и вам хотелось, чтобы там была классная ретро-музыка (типа трекерной или FM синтеза), вы могли бы использовать этот код, чтобы создать движок-синтезатор, но тогда вам пришлось бы не раз изобретать велосипед. В последующих главах вы узнаете, что велосипеды уже существуют и как ими пользоваться. Пример программы преследовал две цели: показать вам основы инициализации и использования DirectSound и показать, что происходит на низком уровне, когда вы проигрываете звук. Когда вы запираете буферы и работаете со звуком в виде байтового потока, вы находитесь на очень низком уровне. Я решил показать эту программу первой, чтобы вы знали, как все происходит на самом деле. Я обнаружил, что, если понимать основу чего-либо, то разбираться с более высокими уровнями гораздо легче. Примите поздравления с написанием вашей первой DirectSound программы. Далее вы узнаете, как использовать высокоуровневые интерфейсы DirectX Audio для решения с других задач музыки и звука.
Глава 3
Воспроизведение Волнового sByka Начиная с этой главы, вы начнете писать настоящий код работы со звуком. Предыдущие две главы говорили о фундаментальных вещах, которые давали твердое чувство понимания основ, но которые не были так уж необходимы нам в реальной игре. Как бы там ни было, начиная с этой главы, вы узнаете практические методы, которые сможете применять непосредственно в ваших собственных играх. В этой главе мы рассмотрим пример программы, с которой начнем создание современного C++ звукового движка. Несколько следующих глав рассматривают разработку этого движка, и к их завершению у вас будет полностью рабочий игровой звуковой движок, который вы можете вставлять в ваши проекты (или использовать как отправной пункт для создания вашего собственного движка). Но прежде чем вы сможете этим всем заняться, нужно разобраться с несколькими новыми понятиями, включающими устрашающую, засасывающую программистов трясину, DirectMusic.
SBykoBbie эффеКты с DirectMusic Для большинства создателей игр работа с DirectMusic - это достаточно сложный кусок кода. Помню, что, когда я только начинал изучение, я сильно запутался и очень часто расстраивался. Это было пару версий DirectX назад, и, к чести Microsoft, они уже немного все упростили, но все равно получилось еще достаточно сложно. Все так сложно, потому что очень насыщенно. DirectMusic - это одна из тех библиотек, которые содержат почти все. Она воспроизводит wave файлы. Она забирает wave-файлы с диска. Она воспроизводит MIDI. Она воспроизводит динамическую музыку. Да что там, она даже помогает сочинять музыку. Существует совсем немного вещей, которые не сможет сделать DirectMusic.
54
Глава 3
Все это делает ее невероятно мощной, но при этом временами и невероятно сложной. У меня есть поговорка: «большие библиотеки делают сложные вещи простыми, а простые вещи - сложными». Например, достаточно просто забрать файл с диска: нужно просто загрузить его, получить интерфейс сегмента и скомандовать интерфейсу воспроизведения, чтобы он проиграл этот сегмент. В DirectSound получить wave файл с диска сложнее, как вы это увидите позже в этой главе. Это один из примеров «сложного, превращенного в легкое». С другой стороны, было то, что сводило меня с ума, -явление, из-за которого мне хотелось вышвырнуть свой монитор в окно, - регуляторы громкости. Я долго не мог найти регулятор громкости в DirectMusic, а когда, в конце концов, все-таки нашел, то обнаружил, что он не один. Есть, по крайней мере, с полдесятка различных способов управления громкостью, и если хотя бы одним регулятором все выключить, то вы ничего не услышите. Иногда простые вещи становятся сложными. Мой вам совет: не торопитесь и убедитесь, что вы точно поняли, как работает одна вещь, прежде чем переходить к другой. DirectMusic - это огромный API, но он не является чрезмерно сложным, просто в нем есть несколько хитрых понятий и он не перегружен сложной математикой. Этим он отличается, скажем, от DirectGraphics, где, если вы не знаете линейную алгебру, вы приезжаете на гонку уже со спущенной шиной. В течение нескольких следующих глав я постараюсь облегчить вам путь через DirectMusic. В этой главе вы узнаете основные понятия, которые вам необходимо знать, чтобы использовать DirectMusic для создания звуковых эффектов. В следующих главах вы будете постепенно узнавать подробности. В главе 5 «Контролируемая неконтролируемость» вы узнаете об интерфейсах, используемых для воспроизведения музыки, а во второй части книги - об интерфейсах для динамической музыки. А сейчас начинайте, поглядывая на рис. 3.1 по мере прочтения следующих разделов.
Loader Все начинается с чтения wave файла с диска. В DirectMusic это делается посредством объекта Loader (загрузчик). Вы получаете доступ к объекту Loader через интерфейс IDirectMusicLoaderS. Loader - это один из недооцениваемых объектов DirectMusic. Загрузка wave файла - это сложная задача, как вы увидите в этой главе далее. Loader заботится о всех сложностях загрузки wave файла: выяснении, был ли он сжат и как; чтении байт сохранении их в памяти. Вы просто говорите: «Загрузчик, загрузи, пожалуйста, этот wave файл, создай для него объект и возврати мне интерфейс этого объекта».
Воспроизведение Волнового aByka
53
WAV файл на доске
Загрузчик считывает WAV файл с диска и создает сегмент, содержащий звук
Загрузчик
Для того чтобы сегмент был воспроизведен, он передается объекту исполнения
Сегмент
Объект исполнения
Устройство вывода звука
Объект исполнения воспроизводит сегмент и возвращает интерфейс состояния сегмента для играющего сегмента
Состояние сегмента
Вы можете использовать состояния сегмента для определения того, что происходит сейчас с воспроизводимым сегментом
Рис. 3.1. Общий обзор интерфейсов DirectMusic, используемых для воспроизведения звуковых эффектов
Loader - это весьма мощное средство. Вы можете указать ему, где искать вспомогательные файлы, вы можете заставить его читать из ресурса, связанного с вашим ЕХЕ, и вы можете даже написать специальные загрузочные объекты, чтобы научить его загружать ваши собственные звуковые форматы.
56
Глава 3
Segment **
Когда вы говорите загрузчику, чтобы он что-то загрузил, он создает сегмент (segment) DirectMusic и наполняет его тем, что находит на диске. Вы получаете доступ к сегменту DirectMusic с помощью интерфейса IDirectMusicSegmentS. Сегмент может содержать любые аудио данные: wave файлы плюс любое количество музыки. Любой сегмент может содержать многочисленные треки (которые вы можете воспроизводить с помощью интерфейса IDirectMusicTrackS). Представьте себе трек как единое музыкальное содержание песни. Откройте сборник мелодий для пианино, и вы увидите, что мелодия состоит из двух треков: один, верхний, играется правой рукой (обычно скрипичный ключ), а нижний - левой. Обычная мелодия может содержать трек для басов, ударных, струнных и вокала (или мелодии.) Когда вы загружаете wave файл в сегмент, у вас получается сегмент с одним треком. Обычно вы не имеете дело непосредственно с треками, но полезно знать, что они там есть. Есть много способов создать сегменты, их не обязательно загружать. Например, если вы используете динамическую музыку, для вас сегменты создает DirectMusic. Вы обеспечиваете продвижение аккордов, ритм, инструменты и мелодию, a DirectMusic создает для вас сегмент. Сделайте это правильно, и вы никогда не замучите своего игрока мелодией, которая будет повторяться снова и снова. В противоположность тому, что вы, должно быть, подумали, интерфейс IDirectMusicSegmentS не обладает методом Play. Вы не можете просто сказать сегментам, чтобы они воспроизводились. Чтобы что-то услышать, нужно использовать другой объект - объект Performance (исполнения).
Performance Объект Performance (исполнения) - это главный объект, который контролирует все остальные объекты. Когда я хочу послушать музыку, я покупаю билет, который дает мне право услышать представление. Представление (стоит надеяться) включает несколько мелодий, исполняемых на нескольких разных инструментах, воспроизводимых из разных динамиков с использованием разных эффектов. Аналогично., когда я использую интерфейс IDirectMusicPerforrnanceS, я могу использовать его для воспроизведения сегментов, контролировать, из каких они будут слышны динамиков, и прибавлять темп, громкость и другие эффекты, которые влияют на воспроизведение сегмента.
Воспроизведение Волнового aByka
57
Все, что вам нужно сделать, чтобы заставить какой-то конкретный сегмент воспроизводиться, - это сказать: «Объект исполнения, пожалуйста, воспроизведите этот сегмент». Вы также можете попросить и настроить разного вида управления выдержкой времени. Например, вы можете спросить у объекта Performance, каково текущее время задержки (это время, которое проходит между тем, как вы говорите «играй!» и тем, что звук начинает выходить из колонок).
Другие объеКты Как упоминалось ранее, DirectMusic обладает множеством других объектов и интерфейсов, но о тех трех, о которых вы только что узнали: Loader (загрузки), Segment (сегмента) и Performance (исполнения), - вам уже нужно знать. Другие объекты в большей степени используются при воспроизведении музыки, и вам нужно подождать до главы 5, чтобы о них. узнать. Теперь, когда вы ознакомлены с главными интерфейсами этой главы, вы можете начинать учиться, как скручивать, укрощать и использовать их для конструирования основы звукового движка. Но в первую очередь вам необходимо узнать, как обрабатываются ошибки,
O6pa6omka ouiu6ok В зВуКовом gBiukke Одна из самых главных черт любого движка - это его надежность, т. е. сопротивление разрушению под воздействием чрезвычайных условий. Конечно, всегда важно убедиться, что ваш код не ломается, но, когда вы имеете дело с движками, это особенно важно. Даже если вы не собираетесь продавать свой движок за полмиллиона долларов и он предназначен только для вас, я гарантирую, что вы забудете обо всех сложностях, связанных с ним, к тому времени, как закончите его и начнете разрабатывать игру, которая его использует. Совсем неинтересно переучивать, как работает API или движок, находясь уже на полпути написания реальной игры. Поэтому потратьте на это время заранее и включите в программу хорошую обработку ошибок. Первый способ, которым это можно сделать, - это использование обработки исключительных ситуаций C++. Если вы никогда раньше не слышали об обработке исключительных ситуаций, вам следует здесь остановиться и выяснить, что это такое, прежде чем продолжать. С этого момента я буду предполагать, что вы понимаете операторы try, throw и catch. Если они для вас новы, то используйте одну из приведенных на вашем компакт-диске ссылок, чтобы узнать о них.
58
Глава 3
Стоит ли использовать обработку исключительных ситуаций? Я знаю много программистов, и все они по-разному отзываются об обработке исключительных ситуаций. Некоторые из них говорят, что это единственный способ написать безошибочный код. Другие настаивают на том, что обработка исключительных ситуаций - это работа дьявола, так же, как goto и непродуманные оптимизации. Защитники говорят, что обработка исключительных ситуаций делает ваш код более крепким и проще поддерживаемым. «Ученые мужи» провозглашают, что это способствует неряшливому, раздутому коду и ленивому программированию. Когда дело доходит до обработки исключительных ситуаций, я предпочитаю оставаться где-то между этими двумя лагерями, слегка склоняясь к «крепости защитников».
Правила, чтобы ВыЖить, используя o6pa6omky исключительных ситуаций Я думаю, что обработка исключительных ситуаций сделает ваш код проще в чтении и в написании, если вы будете соблюдать некоторые золотые правила. 1. Если вы пишете throw, пишите и catch. 2.
Используйте throw, когда возникает непредвиденная операция, а не коща происходит что-то плохое, но вероятное.
3.
Пишите для ошибок catch так рано, как это возможно, но не раньше.
Золотое правило номер один Если вы пишете throw, пишите и catch. Другими словами, когда вы пишете блок, где могут появиться ошибки, вы должны также озаботиться написанием блока, где они будут отслеживаться, и решить, что будет происходить при их обнаружении. Любая возникшая ошибка, которая не обнаружена вашей программой, — это «необработанная исключительная ситуация», которая заставляет вашу программу работать медленнее, выдавая сообщающий об ошибке диалог наподобие того, представлен на рис. 3.2. Я видел много хороших программистов, которые наудачу писали код, который разваливался только потому, что сравнительно безобидная исключительная ситуация «ускользала», не будучи обнаруженной.
Воспроизведение Волнового sByka
n Ch^LjMWUybKk.e
7363: иооьЛ с*+
Рис. З.2. Что происходит, когда вам не удается обнаружить исключительную ситуацию
Это правило также очень важно, когда вы имеете дело с большим базовым кодом, который не использует исключительных ситуаций.
Золотое правило номер два Используйте throw, когда возникает непредвиденная операция, а не когда происходит что-то плохое, но вероятное. Чеширский кот сформулировал бы это правило так: обработка исключительных ситуаций занимается исключительными ситуациями. Она не обрабатывает неприятности, которые в принципе часто встречаются. Классический пример этого правила - чья-то попытка войти на сервер. Есть два привычных пути выполнения: или сервер позволяет пользователю войти, или сервер отказывает ему в доступе. Эти два пути кода должны быть обработаны без исключительных ситуаций (например, возвращением true при разрешении на доступ и false - при отказе). С другой стороны, может возникнуть исключительная ситуация, если сервер возвращает «мусор»: он не говорит «да», не говорит «нет» — он говорит какую-то белиберду. В таком случае использование throw для исключительной ситуации оправдано, потому что ситуация действительно ненормальна: проблемы у пользователя гораздо серьезнее, чем просто неправильный ввод пароля. Другие исключительные ситуации могут включать в себя невозможность клиента соединиться с сервером, исчерпанность свободной памяти или невозможность послать данные по сети. Зачем следовать этому правилу? Для начинающих обработка исключительных ситуаций очень медленна. Люди, которые писали ваш компилятор C++, предполагали, что вы не будете использовать throw, если что-то не будет совсем плохо, но в этом случае, если заботиться, чтобы не было медленно, все просто сломается. Честно говоря, я не знаю, думали ли они об этом на самом деле, но по какой-то причине обработка исключительной ситуации с помощью throw действительно очень медленная.
6О
Глава 3
Другая причина (если она вам еще нужна!) в том, что частое использование исключительных ситуаций делает код трудным для прочтения. В общем-то, все важное было сказано уже пару абзацев назад, поэтому я здесь останавливаюсь и перехожу к следующему правилу.
Золотое правило номер три Пишите для ошибок catch так рано, как это возможно, но не раньше. Очевидно, что вы не захотите отлавливать ошибку до того, как вы сможете что-то с ней сделать. Код, который обнаруживает ошибки, должен быть достаточно высоко расположен в цепи команд, чтобы он мог решить, что следует делать, если что-то идет не так Вот пример, иллюстрирующий это правило (только один, я обещаю!). Представьте, что вы пишете код, который должен считывать wave файл и сохранять его в классе CSound. У вас есть несколько функций, настроенных, как показано на рис. 3.3. Вверху находится функция mainQ вашей C++ программы. Функция mainQ вызывает LoadGameResources - большую функцию, ответственную за загрузку всех картинок, музыки и уровней игры. LoadGameResources, в свою очередь, вызывает LoadSoundFile, которая, в свою очередь, вызывает LoadWaveFile для загрузки wave файлов и LoadMP3File для загрузки МРЗ. Теперь предположите, что LoadWaveFile выполняет команду fopen, чтобы открыть файл с диска, и команде fopen это сделать не удается (представьте, что файла, который она хочет открыть, на диске нет). Вы используете исключительные ситуации, поэтому вы решаете, что, если fopen ошибется, нужно это обработать с помощью throw. Золотое правило номер один говорит, что если вы пишете throw, вы пишете и catch, поэтому теперь вы должны решить, где будет располагаться catch. Есть несколько мест, где это можно сделать: - в функции LoadWaveFile; - в функции LoadSoundFile; - в функции LoadGameResources; — в самом начале, в функции main(). Начнем с того, что подумаем о LoadWaveFile. Что эта функция может сделать в catch обработке? К сожалению, практически ничего. LoadWaveFile не знает, почему загружается файл, поэтому не может нормально решить, что делать, если что-то не так. В качестве контрпримера вы можете предложить, чтобы LoadWaveFile обнаруживал ошибку и выходил с помощью вызова exit(-l). Для игры это еще, может быть, и ничего, но представьте себе, что вы хотите использовать LoadWaveFile
Воспроизведение Волнового aByka
main()
LoadGameResources
Catch
\L -/
LoadSoundFile
-/ LoadWaveFile
Throw
Рис. 3.3. Гипотетический стек вызовов в игре, показывающий, где должны располагаться catch и throw в приложении, обрабатывающем звук. Пожалуй, это не будет иметь смысла, так как ваше приложение будет выклчаться, теряя все, сделанное пользователем, только потому, что запрашиваемого файла нет на диске. Было бы лучше, если бы выдавалось сообщение «Файл не найден» и пользователь продолжал свою работу с приложением. Суть в том, что LoadWaveFile - это просто служебная функция, очень мало знающая о контексте программы. Далее, продвигаясь вверх по стеку, как насчет LoadSoundFile? LoadSoundFile уже ближе, но не еще не достаточно хороша. Ей так же, как и предыдущей, не хватает информации. Следующей идет функция LoadGameResources. Это первая точка, где вы обладаете достаточным контекстом, чтобы выяснить, что делать. LoadGameResources знает, что, если вы не сможете положить те байты, которые вам нужно, с диска на RAM, игра не будет идти так, как она задумана. В этой функции вы можете написать в коде,
62
Глава 3
чтобы появлялось сообщение, требующее, переустановки игры игроком, или вы можете попытаться выпутаться из ситуации иначе (например, посмотреть, вставлен ли диск в дисковод, и, если это так, считать оставшуюся информацию с него). В заключение, а почему бы не вставить catch в main? Конечно же, вы можете обработать ошибку и здесь, но я могу поспорить с вами, что это слишком высоко по стеку. До некоторой степени это голос здравого смысла: конечно, это не будет концом света, если вы будете отлавливать ошибку здесь, однако main, обрабатывающая такую ошибку, - это как если бы Пикард вытолкнул Джорди из машинного зала и стал чинить двигатель самостоятельно. Функция main не должна заботиться о внутренней работе загрузки ресурсов. Итак, если бы LoadGameResources обнаружила ошибку и попыталась с ней справиться и все равно оставались бы проблемы, main могла бы обработать переданную ей в гаком случае ошибку. Этот код был бы похож на главного чиновника, говорящего главному администратору своей компании; «Посмотрите, у нас есть вот такая главная проблема, и сам я не в состоянии ее решить, хотя я пытался, и мне нужен ваш совет, чтобы двигаться дальше». Умение находить лучшее место для отлавливания ошибок - это одно из тех умений, которые приходят только с опытом. Есть, конечно, какие-то руководства, но они подходят не для всех ситуациях. В общем, гораздо хуже отлавливать ошибку «слишком низко» (слишком близко к тому месту, где она возникает), нежели «слишком высоко», особенно в программировании игр. В игровом программировании ошибки часто вызывают остановку, поэтому отлавливание их на высшем уровне рационально. Даже если все, что вы делаете, — это отлавливаете ошибки в main, выдавая сообщение вроде «this game crashed», и дальше делаете то, что, возможно, это гораздо лучше, чем я встречал в некоторых профессионально сделанных играх, которые часто просто выгружатся с нарушением доступа, оставляя мою систему в совершеннейшем беспорядке. В игре также существует правило наименьших затрат: часто сделать «наиболее правильную вещь» требует длинного кода и практически не имеет особой пользы (если вы занимаетесь не оборудованием жизнеобеспечения и не управлением шаттлов). В программировании игр доверяйте своему внутреннему чувству и не заботьтесь о том, чтобы оно было абсолютно верно.
Воспроизведение волнового зЗуАа
63
Основы aBykoBoeo gBudtka Ну вот, теперь в этой главе уже не будет новых понятий! Теперь можно приступить к обучению, как собрать вместе первую версию вашего звукового движка. Проверьте пример программы этой главы (Ch3pl_WAVPIayback), которая использует наш перспективный движок для проигрывания WAV файла.
Исключительные ситуации В aBykoBoM gBiukke Я понимаю, что вы только что ознакомились с моими правилами об обработке исключительных ситуаций и просто устали узнавать о них еще и еше. Но в этом параграфе вы используете всю эту теорию на практике и построите логическую схему обработки ошибок вашего движка. Весь код, который я должен вам объяснить, находится в ErrorHandling.h и ErrorHandling.cpp source файлах, если вы хотите следовать дальше.
ОбъеКт СЕггог Все начинается с объекта СЕггог. Большую часть времени, когда программисты используют исключительные ситуации, они не используют в throw примитивные типы данных вроде string или integer. Наоборот, они используют классы ошибок, которые содержат не только ошибку, но и некоторую диагностическую информацию и описание ситуации, почему и когда возникает эта ошибка. Аудио движок, описываемый в этой книге, сохраняет эту информацию в объекте СЕггог, как показано здесь. class CError I public: CError(HRESULT hr, std::string err, std::string filename, int line) { SetFile(filename); SetError{err}; SetLine(line); if (hr) SetReason{DXGetErrorString8(hr)); } virtual -CError() { } std: : string Get File () { return'(m_File) ; } void SetFile (std: : string f) { m__File = f; } std::string GetErrorO { return(m_Error); } void SetError (std:: string f) { m__Error = f; } int GetLineO { return (m_Line) ; } void SetLine(int 1) { m Line = 1; }
64
.
Глава 3
std::string GetReason() { return (m_Reason) ; } void SetReason (std: : string f) { m_Reason = f; } std: istring GetMessageBoxString {) ; protected: std:: string m_File; int m__Line;
std::string m_Error; std::string m_Reason; };
Как вы видите. CError - это очень простой класс. Он содержит ряд из названия source файла, где возникла ошибка (m_File), номер строчки внутри этого файла (m_Line), неправильную строку, показываемую пользователю(т_Еггог) и внутреннее диагностическое сообщение (m_Reason). Есть средства доступа к любому из его членов, конструкция shortcut, которая автоматически вызывает DXGetErrorStringS, если это необходимо, и элемент GetMessageBoxString, который собирает переменные в сообщение, показываемое пользователю.
Define В o6pa6omke ouiu5ok Я написал немного #define кода, чтобы обращение с CErrors стало легче. Я основывал #defme код, который вы увидите, на замечательной статье Стива Рабина, называющейся «Squeezing More Out of Assert», которая была напечатана в Game Programming Gems 1 (ISBN 1-58450-049-2), замечательной книге, которую я рекомендую для любого серьезного игрового программиста (и не только потому, что я написал для нее парочку статей!). Внутри ErrorHandling.h есть два определения, называющиеся ThrowIfF ailed и Throw (обратите внимание на заглавную букву, чтобы не спутать со словом throw). Сначала я объясню Throw, потому что это проще. tfdefine Throw (err) { \ if ( !ThrowCError(NULL, err, _ FILE_, _ LINE__) ) { \ _asm { int 3 } \
Throw - это просто сокращение для вызова глобальной функции ThrowCError. Вот как CError получает переменные m_File и m_Line: #define автоматически поддерживает текущие название файла и номер строки, благодаря встроенным макросам _ FILE _ и _ LINE _ . Функция ThrowCError отвечает за создание объекта CError.
Воспроизведение Волнового aByka bool ThrowCError{HRESULT hr, std::string err, std::string filename, int line) { CError e(hr, err, filename, line); string displaystr = e.GetMessageB.oxString (); displaystr += "Press ABORT to end the program, RETRY to debug, IGNORE to throw the error,"; int result = MessageBox(NULL, displaystr.c_str(), "ErrorHandling.Cpp", MB_ABORTRETRYIGNORE I MB_ICONSTOP); switch(result) { case IDABORT: // немедленно выйти из программы exit(-l); // здесь также можно использовать abort() case IDRETRY: // войти в отладочную программу return(false); case IDIGNORE: // продолжать как обычно (используем к ошибке throw) throw(e); } return(true); // это чтобы избежать предупреждений компилятора }
ThrowCError должен показать сообщение с кнопками Abort, Retry и Ignore (рис. 3.4). Если пользователь выбирает Abort, функция немедленно выходит. Если он выбирает Ignore, код немедленно использует к ошибке throw, как будто это сообщение никогда не появлялось. Если он выбирает Retry, функция возвращает false. Посмотрите назад на define в Throw: если ThrowCError возвращает false, то мы осуществляем (в компоновке) инструкцию int 3. Инструкция int 3 производит отладочное прерывание, которое, когда программа управляется программой отладки, тут же вызывает остановку выполнения. Ал ttrnr Iw «UlM n If.!
ЕРСГ. Dtlbun oyftdJ cfedad; wrp corn
Fin: e
Urn:*!
Рис. З.4. Сообщение, выдаваемое вашим обрабатывающим ошибки кодом
Тогда почему бы ни вставить int 3 прямо в ThrowCError? Если бы она была частью функции ThrowCError, вам нужно было бы двигать стек на одну ячейку, чтобы посмотреть, что же на самом деле произошло. Если int 3 является частью define, когда вы выходите, вы попадаете непосредственно к источнику ошибки. Этот ловкий трюк, за который я обязан отдать должное Стиву Рабину. Теперь, когда вы понимаете, как работает Throw, ознакомьтесь с более сложной функцией ThrowIfFailed. 3-1490
66
Глава 3 ^define ThrowIfFailed (result, err) { \ if {FAILED (result) ) { \ if ( !ThrowCError(result, err, _ FILE _ , _ LINE _ )) { \ asm { int 3 } \
Define делает то же самое, что и Throw, но только если HRESULT, который вы ему передали, является неправильным кодом. Если FAILED(result) возвращает true, код передает HRESULT функции ThrowCError, которая, в свою очередь, передает его СЕггог конструктору. Конструктор ищет код и соответствующую ему ошибочную строку в m_Reason. Эти два define часто оказываются полезными, поэтому вы увидите их довольно часто во всем коде звукового движка. Эта маленькая система обработки ошибок придаст вашей программе надежности. Конечно, вы всегда можете в нее добавить много вещей, но я обнаружил, что даже эта простая схема действительно помогает, когда что-то идет не так.
Построение CAudioManager Теперь, когда вы уже знаете, как обрабатывать исключительные ситуации, вы можете приступать к написанию CAudioManager, класса, который будет руководить всем вашим звуком в вашей игре. Самый большой движок начинается с единственного заголовочного файла, и этот не будет исключением. Вот так выглядит декларация класса CAudioManager. class CAudioManager { public: CAudioManager ( ) ; virtual -CAudioManager () ; void Init(HWND hwnd, bool stereo = true); void Unlnit ( ) ; CSoundPtr LoadSound(std: : string filename) ; IDirectMusicPerf ormanceB* GetPerformance () { return (m_Performance) ; } protected: bool m_InitGood; IDirectMusicLoaderS* m__Loader; IDirectMusicPerformanceS* m_Performance; static const int CAudioManager : :m_PerfChannels;
Воспроизведение Волнового aByka
67
Очевидно, что в конечном варианте движка будет больше методов, чем здесь, но и это уже неплохое начало. Вы можете увидеть методы для инициализации и деинициализации движка, равно как и для загрузки звука. Есть также способ получить интерфейс выполнения напрямую. Внутри CAudioManager отслеживает интерфейсы выполнения и загрузки, а также флаг m_InitGood, который отслеживает, правильно ли инициализирован Manager. Константа m_PerfChannels показывает число каналов воспроизведения - количество звуков, которые вы можете одновременно воспроизводить. Совет. Ваш CD содержит несколько «сни.мков» проекта звукового движка в том порядке, в каком они соответствуют в каждой главе. Это значит, AudioMonager.cpp и AudioManager.h файлы в директории главы 3 включают только выполняемые функции, описанные в главе 3. Файлы Audio Manager в директории главы 4 содержат только исполняемые функции главы 4 плюс модернизации 4 главы. Это позволяет вам следовать за изложением в книге номере того, как я добавляю характеристики движку, и дает вам несколько разных точек по мере продвижения, в которых вы можете отклониться и начать писать какие-то вещи своим способам. Если вы хотите просто взять звуковой движок этой книжки и работать с ним, возьмите законченный звуковой движок в папке AudioEngine на вашем компакт-диске. Он является полнофункциональным.
Init CAudioManager использует двухфазное создание (two-phase creation). Это такой специальный термин программистов, означающий, что создание объекта требует не только вызова конструктора, но и вызова, инициализирующего метод. Я предпочитаю называть этот метод Init, хотя я видел, как другие программисты называют его Create, Setup, Initialize или Reset. Двухфазное создание - достаточно удобная идея по ряду причин. Во-первых, она великолепно подходит для консолей и платформ с маленьким количеством памяти (low-memory), таких, как PDA. При использовании таких устройств чем меньше раз игра выделяет память, тем лучше. Таким образом, вы можете использовать двухфазное создание для инициализации всех объектов, которые понадобятся вашей игре, всего один раз, в начале программы. Как только игра началась, все вызовы new запрещаются. Вместо этого, объект просто вызывает метод Init объекта, который восстанавливает первоначальное состояние объекта. Это что-то вроде получения нового объекта без неразберихи с памятью. Звуковой движок этой книги на самом деле не заботится об этом, но зато он заботится о второй причине использования двухфазного создания: контролем над тем, когда создается объект. Делая движок настолько гибким, насколько это возможно,
68
Глава 3
двухфазное создание позволяет вам полностью контролировать, когда произойдет инициализация. Таким образом, движок использует двухфазное создание. Метод Init выглядит примерно так. void CAudioManager::Init(HWND hwnd, bool stereo, int perfchannels) { HRESULT hr; // инициализация COM hr = CoInitialize(NULL); ThrowIfFailed(hr, "CAudioManager::Init: Colnitialize failed."); // Создание загрузчика hr = CoCreatelnstance (CLSID__DirectMusicLoader, NULL, CLSCTX^INPROC, IID_IDirectMusicLoader8, (void**} &m__Loader) ; ThrowIfFailed(hr, "CAudioManager::Init: CoCreatelnstance for loader failed."); // Создание объекта исполнения hr = CoCreatelnstance( CLSID_DirectMusicPerformance, NULL, CLSCTX^INPROC, IID_IDirectMusicPerformance8, (void**)&m_Performance); ThrowlfFailed(hr, "CAudioManager::Init: CoCreatelnstance for performance failed,"); hr = m_Performance->InitAudio(NULL, NULL, hwnd, stereo ? DMUS_APATH__DYNAMIC__STEREO : DMUS_APATH_DYNAMIC_MONO, perfchannels, DMUS_AUDIOF_ALL, NULL); if (hr =- DSERR_NODRIVER) f // выводим предупреждающее сообщение, затем продолжаем, как обычно MessageBox(hwnd, "The program could not locate your audio hardware.", MB_ICQNSTOP); return; // обратите внимание, что мы не выставляем m_InitGood в true } else ThrowlfFailed(hr, "CAudioManager::Init: m^Performance->InitAudio failed."); m_InitGood = true; }
Из трех аргументов функции два необязательны - требуется только обработка окна. Хотя, если хотите, вы можете также установить, должен ли звук быть моно или стерео и максимальное число каналов, которое может быть для него приготовлено. Каналы занимают немного места (если они не используются), поэтому нет ничего страшного в том, чтобы взять больше, чем необходимо. По умолчанию эта величина равна 128. Код начинается с инициализации СОМ и взятия интерфейса Loader DirectMusic. Как только он получил объект Loader (загрузчика), он получает также объект Performance. Все эти вызовы используют CoCreatelnstance, функцию COM API, которую вы
Воспроизведение Волнового aByka
69
можете использовать, чтобы создать любой СОМ объект. У некоторых других компонентов все CoCreatelnstance содержание скрыто внутри функций, но в DirectMusic слишком много СОМ интерфейсов, чтобы это можно было осуществить, поэтому вы должны иметь дело напрямую с функцией CoCreatelnstance. Что существенно, CoCreatelnstance создает объект определенного класса и интерфейс, чтобы взаимодействовать с этим новым объектом. С начала и до конца аргументы определяют ID класса (название GU1D обычно начинается с CLSID_J, который идентифицирует создаваемый объект; агрегированный указатель (обычно NULL); контекст (обычно CLSCTX_INPROC, который означает, что новый объект использует ту же область памяти, что и код создания, как DLL); ID интерфейса (название GUID обычно начинается с IID_), который идентифицирует конкретный интерфейс, который вы хотите использовать, чтобы взаимодействовать с только что созданным объектом; наконец, адрес указателя, который будет содержать новый интерфейс. Как только код создал все свои объекты, он вызывает метод InitAudio из интерфейса IDirectMusicPerformanceS, который он только что создал. InitAudio инициализирует воспроизведение и настраивает устанавливаемую по умолчанию звуковую дорожку. Вы можете видеть, что код проверяет параметр stereo и устанавливает либо DMUS_APATH_DYNAMIC_STER EO, либо DMUS_APATH__DYNAMIC_MONO. Я должен упомянуть, что есть еще две опции, включающие DMUS_APATH_DYNAMIC_3D (для создания трехмерных звуков, что вы изучите в последующих главах) и DUS_APATH_SHARED_STEREOPLUSREVERB, обычно используемый для обогащения музыки с применением искусственного эха. Обратите внимание, что код проверяет значение, возвращаемое InitAudio для DSERRJMODRIVER. Это специальная ошибка, которая показывает, что DirectX Audio не может найти никакого звукового оборудования. Когда DirectX Audio видит DSERR_NODRIVER, код выводит на экран сообщение, информирующее игрока о том, что его звуковое оборудование не найдено. Принимая, что все работает и, следовательно, нет переданных ошибок, функция завершает работу, выставляя флаг m_InitGood в true, чтобы все последующие функции могли видеть, может ли Manager воспроизводить звук.
Unlnit Unlnit функция CAudioManager гораздо проще. void CAudioManager::Unlnit() { if(m_Performance != NULL) { m_Performance->Stop( NULL, NULL, 0, 0 ); m Performance->CloseDown();
70
Глава 3 SAFE_RELEASE( m_Performance }; } SAFE_RELEASE ( m__Loader ) ; m_InitGood = false; I
Код нанимается с вызова Stop и CloseDown доя m_Performancet предполагая, что это не NULL. Затем он отключает объекты m_Performance и m_Loader и очищает флаг m_InitGood.
ЗагрузКа aByka (LoadSound) До сих пор самый важный метод из CAudioManager - это тот, который загружает wave файлы и создает из них звуковые объекты. Вот как это выглядит. CSoundPtr CAudioManager::LoadSound(std::string filename) { HRESULT hr; CSound *snd = newCSound(this); // преобразовываем имя файла в wide-string WCHAR widefilename[MAX_PATH]; DXUtil_ConvertGenericStringToWide( widefilename, filename. c_str (} ) ; // даем команду загрузчику загрузить этот файл hr = m_Loader->LoadObjectFromFile( CL3ID_DirectMusicSegment, IID_IDirectMusicSegment8, widefilename, (void**) &snd->m__Segment) ; ThrowlfFailed(hr, " . "CAudioManager::LoadSound: LoadObjectFromFile failed."); return(CSoundPtr(snd)); }
Это гораздо проще, чем было бы сделано в DirectSound. Это потому, что загрузчик DirectMusic работает с wave файлами, делая этот код двухшаговым процессом. Во-первых, он преобразовывает данное имя файла в строку в формате юникод (потому что DirectMusic обращается только с юникодовскими строками). Затем он говорит загрузчику, чтобы он загрузил wave файл, используя LoadObjectFromFile. Он отдает загрузчику ID класса (CLSID_DirectMusicSegment) и ID интерфейса (IID_IDirectMusicSegment8), которых он ожидает, и загрузчик делает то, что от него требуется.
Unicode - данный формат использует 16-битное представление символов, что позволяет использовать алфавиты большинства существующих языков. - Примеч. науч. ред.
Воспроизведение Волнового зву/са
71
Как только он получает интерфейс сегмента, он сохраняет его в переменной CSound's m_Segment. (CAudioManager- это дружественный класс для CSound, поэтому он имеет доступ к «защищенным» переменным CSound.) Вы узнаете о последней строке программы в следующей части. Сейчас просто запомните эту строку, к ней мы еще возвратимся. Совет. Представляйте себе -wave файлы как сегмент музыки из одной ноты, играемый на одном инструменте. У вас все еще есть интерфейс сегмента, но это на самом деле именно wave файл.
Интеллектуальные уКазатели В списке самых необходимых характеристик движка, которые необходимо обеспечить, прямо после надежности и обработки ошибок стрит управление ресурсами. Хороший движок должен быть сделан так, чтобы его клиентам было просто создавать объекты, использовать их и избавляться от них, когда они уже больше не нужны. Один из хороших способов сделать это - использовать интеллектуальные указатели. Интеллектуальный указатель (smart pointer) - это класс, который ведет себя как обычный указатель, но с одним отличием: он умный! Он настолько умен, что понимает, когда удалять объекты, которые уже больше не используются. Многие программисты используют интеллектуальные указатели, чтобы облегчить тяжесть управления памятью, особенно когда используются также исключительные ситуации. Если вам необходим освежающий ваши знания курс о том, что такое интеллектуальный указатель, воспользуйтесь ссылками на вашем компакт-диске. Все интеллектуальные указатели для звуковых движков происходят из обычного базового класса, который также оказывается шаблоном. Этот шаблонный базовый класс называется CRefCountPtr и приводится в исполнение в RefCountPtr.h, который основан непосредственно на коде, написанном Дэвидом Гарвеем. Я включил в CD ссылку на статью, которая сначала сопровождала этот код; откройте ее, если вы хотите узнать, как это работает. CSoundPtr является исходным шаблоном для создания указателей CSound. Я предоставил конструктор и метод Release, который выводит сообщения на отладочную консоль, так что вы можете (в реальном времени!) увидеть, как создаются и разрушаются звуки.
72
ГлаВаЗ
CSound В звуковом движке объект CSound представляет звуковой объект. Как вы знаете, основной частью этого объекта является сегментный интерфейс DirectMusic, который содержит сам файл. CSound охватывает этот интерфейс. Я решил, что я не хочу точно следовать DirectMusic; вместо этого, я хочу использовать метод CSound, чтобы я мог говорить: «звуковой эффект, воспроизводись!». Альтернатива в том, чтобы включить метод Play внутрь CAudioManager и передать CSound в качестве параметра. Причина, по которой я решил сделать не так, заклчается в моем личном предпочтении; мне просто кажется, что так будет проще.
Воспроизведение aByka Внесение Play метода внутрь CSound потребовало от меня открытия интерфейса Performance. Вот почему у CAudioManager есть интерфейс GetPerformance. Объект CSound использует этот метод, затем вызывает PlaySegment, передавая свой собственный m^Segment. bool CSound::Play() { if {NULL == m_Segment) return(false); if {NULL == m_Manager) return(false); m_Segment->Download(m_Manager->GetPerforraance() ) ; m_Manager->GetPerformance()->?laySegment( m__Segment, 0, 0, (IDirectMusicsegmentstate **)um_SegmentState); return(true); }
Главное в этом методе - это вызов PlaySegment. Для того чтобы заставить играть сегмент, вы должны обеспечить интерфейс Performance сегментом, который должен быть воспроизведен (m_Segment), любыми флагами, которые вам нужны (никакими в этом случае), и временем, когда начинать воспроизведение (0, чтобы как можно раньше). Функция возвращает вам интерфейс IDirectMusicSegmentState, который вы можете использовать, чтобы запрашивать сегмент, пока он играет.
ЗагрузКа зВуКоВ Обратите внимание в предыдущем коде Play на строчку перед вызовом PlaySegment. Прежде чем что-то сможет воспроизводиться, оно должно быть загружено в синтезатор. Это очень важно, потому что если вы это не сделаете, никакого звука не будет. Вы загружаете сегмент вызовом метода Download, передавая интерфейс Performance (исполнения), которое вы, в конечном счете, будете использовать, чтобы сыграть его.
Воспроизведение Волнового a&yka
73
Совет. Вам надоело помнить о загрузке ваших сегментов? Вы можете заставить DirectMusic автоматически загружать сегменты, когда ему это необходимо. Вы сделаете это вызовам метода SetGlobalParam из интерфейса IDirectMusicPerformanceS, передавая GUID_PerfAutoDownload. Просмотрите документацию к DirectX для изучения деталей.
Определение, Воспроизводится ли aByk Самая интересная вещь, которую вы можете сделать с помощью интерфейса IdirectMusicSegmentState, полученного от PlaySegment, - это определение, играет ли в данный момент ваш сегмент. Просто вызовите метод IsPlaying из интерфейса воспроизведения и передайте ему интерфейс состояния сегмента, который вас интересует. IsPlaying возвратит S_OK, если сегмент воспроизводится, и S_FALSE, если нет. Будьте осторожны, когда вы вызываете метод для сегмента, который еще только получил команду воспроизводиться. Иногда IsPlaying слишком аккуратен: он просто говорит, выходит ли из колонок звук этого сегмента. Если вы только что вызвали PlaySegment, IsPlaying может возвратить S_FALSE из-за задержки - вы уже приказали звуку воспроизводиться, но он еще не успел скомандовать колонкам.
ВыгрузКа sByka Как только вы закончили работать со звуком, необходимо выгрузить его вызовом метода Unload из интерфейса сегмента. Он выгрузит данные сегмента из воспроизведения.
Альтернативные способы Воспроизведения aByka До сих пор вы рассматривали преимущественно, как использовать DirectX API для воспроизведения звука. Однако это не единственный способ, а иногда даже не лучший. В этом разделе вы рассмотрите пару альтернативных способов воспроизведения звука.
DirectSound Одна из альтернатив использованию DirectMusic - это использование низкоуровневых интерфейсов DirectSound. Вообще-то, если вы уже начали использовать DirectX, нет никакого смысла в том, чтобы не использовать высокоуровневый ин-терфейс
74
Глава 3
DirectMusic Loader. Я бы посоветовал использовать DirectSound, только если вы не хотите использовать DirectX 8 (например, если вы хотите, чтобы ваша игра могла идти под Windows NT 4.0). Вот как загружать и воспроизводить звук, не используя ничего, кроме интерфейсов DirectSound. 1. Инициализировать DirectSound. Как конкретно вы будете это делать, зависит от версии DirectSound, которую вы используете, но в целом процесс аналогичен тому, как вы учились это делать в прошлой главе. 2.
Создать вторичный буфер и загрузить в него свой wave файл. Это сложная часть, потому что она включает анализ формата wave файла вручную. Это можно сделать, используя примеры программ DirectX или коды из Интернета.
3. Воспроизведите свой звук вызовом метода Play вторичного буфера, созданного вами. 4. Удалите вторичный буфер, с которым вы работали. Пример программы под названием Ch3p2_DirectSound наглядно показывает, как воспроизводить звук, используя только интерфейсы DirectSound. Совет. Есть несколько примеров программ DirectX, демонстрирующих, как воспроизводить wave файлы, используя интерфейсы DirectSound В DirectX 8 SDK есть 2 файла (dsutil, cpp и dsutil.h), которые содержат множество полезных вещей, включая код для класса CSound, который использует буферы DirectSound.
Windows API Waveform Audio Если вы вообще не хотите использовать DirectX, есть еще один способ заставить wave файлы выходить из колонок: использовать Win32 API, особенно функции Waveform Audio. Waveform Audio сегмент из SDK документирован в MSDN, в Platform SDKAGraphics and Multimedia ServicesVMultimedia AudioYWaveform Audio. Совет. Если вы хотите использовать PlaySound, вы должны добавить библиотеку winmm.lib. Убедитесь, что вы ее добавили в список библиотек в компоновочной таблице в настройках проекта, иначе у вас будут возникать ошибки связи.
Исключительного внимания заслуживает Win32 API функция PlaySound, которая может воспроизводить звук с диска, из ресурса или памяти. Пример программы Ch3p3_WaveformAudio показывает, как воспроизводить звук из памяти, используя эту функцию.
Воспроизведение Волнового aByka
75
Windows MCI Если вы хотите получить немного больше, чем просто функцию PlaySound, но не хотите использовать DirectX, вы можете воспроизвести звук, используя Win32 API's Media Control Interface (MCI)- MCI был предназначен для связи между широким рядом мультимедийных устройств, устройств воспроизведения волнового звука, MIDI секвенсоров, устройств воспроизведения компакт-дисков и устройств воспроизведения цифрового видео. MCI - это подходящий интерфейс, который дает вам больше контроля над воспроизведением звуков. Например, вы можете играть сегмент wave файла и установить точки начала и конца. Это значит, вы можете сказать: «Играй этот звук с 2,54 секунды по 3,81 секунды». Вы научитесь использовать MCI для воспроизведения CD данных позже. Там также будет больше информации о MSDN, на Platform SDKAGraphics and Multimedia Services\Multimedia AudioVM CI.
Заключение В этой главе вы изучили, как управлять тремя интерфейсами DirectMusic: Loader (загрузки), Segment (сегмента) и Performance (исполнения). Вы также узнали некоторые основные правила построения хорошего движка и несколько альтернатив использования DirectAudio для воспроизведения звуковых эффектов. В следующей главе вы глубже погрузитесь в DirectMusic и расширите возможности вашего звукового движка, чтобы он поддерживал некоторые манипуляции со звуком.
Глава 4
ЗагрузКа WAV файлов В последних трех главах вы шли по долгому пути к полному господству над звуковыми эффектами вашей игры. В этой главе ваше господство увеличится, потому что вы узнаете еще несколько способов получать ваши wave файлы с накопителя и передавать их DirectMusic.
Строение WAV файла Wave файлы вовсе не так просты, как вы думаете, и, хотя DirectMusic заботится о сложностях вместо вас, вы никогда не можете заранее знать, когда над вами не будет этого спасительного зонтика Microsoft и придется что-то делать самостоятельно. Поэтому вот беглое рассмотрение файлового формата WAV. Для более детального анализа пройдите по одной из ссылок, которые я оставил на вашем компакт-диске, или, если хотите увидеть быструю побайтовую картину файла, посмотрите на рис. 4.1.
RIFF Для того чтобы понять, что такое WAV, нужно сначала разобраться с RIFF. Это связано с тем, что файловый формат WAV - это одно из подмножеств Microsoft технологии RIFF. RIFF означает Resource Interchange File Format (файловый формат обмена ресурсами), файловый формат, созданный специально для того, чтобы содержать данные мультимедиа. Достаточно интересно, что песни и другие музыкальные данные DirectMusic также сохраняются форматом RIFF. RIFF формат - это просто чудовище невероятной сложности. К счастью, для того чтобы загрузить WAV файл, вам вовсе не обязательно понимать все это. Вообще-то, если вы не делаете что-то очень уж продвинутое, вам не обязательно понимать даже большую часть этого формата. Все, что вам нужно знать для обычного WAV файла, где хранится какая часть данных. Вот чему вас собирается научить этот раздел. Если
ЗагрузНа WAV файлоВ
77
ID чанка Размер чанка Формат ID субчанка Размер субчанка 20
22
Аудио формат Количество каналов
26
Частота выборки Частота байт Выравнивание данных Бит на сэмпл ID субчанка 2
"RIFF" 36+размер субчанка 2 "WAVE"
ЧтГ 16
1 = PCM 1 = моно, 2 = стерео и т. д. • Обычнов или 16 Частота дискретизации * Количество каналов * Количество бит на сэмпл/8 • Количество каналов * Количество бит на сэмпл/8 • "data"
Размер субчанка 2
Данные
44+размер субчанка 2 Рис. 4.1. Этот рисунок демонстрирует файловый формат обычного несжатого WAV
же вы патологически любопытны, вы можете узнать о принципах работы RIFF на более высоком уровне, изучая MSDN или пройдя по ссылкам на вашем компактдиске.
Chunks Нет, я не выдумал это. Различные части файла R1FF действительно называются chunks-чанки (доел. - ломоть). Эти чанки как дольки фруктов в бакалейном магазине. Как яблоко Грэнни Смита обладало 4017 бирками, чанк обладает четырех символьными «chunk ID», которые говорят нам, что это за чанк. Этот специальный вид ID называется FOURCC (four-character code).
78
Глава 4
На самом деле, FOURCC - это просто 32-битный integer (typedef которого DWORD), который состоит из 4 восьмибитных ASCII кодов символов, расположенных внутри него.
Рис. 4.2. Характерный код FOURCC для R1FF - обратите внимание на перестановку символов
WAV файл состоит из трех чанков. Во-первых, есть главный чанк, FOURCC которого RIFF. Внутри чанка RIFF содержатся данные., говорящие о том, что мы имеем дело с волновым форматом. Также внутри RIFF чанка есть два субчанка: «fmt» (обратите внимание на его объем) и «data». Чанк fmt содержит формат wave файла (8 или 16 бит, стерео или моно, частоту дискретизации, и т. д.). Чанк «data» содержит те байты, которые формируют непосредственно саму звуковую волну. Совет. Верхний регистр FOURCC стандартизован, а нижний регистр FOURCC обозначает определенный формат.
Каждый чанк имеет две одинаковые вещи. Самые первые 4 байта чанка всегда будут содержать FOURCC этого чанка. Так как WAV файл -это RIFF файл, первые 4 байта верного WAV всегда будут RIFF. Далее в любом чанке, сразу за ID чанка, находятся 4 байта, хранящие размер чанка. Обратите внимание, что этот размер.не включает в себя размер ID чанка и размер байт размера. Другими словами, размер, включенный сюда, на 8 байт меньше размера чанка целиком, потому что первые четыре байта - это FOURCC чанка, а следующие четыре - это размер чанка. Очень важно это помнить; если это поможет, рассматривайте байты размера как определяющие «размер с этого места». В следующих разделах главный чанк, чанки формата и данных будут рассмотрены подробно.
Главный чанК Этот чанк содержит два других чанка. Его FOURCC - это RIFF. Этот чанк содержит важный факт. После ID чанка и размера чанка единственное, что осталось, - это FOURCC, который описывает, что фактически находится в этом RIFF файле. У WAV файла эти четыре байта данных всегда содержат WAVE. Для других типов файлов RIFF там будут другие коды.
ЗагрузЬа WAV файлов
79
Таким образом, главный чанк говорит только то, что «Я RIFF файл, и я содержу wave». Обратите внимание, что размер чанка - это размер следующих двух чанков плюс четыре байта на формат данных.
ЧанК формата Теперь становится уже интереснее. Чанк формата содержит все, что вам нужно для правильной интерпретации чанка данных. Как вы можете видеть из рис. 4.1, этот чанк сообщает вам аудио формат, число каналов, частоту дискретизации, частоту байт, выравнивание данных и количество бит на выборку данных.
Аудио формат
.
Первым идет аудио формат: это два байта данных о том, сжат ли чанк данных. Для несжатых WAV это значение равно 1, что означает, что чанк данных записан с использованием Импульсно-Кодовой Модуляции (PCM). PCM - это прихотливый способ сообщить, что wave данные не сжаты. Значения, отличающиеся от 1, говорят о том, что wave файл был каким-либо образом сжат (см. табл. 4.1). Таблица 4.1. Стандартные Ю волновых форматов Волновой формат
Определение в MMREG.H
Номер
Неизвестный (это плохо)
WAVE_FORMAT_UNKNOWN
0
Импульсно-кодовая модуляция
WAVE_FORMAT_PCM
1
Адаптивна- дифференциальная импульсно- кодовая модуляция
WAVE_FORMAT_ADPCM
2
32-битный с плавающей точкой
WAVE_FORMAT_lEEEJLOAT
3
CCITTG.711 A-law
WAVE_FORMATALAW
6
CCITTG.711 u-law
WAVE_FORMAT_MULAW
7
MPEG Layer З(МРЗ)
WAVE_FORMAT_MPEGLAYER3
55
Существует еще огромное множество форматов, кроме перечисленных в этом списке; полный список можно прочесть в MMREG.H. Как видите, существует множество форматов, и написание кода для всех них растянулось бы навечно. К счастью, огромное большинство WAVE файлов - это РСМ, и вы всегда сможете использовать специальную программу для преобразо-
8О
Глава 4
вания не-РСМ в РСМ. Если вам нужно (или вы хотите) написать свой собственный анализатор WAV файлов, я бы посоветовал обязательно поддерживать РСМ. Совет. В сети есть несколько доступных библиотек, позволяющих вам читать различные сжатые форматы волнового звука. Я включил ссылки на некоторые из них в ваш компакт-диск.
Количество Каналов Сразу за байтами аудио формата следуют два байта, сообщающие, сколькими каналами обладает WAV файл. Если это моно, то будет один канал; а если стерео, то два. Возможны другие числа, например, WAV может содержать данные о 6-канальном объемном звуке. Как бы там ни было, самые распространенные - это моно и стерео.
Частота дискретизации Это очевидное понятие. Эти четыре байта содержат 8,000 для 8,000Гц. 44,100 для
44.1 кГц и т. д.
Частота байт Это поле показывает вам, сколько байт используется для каждой секунды аудио. Это число всегда равно частоте дискретизации, умноженной на число каналов, умноженное на количество бит на выборку и разделенное на восемь (помните, что 16 бит на выборку означают 2 байта на выборку).
ВыраВниВание данных Здесь говорится о количестве байт на одну выборку. Это всегда число каналов, умноженное на количество бит на выборку и разделенное на восемь.
Количество бит на выборКу Значение 8 здесь означает, что вы имеете дело с 8-битной выборкой, 16 означает 16 бит и т. д. 8 и 16 бит —это самые стандартные значения.
4auk данных Если вы уже считали все данные о формате, вы теперь знаете, как правильно интерпретировать данные в этом разделе. Это хорошо, потому что чанк данных после полей FOURCC и размера - это просто океан сырых байт. Как их интерпретировать, зависит от блока формата.
Sazpyaka WAV файлов
81
Для несжатых WAV файлов размер блока данных - это всегда число выборок, умноженное на количество каналов, умноженное на количество бит на выборку и разделенное на восемь. Совет. Важно помнить, что вам обычно не нужно иметь дело непосредственно с форматам -wove фата, потому что в основном вы будете использовать DirectMusic Loader.
Воплощение Всего этого В Жизнь Теперь, когда вы знаете, что находится внутри WAV файла, кажется вполне естественным улучшить наш любимый звуковой движок несколькими новыми методами.
CWAVFile Эти методы позволяют прямое управление данными wave файлов посредством класса, называющегося CWAVFile. class CWAVFile ( public: friend class CAudioManager; void Init () ; CWAVFile () ( lnit(); } CWAVFile(const CWAVFile &r) { Init(); Copy{r); } virtual -CWAVFileО; int m_AudioFoonat; int m._NumberOf Channels; int m_SampleRate; int m_ByteRate; int m_BlockAlign; int m_BitsPerSample; unsigned int m^DataLen; CWAVFile &operator=(const CWAVFile &r) { Copy(r); return(*this); } unsigned char *GetData() { return{rn_Data} ; } void SetData(const unsigned char *data, unsigned int len); void Load(const unsigned char *data); void Loadfstd::string filename); unsigned char *Save() const; bool Save(std::string filename) const; int GetTotalSize() const { return(44 + m_DataLen); } std::string GetlnfoO; protected: void Copy(const CWAVFile &r); // snip // чанк данных unsigned char *m_Data;
82
Глава 4
Этот класс отслеживает чанки формата и данных для WAV файла. Он открывает переменные для всех частей информации, которая получена в блоке формата, так же, как защищенный указатель m_Data, который хранит содержимое блока данных. Я не строил средств доступа к этому классу, потому что ни один из типов данных здесь не будет изменяться, но если вам удобнее использовать методы Get/ Set, сделайте это. Совет. CWAVFile обладает одним дополнительным оператором присваивания, для того чтобы он мог правильно копировать свой указатель m_Data. Невозможность заменить operator^ для класса, который динамически распределяет память, — это главное табу C++; будьте осторожны. И не забудьте также о своих виртуальных деструкторах.
3azpyska В CWAVFile Загрузка WAV файла в CWAVFile довольно проста теперь, когда вы знаете формат файла. Заметьте, что в классе CWAVFile есть две перегрузки для загрузки данных: одна для файла, а вторая для данных «в памяти». Перегрузка файла просто считывает файл в память и вызывает перегрузку данных «в памяти». Она делает всю работу. void CWAVFile::Load(const.unsigned char *data) { FOURCC riff,, wave, fmt, datacc; // проверяет, написано ли RIFF в ChunkID memcpy(&riff, sdata[0], sizeof(FOURCC}); 1 if (riff 1= mmioFOURCCCR', 'I', 'F , 'F')) t Throw("invalid WAV file data"); } // проверяет, WAVE ли прописано в формате memcpy(Swave, &data[8], sizeof(FOURCC)); if (wave 1= mnioFOURCC ('W, 'A', 'V1, 'E1 } ) { Throw("Invalid WAV file data (not WAVE format)."); } // проверяет, написано ли FMT в ChunkID memcpy(&fmt, sdata[12], sizeof(FOURCC}); if (fmt != mmioFOURCCf'f', 'm1, 't', ' '}) { Throw("invalid WAV file data {FMT subchunk not found)."); } // проверяет, есть ли DATA в ChunkID memcpy(&datace, &data[36], sizeof(FOURCC}); if (datacc '= mmioFOURCC<'d', 'a', 't1, 'a')} t Throw("invalid WAV file data (DATA subchunk not found}."); • // загрузка а элементы класса memcpy(&m AudioFormat, &data[20], sizeof(short int)); 1 Перегрузка (операций) - возможность выбора компилятором различных реализаций одной и той же функции в зависимости от типов ее параметров в вызове функции. - Примеч. науч. ред.
Saepyaka WAV файлов
83
rnentcpy(&m BitsPerSample, &data[34] , sizeof {short int)); memcpy(&rn BlockAlign, Sdata [32] , sizeof (short int)) ; memcpy(&m ByteRate, &data[28] , sizeof (int) ) ; memcpy(&m NumberOf Channels, &data [22] , sizeof (short int)) ; merncpy (&m_SampleRate, &data [24 j , sizeof (short int)); unsigned int datasize=0; memcpy (&datasize, &data [40] , sizeof (int) ) ; SetData(&data[44] , datasize)
Код удивительно пессимистичен; прежде чем он сделает какую-либо реальную работу, он делает все, что может, чтобы убедиться, что data действительно указывает на действующий WAV файл. Если все FOURCC соответствуют необходимому, то он начинает скачивать биты относительных данных из чанка памяти, используя memcpy. В конце концов, он скачивает сами wave данные посредством вызова метода SetData. Обратите внимание, что класс не принимает на себя владение указателя data, а создает его копию; это значит, что клиент должен освободить эту память как можно раньше. Функция mmioFOURCC - это удобная функция Win32, определенная в mmsystem.h, которая берет четыре буквы и превращает их в FOURCC. FOURCC на самом деле состоит из 32 бит, поэтому не происходит никакого сжатия, просто умелая перестановка байт и преобразование типа.
Сохранение из CWAVFile Методы Save из CWAVFile симметричны методам Load. Существуют два метода Save: один для сохранения на диск, а второй для сохранения в память. Перегрузка диска просто вызывает перегрузку памяти и затем сохраняет эту память. Я не собираюсь включать сюда также и метод Save, просто объясню, что он занимает достаточное количество памяти, пересоздает все FOURCC, помещает все элементы CWAVFile в специальные офсеты и добавляет wave данные. Как только все это сделано, он возвращает указатель на память, которую он запросил, снова с пониманием того, что вызывающий оператор владеет памятью, которую возвратил Save. Пример программы Ch4pl_WAVFileXRay показывает, как считывать в память WAV файл. Совет. Если вы хотите улучшить программу, попытайтесь вставить в звуковой двшчсок библиотеку, которая считывает сжатые форматы wove файлов. Будет здорово, если вы сможете заставить звуковой движок считывать wove файл с диска и автоматически разворачивать его, как будто бы мы воздействовали на него объектам CwoveFile.
84
Глава 4
Другие способы загрузить WAV файлы Теперь, когда у вас есть фундаментальное понимание формата WAV файлов, настало время перевести стрелки назад в режим DirectMusic и научиться некоторым другим способам загружать wave файлы. В последней главе вы научились скачивать их с диска, а теперь вы научитесь загружать их из памяти или с ресурсов своих приложений.
ЗагрузКа из памяти Можно заставить объект Loader (загрузчик) DirectMusic считывать строку байт из памяти вместо диска. Это очень выгодная опция, потому что она позволяет вам хранить ваши wave файлы, где вам угодно, пока вы можете считать целый файл в память. Вы можете хранить wave файлы в ZIP файле, или в обычном файловом формате, или вы даже можете закачивать их из Интернета. Как бы вы это ни сделали, если вы закачали целый файл в память, вы можете быть уверены, что объект Loader DirectMusic проанализирует его и возвратит вам сегмент. В третьей главе «Воспроизведение волнового звука» вы использовали метод LoadObjectFromFile из IDirectMusicLoaderS для считывания с диска; очень действенен метод GetObjec загрузки, который может считывать с диска или памяти. Чтобы использовать GetObject, вы сначала должны заполнить структуру DMUS^OBJECTDESC, которая указывает DirectMusic, где находится объект и как вы хотите его загрузить. Для того чтобы загрузить файл из памяти, вам нужно заполнить два важных элемента в этой структуре. Во-первых, заполните байтовый указатель pbMemData местонахождением вашего файла в памяти. Затем заполните элемент HMemLength, который сообщает DirectMusic длину вашего чанка памяти. Используя эти знания, вы можете усилить класс аудио менеджера, созданием другой перегрузки LoadSound. CSoundPtr CAudioManager::LoadSound(unsigned char *data, int datalen) { . HRESULT
hr;
CSound *snd = new CSound(this) ; DMUS^OBJECTDESC desc; memset E&desc, 0, sizeof(DMUS_OBJECTDESC)}; desc.dwSize = sizeof(DMUS_OBJECTDESC); desc.dwValidData = DMUS_OBJ_MEMORY | DMUS_OBJ_CLASS; desc.guidClass = CLSID_DirectMusicSegment;
desc. HMemLength = datalen;
desc.pbMemData = data; hr = m_Loader->GetObject(fidesc, IID_IDirectMusicSegraent8, (void **}&snd->m Segment);
Saapyaka WAV файлов
85
ThrowlfFailed(hr, "CAudioManager::LoadSound: GetObject failed."); snd->m_OriginalData = data; return(CSoundPtr(snd));
}
Этот код начинается так же, как и оригинальный метод LoadSound: он создает новый объект CSound. Начиная отсюда, пути этих кодов расходятся; тогда как оригинальный LoadSound вызывает LoadObjectFromFile, этот код создает структуру DMUS_OBJECTDESC и вызывает GetObject. Не любой элемент структуры DMUS_OBJECTDESC должен быть заполнен. Переменная dwSize обязательна, так же, как флаги dwValidData. Нуждается ли что-нибудь еще в заполнении, зависит от флагов dwValidData. Определяя MUS_OBJ__MEMORY, вы, таким образом, говорите DirectX Audio, что вы хотите загрузить объект из памяти. Определение этого флага обязывает вас также заполнить структуры IlMemLength и pbMemData размером данных и самими данными, соответственно. Другой флаг, DMUS_OBJ_CLASS, сообщает DirectX Audio, что вы хотите загрузить объект, тип которого указан в элементе guidClass, Код помещает guidClass в CLSID_DirectMusicSegment, сообщая DirectX Audio, что он должен вернуть нам интерфейс сегмента. Затем, после вызова GetObject, код настраивает элемент m_OriginaIData нового звукового объекта. Таким образом, аудио менеджер как бы говорит звуку: «Твой сегментный объект использует память, которую назначила программа, поэтому ты должен освободить эту память, когда ты будешь разрушен». Вы не можете освободить память непосредственно после того, как вызвали GetObject, потому что DirectX Audio использует именно эту память внутри сегмента. Он не создает копии памяти, вместо этого он полагается на данные, находящиеся там, где они есть, все время жизни объекта. Тем не менее., когда сегмент действительно умирает, вы ответственны за освобождение памяти. Программа Ch4pl_WAVFileXRay демонстрирует использование этого метода.
ЗагрузКа из CWAVFile А теперь пришло время повеселиться. Звуковой движок теперь обладает CWAVFile, а у того есть CSound. Было бы чудесно, если бы вы смогли преобразовать CWAVFile в CSound. Если бы вы могли это сделать, ваша игра могла бы создавать звуковые эффекты во время игры, используя CWAVFile, а затем вкачивать эти звуковые эффекты в CSound для воспроизведения в игре.
86
Глава 4
Эта блестящая интеграция на самом деле проста. Просто заставьте CWAVFile сохранить себя в памяти, а затем передайте эту память DirectMusic Loader, который создаст из нее сегмент. Вот какой код. CSoundPtr CAudioManager::LoadSound(const CWAVFile Swavfile} { unsigned char *savedata = wavfile.Save{); return(LoadSound(savedata, wavfile.GetTotalSize()}}; I
Вы полюбите их! Эти две строчки кода! Просто запомните, что не надо освобождать память, которую взял Save, потому что эта память используется CSound. Фактически, потому что перегрузка LoadSound с незащищенной памятью устанавливает новый элемент CSound's m_priginalData, нам не о чем беспокоиться: когда CSound будет разрушен, он освободит память.
ЗагрузКа цз ресурсов Поскольку вы уже знаете, как загрузить wave файл из памяти, загрузка с ресурса - это уже просто один щелчок пальцами. Просто вызовите несколько функций Win32 API, чтобы получить указатель на ваш обычный ресурс (скажем, wave файл), а затем передайте этот указатель Loader (загрузчику) с помощью структуры DMUS_OBJECTDESC, описанной раньше. Вот кусок кода. CSoundPtr CAudioManager::LoadSound(HMODULE hmod, char *type, WORD resID) { CSound *snd = new CSound(this); HRESULT hr; DMUS_OBJECTDESC ObjDesc; HRSRC hFound = FindResource(hmod, MAKEINTRESOURCE(resID), type); if (NULL == hFound) { Throw("couldn"t find resource!"); } HGLOBAL hRes = LoadResource(hraod, hFound}; ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC); ObjDesc.guidClass = CLSID_DirectMusicSegment; ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY; ObjDesc.pbMemData = (BYTE *) LockResource(hRes); ObjDesc.HMemLength = SizeofResource(hmod, hFound); hr = m_Loader->GetObject(SObjDesc, IID_IDirectMusicSegraent8, (void**) &snd->m_Segment); ThrowlfFailed(hr, "GetObject failed."); return(CSoundPtr(snd));
Saepyaka WAV файлов
87
Этот код очень похож на другие методы LoadSound. Главное различие в том, что, перед тем как он заполняет структуру DMUS_OBJECTDESC, код должен найти и загрузить ресурс. Вызов FindResource возвращает дескриптор HRSRC, который затем используется для вызова LoadResource. LoadResource возвращает HGLOBAL, который затем используется LockResource. LockResource берет HGLOBAL и возвращает указатель на ресурс данных в памяти. Код вводит этот указатель в элемент pbMemData структуры DMUS_OBJECTDESC, и можно начинать. Обратите также внимание на вызов SizeofResource для определения размера ресурса. Программа Ch4p2_WAVInResource демонстрирует использование этого метода. Но как вставить wave файл в ресурс, чтобы начать? Вот способ добавления wave файла в ресурсы вашей программы. Если у вас еще нет файла источника, вам нужно создать его и добавить в ваш проект. В DevStudio выберите New из меню File и Resource Script в качестве типа файла. Обычно главный ресурс программы называется script <projectname>.rc, где имя проекта - это то, как вы назвали ваш EXE, DSP и DSW, Например, скрипт источш'ка для Ch4p2_WAVInResource это - Ch4p2_WAVInResource.rc. Закройте файл сразу же, как только DevStudio откроет его для вас. Ы Щ| Oi4pa_WAVInll«
Рис. 4.3. После вставки вашего RC файла в проект, вы получите новую таблицу Resource View
После того как вы создали свой файл, вы увидите таблицу ResourceView на вашем рабочем месте (рис. 4.3). Щелкните по этой таблице, а затем щелкните правой кнопкой мыши по самой верхней папке. Выберите Insert из контекстного меню и затем щелкните по кнопке Import (рис. 4.4). Выберите нужный вам файл, который нужно добавить, в браузере файлов.
Глава 4
88
У Dodaig VW-J
Й"рги*№>5
Рис. 4.4. Вставка WAV файла в ресурс вашего приложения
Теперь вам нужно сообщить DevStudio, что это за тип ресурса. Вы можете выбрать все, что угодно, в качестве типа ресурса, но вы должны помнить, что методу LoadSound аудио менеджера нужно передать тот же самый тип. Я использую «WAVE» для обозначения wave файлов. Если все идет правильно, вы увидите свой ресурс перечисленным в папке с таким же названием, как и у типа ресурса, который вы определили на предыдущем шаге. Откомпилируйте свою программу, и ваш ресурс будет загружен и включен в ваш ЕХЕ. Убедитесь, что вы включили resource.h в любые СРР файлы, которые захотят использовать ресурсы. Resource.h использует #defme для связи ID ресурсов (что хочет Win32 API) с названиями ресурсов, которые вы определили в редакторе ресурсов. Совет. По умолчанию DevStudio не сохраняет целиком ваш ресурс в фате RC, Вернее, он сохраняет метку-идентификатор, сообщающую, где искать начальный файл. Обычно этого достаточно, но если вы все же хотите, чтобы DevStudio сохранил копию выбранного вами WAV прямо в скрипте вашего ресурса, щелкните правой кнопкой по данному ресурсу, выберите Properties, и вы увидите диалоговое окно, как на рис. 4.5. Отмените проверку External File, и ваш ресурс будет сохраняться внутри самого скрипта. Обратите внимание, что ресурсы, сохраняемые в RC файле, занимают значительно больше места - где-то в пять раз больше их обычного размера - из-за того факта, что они «ASCIl-изируются» (например, один байт в 30 hex становится ASCII строкой «0>:30» или около того). Custom Resource Pretmrtw* -И f
Rertutce
I I
Рис. 4.5. Отмените проверку external file, чтобы ваш WAV был вставлен прямо в скрипт ресурса вашей программы
Saepyaka WAV файлов
89
Определение gupekmopuu noucka В большинстве игр данные обычно храняться в специальных директориях. Я видел множество профессиональных игр, хранящих все звуковые данные в специальных папках «Sound» или «GameAudio», в главном месте установки. Вот способ сделать это проще. Вы можете указать Loader (загрузчику), в какой директории он должен искать данные, которые надо загрузить. Тогда, вместо высчитывания и прописывания полного пути к каждому файлу, который вы будете загружать, вы просто будете указывать его название, и программа загрузки будет искать этот файл именно в той директории, которую вы укажете. Вам понадобится метод SetSearchDirectory. Вот новая функция для звукового движка, которая будет сообщать ему, где искать мелодии. void C A u d i o M a n a g e r : : S e t S e a r c h D i r e c t o r y ( s t d : : s t r i n g dirname} { / / преобразовать имя директории в строку юникод WCHAR widedirname[МАХ_РАТН]; DXUtil_ConvertGenericStringToWide( widedirname, d i r n a m e . c _ s t r ( ) ) ; HRESULT hr = ra_Loader->SetSearchDirectory{ CLSID__DirectMusicSegrnent, widedirname, true) ; ThrowlfFailed{hr/ "SetSearcftDirectory(} failed."); )
Как вы можете увидеть, это немного. Просто преобразуйте название директории в юникодовскую строку, а затем вызовите метод SetSearchDirectory. Первый параметр сообщает DirectX Audio, для какого типа объектов вы хотите настроить эту директорию (в данном случае, сегментов). Следующий параметр - это название директории, а последний параметр, если true, заставляет DirectX Audio «забыть» предыдущие местонахождения объекта, которые уже были загружены (обычно это хорошая идея). Пример программы Ch4p3_SearchDir демонстрирует этот метод.
Заключение В этой главе вы узнали несколько различных способов передавать в DirectMusic звуковые данные. Как вы можете видеть, Loader (загрузчик) DirectMusic - очень многоцелевой объект; он позволяет вам значительно облегчить загрузку ваших звуковых файлов. Также помните, что все приемы загрузки, которые вы изучили в этой главе, применимы к музыке, так же, как и к звуковыми эффектами.
ГлаВа 5
Контролируемая неКонтролируемостъ Вы уже практически подошли к завершению работы над той частью звукового движка, которая относится к звуковым эффектам. Тем не менее, остались еще некоторые незавершенные моменты. Все они, так или иначе, касаются управления звуковыми эффектами - прибавления громкости, одновременного воспроизведения звуков и т. д. Эта глава научит вас всем тонкостям того, как заставить звуковые эффекты хорошо звучать. .
Одновременное Воспроизведение зВуКоВ Сообразительный читатель уже успел заметить важную проблему звукового движка в том виде, в каком он сейчас: он не может играть один и тот же звук непрерывно. Другими словами, если у вас есть звук колокольчика, вы не можете заставить звонить три колокольчика одновременно. Самое большое, что вы можете сделать, это заставить этот звук играть, потом остановить его, потом снова запустить, и так три раза. В этом разделе вы узнаете, как справиться с этой проблемой.
Первичные и Вторичные сегменты DirectMusic В DirectMusic есть понятие первичного и вторичного сегмента. Их различие в том, что вторичных сегментов может быть много, а первичный только один. Первичный сегмент считается главной музыкальной дорожкой для вашей игры. Звуковые эффекты и всякие музыкальные трюки - это чаще всего вторичные сегменты, воспроизводимые через первичный сегмент. Первичный сегмент обычно задает
Контролируемая неЬонтролируемость
91
параметры музыки (например, темп, последовательность аккордов и т. д.). Вы узнаете об этом больше в последующих главах, а сейчас, провто запомните, что единственный способ заставить воспроизводиться несколько звуковых эффектов - это сделать так, чтобы они воспроизводились как вторичные сегменты.
Прощай, PlaySegment, здравствуй, PlaySegmentEx В последней главе вы использовали метод PlaySegment объекта Performance (исполнения) DirectMusic как основу для воспроизведения звуков. Это хорошо работает при однократном воспроизведении, но, чтобы заставить один и тот же звук звучать много раз, вам необходимо использовать PlaySegmentEx. PlaySegmentEx позволяет сообщить DirectX Audio, должен ли сегмент быть воспроизведен как первичный или вторичный. Используя PlaySegmentEx (вместо PlaySegment) в методе CSound's Play, вы получите возможность воспроизводить одновременно несколько звуков. m_Manager->GetPerformance()->PlaySegmentEx(m_Segment, NULL, NULL, DMUS_SEGF_SECONDARY, 0, (IDirectMusicSegmentState **)Sm_SegmentState, NULL, HULL);
PlaySegmentEx использует больше параметров, чем PlaySegment, поэтому вам нужно будет ознакомиться с документацией, чтобы разобраться, что они все значат. Действительно важная вещь - это флаг DMUS_SEGF_SECONDARY, который сообщает DirectX Audio, что он должен воспроизводить этот сегмент как вторичный, а не как первичный.
Экземпляры aByka Как вы уже могли предположить, вы еще не заканчиваете работу с одновременным воспроизведением. В последнем разделе вы узнали, как заставить DirectMusic воспроизводить более одного звука. Этот раздел научит вас, как изменить звуковой движок, чтобы приспособить его к новому для него факту, что можно воспроизводить больше одного звука. В настоящее время проблема этого звукового движка в том, что сейчас он отслеживает состояние только одного сегмента (IDirectMusicSegmentStateS). Воспроизведите один раз звук, и mjSegmentState будет заполнен интерфейсом нового состояния сегмента; воспроизведите другой, и произойдет в точности то же самое, но вы навсегда потеряете состояние первого звука. Что хуже, состояние первого сегмента никогда не освобождается, создавая утечку ресурсов.
92
Глава 5
Пришло время навести во всем этом порядок. Звуковому движку нужен новый класс - CSoundlnstance. Этот новый класс будет представлять текущий воспроизводимый звук. Метод CSound::Play будет создавать (и возвращать) новый Csoundlnstance каждый раз, когда воспроизводится этот звук. Таким образом, код, который вызывает Play, может следить за состоянием отдельного экземпляра (instance) звука. Это очень важно, когда звуковые эффекты играют по циклу (об этом вы узнаете в следующем разделе). Обычно, большинство кодов, вызывающих Play для нециклических звуковых эффектов, не будет заботиться о звуке, когда он уже воспроизведен. Когда игрок запускает снаряд, код, ответственный за это место, отрегулирует звуковые параметры (громкость), воспроизведет звук, а в дальнейшем будет его проигрывать с этими параметрами. Подобно грилю Ron Popeil, код настраивает это, а потом забывает о нем. Для воспроизводимых по циклу звуков модель будет немного другой. Обычно воспроизводимый по циклу звук должен быть настроен в соответствии с игровой ситуацией, Представьте себе шум генератора мощности в стратегической игре в реальном времени. Сначала он едва слышен, но потом, когда игрок создает завод супер оружия, мощных ракет, шум становится громче. Экземпляры звука станут более необходимыми по мере того, как вы дальше будете читать эту книгу. Сейчас вы не можете с ними много сделать, но, когда вы начнете изучать трехмерный звук, они станут очень важными. Каждый экземпляр звука будет обладать трехмерным положением и некоторыми другими свойствами, которые вы сможете настраивать. Совет. Взаимосвязь CSound и CSoundlnstance - это обычный пример в игровом программировании. В двумерных играх у вас есть спрайты, которые представляют собой набор отдельных битмапов, задающих отдельные фазы движения персонажей в игре. Битмап представляет собой обычную картинку в какам-либо двоичном формате. Это позволяет вам иметь много спрайтов, которые используют один битмап, и это спасает от ненужного дублирования данных этого изображения. В звуковом движке этой книги CSound играет роль битмапа, a CSoundlnstance - спрайта.
Читая следующие разделы, поглядывайте на пример программы Ch5p2_SoundInstances.
Создание CSoundlnstance CSoundlnstance - это простой класс, поэтому я не буду включать сюда его декларирование. Посмотрите на него в вашей среде разработки, и вы увидите, что там есть методы Init и Unlnit, защищенный элемент состояния сегмента, стандартный указатель звуковой управляющей программы (использующийся IsPlaying) и флаг, показывающий, действует ли объект.
Контролируемая неЬонтролируемость
93
Этот класс, как CAudioManager, использует двухфазную конструкцию. Это позволяет игре заранее распределить звуковые ступени, которые будут использованы позже. Например, в классе CTank вы можете создать статически выделяемый элемент под названием m_EngineSound. class CTank { public: CTank() { } // nothing to do virtual -CTank{) { } // nothing to do protected: CSoundlnstance m_EngineSound; };
Если вы спроектировали CSoundlnstance как CSound (однофазная конструкция), это было бы невозможно, потому что CSoundlnstance понадобились бы параметры конструктора, описывающие звук. Таким образом, методы Init и Unlnit, так же, как флаг m_Va!id, - это вещи, необходимые вам для двухфазной конструкции. Остались только две вещи: метод IsPlaying и элемент m_SegmentState и связанная функция доступа GetSegmentState. Метод IsPlaying выглядит очень похоже на свой двойник в CSound. bool CSoundlnstance::IsPlaying() i
assert(IsValid()); if (!IsValid()) { return(false); } return (GetAudioManager () ->GetPerf ortnance () ->IsPlaying ( NULL, m_SegmentState) =- S_OK);
}
Метод воспроизведения IsPlaying - это двухголовое чудовище. Оно может проверить, воспроизводится ли сегмент или состояние сегмента. Чтобы их различить, вы ставите сегмент первым параметром, а состояние сегмента вторым. Так как CSoundlnstance обладает состоянием сегмента, он передает "NULL в качестве первого параметра. Совет. Обратите внимание на проверку Is Valid в методе IsPlaying, Один из недостатков использования двухфазной конструкции состоит в том, что в каждом методе, прежде чем вы попытаетесь что-то сделать, вам сначала нужно будет убедиться в том, что ваш объект полностью инициализирован. Таким образам, даже если ктото вызовет IsPlaying прежде, чем ступень будет инициализирована, программа не сломается.
94
Глава 5
KoppekmupoBka методов CSound Теперь, когда написан код CSound Instance, вам необходимо будет внести некоторые изменения в CSound. CSound больше не требует элемента m_SegmentState, потому что теперь он управляется CSoundlnstance. Также нужно изменить метод CSound::IsPlaying(), чтобы он использовал сегмент, а не состояние сегмента. Вот новый код. bool CSound::IsPlaying() { if
1
(NULL == m_Segraent)
return ( f a l s e ) ;
if (NULL == m_Manager) return(false); return( m_Manager->GetPerformance()->IsPlaying(m_Segment, NULL) == S_OK );
Здесь я изменил вызов IsPlaying, передавая сегмент в качестве первого параметра и NULL в качестве второго. Это заставляет DirectX Audio возвратить S_OK, если какая-либо ступень звука играет. Тем не менее, самое большое изменение происходит в методе Play. Вот новый код. bool CSound::Play(CSoundlnstance *newinst) { if (NULL == m_Segment | I NULL -= m_Manager) { if {newinst} newinst->Un!nit(}; return(false); } static CSoundlnstance dummy; if (NULL == newinst) newinst = fidummy; m_Segment->Download(m_Manager->GetPerformance()); // инициализируем звуковую ступень // это также настроит играющий звук newinst->Init(this); return(true);
}
Новичкам следует обратить внимание, что у нас есть параметр newinst, который является указателем на ту ступень, на которую захочет клиент, чтобы отслеживать звук, который должен быть воспроизведен. Параметр newinst по умолчанию выставляется в NULL в декларации класса CSound, поэтому код, который не заботится о сохранении ступени звука, может просто вызвать PlayQ, как он это всегда делал,
Контролируемая не&онтролируемостъ
95
Если newinst это NULL, код использует простой статический CSoundlnstance для инициализации звука и его воспроизведения. Иначе - он использует переданный ему newinst. Обратите внимание, что методу CSoundlnstance: :Init() нужен указатель CSound. Сейчас метод Init не использует этот указатель ни для чего, но в последующих разделах он пригодится. Программа Ch4p4_ConcurrentP lay back демонстрирует работу этого нового кода.
Воспроизведение sBykoB В цийле (Looping) Часто очень удобно иметь возможность заставить звук воспроизводиться по циклу. До DirectMusic традиционным способом добиться этого было запустить звук, а затем проверять время от времени, играет ли он. Если нет, то вы запускаете звук снова. Самая большая проблема этого метода в том, что если вы хотите слушать цикл без задержки, то придется проверять, играет ли звук, очень часто, порядка 10 раз в секунду. Для одного звука это еще может показаться не очень болыцим количеством., но для нескольких звуков это может обернуться захватыванием слишком большой части ресурсов центрального процессора. Есть более легкий путь - просто вызвать метод SetRepeats сегментного интерфейса. Вы можете передать SetRepeats флаг DMUS_SEG_REPEAT__INFINITE, чтобы сообщить DirectMusic, что вы хотите повторять сегмент бесконечно. Или вы можете передать какое-то целое число, определяющее количество раз, которое нужно повторить звук. Если вам нужен более точный механизм контроля над циклом, вы можете использовать метод SetLoopPomts сегмента. Это позволит вам определять, где внутри данного сегмента должен начинаться и заканчиваться цикл. Тем не менее, это работает не так, как вы, должно быть, ожидаете. Посмотрите на рис. 5.1. Даже если вы используете SetLoopPoints для определения начальной и конечной позиций, целый сегмент все равно будет воспроизводиться. DirectMusic будет играть с начала сегмента до конечной точки, которую вы определили, потом по циклу дойдет до начальной, повторится нужное количество раз, которое определяется в SetRepeats, а потом будет играть с конечной точки цикла до конечной точки сегмента. Другими словами, SetLoopPoints используется только в цикле. Сегмент по-прежнему воспроизводится снаружи цикла.
Глава 5
96 Начальная точка цикла, устанавливаемая с помощью SetLoopPoints
Начальная точка, устанавливаемая с помощью SetStartPointsi
Абсолютное начало сегмента
Конечная точка цикла, устанавливаемая с помощью SetLoopPoints
Конечная точка, определяемая как SelStartPoints(} + Se1Ungtfi{)
о
i ' 11
CL
ES QJ 3 СЁ И
^
QJ
a S
^ 1
С1
=г с
S°
^^ ^1И^И1И
•
^ Абсолютный конец сегмента
Рнс. 5.1. DirectMusic использует несколько различных параметров, чтобы контролировать цикл в сегменте
Если вы хотите проконтролировать фактические начальную и конечную точки, вам необходимо использовать методы SetStartPoint и SetLength сегментного интерфейса. По умолчанию программа загрузки выставляет начальную точку в начало сегмента, а длину - в длину целого сегмента. Совет. Естественно, есть также методы Get, соответствующие всем методам, настраивающим параметры цикла.
Программа Ch5p3_Looping демонстрирует, как воспроизводить звук по циклу.
Управление громКостью с помощью DirectX Audio Очень часто в игре бывает необходимо изменить громкость звука в зависимости от происходящих в игре событий. В этом разделе вы научитесь делать это, используя DirectX Audio.
Контролируемая ае/сентролируемость
97
SBykoBbie gopodiku В DirectMusic громкость связана с объектом под названием звуковая дорожка (audio path). Звуковая дорожка содержит всю информацию, которая влияет на то, как сегмент воспроизводится, но не обязательно принадлежит этому сегменту как его часть. Классическим примером такой информации является (как вы уже догадались) громкость звука. Не имеет смысла хранить уровень громкости в сегменте. Если бы это было так, то для того чтобы воспроизвести тот же самый сегмент с другой громкостью, вам нужно было бы скопировать объект сегмента - ужасная трата ресурсов. Поэтому вместо этого DirectMusic хранит большую часть параметров, контролирующих звук, внутри объекта, который называется звуковой дорожкой. Совет. Положение трехмерного звука тоже сохраняется как часть объекта звуковой дорожки, но вы узнаете об этом позже.
До настоящего времени звуковой движок использовал звуковую дорожку, устанавливаемую по умолчанию, для всех звуков, которые он воспроизводил. Устанавливаемая по умолчанию дорожка - это та, которую вы создаете, когда вызываете InitAudio. Она просто великолепна для обычного воспроизведения звуков, но, если вы хотите изменить громкость конкретного звука, вы ничего не сможете сделать. Все звуки используют одну и ту же звуковую дорожку, поэтому, когда один звук изменяет громкость звуковой дорожки, автоматически изменяется громкость и всех остальных звуков тоже (рис. 5.2) У этой проблемы есть пара различных решений, но они включают в себя использование многочисленных звуковых дорожек. Некоторые программисты, занимающиеся звуком, любят создавать целый набор звуковых дорожек: когда звук воспроизводится, этот код выбирает пустую звуковую дорожку и отводит туда звук. Это очень удобно по целому ряду причин: новичкам это дает возможность проще наблюдать и манипулировать всеми звуковыми дорожками без необходимости заново проходить через все воспроизведение звука. Альтернативный метод, который я включил в звуковой движок, - это создание нового звукового движка каждый раз, когда воспроизводится звук. Как только звук заканчивает воспроизведение, звуковая дорожка остается, пока не будет освобождена. Так как звуковые дорожки - сравнительно маленький класс, все нормально при условии, что вы согласны на некоторое количество занятой памяти в логической памяти вашей игры. Я думаю, что этот метод значительно проще закодировать и научить ему, чем первому, что, в общем-то, и привело меня к решению использовать его в этой книге. 4-1490
Глава 5
98 Если несколько звуков разделяют между собой одну звуковую дорожку, то, когда вы меняете громкость этой дорожки, меняется громкость всех звуков
Но дайте каждому свою звуковую дорожку, и вы сможете настраивать громкость каждой из них независимо
Рис. 5.2. Звуки, которые разделяют между собой звуковые дорожки, также разделяют и звуковые кнопки
Контролируемая аейонтролируемость
99
Чтобы приспособиться использовать эти звуковые дорожки, нужно провести некоторые изменения в Play методе CSound. Вот новый код. bool CSound::Play(CSoundlnstance *newinst) { HRESULT hr; if (NULL « m_Segment | | NULL == mjflanager) { if (newinst) newinst->Un!nit(); return(false); 1 static CSoundlnstance dummy; if (NULL == newinst) newinst = Sduinmy; IDirectMusicPerformanceS *perf = m_Manager->GetPerforrnance () ; IDirectMusicAudioPathS * pPath = NULL; lUnknown *pConfig; if (SUCCEEDED(m_Segment->GetAudioPathConfig(&pConfig))) { // здесь вставлена конфигурация звуковой дорожки hr = perf->CreateAudioPath(pConfig, TRUE, &pPath); pConfig->Release(); ThrowlfFailed(hr, "Couldn't create audiopath."}; } else ( // Никакой конфигурации... делайте свою! hr «= perf->CreateStandardAudioPath(DMUS_APATH_DYNAMIC_STEREO, CAudioManager::NUMCHANNELS, TRUE, SpPath); ThrowIfFailed(hr, "Couldn't create standard audiopath."}; I m_Segment->Download(perf); // инициализация экземпляра звука // это также заставит ее играть. newinst->Init(this, pPath); return(true); }
(настройка громкости и т.д.)
Этот код начинается так же, но, как только он получает объект исполнения и передает его в perf, все меняется. Во-первых, он смотрит сегмент, чтобы выяснить, если ли там конфигурация для звуковой дорожки. Объект config - это что-то наподобие ДНК звуковой дорожки, поэтому, если код находит конфигурацию, все, что он должен сделать, чтоб создать звуковую дорожку, - это передать эту конфигурацию функции CreateAudioPath, которая кладет интерфейс новой звуковой дорожки в переменную pPath. В wave файлах не содержатся конфигурации звуковых дорожек, в отличие от сегментов музыки, созданных в DirectMusic Producer.
10О
Глава 5
Если никакая конфигурация не вставлена в сегмент, код создает новую стандартную звуковую дорожку и кладет ее интерфейс в pPath. Поэтому в обоих случаях к тому времени, как код загрузит сегмент, уже будет создана действующая дорожка для этой конкретной звуковой ступени в pPath. В последней строчке код передает постоянную pPath методу Init звуковой ступени. Вот новый код для Init. void CSoundlnstance::Init(CSound *sound, IDirectMusicAudioPath3 *path) { HRESULT hr; m_AudioPath = path; m Valid = true; /7 настраиваем громкость, основанную на громкости звука SetVolume(sound->GetVolume()); // теперь, когда все уже настроено, идем вперед и включаем, hr = GetAudioManager()->GetPerformance()->PlaySegmentEx{ sound->GetSegment(), NULL, NULL, DMUS_SEGF_SECONDARY, 0, (IDirectMusicSegmentState **) &m__Segment State, NULL, path); ThrowlfFailed(hr, "PlaySegmentEx failed."); }
Метод Init кладет интерфейс звуковой дорожки в элемент m_AudioPath. Затем он передает этот интерфейс PlaySegmentEx в качестве последнего параметра. А он уже заставляет DirectMusic воспроизводить звук, используя эту звуковую дорожку. Этот метод Init использует интерфейс m_AudioPath внутри SetVolume, когда настраивает громкость звука. Вы узнаете об этом методе позже.
ГромКость отдельного сегмента Как упоминалось ранее, вы управляете громкостью звука через звуковую дорожку. А теперь поговорим о том, как получить и настроить громкость звуковой дорожки.
Hacmpouka epowkocmu В интерфейсе IDirectMusicAudioPathS есть метод под названием SetVolume. Этот метод использует два аргумента: громкость (более точно, поглощение децибел), измеряемую в сотнях децибел, и продолжительность времени, в течение которого изменяется громкость звука (что делает постепенное затухание звука очень простым). Метод SetVolume CSoundlnstance использует этот метод SetVolume звуковой дорожки, чтобы изменить громкость воспроизведения звука.
Контролируемая
ие&онтролируемосдл*
1О1
Я построил еще несколько продвинутых выполняемых функций в звуковом движке поверх этой модели. Если вы посмотрите на декларацию класса CSound, вы заметите, что там теперь также есть метод SetVolume. Он позволяет вам настроить громкость по умолчанию для новых ступеней этого конкретного звука. CSound вовсе не должен многое делать, чтобы этого добиться, - он просто запоминает то значение, какое вы зададите. Настоящая же работа происходит в методе CSoundInstance::Init. Обратите внимание настрочку. SetVolume(sound->GetVolume());
Она настраивает громкость звуковой дорожки на текущую громкость CSound. Совет. Обратите внимание на порядок, в котором CSoundInstance::lnit выполняет операции. В особенности обратите внимание па то, что он настраивает громкость звуковой доролски до того, как она начнет воспроизводить звук. Это гарантирует, что звук начнет играть с самого начала с должной громкостью. Если бы код настраивал громкость после вызова PlaySegmentEx, мог бы быть маленький промежуток времени, в течение которого пользователь слышал бы звук на полной громкости. Порядок, в котором вы делаете операции, очень важен - дернейте это в памяти.
Запрашивание громКости Запрашивание громкости звуковой дорожки так же просто: вызовите метод GetVolume для той звуковой дорожки, которая вам нужна. GetVolume берет один параметр: указатель на длинное целое, который он заполняет текущей громкостью звуковой дорожки.
Другой способ управления громКостью Естественно, громкость звуковой дорожки - это не единственная громкость, которую вы можете регулировать. Нижележащие буферы DirectSound (помните такие?) также дают возможность управления их громкостью, доступные через методы SetVolume и GetVolume IDirectSoundBufferS. Я решил, что проще работать только с одной звуковой «кнопкой», и отставил все остальные возможности, Регулировка и громкости буфера DirectSound и громкости сегмента могут привести к путанице. Я просто выбрал, что мы будем двигаться вперед с громкостью сегмента.
Почему диапазон громкости makou странный? Большая часть программистов, привыкших работать с диапазонами, начинающимися с нуля и заканчивающимися на каком-то другом положительном числе, бывают очень озадачены тем, почему контроль громкости DirectMusic имеет диапазон от -9,600 до 0.
1О2
Глава 5
Прежде всего, ряд измеряется в сотнях децибел, т. е. -100 — это -1 децибел, и -9,600 - это -96 децибел. Так делается это более аккуратным, но для чего отрицательные числа? Во время существования аналоговой записи звуковые инженеры измеряли качество своего оборудования отрицательными децибелами. Оценка 0 была недостижимо замечательной, потому что она означала, что оборудование ничего не теряет в процессе записывания звука. Чем ниже опускался уровень, тем ниже было качество аппаратуры. Неповрежденное человеческое ухо обладает диапазоном слышимости в 120 децибел. Это значит, что, если бы вы включили ваши колонки достаточно громко для того, чтобы зву к, если он воспроизводится на громкости 0, мог нанести вред, тогда вы только-только сможете услышать звук, воспроизводимый на громкости -120 децибел. Естественно, более практичным будет диапазон несколько меньше, скажем, от -96 до 0 децибел. Этот метод только уменьшения громкости звуков называется ослаблением. Практически вся звуковая аппаратура сейчас осуществляет контроль над звуком с помощью ослабления; другими словами, исходные выборки никогда не делаются громче, только тише.
Глобальная громКость Все же есть еще один способ управления звуком, который вы можете настроить в DirectMusic. DirectMusic позволяет вам определять звук воспроизведения в целом. Итак, как же это делается? Естественно, это будет не так просто, как вызов метода SetVolume IDirectMusicPerformanceS; это покажется достаточно странным, но этот метод не существует. Вместо этого, вы должны настроить громкость посредством вызова метода SetGlobalParam интерфейса воспроизведения. Передайте в качестве первого параметра GUIDJPerfMasterVolume, в качестве второго - желаемый звук, а в качестве третьего - размер long. Этот способ управления громкостью использует не такой диапазон, как звуковая дорожка. Диапазон зависит от программного обеспечения синтезатора, и для стандартного синтезатора Microsoft громкость измеряется в сотнях децибел - в пределах от -200 до 20. Это означает, что фактический диапазон от -20,000 до 2,000, вместо диапазона от -9,600 до 0, как у звуковой дорожки. Я добавил методы GetVolume и SetVolume в CAudioManager, но, поскольку они понятны, я не буду тратить место на то, чтобы разбирать их по строчке.
Контролируемая нейонтролируемость
ЮЗ
Подытоживая способы управления гром(состью с помощью DirectX Я надеюсь, что вам кажется полезным, интересным и красивым этот «тур по способам управления громкостью DirectMusic». Как я уже говорил раньше, мощные API делают трудные вещи простыми, а простые сложными. К счастью, сложность управления громкостью в DirectMusic ничтожна по сравнению с Mixer API, за который мы примемся теперь. Но прежде чем мы двинемся дальше, остановитесь здесь и изучите пример программы Ch5p4_DMusicVolume, который иллюстрирует повыборочное и глобальное управление громкостью.
Управление громкостью с помощью миКширующего API Управление громкостью в DirectMusic, которое вы только что изучили, похоже на микшер громкости системы. Микшер громкости - это то, что изменяет пользователь, когда делает двойной щелчок по иконке динамика в трее и передвигает ползунки. Обычно ползунки предназначены для общей громкости, а также специально для выхода wave audio, line in и т. д. (рис. 5.3). Я Speaker Otftons fcfclp Speaker
Wave
SW Synth
SeiancK
Balance:
Balance.
оr;
Vokirae:
Volume:
Volume.
DMule rdon SoundStitfcs
Рис. 5.З. Windows обеспечивает Графический Интерфейс Пользователя для микширования громкостей
Глава 5
1О4
Совет. Подавите в себе желание пойти по легкому пути - контроль с помощью микшера регулирует громкость всего в системе, поэтому большинству пользователей не понравится, что громкость меняется как бы у них за спиной. Например, если ваша игра регулирует главную громкость, микшер изменит громкость не только в игре, но также и во всех других запуи^ениых приложениях. Это может быть очень резко ~ представьте себе пользователя, который слушает МРЗ на заднем плане, в то время как играет. Если он заходит в вашу игру и регулирует громкость звукового эффекта или музыки, он все равно ожидает, что громкость МРЗ останется постоянной. Не используйте микшерное управление как простой способ избежать обучения тому, как контролировать выходные устройства вашего приложения. Регулировка этих значений микшера с помощью программы также возможна, хотя это будет сводящим с ума упражнением по «бурению» сквозь несколько слоев структур и псевдо объектов. В этом разделе вы узнаете, как это делается.
Apxumekmypa миЬлиирующего API Архитектура микшера суммирована на рис. 5.4. Там очень много всяких структур и псевдо объектов, поэтому вам надо двигаться медленно. Если вы будете двигаться чересчур быстро и замените случайно где-нибудь ID управления на ID контроля, вам придется провести много времени за отладкой.
Рис. 5.4. Архитектура микширующего API разделена на несколько псевдо объектов
Контролируемая ие/сошпролируемостъ
1О5
Вверху микшерной архитектуры стоит микшер. Каждый микшер состоит из нескольких источников и целей. Вы не модифицируете прямо эти цели и источники; вместо этого, вы модифицируете воображаемые линии, идущие между ними. Вы можете запросить API о TD воображаемой линии, идущей между любыми двумя источниками или целями, или вы можете запросить ID линий всех источников, идущих к определенной цели (например, выходу главного динамика). Каждая линия обладает многочисленными элементами управления. У вас есть ваша стандартная исходящая громкость и панорамный контроль плюс потенциально гораздо больше экзотических элементов управления, таких, как измерители и эквалайзеры, зависящие от того, какая звуковая аппаратура используется. Стоит упомянуть, что API также поддерживает полностью оригинальные элементы управления, не подходящие ни под один из ранее существовавших. Они могут также отображаться в графическом интерфейсе пользователя. Каждый тип управления обладает тем же числом каналов, что и линия. Например, на стерео линии, каждый тип контроля будет обладать двумя каналами (громкость справа и слева), поэтому вы должны уделять внимание элементам структур, возвращаемых вам API. Каждый канал обладает значением. Обычно это значение - целое число со знаком или без знака, но может быть и по-другому. Правильный диапазон значений зависит от звуковой карточки.
Kak изменять параметры Mukuiepa Теперь, когда вы немного ознакомились с архитектурой, вот рецепт, как изменять определенное значение в микшере. 1. Откройте дескриптор микшера вызовом функции mixerOpen API. Проставьте ноль в качестве второго параметра, uMxId, чтобы сообщить Windows, что вы хотите открыть первое устройство микшера. Обычно этого достаточно, потому что первое устройство микшера обычно бывает единственным, но если вы собираетесь сделать что-то сложное и/или у пользователя больше одной звуковой карточки, вам нужно пронумеровать доступные микшеры с помощью вызовов mixerGetNumDevs и mixerGetDevCaps API. 2.
Разберитесь, какую линию вы хотите модифицировать, и определите это в терминах взаимосвязи источника и цели (например, для громкости волнового выхода источником будет волновой выходной сигнал, а целью - колонки).
3.
Передайте эту информацию mixerGetLinelnfo, который возвратит вам ID линии и число каналов на этой линии (для стерео волнового выхода это будет 2).
1О6
Глава 5
4. В той линии, которую вы хотите настроить, выберите тот вид контроля, который вам нужен. Самый простой способ это сделать - запросить у линии ID контроля определенного типа (например, громкости). Вызовите mixerGetLineControls, определяя флаг MIXER_GETLINECONTROLSF_ONEBYTYPE, и впишите тот тип контроля, который вам нужен, в элемент dwControlType структуры MIXERLINECONTROLS. 5.
Вы получите структуру MIXERCONTROL, описывающую ваш тип контроля. Сохраните dwControlID этой структуры: он определяет тот тип, который вам нужен.
6. Выясните, какие детали в этом типе управления вам нужны (например, контроль звука обладает MXERCONTROLDETAILSJJNSIGNED), и сделайте массив «содержащих значение» структур этого типа. Массив должен быть такого же размера, как число каналов линии. Заполните структуру MIXERCONTROLDETAILS, передавая ей IE) контроля, вместе с массивом содержащих значение структур. 7. Если вы хотите запросить управление, вызовите mixerGetControlDetails. Эта функция заполнит каждую из структур значений текущим значением для конкретного канала (например, первый элемент массива будет соответствовать каналу 1, второй - каналу 2 и т. д.). 8.
Если вы хотите установить новые значения для контроля, вы должны установить все эти значения или ни одного из них. Нет способа сказать: «Windows, я хочу установить значение для канала 1 в этой двухканальной линии». Поэтому вместо этого вы должны будете запросить все эти значения каналов, изменить те, которые вам нужны, а потом поместить новые значения (и старые, неизмененные) обратно в систему.
9. Закройте дескриптор микшера, который вы открыли на первом шаге.
Построение CMixer Я готов поспорить, что вы даже не думали, что управление громкостью может быть таким сложным, не правда ли? Теперь пришло время заключить всю эту сложность в дружественный пользователю класс CMixer. Я не собираюсь разбирать по строчке CMixer (он достаточно понятен и так); вместо этого, я собираюсь объяснить его интерфейс вместе с философией моего замысла, а потом предоставить вам постижение того, как внутренние части класса на самом деле работают, на основании кода и предыдущего раздела.
Контролируемая
не/сонтролируемость
Ю7
Я построил CMixer главным образом для того чтобы проработать громкость и приглушения. Вы можете захотеть расширить его, для того чтобы поддерживать другие типы управления (например, поддерживать использование микрофона или контролировать бас и дискант). CMixer использует немой класс CMixerLine, чтобы хранить взаимодействие между источником и целью. Я называю его немым, потому что он не обладает методами для установки громкости или заглушения. Другими словами, вы не говорите: «CMixerLine, заглуши себя»; вы говорите: «CMixer, заглуши CMixerLine». Другой человек мог бы сделать так, чтобы CMixer принимал большее участие в создании CMixerLine; другими словами, вы бы передавали CMixer источник и цель, и тогда, если бы они могли составить линию, CMixer возвращал бы вам CMixerLfne. После этого вы могли бы вызывать методы CMixerLine для изменения управления этой линией. Вы, должно быть, захотите сделать именно так для сложного интерфейса микшера, но для контроля только над звуком я пожертвовал гибкостью в пользу простоты; в моей модели единственный класс, о котором вы беспокоитесь, - это CMixer. Совет. В своем классе CMixer я не уделил внимания такой вещи, как сохранение панорамирования. Большая часть звуковых карт предоставляют для громкости два канала — левая колонка и правая колонка. Чтобы сохранить панорамирование при установке этих значений, вам нужно убедиться, что они в там же сомам отношении (или установите панорамное значение позже). Я оставил это для вас, чтобы вы могли самостоятельно реализовать его, если вам это нужно.
Если вы посмотрите на объявление класса CMixer, вы заметите, что оно включает несколько элементов static const CMixerLine. Это сохраняет источник и цель обычно используемых линий, маскируя их как константы. Например, если вы хотите сохранить основную громкость, вам следует использовать встроенный элемент Master CMixer в вызове SetVolume, например следующим образом. // установить основную громкость в 50% m_Mixer.SetVolume(CMixer:iMaster, 32767);
Конечно, если вы хотите немного поимпровизировать, вы всегда можете определить ваши собственные взаимодействия между целью и источником. // установка громкости телефона (модема) в 50% m_Mixer.SetVolume( CMixerLine{ MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS ), 32767); // около половины
JO8
Глава 5
Идущий выше код создает временный класс CMixerLine, который определяет телефонный источник и цель. Пример программы Ch5p5_MixerVblume показывает, как использовать CMixer, и я также включил парочку ссылок, касающихся микшерных API, на ваш компактдиск. Совет. Есть много бесплатных классов микшеров, доступных для скачивания на сайтах вроде -www.codeguru.com и www.codeproject.com. Посмотрите их, и, может быть, вам повезет.
Заключение Примите мои поздравления! В этой плаве вы прошли долгий путь, узнали о многих несоизмеримых темах и API, Движок также прошел долгий путь. Я считаю, теперь он уже готов к внедрению в какую-нибудь простую игру. Конечно, в нем еще многого не хватает, но, я думаю, почти все самое главное уже на месте. В последующих главах вы узнаете, как дальше улучшить свой движок, особенно в той части, что касается воспроизведения музыки и (позже) динамической музыки, трехмерного звука и остальных свойств.
Глава 6
Воспроизведение MIDI В предыдущих главах вы научились загружать, воспроизводить и управлять звуковыми эффектами. Теперь настало время обратить ваше внимание на другую часть уравнения игрового звука - фоновую музыку, В этой главе вы изучите основы использования MIDT файлов. Эта глава посвящена воспроизведению статической музыки, т. е. музыки, которая не меняется. Во второй части книги вы узнаете, как работать с динамической музыкой. DirectMusic очень старается не делать особого различия между WAV файлами и MIDI файлами. Объект Loader загружает и MIDI, и WAV файлы в сегменты, которые воспроизводятся путем передачи их методу Play воспроизведения так же, как звуковые эффекты. Поэтому, после нескольких сложных глав вы, наконец, добрались до простой.
Kak работает MIDI MIDI - это сокращение or Musical Instrument Digital Interface (Цифровой Интерфейс Музыкальных Инструментов). Исходно изобретенный в 1980-х, сегодня MIDI стал неофициальным стандартом взаимодействия между электронными музыкальными инструментами. Вы можете представлять себе MIDI как обыкновенный сетевой протокол. В сущности, вы подключаете всю свою аппаратуру (синтезаторы, барабанные машины, компьютеры и т. д.) кабелями MIDI и приписываете каждому прибору свой ID канала (рис. 6.1). Например, ваш синтезатор может быть каналом 2, а барабан может быть каналом 4. Тогда, если вы нажмете кнопку на вашем синтезаторе, сообщение о нажатой ноте посылается по каналу 2. Когда вы ударяете по барабану, сообщение о нажатой ноте посылается по каналу 4. MIDI поддерживает до 16 различных каналов.
по
Глава 6
Программное обеспечение вашего компьютера (или вашего синтезатора, если он у вас продвинутый) может воспринимать любые из этих, или даже все, MIDI каналов и записывать сообщения о нажатой ноте. После этого он может воспроизводить их, посыпая по определенным каналам сообщения, или сохранить в MIDI файле эту последовательность нот. Звуковой фрагмент. Проиграйте музыкальные фрагменты 6.1 и 6.2, чтобы услышать различные примеры MIDI музыки. Сообщение о нажатой ноте содержит несколько полей: конечно, там есть число, показывающее, какая нота какой октавы была сыграна (например, средняя С или С4 более высокие номера означают более высокую ноту), а также пункты для громкости ноты, ее продолжительности и т. д. Для барабанов «нота» означает тип барабана, на котором играть — когда синтезатор барабана получает ноту С4, он играет на высоком барабане бонго. Для барабанных машин более высокие ноты означают не более высокий тон барабана, они означают различные совокупности барабанных звуков, отсортированных наподобие набора символов ASCII.
Другие приборы на каналах 5-16
PC, работающий 9 качестве оператора MIDI
Рис. 6.1. Профессиональная установка MIDI должна обладать различной аппаратурой, соответствующей различным каналам MIDI
Воспроизведение MIDI
Щ
В MIDI определено множество различных типов сообщений. Вероятно, сообщение о нажатой ноте - самое важное из них, но существуют также сообщения, используемые для того, чтобы заставить устройство изменить свое звучание или исполнять специальные эффекты (как вибрато или pitch-bending 1 ). MIDI каналы - это совсем не то же самое, что и треки. Программное обеспечение синтезатора (например, DirectMusic Producer или Cakewalk Pro Audio) использует понятие треков. В то время как существует только 16 MIDI каналов, треков могут быть сотни. Тем не менее, в любой конкретный момент времени у вас могут быть активны 16 каналов, хотя эти 16 каналов могут быть распределены по большему количеству треков (например, какие-то треки должны быть отключены, потому что они содержат различные записи или различные эффекты). Это не означает, что одновременно могут воспроизводиться не более 16 нот - каждый канал отвечает одному устройству или инструменту, но большое количество устройств или инструментов позволяют сразу нескольким нотам воспроизводиться (что называется полифонией). В DirectMusic сообщения MIDI обычно не посылаются из порта MIDI, no MIDI проводу, к внешним синтезаторам. Скорее, они посылаются внутреннему синтезатору DirectMusic, который программно синтезирует звук и отсылает его колонкам. В этом потрясающая выгода для видеоигр, потому что это означает, что вам не нужно иметь тысячи долларов на синтезаторы и оборудование, чтобы заставить хорошо звучать MIDI файлы; и все, что вам необходимо, - это стандартный синтезатор DirectMusic, который вы получаете вместе с Windows.
Борьба Классов Прежде чем вы запрыгнете на ринг с MIDI музыкой, вам нужно решить, как MIDI файлы будут приспособлены к структуре нашего любимого звукового движка. В настоящее время звуковой движок использует класс CSound для воспроизведения звуковых эффектов. Это хорошо, но все же не отлично. Следуя принципам хорошего проектирования на C++, код этой главы не слишком сильно изменится. Он делает CSound основным классом, с тремя дочерними классами: CDirectMusicSegment, CSoundEffect, и CMIDIMusic. Рис. 6.2 объясняет новую схему классов. CSound практически «выпотрошен». В предыдущих главах он содержал сегмент DirectMusic, громкость и много другого содержимого. А теперь это просто абстрактный базовый класс. Это так, потому что в новой иерархии классов CSound является базовым классом для любого аудио объекта, выходящего из колонок. Это включает 1 Эффект варьирования высоты тона, например «подвывания» электрогитары или изменения звучания скрипки. - Примеч. науч. ред.
Глава 6
112 CSound - это абстрактная основа, из которой получаются все остальные классы. Этот класс содержит простые виртуальные функции Play, Stop и IsPlaying
CDirectMusicSegment - это базовые класс для всего, что может быть загружено в сегмент CDirectMusicSegment
CSound Effect
CSoundEffect - это то, чем раньше был CSound. CMIDIMusic - это новый класс из этой главы
Вы узнаете о CDirectShowMusic, CMP3, и CTrackedMusic в последующих главах
Рис. 6.2. Новая схема звукового движка
вещи вроде звуковых эффектов и MIDI музыки (которая использует сегменты DirectMusic), а также вещи, не имеющие к DirectMusic никакого отношения, например музыкальные файлы МРЗ или Ogg Vorbis (о которых вы больше узнаете в следующих двух главах). CSound обладает тремя чисто виртуальными методами: Play, Stop и IsPlaying, а также еще двумя виртуальными методами: GetVolume и SetVolume. Эти пять методов представляют то, что я- считаю минимально необходимым интерфейсом для любого типа звука, выходящего из колонок. Будет ли это МРЗ, WAV файл или чанк динамической музыки, сочиненной налету DirectMusic, вы, как минимум, захотите ее воспроизвести, остановить, убедиться в том, что она играет, и регулировать ее громкость. У CSound появляется дочерний класс - CDirectMusicSegment. Этот класс представляет сегмент DirectMusic и содержит то, что раньше содержал CSound. У него есть сегментный указатель DirectMusic, и он использует этот сегментный указатель
ВоспроизВедение MIDI в реализации Play, Stop и IsPlaying. В будущем, если вы захотите совершить с сегментом какое-то новое действие (независимо от того, будет ли это музыкальный или звуковой эффект), вы можете вставить эти методы в этот класс. У CDirectMusic Segment появилось два дочерних класса: CSoundEffect и CMIDIMusic. Методы, которые имеют смысл только для звуковых эффектов или только MIDI музыки (например, настройка темпа), принадлежат этим классам. Почему так? Это может показаться глупым: разбивать звуковые эффекты и музыку в разные классы, особенно потому что DirectMusic идет на все эти трудности по загрузке WAV файлов и MIDI файлов в один сегментный объект. Делая это, DirectMusic позволяет вам рассматривать WAV файлы и MIDI файлы одинаковым образом. Но то, что ты можешь, тем не менее/вовсе не означает, что ты обязан. Для музыкальных файлов существуют такие действия, которые не имели бы никакого смысла по отношению к звуковым эффектам. Например, темп имеет смысл рассматривать только для MIDI музыки, но не для звуковых эффектов WAV. Форматы взаимозаменяемы: и WAV файлы, и MIDI файлы могут быть использованы как для звуковых эффектов, так и для музыки, но концептуальные различия между «фоновой музыкой» и «звуковым эффектом» остаются. (А если вы все же не хотите делать между ними различия, то вы можете просто использовать класс CDirectMusicSegment вместо ответвлений CSoundEffect и CMIDIMusic!) Оставшаяся часть иерархии движка остается прежней. Аудио менеджер по-прежнему смотрит за всеми операциями, а множество других классов (CWAVFile, CMixer и т. д.) остались без изменений.
Saepyaka u ВоспроизВедение MIDI файлов Как я уже упомянул в начале этой главы, DirectMusic старается рассматривать MIDI музыку и WAV файлы одинаковым образом. Они оба загружаются сегменты с помощью объекта Loader (загрузчик). Код, осуществляющий загрузку песни MIDI с диска, памяти или ресурса, ничем не отличается от кода для загрузки WAV файла.
Улучшение aBykoBozo gBiukka Однако есть и плохие новости: операция загрузки в аудио менеджере должна быть несколько изменена, чтобы приспособиться к этому. Аудио менеджер позволяет вам загружать WAV посредством ее метода LoadSound. Хорошим проектным решением было бы заставить ее загружать WAV файлы путем создания трех новых методов LoadMTDI (один для загрузки с диска, второй для загрузки из памяти и третий для загрузки с ресурсов).
114
Глава 6
Вам не нужно вырезать и вставлять операции LoadSound и изменять их названия. Вам не следует никогда вырезать и копировать большие куски кода, потому что, когда бы вы это ни делали, вам необходимо увеличивать количество кода, с которым вам приходится иметь дело. Гораздо лучшим решением является изолировать эти куски кода в отдельные основные функции, а потом заставить операции LoadSound и LoadMIDI вызывать эти функции. Эти три основные функции называются LoadSegmeraFromDisk, LoadSegmentFromMemory и LoadSegmentFromResource. Они используют различные аргументы. Разберем для примера одну из них - LoadSegmentFromDisk. void CAudioManager::LoadSegmentFromDisk(CDirectMusicSegment *dest, s t d : : s t r i n g filename) { HRESULT hr; // преобразуем название файла в широкую строку WCHAR widefilename[MAX_PATH]; DXUtil_ConvertGenericStringToWide( widefilename, filename. c__str () ) ; // заставляем объект Loader загрузить этот файл hr = mJLoader->LoadObjectFron\File ( CLSID_DirectMusicSegment, IID_IDirectMusicSegment8, widefilename, (void**} &dest->m_Segment}; ThrowIfFailed(hr, "LoadObjectFromFile failed."}; }
Этот код содержит практически то же самое, что содержала раньше перегрузка операции загрузки с диска метода LoadSound. Пропущена реализация класса; вместо создания нового класса, LoadSegmentFromDisk передается указатель на класс. Это благодаря новому дизайну. LoadSound создает классы CSoundEffect и передает их LoadSegmentFromDisk, несмотря на то что LoadMIDI создает классы CMIDTMusic. Благодаря полиморфизму, LoadSegmentFromDisk не знает о различии между классами CMIDIMusic и CSoundEffect; он использует их общий базовый класс - CDirectMusicSegment Вот новая версия LoadSound. CSoundPtr CAudioManager::LoadSound{std::string filename) { CSoundEffect *snd = new CSoundEffect(this); LoadSegmentFroraDisk(snd, filename); return(CSoundPtr(snd));
Воспроизведение MIDI
115
Как вы можете видеть, теперь LoadSound просто создает класс, вызывает LoadSegmentFromDisk для совершения грязной работы и возвращает новый CSoundPtr. LoadMIDI выглядит фактически так же. CSoundPtr CAudioManager::LoadMIDI(std::string filename) { CMIDIMusic *music = new CMIDIMusic(this); LoadSegmentFromDisk(music, filename); return{CSoundPtr(music) ) ; }
Вместо CSoundEffect, LoadMIDI создает класс CMIDIMusic, который делает то же самое, что и LoadSound. Другие две перегрузки LoadSound и LoadMIDI выглядят похоже - они просто создают соответствующий класс и передают его LoadSegmentFromMemory или LoadSegraentFromResource.
Получение указателя CMIDIMusic Вы, должно быть, интересуетесь теперь, где взять специальные методы для CMIDIMusic, когда вы уже загрузили MIDI файл. Например, вам может понадобиться изменить темп. Метод SetTempo содержится в классе CMIDIMusic, но все, что у вас есть, - это указатель на CSound! Чтобы выбраться из этой неловкой ситуации, используйте информацию о типах в процессе выполнения (run-time type information). RTTI была предназначена специально для случаев вроде этого: у вас есть указатель на базовый класс, но необходимо знать, является ли этот класс вторичным. Например, вот как получить указатель на CMIDIMusic из CSoundPtr. CSoundPtr snd = g_AudioMgr.LoadMIDI("test.mid"}; CMIDIMusic *music = dynamic__cast
{snd. Get {) ) ; If (music !=- NULL) { // snd действительно указатель на CMIDIMusic! ) else ( // snd IS Sue oeacaoaeal CMIDIMusic! D'oh'
Обратите внимание на ключевое слово dynamic_cast. Это ключевое слово преобразует указатель в другой тип. Здесь код получает указатель путем вызова snd.Get, а затем попытки преобразовать его к указателю CMIDIMusic. 'Если 2 Фраза мультипликационного персонажа Гомера Симеона. Означает выражением недовольства чем-либо. Примеч. перев.
116
ГлаВа 6
dynamic_cast работает, указатель кладется в music (обратите внимание, что music и snd до сих пор указывают на один и тот же адрес в памяти, они просто принадлежат различным типам). Если по каким-либо причинам dynamic_cast не может преобразовать указатель (например, если вы передали указатель на класс CSoundEffect), он выставит music в NULL. Таким образом, вы можете добраться до методов в CMIDIMusic или CSoundEffect, обладая указателем на CSound или CDirectMusicSegment.
Изменение темпа Вы можете сделать с вашими MIDI файлами одну замечательную вещь (которая невозможна для МРЗ или WAV): регулировать темп налету. Тетрис сделал популярным изменение темпа для создания напряжения: когда ваше игровое пространство почти заполнено, музыка убыстряется, а когда (или если!) ситуация вновь возвращается под ваш контроль, скорость музыки снова падает до нормальной. Этот простой прием добавил очень много качеству игры, и вы можете использовать его под вашим собственным названием.
Сравнение nacmpoek темпа с помощью gopojkku или объеКста Performance В DirectMusic есть два различных способа, которыми вы можете изменить темп. Вы можете изменить дорожку темпа, содержащуюся в сегменте, или вы можете отключить от работы эту дорожку темпа и заставить объект Performance изменить темп любого сегмента, воспроизводимого им. Это звучит странно, но лучше всего будет использовать оба способа. Вы хотите изменить темп сегмента, поэтому, если вы позже запросите сегмент о темпе, он выдаст действующий темп, а не тот, который был, когда он впервые был загружен. Например, если вы загрузите MIDI файл с темпом 109 тактов в минуту и потом смените темп воспроизведения на 200 тактов в минуту, то, когда вы запросите сегмент о темпе, он возвратит 109 (темп в сегменте действительно 109 - он только воспроизводится как 200 из-за соответствующей настройки воспроизведения). Вы, наверное., также захотите настроить темп объекта Performance, потому что вам нужно, чтобы вся музыка ускорялась, когда вы меняете темп, а не только этот конкретный сегмент. К счастью, код, позволяющий достичь этого, для обоих способов очень прост. Вот как выглядит CMIDIMusic: :SetTempo.
ВоспроизВедение MIDI
JJ7
void CMIDIMusic: : SetTempo {int beats_per_minute) { // шаг первый: установка темпа дорожки DMUS_TEMPO_PARAM Tempo; Tempo. dblTempo = beats_per_minute; Throwlf Failed ( m_Segment->SetParam(GUID_TempoParam, QxFFFFFFFF, 0, 0, sTempo) , "CMIDIMusic: :SetTerapo: SetParam failed!" ); // шаг второй: установка темпа исполнения DMUS_TEMPO_PMSG* pTempo; ThrowIfFailed( m__Manager->GetPerf ormance ( ) ->AllocPMsg ( sizeof (DMUS_TEMPO_PMSG) , (DMUS_PMSG**}&pTempo) , "CMIDIMusic: : SetTempo: couldn't allocate PMsg!" ); // начинаем изменение темпа. ZeroMemory {pTempo, sizeof (DMUS_TEMPO_PMSG) } ; pTempo->dwSize - sizeof (DMUS_TEMPO_PMSG) ; pTempo->dblTempo - beats_per_ininute; pTempo->dwFlags = DMUS_PMSGF_REFTIME; pTempo->dwType = DMUS_PMSGTJTEMPO; ThrowIfFailed( m_Manager->Get Performance () ->SendPMsg ( (DMUS_PMSG*) pTempo) , "couldn't send PMsg!"
Этот код основан на образце кода, данном в документации к DirectX, см. DirectX Audio\Using DirectX AudioYPlaying Sounds\Timing\Changing the Tempo. Первый этап - это установка дорожки темпа на данное значение (beats_per_minute). Код делает это путем заполнения структуры DMUS_TEMPO_PARAM и пересылки ее дорожке путем вызова метода SetParam сегментного интерфейса. Структура DMUS_TEMPO_PARAM содержит только один элемент, dblTempo, новый темп, кторый вы хотите установить. Второй шаг несколько более сложный. Во-первых, код должен выделить новый PMsg и сохранить его в pTempo. После выделения, код заполняет структуру. Самый важный элемент - это dblTempo, который должен быть установлен на тот темп, на который вы хотите изменить темп воспроизведения. Обратите внимание, что есть много элементов, которые автоматически выставляются в ноль при вызове кодом ZeroMemory. Тем не менее, элемент dwFlags не может обращаться в ноль; он должен быть выставлен в DMUS_PMSGF_REFTIME или
118
Глава 6
в DMUS_PMSGF_MUSICTIME. Это не важно, в какой именно, потому что элементы rtTime и mtTime (не показанные в этом коде) выставлены в ноль благодаря ZeroMemory, поэтому я просто выбрал тот, в котором меньше букв. Аналогично, элемент dwType также не может обращаться в ноль. Он должен иметь значение DMUS_PMSGT_TEMPO, чтобы DirectMusic знал, что он получил событие «изменения темпа». В итоге, когда структура вся уже настроена, код вызывает SendPMsg, которое «выпаливает» сообщение рТетро и заставляет DirectMusic изменить темп.
Получение темпа Получение темпа проще, чем его установка. Чтобы получить темп, просто передайте структуру DMUS_TEMPO_PARAM сегментному методу GetParam. int {
CMIDIMusic::GetTempo()
DMUS_TEMPO_?ARAM Tempo; m_Segment->GetParam(GUID_TempoParam, OxFFFFFFFF, 0, 0, NULL, &Tempo);
return(Tempo.dblTempo) ;
Передайте GetParam GUID того параметра, который вы хотите получить (GUIDJTempoParam), и структуру, которую он должен туда положить (Tempo), и запрашиваемая информация неожиданно появится! Не беспокойтесь об остальных параметрах между GUID_TempoParam и Tempo - обычно они не используются. Если вы хотите узнать, что они собой представляют, просмотрите GetParam в документации DirectX.
Масштабирующий мно&итель темпа DirectMusic дает другой тип контроля, называющийся мастером темпа, о котором вы также должны знать. Мастер темпа - это не темп, а масштабирующий фактор для темпа воспроизведения. По умолчанию он принимает значение единица, но вы можете изменять его значение от нуля до единицы для торможения воспроизведения, или вы можете поднять его значение выше единицы, чтобы ускорить воспроизведение. Константы DMUS_MASTERTEMPO_MIN и DMUS_MASTERTEMPO_MAX определяют минимальный и максимальный пределы.
Воспроизведение MIDI
119
Мастер темпа очень удобен для того, чтобы ускорять музыку в стиле Тетрис, потому что вам не нужно знать начальный темп, чтобы ускорить мелодию. Все, что вы делаете, - это поднимаете значение мастера темпа выше единицы для ускорения ритма и затем устанавливаете в единицу для восстановления нормального темпа. Получение и настройка значения мастера темпа очень проста. Я добавил парочку новых методов в CAudioManager, чтобы сделать это. Во-первых, вот SetMasterTempoScaleFactor. void CAudioManager::SetMasterTempoScaleFactor{float value) i if (NULL == m_Performance) return; ThrowlfFailed(m_Perfоrmance->SetGlobalParam(GUID_PerfMasterTempo, &value, sizeof(value)), "CAudioManager::SetMasterTempo: SetGlobalParam failed."); }
Этот кусок похож на то, как вы устанавливали мастера громкости: просто вызовом SetGlobalParam, передавая GUID-roro, что вы хотите настроить, вместе с новым значением и размером в байтах нового значения. Теперь идет GetMasterTempoScaleFactor. float CAudioManager::GetMasterTempoScaleFactor() { if (NOLL == m_Performance) return(0.Of); float tempo=0.0f; ThrowlfFailed(m_Performance->GetGlobalParain(GUID_PerfMasterTempo, Stempo, sizeof {tempo} ) , "CAudiqManager::GetMasterTempo: GetGlobalParam failed."); return(tempo); )
Это так же просто: вызываем GetGlobalParam, передаем GUID параметра, который нам нужно запросить, вместе с переменной, чтобы в ней хранить значение, и размером переменной, и DirectMusic с удовольствием возвратит вам любой глобальный параметр, который вы пожелаете. Эта пара простых методов, которые очень удобны, когда вам не нужно заботиться о том, какой темп у мелодии, а нужно просто ее ускорить.
ДетеКтироВание ритма Используя MIDI, вы можете детектировать ритм музыки и делать что-то с его помощью. Например, вы можете сделать назойливого противника в игре, который будет танцевать под фоновую музыку, или вы можете сделать стробоскопические огни и другие эффекты, которые бы вспыхивали в такт.
12О
Глава 6 Совет. Есть небольшое отставание (20-100 мс) между тем, когда происходит ритмический удар и когда код об этам узнает. Тем не менее, это запаздывание не воспринимается человеческим ухом, поэтому, если вам нужно просто синхронизовать звук и изобрао>сепие, просто забудьте об этом. Однако если вы собираетесь сделать что-то более продвинутое (например, синхронизовать другой прибор или синтезатор с ритмом DirectMusic), вам следует сделать поправку на это отставание. Ознакомьтесь с документацией к DirectX, чтобы узнать, как это делается.
Уведомление DirectMusic Детектирование ритма требуется запрограммировать. DirectMusic не обладает функцией IsPlayingBeat, которую могли бы вы вызвать; вместо этого, существует другой тип взаимодействия, с помощью так называемых сообщений уведомления. Если происходит что-то интересное (например, биение (ритм), воспроизведение сегмента по циклу или целый ряд других событий), DirectMusic добавляет сообщение в свою внутреннюю очередь (как в Windows). Вы снимаете эти сообщения с этой очереди, используя метод GetNotificationPMsg воспроизведения. Совет. Вспомните, ведь вы уже сталкивались с PMsg раньше: когда вы настраивали темп, вы посылали воспроизведению DMUS_TEMPO_PMSG. Теперь вы на принимающей стороне.
Прежде чем вы этого сделаете, тем не менее, вы должны сообщить DirectMusic, какие именно собьггия вас интересуют. Вы можете сделать это, вызвав метод AddNotificationType воспроизведения. Каждое сообщение обладает своим GUID; вы передаете AddNotificationType GUID того сообщения, которое вам нужно (табл. 6.1), Рис. 6.3 показывает базисную блок-схему использования сообщений воспроизведения DirectMusic. В сущности, вы можете вызвать AddNotificationType во время вашей программы инициализации, а затем периодически вызывать GetNotificationPMsg (например, внутри главного цикла игры). GetNofiticationPMsg возвращает S_OK, если он получает сообщение, и S_FALSE, если нет сообщений. Таким образом, чтобы обработать все сообщения, которые появились между этим моментом и последним разом, когда вызывалась GetNotificationPMsg, напишите код наподобие следующего. DMUS_NOTIFICATION_PMSG* pPmsg; while (m_Performance->GetNotificationPMsg(SpPmsg) == S_OK) { // тут делаем с pPmsg что-то интересное // освобождаем его, когда закончим m_Performance->FreePMsg((DMUS_PMSG*JpPmsg); 1
ВоспроизВедение MIDI
121
3
Таблица 6.1. QUID для извещения GUID
Сообщение
GUID
Сообщение
GU!D_NOTIFICATION_CHORD
Произошло изменение аккорда. Этот GUID используется главным образом для того, чтобы сообщить вам, что программа-компоновщик DirectMusic решила изменить играющийся аккорд
G№D_NOTIFICATION_COMMAND
Сообщение о команде
GUID_NOTIFICATION_MEASUREANDBEAT
Это сообщение посылается наружу на такт ритма. Значение ноль внутри dwFieldl показывает новое измерение. Любое другое значение показывает такт внутри этого измерения
GUID_NOTIFICATION_PERFORMANCE
Произошло событие воспроизведения. Вы можете посмотреть HadwNotificationOption, чтобы определить, что именно это такое
GUID_NOTIFICATION_RECOMPOSE
Трек был переделан программой-компоновщиком DirectMusic
GUID_NOTIFICATION_SEGMENT
Произошло сегментное событие. Вы можете посмотреть в dwNotificationOption, чтобы определить, что конкретно случилось
а. Сокращение от globally unique identifier - глобально уникальный идентификатор (16-байтовая величина, генерируемая утилитой и используемая для получения уникальных имен интерфейсов СОМ-объектов, UUID). Примеч. иерее.
GetNotificationPMsg берет указатель на структуру DMUS_NOTIFICATIONJPMSG, которую вы хотите заполнить. Структура DMUS_NOTIFICATION_PMSG содержит всю информацию, которая вам нужна о сообщении, включая его тип (guidNotificationType), дополнительный подтип (dwNotificationOption) и другие дополнительные параметры (dwFieldl и dwFieldl). Предупреждение. После того как вы ознакомились с сообщением уведомления, которое вы получили от GetNotificationPMsg, вы должны удалить его путем вызова метода FreePMsg воспроизведения. Если вы этого не сделаете, это повлечет за собой утечку памяти!
BcmaBka извещений в aBykoBou gBuikok Теперь, когда у вас уже есть базовое представление о схеме сообщений уведомления DirectMusic, пришло время вставить ее в звуковой движок.
Глава 6
122 Вызовите AddNotifkatioriType, чтобы показать DirectMusic, какие виды сообщений вас интересуют
Инициализируйте все остальное в вашей игре
Больше никаких сообщений! Делайте все остальное, что вам нужно для этого фрейма Рис. 6.3. Блок-схема фундаментального процесса использования событий воспроизведения DirectMusic
Самая большая проблема здесь связана с тем методом, который вы используете, чтобы сообщения взаимодействовали с кодом, который использует ваш звуковой движок. Различные игры захотят прислушиваться к разным сообщениям и отвечать на них по-разному. Вам необходимо сделать так, чтобы звуковой движок мог легко сообщать коду игры, что что-то случилось. В С большая часть программистов решают эту проблему, используя функциональные указатели. Код-клиент предоставляет адрес функции, которая должна быть вызвана, скажем, при появлении ритма, и звуковая программа просто вызывает этот функциональный указатель, если это нужно. Это вполне приемлемое решение проблемы, которое предлагает С.
Воспроизведение MIDI
123
Тем не менее, программисты C++ заходят несколько дальше, используя класс вместо функциональных указателей. Этот класс содержит виртуальные функции для каждого типа событий, которые могут случиться. Звуковой движок содержит указатель на этот класс, и, когда настает нужный момент, звуковой движок использует этот указатель, чтобы вызвать методы этого класса. Например, рассмотрим класс под названием CNotificationHandler. Этот класс содержит виртуальные функции для каждого типа событий, на которое вы можете захотеть как-то прореагировать. class CNotificationHandler { public: CNotificationHandler() { } virtual -CNotificationHandler() { } // эта функция вызывается, когда появляется ритм virtual void OnBeat{DMUS_NOTIFICATION_PMSG Smsg) { } // Эта функция вызывается, когда впервые встречается новый размер virtual void OnMeasure(DMUS_NOTIFICATION_PMSG Smsg) { } // и т.д...
};
Обратите внимание, что все функции виртуальные, и ничего не делайте. Теперь представьте себе, что у звукового движка есть указатель на этот класс под названием m_NotificationHandler. По умолчанию m_Notif!cationHandler выставлен в NULL, но код игры может это изменять. Когда происходит какое-то событие, звуковой движок использует указатель m_NotificationHandler, чтобы вызвать соответствующий метод. void CAudioManager::DispatchNotificationMessages() { DMUS_NOTIFICATION_PMSG* pPmsg; while <m_NotificationHandler && m_Performance->GetNotificationPMsg(&pPmsg) == S__OK) { if (pPmsg~>guidNotificationType == GUID_NOTIFICATION_MEASUREAMDBEAT) { // если это такт ритма, вызываем OnBeat... иначе вызываем OnMeasure. if (pPmsg->dwFieldl == 0) { m_KotificationHandler->OnMeasure(*pPmsg); } else { m_NotificationHandler->OnBeat(*pPmsg); } } // не забудьте освободить сообщение, когда вы с ним закончите работать! m Performance->FreePMsg((DMUS_PMSG*)pPmsg); } /7 закрываем while } // закрываем метод
124
Глава 6
Красота этого плана в том, что теперь код игры может получать новый класс из CNotificationHandler и подменить виртуальные функции, чтобы сделать что-то нужное. Затем код может установить указатель m_NotificationHandler в то место, куда потребуется этому полученному классу, таким образом, чтобы тот мог делать то, что нужно, в соответствии с полученным сообщением. Как вы, наверное, уже догадались, звуковую управляющую программу я писал именно так. Управляющая программа обладает двумя методами доступа, SetNotificationHandler и GetNotificationHandler, которые позволяют вам устанавливать и возвращать в прежнее положение указатель m_NotificationHandler. Игра периодически вызывает DispatchNotificationMessages, и DispatchNotificationMessages использует указатель m_NotificationHandler для того, чтобы справиться с сообщениями с помощью вызова OnMeasure, OnBeat и других виртуальных методов. Пример программы для этой главы показывает, как работает такая система. В Ch6pl_MIDIPlayback есть определение для класса под названием CCh6plStupid NotiflcationHandler. Этот класс получается также из CNotificationHandler, но с другим способом реализации методов. class CCh6plStupidNotif icationHandler : public CNotificationHandler { virtual void OnBeat (DMUS_NOTIFICATION_PMSG &msg) { cout « ". .Beat! } virtual void OnMeasure (DMUS_NOTIFICATION__PMSG &msg) {
cout « endl « "Measure!..";
Как вы можете видеть, он действует в соответствии со своим названием глупый класс, который просто печатает сообщения. Более высокоорганизованные классы делали бы что-то более интересное, но для целей примерной программы это работает просто замечательно.
Организация многопоточной o6pa5omku детектирования сообщений ЕСЛИ вас раздражает идея необходимости все время вызывать метод DispatchNotificationMessages управляющей программы, есть другой способ: многопоточная обработка! Вы можете создать отдельный поток, единственная задача которого будет отслеживать и отвечать на сообщения DirectMusic.
Воспроизведение MIDI
125
Естественно, вы не захотите бесконечно крутиться в этом отдельном потоке это было бы сжиганием драгоценного времени ЦПУ. Вы захотите, чтобы этот поток «спал», пока не придет сообщение, затем принимал какое-то решение в связи с этим сообщением, и снова «спал», пока не придет следующее сообщение. К счастью, DirectMusic обеспечивает возможность сделать это. Вы можете передать ему дескриптор примитива сообщений Windows, и он будет извещать о событии, когда будет генерироваться новое сообщение. Чтобы сделать так, создайте соответствующее сообщение, используя функцию Win32 API Create Event, затем сообщите DirectMusic, что вы бы хотели использовать это созданное вами сообщение, передавая его методу SetNotificationHandle воспроизведения. Теперь вы можете создать код для вашего потока, используя WaitForSingleObject, передавая ей тот же самый дескриптор, который вы передали DirectMusic, и он автоматически «заснет» до тех пор, пока DirectMusic не известит о событии. После «пробуждения» поток может использовать GetNotificationPMsg для того, чтобы взять это сообщение и сделать с ним то, что нужно. После этого он может освободить сообщение посредством вызова FreePMsg и уйти в цикл, пока не появится новое сообщение. Предупреждение. Убедитесь в том, что, когда вы создаете ваше сообщение, вы делаете его самосбрасывающимся или что вы сами его удаляете после того, как оно просигнализировало. Иначе вы никогда не сможете пойти поспать после того, как получите ваше первое сообщение! Чтобы заставить сообщение автоматически сбрасываться, проставьте false в качестве второго параметра в CreateEvent. Для получения большей информации ознакомьтесь с документацией Win32 API к CreateEvent.
Документация DirectX обладает примером такого кода. Вы можете найти его (вместе с более детальным объяснением принципа его работы) в DirectX AudiottJsing DirectX AudioVPlaying Sounds\Notification and Event Handling. Звуковой движок делает часть этой работы для вас. Он создает сообщение и говорит DirectMusic об этом. Вы можете получить доступ к этому сообщению с помощью метода GetNotificationEvent CAudioManager. Потом напишите свой собственный код, используя WaitForSingleObject, и не забивайте голову вызовом DispatchNotificationMessages до тех пор, пока не получите сообщение! Я не включил пример кода многопоточной обработки, но будет достаточно просто собрать воедино все, что я объяснил. Ха-ха, упражнение для читателя!
126
Глава 6
MIDI без DirectMusic Как и в случае со многими другими объясненными мною вещами, если вы не хотите использовать DirectMusic для воспроизведения MIDI файлов, вы можете использовать Windows MCI для их воспроизведения. Обратите внимание, что Windows MCI не дает вам точного контроля над MIDI файлами, например, вы не сможете синхронизовать их с другими объектами и вы не можете воспроизводить определенные MIDI файлы. Просмотрите документацию Windows Platform SDK, в Platform SDKAGraphics and Multimedia Services\Multimedia Audio\Musical Instrument Digital Interface (MIDI)\About MIDIVMedia Control Interface (MCI). Вы также можете воспроизводить MIDI файлы третьим способом - используя стандартные низкоуровневые операции MIDI по вводу и выводу. Эти программы - это то, что многие синтезаторные программы используют для записи, воспроизведения и обработки MIDI данных на очень низком уровне. Там нет ничего, что вы захотели бы сделать и не смогли бы, но некоторые вещи очень трудны в реализации, потому что требуют написания большого количества кода. Большая часть игр не требуют того, чтобы беспокоиться о низкоуровневом интерфейсе MIDI ваш лучший выбор заключается в том, чтобы использовать DirectMusic, если вы хотите продвинутый контроль и возможность синхронизации MIDI, или MCI, если вы хотите простое воспроизведение MIDI без ограничений DirectX.
Заключение Эта глава завершила несколько вещей. Самое простое, что вы изучили, - это то, что и MIDI файлы и WAV файлы загружаются в сегменты и воспроизводятся одинаковым образом. Как бы там ни было, вы также совершили небольшую реорганизацию в звуковом движке (подготовив сцену для следующих глав) и вы узнали, как посылать и получать сообщения DirectMusic. В следующих главах вы узнаете, как воспроизводить другие типы звуковых файлов (включая Ogg Vorbis и МРЗ), прежде чем вы вернетесь к славным интерфейсам Segment (сегмента) и Performance (воспроизведения) и по-настоящему их раскроете, когда будете изучать динамическую музыку.
Глава 7
Воспроизведение МРЗ uWMA В этой главе вы начинаете изучать, как читать и воспроизводить другие музыкальные форматы, кроме MIDI. Сейчас на разделочной колоде лежат форматы МРЗ и WMA (Windows Media Audio); в последующих главах вы запустите зубы в Ogg Vorbis, CD аудио и трекерную музыку. Но сейчас вот все, что вам нужно знать о воспроизведении МРЗ.
Воспроизведение MPEG Layer 3 (МРЗ) Так как вы читаете эту книгу, я намерен предположить, что вы знаете, что такое МРЗ, что МРЗ означает MPEG 3 уровня, a MPEG означает Motion Picture Experts Group (та же самая группа людей, которая принесла нам алгоритм сжатия видео, использующийся в DVD!). Это все, конечно, интересно, но вам все же нужно знать, как работает МРЗ и как написаггь код, чтобы воспроизводить файлы.
Kak работает МРЗ Очарование МРЗ в том, что этот формат звучит как WAV, но занимает гораздо меньше места. Стандартный WAV файл мелодии займет от 20 до 40Мб памяти, в зависимости от настроек записи и длины песни. МРЗ же обычно занимает только десятую часть, а звук практически такой же, Магия? Нет, просто психоакустическое сжатие. Звуковой фрагмент. Прослушайте.музыкальные фрагменты 7.1, 7.2 и 7.3, чтобы услышать, как формат МРЗ сжимает звук.
128
Глава 7
Причина, по которой МРЗ может занимать всего десятую часть WAV, а звучать хорошо, лежит в человеческом мозге, а именно, в глупости человеческого мозга. Мелодия представляет собой не что иное, как набор звуковых волн разных частот и амплитуд, и человеческий мозг не в состоянии расслышать все эти различные звуковые волны одновременно. Некоторые из них слишком высоки или низки, некоторые теряются из-за более громких. Магическая философия МРЗ сжатия - это «если мы не можем этот звук услышать, зачем беспокоиться о его кодировании?» Громкий удар тарелок сделает неслышной акустическую гитару на фоне, поэтому нет необходимости тратить байты на то, чтобы быть уверенным в том, что акустическая гитара хорошо передается, когда ударяют тарелки. Кроме того, совершенно не нужно тратить тысячи байт на то, чтобы воспроизвести форму такой высокой звуковой волны, что она находится вне пределов слышимости человеческого уха. Ваша собака, наверно, предпочла бы оригинальные CD треки МРЗ, но ваши уши не могут уловить недостачу высокочастотных звуковых волн, поэтому для вас МРЗ лучше.
файловый формат МРЗ Это странно, но у МРЗ файла нет заголовка — он сделан не как RIFF файл с вложенной иерархией и несколькими заголовочными байтами вначале. Вместо этого, МРЗ файл состоит из многих фреймов, и каждый фрейм - это независимый фрагмент мелодии. Каждый фрейм обладает своим собственным маленьким заголовком, который сообщает вам о его скорости передачи битов, версии MPEG (самой распространенной является третья, но есть также первая и вторая) и другой информации, Рис. 7.1 показывает подробный план заголовка фрейма. Как вы можете видеть, целиком заголовок занимает 32 бита и информация упакована в эти 32 бита безотносительно к границам байт. Это немножко раздражает, потому что вы должны использовать битовые маски, чтобы извлечь какую-то информацию (например, скорость передачи битов (bit rate) описывается старшим битом третьего байта). Создатели МРЗ сделали так для экономии места: они хотели сделать МРЗ настолько маленьким, насколько это возможно, и это означало, что каждый бит каждого байта должен содержать полезную информацию. Я не намерен входить в подробности, что значит каждый отдельный элемент информации , потому что для того, чему вы будете учиться в этой главе, вам совершенно не нужны эти низкоуровневые подробности (тем не менее, если вы любоИменно скорость передачи битов, поскольку трЗ - потоковый формат. - Примеч. науч. ред. Более подробно о формате трЗ смотрите на www.mp3.org . - Примеч. науч. ред.
ВоспроизВедение МРЗ и WMA
129
пытны, я положил на компакт-диске пару ссылок, которые подробно объясняют все тонкости). Действительно необходимо вам знать, что МРЗ файл разбит на фреймы и у каждого фрейма может быть своя собственная скорость передачи битов. Размер в битах
Смещение:
Фрейм синхронизации (все биты равны 1 }
БайтЗ
Аудио Версия
Байт 2
Дескриптор слоя Мред Защита от ошибок Скорость передачи битов Частота выборки
Байт 1
Тип заполняемое™ неиспользуемых битов Приватность Тип звучания - стерео,джойнт стерео,дуал моно Режим расширения, только для джойнт стерео Копирайт
Байт О
С .4
Мастер- копия
ГО
Эмфаза
с)
Рис. 7.1 . Что содержится в 4 байтах фрейма
В самом конце МРЗ находится то, что называется аудио тегом. Этот аудио тег содержит название, исполнителя и строку альбома для МРЗ, как показано на рис. 7.2. Размер (в байтах)
(•байтах)
1
30
4
30
30
30
Жанр
Комментарий
Год
Альбом
Исполнитель
Название
97
03
Рис. 7.2. Показывает формат 128-байтового аудио тега 5-1490
33
3
Аудио 1БГ
Глава 7
130
Это все ASCII данные, кроме поля жанра, которое заключает однобитный код, который означает определенный жанр, как показано в табл. 7.1 Таблица 7.1 , Коды жанров Жанр
№
Жанр
№
Жанр
№
0
Блюз
22
Дэт Метал
44
Спэйс
1
Классический рок
23
Шутливая
45
Медитативная музыка
2
Кантри
24
Фонограмма
46
Инструментальный поп
3
Дане
25
Евро- техно
47
Инструментальный рок
4
Диско
26
Эмбиент
48
Этническая музыка
5
Фанк
27
Трип-хоп
49
Готика
6
Грандж
28
Вокал
50
Дарквэйв
7
Хип-хоп
29
Джаз & Фанк
51
Техно-индастриал
8
Джаз
30
Фьюжн
52
Электронная музыка
9
Металл
31
Транс
53
Поп-фолк
10
Нью эйдж
32
Классика
54
Евроданс
11
Старинная
33
Инструментальная музыка
55
Дрим
12
Другая
34
«Кислотная» музыка
56
Совен рок
13
Поп
35
Хаус
57
Юмористическая
14
Эрандби
36
Музыка из игр
58
Религиозная
15
Рэп
37
Музыкальный клип
59
Гангстерская
16
Регги
38
Церковная музыка
60
Музыка из хит-парад
17
Рок
39
Нойс
61
Христианский рэп
18
Техно
40
Альтернативный рок
62
Поп/Фанк
19
Индустриал
41
Бас
63
Джангл
20
Альтернативная
42
Соул
64
Индейская
21
Ска
43
Панк
65
Кабаре
ВоспроизВедеиие МРЗ и WMA
131
Таблица 7.1 . Коды жанров
№ 66
Ньювэйв
87
Ривайвел
108
Примус
67
Психоделик
88
Кельтик
109
Поргрув
68
Рейв
89
Блюграс
110
Пародии
69
Музыкальные заставки
90
Авангард
111
Слоуджем
70
Трейлер
91
Готик рок
112
Клубная музыка
71
Низкого качества записи
92
Прогрессив рок
113
Танго
72
Трибал
93
Психоделик рок
114
Самба
73
Эйсид
94
Симфонический рок
115
Фольклор (народная)
74
Кислотный джаз
95
Медленный рок
116
Баллада
75
Полька
96
Биг бэнд
117
76
Ретро
97
Хор
118
Пауэр баллад Ритмик соул
119
Свободный стиль
Жанр
Жанр
№
Жанр
№
77
Musical
98
Легкая музыка
78
Рок-н-ролл
99
Акустика
120
Дуэт
79
Хард рок
100
Юмор
121
Панк рок
80
Фолк
101
Речь
122
Драм соло
81
Фолк рок
102
Шансон .
123
Акапелла
82
Народов мира
103
Опера
124
Евро-хаус
83
Свинг
104
Камерная музыка
125
Дэнсхалл
84
Фаст фьюжн
105
Соната
85
Бибоп
106
Симфония
86
Л эти на
107
Бути брасс
Чтобы прочитать этот аудио тег, пройдите 128 байт от конца МРЗ файла, который вас интересует, и поищите три ASCII буквы, TAG. Если вы не можете их найти, значит, в этом файле нет аудио тега. Если вы не можете его найти, считайте последние 128 байт и разберите их, используя информацию в рис. 7.2. Вот как это выглядит в коде.
132
Глава 7 bool CMP3AudioTag::Read(std::string filename) { char header[4] = { 0 }; char title[31] = { 0 }; char artist[31] = { 0 }; char album[31] = { 0 }; char year[5] = { 0 }; char comment[31] = { 0 }; char genre = 0; int handle = open(filename.c_str(}, 0_RDONLY); Iseek(handle, -128L, SEEK_END); read(handle, header, 3} ; bool result = false; if (!stricmp(header, "TAG")) { // нормальный тег... у этого МРЗ есть аудио тег! readfhandle, title, 30); mjlitle = title; read(handle, artist, 30); m_Artist = artist; read(handle, album, 30); m_Album = album; read(handle, year, 4); m_Year = year; read(handle, comment, 30); m_Comment = comment; readfhandle, &genre, 1); m_Genre = (MP3_GENRE)genre; m_GenreStr = GenreToString(m_Genre); result = true; ) close(handle); return(result); }
Это код из нового класса, CMP3AudioTag. Если вы работали с файловым вводом/выводом в С, это просто. Единственное опасное действие, это вызов Iseek, который помещает вас на 128 байт от конца файла. Отсюда вы просто читаете данные в описанном порядке, и все устроено! Совет. Мои пальцы устаяи. Я сделал огромный перечень, MP3J3ENRE, который содержит все содержимое табл. 7.1. Я также сделал метод, CMP3AudioTag::GenreToString, который преобразовывает MP3_GENRE в строку STL (Standard Template Library стандартная библиотека шаблонов для C++ 4.О.).
МРЗ В DirectMusic, Когда WAV - это не WAV Все правильно, теперь вы знаете, что никогда не нужно слушать МРЗ с кодом жанра 103. А теперь пришло время научиться воспроизводить МРЗ с помощью DirectMusic. Как оказывается, это не так просто, как вы думаете. Если вы попытаетесь загрузить МРЗ файл в сегмент с помощью Loader (загрузчика), у вас ничего не выйдет и будет ошибка DMUS_E_UNSUPPORTED_STREAM. Вы подумаете, что, судя по На самом деле, например, WinAmp использует свою таблицу из 187 жанров, которая частично не совпадает с оригинальной. - Примеч. науч. ред.
Воспроизведение МРЗ и WMA
133
популярности МРЗ файлов, DirectMusic сделал их простыми, но это не так. Попробуйте загрузить любой МРЗ файл в программу воспроизведения WAV из главы 3 (Ch3pl_WAVPlayback), и вы вновь получите ту же ошибку. Но даже с ограничением DirectMusic есть способ, которым вы можете заставить Ch3pl_WAVPlayback воспроизводить ваши любимые МРЗ, и для этого даже не нужно менять код или переключать форматы! Вот в чем хитрость. Возьмите свой любимый МРЗ и превратите его в WAV файл. У вас получится файл около 30 или 40Мб. Это был бы достаточно ламерский трюк, если бы требовалось сделать только это. Конечно, вы можете заставить воспроизводиться WAV файлы, но вам-то нужны МРЗ! Не отчаивайтесь, продолжайте читать. Возьмите этот WAV файл и загрузите его в Sound Recorder (как вы знаете, это глупая записывающая программа, которая поступает с Windows в качестве дополнения, она показана на рис. 7.3). "*t Sound- Sound Itecorder
Рис. 7.З. Windows Recorder во всей своей красе
Выберите Properties из меню File, затем щелкните по кнопке Convert Now. Появится диалоговое окно (как показано на рис. 7.4). Sound Selection ti лпе:
Fenat AUribUes:
I- ••
-.
НШЮ kHa. в В». Mono
1 kb/iec
iH —J
OK
Cancel
Рис. 7.4. Диалоговое окошко Convert Now
Из ниспадающего меню Format выберите MPEG Layer 3. Дважды нажмите на OK, чтобы убрать маленький диалог, а также страничку свойств. Окажется, что ничего не случилось.
134
Глава 7
Из меню File выберите Save. Теперь, если вы заглянете в Explorer и посмотрите, какого размера ваш WAV файл, вы увидите, что он порядка одного или двух мегабайт — размера МРЗ. Будьте уверены, что теперь Ch3pl сможет загрузить и воспроизвести этот WAV файл. Что происходит? Прежде всего, запомните, что WAV файл - это RIFF файл, a RTFF может хранить много различных типов данных. До сих пор вы использовали WAV файлы, чтобы хранить РСМ-форматированные данные (то же самое, что несжатый звук). Но WAV файлы также могут хранить огромное разнообразие сжатых форматов, включая MPEG Layer 3. Если вы откроете ваш конвертированный WAV файл в двоичной программе просмотра, вы увидите, что у него есть заголовок RIFF, кроме РСМ данных, которые были замещены МРЗ данными. В этом и есть секрет: загрузчик DirectMusic может загрузить МРЗ, если у него есть заголовок RIFF, как у WAV файла. Заголовок RIFF говорит: «Этот WAV файл сжат с помощью технологии МРЗ», - и затем Загрузчик использует кодек МРЗ, чтобы развернуть псевдо-МРЗ и загрузить его в сегмент. Вспомните, что нормальные МРЗ файлы не имеют заголовочных данных - они просто содержат набор фреймов и иногда аудио тег в конце. Загрузчик DirectMusic не знаете что с этим делать, поэтому он предполагает, что это неподдерживаемый файловый формат и выводит ошибку DMUS_E_UNSUPPORTED__STREAM. Но если вы возьмете ту же самую последовательность фреймов МРЗ и скроете ее за заголовком WAV RIFF, загрузчик поймет смысл этого и загрузит файл без проблем. Поэтому, если вы в стесненном положении и прямо-таки должны загружать все через Loader (загрузчик), и вам нужны маленькие размеры МРЗ файлов, то это путь к тому, чтобы добиться наибольшего эффекта и от того, и от другого. Совет. Конвертированный WAV файл должен на самом деле быть меньше, чем МРЗ, с которого вы начинали. Это связано с тем, что конвертер может конвертировать только в МРЗ со скоростью передачи бит 5бк6с, тогда как у оригинального файла было где-то 128-192кбс. Скорость передачи битов 56кбс сильно портит звук, поэтому вы не захотите использовать ее в игре (даже если она действительно помогает замечательной демонстрации).
Воспроизведение МРЗ с помощью DirectShow Для того чтобы воспроизвести обычный МРЗ файл, вам понадобится заручиться поддержкой другой компоненты DirectX, DirectShow. Сфера деятельности DirectShow - это обращение со сложными форматами сжатых файлов, таких, как МРЗ. Вы можете также использовать его для воспроизведения WMA файлов, а также AIFF, AU, SND и даже WAV и MIDI. К счастью,
Воспроизведение МРЗ и WMA
135
процесс, который я намерен вам описать, будет работать без единого изменения для всех этих типов файлов. Это делает стоящей попытку немного разобраться, как работает DirectShow.
Основы интерфейса DirectShow DirectShow работает, связывая вместе фильтры. По существу, фильтр - это что-то, что делает что-либо с мультимедиа потом. Представьте себе, что фильтр — это программное обеспечение, являющееся эквивалентом непомерно дорогой аппаратуры, которую вы так безнадежно хотите купить в вашем любимом музыкальном магазине. Например, работа проигрывателя грампластинок заключается втом, чтобы чувствовать желобки на виниловой пластинке и посылать электрические импульсы на правый и левый выводы звукового разъема. Если вы похожи на меня, вы, конечно же, захотите подсоединить к выходам проигрывателя грампластинок вход эквалайзера, который регулирует величины звуковых частот. Затем вы, конечно же, захотите соединить выход эквалайзера и вход усилителя, который передает эти электрические импульсы к вашим колонкам. Точно так же это работает в DirectShow. Например, чтобы воспроизвести МРЗ файл, вы должны начать с МРЗ кодека, который распакует МРЗ и «вышлет» аудио байты. Вы должны подсоединить к «выходу» кодека «вход» прибора Default DirectSound, который является аналогом усилителя — он должен посылать колонкам звуковые байты. Безусловно, вы можете придумать схему гораздо причудливее этой, но этого совершенно не требуется. То, что вы создали, - это элементарный граф фильтра. В терминологии DirectShow граф фильтра - это набор связанных между собой фильтров. В сущности, вам даже и не понадобится иметь дело с соединением фильтров. Вы можете использовать управляющую программу графов фильтров, чтобы автоматически создавать стандартные графы фильтров для различных типов файлов источников. Вы просто говорите: «Filter Graph Manager, я хочу воспроизвести этот МРЗ», - и управляющая программа графов позаботится обо всей грязной работе подключения фильтров. Таким образом, чтобы заставить МРЗ файл играть, потребуется пара шагов, но в них нет ничего сложного. Вот как это работает.
Стриминг МРЗ Первое, что вы должны сделать, - это создать несколько интерфейсов. Вам понадобятся три: интерфейс IGraphBuilder, которому вы поручите построить граф фильтров, интерфейс IMediaControl (не имеющий никакого отношения к Media Control
136
Глава 7
Interface API Windows'a), который обладает методами для воспроизведения, приостановки и остановки графа фильтров; интерфейс IMediaEvent, чтобы вы могли получать сообщения (например, сообщение, говорящее: «ваш МРЗ начал играть»). Некоторые также любят создавать четвертый интерфейс, IMediaSeeking, чтобы сделать виртуальные кнопки «вперед» и «назад». Из этих интерфейсов вам нужно построить только интерфейс строителя графов. Вы можете дать задание строителю графов построить оставшиеся три интерфейса. Вот как выглядит код создания. CoCreatelnstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID__IGraphBuilder, {void **)SpGraphBuilder); pGraphBuilder->QueryInterface(IID_IMediaControl, (void **}SpMediaControl) ; pGraphBuilder->QueryInterface(IID_IMediaEventEx, (void **}SpMediaEventEx);
Здесь вы видите код создания строителя графов и запрос двух других интерфейсов. Это стандартная штука, созданная для вас, СОМ программистами. Все 1ГО определены в dshow.h. После того как ваши интерфейсы созданы, вы просите строителя графов построить граф фильтров вызовом метода RenderFile. WCHAR widefilename[MAX_PATH]; DXUtil_ConvertGenericStringToWide( widefilename, filename.c_str()); mp3->m_GraphBuilder->RenderFile(widefilename, NULL);
Как и многие вещи DirectX, RenderFile требует юникодовскую версию названия файла, для которого вы хотите построить граф. Второй параметр обычно зарезервирован и может быть выставлен в NULL. После того как граф фильтров окажется на месте, вы можете нажать на виртуальную кнопку воспроизведения с помощью интерфейса управления медиа. ThrowIfFailed(pMediaControl->Run{), " Run f a i l e d . " ) ;
Существуют также интерфейсные методы управления медиа для остановки (Stop) и приостановки (Pause). Как бы там ни было, Stop не перематывает МРЗ на начало - для этого вам необходимо использовать интерфейс IMediaSeeking. После того как МРЗ начнет играть, вы можете освободить все ваши интерфейсы. Следуя хорошей тактике программирования СОМ, их нужно освобождать в порядке, обратном порядку создания: управление медиа, поиск по медиа (если вы его создавали), сообщения медиа и, наконец, IGraphBuilder.
Воспроизведение МРЗ и WMA
137
Сообщения DirectShow Пока вы воспроизводите, останавливаете или иначе манипулируете графом фильтров, DirectShow посылает вам сообщения, которые называются «event». Вы отыскиваете эти сообщения с помощью интерфейса IMediaEvent long eventcode, paraml, param2; while (m_MediaEventEx->GetEvent( Seventcode, Sparaml, ¶m2, 0) =- S_OK) { if (eventcode == EC_COMPLETE) { // МРЗ начал играть } 1
m_MediaEventEx->FreeEventParams(eventcode, paraml, param2); '
Этот код выглядит очень похоже на то, что вы делали в прошлой главе с уведомлениями DirectMusic. В сущности, вы вызываете метод GetEvent, который «вытягивает» первое сообщение из внутренней очереди и возвращает его туда, куда вы хотите. Если сообщений больше нет, GetEvent не возвратит S_OK и цикл закончится. После того как вы закончили работу с сообщением, вы должны вызвать FreeEventParams, чтобы освободить память, которую брал DirectShow. Теперь, когда вы все это знаете, мы узнаем, как добавить поддержку МРЗ в звуковой движок.
Добавление noggepjkku МРЗ В aBykoBou gBuikok Новичкам следует посмотреть strmiids.lib, которая содержит IID интерфейсов, которые вы используете. Это достаточно просто. Следующее — несколько новых классов. Я создал класс CDirectShowMusic, который представляет любой тип аудио файла, который вы можете передавать из DirectMusic. CDirectShowMusic получается из CSound, высшего общего звукового объекта. Из класса CDirectShowMusic я получил класс СМРЗ на случай, если вы захотите добавить какие-то методы, характерные для МРЗ (скажем, манипулирование их аудио тегами). На этот случай я также создал новый класс CMP3AudioTag, с которым мы бегло знакомились пару разделов выше. Сейчас СМРЗ является, в сущности, пустым производным классом. Он просто содержит CMP3AudioTag. Весь интересный код содержится в CDirectShowMusic. Класс CDirectShowMusic осуществляет простые виртуальные функции Play, Stop и IsPlaying (определенные в CSound).
138
Глава 7
Самая трудная часть добавления поддержки МРЗ в наш любимый звуковой движок - это обработка сообщений. Нет никакого легкого способа спросить граф фильтров, играет ли он еще, поэтому звуковому движку необходим «пуленепробиваемый» способ это эмулировать, так чтобы простая виртуальная функция IsPlaying работала надежно. Сначала это кажется простым: делаем логическую переменную и выставляем ее в false в конструкторе. Когда бы ни вызывалась функция Play, выставляем ее в true, а когда вызывается Stop, выставляем в false. Но это не поддерживает случай, когда МРЗ файл играет целиком, а затем сам останавливается. Чтобы обработать этот случай, нужно будет использовать сообщения DirectShow. Поэтому я прикрепил немного нового кода к методу DispatchNotificationMessages звуковой управляющей программы (об этом методе вы узнали в последней главе). DispatchNoti fie ationMess ages теперь вызывает статический метод CDirectShowMusic под названием DispatchDirectShowNotifications. DispatchDirectShowNotifications идет по циклу по всем классам CDirectShowMusic, иллюстрированным теперь примерами, и вызывает нестатическую функцию член ProcessMyDirectShowNotifications. Этот метод работает так, как было описано в предыдущем разделе: он вызывает GetEvent снова и снова, пока в очереди не кончатся сообщения. Если он видит сообщение EC_COMPLETE, он выставляет логическую переменную m_IsPlaying в false. Это гарантирует, что m_IsPlaying всегда точна. В заключение я добавил один новый метод в CAudioManager. Этот новый метод, LoadMPS, загружает МРЗ в новый объект СМРЗ и возвращает его в CSoundPtr. Все достаточно уже разобрано, но если вы что-то не поняли, посмотрите код.
Воспроизведение Windows Media Audio (WMA) Windows Media Audio - это формат, разработанный Microsoft и рекламирующийся, как формат, сжимающий лучше, чем МРЗ. Microsoft утверждает, что вы сможете хранить в два раза больше музыки с помощью WMA, чем вы это можете сделать в МРЗ файлах. Microsoft утверждает, что WMA файл со скоростью передачи бит 64кбс звучит так же, как МРЗ со скоростью передачи бит 128кбс. Вы должны сами решить для себя, правы ли они. В Интернете много прений на тему, какой формат лучше, и множество людей просмотрели уже все виды аудио форматов в поиске настоящего «лучшего». На вашем компакт-диске лежат включенные мною ссылки на некоторые исследования.
Воспроизведение МРЗ и WMA
139
Если вы решите использовать WMA, вам будет приятно узнать, что это очень просто. Просто используйте код DirectShow воспроизведения потока МРЗ! Он замечательно сможет воспроизводить WMA файлы. Я упомянул это здесь, на случай, если вы ищете это где-то в содержании и дошли сюда. Те знания, которые вы ищете, находятся на следующих страницах после тех, которые вы сейчас читаете!
Заключение Эта глава должна была называться «МРЗ с намеком на WMA». В этой главе мы заставили DirectShow играть МРЗ, WMA и некоторые другие типы файлов. Вы немного узнали о формате МРЗ и хитрый трюк, как заставить МРЗ файл казаться WAV файлом. В следующей главе вы продолжите путешествие по миру музыкальных форматов и остановитесь, чтобы изучить Ogg Vorbis, доступную альтернативу формату МРЗ.
Глава 8
Воспроизведение Ogg Vorbis Мы уже изучили два формата сжатия аудио, остался один. В этой главе вы узнаете, как заставить наш звуковой движок поддерживать кодек Ogg Vorbis. На случай, если вы не слышали о нем раньше, Ogg Vorbis - это открытый кодек, созданный Xiphophorus (xiph.org), Он весьма популярен в Linux благодаря своему open-source наследию и стремительно набирает популярность в качестве альтернативы МРЗ. То, что Ogg Vorbis является проектом с открытыми исходниками, делает его просто прекрасным. Вся спецификация бесплатна-у вас есть абсолютно все права на написание кода, который будет читать или писать Ogg файлы, и вам не нужно будет платить за лицензию. Библиотеки Ogg Vorbis доступны по лицензии BSD, что значит, что вы можете использовать их полностью или частично в ваших собственных закрытых приложениях. Совет. Если вы действительно будете широко использовать Ogg Vorbis в вашей игре, я советую вам как-нибудь отблагодарить организацию Ogg Vorbis. Программное обеспечение с открытыми исходниками основано на идее, что человек посмотрит исходники программы, сделает ее лучше, а затем сделает свой вариант открытым для всех, чтобы остальные могли дальше улучшить ее. Подарите организации Xiph.org деньги, чтобы они могли продолжать выпускать замечательный код, и/или подарите ей работу своего мозга, сделав какой-то вклад в улучшение кодека.
Многие исследования, более-менее научные и не только, сравнивали Ogg Vorbis с МРЗ, WMA и т. д., но вам придется самостоятельно оценить каждый звуковой формат и решить, что вам больше всего подходит. Лично я считаю, что Ogg Vorbis звучит лучше, чем МРЗ, но мне можно возразить, что мои студенческие годы, которые Движение за распространение программ с открытыми исходниками. Зачастую (у нас в стране) путается с бесплатным распространением ПО. Из первою далеко не всегда следует второе. - Примеч. науч. ред.
Воспроизведение Одд Vorbis
141
я провел под шум Ozzy и MegaDeth, отрицательно сказались на моем слухе. Поэтому не прислушивайтесь к моим словам - сожмите WAV файл технически эквивалентными форматами МРЗ и Ogg Vorbis и посмотрите, что вам больше понравится. Звуковой фрагмент. Звуковые фрагменты 8.1 и 8.2 это примеры, которые показывают разницу между обычным WAV файлом и его эквивалентам, сжатым Vorbis.
Вы также должны понимать, что то, что я показываю вам, не может быть сделано с МРЗ форматом без уплаты денег. В этой главе мы вставим декодер Ogg Vorbis прямо в звуковой движок. МРЗ требует от вас уплаты лицензионного взноса, чтобы сделать это. Мы обошли стороной этот вопрос в предыдущей главе, потому что там не мы совершали декодирование, a DirectShow Microsoft. С другой стороны, Ogg Vorbis позволяет вам декодировать формат самостоятельно без уплаты денег кому-либо.
Cmpykmypa Ogg Vorbis Ogg Vorbis API состоит из множества различных библиотечных файлов, предназначенных для особенных целей. Начать хотя бы с того, что Ogg Vorbis APT позволяет и статическое связывание и использование DLL. Статические библиотеки автономны, и не требуют никаких внешних DLL файлов, поэтому для большинства игр вы захотите использовать именно их, чтобы избежать трудностей с DLL. Тем не менее, вы также можете использовать суррогатные библиотеки, которые требуют некоторых DLL файлов для работы. Эти DLL можно найти в вашей директории bin. Ogg Vorbis API разделен на четыре отдельные библиотеки: Ogg, Vorbis, Vorbisenc и Vorbisfile.
Библиотеки Одд и Vorbis Эти две библиотеки содержат основные функции, необходимые для работы с файлами Ogg Vorbis. Естественно, библиотека Ogg содержит стандартные функции общего назначения для работы с файлами формата Ogg. Она ничего не знает о Vorbis; в сущности, библиотека Ogg даже не знает (и не хочет знать) о том, работает ли она со звуковыми файлами или с файлами видеоизображения. Она просто создает основной каркас. Библиотека Vorbis содержит основополагающие функции, необходимые для декодирования потоков с помощью кодека Vorbis.
142
Глава 8
Одд Vorbfs: забаВное название, серьезный kogek Проясню это странное название: организация Xiph.org создала структуру сжатия звука и видеоизображения. Эта структура называется Ogg. Сам звуковой кодек называется Vorbis. Это похоже на RIFF: так же, как RIFF файл мог содержать данные, сжатые МРЗ-кодеком, и файл Ogg может содержать данные, сжатые Vorbis. В настоящее время только кодек Vorbis использует структуру Ogg, но Xiph.org планирует выпустить дополнительные кодеки для широкого круга приложений. Эти кодеки после выпуска также будут использовать структуру Ogg, поэтому представьте себе Ogg как нечто, сидящее над Vorbis.
Библиотека Vorbisenc Библиотека предназначена для того, чтобы помочь вам кодировать файлы Vorbis. Здесь вы найдете функции, чтобы устанавливать и сносить кодер Vorbis. Если вы создаете приложение, которое будет кодировать файлы качества CD аудио в файлы Ogg Vorbis, эта библиотека для вас, но в играх вам такое обычно не нужно.
Библиотека Vorbisfile Это очень удобная библиотека, предназначенная для работы с файлами Ogg Vorbis. Эта библиотека использует библиотеки Ogg и Vorbis и добавляет код, который создает им более простой интерфейс, специально предназначенный для декодирования файлов. Эта библиотека лучше всего подходит играм, поэтому именно о ней в этой главе вы узнаете больше всего.
Получение помощи Я должен упомянуть еще кое-что, прежде чем мы двинемся дальше: где брать документацию к библиотекам Ogg Vorbis. В установочной папке есть папка документации, а в ней лежит файл HTML под названием index.html. Это корневой каталог документации. Если вы знаете, про какой APT вы конкретно хотите узнать, вы просто можете открыть одну из папок, находящихся после doc (ogg, vorbis, vorbisenc или vorbisfile) и файл index.html, находящийся там, чтобы получить помощь для именно этой библиотеки.
Воспроизведение Одд Vorbis
143
От нуля k Vorbis Все начинается с установки библиотек. На компакт-диске находятся несколько zip файлов Ogg Vorbis (в папке Goodies). Далее приведен список действий, необходимых для установки этих файлов. (Я все это включил в ваш компакт-диск, если вам любопытно, но в этой главе вам понадобятся только файлы libvorbis-1.0.zip и Ogg Vorbiswin32sdk-1.0.zip.) Наша задача в том, чтобы все файлы из обоих zip файлов положить в дерево каталогов. Поэтому вам нужно разархивировать каждый файл в отдельную директорию (убеждаясь, что структура директории оставлена сохраненной в zip файле), а затем скопировать содержимое каждой директории в одну главную директорию (безопаснее всего перезаписать файлы). В любом случае, когда вы закончите, у вас должно получиться дерево, содержащее примеры include, lib и Win32 и множество других папок. В особенности убедитесь, что в папке include лежат и папка ogg, и папка vorbis. 1. Запустите консоль (откройте Пуск (Start), далее Выполнить (Run), затем наберите cmd, если у вас Windows NT, 2000 или ХР, или command, если у вас Windows 95,98 или Me). 2.
Вам необходимо установить переменную переключения режима под названием SRCROOT. Чтобы установить библиотеки правильно, SRCROOT должна указывать на ту папку, где вы разархивировали файлы. Например, если вы скопировали обе папки в папку под названием C:\libraries\oggvorbis, тогда на приглашение на ввод команд вы должны ввести строчку SRCROOT=C:\libraries\oggvorbis. Я настоятельно рекомендую убедиться, что в инсталляционном пути нет пробелов и с этого момента вам не придется приводить этот путь.
3. Напечатайте cd %SRCROOT%\win32. Если вы правильно настроили переменную переключения режима SRCROOT, у вас получится директория Win32, C:\libraries\oggvorbis\ Win32. 4. В этой директории находится пакет файлов, которые создают всевозможные библиотеки Ogg и Vorbis. Если вы хотите, вы можете запустить каждый файл, но сейчас вам нужно запустить build_vorbis_static.bat, build__vorbis_static_debug.bat, build_vorbisfile_static.bat, и build_vorbisfile_static_debug.bat. Этот пакет файлов создаст vorbis_static.lib, vorbis_static_d.lib, vorbisfile_static.lib, и vorbisflle_static_d.lib, соответственно. Обратите внимание, что у вас будут предупреждения, когда вы будете компилировать.
144
Глава 8
Вот и все, что нужно сделать! Я в точности следовал этому процессу, когда писал Ch8pl_OggVorbisPlayback. В директории кода источника главы 8 вы найдете папку под названием libvorbis-1.0, которая содержит построенные библиотеки. Теперь нужно настроить ваш проект DevStudio, чтобы он использовал библиотеки Ogg Vorbis. Если вы хотите добавить Ogg Vorbis в проект, вам нужно сделать следующее. 1. Включить путь к Ogg Vorbis include в ваш проект. Чтобы сделать это, выделите Settings в меню Project и откройте C/C++ Tab. Выберите Preprocessor из ниспадающего меню Category. В меню Additional Include Directories наберите относительный путь из вашего проекта в папку include, где вы установили Ogg Vorbis. Например, для примера программы этой главы я должен набрать ..VAlibvorbisl.OMnclude. 2. Вам нужно соединиться с библиотеками Ogg Vorbis. Чтобы это сделать, в диалоге настроек пройдите по ярлыку Link и выберите категорию Input. В меню под названием Object/library modules добавьте те библиотеки Ogg Vorbis, которые вам нужны. Я добавил vorbisfilejstatic. lib, ogg_static.lib, Hvorbis_static.lib. для примера программы этой главы. 3.
Вам нужно добавить директорию библиотек Ogg Vorbis в библиотечный путь. В том же самом месте, где находятся пара меню под Object/library modules, есть меню под названием Additional Library Path. Добавьте относительный путь к вашей директории библиотек Ogg Vorbis. Для примера программы Ch8pl_OggVorbisPlayback этой главы это ..\.Alibvorbis-1.0\lib. Отделяйте пути запятыми.
4. Убедитесь в том, что вы включили в код правильные заголовки Ogg Vorbis и что вы используете в их пути vorbis/. Например, если вы хотели включить vorbisfile.h, вам нужно было написать #include . Препроцессор добавляет это к тому относительному пути, который вы добавили в пункте 1, поэтому получится что-то вроде ..\..\libvorbis-1.0\include\vorbis\vorbisfile.h.
Использование Vorbis ЕСЛИ вы это читаете, значит, я могу предположить, что вы счастливо закончили самую неприятную часть работы с третьей частью API - установкой и конфигурированием библиотек. Теперь пришло время посмотреть на код Ogg Vorbis.
Воспроизведение Одд
145
Использование Vorbisfile API Vorbisfile API - это самый простой из имеющихся Ogg Vorbis API. Он позволяет вам легко читать файлы Ogg Vorbis и превращать их в несжатый поток РСМ. Рис. 8.1 показывает, как использовать этот API. Есть и другие API (посмотрите документацию Ogg Vorbis), но в этой главе мы будем использовать только Vorbisfile API.
ov_open
ov_read
Кладем данные в буфер
^<Х"В1}звра ^\^ ov_rea
>t Нет Рис. 8.1. Блок-схема использования Vorbisfile API
Процесс работы этого API напоминает работу по считыванию обычного файла. Чтобы работать с нормальным файлом, вы открываете его, потом читаете из него, а потом закрываете, когда он вам больше не нужен. Аналогично, прежде чем вы сможете что-то сделать с файлом Ogg Vorbis, вам нужно его открыть с помощью API вызова ov_open. Этот вызов в качестве входных данных берет дескриптор файла, который вы перед этим открыли с помощью fopen, и адрес структуры OggVorbis_File, которую он должен заполнить. После того как вы откроете файл Ogg Vorbis, вы можете совершать с ним несколько различных вещей. Вы можете получить какую-то информацию о нем с помощью вызова функции API ov__info. Этот вызов в качестве выходных данных использует указатель на структуру OggVorbis_File (такой же, какой возвращается ov_open) и адрес структуры vorbis_info, которую нужно заполнить. Структура vorbis_info, объявленная в vorbis/codec.h, содержит версию файла, количество кагалов, частоту выборки и, возможно, еще скорость передачи битов (в зависимости от того, изменяется ли она). Также полезны функции ov_pcm_total, которая, если может, выдает вам, соответственно, полное количество выборок, и ov_comment, которая выдает вам пользовательские комментарии к файлу (исполнитель, альбом, название и т. д.). Тем не менее, самая нужная функция API - это ov_read. Она разворачивает файл в поток РСМ. Эта функция в качестве входных данных берет указатель на структуру OggVorbis_File, буфер байтов, длину его (вместе с некоторыми сообщают, хотите ли вы прямой или обратный порядок байтов в результате), размер слова вашей платформы и информацию, хотите ли вы данные со знаком или без знака.
146
Глава 8
Последний параметр ov_read - это указатель на целое, означающее текущий логический поток битов. Для простого декодирования, которое вы тут хотите сделать, просто установите его в ноль и передайте адрес этого целого всем вызовам ov_read. Это освободит вас от необходимости беспокоиться о логических потоках битов; тем не менее, если вы любопытны, посмотрите документацию Ogg Vorbis. Функция ov_read не обязательно полностью заполняет байтовый буфер, который вы ей передаете. Таким образом, важно проверить возвращенное значение, которое показывает, сколько байт вам возвращается. Если это ноль, вы дошли до конца потока Ogg Vorbis. После того как вы закончили читать файл, вы закрываете его посредством вызова ov_clear API. Обратите внимание, что этот вызов автоматически закроет и указатель FILE, так что вам самим не придется вызывать fclose.
Добавление Ogg Vorbis в aBykoBou gBiukok Теперь, когда библиотеки установлены, а вы знаете, как работает API, вы можете начинать писать остальной код. В этом разделе вы научитесь, как загрузить файл Ogg Vorbis в сегмент DirectMusic. Это кажется трудным, но не является таковым. Все, что нужно сделать, - это распаковать файл Ogg Vorbis в поток РСМ (т. е. несжатый поток), положить его в заголовок wave файла и передать его объекту Loader DirectMusic для загрузки в сегмент. У вас есть все кусочки мозаики. Один из примеров программ Ogg Vorbis (vorbisfile_example.c) показывает, как использовать библиотеку vorbisfile для перевода файла Ogg Vorbis в поток РСМ, а благодаря главе 4 «Загрузка WAV файлов» вы уже написали класс CWAVFile, который может создавать WAV файл из чанка несжатых данных. Все просто замечательно! Я добавил новый метод в CAudioManager под названием LoadOggVorbis. Вот код файла источника. CSoundPtr CAudioManager::LoadOggVorbis(std::string filename) { std::vector pcmdata; // этот код основан на vorbisfile_example.с, // включенный в ogg распространение. char pcrnout [4096] ; // буфер декодирования ogg OggVorbis_File vf; int eof=0; int current_section; FILE *f = fopen(filenarae.c_str() , "rb"}; if (f == MULL) { Throw("could not open file."); }
Воспроизведение Одд Vorbis
147
if (ov_0pen(f, Svf, NULL, 0) < 0) { Throw ("Input does not appear to be an Ogg bitstream. ") ; ) /* Передаем комментарии плюс еще пара слов о битовом потоке, который мы декодируем */ vorbis_info *vi=ov__info (Svf , -1) ; // приведем размер pcmdata vector к размеру файла Ogg // два байга на канал на выборку int samples = ov_pcm_total (Svf , -1) ; int channels = vi->channels; int samplerate = vi->rate; if (samples > 0) { pcmdata. reserve (channels* 2* samples) ; } while ( !eof ) ( long ret=ov_read (Svf , pcmout, sizeof (pcmout) ,0,2,1, ¤t_section} ; if (ret == 0) { /* EOF */ eof-1; } else if (ret < 0) { /* ошибка Б потоке. Это не проблема, просто сообщаем об этом в случае/ если мы (приложение) об этом заботится. В этом случае нет. */ } else { /* мы не заботились об изменении частоты выборки и так далее, но вам придется */ // ladailflei a'aiiua eg аббаба Ogg a ia0. for (int q=0; q < ret; q++} { pcmdata. push_back (pcmout [q] ) ;
/* чистка */ ov_clear (Svf } ; // теперь, когда у нас в памяти есть РСМ данные, превращаем их в WAV файл // и передаем DirectMusic для загрузки Б сегмент. CWAVFile wavfile; wavfile.m_AudioFormat = WAVE_FORMAT_PCM; wavf ile.m_BitsPerSample = 16; // 16 бит / 8 бит на байт * каналы wavf ile.m_BlockAlign = 2*channels; wavf ile.m_ByteRate = samplerate * channels *2 ; wavfile . m_NumberOf Channels = channels ; wavf ile.m_SampleRate = samplerate; wavfile. Set Data (pcmdata. begin ( ) , pcmdata. size ( ) ) ; return (LoadSound (wavfile) ) ;
148
Глава 8
Большая часть этого кода -- это просто скопированные куски из файла vorbisfile_example.c. Vorbisfile_exaniple.c считывает поток Ogg Vorbis из stdin (стандартный поток ввода) и записывает несжатый поток РСМ в stdout (стандартный поток вывода). Я модифицировал его таким образом, чтобы он считывал поток Ogg Vorbis из указателя FILE и записывал его в вектор unsigned char. Обратите внимание, что этот код знает, что выходящая волна должна быть 16-битной, потому что он определил 16-битный размер слова для функции ov_read, передавая 2 в качестве пятого параметра. Если вы хотите, чтобы волна была 8-битной, вы должны передать вместо этого 1. Так как поток РСМ это вектор pcmdata, код передает его экземпляру CWAVFile (wavfile) вместе с деталями его формата (количеством каналов, частотой выборки и т. д.). Как только wavfile собран, код вызывает перегрузку LoadSound (о ней вы узнали в четвертой главе), читающую класс wave файла и создающую для него сегмент DirectMusic.
Заключение В этой главе вы узнали, как использовать открытый кодек Ogg Vorbis вместо МРЗ или WMA. Конечно, об Ogg и Vorbis написано гораздо больше, чем просто, как их декодировать. Я предлагаю вам поэкспериментировать с другими библиотеками Vorbis и посмотреть на технические .спецификации, посредством которых Vorbis совершает свою магию - свое восхитительное чтение. На этом кончается рассмотрение сжатых файловых форматов. В следующей главе вы узнаете, как считывать и воспроизводить музыкальный формат трекерной музыки, основу демо-сцены, как вы увидите, тоже весьма полезную в игровом программировании.
Глава 9
Воспроизведение аудио koMnakm-guckoB ЕСЛИ вы хотите использовать в качестве носителя своих игр компакт-диски, то мои поздравления - у вас самый простой и в то же время универсальный подход к внедрению игровой музыки. За исключением случаев сжатия данных на компактдиске (и невозможности выделения места под несжатый звук) или при необходимости иметь музыку, которая меняется в процессе воспроизведения, вы, конечно же, захотите использовать аудио компакт-диск в качестве музыкального сопровождения к вашей игре. Эта глава будет сосредоточена на создании класса CCDPlayer. Вот объявление, взятое из класса CDPIayer.h в программной папке Ch9pl_CDPlayer. class CCDPlayer { public: CCDPlayer(); virtual -CCDPlayer{); void I n i t f H W N D hwnd, char driveletter = 0 ) ; void U n l n i t ( ) ; void Play(int track); bool IsPlaying(); bool IsPaused(); void StopO ; void Pause (); void Resume(); void OpenCDDoor(); void CloseCDDoor(); bool IsCDlnDriveO ; CTrackMinSecFrame GetCurrentPos(); int GetTrackLength(int track); int GetNumTracks{); static std::vector GetAvailableDevices();
J5O
Глава 9 protected: bool m_InitGood; bool m_Paused; CTrackMinSecFrame m_PausedPos; static void HandleError(MpIERROR err); HWND ra_hWnd; DWORD m^DevicelD; }}
Как вы можете видеть, это проигрыватель компакт дисков без всяких излишеств нет методов для поиска по треку или исследования специальных возможностей CDROM. Содержимое класса очень просто - все, что требуется в игре, и ничего больше. Обратите внимание на то, что класс основан на паре других классов: CTrackMinSecFrame и CCDDevicelnfo. Оба они - это просто собрания открытых переменных. CTrackMinSecFrame содержит четыре целых переменных: m_Track, m_Minute, m_Second и m_Frame, - которые вместе определяют временное положение на компакт-диске. CCDDevicelnfo содержит несколько строк, которые описывают данный CD-ROM. Здесь лежат имя дисковода (ni_DriveLetter), потом рядом короткое описание CD-ROM (m_Description), UPC код (m_CDProductCode) и CD ID (m_CDID) компакт-диска в CD-ROM, если он там есть. Все эти лакомые кусочки данных получаются с помощью Windows, и вы узнаете о них больше в следующих разделах. Теперь мы узнаем, как провести это объявление класса с использованием MCI.
Использование MCI для аудио koMnakm-guckoB Вы уже знаете Windows MCI, мы знакомились с ним в главе 3. Там я уже говорил вам, что MCI означает Media Control Interface (Интерфейс управления медиа) и что этот интерфейс был предназначен для устройств мультимедиа, вроде проигрывателей компакт-дисков. MCI - это самый простой способ использования компакт-дисков. Есть способы обращаться с ними на низшем уровне, но, если вы не пишете программу для декодирования аудио, вам это не понадобится. Рис. 9.1 показывает процесс использования MCI: вы видите, какие устройства свободны, выбираете один и открываете его, совершаете нужные действия и закрываете его, когда закончите. Вы можете установить взаимодействие посредством API функций, или вы можете сформировать текстовую строку, содержащую команды и послать ее. Например, строка команды «play thissound from 0 to 2000 notify» (проиграть этот звук от 0 до 2000 уведомить) заставляет систему воспроизвести «этот звук» от начала до позиции 2000 и сообщить, когда это будет проделано. Если вы забудете синтаксис командных строк, просто притворитесь армейским сержантом, командующим проигрывателем компакт-дисков.
Воспроизведение аудио kownakm-guckoB
151
Библиотека MCI - это мощный инструмент, позволяющий делать практически все, что большинство игр потребуют от проигрывателя компакт-дисков. Если вам интересна другая информация об API в целом, посмотрите документацию MSDN, в Platform SDKXMultimedia AudioVMCI. MCI - это API с двойственным интерфейсом: вы можете использовать текстовые команды свободного формата (интерфейс командной строки) или набор ID сообщений и структур (интерфейс командных сообщений), чтобы взаимодействовать с ним. Я предпочел не использовать первое и везде действую с помощью интерфейса сообщений. Главный мозг интерфейса сообщений - это вызов mciSendCommand API. Эта функция берет ID командного сообщения (например, MCI_PLAY) вместе со структурой для этого сообщения (например, MCI_PLAY_PARMS). Сначала это немного устрашающе, потому что вам нужно передавать адреса ваших структур DWORD так же, как вы передаете их функции. MCI_PLAY_PARMS mciPlayParms; ZeroMemory(fimciPlayParms, sizeof(MCI_PLAY_PARMS)); mciPlayParms.dwFrom = MCI_MAKE__TMSF(bTrack, 0, 0, 0) ; mciPlayParms.dwTo = MCI_MAKE_TMSF(bTrack + I, 0, 0, 0); mciPlayParms.dwCallback = (DWORD) hWndNotify; MCIERROR e = mciSendCommand(wDevicelD, MCI_PLAY, MCI__FROM I MCI_TO I MCI_NOT1FY, (DWORD) (LPVOID) imciPlayParms);
Вот типичный кусок кода, взятый из метода Play класса проигрывателя компактдисков. Как вы видите, код устанавливает различные элементы структуры MCIJPLAY_PARMS, которая требуется команде MCI_PLAY. Код определяет диапазон проигрывания посредством элементов dwFrom и dwTo, добавляет указатель окна для обратного вызова и затем вызывает mciSendCommand, чтобы сделать команду. Вторым параметром mciSendCommand является ID сообщения, а третьим - набор флагов для сообщения. Пока не беспокойтесь о деталях - я просто хочу, чтобы вы ознакомились с этой схемой, потому что вы будете ее встречать не раз в классе проигрывателя компактдисков. Если mciSendCommand достигла цели, то возвращается ноль, а в другом случае возвращается код ошибки MCI, который вы можете декодировать в текстовую строку с помощью функции mciGetErrorString.
Глава 9
152
Запрашиваем доступные устройства CD аудио Вектор устройства классов информации
Используем 10 компакт-дисков и\или другую информацию (например, настройки системного регистра), чтобы определить, каши дисковод открыть
Имя дисковода
Закрываем устройство, когда закончим работу с ним
Открываем устройство
ID устройства ID устройства
Программа уведомляет нас о выполнении определенных команд (например,, MCI_PLAY) чтобы мы могли предпринять какие-то действия (например, подать сигнал следующему треку}
V Сообщение MM_MCINOTIFY Windows
Посылаем команды устройству посредством mciSendCommand MCLPLAY, МС1_5ТОРит.д.
Рис. 9.1. Процесс для использования Media Control Interface (Интерфейс управления медиа - MCI)
Воспроизведение аудио koinnakm-guckoB
153
Нумерация, omkpbimue и заКрытие приборов В первую очередь наша задача выяснить, какой аппаратурой обладает наша игра. Когда-то это было просто, потому что у большинства компьютеров либо не было дисководов вообще, либо был один. Благодаря распространению CD-RW-приводов и USB все стало сложнее: теперь пользователь теоретически может иметь дюжины периферийных устройств, которые могут проигрывать компакт-диски. Ваш диск с игрой это - один из них; как вы узнаете, какой именно? На этой стадии мне нужно предупредить вас, что иногда то, как вам требуется делать какие-то вещи, будет является сложным без всякой на то необходимости. Например, если бы вам нужно было начать просматривать документацию через MSDN на MCI API, вы бы быстро обнаружили, что самым лучшим способом пронумеровать устройства было бы разослать команду MCI_SYSINFO с несколькими флагами один раз, чтобы получить число устройств MCI, а затем еще несколько раз (с различными флагами), чтобы достигнуть каждого конкретного устройства. Но, если вы не делаете чего-то, что гораздо больше и шире простого воспроизведения музыки, вам это, вероятно, и не понадобится. Вместо этого, вы сможете использовать более простой метод. Windows дает каждому устройству, способному воспринимать компакт-диски (проигрывателям CD и DVD, CD-RW-приводу и т. д.), букву в качестве названия дисковода между D и Z (помните, что А и В используются обычно для флоппи-дисководов, а С - для жесткого диска). Поэтому все, что вам нужно сделать, это пройти по всем буквам от D до 2 и посмотреть, присоединено ли там устройство аудио компакт-дисков. Если да, то вы можете установить связь с этим дисководом, чтобы выяснить, какой компакт-диск там находится (если там что-то есть). Это проще, чем устраивать должным образом нумерацию MCI, и вы, таким образом, узнаете имя дисковода, где находится ваш диск с игрой (в случае, если вы захотите воспроизводить с него какие-то видеоролики или что-то в этом роде). Вот метод CCDPlayer::GetAvailableDevices. Этот метод просматривает все устройства системы и возвращает вектор информационных классов устройств -для каждого обнаруженного CD-ROM по информационному классу. vector CCDPlayer::GetAvailableDevices() {
DWORD retvalue=0; MCIERROR e; vector devices; // проведем цикл через все имена дисководов for (char drive = 'С'; drive <= 'Z1; drive++) ( char buf[256]; char driveletter[3] = "C:";
154
Глава 9 II смотрим, можем ли мы использовать этот дисковод в качестве устройства воспроизведения компакт-диска MCI_OPEN_PARMS openparms; ZeroMemory(Sopenparms, sizeof(MCI_OPEH_PARMS)); openparms.IpstrDeviceType = (LPCSTR) MCI_DEVTYPE_CD_AUDIQ; driveletter[0] = drive; openparms.IpstrElementName = driveletter; e = mciSendCoimand(NULL, MCI_OPEN, MCI_OPEN_TYPE I MCI_OPENJTYPE_ID | MCI__OPEN_ELEMENT, (DWORD)(LPVOID)bopenparms); if (le) ( // удача! Запрашиваем этот дисковод CD Audio и добавляем его в // наш вектор. CCDDevicelnfo devinfo; devirifo.m_DriveLetter = drive; int deviceid = openparms.wDevicelD; // из этого листинга удалены для экономии места: // запрос устройства о его описании // запрос устройства о коде продукта компакт-диска, который в нем находится // запрос устройства об ID компакт-диска, который в нем находится // закрываем устройство MCI_GENERIC_PARMS genericparms; ZeroMemory(sgenericparms, sizeof(MCI_GENERIC_PARMS)]; mciSendCommand(deviceid, MCI_CLOSE, MCI_WAIT, (DWORD)(LPVOID)Sgenericparms); // дойавляем в вектор devices.push_back(devinfo} ; 1 } // следующее имя дисковода
return(devices); }
Я выкинул скучные повторяющиеся части функции GetAvailableDevices, чтобы вам было проще увидеть общую структуру. В сущности, код пробегает по всему алфавиту, пытаясь открыть каждый дисковод в качестве дисковода аудио компакт-дисков. Ключевой значимостью обладает MCI_OPEN_ELEMENT, который запускает MCI, чтобы тот посмотрел на элемент IpstrElementName структуры MC1_OPEN_PARMS. В этом контексте, IpstrElementName - это фактически имя дисковода для устройства. Совет. Универсальный код изделия (UPC) компакт-диска вырезан протводитепем на компакт-диске, и при этом гарантируется его уникальность во всей вселенной. Это что-то наподобие штрих-кода на ваших любимых продуктах в бакалейном магазине. Например, вы можете использовать CD UPC, чтобы поискать в Интернете CDDB (база данных компакт-диска), чтобы узнать название, исполнителя и названия треков компакт-диска. Тем не менее, опасайтесь - некоторые старые компакт-диски не имеют кода изделия.
ВоспроизВедение аудио koMnakm-guckoG
155
В отличие от кода изделия компакт-диска, код ID компакт-диска - это самодельный идентификатор, вычисляемый Windows it основанный на длинах треков и других характеристиках компакт-диска. Windows может генерировать ID для любого компактдиска, но возможно (хоть и маловероятно), что два различных компакт-диска будут обладать одним и тем же ID.
Если данное имя дисковода соответствует устройству CD аудио, код возвратит код успеха (ноль) от сообщения MC1_OPEN, посланного вместе с mciSendCommand. Это говорит коду, что это имя дисковода соответствует устройству, способному воспроизводить аудио компакт-диск. Затем код посылает несколько сообщений MCI_INFO, запрашивающих у дисковода его описание, код изделия, ID вставленного компакт-диска (если он есть). Я не стал вставлять в предыдущий листинг этот код, но он ждет нас впереди. После того как код закончит запрашивание устройства, он закрывает его. посылая команду MCI_CLOSE. Обратите внимание на флаг MCI_WAIT. MCI команды по умолчанию не синхронны, определение MCI_WA1T заставляет MCI не возвращаться до тех пор, пока устройство не обработает сообщение. Код делает это безопасным, так как ожидает команды закрытия, чтобы завершить перед добавлением информационного класса устройства в вектор, и перейдем к следующему имени дисковода. Статическая функция возвращает вектор классов CCDDevicelnfo вызывавшей функции. После того как она получает список работающих имен дисководов, она выбирает одно из них и вызывает метод Init CCDPlayer. Я не включил код источника Init, потому что он будет впереди. Init открывает устройство, соответствующее полученному имени дисковода, и кладет ID устройства в m_DeviceID. Этот ID устройства - это что-то вроде дескриптора файла; код получает его в соответствии с сообщением об открытии и использует для определения цели более интересных сообщений, таких, как MCI_PLAY. Вот как можно заставить крутиться нужный диск в системе с множеством дисководов для аудио компакт-дисков. После того как устройство открыто, метод Init посылает сообщение MC1_SET,, чтобы установить формат времени для того, что называется TMSF, сокращение от «трек, минута, секунда, фрейм» - четырех идентификаторов, которые вы все вместе используете, чтобы точно указать положение на аудио компакт-диске. Есть другие форматы времени (например, простые старые миллисекунды), но TMSF - это самый лучший выбор для тех ситуаций, где вы, главным образом, будете проигрывать целые треки. Вы увидите, почему, когда будете учиться это делать.
156
Глава 9
В конце концов, метод Init устанавливает флаг m_lnitGood в true. Ага, вы уже догадались, это снова двухфазная конструкция. Этот флаг — это «печать удостоверения», что проигрыватель компакт-дисков готов; все остальные методы CCDPlayer проверяют этот флаг, прежде чем попытаются что-то делать. Когда вы закончите использовать класс проигрывателя компакт-дисков, вызовите метод Unlnit, который закроет устройство, послав сообщение MCI_CLOSE.
форматы Времени MCI Теперь отойдем немного в сторону и поговорим о форматах времени компакт-дисков. Я написал вспомогательный класс под названием CTrackMinSecFrame, который содержит целые для трека, минуты, секунды и фрейма. Как бы там ни было, функции MCI укладывают все эти четыре знамения в один DWORD, по одному байту для каждого значения. MCI поддерживает макрос для упаковки и распаковки TMSF DWORD; вы можете использовать этот макрос для создания методов, которые преобразовывают из классов CTrackMinSecFrame в DWORD. class CTrackMinSecFrame { public: // кусок CTrackMinSecFrame ^operator-(DWORD dw) { mJTrack = MCI_TMSF_TRACK(dw}; m_Minute = MCI_TMSF_MINUTE(dw); m_Second = MCI_TMSF_SECOND (ciw) ; m_Frame = MCI_TMSF_FRAME(dw}; return(*this); } operator DWORD() { return(MCI_MAKE_TMSF(m_Track, m_Minute, m_Second, m_Frame)); } );
Здесь есть два искусных оператора перегрузок. Первый из них, operator=, использует четыре макроса MCI для получения конкретных байт трека, минуты, секунды и фрейма и помещения их в их новые целые «дома». Второй метод, оператор DWORD, использует MCI_MAKE_TMSF, чтобы снова упаковать их обратно. Эти операторы очень хороши тем, что вместе они позволяют нам относиться к CTrackMinSecFrame так, как если бы это был DWORD, в любом случае! Урок С++ здесь - это то, что, когда операторы перегрузки используются для автоматизирования стандартных переходов, они могут значительно упростить жизнь.
Воспроизведение аудио kownakm-guckoB
157
Воспроизведение mpeka Что довольно неудобно -это то, что вы не можете сказать проигрывателю компакт-дисков: «Играй такой-то трек». Вы должны указать начальный и конечный моменты. А вот что удобно - это то, что, благодаря использованию формата TMSF, вам будет легко это сделать. void CCDPlayer::Play(int track) { if (!m_InitGood) return; m_Paused = false; MCIERROR e; MCI_PLAY_PARMS mciPlayParms; mciPlayParms .dwFrorn = OL; mciPlayParms.dwTo = OL; mciPlayParms.dwFrom = MCI_MAKEJTMSF(track, 0, 0, 0); mciPlayParms.dwTo = MCI_MAKE_TMSF(track + 1, 0, 0, 0); mciPlayParms.dwCallback = (DWORD)m_hWnd; e = mciSendCommand(m_DeviceID, MCI_PLAY, MCI_FROM | MCIJTO | MCI_NOTIFY, (DWORD)(LPVOID) SmciPlayParms); if (e) HandleError(e); '
Благодаря использованию TMSF, код получает возможность легко определять диапазон воспроизведения - с начала того трека, который нам нужен (номер трека, О, 0, 0), до (но не включая) начала следующего трека (номер +1, О, О, О). TMSF здесь очень удобен, если бы не он, вам пришлось бы высчитывать диапазон, необходим ый вам, в миллисекундах. Также в предыдущем коде очень важен флаг MCI_NOTIFY. Вы узнаете о нем в разделе «Синхронное или асинхронное выполнение команд» через несколько страниц.
OcmanoBka Чтобы остановить воспроизведение CD, пошлите сообщение MC[_STOP. void CCDPlayer::Stop() { m_Paused = false; if { !m__lnitGood) return; MCI__GENERIC_PARMS genericparrns ; ZeroMemory(Sgenericparms, sizeof{MCI_GENERIC_FARMS}); mciSendCornmand(ra_DeviceID, MCI_STOP, MCI_WAIT, (DWORD)(LPVOID)&genericparms);
158
Глава 9
Сообщения, которые берут только общие параметры, самые простые. Здесь код посылает команду MCI_STOP и поставляет структуру общих параметров. Обратите внимание на флаг MC1_WAIT, который удостоверяет, что проигрыватель компакт-дисков остановлен в то время, которое возвращает метод Stop.
Приостановка и Возобновление работы Обратите внимание, что первое же, что делает метод Play, - это выставляет в false флаг iri_Paused. Возможно, вас это удивит, если вы узнаете, что MCI поддерживает паузу вовсе не для всех проигрывателей компакт-дисков. Чтобы сделать хороший класс проигрывате.тя компакт-дисков, вам придется сделать приостановку и возобновление воспроизведения самому. К счастью, это не сложно: чтобы сделать паузу в воспроизведении, вам нужно всего лишь запросить текущую позицию воспроизведения, сохранить ее и послать команду остановки. Чтобы продолжить воспроизведение, нужно просто начать играть от того места, где вы остановились, до конца. Добавьте флаг, чтобы помнить, остановлен ли в данный момент проигрыватель или нет, и все будет настроено. Выполнение этой формы «псевдо остановки» требует пары дополнительных переменных экземпляра. Во-первых, это n\_Paused, логический флаг. Во-вторых, это m_PausedPos, CTrackMinSecFrame, который сохраняет конкретный момент, когда было остановлено воспроизведение CD. Вот как выглядит код. void CCDPlayer::Pause(} I if (!m_InitGood) return; m_PausedPos = GetCurrentPos(); Stop () ; m Paused == true; } void CCDPlayer::Resume() { if (!m_InitGood) return; if (!ra_Paused) return; // играем от места остановки до конца трека MCIERROR е; MCI_PLAY_PARMS mciPlayParms; mciPlayFarms.dwFrom = OL; mciPlayParms.dwTo = OL; mciPlayParrns .dwFrom == m_PausedPos; mciPlayParms. dwTo = MCI_MAKE_TMSF(m_PausedPos.ra__Track + 1, 0, 0, 0); mciPlayParms. dwCallback = (DWORD) m__hWn<3; e = mciSendCommand(m_DeviceID, MCI__PLAY, MCI FROM | MCI TO | MCI NOTIFY,
Воспроизведение аудио koMnakm-guckoB
159
(DWORD)(LPVOID) &mciPlayParms); if (e) HandleError(e); m Paused = false; }
Метод Pause очень прост (вы узнаете о GetCurrentPos в следующем разделе), и единственная сложная вещь в методе Resume - это диапазон воспроизведения. Код устанавливает структуру MCI_PLAY_PARMS таким образом, что проигрыватель начинает с того места, где он закончил (m__PausedPos), и играет до начала следующего трека (m^PausedPos.mJTrack+l, О, О, О), В конце он устанавливает флаг m_Paused в false, завершая иллюзию паузы. Вы увидите пару других методов, которые устанавливают «остановленный» флаг в false. Например, метод, который выдвигает компакт-диск, также выставляет m_Paused в false. Совет. Метод Ploy проигрывателя компакт-дисков не такой уж и разумный. Play не проверяет флаг m_Paused; он безоговорочно начинает воспроизводить с начала трека. Класс проигрывателя компакт-дисков полагается на другой код, чтобы следить за состоянием паузы и в соответствии с ним вызывать Play или Resume.
Определение позиции ВоспроизВедения Чтобы спросить у проигрывателя компакт-дисков, что сейчас играет, пошлите сообщение MCI_STATUS. MCI_STATUS позволяет вам сделать целый ряд необходимых запросов о состоянии различных объектов - посмотрите на все флаги в MSDN, но, чтобы определить текущую позицию воспроизведения, вы должны передать MC1_STATUS__PARMS в MCI_STATUS__POSITION, как dwltem в структуре параметров состояния. Вот код: CTrackMinSecFrame CCDPlayer::GetCurrentPosf) { CTrackMinSecFrame pos; if (!m InitGood) return(pos); if (m_P~aused) return(m_PausedPos) ; if (!IsPlaying()) return(pos); MCI_STATUS_PARMS statusparms; ZeroMemoryUstatusparms, sizeof(MCI_STATUS_PARM3)); statusparms.dwltern = MCI_STATUS_POSITION; MCIERROR e = mciSendCommand(m_DeviceID,MCI_STATUS,MCI_STATUS_ITEM I MCI_WAIT, (DWORD)(LPVOID)bstatusparms); if (e) HandleError(e); pos = statusparms.dwReturn; return(pos);
160
Глава 9
Иногда трудно определить, где должен находиться флаг MCI. Я решил, что в случае сообщений о состоянии самым прямым путем будет определить флаг MCI_STATUSJTEM, который заставляет MCI проверить элемент dwltem структуры MCI_STATUS_PARMS, чтобы определить, какой пункт состояния вы хотите. Затем MCI кладет запрошенную вами информацию в элемент dwReturn, которую проверяет предыдущий код, преобразовывает в CTrackMinSecFrame (это ловкая перегрузка оператора присваивания) и возвращает.
Выдвигание komnakm-gucka Как показывает рис. 9.2, написание кода, который заставит двигаться вещи, очень искусно, поэтому написание кода, который заставит проигрыватель компакт-дисков «высунуть язык», очень захватывающе. Конечно, может быть, только в крайней необходимости, но, тем не-менее, код для выдвигания диска может быть полезным. Вот как он выглядит. void CCDPlayer::OpenCDDoor() { if (!m_InitGood) return; m_Paused == false; mciSendCornmand(m_DeviceID r MCI_SET,MCI_SET__DOOR_OPEN,0) ; }
Проще сделать уже нельзя. Просто пошлите сообщение MCI_SET, определите флаг MCI_SET_DOOR_OPEN, и все готово - не нужно передавать структуру. Задвигание обратно так же просто. • void CCDPlayer::CloseCDDoor() { if (!m_InitGood) return; mciSendCommand(m_DeviceID,MCI_SET,MCI_SET_DOOR_CLOSED,0); }
В этом коде флаг MCI__SET_DOOR_CLOSED заставляет устройство CD аудио «спрятать» дисковод. Если он даже не выдвинут, никакого вреда не будет.
ДетеКтороБание смены koronakm-gucka Аккуратная смена компакт-дисков состоит из двух частей. В первую очередь, мы запрашиваем устройство, лежит ли в нем компакт-диск. Вот код. bool CCDPlayer::IsCDInDrive() if {!m InitGood) return(false);
Воспроизведение аудио koMnakm-guckoB
161
Эффект от MCI_SET_DOOR_OPEN на то, как выглядит проигрыватель компакт-дисков До получения MCLSETJDOOR.OPEN
После получения MCI_SET_DOOR_OPEN
Рис. 9.2. Эффект от команды MCI_SET_DOOR_OPEN MCI_STATUS__PARMS statusparms; ZeroMemory(sstatusparms, sizeof(MCI_STATUS_PARMS)); statusparms.dwltem = MCI_STATUS_MEDIA_PRESENT; MCIERROR e = mciSendCommand {m_DeviceID, MCI_STATUS, MCI__STATUS_ITEM I
MCI_WAIT, (DWORD)(LPVOID)Sstatusparms); if (e) HandleError(e) ; return (statusparms .dwReturn == TRUE);
Очень простой код - он посылает сообщение MCI_STATUS, запрашивающее MCI_STATUS_MEDIA_PRESENT, и вы получаете TRUE, если в дисководе лежит компакт-диск. Следующая, немного более хитрая часть, - это осведомление о том, когда диск будет сменен. Вы же не хотите непрерывно запрашивать MCI_STATUS_MEDIA_PRESENT. Чтобы должным образом определить смену диска, вы должны ожидать сообщения WM_DEVTCECHANGE, посылаемого Windows. Windows пошлет это сообщение высокоуровневому HWND всех программ после того, как компакт-диск будет вынут или вставлен. Совет. С технической точки зрения, сообщение WM_DEVICECHANGE означает, что изменилась конфигурация аппаратуры, поэтому вы также получите это сообщение, когда начнете закрытие или открытие USB и устройства/irewire.
6-1490
162
Глава 9
Чтобы правильно интерпретировать это сообщение, вы должны проверить его wParam. Если диск был вынут, wParam будет содержать DBT_DEVICEREMOVECOMPLETE (т. е. «закончено перемещение устройства»). Когда будет вставлен новый компакт-диск, вы увидите DBT_DEVICEARR1VAL. Чтобы узнать больше, посмотрите документацию MSDN для WM_DEVICECHANGE.
Выяснение содержимого komnakm-gucka Вы уже достигли практически полного господства над компакт-диском. Остается одна вещь - как вы узнаете, что на этом компакт-диске находится? Конечно, вы уже знаете, как получить код изделия компакт-диска и сгенерированный ID, но как вы узнаете, сколько на диске треков? Вам дополнительные очки, если вы догадались, что нужно использовать сообщение MCI_STATUS. Вот как выглядит код. int CCDPlayer::GetNumTracks() <
if (!m_InitGood) return(false); MCI_STATUS_PARMS statusparms; ZeroMemory(ustatusparms, sizeof(MCI_STATUS_PARMS)); s t a t u s p a r m s . d w l t e m = MCI_STATUS_NUMBER_OF_TRACKS; MCIERROR e = mciSendCommand{m_DeviceID,MCI_STATUS,MCI_STATUS_ITEM I MCI_WAIT, {DWORD}[LPVOID)Sstatusparms); if ( e ) H a n d l e E r r o r ( e ) ; return(statusparms.dwReturn);
}
MCI обеспечивает специальный тип запроса о состоянии, MCI_STATUS_NUMBER_OFJTRACKS, который замечательно справляется с работой. Он кладет количество треков в элемент структуры MCI_STATUS_PARMS, которую вы ему даете. Совет. Существует гораздо больше команд состояния, чем те, которые я затронул здесь. Посмотрите документацию MSDN, если вы хотите узнать, как запросить длину каждого трека и его тип (данные или звук).
Синхронное или асинхронное выполнение Команд В прошлом разделе, когда я описывал метод Play, я упомянул флаг MCI__NOTIFY. Вы можете использовать этот флаг для любых сообщений MCI, и он очень существенен, потому что он сообщает MCI, что вы хотите об этом знать, если что-то из того, о чем вы запрашиваете, будет выполнено. Присоедините его к сообщению
Воспроизведение аудио koMnakm-guckoB
163
MCIJPLAY, и MCI сообщит вам, когда воспроизведение будет завершено, т. е. когда компакт-диск достигнет конца заданной программы воспроизведения. MCI уведомляет вас с помощью сообщений MM_MCINOTIFY, посылаемых в главное окно. Вы можете определить, что произошло, посмотрев на vvParam сообщения MM_MCINOTIFY, которое будет представлять собой или MCI_NOTIFY_ABORTED, или MCI_NOTIFY_FAILURE, или MCI_NOTIFY_SUCCESSFUL, или MCI_NOTIFY_ SUPERSEDED. LParam содержит ID устройства., которому вы послали сообщение. С помощью MMJVICINOiTFY вы можете играть треки в цикле неограниченно, играть треки вперемешку и делать многие другие замечательные вещи. Просто совершите соответствующее действие, когда вы получаете сообщение ММ_МС1NOTIFY с ID устройства вашего проигрывателя компакт-дисков в IParam и МС1_ NOTIFY_SUCCESSFUL в wParam. С другой стороны, если вы хотите команды выполняться синхронно - т. е. вы хотите, чтобы mciSendCommand ждало выполнения команды, чтобы возвратиться, вы можете определить флаг MCI_WAIT. MCI_WAIT позволяет вам сделать это безопасным и быть уверенным, что сообщение полностью обработано — это очень удобно, если вы посылаете много сообщений одно за другим. Посмотрите на рис. 9.3, чтобы сравнить синхронное и асинхронное выполнение команд, а если вам нужно больше информации, посмотрите MM_MCINOTIFY в MSDN.
Управление гролЖостью аудио kojvmakm-gucka Вы можете управлять громкостью проигрывателя компакт-дисков с помощью Mixer API, описанного в главе 5. Я полагал, что я упомяну это на случай, если вы упустили это тогда — просто обеспечьте правильные настройки линии (вы можете использовать встроенную константу CDAudio в CMixer), и установите ту громкость, которая вам нужна.
Образец программы CD Player Примером программы этой главы, как вы, наверное, догадались, является проигрыватель компакт-дисков. Используя класс CCDPIayer, это простое приложение Win32 GUI позволяет вам воспроизводить (и даже приостанавливать) ваши любимые компакт-диски. Вы можете также перескакивать между треками и выбирать различные дисководы, с которых воспроизводить. Посмотрите скриншот на рис. 9.4.
Глава 9
164 Синхронная передача информации Сообщение MCIJOAY посылается с использованием
Mcijiwr
Проверка возвращается вашей программе
ф Ф
Асинхронная передача информации
Проигрыватель компакт-дисков начинает воспроизведен на
Проигрыватель компакт-дисков заканчивает воспроизведение
Сообщение MCI_PIAY посылается с использованием MCLNQTIFY
Проигрыватель компакт-дисков начинает воспроизведение. Windows "ут- же возвращает
Ф
Проверка возвращается вашей программе
Проигрыватель компакт-дисков «грает
Ваша программа получает сообщение MCLNOTIFY
Windows посыпает сообшение MCLNOTIFY
Рис. 9.3. Синхронное или асинхронное выполнение команд СЬ9р1„Ш»1ауег
Рис. 9.4. Скриншот примера программы этой главы в действии
Большая часть кода в Ch9pl_CDPlayer.cpp выполнена в коде Win32 GUI. Я не использовал никаких фундаментальных классов-это просто чистый код Win32 API. Его достаточно сложно понять, поэтому я объясню некоторые самые интересные места его выполнения. Класс проигрывателя компакт-дисков сам по себе - это глобальная переменная, g_CDPlayer. Когда инициализируется основной диалог, он вызывает функцию PutAvailableDevicesInComboBox, которая, в свою очередь, вызывает статический метод CCDPlayer::GetAvailableDevices. Этот метод проходит через весь алфавит имен дисководов и возвращает массив информации об устройствах, который используется программой, чтобы заполнить комбинированной окно.
Воспроизведение аудио kownakm-guckoB
165
Класс проигрывателя компакт-дисков инициализируется в ответ на сообщение изменения выбора комбо-бокса1 (CBN_SELCHANGE). Функция OnComboBoxChange переинициализирует класс проигрывателя компакт-дисков, передавая выбранное пользователем имя дисковода. Каждый пункт комбо-бокса обладает 32 битами места - пример программы использует восемь из них, чтобы хранить тип char, означающий имя дисковода, поэтому все, что ему нужно сделать, - это получить эти восемь бит из того пункта комбо-бокса, который в настоящее время выбран, и затем передать это имя дисковода методу Init проигрывателя компакт-дисков. Есть таймер, который срабатывает каждую секунду, вызывая RefreshGUI. Эта функция совершает несколько действий. Во-первых, она или включает, или выключает все «кнопки» управления в зависимости от того, есть ли диск в выбранном дисководе. Во-вторых, она запрашивает у класса проигрывателя компактдисков позицию трека, которая отображается под ярлыком IDC_LCD. Она также устанавливает заголовок на кнопке пуска, «Play», «Pause» или «Resume» в зависимости от того, что подходит. Я включил заголовки и для сообщения WMJDEVICECHANGED и ММ__МСГМОTIFY. Устройство изменяет заголовок сообщения, просто вызывая RefreshGUI, и, таким образом, элементы управления включаются, когда вставляется диск, и отключаются, когда его вынимают. MM_MCINOTJFY переходит к следующему треку на компакт-диске. Вы свободно можете добавить к этому драйверу код для повторения или перемешивания треков. В заключение обратите внимание на то, что вся процедура диалога заключена в блоке try. Если класс проигрывателя компакт-дисков сталкивается с какой-нибудь ошибкой MCI, он вызывает свой защищенный метод HandleError, который возвращает строку. Процессор диалога берет эту строку и показывает ее в виде сообщения для пользователя. В реальной игре вы, конечно же, захотите сделать по-другому, но для простого проигрывателя компакт-дисков это замечательно. Поводя итог, если вы раньше не занимались программированием никаких простых Win32 API, изучение этой программы будет тем же самым, что питье из пожарного шланга. Вы наверняка захотите достать книгу об основах этого, такую, как, например, «Windows Programming for Dummies», или любую flpyiyto из книг по тематике Windows API.
1 Один из стандартных графических элементов управления (GUI) в Windows. Представляет собой выпадающий список элементов. - Примеч. науч. ред.
166
Глава 9
Заключение Здесь можно остановиться и с благодарностью подумать о том, как много работы делает за вас MCI. Вам не нужно беспокоиться о файловой системе компактдиска (которая ужасно сложна), вам не нужно передавать данные потоком и вам не нужно проверять ошибки. Все, что вы делаете, - это нажимаете виртуальную кнопку пуска, посылая сообщение MCI_PLAY. Конечно же, вы можете нырнуть глубже, и есть множество бесплатных программ и программ с открытым исходным кодом, с которых можно поучиться. В особенности, на SourceForge (http://www.sourceforge.net) есть некоторое количество полезного открытого кода для взаимодействия со службой CDDB, основанной на Интернете (чтобы находить название альбома, исполнителя и названия треков для конкретного компакт-диска). Там также есть открытый код для выделения с компакт-дисков цифрового звука с использованием низкоуровневых вызовов ASPI. Я включил на ваш компакт-диск несколько симпатичных ссылок. Этот класс проигрывателя компакт-дисков, тем не менее, делает все, что вам нужно, для вашей игры. Большинству игр необходимо просто иметь возможность воспроизводить определенный трек в определенное время и повторять его, и этот класс, находящийся наверху Media Control Interface, делает такие задачи простыми.
Глава 10
Воспроизведение mpekepuou музыКи Задолго до того, как появились МРЗ, Ogg Vorbis и Windows Media Audio, уже существовала трекерная музыка (рис. 10.1 и 10.2). Трекерная музыка засветилась в игровых радарах в начале девяностых и осталась очень популярным способом реализации музыки в играх, особенно для игр, распространяемых он-лайн. Это потому, что она очень хорошо совмещает качество музыки и объем, занимаемый ей на диске. Тогда как стандартный МРЗ файл занимает 3-4МГ6, трекерная музыка требует раз в 10 меньше, а звучит все равно отлично. В этой главе вы узнаете, как работает трекерная музыка и как включить ее в ваш звуковой движок, используя MikMod API, библиотеку GNU GPL, которая делает легкими загрузку и воспроизведение трекерной музыки.
Рис. 10.1. Скриншот Impulse Tracker, популярного редактора трекерной музыки, написанного Джеффри Лимом 1 На мой взгляд, в начале 90-х годов XX века трекерная музыка переживала свой пик популярности, а «засветилась»она гораздо раньше. - Примеч. науч. ред.
168
Глава 10
Рис. 10.2. Скриншот Scream Tracker, другого популярного редактора трекерной музыки, написанного Сэми Таммилето
Введение В треКерную музыКу В начале большинство видео игр использовали FM синтезированную музыку. Вспомните, что FM синтез генерировал звуки с помощь математических формул - вы говорите, что вам нужна синусоидальная волна, модулированная другой волной, с некоторыми математическими эффектами или помехами, добавленными для забавы. Такой способ позволяет получить огромную гамму звуков, но вот беда, все они будут звучать, как синтезатор. В течение нескольких лет было сделано немало отважных попыток представить звучание различных инструментов математически. Для некоторых инструментов (например, флейт) это работало замечательно, но для большинства инструментов результаты были гораздо менее эффектны, а для нескольких критических инструментов (духовых и ударных., в особенности) результаты были ужасны. Трекерная музыка - это, по существу, FM синтезированная музыка на стероидах. Вместо того чтобы использовать математические формулы для представления звучания инструментов, звуки самих инструментов цифруются и сохраняются в мелодии, а центральный процессор использует математику, чтобы повысить или понизить высоту выборки для соответствующей ноты. Теперь инструменты звучат достаточно похоже на оригиналы. Качество звука для любого инструмента все еще несовершенно, но уже достаточно хорошо, и, когда вы комбинируете несколько голосов этих инструментов, у вас может получиться достаточно неплохая песня. Трекерная музыка делает то же самое, что делает DirectMusic, только на более элементарном уровне. Синтезатор DirectMusic позволяет вам использовать ваши собственные наборы выборок (называющиеся бэндами) для инструментов и преду-
Воспроизведение mpekepnou музыйи
169
сматривает больше возможностей использования выборок (например, у вас может быть несколько выборок для одного инструмента, каждый для представления ноты, играемой на определенной высоте или интенсивности).
Использование MikMod В mpekepuou музыКе В этой главе вы узнаете, как добавить возможность воспроизведения трекерной музыки к звуковому движку. Тем не менее, прежде чем вы доберетесь до кода, вы сначала должны узнать, как установить MikMod.
Установка MikMod Вот как запустить работу MikMod в вашей системе. Обратите внимание, что я уже проделал все эти шаги для примера программы этой главы; они понадобятся только, когда вы будете работать над вашим собственным кодом. 1. В папке Goodies на вашем компакт-диске находится папка MikMod. содержащая архив MikMod, libmikmod-3.1.10 (версия 3.1.10 была самой последней, когда я писал книгу). Разархивируйте этот файл в папку по вашему выбору. 2. Откройте командное окно; дойдите до папки Win32 под вашей главной папкой MikMod. 3.
Если вы хотите использовать статическую версию библиотеки, наберггге nmake —f makefile.static. Если же вы хотите использовать динамическую версию (которая использует mikmod.dll), наберите nmake -f makefile.dynamic, обратите внимание, что оба эти make файла ведут к созданию mikmod.lib., поэтому будьте внимательны.
4. Если в вашей директории include нет mikmod.h, скопируйте win32\mikmod_build.h в include\mikmod.h в директории include. Make файл должен делать это за вас, но в моей системе он не сделал этого. Чтобы использовать библиотеку MikMod в ваших собственных приложениях, проделайте следующую последовательность. 1.
Выберите Settings из ниспадающего меню Project внутри DevStudio.
2.
Добавьте include директорию MikMod в include путь вашего проекта. Вы сделаете -, если зайдете в таблицу C/C++ и выберете там Preprocessor. Добавьте относительный путь в директорию MikMod include. Для примера программы этой главы это будет .A..\libmikmod-3.1.10\include.
Глава 1Q 3.
Добавьте библиотечную директорию mikmod (которая в действительности является директорией Win32) в библиотечный путь вашего проекта. Чтобы сделать это, дойдите до таблицы Link, выберите Input в качестве категории и добавьте относительный путь в окне редактирования под названием Additional library path. Для примера программы этой главыя набрал .A..\libmikmod-3.1.10\win32.
4. Добавьте библиотеку MikMod в ваши библиотеки компоновки путем добавления mikmod.lib в окне редактирования под названием object/library modules. 5.
Не забудьте включить mikmod.h в любые другие файлы источника, использующие MikMod.
Помощь В MikMod Справочная информация о MikMod API содержится в документации HTML, в вашей директории docs. Запустите файл mikmod.html, который содержит всю документацию.
использование MikMod В aBykoBoM gBinkke Чтобы включить в звуковой движок MikMod, нужно создать новый класс, а также провести некоторые изменения в классе CAudioManager. В следующих разделах мы разберем это подробнее.
CTrackedMusic Новый класс CTrackedMusic, получающийся из CSound, как и любая другая форма музыкального или звукового класса в движке, представляет объект трекерной музыки. Вот как выглядит объявление класса. class CTrackedMusic : public CSound {
public: friend class CAudioManager; CTrackedMusic (CAudioManager *mgr} ; virtual -CTrackedMusic (); virtual bool Play(); virtual bool StopO; virtual bool IsPlaying ( ) ; std::string GetFileName ( ) { return (m_FileName) ; ) static void FreeAllModules { ) ; protected: static std: : vector m_AllTrackedMusicInstances ; std::string m_FileName; MODULE *mModule;
Воспроизведение mpekepuou музь&и
171
В основе этого класса находится указатель на MODULE: m_Module. MODULE это тип данных, который использует MikMod; в сущности, это дескриптор мелодии трекерной музыки. Вы получаете адрес MODULE, вызывая Player_Load API. Но я забегаю вперед себя самого. В CTrackedMusic есть еще два элемента. Один из них - это вектор указателей CTrackedMusic. Этот вектор следит за всеми классами трекерной музыки, подвергнутыми обработке,- в конструкторе CTrackedMusic код «проталкивает» в вектор свой указатель this, а в деструкторе он находит и убирает этот указатель this из массива. Движок использует этот список, чтобы освободить все модули, прежде чем прекратит работу MikMod. Очень неряшливо прекращать работу MikMod без освобождения всех модулей, поэтому этот механизм используется для того, чтобы убедиться, что все очищается, когда приходит время. Другой элемент - это m_J41eName, который содержит название файла, из которого был создан класс CTrackedMusic. В нем нет никакой острой необходимости, но он очень удобен для справки. Три обязательных метода: Play, Stop и IsPlaying все в одну строчку, благодаря MikMod API. Для Play код просто вызывает Player_Start; для Stop он вызывает Player_Stop; а для IsPlaying он вызывает Player_Active. Метод FreeAllModules проходит по циклу через вектор m_AHTrackedMusicInstances, вызывает Stop для всех действующих модулей, а 'потом высвобождает их указатели MODULE вызовом Ptayer_Free.
CAudioManager Теперь, когда вы знаете все, что нужно знать о классе CTrackedMusic, начинается вторая часть истории. Поддержка MikMod требует парочки ловких изменений в CAudioManager. Для новичков, в метод Init нужно добавить немного нового кода. MikMod__RegisterAllDrivers ( ) ; MikMod_RegisterAllLoaders() ; MikMod_Init("");
Все эти функции являются необходимыми условиями для установки библиотеки MikMod. Вызов MikMod_RegisterAllDrivers выбирает соответствующий драйвер выходного устройства - в Windows это или драйвер для Windows Multimedia Audio, или сочетающийся драйвер DirectX 5.O. Да, это значит, что вы можете использовать MikMod для каких-то старых оперативных систем, которые не поддерживают DirectX 5.O.
172
Глава 10
Функция MikMod_RegisterAllLoaders выясняет, какие файловые форматы (IT, S3M, MOD и т. д.) может загрузить библиотека. Будьте осторожны - если вы забудете вызвать эту функцию в начале своих программ, вы не сможете ничего загрузить - вы будете просто получать NULL от Player_Load. В конце вызов MikMod_Init включает все и сообщает, какой драйвер MikMod_RegisterAllDrivers выбрал для инициализации и запуска. Символьная строка - это то, где вы помещаете те опции, которые вы хотите передать драйвер, - обычно вам ничего не нужно передавать, поэтому просто оставьте ее пустой строкой. Обычно если мы добавляем новый код в метод инициализации, то мы должны добавить новый код и в метод деинициализации, и этот раз не исключение. Вот новые строчки. CTrackedMusic::FreeAllModules(); Player_Stop(); MikMod_Exit();
Первый вызов заставляет все экземпляры звука освободить их указатели MODULE. Затем Unlnit вызывает Player_Stop в случае, если что-то еще играет, а затем вызывает MikMod_Exit, чтобы все выключить. Как вы можете видеть, работа с MikMod очень проста.
Заключение MikMod сделал эту главу очень простой, и, в действительности, я не говорил о некоторых функциях MikMod. Например, есть API команды для изменения темпа и громкости мелодии; посмотрите документацию MikMod (mikmod.html внутри вашей директории docs) для ознакомления с деталями. Я также советую вам заглянуть в коды источника MikMod, чтобы определить, что в действительности происходит и что делает ваш компьютер, когда вы запускаете воспроизведение файла трекерной музыки. Эта глава - последняя в первой части этой книги, что значит, что теперь вы можете обеспечить выполнение всех основных функций звукового движка. Погладьте себя по голове, съешьте кусок торта, купите себе новую аппаратуру или сделайте еще что-нибудь, чтобы вознаградить себя - вы это заслужили! В следующей части книги вы узнаете, как заставить сиять ваш звуковой движок с помощью включения некоторых продвинутых технологий, таких, как трехмерный звук, аудио скриптинг (audio scripting) и буферы эффектов. Не останавливайтесь здесь, на полпути!
Часть II
Продвинутые Возможности aBykoBoao gBudtka Добро пожаловать в продвинутый раздел этой книги. Теперь, после того, как вы изучили первые главы, вы готовы расширить возможности звукового движка за пределы простых звуковых эффектов и воспроизведения музыки. В этом разделе вы познакомитесь с несколькими продвинутыми методиками, повышающими качество вашего внутриигрового звука. Вы узнаете, как создавать и воспроизводить динамическую музыку - то есть, музыку, которая изменяется в зависимости от событий окружающего мира. Динамическая музыка позволяет вам изменять музыку в ответ на игровые события. Вы также познакомитесь с аудио скриптингом, возможностью DirectX Audio, которая позволяет вам передать вашим звуковым дизайнерам или управление целиком, или часть управления звучанием игровых эффектов и музыки. Больше не будет таких вопросов: «Эй, я сделал новый звуковой эффект, ты не мог бы добавить его в игру для меня?» - используя аудио скриптинг, звуковые дизайнеры могут добавлять, удалять и изменять звуковые эффекты без необходимости повторной компиляции игрового кода. Далее в этой части, вы узнаете о 3D звуке, удивительной технологии, которая позволяет обмануть ваш мозг и заставить его думать, что звук идет из определенной точки трехмерного пространства. При использовании 3D звука для людоеда, пытающегося подкрасться к вашим персонажам сзади, его дыхание и звуки шагов будут раздаваться действительно позади игроков, что значительно увеличит чувство реалистичности и погружения в игру.
Глава 11
ДинамичесКая музьЖа Динамическая музыка и трехмерный звук возвышаются как две самые захватывающие революции в современных звуковых технологиях видео игр. Долгие годы игровая музыка была ограничена короткими музыкальными фрагментами, повторявшимися достаточно часто для того, чтобы напоминать пытку. Я искренне сочувствую своим родителям, которые были вынуждены слушать одни и те же двухминутные 8-битные саундтреки Nintendo с утра до вечера целыми неделями во время моих летних каникул в начальной школе. В наше время компьютеры обладают достаточной мощностью, чтобы собрать даже сложный саундгрек в реальном времени, в соответствии с событиями игры. Они могут причудливо перейти к музыке в другом ключе или незаметно превратить ее во что-то совершенно другое, увеличивая погружение игрока в игру. Хотя, конечно, все это предполагает совместную работу программиста и композитора, чтобы создать необходимые части, которые уже компьютер должен будет собрать вместе. Эта часть и Приложение А идут «рука об руку». В этой части содержится лексикон и объяснение, как воплотить в коде динамическую музыку. Приложение А содержит примеры написания самой музыки. Чтобы уменьшить путаницу, вам следует начать с первого раздела этой главы (элементы динамической музыки). Затем, наденьте свою шляпу, в которой вы пишете музыку, и переходите к Приложению А. А затем снова надевайте свою шляпу для программирования, возвращайтесь сюда и учитесь, как с помощью кода управлять динамической музыкой, которую вы создадите в Приложении А.
Элементы динамически музыКи Предыдущие несколько глав хорошо ознакомили вас с DirectMusic, но они ничего не рассказали вам о том, что лежит за поверхностными функциями. Вы знаете, что такое сегмент и как его загружать и воспроизводить, но вы практически ничего не знаете о том, что лежит в сегменте.
ДинамичесЬая музы/са
175
Приготовьтесь к большому уроку лексики. В этом разделе вы узнаете названия всех объектов DirectMusic, о которых вам необходимо знать. Не беспокойтесь, если вы почувствуете себя перевернутым вверх тормашкамиздесь очень много нового материала. Если вы уже прочитали пару раз, а все еще не можете понять, прочитайте Приложение А, где дается очень наглядная демонстрация всех этих различных объектов DirectMusic.
Колле1щии DL3 ЕСЛИ у вас нет инструментов, вам уже ничего не поможет. DLS коллекция DirectMusic - это набор цифровых выборок инструментов, которые вы будете использовать в вашей музыке. Когда я начинаю новое произведение, то первое, что я делан:), это выбираю нужные мне инструменты и добавляю их в коллекцию DLS мелодии. Вы должны понимать, что, в общем-то, возможно создать мелодию, не используя коллекцию DLS. Каждая мелодия DirectMusic может использовать 127 звуков Общего набора инструментов MIDI; DLS коллекция - это добавочные инструменты, которые вы можете захотеть включить. Также вы должны помнить, что звучание общего набора инструментов MIDI стандартизовано: то, что слышите вы, услышит и игрок. Тем не менее, если вам необходимы особенная выборка, вы будете приятно удивлены потрясающими возможностями, которые вам дает стандарт DLS. Просто чтобы возбудить ваш аппетит: вы можете подавать множество выборок, для различных нот одного инструмента, и вы можете регулировать атаку, спад, поддержку, затухание (ADSR) инструмента (чтобы узнать больше, посмотрите Приложение А).
Ноты Когда мы говорим о динамической музыке, важно помнить, что большинство нот, которые вы вводите, не абсолютны - они относятся к текущему аккорду и ключу, в котором DirectMusic решил воспроизводить вашу музыку. Эти ноты отличаются от нот, используемых в MIDI, которые являются абсолютными величинами. Ноты имеют следующие свойства. Q Высота (Pitch). Очевидное свойство. Не забывайте, она относительна. а
Скорость (Velocity). Это громкость ноты. Скорости имеют значения от 0 (нет звука) до 127 (максимальная громкость).
а
Начальное время (Start time). Время начала звучания ноты. Определяется через такты (bars), доли такта (beats), грид (grid) и тики (tick), что дает вам очень
176
ГлаВаП
точный контроль. В размере 4/4 каждая доля разделена на 4 грида. Четвертные ноты начинаются с нулевого тика нулевой грида данного доли данного такта. Восьмые и шестнадцатые ноты начинаются с различных номеров грида. Q
Конечное время (End time). Время окончания звучания ноты. Также определяется через такты, доли, гриды и тики.
Q
Продолжительность (Duration). Как долго звучит нота. Всегда равняется разности конечного и начального времен.
Также у нот имеются другие продвинутые свойства, которые бывают полезны лишь иногда, такие, как обращение (inversion) и методы воспроизведения (play modes). Для более подробной информации по этим свойствам обращайтесь к документации DirectMusic. Совет. В DirectMusic нота не всегда воспроизводится в нужное время. Это прозвучит странно, но на самом деле это замечательно. DirectMusic автоматически добавляет очень небольшие случайные задержки к времени начала звучания ноты, чтобы сымитировать игру человека-музыканта. Подумайте сами - даже лучшие музыканты не могут сыграть каждую ноту точно вовремя. Они начинают несколькими миллисекундами раньше или позже. DirectMusic делает то же самое, так чтобы музыка не звучала чересчур «идеально» и механически.
Tpeku Не только ноты организуются в треки. Конечно, внутри схемы (pattern) у вас есть нотный трек, который говорит DirectMusic, какие ноты нужно воспроизводить. Но различные типы треков содержат различную информацию, такую же необходимую, как и ноты. Например, трек стилей может указать DirectMusic, что до 13 такта используется один стиль, а после идет переключение на другой. Аналогично, трек настроек (band) может указать на то, что необходимо сменить настройки, и трек аккордов может указать на то, какой аккорд хорошо сочетается с воспроизводимой музыкой.
Партии Партия - это набор нот, воспроизводимых одним инструментом. Если бы вы писали симфонию, то у вас были бы различные партии для скрипки, тубы, банджо и т. д. Аналогично и в DirectMusic. Конкретнее, партия в DirectMusic состоит из трех различных треков. Нотный трек, трек Переключения Вариаций (Var Switch Point) и трек Управляющей Кривой (Control Curve или СС). Трек Переключения Вариаций указывает DirectMusic на то, что можно спокойно переходить к другой вариации внутри этого трека (подробнее
ДинамичесЬая музьЖа
177
об этом будет рассказано далее в главе и в Приложении А). Трек Управляющей Кривой содержит «подтяжки» нот, использование педали и подстройки модуляции, которые были сняты с MIDI клавиатуры во время вашей игры.
Каналы Воспроизведения (PChannels) Считайте, что канал воспроизведения - это связующее звено между партией и звуковой дорожкой. Несколькими главами ранее вы узнали, что вы создаете звуковую дорожку для каждого экземпляра звука, который вы хотите воспроизвести. Звуковая дорожка контролирует громкость экземпляра, его панорамирование и, в конце концов, устройство вывода, с помощью которого будет воспроизведен звук. Каждая партия имеет свой PChannel, в который идет вывод. PChannel определяет громкость и панорамирование, а также инструмент, использованный для того, чтобы сыграть ноту. Вы можете также определить параметры, используемые PChannel для создания своей звуковой дорожки из DirectMusic Producer. Например, вы можете заставить определенный PChannel использовать искусственное эхо; любые партии, воспроизведенные на этом PChannel, будут использовать этот эффект. Можно использовать один PChannel для разных партий. Например, если у вас имеются 4 скрипичные партии, которые вы хотите воспроизвести, используя один и тот же инструмент с одинаковой громкостью и панорамированием, вы можете использовать для них один и тот же PChannel. Вы не будете испытывать недостатка PChannels - максимальное их количество 999.
Схемы Схемы - это центральные объекты DirectMusic, вокруг которых обращаются все остальные. Схемы состоят из нескольким частей и могут быть любой длины, которую вы пожелаете (обычно несколько тактов, но иногда короче или длиннее, в зависимости от структуры музыки и от того, насколько быстро вы хотите ее изменять). Схемы в DirectMusic - это то же, что метры для физики и граммы для химии. Другими словами, схемы являются «удобными» единицами для работы. Если вы ранее писали трекерную музыку, то вы уже знакомы с организацией вашей музыки в схемы. Совет. В большинстве случаев вам будет нужно, чтобы цикличное воспроизведение схем было гладким. Исключением из этого правша являются схемы украшения (см. далее).
Глава 11
178
Партия
Партия
Несколько партий ^ОА А^/ один и тот же PChannel
рс^е,
\/
Партия
Партия
XX
XX
V
PChannel
PChannel
РСпапле!
PChannel определяет инструмент, громкость и панорамирование партии \/
V
JL Вторичный буфер DrectSound
\\. ^V\A Л/ /Несколько PChannels <_Л/1^ могут иметь общую звуковую дорожку Звуковая дорожка
Звуковая дорожка
Звуковая дорожка
|
Партия
V
Синтезатор Direct Music
JL Вторичный буфер DirectSound
^"""^"••A ^^ \
v 7 \/ Первшчный буфер DirectSound
V
JL
Выводом синтезатора DirectMusic являются вторичные буферы DirectSound
Вторичный буфер DirectSound
x,^-'^^*X*^DirectSound микширует / ^"^ втооичные бу[Ьеоы в первичный (как вы узнали в Главе 2}
Рис. 11.1, Иллюстрирует взаимосвязь партий, PChannels и звуковых дорожек в DirectMusic
Вариации Каждая схема может содержать 32 вариации. Вариация - это именно то, что вы думаете, - небольшое изменение в партии, которое случайным образом воспроизводится DirectMusic. Это позволяет вашей музыке быть разнообразнее. Если бы вы вручили схему музыканту и сказали: «Играй это по циклу», - то, в конце кон-
179
цов, музыканту бы это наскучило, и как только он освоился бы с мелодией, то стал бы добавлять вариации, чтобы звучание стало разнообразнее - лишняя нота здесь, синкопа или арпеджио там, и т. д. Вы можете заставить DirectMusic делать то же самое, и только потому, что это не человек, он не может добавлять эту индивидуальность самостоятельно. Вам нужно обеспечить вариации. Заметьте, что вариация не предполагает полного изменения схемы (хотя такое и возможно). В DirectMusic есть много возможностей предоставить интерфейсу выбор из большого числа возможностей, который он совершит случайным образом. Вариации используются только для небольших изменений; если у вас есть две полностью различные мелодии, необходимо поместить их в различные схемы или даже стили — «выше» в структуре объектов DirectMusic (продолжайте читать).
Мотивы Считайте, что мотивы - это «специальные схемы». Мотивы - это небольшие музыкальные фрагменты, воспроизводимые, когда случается что-либо интересное. Они никогда не выбираются DirectMusic случайным образом; вместо этого, вы указываете, когда нужно их воспроизводить. Например, у вас может быть небольшая «бонусная» мелодия, которую вы проигрываете поверх (или вместо) основной темы, когда ваш игрок подбирает какой-то бонус. Правильное использование мотивов позволяет динамической музыке звучать действительно великолепно. Один из простейших путей определения, когда нужно вставить мотив, - это просмотр фильмов. Заметьте, как в фильме ужасов звук нарастает и ослабляется прямо перед тем моментом, когда зомби набрасывается на героиню, или как звук достигает крещендо, когда космический корабль едва-едва успевает улететь с взрывающейся станции. Конечно, в фильмах этого легко достичь, потому что композитор точно знает, когда что-то интересное должно случиться, и может определенным образом выстроить музыку вокруг этого момента. В игре все контролирует непредсказуемый игрок, так что мотивы должны быть готовы к воспроизведению по любому сигналу.
YkpauieHim (Embellishments) Украшения - это также специальные схемы. Украшения используются при значительном изменении музыки, как можно заметить из следующего списка стандартных украшений.
18О
Глава 11
Q Вступление (Intro). Воспроизводится в начале динамической музыки. Q Заполнитель (Fill). Короткое украшение, используемое при переходе к чемулибо отличающемуся, например, от куплета к припеву. Q
Брейк (Break). Аналогичен заполнителю, но в другой форме. Используется, когда одна или несколько партий временно отключаются, чтобы выделить чтолибо еще - возможно, ритм и фоновые аккорды убираются, чтобы выделить место для взрывного гитарного соло, или, возможно, вся мелодия убирается для соло на ударных.
Q Концовка (End). Короткое завершение динамической музыки. Попросту говоря, украшение это схема со специальным флагом, указывающим DirectMusic, что - это за украшение. В дополнение к стандартным типам, перечисленным выше, вы можете устанавливать специальные типы украшений (до 100), если у вас есть события, которые требуют чего-то более сложного, чем просто мотив. Разница между мотивом и специальным украшением сострит в том, что мотив обычно играется поверх основной музыки, тогда как специальное украшение изменяет целую схему, позволяя делать более резкие переходы. Если вы захотите, DirectMusic будет автоматически проигрывать украшения, когда это необходимо (например, когда фрагмент начинается, заканчивается или когда происходит ключевое изменение и т. д.).
Ансамбли Ансамбль - это инструменты, используемые стилем. Ансамбль представляет собой часть вашей музыкальной DLS коллекции (рис. 11.2). В целом, объект ансамбля указывает DirectMusic, какой из инструментов DLS использовать, а также его панорамирование по умолчанию, громкость по умолчанию и канал воспроизведения. Когда ваша схема говорит: «сыграй D диез, используя инструмент номер три», - DirectMusic смотрит на текущий ансамбль, чтобы понять, что такое инструмент номер три. С первого взгляда может показаться, что дополнительная запутанность при обращении к вашим инструментам бесполезна, но вы узнаете, что ансамбли - это мощный инструмент, так как они позволяют вам с легкостью «переключать» наборы инструментов. Каждый стиль может иметь более одного ансамбля, и, при написании программы, вы способны легко между ними переключаться, что дает возможность сделать вашу музыку интереснее и лучше приспособленной к игровым событиям.
ДинамичесЬая музыка
181
Ансамбль использует звуки из звукового набора GM и любых DLS коллекций и присваивает им громкость и панорамирование
\
N '-
Бас 1 {гром. 16, панорамирование по центру)
Барабаны 1
-.,
Гитара 1
5
Флейта (гром. 48, панорамирование справа)
Электрогитара 2 DLS коллекция 1
Флейта
панорамирование слева)
Л 1
Ансамбль
£
Бамбуковая флейта фортепиано I
)вуковой набор GM
Рис. 11.2. Выбор инструментов в ансамбль из DLS коллекции и GM звукового набора
Глава 11
182
Стили Стиль - это объединение схем, украшений, мотивов и ансамблей. Иначе говоря, стиль - это большой кусок музыки. Считайте, что стиль - это большая неаккуратная картонная папка, заполненная листками с нотами - где-то там есть песня, но нужно что-то еще (а именно, сегмент), чтобы все упорядочить. Программисты- могут считать, что стили сродни отдельным СРР файлам в программе. Как код для больших программ разделен по нескольким СРР файлам, так и звуки для сложной части динамической музыки разделены по нескольким стилям.
Сегменты Сегмент — это объединитель всех ваших стилей. Треки, содержащиеся в сегменте, указывают DirectMusic, какой стиль использовать, когда и какие схемы воспроизводить из этого стиля (рис. 11.3). Время
Трек стилей
Стиль!
Трек ритмического уровня
Стиль 2
16 Сегмент
ThisPaltem (ритмический диапазон 1 -10)
CoolPattern (ритмический диапазон 1-10)
AnotherPattern (ритмический диапазон 11-20)
CoolPattern2 (ритмический диапазон 5-15)
Стиль 1
LJnusedPattern (ритмический диапазон 20-30] Стиль 2 Если ритмический уровень позволяет использовать более одной схемы, то DirectMusic выбирает на основании заданного вами критерии
Рис. 11.3. Треки стиля и ритма задают DirectMusic какая схема должна воспроизводиться далее
ДииамичесЬая музьЖа
Jff3
Два самых важных трека в сегменте - это трек стиля, который указывает на определенный стиль, и трек ритмического уровня (groove level) (см. следующий раздел), который помогает DirectMusic определить, какую схему из этого стиля воспроизводить дальше. Также сегменты могут содержать трек ансамбля (т. е. можно проигрьгеать одну схему с разными инструментами), трек темпа и несколько менее используемых треков, влияющих на схемы различным образом. Чаще всего, определенный уровень или часть вашей игры будет использовать только один сегмент. Если вы создаете «экшн» с разделением на уровни, то самый обычный подход - зто один сегмент на каждый уровень. Эти сегменты могут иметь общие схемы, ансамбли и другие объекты, но когда пришло время загружать 1 уровень, ваш код будет использовать файл сегмента 1 уровня (levelLsgt). В этом случае у вас, вероятно, не будет проблем с переключением между сегментами. Вы запустите level I .sgt при загрузке уровня и скажете ему плавно завершиться (возможно, используя украшение Концовки), когда игрок закончит уровень. Если в вашей игре нет такого четкого разделения, то вам может понадобиться переключаться между сегментами. Например, в игре - космической битве вы можете создать различные сегменты для различных эмоциональных состояний. Когда ваш игрок начинает, воспроизводится «неторопливый, спокойный» сегмент. Когда игрок встречает противника, вы можете переключиться на боевой сегмент; когда появляется босс, вы можете переключиться с боевого на сегмент босса, и после, когда игрок побеждает босса, с сегмента босса обратно на спокойный. Или, в многопользовательской игре, вы будете использовать различные сегменты на тот случай, когда ваш игрок побеждает или, наоборот, проигрывает. Как видите, существует огромное число способов для разделения вашей музыки на части. Мне нравится использовать правило, которое гласит, что каждый сегмент является самодостаточным «куском» музыки и достаточно разнообразен, чтобы не надоесть. Конечно, можно всю вашу игровую музыку запихнуть в один сегмент или, наоборот, создать свой сегмент для каждого такта, но и тот, и другой путь только для экстремалов.
Ритмы и ритмичес!ше уровни Каждая схема, которую вы создаете, имеет ритмический диапазон (groove range), определяемый минимальным и максимальным значениями, которые принимают значения от 1 до 100. По умолчанию ритмический диапазон для схемы равен 1-100 (максимально возможный), но вы можете изменить его на любой другой (например, 10-20) или даже на одно число.
184
Глава 11
DirectMusic использует ритмический диапазон, чтобы определить, какую схему воспроизводить. Каждый сегмент содержит ритмический трек, который указывает DirectMusic текущий ритмический уровень, и, таким образом, схемы, которые можно воспроизводить. Например, текущий ритмический уровень равен 10, и у вас есть две схемы: Схема А с ритмическим диапазоном 1 — 5 и Схема Б с ритмическим диапазоном 8 - ТА DirectMusic выберет для воспроизведения Схему Б, потому что текущий ритмический уровень попадает в ритмический диапазон схемы. Также ритмический трек указывает DirectMusic, что делать в том случае, когда для воспроизведения доступны несколько схем. Например, представьте, что в предыдущем примере Схема А имела ритмический диапазон 9-11. Это означает, что и Схема А, и Схема Б доступны для воспроизведения. Ритмический трек имеет дополнительные параметры, указывающие DirectMusic, выбрать ли схему случайным образом (вариант по умолчанию), или сначала воспроизвести Схему А, после Схему Б, или выбрать какую-либо другую схему поведения. При написании программы, вы можете настраивать текущий ритмический уровень, используя глобальный параметр ритмический уровень. Это число прибавляется к ритмическому уровню, определенному в сегменте, и в результате получается текущий ритмический уровень. К примеру, если в ритмическом треке сегмента установлен groove level 3, глобальный groove level равен 7, то DirectMusic будет пытаться воспроизводить схемы с ритмическим диапазоном, включающим число 10. Совет. Ритмические диапазоны не обязательно должны быть диапазонами. Вы можете выставить верхнюю и нижнюю границы диапазона, равными одному и тому же числу, и тогда схема будет воспроизводиться только тогда, когда ритмический уровень в точности равен этому числу.
DirectMusic В понятиях треКерной музыКи Итак, только что я вывалил на вас все эти новые объекты и метафоры, и, вероятнее всего, вы думаете, да как же можно все это собрать воедино. Чтобы помочь разобраться, в этой части мы рассмотрим DirectMusic немного с другой стороны - в сравнении с трекерной музыкой. В трекерной музыке (IT, S3M и т. д.) иерархия объектов выглядит примерно так. Q
Ряды (Rows): «точки», куда можно записывать ноты. Большинство пустует, но в некоторых есть ноты, громкость и/или эффекты и команды (например, G01, говорящая: «Не играть эту ноту самостоятельно, вместо этого залиговать ее с предыдущей»).
Динамически музъЖа
185
О
Треки (иначе столбцы или голоса - tracks, columns, voices): в целом, треки - это массивы интервалов. Обычная длина трека составляет 64 интервала, но некоторые программы (например, Impulse Tracker) позволяют изменять длину треков в схеме.
а
Схемы (patterns): также называемые таблицами (tables), схемы - это основные «кирпичики» музыки. Фрагмент трекерной музыки состоит как минимум из одной схемы, обычно используется большее их число. 64-рядовая схема обычно разбивается на 4 такта, по 16 долей в каждом... Поместите по ноте на каждые четыре ряда, и вы получите обычную четвертную ноту, размер 4/4; поместите две ноты, каждую на два ряда, и вы получите пару восьмых нот (рис. 11.4)
Группа нот в нотной записи
Та же группа нот внутри стандартной программы редактирования трекерной музыки Рис. 11.4. Группа нот в трекерной и в нотной формах записи
Q
Список порядка (Order List): также называемый списком схем или последовательностью схем (pattern list, pattern sequence). Это список, определяющий порядок воспроизведения схем.
186
Глава 11
Q Список выборок (Sample list): это список всех цифровых выборок, используемых в мелодии. Каждое вхождение в список выборок содержит название инструмента, до тех пор, конечно, пока мелодия не завершена и композитор не перепишет эти названия в несколько параграфов ASCII текста и/или картинок. Q Параметры выборки (Sample Parameters): параметры для каждой выборки (громкость и панорамирование по умолчанию). К счастью, все эти конструкции имеют свои аналоги в DirectMusic. Рассмотрим отличия основных объектов трекерной музыки и объектов DirectMusic.
Ряды - это ноты DirectMusic В DirectMusic нет никаких «рядов» в обычном понимании. Вместо этого, у каждой ноты есть присвоенные ей временная метка и продолжительность, которые указывают DirectMusic, когда и как долго звучит нота (плюс-минус несколько сотен миллисекунд, чтобы сымитировать человека). Точность этих временных меток велика, что дает возможность легко воспроизводить такие непростые вещи, как триплеты и нестандартные по длительности ноты. DirectMusic делит время на такты (bar), доли (beat), грид (grid) и тики (tick), также присутствует необязательный пятый + /- параметр, который определяет, насколько раньше или позже может быть нота (в тиках). К примеру, если у меня есть нота, которая начала звучать в первом такте, первой доли, первом гриде и 50-м тике (да, необычная нота) и параметр +/- выставлен в 10, DirectMusic сыграет эту ноту где-то между 40 и 60 тиками. (Это был просто пример, на самом деле, 99,9 % ваших нот будут начинаться с нулевого тика, потому что вряд ли вам понадобится такой уровень управления нотами.) Длительность звучания каждой ноты имеет аналогичный +/- параметр, отвечающий за случайное изменение длины. У нот есть громкость, только теперь она называется скорость. И снова присутствует +/- параметр, отвечающий за диапазон скорости.
Столбцы - это партии DirectMusic Поднимаемся на уровень выше - столбцы в трекерной музыке аналогичны партиям в DirectMusic. У нас одна партия на инструмент, таким образом, для струнного квартета полагается четыре партии. В DirectMusic для каждой партии полагается только один инструмент - вы не можете создать партию, которая использует звучание различных инструментов. Это сильно отличается от того, что было ранее;
Дuнaмuчeckaя музьЖа
187
в трекерной музыке вы использовали один трек для множества инструментов, ведь треки были ценным ресурсом, но DirectMusic способен поддерживать сотни партий, так что нет никакой необходимости перемешивать инструменты друг с другом.
Схемы - Все me ike схемы Поднимаемся еще выше и добираемся до схем. Схемы в трекерной музыке аналогичны (барабанная дробь)... схемам DirectMusic! Но они не совсем совпадают. В трекерной музыке схемы обычно короткие (четыре такта - это стандартный максимум, если песня спокойная и неторопливая), но схемы DirectMusic обычно длиннее. Удобно делать ваши схемы примерно такой длительности, какую имеет фоновая музыка для любой игры на 8-битной консоли (например, первая Nintendo). Другими словами, от 30 секунд до минуты - от 4 до 8 тактов стиля А, далее от 4 до 8 тактов стиля Б, и возвращаемся обратно к стилю А. Конечно нет никаких жестких правил, говорящих, что все должно быть именно так; если хотите, то можете сделать каждую схему длительностью в такт или всю мелодию поместить в одну схему. Качественная динамическая музыка должна использовать несколько различных схем для различных «ритмических уровней» (иначе, уровней интенсивности).
Cnucku nopflgka - это mpeku puniMimeckux уровней и стилей Наконец, мы подошли к последней аналогии и, наверное, самым большим отличиям. В DirectMusic нет такого понятия, как список порядка (список схем). Вместо этого, существуют два трека, обитающих внутри сегмента. Один из них это трек стилей, указывающий DirectMusic на множество схем, из которых он может выбирать сегмент (не забывайте, стиль — на самом деле просто подборка схем) и ритмический трек, который уменьшает это множество схем, задавая ритмический уровень. Например, пусть трек стилей вашего сегмента не изменяется и устанавливает MyCoolStyle.stp, в качестве одного стиля на всю мелодию. Внутри MyCoolStyte.stp содержатся две схемы. Схема А имеет ритмический диапазон (groove range) I - 60. Схема Б имеет ритмический диапазон 50 - 100. Когда ритмический уровень вашего сегмента устанавливает ритмический уровень равным 1, единственная схема, которая может воспроизводиться, - это Схема А; ритмический диапазон Схемы Б не включает текущий ритмический уровень. И наоборот, если ритмический трек устанавливает
188
Глава 11
ритмический уровень равным 75, воспроизводится только Схема Б. Интереснее случай, когда задается ритмический уровень 55 - он попадает в диапазоны обеих схем, поэтому DirectMusic выберет случайным образом, какую схему воспроизводить. Если вы начинающий и вам чересчур сложно все это осмыслить, вы можете считать, что вы пишете трекерную музыку следующим образом. 1. Задайте для каждой схемы уникальный ритмический уровень - не диапазон, а одно число. Для первой схемы groove level 1, для второй схемы groove level 2 и т. д. 2. Добавьте в свой сегмент трек стилей и заставьте его все время воспроизводить из одного и того же стиля (где сохранены все схемы для вашей мелодии) 3. Теперь вы можете использовать ритмический уровень в качестве списка порядка. В ритмическом треке задайте новый ритмический уровень, и соответствующая ему схема будет воспроизводиться. Это, конечно, не совсем то же самое - в отличие от трекерной музыки, ваш ритмический трек может переключать схемы в любое время, а не только по границам схем - но все равно, почти одно и то же. 4. Теперь, если вам этого захочется, вы можете нескольким схемам задать один и тот же диапазон ритмического уровня, и DirectMusic будет выбирать одну из них случайным образом. На самом деле, я вас обманул. Можно сделать немного иначе - при изменении ритмического уровня в треке ритмического уровня вашего сегмента вы можете указать DirectMusic, что делать, когда для воспроизведения разрешено более одной схемы. С помощью окна диалога, показанного на рис. 1.5, можно выбрать один из следующих вариантов. Q Выбрать случайным образом одну схему, проиграть ее, после снова выбрать случайным обраюм схему, проиграть ее и т. д., пока не сменится ритмический уровень. Это действия по умолчанию. Q Выбрать одну схему-счастливицу случайным образом и играть ее по циклу, пока не изменится ритмический уровень. Это называется «Повторять ту же» (Repeat same). Q Начать воспроизводить первую из возможных схем, проиграть их все по порядку, потом вернуться к первой, пока не изменится ритмический уровень. Это называется «Последовательно с первой» (Sequential from First). U Начать воспроизводить случайную схему, далее проиграть все по порядку, выходя на цикл, если необходимо. Это называется «Случайная последовательность» (Sequential from Random).
ДинамичссЬая музь&а
189
Перемешать все доступные схемы случайным образом, воспроизвести их в получившемся порядке и повторять, пока не изменится ритмический уровень. Это называется или «Беспорядочно» (Shuffle) или «Без повтореннй»(Мо repeat), в зависимости от того хотите ли вы повторного воспроизведения схем до того, как все они будут проиграны.
Рис. 11,5. Диалог Groove Level Properties
Cnucku Bbi5opok - это DLS Для создания трекерной музыки у вас должны быть выборки инструментов. В DirectMusic вам не обязательно иметь свои собственные WAV файлы. Все композиции DirectMusic могут по умолчанию использовать Общий звуковой набор MIDI (General MIDI sound set). Общий звуковой набор MIDI содержит 127 различных инструментов, от фортепиано и скрипок до специальных эффектов и флейт «shakuhachi». Высококачественные выборки встроены в DirectX, что гарантирует их одинаковое звучание на всех платформах. Благодаря этому звуковому набору, довольно обычной является партитура DirectMusic, которая не содержит никаких дополнительных выборок. Тем не менее, звуковой набор не в состоянии вам помочь, если вы хотите добавить в свою музыку такие вещи, как барабанные петли (drum loops), текст и сложные гитарные пассажи. Именно поэтому DirectMusic поддерживает DLS коллекции. DLS коллекции гораздо более продвинуты по сравнению с теми, что вы использовали в трекерной музыке. Большинство форматов трекерной музыки позволяют инструменту иметь только одну выборку; в DLS вы можете взять свою выборку для каждой ноты. Также есть возможность настроить фильтр ADSR (Attack - Decay - Sustain - Release), как путем относительной громкости, так и путем относительной высоты. Можно также создавать наборы ударных. Все это достаточно мощные инструменты. Более подробно см. приложение А. - Примеч. науч. рей,
19О
Глава П
Параметры ВыбороК - это ансамбли Для ансамбля DirectMusic нет подходящей альтернативы в трекерной музыке. Это потому, что вы не можете использовать одни и те же списки выбороок для разных файлов трекерной музыки - каждый файл использует свои выборки, ничего не поделаешь. Но представьте, что вы можете это делать. Вы хотите, чтобы все файлы использовали одни и те же выборки, но, возможно, с разной громкостью и панорамированием. Например, в песне 1 вы хотите, чтобы громкость флейты по умолчанию была достаточно высока, так как она ведет мелодию, а в песне 2, где флейта играет в фоне, вы хотите, чтобы ее громкость по умолчанию была существенно ниже. Именно поэтом\' DirectMusic использует ансамбли. В DirectMusic не только возможно, но и часто встречается использование одних и тех же выборок двумя различными песнями. В крайнем случае, все используют Общий звуковой набор MIDI, и вам нужна возможность сказать: «Для этой песни мы берем флейту "shakuhachi", делаем ее громкой, и пусть она звучит из левого динамика». Для другой песни она может понадобиться с половинной громкостью, звучащая из правого динамика, и ансамбли предоставляют нам достаточно гибкости для реализации этого.
Подводим итоги: DirectMusic дает больше, чем треКерная музыКа Многие понятия в DirectMusic и трекерной музыке совпадают, и я написал этот раздел, чтобы обеспечить вам, профи в трекерной музыке, прочный фундамент. Но как уже было сказано, не ограничивайте себе, считая, что DirectMusic — это всего лишь еще один редактор трекерной музыки. Написание динамической музыки с использованием DirectMusic и написание трекерной музыки - это разные вещи, как с точки зрения концепции, так и с точки зрения самого процесса. Изучите принцип работы DirectMusic, и это окупит себя более качественной динамической музыкой в ваших играх.
Программирование динамически музыКи ЕСЛИ вы впервые встретились с динамической музыкой, то самое время ненадолго прерваться. Остановитесь здесь и прочитайте Приложение А, в котором объяснено, как создать простую динамическую партитуру. Как только вы создадите образец партитуры, возвращайтесь к чтению этого раздела, чтобы научиться написанию соответствующего кода.
ДинамичесЬая музьЖа
19J
Класс CDinamicMusic Код этой главы, как и код многих глав до нее, начинается с создания нового класса C++. Новый класс называется CDynamicMusic и получается из класса CDirectMusicSegment, который вы уже знаете и любите. Благодаря качественной C++ архитектуре, код этого раздела будет достаточно простым. Так как партитуры динамической музыки, созданные в DirectMusic Producer, на самом деле просто сегменты, большую часть тяжелой работы на себя возьмет CDirectMusicSegment. Все, что вам нужно сделать, - это добавить несколько методов, характерных для динамической музыки, новому классу CDynamicMusic, этого будет вполне достаточно.
Hacmpouka Для новичков вот код, позволяющий загрузить сегмент динамической музыки с диска, из памяти или с ресурсов. Сейчас он уже хорошо знаком нам. CSoundPtr CAudioManager::LoadDynamicMusic(std:: string filename) { CDynamicMusic *music = new CDynamicMusic(this) ; LoadSegmentFromDisk(music, filename);
rnusic->LoadMotif s { ) ;
return(CSoundPtr(music)) ; } CSoundPtr CAudioManager::LoadDynamicMusic(unsigned char *data, int datalen) CDynamicMusic *music = new CDynamicMusic{this}; LoadSegmentFromMemory(music, data, datalen); music->LoadMotifs(); return(CSoundPtr(music)); } CSoundPtr CAudioManager::LoadDynamicMusic(HMODULE hmod, char *type, WORD resID) I CDynamicMusic *music = new CDynamicMusic(this); LoadSegmentFromResource(music, hmod, type, resID); music->LoadMotifs(); return(CSoundPtr(music)); :
Говоря бессмертными словами Дэвида Бирна: «Такой же, как всегда». Единственное отличие здесь в том, что код обновляет класс CDynamicMusic и затем вызывает его метод LoadMotifs. После LoadMotifs он возвращает «умный указатель» CSound.
192
Глава 11
LoadMotifs загружает все мотивы, содержащиеся в стилях, представленных в сегменте. CAudioManager вызывает этот метод, когда загружает мелодию, поэтому, когда приходит время воспроизводить мотив, код не теряет времени на то, чтобы найти специальный интерфейс ID irectMusic Segments для этого мотива. LoadMotifs проходит по всему сегменту и собирает карту имен мотивов (строк STL) в интерфейсы IDirectMusicSegmentS. void CDynamicMusic: : LoadMotifs () ( IDirectMusicStyleS* pStyle = NULL; DWORD dwStylelndex = 0; while ( 1) { HRESULT hr = GetSegment ( ) ->GetParam( GUID_IDirectMusicStyle, Qxffffffff, dwStylelndex, 0, HULL, (VOID*) &pStyle) ; if (FAILED (hr) ) (
стилей
//ошибка не фатальная... просто говорит о том, что больше нет
break; } DWORD dwlndex - 0; while (1) { WCHAR wstrMotifName[MAX_PATH] ; CHAR strMotifName [MAX_PATH] ; if{S_FAL3E == pStyle->EnumMotif (dwlndex, wstrMotifName) ) { // нет больше мотивов в данном стиле break; }
wcstombs (strMotifName, wstrMotifName, wcsleniwstrMotifName) + 1) ; CDirectMusicSegment *r.ewseg = new CDirectMusicSegment (m_Manager) ; hr = p5tyle->GetMotif (wstrMotifName,
(IDirectMusicSegment **) &newseg->m_Segment) ; Throwl f Fai led ( hr , "CDynamicMusic : : LoadMotifs : pStyle->GetMotif ( ) failed. ") ; // добавить этот сегмент к нашей карте m_MotifMap[std: : string (strMotifName) ] = newseg; dwlndex-f+; } SAFE_RELEASE(pStyle) ; dwStyle!ndex++;
Этот код, основанный на примере программы PlayMotif DirectX, проходит по всем стилям в сегменте. Для каждого стиля он проходит уже по стилю — на этот раз по всем мотивам, имеющимся в стиле. Для каждого мотива он берет интерфейс
Динамически музьЖа
193
IDirectMusicSegments и передает этот интерфейс новому классу CDirectMusicSegment. После этого новый класс помещается в карту mJVtotifMap. Деструктор CDynamicMusic заботится об удалении всех новых CDirectMusicSegment, созданных LoadMotif. CDynarnicMusic: : ~CDynamicMusic () ( for (std::map<std::string, CDirectMusicSegment *>::iterator i == m_MotifMap.begin () ; i != m__MotifMap. end () ; ++i) { delete (*i).second; I m MotifMap.clear(); }
Ничего особенного здесь нет. Деструктор использует цикл, чтобы пройтись по всему содержимому карты и удалить все встретившиеся CDirectMusicSegment.
Воспроизведение динамически музы!ш Чтобы динамическая музыка работала правильно, вам нужно изменить одну ключевую вещь в классе CDirectMusicSegment. Чтобы понять почему, вам нужно сначала разобрать с тем, что такое контрольный сегмент. DirectMusic позволяет вам воспроизводить более одного сегмента одновременно. Например, вы можете проигрывать сегмент, содержащий вашу динамическую музыку, и одновременно с ним сегмент, содержащий WAV файл или звуковой эффект. Как вы знаете, каждый сегмент содержит набор треков. Большинство из них влияют только на свой сегмент - ничего страшного, если воспроизводятся два сегмента и каждый имеет свой трек ритмического уровня. Но некоторые треки сегмента должны быть одинаковы для всех. Например, у всех сегментов должен быть одинаковый темп и карта аккордов (это не производит впечатления, когда музыка играется с разными скоростями и с разными аккордами). Итак, если у вас есть сегмент с аккордами 2СМ и еще один с аккордами А#т7 и вы проигрываете их вместе, то как DirectMusic должен выбирать, какой трек аккордов «победит»? В таком случае он использует мнение контрольного сегмента. Контрольные сегменты «правят» другими играющими сегментами; DirectMusic использует всегда их глобальные треки (аккордов, темпа и т. д..). Это достаточно важно, потому что чаще всего вам необходимо, чтобы ваш сегмент динамической музыки был контрольным. Иначе все использованные вами изменения аккордов не будут учтены, и музыка будет звучать неверно.
7-1490
194
Глава 11
Хорошо, что легко указать DirectMusic, что конкретный сегмент должен быть контрольным - нужно просто передать флаг DMUS_SEGF_CONTROL методу PlaySegmentEx IDirectMusicPerformanceS. Плохо, что из-за C++ архитектуры звукового движка, превращение этой процедуры в доступную требует исправления нескольких различных классов. Начнем с верхнего уровня- вам необходимо добавить параметр к методу Play сегмента CDirectMusicSegment. Новый параметр, называемый control, - это логическая переменная, определяющая, должен ли сегмент, воспроизводимый вами, быть помечен как новый контрольный сегмент. Этот параметр напрямую передается методу Init CSoundlnstance, потому что именно CSoundInstance::Init вызывает PlaySegmentEx. Init также приобретает новый параметр (он тоже называется control - представьте себе!), который определяет, нужно ли флаг DMUS_SEGF_CONTROL передать в PlaySegmentEx. Теперь достаточно просто заставить метод Play CDynamicMusic использовать новый флаг. bool Play () { return (CDirectMusicSegment:: Play (NULL, true')}; }
Это достаточно хитро, но тем не менее важно, так как это гарантирует, что ваша динамическая музыка воспроизводится правильно.
Изменение PumMimeckoao уроВня Теперь, когда вы знаете, как воспроизводить динамическую музыку, время разобраться с тем, как заставить ее реагировать на текущую ситуацию. Предположим, что ваш игрок запрыгивает на маленького динозаврика, и вы хотите, чтобы музыка поменялась. Это можно сделать, используя ритмический уровни (groove levels): представьте, что вы создаете две схемы «без динозаврика» и присваивайте им groove levels 3 и 2, после этого вы создаете две аналогичные схемы, но «с динозавриком» и присваивайте им groove levels 11 и 12. В своем сегменте вы организуете ритмический трек (groove track), который периодически изменяет groove level на 1 или 2. (Кстати, именно в Приложении А объяснено, как это сделать.) В вашей игре, когда персонаж запрыгивает на динозаврика, вы просто устанавливает модификатор groove level равным 10. DirectMusic добавит 10 к текущему groove level, который равен 1 или 2, в зависимости от того, где вы находитесь в сегменте. В итоге вы получите groove level 11 или 12, и вы услышите одну из схем «с динозавриком».
795
Когда ваш персонаж потеряет динозаврика (динозаврики всегда теряются), ваш код устанавливает модификатор groove level равным нулю. Это вернет groove level к значениям 1 и 2, и будут проигрываться схемы «без динозаврика». То, что я описал, - это пример очень простого, очень резкого изменения в динамической музыке. Вы можете создать динамическую музыку, которая содержит менее резкие переходы, скажем, заставив ваш код, медленно изменять модификатор groove level со временем или создав несколько схем с перекрывающимися ритмическими диапазонами. Это лишь вопрос того, сколько сил вы на это хотите истратить. Но перед тем как этим заниматься, вам нужно узнать, как в коде устанавливать модификатор groove level. К счастью, это очень просто - это то же самое, что и установка темпа! Вот код, позволяющий получать и устанавливать модификатор. void CAudioManager: : SetPerformanceGrooveLevelModif ier (int value) { if (value > 100) value=100; if (value < 0) value=0 ; char v = value; if (NULL == m_Performance) return; Throwlf Failed ( m_Perfonnance->SetGlobalParam(GUID_PerfMasterGrooveLevel, &v, sizeof ( v ) ) , "CAudioManager: : SetMasterGrooveLEvel : SetGlobalParam failed.") ; } int CAudioManager: :GetPerformanceGrooveLevelModifier () { if (NULL =~ ra_Performance) r e t u r n { 0 ) ; char groove=0; m__Performance->GetGlobalParam(GUID_PerfMasterGrooveLevel, &groove, sizeof (groove) ) ; return (groove) ; }
Эти два метода точно такие же, как и SetMasterTempoScaleFactor и GetMasterTempoScaleFactor, за исключением того что вместо работы с GUID_PerfMasterTempo код работает с GUID_PerfMasterGrooveLevel. /
Воспроизведение мотивов Когда ваш игрок подбирает бонус или делает что-нибудь хорошее, вы можете захотеть проиграть звуковой эффект, называемый мотивом. Так как звуковой manager уже указал новому классу CDynamicMusic собирать интерфейсы ко всем мотивам и тар их по имени, то код для воспроизведения5 мотива умещается в несколько строчек.
196
Глава 11 void CDynaniicMusic: : PlayMotif (std: : string name, bool bUseDef aultBounciary, bool bUseGridBoundary, bool bUseMeasureBoundary, bool bUseBeatBoundary) { if (rn^MotifMap. f ind(name) != m_MotifMap. end ( ) ) { CDirectMusicSegrnent *motifseg = m_MotifMap[name] ; int segflags = 0; if (bUseDef aultBoundary) segflags | = DMUS_SEGF_DEFAULT; if (bUseGridBoundary) segflags 1= DMUS_SEGF_GRID; if (bUseMeasureBoundary) segflags 1= DMUS_SEGF_BEAT; if (bUseBeatBoundary) segflags |= DMUS_SEGF_MEASURE; HRESULT hr = m_Manager->GetPerformance ()->PlaySegment { motifseg->GetSegment ( ) , segflags, 0, NULL) ; Throwlf Failed (hr, "CDynamicMusic: :PlayMotif : PlaySegment failed."} ;
Этот метод в качестве входных данных получает имя мотива, который нужно проиграть, и набор флагов, которые указывают, когда можно его начинать воспроизводить (по грид, доле, такту или по любой другой величине по умолчанию). Эти флаги имеют значения по умолчанию, указывающие коду на использование разметки по умолчанию, и в большинстве случаев эта разметка работает нормально.
Заключение DirectMusic - это мощный инструмент, но цена, которую вы платите за его освоение,- это изучение целого вороха новых терминов и концепций. Положительной стороной, тем не менее, является то, что уровень сложности вы выбираете сами. Начните с простого, создавая динамическую музыку, содержащую всего лишь несколько компонент, — музыку, которая почти все время звучит одинаково. А потом постепенно наращивайте сложность музыки и, соответственно., кода до тех пор, пока вы не поймете, как все это работает вместе.
Глава 12
Ckpunmbi u эффеКты В каждой книге существует точка, от которого нет возврата — точка, где вы осознаете, что ваша цель не начать читать книгу, а закончить ее читать. Мне кажется, что эта глава и есть такая точка. Вы изучили действительно трудный материал - в частности динамическую музыку - и теперь можно завершать формирование ваших знаний различными продвинутыми методами. Два из этих продвинутых методов рассматриваются в этой главе. Здесь вы узнаете про возможности DirectX Audio скриптинга и про возможности эффектов текущего времени.
Аудио ckpunmuHa Включаемый в DirectX, начиная с версии 8.0, аудио скриптинг обеспечивает мощную возможность параллельной работы командам разработчиков. На первый взгляд, кажется, что использование языка скриптов для воспроизведения звуков это пальба из пушки по воробьям, но, на самом деле, он очень удобен для командной разработки игр, так как он позволяет звуковым дизайнерам работать быстрее и меньше зависеть от программистов.
Зачем использовать аудио ckpunmui»? До того как появился аудио скриптинг, звуковым дизайнерам (sound designers) было сложно работать автономно. Представьте - вы звуковой дизайнер, работающий в команде, включающей еще несколько звуковых дизайнеров и программистов (а также художников, аниматоров, продюсеров и много других людей). Сегодня ваша работа состоит в том, чтобы убедиться, что пехотинцы орков в вашей игре хорошо озвучены.
198
Глава 12
Вы делаете свое дело и в итоге получаете то, что, по вашему мнению, является хорошим WAV файлом для пехотинца орка. Вы заносите его в базу данных ресурсов вашей игры и отправляете сообщение звуковому программисту с просьбой вставить ваш звук в игру. Программист делает это и возвращает вам новую версию игры, в которой вы можете услышать свой звук. Вы слышите свой звук в игре и понимаете, что он отвратителен. Ваш пехотинец, который так замечательно звучал в вашем WAV редакторе, воспроизводится в игре слишком мягко и тихо, и вы едва можете расслышать орков. Тогда вы отправляете письмо программист}' с просьбой увеличить громкость звукового эффекта орка. Программист делает это и собирает новую версию игры. И тут вам приходит в голову, что звуки пехотинца орков звучат замечательно для синего орка, но звучат глупо, когда красный орк издает тот же самый хрюкающий звук, что и синий орк. Тогда вы делаете новый файл blue_orc_grunt.wav и отправляете письмо программисту с просьбой вставить этот новый звук в игру. Видите, что получается? Каждый раз, когда вам как-то нужно изменить звук, вы должны отвлекать программиста с просьбой о новой версии. Это тратит и ваше и его время впустую — программист не хочет, чтобы его постоянно отвлекали, а вы не хотите постоянно быть обязанным ждать новой версии. Аудио скригггинг решает эту проблему, располагая код, который воспроизводит звуковые эффекты, в скрипте, загружающемся в реальном времени1. Когда все работает таким образом, звуковые дизайнеры больше не должны зависеть от программистов, при внесении своих изменений в игру. Они могут просто отредактировать скрипт (текстовый файл) и перезапустить игру, чтобы посмотреть на изменения.
На заметЬу: Время циАла Профессиональные команды разработчиков игр всецело зависят от того, что они называют «временем цикла». Время цикла - это просто время между двумя тестами минус время, которое вы истратили на исправление ошибок. Для программиста время цикла зависит от того, насколько много времени займет остановить отладчик, заново скомпилировать код, перезапустить отладчик и вернуться к тому месту в игре, где возникла проблема. Для художника То есть к игре прикручивается какой-либо внешний интерпретатор скриптового языка - lua, tcl и т. д. Поскольку команды данного языка нет необходимости компилировать (они интерпретируются), то можно менять код обработки звука без изменения самого игрового движка. Тем более, что скриптовые языки довольно просты и обучить им можно даже далеких от программирования людей (звуковых дизайнеров и т. д.). - Примеч. науч. ред.
Ckpunmbi и эффекты
199
или аниматора время цикла зависит от того, насколько много времени займет экспорт нового элемента (текстуры, модели, анимации и т. д.) и увидеть его внутри игры. Очевидно, что чем меньше время цикла, тем больше «отладок в минуту» вы можете позволить и тем лучше будет конечный продукт. Если вы художник и вам требуется потратить несколько минут на то, чтобы увидеть ваше новое творение внутри игры, вы сможете пройти только два или три цикла, до того момента как вы решите, что оно «достаточно хорошо» и можно заняться чем-то еще. Но, если ваше время цикла невелико - скажем, вам нужно напечатать всего пару команд, чтобы увидеть результат работы в игре - у вас есть возможность пройти через большое количество повторений «посмотреть/изменить», и в итоге игра будет выглядеть лучше, потому что вы потратили значительное время на ее усовершенствование. Итак, если вы работаете в команде, старайтесь делать циклы времени для всех настолько короткими, насколько это возможно. Вы можете сделать это, создав игровые редакторы типа WYSIWYG («what you see is what you get») и сделав максимум вещей, управляемых данными. В итоге вы получите более качественную игру и довольных товарищей. Конечно, программист и звуковой дизайнер все еще должны согласовать список звуковых эффектов в игре. Но программисту уже не нужно думать о деталях их воспроизведения (например, используется ли один звуковой файл для нескольких эффектов, какова громкость эффектов и т. д.). Программист просто вызывает подпрограмму, описанную в аудио скрипте, и аудио скрипт берет на себя заботу об указании DirectMusic, что и как играть. Совет. Аудио скрипты вряд ли окажутся вам полезны, если вы в одиночку разрабатываете игру или если случилось так, что ваш звуковой дизайнер одновременно является программистом и может изменять код напрямую. Тем не менее, если вы работаете в большой команде, аудио скрипты могут сильно помочь.
Основы аудио сКриптинга Представьте, что вы создаете игру про кошек (ну, это же сработало в мюзикле почему бы теперь не сделать про это игру?). Игрок управляет персонажем-кошкой и может находить бонусы, которые со временем приводят к росту кошки. Совет. Очень важно выбрать правильный аудио интерфейс, т. е. список подпрограмм и переменных, используемых в вашем аудио скрипте. Аудио скриптинг работает лучше всего, если есть детально описанный интерфейс между игровым кодам и звукам, В идеальном мире вы определяете аудио интерфейс раз и навсегда. Про-
2ОО
Глава 12
громмисты и звуковые дизайнеры встречаются, обсуждают, какие переменные игровой код будет выставлять, какие подпрограммы он будет вызывать, и далее каждый занимается своим делом. Дизайнеры работают со звукам, а программисты над оставшейся частью игры. На практике этого не будет никогда, но к этому нужно стремиться. Несколько изменений в вашем интерфейсе появятся в любом случае, но если вы постоянно будете заниматься изменением своего аудио интерфейса целиком, вы не поймете, в чем смысл аудио скрипттга.
Вы звуковой дизайнер этой (несомненно, революционной) игры, так что ваша работа — это создание кошачьих звуковых эффектов. Вы с вашими программистами сошлись на простом интерфейсе. Код игры будет отслеживать, насколько велика кошка игрока, и передавать вам эту информацию с помощью одной переменной CatSize. Также будет одна функция, sfxCatMeow, которую код игры будет вызывать в моменты, когда кошка должна мяукнуть. Эта sfxCatMeow функция должна посмотреть на переменную CatSize и создать мяуканье соответствующей силы. В идеале должен быть набор различных звуковых эффектов мяуканья, так чтобы кошка не мяукала одинаково снова и снова. Эта ситуация специально смоделирована, чтобы показать полезность аудио скриптов. При использовании аудио скриптов звуковые дизайнеры могут свободно создавать так много различных звуковых эффектов мяуканья, как они хотят, и могут свободно организовывать их любым образом без необходимости редактирования кода игры. Аналогично, программисты не рассматривают детали того процесса, как кошка мяукает — они просто вызывают функцию в скрипте и правильное мяуканье, как по волшебству, раздается из динамиков. Следующие два раздела покажут, с точки зрения звукового дизайна и с точки зрения программирования, как реализуется этот скрипт. Скрипт будет иметь только одну подпрограмму и только одну переменную; не забывайте, что аудио скрипт для настоящей игры будет иметь десятки переменных и подпрограмм. Сначала вы научитесь тому, как создать скрипт (т. е. собственно код скрипта), а далее - как интегрировать этот скрипт в ваш игровой C++ код.
Tonka зрения зВуКоВого дизайнера Язык аудио скриптинта, предлагаемый DirectX, основан на языке Visual Basic, так что если вы уже использовали любой из других вариантов VB (Active Server Pages, макросы в Microsoft Office и т. д.), то вы знаете большинство из того, что вам нужно. Если вы пока никоим образом не знакомы с VB, то вы, вероятно, захотите познакомиться с каким-либо учебным пособием, чтобы разобраться с основами управления потоком и синтаксисом (посмотрите ссылки на приложенном CD).
Ckpunnibi и эффекты
2OJ
Можно создавать AudioVB скрипты, используя DirectMusic Producer, но это требует большего, чем просто создание нового объекта скрипта и набора вашего скрипта в редакторе. Так происходит, потому что вы не можете воспроизводить WAV файлы напрямую из AudioVB скрипта - вы можете только воспроизводить сегменты. Таким образом, вам нужно создать сегмент для каждого WAV файла, который вы хотите воспроизвести в скрипте. Рассматривайте WAV сегмент как сегмент, который вместо схем содержит WAV файлы. В этой главе ваши WAV сегменты не будут иметь ритмического уровня, они не будут зависеть от стилей, они не будут воспроизводить схемы. Вместо этого, они будут воспроизводить звук, используя WAV трек. Я считаю, что сегменты с WAV треками - это ярлыки в DirectMusic; конечно, вы можете создать стиль и несколько схем, создать трек стилей и трек groove в вашем сегменте и заставить ваши звуковые эффекты воспроизводиться таким образом. Этот способ подходит для создания сложных звуковых эффектов из простых WAV элементов. Но если вам нужно просто воспроизвести простой звуковой эффект, вам не нужно разбираться со всем этим. Вы можете просто вставить ваш WAV файл напрямую в сегмент и на этом закончить. Совет. AudioVB скрипты не ограничены простым воспроизведением звуковых объектов (хотя здесь вы научитесь только этому). Вы также можете использовать их для воспроизведения обычных сешентов, изменения уровня groove, проигрывания мотивов - теоретически все, что вы может быть сделано в DirectMusic Producer, вы можете сделать, используя скрипт.
Создание сегментов зВуКоВых эффеКтов Это то, чему вы сейчас научитесь. Запускайте DirectMusic Producer и создавайте новый проект (я назвал свой CatProject). Вам понадобятся аудио скрипт и два сегмента. Назовите скрипт catsfx.spp и назовите сегменты BigCat.sgp и SmallCat.sgp. Далее, вставьте четыре WAV файла с мяуканьем в ваш проект, щелкая правой кнопкой мыши на корневом узле дерева проекта и выбирая Insert Files. (Вы можете найти файлы с мяуканьем в папке этой главы. Там четыре файла, два из которых это мяуканья маленькой кошки и еще два - урчание большой.) Выберите четыре WAV файла, вы должны увидеть четыре появившихся WVP объекта. Теперь вам необходимо вставить эти четыре WAV файла в два сегмента, каждый с двумя вариациями (wave трек, как и часть схем, может иметь до 32 вариаций). Перейдите к BigCat сегменту, щелкните правой клавиши мыши на Time Signature Track и выберите Add Track(s) из контекстного меню. Добавьте Wave трек.
Глава 12
2О2
Совет. Вместо того чтобы 29 раз нажимать па кнопки, чтобы включить/выключить вариации, вы можете переключить целый набор вариаций, нажав на узкую кнопку слева (рис. 12.1) Нажмите на эту кнопку, чтобы включить/выключить набор вариаций целиком
Рис, 12.1. Нажатие на узкую кнопку слева включит/выключит вариацию целиком
Теперь сегмент начинает походить на схему. Обратите внимание, что ваш новый трек wave (или Wave Part) имеет кнопки вариаций, так же, как и схема. Отключите все, кроме вариации 1, переведите курсор на начало трека и нажмите Insert. Появятся wave свойства, показанные на рис. 12.2. Выберите CatProjectiBigCatl, чтобы указать DirectMusic, что необходимо воспроизвести этот файл в качестве вариации 1 (заметьте, что в списке появляются только те WAV файлы, которые являются частью проекта, именно поэтому необходимо было их предварительно импортировать). Если все в порядке, то вы увидите графическое представление вашего WAV файла, изображенное в начале трека.
' Beat Stxt
|1
G(id
|>"~Ш I"
Id. ЕБ1°EB
Source: 2 Source 1
ШМ8 0
Рис. 12.2. Окно wgye свойств
Повторите то же самое, чтобы файл bigcat2.wav воспроизводился в качестве вариации 2. Не забудьте отключить вариацию 1, чтобы случайно не поместить bigcat2.wav в обе вариации. Вы можете легко проверить правильность вставки: bigcatl.wav должен появляться и исчезать при переключении кнопки вариации 1, а bigcat2.wav должен появляться и исчезать при переключении кнопки для вариации 2.
Ckpumnbi и эффекты
2ОЗ
Еще одна вещь, которую важно сделать, - в окне сегмента, щелкните правой кнопкой мыши на названии wave трека на сиреневом фоне и выберите Properties. DirectMusic Producer покажет вам свойства wave трека (рис. 12.3). Перейдите к закладке wave track и убедитесь, что флаг Reset Variation order on Play выставлен. Это необходимо при создании сегмента, который проигрывает bigcatl.wav или bigcat2.wav случайным образом. WiWeVrack Properties TrackGioupAssignmentj Flagt
Wave 1 tack |Wa*eP*tj
Allirwaliori |
J| |000 ^j dB
Flags- • Г" tock venation lo paHwn track P Resel variation older on play
Рис. 12.3. Окно свойств wave трека
Если вы воспроизведете сегмент, используя панель инструментов, то вы должны услышать bigcatl.wav и bigcat2.wav, воспроизводимые случайным образом. (Если вы слышите их вместе, то значит, вы случайно не отключили одну из вариаций, пока работали с другой.) Рис. 12.4 показывает окончательный сегмент (обе вариации активны). Используйте тот же метод, чтобы создать SmallCat сегмент, который случайным образом воспроизводит smallcatl.wav или smallcat2.wav. Опять же, следите внимательно за включенными вариациями и не забудьте проверить сегмент, используя панель инструментов.
Разработка простого ckpunma Настроив ваши сегменты, вы готовы к созданию собственно аудио скрипта. Начните с нажатия на кнопку New панели инструментов; выберите Script в появившемся диалоговом окне. Переименуйте новый скрипт в catsfx. Прежде чем вы начнете написание кода для аудио скрипта, необходимо сделать одну вещь — указать DirectMusic на те объекты, на которые будет ссылаться ваш скрипт. Скрипт, который вы собираетесь написать, будет использовать два сегмента SmallCat и BigCat, поэтому нужно добавить эти два сегмента в ссылки скрипта. Откройте узел в древовидном меню, содержащий контент вашего скрип-
Глава 12
204
Рис. 12.4. Окончательный BigCat сегмент с обеими активными вариациями
та, и щелкните правой клавишей мыши на Reference Runtime. Добавьте BigCat.sgp и SmallCatsgp в список ссылок (рис. 12.5). Нажмите ОК, и вы должны увидеть ярлыки ваших сегментов, содержащиеся внутри директории Reference Runtime в вашем скрипте. fldriflternowcaisfifComainarffles : AveiMfc £fei л Piqert Ь!цса11 HYP.. _ .
.
Embed Ячйия ™ Eirteth
(Ш] Retee^e Runhm*; BigCal >jp
|
Q^
j
ttBgnove 1
»R^* | •SdtBji- --
Rajtl
|
r^
Рис. 12.5. Добавление ссылок в аудио скрипт
Ckpunmbi и эффе&ты
2О5
Предупреждение. Очень важно добавлять ссылки на все объекты, на которые ссылается ваш аудио скрипт. Если вы этого не сделаете, то ваш скрипт не будет работать.
После того как вы разобрались со ссылками, возвращайтесь в редактор скриптов (щелкните дважды на вашем catsfx скрипте) и введите следующий AudioVB код. dim CatSize sub sfxCatMeow if CatSize < 10 then SmallCat.Play(IsSecondary) else BigCat.Play(IsSecondary) end if end sub
Следите за синтаксисом - AudioVB отличается от C++ (например, точка с запятой после оператора AudioVB сильно вам все испортит). Что касается самого нового скрипта, он совсем простой - объявляется одна переменная, CatSize, и одна подпрограмма sfxCatMeow. Эта подпрограмма смотрит на переменную CatSize (которую будет выставлять игра). Если она меньше десяти, то воспроизводится SmallCat сегмент; иначе воспроизводится BigCat сегмент. Обратите внимание на использование флага IsSecondary, который указывает DirectMusic на то, что сегмент должен воспроизводиться как вторичный сегмент (обычно вам будет удобнее, чтобы ваши звуковые эффекты были именно вторичными сегментами, так чтобы они не забирали управление от фоновой динамической музыки, которая может проигрываться). Совет. При редактировании вашего скрипта, вы можете использовать DirectMusic Producer для проверки наличия ошибок синтаксиса. Если вы нажали кнопку refresh, которая находится над вашим спискам подпрограмм, справа от области редактирования, и окно списка заполнилось списком ваших подпрограмм, можете быть уверены, что с синтаксисом все в порядке. И наоборот, если ваши подпрограммы и переменные не появились, когда вы нажали refresh, проверьте синтаксис!
Сохраните этот скрипт, сохраните проект и после этого сохраните все в Runtime формате, и вы закончили! Поместите Runtime файлы в рабочую директорию примера программы главы 12 - директорию под названием Chl2pl_AudioScripts. Вы можете сделать это, сохраняясь туда напрямую или копируя туда файлы, сохраненные в рабочей директории вашего проекта. С другой стороны, вы можете написать код, который будет вести поиск в другой директории, по умолчанию поиск производится в текущей директории, которая, при запуске примера программы с помощью DevStudio, и есть Chl2pl_AudioScripts.
2О6
Глава 12
Очевидно, что настоящая игра будет иметь более сложный аудио скрипт. Настоящая игра, вероятно, будет иметь функции для каждого звукового эффекта, так же, как и функции для управления фоновой динамической музыкой, проигрывания мотивов и для всего остального, что требуется игре. Это исключительно ваше дело, как организовать все эти подпрограммы. Вы можете иметь столько различных файлов скриптов, переменных и подпрограмм, сколько захотите, хотя хорошим правилом является использование одного файла скрипта для каждого уровня вашей игры. Совет. Обратите внимание, в аудио скрипте название подпрограммы начинается с sfx. Это хороший стиль — выбрать определенный способ стандартного создания имен в аудио скрипте, например, установить, что каждая подпрограмма, имеющая отношение к воспроизведению звукового эффекта, начинается с sjx. Создание стандартных соглашений по именам (и следование им!) может помочь сохранять большие скрипты систематизированными.
Tonka зрения разработчика Как только вы создали сегменты и скрипт, сохранили их в runtime формате, вам следует обратить внимание на написание игрового кода, который будет взаимодействовать со скриптом. DirectX Audio обеспечивает очень простой, на 100% полный, интерфейс для ваших аудио скриптов. По существу, после инициализации интерфейса скрипта, вы можете запросить у интерфейса список подпрограмм и переменных, вы можете выставлять и извлекать переменные и вы можете вызывать подпрограммы. На самом деле, это все, что вам нужно делать, и в большинстве случаев вам даже не нужен будет список подпрограмм и имен переменных, потому что простое соглашение по наименованию имен переменных и подпрограмм работает достаточно хорошо. Таким образом, нам нужно внести некоторые усовершенствования в наш код. Последующие разделы покажут вам, как добавлять поддержку аудио скриптинга а звуковой движок. Большинство этой поддержки осуществляется через новый класс CAudioScript плюс несколько новых методов CAudioManager. Но прежде чем вы сможете заняться CAudioScript и CAudioManager, Вам нужно создать новый интеллектуальный указатель.
Ckpunmbi и эффекты
2О7
Новый, чуть более умный указатель До сих пор вы обходились одним классом интеллектуальных указателей, CSoundPtr. Так было, потому что все, что вы изучали до сих пор, можно, в простейшем приближении, рассматривать как нечто, что вы можете проигрывать, останавливать и включать громкость. На самом деле, вы не можете воспроизвести аудио скрип так же, как сегмент или wave файл. Таким образом, не имеет смысла получать ваш новый класс CAudioScript из класса CSound. CAudioScript не получается ни из чего, потому что он не похож ни на что, из ранее вами написанного. Это означает, что когда вы попросите CAudioManager загрузить аудио скрипт, вы не получите обратно CSoundPtr. Но что-то получить нужно! Конечно, можно просто написать процедуры загрузки и заставить их возвращать указатель на CAudioScript, но очень просто создать новый интеллектуальный указатель (получаемый из шаблона CRefCountPtr) созданный специально для CAudioScript. Выглядит он так. // интеллектуальный указатель на CAudioScript class CAudioScriptPtr : public CRefCountPtr { public: explicit CAudioScriptPtr(CAudioScript* p = NULL); void Release (}; };
Все не так уж плохо - всю тяжелую работу берет на себя CRefCountPtr. Все, что нужно CAudioScriptPtr, -это конструктор и метод Release. Вот эти методы: CAudioScriptPtr::CAudioScriptPtr(CAudioScript* p) : CRefCountPtr(p) { 'Stringstrearn str; if (P) i str « "CAudioScriptPtr::CAudioScriptPtr: constructed for pointer " « p « " name: " « p->GetName() « " (count = " « itsCounter->count « ")..." « endl; OutputDetougString(str.str{).c_str()); } } void CAudioScriptPtr::Release{] { stringstream str; if (itsCounter) { str « "CAudioScriptPtr: :Release (}: pointer " « itsCounter->ptr « " name: " « itsCounter->ptr->GetName()
2О8
Глава 12 « " (count = " « itsCounter->count « ")..." « endl; OutputDebugString {str . str ( } . c_str () ) ;
Ни тот, ни другой метод не делает ничего критичного - они просто много говорят. Конструктор копирует строку состояния в трассировочный вывод DevStudio, так что вы точно можете увидеть, когда создаются интеллектуальные указатели, и метод Release копирует строку, чтобы вы могли видеть, что все освобождается корректно. Обратите внимание, что эти подпрограммы выводят имя класса CAudioScript, полученное с помощью метода GetName, который вы создадите позже. Также обратите внимание, что метод Release фактически ничего не делает - на самом деле, удаление указателя идет внутри CReiCountPtr. Интеллектуальный указатель на CAudioScript идентичен интеллектуальному указателю на CSound с той разницей, что выводятся различные трассировочные записи и в CRefCountPtr передается другой параметр шаблона. CAudioScriptPtr отображает имя аудио скрипта, с которым он работает, так что вы можете сказать, что CAudioScriptPtr чуть интеллектуальнее, чем CSoundPtr, хотя, раз оба точно знают верное время удаления своих указателей, они оба достаточно умны.
CAudioScript Раз вы создали новый интеллектуальный указатель CAudioScriptPtr, то теперь вы можете создать и сам класс CAudioScript. Вот его объявление. class CAudioScript { public: Vfriend class CAudioManager; CAudioScript {CAudioManager *mgr) ; virtual ~CAudioScript ( ) ; void SetVariable (std: : string varname, int value} ; int GetVariable (std: : string varname); void CallRoutine {std: : string procname) ; std: :vector<std: :string> EnumVariables (} ; std: :vector<std: :string> EnumRoutines ( ) ; s t d : : s t r i r i g GetNameO { return (m_Name) ; } std: : string AssembleErrorlnfoString (std: : string header, DMUS_SCRIPT_ERRORINFO Serrlnfo) ; protected: std:: string m_Narae; void Init (std: -.string name); CAudioManager *m_Manager; IDirectMusicScriptS *m Script; unsigned char *m OriginalData;
Ckpunmbi и эффе/ипы
209
Здесь должно быть много вещей вам, уже знакомых, и несколько, пока еще нет. Начнем со знакомого: обратите внимание, что CAudioScript имеет указатель m_Origina!Data так же, как и CSound. Этот указатель m_OriginalData устанавливается аудио менеджером, когда аудио скрипт создается из памяти - как и производные CSound, класс CAudioScript помнит память, из которой он был создан, и освобождает память, когда уничтожается. Также обратите внимание, что, как и CSound, CAudioScript дружественен по отношению к CAudioManager (CAudioManager имеет доступ к личным и защищенным методам CAudioScript напрямую при загрузке нового аудио скрипта) и что новый класс CAudioScript может быть создан только при наличии у казателя на CAudioManager (т. е. каждый CAudioScript имеет указатель на CAudioManager и, таким образом, на интерфейс воспроизведения DirectMusic). Теперь переходим к новым вещам. В центре CAudioScript лежит m_Script, интерфейс IDirectMusicScriptS, обеспечиваемый DirectX Audio. Также вы можете заметить, что аудио скрипт имеет член m_Name, доступ к которому осуществляется с помощью методов GetName и SetName. Это сделано исключительно для удобства игрового программиста - имя аудио скрипта будет выводиться в трассировочный вывод интеллектуальным указателем на CAudioScript при создании и освобождении. Вы увидите, как код работает с именами в следующем разделе: Класс CAudioScript имеет приватную функцию Init, которая заботится о проведении одноразовой инициализации для скрипта. Эта функция Init вызывается только CAudioManager (друг класса, который может вызывать приватные функции) при загрузке нового скрипта. Остались методы получения и выставления переменных, вызова подпрограммы, перечисленияя доступных переменных и подпрограмм, создания строк ошибки, обо всем этом вы узнаете далее.
Инициализация и aaepyaka ckpunma Перед тем как вы начнете работать с аудио скриптом, вам нужно его загрузить и инициализировать. Это означает, что вам нужны три новых метода для CAudioManager: один метод для загрузки из памяти, один для загрузки из ресурсов и еще один для загрузки из памяти. Так выглядит метод загрузки с диска. CAudioScriptPtr CAudioManager::LoadScript(std::string filename, std::string name) { CAudioScript *script = new CAudioScript(this}; HRESULT hr; //преобразуем в уникодовскую строку
210
ГлаВа12 WCHAR widef ilename [MAX__PATH] ; DXUtil^ConvertGenericStringToWide ( widef ilename, filename . c_str ( ) ) ; //загружаем этот файл hr = rn_Loader->LoadObjectFromFile ( CLSID_DirectMusicScript, IID_IDirectMusicScript8, widef ilename, (void**) &script->m__Script) ; ThrowIfFailed(hr, "LoadObjectFromFile failed. "} ; if ( Iname. length{ ) ) { name = filename; } acript->Init (name) ; return (CAudioScriptPtr (script) ) ; }
К текущему моменту вы должны быть хорошо знакомы с тем, что делает метод LoadScript. Он создает новый CAudioScript, преобразует заданное имя файла в уникодовкую строку, заставляет DirectMusic зафузить файл (задавая CLSID_DirectMusicScript и IID_IDirectMusicScript8 в качестве класса и интерфейса соответственно) и передает интерфейс, обеспечиваемый DirectMusic, в переменную m_Script объекта скрипта. Как только все это завершено, метод выставляет имя объекта скрипта и вызывает метод Init Когда Ink завершается, то возвращается новый интеллектуальный указатель на новый скрипт. Единственной тайной остается метод Init скрипта. Вот как он выглядит. void CAudioScript :: Init (string name) { m__Name - name ; DMUS_SCRIPT_ERRORINFQ err Info; HRESULT hr; errlnfo.dwSize = sizeof (DMUS_SCRIPT_ERRORINFO) ; hr = m_Script— >Init ( m_Manager->GetPerformance () , serrlnfo) ; if (FAILED [hr) ) ( Throwlf Failed (hr, As serableErrorlnfoS trine ( "m_Script->Init {) f a i l e d ! ", err I n f o ) ) ;
Метод короток и изящен. Все, что он на самом деле делает, - это вызов метода Init интерфейса аудио скрипта. Это требование DirectX Audio; прежде чем вы начнете что-либо делать с интерфейсом скрипта, вы должны его инициализировать путем указания, к какому воспроизведению он привязан. Обратите внимание на DMUS_SCR1PT_ERRORINFO объект, errlnfo. Адрес этой структуры передается как второй аргумент в IDirectMusicScript8::Init, и код передает структуру в AssembleErrorlnfoString, когда что-то идет неверно.
СЬрипты и эффе/ипы
211
Объект DMUS_SCRIPT_ERRORINFO замечателен. Когда вы применяете Init к интерфейсу IDirectMusicScriptS, вы фактически компилируете Audio VB код, так что, если в коде есть какие-нибудь ошибки, инициализация завершится с ошибкой и errlnfo будет заполнен самой разнообразной полезной информацией. Вы увидите файл, номер строки, вызвавшей ошибку, описание ошибки и компонент, который вызвал ошибку (т. е. название компонента DirectX, который конфликтует с вашим скриптом). AssembleErrorlnfo String использует информацию из переданного ему объекта DMUS_SCRIPT_ERRORINFO, чтобы создать подробную строку ошибок. Эта строка потом выводится через макрос ThrowIfFailed и показывается в окошке, аналогичном окошку на рис. 12.6.
An error has occured г this program. Error: CAudloScrehiimt: m_5c'lpt- >lf*Q f ah* 1 There «as an error in script fie "CatFroiecORuntmeFle^catsf j.jpt", Ffct CatProtecrtRiJnllmBfle^catsfx.spt, Irvs •)-. Eipectsd If Error reported by: Mirosoft Au&jVEScript iyntax error fie; CtoodriaudiObankyihlZ • More DrettX ftutfo Goodies\CrH 2pl uudra5crip»tAudioEnghe\AiKfto5cr>pt,cpp Press ABORT to end the program, RETRY to debug, IGNOBE to throw th* ягог.
Рис. 12.6. Окно ошибки, созданное AssembleErrorlnfoString
Вот как выглядит AssembleErrorlnfoString. string CAudioScript::AssembleErrorInfoString(string header, DMUS SCRIPT ERRORINFO fierrlnfo) -. stringstream str; str « header « endl; //помещаем информацию об ошибке в строку char file[DMUS_MAX_FILENAME]; char desc[DMQS__MAX_FILENAME] ; char component[DMUS_MAX_FILENAME]; DXUtil_ConvertWideStringToGeneric(file, errlnfo.wszSourceFile, sizeof(file)); DXUtil_ConvertWideStringToGeneric(desc, errlnfo.wszDescription, sizeof (desc)); DXUtil ConvertWideStringToGeneric(component, errlnfo.wszSourceComponent, sizeof(component)); str « "There was an error in script file \"" « GetNameO « "\"." « endl « endl; str « "File: " « file « ", line " « errlnfo.ulLineNumber
212
Глава 12 « ": " « desc « endl « endl; str « "Error reported by: " « component « endl; return(str.str () ) ;
}
Единственным неудобством в отношении объекта DMUS_SCRIPT_ERRORINT;1O является то, что все его строковые члены являются юникодовскими строками, поэтому вы должны преобразовывать их к обычным ASCII строкам (обычно с помощью функции DXUtil_ConvertWideStringToGeneric). Как только вы это сделали, вы получаете подробную информацию об ошибке, которую вы можете использовать на ваше усмотрение - функция AssembleErrorlnfoString выводит все в одну большую STL строку и возвращает ее.
Получение имен переменных и подпрограмм После загрузки и инициализации скрипта вы можете заглянуть внутрь него, используя методы, которые получают имена его подпрограмм и переменных. Вот как метод EnumRoutines класса CaudioScript получает список всех подпрограмм скрипта. vector<string> CAudioScript::EnumRoutines(} { vector<string> routines; int count = 0; HRESULT hr; bool done=false; while (Idone) ( WCHAR widename[MAX_PATH]; char name[MAX_PATH]; hr = m_Script->EnumRoutirie (count, widename); ThrowlfFailed(hr, " EnumRoutine failed."); if (hr == S_FALSE) done=true; else { DXUtil_ConvertWideStringToGeneric(name, widename, sizeof(name)); routines.push_back(name); count++; } } return(routines); }
Этот код идет по циклу, повторяя вызов метода EnumRoutine IDirectMusicScriptS. Этот метод имеет два параметра: номер подпрограммы, которая вас интересует, и указатель на массив юникодовских символов, который будет содержать название подпрограммы. Если заданный номер чересчур велик, то метод вернет S_FALSE.
Ckpunnibi и эффекты
213
Этот метод делает получение списка подпрограмм очень простым. Код начинает с номера ноль, вызывает EnumRoutine и смотрит на то значение, которое он вернул. Если это S_FALSE, то цикл завершается; иначе код преобразует полученное имя в обычную строку и помещает ее в вектор. Реализация метода CAudioScript::EnumVariables проведена тем же способом, за исключением того что вместо вызова EnumRoutine используется вызов EnumVariable.
Выставление значения переменных Итак, вы загрузили скрипт, инициализировали его и, может быть, познакомились с ним поближе, получив имена переменных и подпрограмм. Теперь вы хотите с ним пообщаться, выставив значения некоторых из его переменных, для подготовки к вызову одной из подпрограмм. Нам ничего не понадобится, кроме подпрограммы CAudioScript::SetVariable. void C A u d i o S c r i p t : : S e t V a r i a b l e ( s t r i n g varname, int value) { DMUS_SCRIPT_ERRORINFO e r r l n f o ; WCHAR widevarname[MAX_PATH]; DXUtil^ConvertGenericStringToWide(widevarname, v a r n a m e . c _ s t r ( ) I ; HRESULT hr = m__Script->SetVariableNurnber (widevarname, value, &errlnfo); if ( F A I L E D ( h r ) ) { ThrowlfFailed(hr, AssembleErrorlnfoString ("SetVariableNurnber failed! ", errlnfo ) ) ; '}
Выставление целочисленного значения сводится к вызову метода SetVariableNumber интерфейса IDirectMusicScripts. Методу требуется имя переменной, которую вы хотите выставить (как обычно, в формате юникода!), и ее новое значение, тогда DirectX Audio выставит значение этой переменной, если она существует (например, если в аудио скрипте эта переменная объявлена через dim), иначе заполняется структура DMUS_SCRIPT_ERRORINFO, в случае какой-либо ошибки. Совет. Вы .можете получать и выставлять строковые значения, используя процесс, аналогичный выставлению целых величин. Ищите метод SetVariableVariant в документации DirectX Audio.
Получение Величин переменных Получение значения переменных настолько же простое.
214
Глава 12 int CAudioScript : : GetVariable (string varname) { DMUS_SCRIPT_ERRORINFO errlnfo; WCHAR widevarname [MAX_PATH] ; long value=0; DXUtil_ConvertGenericStringToWide (widevarname, varname . c__str () ) ; HRESULT hr = m_Script->GetVariableNumber (widevarname, lvalue, Serrlnfo) ; if (FAILED (hr) ) { Throwlf Failed (hrr AssembleErrorlnf oString ("GetVariableNumber failed!", errlnfo)); } return (value) ; }
Метод GetVariableNumber либо помещает значение заданного в юниковских символах имени переменной в указатель на long, либо он завершается с ошибкой и заполняет структуру DMUS_SCRIPT__ERRORINFO, если что-то пошло не так.
Вызов подпрограммы После выставления значения переменных вызов подпрограммы сводится к вызову метода CallRoutine TOirectMusicScriptS, что показывает следующий код. void CAudioScript: :CallRoutine (string procname) { DMUS_SCRIPT_ERRORINFO errlnfo; WCHAR wideprocname[MAX_PATH] ; long value=0; DXUtil_ConvertGenericStringToWide (wideprocname, procname. c_str ( ) ) ; HRESULT hr = in_Script->CallRoutine (wideprocname, serrlnfo) ; if (FAILED (hr) ) { Throwlf Failed (hr, AssembleErrorlnfoString ("CallRoutine failed!", errlnfo) ) ;
И снова, если не обращать внимания на ставшую уже привычной работу со структурой errlnfo. здесь нет ничего особенного. Преобразуйте имя процедуры, которую вы хотите вызвать в строку юникодовских символов, вызовите CallRoutine, а остальное сделает DirectX! Чтобы закончить, приведу несколько последних замечаний по поводу подпрограмм аудио скрипта. Во-первых, они запускаются синхронно т. е. CallRoutine не заканчивает работу, пока не достигнет строки end sub, вызванной вами подпрограммы, так что ваш игровой код может стать жертвой бесконечных циклов и подвисаний плохо отлаженного скрипта.
Ckpunmbi и эфф&ты
215
Во-вторых, запомните, что вы не можете передавать аргументы подпрограммам аудио скрипта; все они должны быть глобальными переменными, к которым подпрограмма имеет доступ. Это подчеркивает необходимость использования соглашений для создания имен; к примеру, можно сымитировать аргументы подпрограммы, если перед именем каждой используемой подпрограммой переменной, префиксом поставить имя подпрограммы. И, наконец, вы должны знать, что аудио скрипты могут вызываться самим DirectMusic, вы можете вставить вызовы к подпрограммам аудио скрипта внутрь других объектов DirectMusic. Это не то, чего нужно бояться, просто некоторая информация для вас и вашего звукового дизайнера, которую нужно знать, так как она может быть полезной в определенной ситуациях.
Аудио ckpunm В игре Теперь у нас имеется готовый объект CAudioScript, который просто необходимо использовать в настоящей программе. Здесь приведены некоторые замечания о том, как использовать CAudioScript для завершения примера с мяуканьем кошки, который обсуждался в начале главы. Полный код этого примера содержится в Chl2pl_AudioScripts. Конечно, до примера настоящей игры этому примеру далеко, но он демонстрирует все, что вам нужно для реализации аудио скриптинга в вашей игре.
Аудио ckpunimiHa: подытожим Вы получили краткий обзор аудио скриптинга, и если до этого вы никогда не работали с интегрированием языка скриптов в игру, то вы, вероятно, узнали много нового. Если вы хотите заняться изучением аудио скриптинга подробнее, обращайтесь к документации по DirectX Audio, точнее DirectX Audio\Understanding DirectX Audio\Audio Scripts и DirectX Audio\Using DirectX AudioUJsing Audio Scripts. Также посмотрите ссылки на вашем CD.
ЭффеЬты Правильное использование эффектов реального времени в игре может много дать вашей игре. Добавьте легкое эхо, когда игрок шагает по пещере или небольшое шипение, когда сигнал связи прерывается, и ваш игрок сможет действительно погрузиться в игровой мир.
216
Глава 12
DirectX Audio обеспечивает такие эффекты как эхо (echo), фланж (flange), рефрен (chorus), искажение (distortion), бульканье (gargle) (полный список можно посмотреть в документации DirectX: DirectX AudioMJsing DirectX Audio\Using Efiects\Standard Effects). Также разработчики могут создавать собственные эффекты для использования их в своих играх. Существует два основных способа использования эффектов: их установка в DirectMusic Producer или их использование во время работы программы. Какой из двух методов использовать зависит от того, как устроен ваш игровой звук. Добавлять эффекты с помощью DirectMusic Producer удобно в том случае, если эффекты не будут меняться во время воспроизведения. Например, если вы создаете проект DirectMusic для уровня, который целиком проходит внутри пещеры, вы с легкостью можете изменить звуковую дорожку (audio path) и сделать так, чтобы все ваши эффекты воспроизводились с эффектом эха. С другой стороны, если ваш игрок собирается то входить, то выходить из пещеры, этот метод работать не будет - вам нужно будет то включать, то выключать эффект эха в текущем времени на основании, скажем, типа территории (пещера или не пещера) на текущей позиции игрока. Последующие разделы научат вас использовать оба метода.
Добавление эффеКтоБ, используя DirectMusic Producer DirectMusic Producer позволяет с легкостью добавлять эффекты. Все, что вам требуется — это создать звуковую дорожку для сегмента, к которому вы хотите добавить эффект. Это можно сделать щелкнув правой клавишей мыши на сегменте и выбрав New Audiopath. Обычно вы будете основывать вашу звуковую дорожку на стандартных значениях, таких, как стерео звук и 3D звук, так что выберите соответствующую опцию (если сомневаетесь, выбирайте обычное стерео). Появится редактор звуковой дорожки, показанный на рис. 12.7. Щелкните правой клавишей мыши на 1-128 mix group и выберите Properties. Чтобы добавлять собственные эффекты, вам нужно будет снять флаг Use Standard Buffer (рис. 12.8). После этого закройте окно Properties и просто «перетащите» эффекты, которые вам нужны, с палитры эффектов на правую сторону вашего окна редактирования, в mix group, к которой вы хотите их добавить. Вы должны заметить отобразившийся новый эффект в колонке Effects List.
Ckpunmbi и эффе&ты
217
Fit ь* *"« а*Мч и"*» tfto D Е # к 5 ' О И <ЕГ В Ш
р'нигвйГии&иж || ийкТой'"" R«*J
Рис. 12.7. Редактор звуковой дорожки ЛшпорзКЬ - user Defined Buffo Picoerties Buller Piuperltet | ; , Г Enatte^D Coiurd ;
Г i 1
Г '•' -'-
-i«•
^gorahm Г Jhaie buffer ™ih iMbei eudbpalhi
^ i'
£an _ .._
J Volume „
Рис. 12.8. Снимите фла? Use Standard Buffer, чтобы получить возможность добавлять собственные эффекты в вашу звуковую дорожку
После того как вы добавили эффект, вы можете изменять его свойства, щелкнув по нему правой клавишей мыши, выбрав Properties, затем щелкнув по Edit Effect Properties. Щелчок по этой кнопке вызовет окно Effect Properties (рис. 12.9). Также вы можете вырезать, вставлять и копировать эффекты, все изменения можно услышать в любой момент, воспроизведя объект, чью звуковую дорожку вы редактируете. Просто, не правда ли?
Глава 12
218
Echo
Caned
I
Рис. 12.9. Окно Effect Properties для эффекта эха
Добавление эффеКтоВ В теКуицем Времени Добавлять эффекты в текущем времени не так просто, как с помощью DirectMusic Producer, но все равно особых хитростей не требуется; также это научит вас еще одной необходимой методике: конкретно, как получить интерфейс к буферу DirectSound лежащему в основе звуковой дорожки. Напрямую с DirectSound вы работали в главе 2, когда вы разбирали, как можно поместить поток байтов во вторичный буфер DirectSound, смикшировать и вывести его на динамики через первичный буфер. Внутри DirectMusic происходит тот же процесс, за исключением того что поток байтов генерируется не вашим кодом, а синтезатором DirectMusic. Каждая из звуковых дорожек DirectMusic на самом деле является вторичным буфером DirectSound, и на каждый буфер могут действовать несколько эффектов. При микшировании этих вторичных буферов в первичный DirectX Audio применяет действующие эффекты. Включение или выключение эффекта в текущем времени совершается путем выбора верного буфера DirectSound и добавлением или удалением эффекта для него. Каждый эффект, примененный к буферу, имеет свои собственные параметры, структуру и интерфейс для выставления или получения определенных значений для этого эффекта. Например, если вы хотите изменить задержку для эффекта эхо,
Ckpunrrtbi и эффекты
219
вы берете интерфейс IDirectSoundFXEchoS, который вам нужен, заполняете соответствующие члены структуры параметров DSFXEcho и передаете эту структуру методу SetAllParameters интерфейса IDirectSoundFXEchoS.
Программирование эффеКтоВ DirectX Audio Для каждого эффекта существует GUID, описанный в файле dsound.h. Например, название GUID для эффекта эхо GUID__DSFX_STANDARD_ECHO. Эти GUID являются способом объяснить DirectX Audio, что за эффект вам нужен. Чтобы использовать эффект, вы делаете две вещи. Во-первых, вам нужно получить интерфейс вторичного буфера DirectSound звуковой дорожки, для которого вы хотите использовать эффект. После того как вы получили интерфейс буфера, вы вызываете его метод SetFX, чтобы применить все ваши эффекты к буферу. После того как эффект применен, вы можете получить его интерфейс и настроить его параметры. Гипотетически, так может выглядеть код этого процесса (полагаем, что audiopath - это интерфейс звуковой дорожки, к которой вы хотите применить эффект). //получение буфера directsound лежащего в основе звуковой дорожки hr = audiopath->GetQbject!nPath(DMUS_PCHANNEL_ALL, DMUS_PATH_BU FFER, 0, GUID_NULL, 0, IID_IDirectSoundBuffer8, (LPVOID*)fidirectsoundbuffer) ; ThrowlfFailed(hr, "Couldn't get DirectSound buf£er interface."); //Добавляем эффект эхо, DSEFFECTDESC dsEffect; dsSffect.dwSize = sizeof(DSEFFECTDESC}; dsEffect.dwFlags = 0; dsEffect.guidDSFXClass = GUID_DSFX_STANDARD_ECHO; dsEffect.dwReservedl = 0; dsEffect.dwReserved2 = 0; DWORD dwResults; hr = directsoundbuffer->SetFX{l, SdsEffect, &dwResults); ThrowIfFailed(hr, "Couldn't set effect."); //получаем интерфейс эффекта IDirectSoundFXEchoS* effect = NULL; hr = audiopath->GetObjectInFath(DMUSJ?CHANNEL_ALL, DMUS_PATH_BUFFER_DMO, 0, GUID_All_Objects, 0, IID__IDirectS0undFXEcho8, (LPVOID*) Seffect); ThrowlfFailed(hr, "Couldn't get effect interface."}; //выставляем параметры эффекта DSFXEcho params; params.fWetDryMix = 50; // 50% эхо (wet), 50% исходный звук (dry) params.fFeedback = 50; // 50% выхода идут на вход params.fLeftDelay = 750; // ждем 750мс, затем эхо из левого динамика
22О
Глава 12 p a r a m s . f R i g h t D e l a y ~ 500; // ждем 500мс, затем эхо из правого динамика params.iPanDelay = 0; // don't swap echo hr = ef fect->SetAHParameters { Sparams) ; //освобождаем интерфейс эффекта SAFE_RELEASE(effect); //освобождаем интерфейс буфера SAFE_RELEASE(directsoundbuffer);
В таком виде код нигде не используется - в реальности он разбросан по нескольким разным местам (о чем вы узнаете позже). Но так бы он выглядел, если бы вы работали строго с DirectX Audio; этот код показывает минимум шагов, необходимых для использования эффекта эхо для конкретной звуковой дорожки (точнее, для буфера DirectSoimd конкретной звуковой дорожки). Код начинается с получения интерфейса IDirectSoundBufferS, содержащегося в звуковой дорожке. Этот интерфейс сохраняется в directsoundbuffer. Далее, код задает структуру DSEFFECTDESC. Эта структура сообщает DirectX Audio, какого рода эффекты требуются. Метод буфера SetFX позволяет использовать несколько эффектов сразу; на вход методу подается массив структур DSEFFECTDESC, где каждая структура содержит GUID эффекта, который будет использоваться. Первый параметр для SetFX - это количество структур DSEFFECTDESC, которые вы подаете ей на вход; второй параметр - это адрес первой структуры массива. Третий параметр - это адрес массива DWORD с количеством элементов, равным количеству структур DSEFFECTDESC. Если с каким-либо эффектом возникают проблемы, то DirectX Audio помещает код в соответствующий элемент массива. Это может быть использовано для определения, что конкретно вызвало ошибку в вызове SetFX. После того как SetFX закончит работу без ошибок, эффект эхо будет приложен к звуковой дорожке, но эффект эхо будет использовать значения по умолчанию. Чтобы изменить это, код получает интерфейс для эффекта эхо, вызывая GetObjectlnPath. После этого настраиваются параметры, содержащиеся в структуре DSFXEcho, и новые значения устанавливаются для эффекта, через вызов SetAllParameters. В конце полученные интерфейсы освобождаются. Повторю еще раз, это только гипотетический код - на самом деле, различные шаги разбросаны по разным классам, о чем вы сейчас узнаете.
Добавление эффе1стоВ В aBykoBou gBiukok Для добавления поддержки эффектов в аудио движок, нам необходимо внести несколько изменений в класс CSoimdlnstance и создать несколько новых классов (по одному для каждого стандартного эффекта, поддерживаемого DirectX Audio: эхо, рефрен, искажение и т. д.).
Ckpunmbi и эффЖты
221
Когда вы закончите, вы сможете, благодаря C++ коду звукового движка, приведенному в этой главе, с легкостью использовать эффекты. Вспомним из главы 5, что, если вы хотите воспроизвести несколько звуков одновременно, вам необходим С Sound Instance для каждого экземпляра звука. CSoundlnstance inst[numinsts]; for (int q=0; q < numinsts; q++) { snd->Play(&inst[q]); Sleep(SOO);
}
Этот код, взятый из Ch5p2_SoundInstances, иллюстрирует основную идею: вы создаете несколько структур CSoundlnstance, и каждый раз, когда вы воспроизводите звук, вы передаете методу Play уникальный экземпляр, который он может использовать. Я внес некоторые улучшения, связанные с эффектами, так чтобы эффекты могли использоваться, следуя той же процедуре. Все, что вам нужно, чтобы использовать эффект, — это добавить его к экземпляру звука. Все, что будет воспроизведено для этого экземпляра звука, будет использовать этот эффект. Например, так можно проделать то же самое, что и выше, но с добавленным для каждого экземпляра звука эффектом эха. CSoundlnstance inst[numinsts]; for (int q=0; q < numinsts; q++) { CEchoEffect *echo = new CEchoEffect; //выставление параметров для эффекта эхо inst[q].AddEffeet(echo); //после использования AddEffect аудио движок получает указатель на эффект snd->Play{Sinst[q]); // with echo! Sleep(500); 1
На этом примере вы можете пронаблюдать необходимые шаги - сначала вы создаете новый класс эффекта эхо (CEchoEffect). Обычно сразу же после создания класса эффекта вы будете заполнять его член m_Params определенными параметрами для этого эффекта, но я не стал включать этот код для экономии места. После того как вы это сделали, вы вызываете метод AddEffect класса CSoundlnstance (о котором вы узнаете в последующих разделах), и эффект магическим образом применяется к этому экземпляру. Любой звук, воспроизводимый с этого экземпляра, будет иметь эхо!
222
Глава 12
Вы можете вызывать AddEffect неоднократно, добавляя другие эффекты, и если вы хотите все удалить и начать сначала, вы можете вызвать Remove A HEffects. Я не стал утруждать себя и создавать метод для удаления отдельного эффекта, потому что в играх обычно это не нужно, но если вдруг он вам понадобится, то для вас не будет ничего сложного в том, чтобы его добавить. Теперь, когда вы понимаете, как все работает с точки зрения клиента, рассмотрим, как добиться этой функциональности, изменив код звукового движка. Совет. Обратите внимание на то, что звуковой движок поддерживает только один экземпляр эффекта. Это значит, что вы не можете использовать два эффекта эхо для одной и той же звуковой дорожки. Я сделал так для того, чтобы было легче понимать код; если вы считайте нужным реализовать поддержку нескольких экземпляров одного и того же эффекта, то сделайте это!
Классы эффеКтоВ В звуковом движке каждый отдельный эффект представлен своим классом эффекта. Все эти классы эффекта получаются из обычного базового класса CEffect Он выглядит примерно так. class
CEffeet
{
public: CEffeet () ; virtual - C E f f e c t ( ) ; virtual GUID GetGuid() = 0; virtual void SetAllParameters(IDirectMusicAudioPathB *audicpath) = 0; };
Два важных метода в классе CEffeet - это GetGuid и SetAllParameters. Они являются чисто виртуальными функциями, так что они должны быть определены всеми производными классами эффекта. Метод GetGuid возвращает GUID для конкретного эффекта (например, GUID_DSFX_STANDARD_ECHO), и метод SetAllParameters выставляет все параметры класса эффекта на данной звуковой дорожке. Больше о методе SetAllParameters вы узнаете в следующем разделе. Вот класс эффекта эхо, который вы уже видели раньше. class CEchoEffect : public CEffeet ( public: CEchoEffect() ( ZeroMemory(&m_Params, sizeof(DSFXEcho)); } virtual -CEchoEffect() { } virtual void SetAllParameters(IDirectMusicAudioPathS *audiopath); virtual GUID GetGuid() { return(GUID_DSFX_STANDARD_ECHO); } DSFXEcho m_Params; 1;
Ckpunmbi и эффекты
223
Отсюда видно, что CEchoEffect является производным классом от CEffect и в нем реализуются две необходимых чисто виртуальных функции. Заметьте, что я поленился, когда писал класс, и просто сделал структуру m_Params публичным членом. Если вам хочется, вы можете защитить эту структуру и создать функции доступа для получения и выставления определенного ее члена (например, GetWetDryMix и SetWetDryMix). Также вы можете заставить конструктор заполнять структуру m_Params осмысленными величинами по умолчанию, а не просто выставлять все в ноль.
Изменения В CSoundlnstance Класс CSoundlnstance содержит несколько новых функций и переменных. class CSoundlnstance { public: //здесь только новое, чтобы.сэкономить место void AddEffeet(CEffect *effect}; void ReraoveAllSffects(); void ApplyEffectsToAudioPathO; protected: void "ClearEffectsVector(); std: :vector m__Effects; IDirectSoundBuffer8 *m_DirectSoundBuffer; }}
Во-первых, это m_Effects, вектор STL из указателей на класс CEffect. Этот вектор представляет собой все эффекты, приложенные к экземпляру. Во-вторых, это указатель на буфер DirectSound, mJDirectSoundBuffer. Этот буфер извлекается из звуковой дорожки и используется для применения эффектов, путем вызова метода SetFX. Также в классе CSoundlnstance появилось несколько новых методов. Публичный интерфейс эффектов представлен тремя методами: AddEffect, RemoveAllEffects и ApplyEifectsToAudioPath. Метод ApplyEffectsToAudioPath является методом обновления; если вы измените параметры эффекта после того, как он был добавлен, вам необходимо будет вручную вызвать эту функцию, чтобы отправить изменения эффекту. Также добавлен новый защищенный метод, СlearEffectsVector, который вызывается при деструкции или при вызове RemoveAllEffects и удаляет все указатели на эффекты в массиве m_Effects. Метод AddEffect достаточно простой. void CSoundlnstance::AddEffeet(CEffect *effect) { m_Effects.push_back(effect); ApplyEffectsToAudioPath(); I
224
Глава 12
Как можно заметить, вся его работа состоит в отправке указателя на эффект на вектор и последующий вызов ApplyEffectsToAudioPath, который и совершает всю реальную работу по применению эффекта к звуковой дорожке. Remove A HEffects так же прост. RemoveAllEffects is similarly easy: void CSoundlnstance::RemoveAllEffects() { ClearEffectsVector (); ApplyEffectsToAudioPath(); }
Метод Remove A llEffects просто очищает вектор эффектов и после этого доверяет методу ApplyEffectsToAudioPath сообщить DirectX Audio об изменении. А теперь важный метод. void CSoundlnstance::ApplyEffectsToAndioPath() { HRESULT hr; if (NULL == m_AudioPath | I NULL == m_DirectSoundBuffer) return; //деактивируем звуковую цсрожку m AudioPath->Activate(false); /7удаляем возможные эффекты hr = m_DirectSoundBuffer->SetFX(0, NULL, NULL); ThrowlfFailed(hr, "Couldn't remove effects chain."); if (m_Effects.size() == 0) { m_AudioPath->Activate(true); return; } //наш вектор эффектов не пуст, поэтому проходимся по нему //и применяем эффекты к звуковой дорожке std::vector effectdescs; std::vector effectdescresults; for (std::vector::iterator i = m_J2ffects.begin(); i != m_Ef fects. end (}; ++ij { DSEFFECTDESC edesc; edesc.dwSize = sizeof (DSEFFECTDESC) ; edesc.dwFlags = 0; edesc.guidDSFXClass = <*i)->GetGuid(); edesc.dwReservedl = 0; edesc.dwReserved2 = 0; effectdescs.push_back(edesc); effectdescresults.push_back(У) ; //фиктивная величина } hr = nv__DirectSoundBuffer->SetFX(effectdescs.size(), &effectdescs[0], &effectdescresults[0]); ThrowIfFailed(hr, "Creation of effects chain failed."); //вы можете сделать более развернутым сообщение об ошибке, если хотите
Ckpunmbt и эфф&ты
225
//после того, как все эффекты созданы, забираем их //интерфейсы и выставляем параметры for (std::vector::iterator q = m_Effects.begin(); q != m_Ef fects. end () ; +-fq) { (*q)->SetAllParameters(m_AudioPath);
} //активируем звуковую дорожку m_AudioPath->Activate(true); }
Работа кода состоит в просмотре вектора m_Effect и применения эффектов, лежащих в нем, к звуковой дорожке. Код начинается с деактивации звуковой дорожке — это обязательно, поскольку при активном пути вызов SetFX вызовет ошибку, Далее, код удаляет все уже приложенные эффекты, передавая ноль и два NULL в SetFX. Это делается для того, чтобы любые возможные эффекты были удалены из вектора m_Effects. Обратите внимание, что на этом этапе код завершает работу, если вектор m_Effects пуст. Если вектор не пуст, код проходит по нему и создает вектор структур DSEFFECTDESC. Векторы, на самом деле, являются просто массивами, так что, используя векторы, вы избегаете проблемы с необходимостью выделения памяти под массивы; вместо этого, вы просто помещаете новый элемент на вектор. Для каждого эффекта в векторе m_Effects, код запрашивает его GUID, помещает этот QUID в новую структуру DSEFFECTDESC и добавляет эту структуру к вектору effectdescs. Обратите внимание, что также на вектор effectdescresults добаштяется ноль. Тогда к окончанию прохода массив effectdescresults будет достаточного размера для SetFX. После того как проход завершен, код вызывает SetFX, передавая ей созданные векторы вместе с количеством эффектов, которые необходимо создать (иначе говоря, размер вектора effectdescresults). В коде нет детальной проверки ошибок для SetFX если он будет возвращать ошибки, то проверку можно улучшить, заставив метод посмотреть массив effectdescresults, чтобы понять, какой эффект вызвал ошибку. После того как SetFX успешно завершает работу, код снова проходит по всем классам в векторе m__Effects. Код заставляет каждый класс эффекта применять свои параметры к звуковой дорожке. После того как это сделано, код активирует звуковую дорожку и завершает работу - теперь все настроено! Теперь все, что осталось рассмотреть, - это то, как классы эффектов применяют свои параметры к звуковой дорожке.
226
Глава 12
Применение параметров эффеКтов У каждого класса эффекта есть метод SetAllParameters, который должен применять параметры, содержащиеся в этом эффекте, к DirectX Audio объекту эффекта на звуковой дорожке. Вот как выглядит код. void CEchoEffect::SetAllParameters(IDirectMusicAudioPathB *audiopath) { HRESULT hr; IDirectSoundFXEchoB* effect = NULL; //получаем интерфейс эффекта hr = audiopath->GetObjectInPath(DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER_DMO, 0, GUID_All_Objects, 0, IID_IDirectSoundFXEcho8, (LPVOID*) seffect); ThrowIfFailed(hr, "couldn't get effect interface."); //выставляем параметры эффекта hr = effect->SetAllParameters(&m_Params); //освобождаем интерфейс эффекта SAFE_RELEASE(effect);
}
Три основных этапа - это получение интерфейса объекта, вызов метода SetAllParameters этого интерфейса и освобождение интерфейса. Обратите внимание, что интерфейс получается из данной звуковой дорожки, а не из буфера DirectSound. Также обратите внимание на то, что все эффекты используют флаг DMUS_PATH_ BUFFER_DMO; отличие только в их IID. Это из-за того, что внутри DirectX Audio эффекты являются DirectX Media Objects (DMOs) - и они используются другим компонентом DirectX, таким, как DirectShow. Также они используются напрямую другими программами. Каждому классу эффекта требуется его собственная версия метода SetAllParameters, потому что каждый класс эффекта должен запрашивать свой интерфейс эффекта DirectX Audio и использовать свою структуру m_Params. Возможно, вы можете написать шаблон, чтобы избежать, в некоторой степени, повторения кода — если вам это нужно, то вперед!
Программирование эффЫипов В теКущем Времени: подытожим Вы познакомились с полным циклом разработки для одного конкретного эффекта: эффекта эхо. Код на вашем CD содержит полные реализации стандартных эффектов DirectX (например, там есть также CGargleEffect, CFlangeEffect и CParamEqEfrect). Пример программы Chl2p2_Effects показывает, как звучат некоторые из этих эффек-
Ckpunnibi и эффеЬшы
227
тов. Не стесняйтесь изменять программу, чтобы усльииать звучание других эффектов, или изменять параметры эффектов, чтобы добиться другого звучания эффектов. (Хотя, вероятно, вам проще будет это делать в DirectMusic Producer, так как он не требует новой компиляции после любого изменения параметра!)
Эффекты: подытожим Предупрежу вас: используйте эффекты экономно. Как и блики на линзах и обработка картинки, эффекты наилучшим образом проявляют себя, когда используются разумно и с определенной целью. Нередко их нужно применять с большой осторожностью. Обычной ошибкой будет посмотреть на все эти новые интересные эффекты и начать их запихивать в ваш игровой звук куда ни попадя. Воздержитесь от этого соблазна. Вместо этого, применяйте эффекты сдержанно и вовремя, и тогда ваша игра зазвучит по-настоящему замечательно. Если вы хотите больше узнать об эффектах, смотрите документацию по DirectX, точнее DirectX Audio\Using DirectX AudiottJsing Effects.
Зайлючение В этой главе вы немного познакомились с двумя различными технологиями DirectX Audio. Они ни в коей мере не являются обязательными к использованию, но в определенных ситуациях могут помочь.
Глава 13
3D aByk, используя DirectX Audio He устали ли вы от 2D звука? Не надоело вручную выставлять громкость звука и его панорамирование? Вы хотите использовать крутые эффекты (такие, как эффект Доплера) в вашей игре? Тогда читайте дальше, потому что эта глава научит вас всему этому и кое-чему еще! Эта глава познакомит вас с концепциями и методиками, связанными с 3D звуком, при использовании DirectX Audio.
Основы 3D aByka Перед тем как вы начнете изучать то, как кодировать 3D звук, вам нужно сначала потратить некоторое время на то, чтобы понять, что же такое 3D звук на самом деле. Когда вы говорите, что в игре 3D звук, что вы имеете в виду? Это примерно так же, как сказать, что игра в 3D - и для графики и для звука, игра может быть в 3D в разной степени. Очевидно, в FPS (first-person shooter - «стрелялка» от первого лица) графика в полном 3D (вероятнее всего, и звук тоже в полном 3D), но что можно сказать про RTS (real-time strategy - стратегия в реальном времени), где весь фон нарисован в 2D, а все фигурки боевой техники в 3D? Чтобы понять, что означает 3D звук, вам нужно понять основные принципы того, как человеческий мозг воспринимает звук. Ученые, изучающие восприятие звука, открыли, что ваш мозг опирается на следующие черты звука при расположении источника относительно себя. 1 Речь, конечно, идет о визуализации целиком трехмерного мира в двухмерной проекции на экран монитора, в отличии от визуализации двухмерного изображения на двухмерном же экране. В настоящее время с появлением пространственных (трехмерных) экранов возможна некоторая путаница в определениях. В любом случае, трехмерную картинку можно увидеть только на трехмерном экране, а уж никак ни на мониторах ваших компьютеров. - Примеч. науч. ред.
3D з0у&, используя DirectX Audio
229
Громкость (Loudness): чем громче звук, тем он кажется мозгу ближе. Конечно, для разных источников обычная громкость будет разной, но для конкретной звуковой выборки - чем он громче, тем ближе он будет казаться мозгу. Звуковая разница интенсивности (Interaural intensity difference): звук, идущий точно слева, все равно попадет в правое ухо, но будет уже чуть тише, что дает мозгу информацию о том, что нужно считать, что звук идет слева. • Звуковая разница времен (Interaural time difference); эта черта аналогична предыдущей; звуки, идущие слева, достигнут вашего правого уха примерно на миллисекунду позже, чем левого уха (все зависит от того, насколько далеко у вас расположены уши). • Глушение (Muffling): ваше ухо имеет такую форму, что лучше всего воспринимает звуки, источник которых расположен перед вами. Если источник звука находится за вами, то звук будет приглушенным. Чтобы усилить этот эффект, сложите руки чашечкой за ушами, так чтобы ладони смотрели вперед, и после этого расположитесь к источнику звука лицом, а затем спиной. Звук будет разным, и самое замечательное, что ученые могут рассчитать эту разницу. К счастью, DirectX Audio берет на себя заботу обо всех этих вещах. Все, что вам нужно, - это сказать, откуда в виртуальном мире идет звук, и где находятся уши игрока. Чтобы это сделать, вы будете использовать приемники (listeners) и буферы (buffers).
•
ПриемниКш и буферы В мире 3D звука DirectX Audio существуют всего два типа объектов: буферы, которые создают звук, и приемники, которые его «слышат». Если вы смотрите кино, зрители - это слушатели, но в случае 3D звука в ТНХ кинотеатре, зритель сам является «ухом». Обычно у вас будет несколько буферов и один приемник. Чтобы вычислить звук, который должен раздаться из динамиков для конкретного буфера, DirectX смотрит на позицию этого буфера относительно позиции приемника. Совет. Если в лесу падает дерево и нет никого рядом, есть ли звук падения? Если буфер 3D звука, содержащий выборку с записью звука падения дерева, начнет воспроизводиться и рядом не будет ни одного 3D приемника, то звука не будет, В 3D звуке вам нужен хотя бы один буфер и один приемник, чтобы хоть что-нибудь раздалось из динамиков. Совет. Если буфер 3D звука расположен слева от приемника, то это не значит, что звук будет исходить только из левого динамика, а правый динамик будет .молчать. В реальной жизни, если звук исходит слева от вас, то вы слышите звук обоими ушами, но звуковые волны достигают правого уха чуть позднее левого, и ваш мозг из-за этого счита-
23О
Глава 13 em, что звук исходит слева. 3D звук моделирует то, что вы должны услышать в реальной жизни, так что из правого динамика звук раздаваться будет, но он будет чуть тише и с небольшим опозданием (основанным на математической модели слухового восприятия), так что вместе оба динамика «обманут» мозг так, что он будет считать звук идущим слева. Это важный принцип 3D звука - мы не просто изменением громкость и панорамирование; мы моделируем реальные звуковые волны.
ЭффеКт Доплера ЕСЛИ вы никогда не слышали этот термин, представьте, что вы стоите рядом с железнодорожными путями. Вдалеке вы видите поезд. Он едет быстро. Как только поезд подъезжает к вам достаточно близко, он издает свисток постоянной высоты. Когда поезд проезжает мимо, кажется, что высота свистка падает. Это и есть эффект Доплера, названный в честь австрийского математика и физика Кристиана Доплера, который открыл его и первым создал его математическую модель в девятнадцатом веке. DirectX Audio может имитировать эффект Доплера, но, кроме позиций, ему также требуются скорости приемника и буфера. Звуковой фрагмент. Прослушайте фрагмент 13.1 в качестве примера эффекта Доплера.
Свойства буферов и npueMHukoB Итак, обзор всех концепций завершен и можно приступать к обзору тех переменных, которые используются DirectX Audio для правильного моделирования 3D звука.
Расположение ЕСЛИ вы не знаете, то и приемники, и буферы имеют расположение, тройку координат (х, у, z), определяющих положение в мировом пространстве. Обратите внимание, что нет никаких матриц или локальных координатных сеток; вы просто говорите DirectX Audio: «Этот буфер со звуком цепной пилы в точке (50, -50, 10) мирового пространства».
Ckopocmb Приемники и буферы двигаются с определенной скоростью. Скорости определены как направляющие векторы с определенной величиной. На практике, вы можете образовать вектор скорости, используя расположение объекта в предпоследнем кадре и расположение объекта в текущем кадре.
3D aByk, используя DirectX Audio
231
Минимальная и максимальная дистанция (moAbko для буферов) У буфера 3D звука имеются два свойства, которые определяют максимальное расстояние, с которого его слышно и насколько близко нужно к нему подойти, чтобы громкость достигла максимального значения. Эти два свойства называются максимальной и минимальной дистанцией (рис. 13.1). Приемник в точке 1 не услышит звук. Приемник в точке 2 услышит звук на пониженной громкости. Приемник в точке 3 будет слышать звук на полной громкости
Минимальная дистанция Источник звука
Рис. 13.1. Минимальная и максимальная дистанция задает DirectX Audio насколько, далеко можно услышать звук
Минимальная дистанция определяет, насколько близко вы должны быть, чтобы получить максимальную громкость буфера. Если вы подойдете ближе, то громкость останется на прежнем уровне - DirectX Audio не будет усиливать звук. Аналогично, максимальная дистанция - это дистанция, на которой звук затихает совсем. Если вы находитесь хотя бы чуть-чуть ближе, то вы можете едваедва расслышать звук; если вы находитесь на расстоянии от буфера, чуть большем максимальной дистанции, то вы не услышите ничего. Вы можете пропустить
232
Глава 13
все, вплоть до раздела о свойствах фактора дистанции, если вы хотите узнать, как DirectX Audio строит громкость между минимальной и максимальной громкостью буфера на основании интерполяции.
Направление (толъКо приемниКи) У приемника DirectX Audio также имеется направление, так что DirectX Audio знает, где у источника «перед», и может правильно заглушать звуки идущие «со спины». Направление задается двумя векторами: вектор верха и лицевой вектор. Если вы создаете FPS, то найти их несложно - вы можете получить векторы, задающие направление, от вашей камеры.
ЗБуКоБые Конусы (тольКо для буферов) Для буферов не задается направление, но задаются в некотором смысле аналогичные параметры, которые называются звуковыми конусами. Звуковой конус - это что-то похожее на световой конус направленного источника света. По существу, вы задаете внутренний и внешний конусы. Приемник во внутреннем конусе услышит звук на максимальной, возможной для его расстояния, громкости (рис. 13.2). Приемник, находящийся между внутренним и внешним конусом услышит звук, у которого дважды понижена громкость - один раз она понижается на основании его расстояния от объекта, второй раз на основании его позиции между внутренним и внешним конусом. (На самом деле, громкость может быть понижена более двух раз, так как направление приемника и некоторые другие вещи также берутся в расчет, но основная идея состоит в том, что звуковой конус, и в особенности расположение приемника между внутренним и внешним звуковым конусом, - это еще один фактор, определяющий общую громкость звука.) Совет. По умолчанию параметры звукового конуса установлены так, что внутренний и внешний конус совпадают пугач для обоих конусов равен 360°. Если вы поразмыслите над этим, то вы поймете, что когдаугол конусаравен 360°, то онуже больше не является конусам. Вместо этого, он представляет собой звуковую сферу, и у звука больше нет определенного направления, как в случае точечного источника света.
Oakmop спада (тольКо npueMHiiku) Фактор спада - это свойство, присущее только приемнику (у буферов нет фактора спада). Он определяет ослабление всех буферов, другими словами, насколько быстро звуки затухают при удалении приемника от источника звука или насколько быстро идет возрастание при приближении к источнику приемника.
3D aByk, используя DirectX Audio
233
Приемник в точке 2 или 3 будет слышать звук с пониженной громкостью, потому что, хотя эти точки и находятся на расстоянии меньше минимальной дистанции, они находятся вне внутреннего конуса. Только приемник в точке 1 будет слышать звук на полной громкости
Угол внутреннего конуса Угол внешнего конуса
Рис. 13.2. Различные свойства буфера, влияющие на громкость звука
Фактор спада DirectX Audio, на самом деле, является просто множителем, определяющим, насколько сильно идет отклонение от настоящего мира. Если фактор спада равен единице, громкости звука в игре точно соответствуют их аналогам в реальном мире. Фактор спада меньший единицы (но больший ноля — отрицательные значения фактора спада запрещены) определяет, что ослабление звука происходит медленнее в мире DirectX Audio, чем в реальном мире. Другими словами, значение фактора 0,5 означает, что вы можете слышать звуки с большего расстояния. Точнее, если звук становится неслышным на расстоянии 40 метров в реальном мире, в виртуальном мире DirectX Audio он станет неслышным на расстоянии 80 метров. Если фактор спада нулевой, то громкость не изменяется в зависимости от расстояния - вы либо слышите что-нибудь, либо нет.
234
ГлаВа13
Oakmop расстояния (moAbko приемниКи) Существует также свойство приемника, называющееся фактором расстояния. Фактор расстояния - это то же самое, что и фактор спада, т. е. просто коэффициент для единиц измерения, использующихся в игре. По умолчанию DirectX Audio предполагает, что все единицы - это метры. Поэтому если приемник находится в (10,0,0), а источник звука в (40.0,0), DirectX Audio воспринимает это как «буфер находится в 30 метрах от приемника». Вам нужно убедиться, что если DirectX Audio считает, что чтото находится на расстоянии 30 метров, то на экране это тоже будет выглядеть, как с расстояния в 30 метров. Конечно же, вы можете добиться этого, моделируя ваш игровой мир, используя метры, но, если вы уже используете какие-то другие известные единицы измерения, вам необходимо правильно подобрать этот фактор масштабирования. Например, если все ваши модели измеряются в футах (т. е. две локальные координаты стандартной линейки в вашем виртуальном мире (-1,5, 0, 0) и (1,5, О, О)), вам нужно выставить ваш фактор масштабирования равным 0,3048 (количество футов в метре). Фактор расстояния в основном используется для расчета DirectX Audio эффекта Доплера. Совея. Поначалу может показаться странным, что в DirectX Audio факторы расстояния и спада сделаны свойствами приемника, а не буфера. В конце концов, что, если вы хотите установить свой фактор расстояния для каждого буфера так, чтобы определенные буферы было слышно дальше остальных? Чтобы симулировать это, вы должны использовать звуковые конусы буфера. Считайте фактор расстояния и фактор спада глобальными множителями, которые вы можете использовать при необходимости. Фактор расстояния вы можете использовать, чтобы не заниматься растяжением/сжатием всей вашей графики, а фактор спада может быть использован для специальных эффектов. К примеру, если ваш игрок находит «Шлем Исключительного Слуха», вы можете приблизить фактор спада к нулю, чтобы изобразить сверхчеловеческие способности слуха.
Oakmop Доплера (moAbko npueMHuku) Как и факторы расстояния и спада, фактор Доплера является множителем, который масштабирует все эффекты Доплера. Это может пригодиться, если вы хотите создать ощущение скорости, не создавая при этом большого мира с быстродвижущимися объектами. Вы можете создать небольшой мир с медленными объектами, но изменить фактор Доплера так, чтобы они звучали, как будто они очень быстро двигаются.
3D sByk, используя DirectX Audio
235
РеЖим работы (толъКо для буферов) DirectX Audio позволяет вам редактировать свойства буфера, о которых вы только что узнали, в одном из двух режимов: нормальный режим и режим относительно головы (head-relative). Для каждого буфера режим работы может устанавливаться независимо. В нормальном режиме работы вы указываете расположение и направление в мировом пространстве. Это достаточно просто. В режиме работы относительно головы расположение и направление устанавливаются относительно приемника. Это значит, что если ваш приемник находится в точке (7, 3, 0) и вы говорите DirectX Audio, что буфер находится в точке (-3, 5, 0) в прямо ориентированном режиме, вы говорите, что буфер находится на три пункта левее и на пять пунктов выше приемника. DirectX Audio рассчитает позицию буфера в мировом пространстве, равную (4, 8, 0) (рис. 13.3)
у
Прямо ориентированный буфер (-3,5,0) (относительно приемника)
Приемник (7,3,0)
В режиме относительно головы координаты буфера всегда задаются относительно приемника. В этом примере окончательная позиция получилась (4,8,0). Если бы приемник находился на позиции (100, 200, 0), то результатом было бы (97, 205,0) Рис. 13.3. В прямо ориентированном режиме расположение буферов рассматривается относительно приемника
236
Глава 13
Режим работы, который больше подходит вам, зависит от звукового эффекта. Может быть, вам будет проще использовать режим относительно головы для буферов, которые постоянно будут прикреплены к приемнику (считайте, что они закрепляются прямо на голове игрока). Например, в простом симуляторе полета вы можете использовать режим относительно головы для двигателей самолета, при условии что ваш игрок не сможет повернуть голову виртуального пилота в игре только самолет целиком. Если же игрок может поворачивать голову пилота, то вы вряд ли захотите использовать режим относительно головы для двигателей.
Изменения немедленные и отложенные Каждый раз, когда вы изменяете одну из переменных буфера или приемника, DirectX Audio пересчитывает сцену и вносит изменения в конечный звук незамедлительно. Это может привести к огромным затратам ресурсов, если вы изменяете большое количество буферов, потому что каждый раз при изменении одного из буферов DirectX Audio пересчитывает все! Чтобы не тратить огромное количество ресурсов в подобной ситуации, вы можете использовать отложенные изменения. При использовании отложенных изменений, вы говорите DirectX Audio, чтобы он не проводил пересчет сцены до тех пор. пока вы не задали все новые параметры - это именно тот способ, который следует использовать, если вы изменяете все буферы и приемник - каждый кадр. Пример кода этой главы покажет вам, как использовать отложенные изменения - по существу, вы передаете флаг DS3D_DEFERRED при вызове методов, которые выставляют параметры буферов и приемника, и после этого, в самом конце, вызываете CommitDeferredSettings, чтобы изменения вступили в силу. Это добавляет немного проблем, но сохранение процессорных мощностей того стоит!
Программирование 3D aByka ЕСЛИ вы разобрались с основами 3D звука, программировать 3D звук, используя DirectX Audio, будет не очень сложно. Рассмотрим код, который позволяет внести необходимые, для поддержки 3D звука, изменения в звуковой движок.
C3DsoundListener Приемник содержится в новом классе, C3DsoundListener. Вот описание класса. class CSDSoundListener { public:
3D sByk, используя DirectX Audio
237
friend class CAudioManager; virtual ~C3DSoundListener(); void SetPosition(D3DVECTOR position) { m_Props.vPosition = position; RefreshProps(); } D3DVECTOR GetPosition() { return{m_Props.vPosition}; } void SetVelocity(D3DVECTOR Velocity) { m_Props.vVelocity = Velocity; RefreshProps{}; } D3DVECTOR GetVelocity() ( return(m_Props.vVelocity); } void SetOrient{D3DVECTOR OrientTop, D3DVECTOR OrientFront) { m_Props.vOrientTop = OrientTop; m_Props.vOrientFront — OrientFront; RefreshProps(); ) D3DVECTOR GetOrientTopO { return(m_Props.vOrientTop); } D3DVECTOR GetOrientFront() { return(m_Props.vOrientFront); } void SetDistanceFactor(float f) { m_Props.fIDistanceFactor = f; RefreshProps(); } D3DVECTOR GetOrientTopO { return(m_Props.vOrientTop); } D3DVECTOR GetOrientFront () { return (m__Props .vOrientFront) ; } void SetDistanceFactor(float f) { m_Props.fIDistanceFactor = f; RefreshProps(); } float GetDistanceFactor() { return(m_Props.fIDistanceFactor}; } void SetRolloffFactor(float f) { m_Props.flRolloffFactor = f; RefreshProps(); } float GetRolloffFactor!) { return(m_Props.flRolloffFactor); } void SetDopplerFactor(float f) { m_Props.fIDopplerFactor = f; RefreshProps!); } float GetDopplerFactor() { return(m_Props.fIDopplerFactcr); } protected: //доступно только друзьям(то есть, CAudioManager) CSDSoundListener(); CAudioManager *m_Manager; void RefreshProps(); DS3DLISTENER m_Props; IDirectSound3DListener8 *m_Listener;
}; Как видно, в классе много функций доступа к членам структуры m_Props. Структура m_Props является структурой типа DS3DLISTENER, т. е. DirectX-поддерживаемой структурой, содержащей все свойства приемника. Если не считать члена m_Props и всех связанных с ним функций доступа, все, что останется в классе C3DsoundListener, - это защищенный метод RefreshProps, указатель на CAudioManager и указатель на DirectSound интерфейс приемника, m_Listener.
238
Глава13
Обратите внимание на то, что каждый метод доступа, после установления соответствующего значения структуры, вызывает метод RefreshProps. Этот метод отправляет DirectX Audio структуру m_Props, вызывая метод SetAllParameters интерфейса приемника. void C3DSouncListener::RefreshProps(} { HRESULT hr = m_Listener->SetAllParameters(uirMProps, DS3D_DEFERRED); ThrowlfFailed(hr, "Error calling SetAllParameters"); }
Единственная интересная вещь в этом небольшом методе — это использование флага DS3D_DEFERRED. Этот флаг говорит DirectX Audio, что нужно подождать с пересчетом микширующихся буферов до вызова метода CommitDeferredSettings интерфейса приемника.
Изменения В CAudioManager Так как приемник может быть только один, я построил звуковой движок так, чтобы аудио менеджер содержал и следил за глобальным экземпляром приемника. В CAudioManager добавлен новый член m_Listener, являющийся C3DSoundListener. Остальной код обращается с классом приемника, вызывая функцию доступа GetListener. Также на CAudioManager, в первую очередь, лежит обязанность получить интерфейс приемника. После того как интерфейс получен, он передается новому экземпляру приемника путем установки его члена m_Listener. Вот как выглядит код этого - новый код является частью метода Init класса CAudioManager. //получаем буфер приемника IDirectMusicAudioPathS *defaudiopath = NULL; IDirectSound3DListener8 *listener; hr = GetPerformance()->GetDefaultAudioPath(Sdefaudiopath); ThrowlfFailed(hr, "Can't get default audio path!"}; hr = defaudiopath->GetObjectInPath(0, DMUS_PATH_PRIMARY_BUFFER, 0, GUID_NULL, 0, IID_IDirectSound3DListener8, (LPVOID*) Slistener}; ThrowlfFailed(hr, "can't get listener on default audio path!"}; //говорим нашему объекту приемника использовать этот интерфейс приемника GetListener().m Listener = listener; GetListener () .RefreshProps(); CommitSDSoundParameters(); SAFE_RELEASE(defaudiopath);
3D aByk, используя DirectX Audio
239
Код просто получает интерфейс приемника, путем получения интерфейса к звуковой дорожке по умолчанию и вызова метода GetObjectlnPath этого интерфейса. В DirectX Audio существует только один интерфейс приемника, так что не важно, из какой звуковой дорожки он получен. После того как интерфейс передатчика получен, код передает его в объект C3DsoundListener, потом вызывает метод RefreshProps этого объекта, так что объект может отправить DirectX Audio свои значения по умолчанию. Также, поскольку метод RefreshProps приемника устанавливает параметры, используя флаг задержки, код немедленно вызывает CommitsDsoundParameters, чтобы заставить DirectX Audio принять только что отправленные изменения и пересчитать сцену. После завершения всего этого код убирает за собой, освобождая интерфейс звуковой дорожки. Обратите внимание, что интерфейс приемника не освобождается; он передан объекту приемника и будет освобожден при вызове деструктора объекга. Метод Commits DsoundParameters состоит из одной строчки. void CAudioManager::Commit3DSoundParameters() { GetListener () .m_Listener->CoinmitDef erredSettings () ; }
Этот метод просто вызывает метод CommitDeferredSettings интерфейса приемника.
C3DsoundEffect С одним новым классом разобрались, еще один остался. C3DsoundEfTect представляет буфер 3D звука. class CSDSoundEffeet : public CDirectMusicSegment public: . C3DSoundEffeet{CAudioManager *mgr); virtual -CSDSoundEffeet() ; //подменяем функцию сегмента, чтобы воспроизводить нашу //звуковую дорожку virtual bool Play() { return(CDirectMusicSegment::Play(NULL, false, m_AudioPath)); ) virtual bool Play(CSoundlnstance *newinst, bool control) ( return{CDirectMusicSegment::Play(newinst, control, m_AudioPath)}; } virtual bool PlayAsPrimary(CSoundlnstance *newinst) { return(CDirectMusicSegment::Play(newinst, true, m_AudioPath)); } //свойства //функции доступа опущены для экономии места
24О
Глава 13 protected: void RefreshProps{); DS3DBUFFER m_Props; IDirectMusicAudioPathB *m_AudioPath; IDirectSound3DBuffer8 *m_3DBuffer;
};
И снова большая часть класса является функциями доступа к структуре m_Props (для этого класса она типа DS3DBUFFER). Тем не менее, есть и новые вещи. Во-первых, обратите внимание, что C3DsoundEffect получается из CDirectMusicSegment, но при этом он подменяет метод Play класса CDirectMusic Segment своим. Я сделал так, потому что буфер 3D звука требует обязательно обеспечить 3D звуковую дорожку для каждого воспроизведения звука. Это означает, что мне потребовалось обратиться к CDirectMusic Segment и коечто изменить так, чтобы вы могли передавать свой собственный указатель на звуковую дорожку и чтобы, если вы его передали, то CSoundlnstance, который в конце концов был создан, не освободил бы указатель на звуковую дорожку. Я не собираюсь построчно объяснять эти изменения, но вы должны знать, что я их произвел. Это означает, что класс C3DSoundEffect должен создать и управлять своей собственной звуковой дорожкой. Вот как выглядит создание звуковой дорожки (взято из конструктора C3DSoundEffect). //создаем нашу собственную звуковую дорожку HRESULT hr = m_Manager->GetPerformance()->CreateStandardAudioPath( DMUS_APATH_DYNAMIC_3D, CAudioManager::NUMCHANNELS, TRUE, &m_AudioPath); ThrowlfFailed(hr, "Couldn't create 3D audio path!"); //получаем буфер 3D звука из новой звуковой дорожки hr = m_AudioPath->GetObjectInPath(DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER, 0, GUID_NULL, 0, IID_IDirectSound3DBuffer8, (LPVOID*)&m_3DBuffer); ThrowlfFailed(hr, "Couldn't get buffer from audio path!"); Этот код создает стандартную 3D звуковую дорожку (обратите внимание на использование флага DMUS_APATH_DYNAMIC_3D), а после запрашивает из него буфер 3D DirectSound. У C3DSoundEffect также имеется метод RefreshProps, который идентичен методу приемника, за исключением того что он работает с буфером DirectSound, а не с приемником.
3D aByk, используя DirectX Audio
241
Пример программы про дальний Космос Для этой главы я хотел создать пример программы, которая бы демонстрировала работу звука в настоящем 3D мире. Конечно, я мог бы просто сделать консольное приложение, но на его примере я бы не объяснил, как вставить 3D звук в вашу игру. Чтобы полностью понять 3D звук, вам необходимо увидеть код в его родной среде в работе, бок о бок с кодом 3D. графики, над созданием реалистичного мира. Поэтому я обокрал сам себя. СЫЗр! основан на примере программы, которую я создал для своей последней книги Special Effects Game Programming Using DirectX 8.0. Те, кто читали эту книгу, без труда опознают в программе немного измененный вариант Ch20p3_ComplexExplosion - примера программы главы 20. Я не буду тратить время на объяснение того, как работают основы 3D графики этого примера программы, но чтобы правильно понимать, что происходит в примере, вы должны иметь представление о таких вещах, как небесный куб (skyboxes), матрицы преобразования мирового пространства (world transform matrices) и X файл мешей. Между прочим, если вдруг вам понадобилась хорошая книга о спецэффектам, то моя книга... стоп, о чем это я? Вы абсолютно правы! Вы ПОКУПАЛИ МОЮ КНИГУ не для того, чтобы слушать про другую книгу, что больше похоже на ненавязчивую рекламу! Ладно, забудем, я продолжу. Запустите приложение и вы увидите что-нибудь похожее на рис. 13.4. По существу, вы находитесь в дальнем космосе и вы можете свободно перемещаться во всех направлениях. Перед вами космический корабль, издающий два 3D звука: звук двигателей (engine.wav), который идет по циклу, и зуммерный сигнал (beep.wav), который повторяется периодически. С помощью клавиш W, A, S и D вы можете перемещаться вперед, поворачиваться налево, лететь назад и поворачиваться направо, соответственно. Клавиши Q и Z будут перемещать вас вверх и вниз, клавиша В отвечает за включение/выключение зуммерного сигнала (он быстро надоедает), клавиша G отвечает за включение/выключение вашего плазменного двигателя (он позволяет вам двигаться быстрее и явственнее слышать эффект Доплера). Если вы немного полетаете в этом примере программы, вы сможете услышать 3D звук во всей красе. Если вы ничего не заметили, обратите внимание на то, что при приближении к кораблю громкость звука увеличивается, выполняется панорамирование и кажется, что звук на самом деле идет от корабля. Также обратите внимание на эффект Доплера; вы должны слышать, как изменяется частота звука двигателя и зуммерного сигнала, когда вы летаете вокруг корабля.
Глава 13
242
Рис. 13.4. Картинка примера программы этой главы
Пример программы Chl3pl_3Dsound основан на примере каркаса приложения D3D, что означает, что все действие происходит внутри объекта CMyD3Dapplication. Различные члены этого объекта вызываются в соответствующее время примером каркаса приложения D3D.
ЗагрузКа aBykoB В методе OneTimeScenelnit объекта CMyD3Dapplication присутствует часть кода, которая отвечает за загрузку wave файлов с диска и установку их поведения по умолчанию. т_Веер = GetAudioManager{)->Load3DSound("beep.wav"); m_Engine = GetAudioManager()->Load3DSound("engine.wav"}; CDirectMusicSegrnent *engineseg = dynamic_cast ( m _ E n g i n e . G e t ( } ) ; • engineseg->SetRepeats{DMUS SEG REPEAT INFINITE);
3D sByk, используя DirectX Audio
243
II задаем диапазон нашего двигаеля и зуммера C3DSoundEffect *beep = dynamic_cast{m_Beep.Get()); C3DSoundEffect *engine = dynamic_cast(m_Engine,Get()); if (beep) { beep->SetMaxDistance(100); beep->SetMinDistance(10); !
if (engine) { engine->SetMaxDistance(80); engine->SetMinDistance(10); ) GetAudioManager(}->Commit3D5oundParameters();
Этот кусок кода загружает два wave файла в два 3D звуковых эффекта: m_Engine и т_Веер. После этого код устанавливает некоторые параметры 3D звука для этих объектов - обратите внимание, что код устанавливает такие настройки, что звук двигателя исчезает на расстоянии в 80 единиц, а звук зуммера можно слышать еще на протяжении 20 единиц. Оба звука достигают максимальной интенсивности, когда вы на расстоянии в 10 единиц или меньше от корабля. Заметьте также, что код помещает m_Engine в указатель на CDirectMusicSegment и после вызывает новый метод, SetRepeats, зацикливая звук двигателя бесконечно. Вся работа функции SetRepeats сводится к вызову метода с тем же названием интерфейса IDirectMusicSegment. После того как все параметры установлены, код заставляет аудио менеджер принять их и все готово к началу игры.
Обновление буферов и приемниКа Каждый кадр позиции буфера и приемника должны обновляться, вместе с рядом других свойств. Вот код обновления, взятый из метода FrameMove CMyD3DApplic ation. m_BeepTimer += m__fElapsedTime; if (m_BeepActive fi& m_BeepTiraer > BEEPINTERVAL) { m_Beep->Play(); m_BeepTimer = 0; } //устанавливаем позицию зуммера на позицию корабля CSDSoundEffect *beep = dynamic_cast(m_Beep.Get()); if (beep) { D3DVECTOR v; v.x = 0; v. у = 4 ;
244
Глава 13
v.z = 0; beep->SetPosition(v); 1 //устанавливаем позицию приемника на позицию камеры C3DSoundListener ^listener = GetAudioManager()->GetListener(); listener.SetPosition(m_Camera.GetPosition()); //устанавливаем направление приемника D3DXVECTOR3 lookat = D3DXVECTOR3( mjCamera.GetViewMatrix(}._13, m_Camera.GetViewMatrix()-_23, m_Camera.GetViewMatrix{)._33); D3DXVECTOR3 up = D3DXVECTOR3( m__Camera. GetViewMatrix () ._12, m_Camera.GetViewMatrix() .__22, m_Camera.GetViewMatrix()._32); D3DXVECTOR3 nlookat, nup; D3DXVec3Normalize(&nlookat, Slookat); D3DXVec3Normalize(&nup, &up); if (nup == nlookat) { nup = D3DXVECTOR3(0,1,0); } listener.SetOrient(nup, nlookat); //устанавливаем скорость приемника на основании позиции в предыдущем кадре и //текущей позиции m_ListenerVelocity.x = m_Camera.GetPosition{).х - m_LastListenerPosition.х; m_ListenerVelocity.у = m_Camera.GetPosition().у - mJLastListenerPosition.у; m_ListenerVelocity.z = m_Camera.GetPosition().z - m_LastListenerPosition.z; listener. SetVelocity (m_Lister,erVelocity) ; mJLastListenerPosition - m_Camera.GetPosition(); listener.SetDopplerFactor(DS3D_MAXDOPPLERFACTOR); listener.SetDistanceFactor(10); GetAudioManager()->Commit3DSoundParameters(); Этот код совершает много действий, но ни одно из них не является сверхсложным. Он начинается с увеличения значения таймера и определения, не нужно ли сейчас воспроизводить звук зуммера. Далее код устанавливает позицию буфера 3D звука зуммера равной текущей позиции корабля (которая жестко определена как (0,4,0)). Он делает то же самое для приемника и после переключается на установку направления приемника. К счастью, класс камеры содержит матрицу вида, так что нужно всего лишь извлечь векторы «верха» и «взгляда», нормализовать их и после вызвать SetOrient, чтобы сделать их вектором верха и вектором «переда» нашего приемника. После этого, код рассчитывает скорость приемника, рассматривая позицию камеры в текущем кадре и вычитая из нее позицию камеры в предыдущем кадре.
3D aByk, используя DirectX Audio
245
И в конце небольшая хитрость. Код выставляет фактор Доплера в максимум и умножает расстояния на 10, для того чтобы заставить эффект Доплера выделяться (поэтому вы можете услышать его). Поэкспериментируйте с различными значениями для этих двух факторов, чтобы почувствовать, как создать конкретное звуковое окружение в вашей игре. Код завершает работу, подтверждая все внесенные в параметры изменения. Вот и все!
Заключение Я думаю, в конечном счете, самая сложная часть в реализации 3D звука - это изучение основ и правильного пути в работе с 3D буферами DirectSound напрямую, минуя DirectMusic и интерфейсы. Слава Богу, изучив эти концепции и методики, вы освободили себя от написания огромного количества кода, реализуя необходимые математические процессы, чтобы создать иллюзию существования звука в реальном, 3D мире. Не потому, что требуемая математика скучна - на самом деле она великолепна, и если вам интересно, то вы можете узнать больше, пройдя по ссылкам, приведенным на вашем CD. Теперь у вас есть понимание основ 3D звука. В следующей главе вы разовьете это знание, изучив, как использовать определенные API, которые могут проводить продвинутую обработку 3D звука, такую, как учет препятствий и отражений.
Глава 14
3D aByk, используя OpenAL В предыдущей главе вы увидели удобство использования 3D звука в задаче создания действительно правдоподобного 3D мира. Правда, 3D мир был достаточно скучный, только с одним объектом, но все же, вы поняли основную идею. В этой главе вы узнаете, как сделать то же самое, не используя DirectX. Вместо него, вы будете использовать библиотеку, которая называется OpenAL или Open Audio Library. OpenAL - это API, созданный группой людей и компаний в качестве открытого, межплатформенного API для 3D звука. Он является API-братом для OpenGL, работающим с 3D графикой, и дизайн и соглашения, которые используются в OpenAL, такие же, как и в OpenGL. На данный момент поддержкой OpenAL занимается, в основном, компания Creative Labs. Они, вместе с ныне умершей компанией Loki Entertainment Software, возглавили работу над OpenAL, и сегодня вы можете найти OpenAL SDK, расположенными на их сайте (developer.creative.com). OpenAl является очень хорошо написанным межплатформенным API, созданным для реализации одной вещи - 3D звука. И, вероятно, вы можете создать весь свой игровой звук на основе OpenAL, при условии что вы не сильно используете динамическую музыку (или не против написать код, который обеспечит ее поддержку в OpenAL).
3D sByk, используя OpenAL
247
Различия меАду OpenAL и 3D aBykoM В DirectX Audio ЕСЛИ вы не изучали основы 3D звука в главе 13, вы должны на время прекратить чтение этой главы, вернуться назад и изучить основы. У OpenAL и реализации DirectX Audio 3D звука 90 % понятий являются общими.
Источники, буферы и приел/imiku Вся работа OpenAL основана на трех основных типах объектов: источники, буферы и приемники. Понятие приемников в OpenAL и DirectX Audio аналогично; в OpenAL по-прежнему может быть только один приемник, который представляет собой уши игрового персонажа. Тем не менее, в OpenAL имеется новый объект- источник, а также иное значение термина «буфер». В OpenAL буфер - это выборочные РСМ данные, а источник описывает конкретный экземпляр буфера, позицию, из которой «вещает» буфер (рис. 14.1).
Буферы OpenAL
Буфер 2 содержит "carengine.wav"
Буфер 1 содержит "helicopter.wav"
Л Источники OpenAL
Источник I Вертолет 1 Позиция (-173, 264,525) ID Буфера = 1
Источник 2 Вертолет 2 Позиция (-856,365, 45) ID Буфера = 1
Рис. 14.1. Несколько источников могут ссылаться на один буфер
Источник 3 Вертолет 1 Позиция (-62,5, 89) ID Буфера = 2
248
Глава 14
OpenAL использует такие объекты, так что если, например, у вас в игре есть три разных вертолета, то вам не нужно загружать три копии WAV файла вертолета в память. Вы загружаете WAV в буфер и создаете три источника, которые ссылаются на этот буфер. С другой стороны, оба API имеют такую возможность. В OpenAL у вас есть несколько источников, ссылающихся на один буфер, который содержит звуковые данные. В DirectX Audio у вас есть «аудио пути»; можно сказать, что буфер OpenAL эквивалентен сегменту DirectMusic и что источник OpenAL эквивалентен «аудио пути» этого сегмента.
Больше не СОМ DirectMusic API управляется СОМ, что значит, что вы привыкли работать с получением и освобождением интерфейсов. OpenAL не СОМ. Так как этот интерфейс межплатформенный, он полагается на хорошо себя зарекомендовавший для API метод - большой список функций, перечислений (enum) и структур. OpenAL (и OpenGL в этом смысле тоже) придерживается строгого соглашения по наименованиям своих функций, Если вы посмотрите на описание API, то обнаружите несколько функций, чьи имена практически совпадают - за исключением того, что одна оканчивается на f, другая на fv и третья может оканчиваться на i. Окончания соответствуют типам данных, которые функция принимает в качестве своих аргументов. Например, существуют четыре функции с именами alListenerf, alListener3f, alListenerfv и alListeneri (кстати, названия всех функций OpenAL начинаются с al). Все эти четыре функции делают одно и то же: они устанавливают свойства приемника. Тем не менее, alListeneri'требует один аргумент с плавающей точкой, alListener3f требует три таких аргумента (вектор), alListenerfv требует массив аргументов с плавающей точкой (вектор, в смысле понимания этого слова в C++) и alListeneri требует целочисленный аргумент., Это может показаться странным, но как только вы с этим разберетесь, вы быстро привыкнете к таким именам функций, и это поможет вам предотвратить неразбериху с типами данных.
3D aByk, используя OpenAL
249
Свойства буфера Доступные свойства буфера собраны в табл. 14.1. Как можно увидеть, они все связаны со свойствами wave файла, загруженного в буфер. Таблица 14.1. Свойства буфера OpenAL Свойство
Описание
AL.FREQUENCY
Частота WAV файла
AL.BITS
Биты на выборку WAV файла
ALCHANNELS
Количество каналов (1 =моно, 2=стерео). Обратите внимание, что если этот параметр больше единицы, то буфер будет звучать не соответственно позиции (другими словами, если вы хотите, чтобы звук рождался оттуда, откуда должен, используйте моно выборки}
ALSIZE
Размер (а байтах) буфера
AL.DATA
Адрес памяти, откуда первоначально были загружены данные (вряд ли понадобится, так как, скорее всего, вы освободили память сразу же после того, как данные были скопированы в буфер)
Свойства источниКа OpenAL предоставляет несколько свойств его источников (табл. 14.2). С большинством из них вы познакомились в предыдущей главе, но все же имеются некоторые новые. Таблица 14.2. Свойства источника OpenAL Свойство
Описание
AL_PITCH
Частотный множитель для источника (всегда больше нуля). Величины между нулем и единицей понизят частоту; величины больше единицы увеличат
AL_GAIN
Громкость источника. Величины между нулем и единицей понизят громкость; величины больше единицы увеличат
AL_MAX_DISTANCE
Расстояние, на котором приемник перестает слышать источник
AL_ROLLOFF_FACTOR
Множитель спада для данного источника. Обычно равен единице, но может быть уменьшен до величин между нулем и единицей, чтобы ослабить спад, и увеличен до величин больше единицы, чтобы усилить спад
AL_REFERENCE_DISTANCE
Расстояние, на котором громкость источника уменьшится наполовину (без учета фактора спада)
ГлаВа 14
250 Таблица 14.2. Свойства источника OpenAL Свойство
Описание
AL_MIN_GAIN
Минимальное усиление (громкость) данного источника
AL_MAX_GAIN
Максимальное усиление (громкость) данного источника
AL_CONE_OUTER_GAIN
Громкость вне звукового конуса данного источника
AL_CONE_INNER_GAIN
Громкость внутри звукового конуса данного источника
AL_CONE_OUTER_ANGLE
Угол звукового конуса. По умолчанию равен 360° , что совершенно не соответствуют понятию конуса
AL_POSITION
Вектор из 3 величин с плавающей точкой, определяющий расположение источника
ALVELOCITY
Вектор из 3 величин с плавающей точкой, определяющий скорость источника (используется для эффекта Доплера)
AL_DIRECTION
Вектор из 3 величин с плавающей точкой, определяющий направление звукового конуса, при условии, что источник не точечный
AL SOURCE RELATIVE
Если выставлено значение ALTRUE, расположение источника рассматривается относительно приемника (аналогично прямоориентированному режиму в DirectX Audio)
AL LOOPING
Если выставлено значение ALTRUE, то источник воспроизводится по циклу
AL BUFFER
ID воспроизводимого источником буфера
AL SOURCE STATE
Текущее состояние источника (ALJNITIAL, AL_STGPPED, AL_PLAYING,orAL_PAUSED)
AL BUFFERS QUEUED
Число буферов, поставленных в очередь на воспроизведение на этом источнике (это свойство только для чтения -- вы можете выставить его неявно, вызывая alSourceQueueBuffers)
AL BUFFERS PROCESSED
Число поставленных в очередь буферов, которые уже были воспроизведены (опять же, это свойство только для чтения)
Вы можете устанавливать свойства источника., используя функции OpenAL alSourcef, alSourcefV, alSource3f и alSourcei. Получить свойства можно, вызывая alGetSourcef, alGetSourcefv, alGetSource3f и alGetSourcei, в зависимости от типа данных свойства.
3D a&yk, используя OpenAL
251
Совет. Будьте внимательны - OpenAL использует левостороннюю систему координат, тогда как в DirectSD система правосторонняя. Говоря другими словами, в OpenAL ось z выходит its экрана, а в DirectsD ось z уходит в экран.
CBoucmBa приел/iiiuka Приемник OpenAL обладает несколькими свойствами, которые собраны в табл. 14.3. Таблица 14.3. Свойства приемника OpenAL Свойство ALGAIN AL_POSITION
Описание Коэффициент усиления громкости {нечто вроде регулятора громкости; выставьте его выше единицы, и все звуки будут усилены, выставьте его между нолем и единицей, и громкость всех звуков понизится) Вектор из 3 величин с плавающей точкой, определяющий расположение источника
AL_VELOCITY
Вектор из 3 величин с плавающей точкой, определяющий скорость источника для эффекта Доплера
AL_ORIENTATlON
Два вектора; 6 величин с плавающей точкой, определяющие направление источника. Первые три величины формируют вектор взгляда, последние три величины формируют вектор верха
Вы можете установить эти свойства, вызвав семейство функций alListener (alListenerf, alListener3f, alListenerfv и alListeneri). Получить их значения можно, вызвав alGetListenerf, alGetListener3f, alGetListenerfv или alGetListeneri.
Добавление OpenAL 0 зВуКоБой gBiukok Добавление поддержки OpenAL в звуковой движок достаточно прямолинейно. Этот раздел объяснит вам, как инсталлировать OpenAL SDK и с его помощью создать несколько новых AudioEngine классов.
252
Глава 14
Инсталляция OpenAL Чтобы начать инсталляцию OpenAL SDK, зайдите в директорию с полезными программами на вашем CD и дважды щелкните мышью на файле OpenALSDK.exe, содержащемся в папке с полезными программами OpenAL. Следуйте указаниям установщика, задав в качестве директории инсталляции C:\prograra files\creative labs\OpenAL 3.0 SDK. SDK включает в себя несколько примеров программ и некоторую документацию, с которыми вы можете познакомиться позже. Сейчас, для использования OpenAL, все, что вам нужно сделать, - это убедиться в том, что директории Include и Lib OpenAL прописаны в соответствующих поисковых путях проектов, которые используют OpenAL. Пример программы этой главы будет искать include файлы OpenAL в директории ..\..\OpenAL 1.0 SDKUncIude, a lib файлы в директории .A.AOpenAL l.OSDKMib. Совет. Если вы используйте OpenAL, то включайте в ваш игровой дистрибутив редистрибутивы OpenAL, которые вы можете найти в директории dll\Redist вашего OpenAL SDK. Эти файлы позволят всем, кому это необходимо, установить в систему run-time файлы OpenAL.
Cmpykmypa библиоте!ш OpenAL OpenAL SDK разделен на две библиотеки. Первая, основная библиотека OpenAL (OpenAL32.1ib), и вторая, OpenAL Utility Library (ALut.lib). Использование OpenAL Utility Library необязательно, но чаще всего вы будете ее использовать, потому что она делает работу с OpenAL чуть проще. Тем не менее, все функции OpenAL разбиты на три группы. Большинство функций относятся к категории основных функций OpenAL (core category). Все основные функции OpenAL начинаются с al. Тем не менее, существует ряд функций, относящихся к категории вспомогательных функций OpenAL (utilities category) и начинающихся с alut, и ряд контекстных функций, которые здесь обсуждаться не будут, относящихся к категории контекстных функций OpenAL (начинаются с ale). Контекстные функции удобны при работе более чем с одним аудио устройством, или если вам нужен полный контроль над единственным используемым аудио устройством. Большинству игр не нужно беспокоиться о таких вещах, вместо этого они используют OpenAL Utility Library, чтобы автоматически выбрать наилучший контекст и использовать его.
3D 3&yk, используя OpenAL
253
Новые 1слассы OpenAL AudioEngine Поддержка OpenAL звуковым движком подразумевает создание ряда новых классов. Первый и самый важный - это COpenALManager, новый класс, который является OpenAL аналогом класса CAudioManager. Я сделал все так, чтобы часть звукового движка, использующая OpenAL, была полностью независима от остальных частей, использующих DirectX. Вот описание COpenALManager. class COpenALManager { public: COpenALManager(); virtual -COpenALManager(); virtual void Init{); virtual void Dnlnitt); COpenALBufferPtr CreateBufferAndLoadWave(std::string filename); COpenALSourcePtr CreateSource(COpenALBufferPtr linktobuffer); COpenALListener SGetListener() { return(m_Listener); ) protected: static COpenALListener m_Listener;
};
В нем нет ничего особенного - просто методы инициализации и деинициализации OpenAL, два метода для создания буферов и источников и приемник OpenAL, доступ к которому осуществляется через метод GetListener. Код, который использует OpenAL часть звукового движка, отвечает за работу с классом COpenALManager, так же, как и за обеспечение функции доступа к нему (как и для CAudioManager). Остальные новые классы - это COpenALBuffer, COpenALSource и COpenALListener, со всеми которыми вы познакомитесь в последующих разделах. Обратите внимание, что и COpenALBuffer, и COpenALSource используют новый класс интеллектуальных указателей (COpenALBufferPtr и COpenALSourcePtr). Эти интеллектуальные указатели аналогичны тем, с которыми вы встречались ранее; они получаются из шаблона CrefCountPtr и созданы для работы с COpenALBuffer и COpenALSource. Эти интеллектуальные указатели освобождают нас от обязанностей по удалению буферов и источников, когда мы больше в них не нуждаемся - указатели сделают это автоматически.
Глава 14
254
Инициализация и деинициализация OpenAL После того как вы установили OpenAL и получили краткие сведения о новой структуре классов, вы можете начать учиться писать код, который использует OpenAL. Посмотрите на рис. 14.2, который показывает общую идею работы с OpenAL.
Установка '
г
ь
Создание
•
3appraWAV
Установка
Инициализация OpenAL
приемника
. ,це инициализаций OpenAL
Привязка
1 |»
Инициализация игры
*"
источника
Загрузка уровня
Обсчет
Процесс игры
Завершение игры
Рис. 14.2. Обычная работа с OpenAL
Любой код, использующий OpenAL, должен начинаться с инициализации библиотеки OpenAL и заканчиваться ее деинициализацией. Самый простой способ проделать это состоит в вызове двух вспомогательных функций OpenAL alutlnit и alutExit. Вот как выглядит код методов Init и Unlnit класса COpenALManager. void COpenALManager: : Init {} alutlnit(0, NULL); alGetError ( ) ; //очистка кода ошибки void COpenALManager: : Unlnit () { alutExit (} ; }
Функция alutlnit требует два параметра (argc и argv), но на данный момент они не используются, так что вы можете просто передать 0 в качестве argc и NULL в качестве argv. Функция alutExit не требует никаких параметров. Обратите внимание, что код очищает значение ошибки сразу же после инициализации OpenAL. Это приводит нас к нашей следующей теме, как проверять ошибки в OpenAL.
3D aByk, используя OpenAL
255
FIpoBepka ouiuSok В OpenAL При вызове функций OpenAL существует два основных способа проверки ошибок. Первый, и наиболее очевидный, состоит в том, что некоторые функции вернут величину, говорящую о наличии ошибки (если функция возвращает AL_FALSE, то что-то пошло не так). Второй способ состоит в использовании функции alGetError, позволяющую выяснить, вызвало ли то, что вы сделали, ошибку. Функция alGetError возвращает одну из величин, заданных перечислением; наилучшим вариантом является AL_NO_ERROR, но, если что-то вызвало ошибку, возможны и другие значения, показанные в табл. 14.4. Таблица 14.4. Коды ошибок OpenAL Возвращенная величина ALJNVALID_NAME
Что она означает Вы задали на вход недопустимое имя
AL_INVALID_ENUM
Вы передали целочисленное значение на вход функции, которая ожидала величину, заданную перечислением
ALJNVALID_VALUE
Величина, которую вы передали, не имеет смысла или недопустима (например, вы передали отрицательное число в качестве частоты источника)
AL_INVAUD_OPERATION
Вы вызвали что-то в недопустимое время или недопустимым образом
AL_OUT_OF_MEMORY
Распределение памяти вызвало ошибку
Создание и уничтожение буферов Первая вещь, которую вы обычно делаете при настройке звука в вашей игре, это загрузка необходимых вам WAV файлов и создание буферов. Это совершается с помощью вызова метода COpenALManager с длинным, но очень подробным, именем CreateBufferAndLoadWave. Вот как он выглядит. COpenALBufferFtr COpenALManager::CreateBufferAndLoadWave( std::string filename) {
COpenALBuffer *newbuffer = new COpenALBuffer() ; unsigned int newid; alGenBuffers(1, &newid); if (alGetError (} !- AL_NO_ERROR) { Throw("Error generating AL buffer.");
256
Глава 14 newbuffer->m_ID = newid; newbuffer->LoadWave(filename); return(COpenALBufferPtr(newbuffee)); }
Работа кода состоит в создании нового экземпляра COpenALBuffer и последующем вызове alGenBuffers. Функция alGenBuffers требует два аргумента: число буферов, которые вы хотите создать (в данном случае, один), и указатель на массив целочисленных величин (или толью на одну целочисленную величину, если вы создаете один буфер), который будет содержать OpenAL IDs для нового буфера (или буферов). В нашем случае, код создает один буфер и сохраняет его ID в переменной newid, которая потом передается новому классу COpenALBuffer. После того как класс получил ID, менеджер OpenAL вызывает функцию буфера LoadWave, которая загружает указанный WAV файл в буфер. После того как это сделано, код создает и возвращает новый интеллектуальный указатель на новый буфер. Вот как выглядит LoadWave. void COpenALBuffer::LoadWave(std::string filename) {
unsigned char *data = NULL; alutLoadWAVFile(const_cast(filename.c_str{)), &m_Format, (void **}&data, Sm__Size, fim_Freq, &m._Loop) ; //копируем данные в буфер alBufferData(m_ID, m_Format, data, m_Size, m_Freq); if (alGetErrorO != AL_NO_ERROR) { alutUnloadWAV(m_Format, data, m_Size, m_Freq); Throw("Error loading WAV data into buffer."); } //освобождаем начальные данные alutUnloadWAV(m_Format, data, m_Size, m_Freq}; } Вся работа делается вспомогательной функцией OpenAL alutLoadWAVFile. Эта функция требует два параметра - название файла и адрес указателя. Она получает память, загружает WAV файл в эту память, устанавливает указатель на эту память и возвращает свойства WAV файла. Все, что он получает, код передает функции alBufferData, которая копирует звуковые данные из data в буфер OpenAL, заданный своим ID (m__ID). После того как это сделано, начальные WAV данные выгружаются с помощью вызова вспомогательной функции OpenAL alutUnloadWAV. Когда в буфере больше нет необходимости., связанный с ним интеллектуальный указатель (COpenALBufferPtr) берет на себя работу по удалению объекта. Как часть процесса удаления, будет вызван деструктор COpenALBuffer. Деструктор выглядит так.
3D aByk, используя OpenAL COpenALBuf f e r : : -COpenALBuf f e r ( ) { if ( a l I s B u f f e r ( m ID) == AL_TRUE) }
257
aiDeleteBuf fers (1,
&m__ID) ;
По существу, он спрашивает у OpenAL, существует ли его ID (m_ID), и если он существует, то он удаляется через вызов функции alDeleteBufYers.
Создание и уничтожение источниКоВ После того как буферы были созданы и в них загружены WAV файлы, пришло время создать несколько источников, которые будут ссылаться на эти буфера. Это делается путем вызова метода CreateSource класса COpenALManager. COpenALSourcePtr COpenALManager: : CreateSource ( COpenALBufferPtr linktobuf f er) (
COpenALSource *newsource = new COpenALSource (); unsigned int newid; alGenSources (1, snewid) ; if {alGetError !) != AL_NO_ERROR) { Throw ("Error generating AL source."); }
news cure e->m_ID = newid; newsource->LinkToBuf fer (linktobuffer) ; return (COpenALSourcePtr (newsource) } ; }
Схема здесь такая же, как и при создании буфера. Код создает новый класс COpenALSource, вызывает alGenSources для создания источника OpenAL, получает ID от этой функции и передает его новому классу и после вызывает метод класса LinkToBuffer, чтобы указать источнику на буфер, который необходимо использовать. Код завершает работу, возвращая новый интеллектуальный указатель на новый источник. Метод LinkToBuffer класса COpenALSource состоит исключительно из выставления свойства AL_BUFFER для данного источника, void COpenALSource: : LinkToBuffer (COpenALBufferPtr linkto) {
alSourcei{m_ID, ALJ3UFFER, linktO->GetID ( ) ) ; if ( a l G e t E r r o r ( ) != AL_NO__ERROR) {
Throw {"Error linking to OpenAL buffer.");
}
mBuffer = linkto;
9-1490
258
Глава 14
Обратите внимание, что класс COpenALSource сохраняет указатель на буфер, к которому он привязан (m_Buffer). Это гарантирует то, что буфер не будет удален до тех пор, пока не будут удалены или перенаправлены на другие буферы все источники, которые с ним связаны. И снова возможности интеллектуальных указателей делают это несложным. Уничтожение источников выглядит так же, как и уничтожение буферов: оно происходит в деструкторе и, прежде чем вызвать alDeleteSources, уничтожающий источник, вызывает allsSource, чтобы убедиться, что ID источника существует.
Воспроизведение источниКоВ Чтобы воспроизвести источник, необходимо вызвать метод PlayFromStart класса COpenALSource. Также существуют дополнительные методы для установки на паузу, воспроизведения с паузы, остановки и перемотки источников. Они используют вызовы OpenAL методов alSourcePlay, alSourcePause, alSourceStop и alSourceRewind, и в них достаточно легко разобраться, просто просмотрев код.
Пример про дальний Космос В OpenAL Пример программы этой главы в точности совпадает с предпоследней главой, за исключением использования OpenAL вместо DirectX Audio. По смысл}', задания, которые должна выполнить игра, очень похожи на те, что она должна была выполнить для 3D звука DirectX Audio. Игра создает буферы и источники внутри метода OneTimeScenelnit, потом задает расположение и скорости всего внутри метода FrameMove. Вызовы функций не совпадают в точности, но вы вполне способны разобраться в том, что происходит, если сравните предыдущую главу и текущую. Вы можете найти код для этой главы на вашем CD, в директории Chl4pl_3DOpenALSound; код для предпоследней главы в директории Chl3pl_3DSound.
Зайлючение Так что же лучше? Если сравнивать плюсы и минусы, то два API почти ничем не отличаются. Когда дело доходит до 3D звука, то видно, что если у OpenAL есть какая-то возможность, то у DirectX Audio есть что-то аналогичное. Нельзя сказать, чтобы один API серьезно превосходил по возможностям другой.
3D aByk, используя OpenAL
259
Все же, у OpenAL есть серьезное преимущество перед DirectMusic; тогда как 3D звук в DirectX Audio привязан к платформе Windows, OpenAL на данный момент существует и на Windows, и на Macintosh, и ничего не мешает ему существовать на Linux, Unix и других платформах. Если вы хотите сделать вашу игру межплатформенноЙ и хотите использовать 3D звук, то лучшим выбором будет OpenAL. С другой стороны, если вы уже все равно привязаны к платформе Windows (к примеру, вы используете возможности динамической музыки DirectMusic), тогда вряд ли у вас есть стимул тратить время на интегрирование OpenAL. Если вы уже настроили инфраструктуру DirectMusic и у вас все работает, то, вероятно, вы продолжите работу с 3D звуком там же.
Глава 15
Продвинутые Возможности 3D aByka
Две предыдущие главы научили вас использовать два различных API 3D звука для получения простейших 3D эффектов, связанных с расположением объектов. Очень хорошо, что вы это знаете, но 3D звук может дать гораздо больше. В этой главе будут рассмотрены некоторые продвинутые методики для 3D звука. В основном, в главе рассказывается о том, как использовать моделирование окружающей среды (environmental modeling), чтобы добавить колорита вашему 3D окружению, кроме этого, будут затронуты две продвинутые технологии: Environmental Audio Extensions (EAX) от Creative и Dolby технологии.
Моделирование оКруЖающей среды 3D звук - это не только источники и буферы. Предыдущие две главы оставили без внимания одну важную вещь: окружение, в котором раздается звук. К примеру, используя знания, полученные из двух последних глав, вы можете создать реалистичные звуки шагов. Привяжите приемник к голове вашего персонажа и по одному источнику звука к каждой ноге, а потом воспроизводите звук, исходя из анимации ног- все просто. Но кое-что все же упущено из вида. Звуки шагов по мраморному полу атриума огромной библиотеки звучат совсем не так, как звуки шагов по мраморному полу маленькой кухни. В библиотечном атриуме, из-за его большого размера, звуковые волны будут вести себя совсем не так, как бы они вели себя в маленькой кухне.
Продвинутые Возможности 3D a&yka
261
К счастью, существуют API, которые могут смоделировать эти эффекты окружающей среды и предложить разумные приближения того, как заданное окружение повлияет на звук. Если вы предоставите footsteps_on_marble.wav, обработка эффектов окружения может превратить его в звуки шагов по мрамору в огромной библиотеке или маленькой кухне.
Моделирование оКруЖения с использованием I3DL2 Отражение, возможно, - самое важное свойство окружения. В этом разделе вы узнаете о том, как использовать отражение для создания конкретного окружения, и узнаете, как использовать I3DL2 для реализации эффектов отражения в вашей игре.
Что makoe I3DL2? I3DL2 - это аббревиатура для Interactive 3D Audio, Level 2.13DL2 был создан Interactive Audio Special Interest Group, организацией, состоящей из профессионалов, работающих со звуком во всех областях разработки игр и мультимедийных технологий. Первоначально организация была создана в 1994 году, и вскоре после этого была сформирована рабочая группа, которая в 1998 году создала стандарт IA-SIG Interactive 3D Audio Rendering and Evaluation Guidelines (Level 1). В 1999 году они усовершенствовали стандарт и выпустили IA-SIG Interactive 3D Audio Rendering Guidelines (Level 2), или I3DL2.
Термины и понятия I3DL2 Прежде чем начать изучение того, как пишется код, моделирующий окружение с использованием I3DL2, вам нужно познакомиться с теорией отражений. Чтобы точно задать окружение, вам нужно разобраться с некоторыми составляющими, формирующими эхо и глушение звука, характерные для определенной среды. Представьте большое помещение (скажем, кафедральный собор) с источником звука (скажем, рычащим орком) и приемником (доблестным героем), разделенных некоторым расстоянием (рис. 15.1). Когда орк зарычит, доблестный герой услышит несколько различных звуковых волн. Во-первых, он услышит звуковые волны идущие напрямую от орка (путь 1). Этот путь называется прямым (direct path). Во-вторых, он услышит звуковые волны, которые пошли сначала примерно в том же направлении, но отразились от стены на их пути (путь 2) - это ранние отражения (early reflections). И последними доблестный герой услышит звуковые волны, которые сначала пошли в противопо-
Глава 15
262
ложном направлении и отразились от множества разных стен. Все вместе эти волны называются поздним отражением (late reverberation). Рис. 15.2 показывает график этих различных типов волн.
Рис. 15.1. Показывает прямой путь, ранние отражения и позднее отражение '
При мй пут ь
Ра нние oipзжения
Позднее отражение
ДБ
Время
Рис. 15.2. График трех различных типов волн
Продвинутые возможности 3D aByka
263
Конечно, это все чересчур упрощенно, но идея основной модели такая же. Чтобы смоделировать окружение в I3DL2, вы настраиваете переменные внутри модели. Например, в I3DL2 существует конкретная переменная, которая отвечает за то, сколько времени пройдет между тем, как приемник услышит прямой путь звука и первое ранее отражение. Для моделирования большого помещения вы увеличиваете эту переменную, потому что в большом помещении звуковые волны, идущие путем 2, должны пройти большое расстояние, прежде чем они отразятся от стены. Для моделирования маленького помещения нужно сделать разницу во времени между прямым путем и ранним отражением очень маленькой, вероятно, настолько маленькой, чтобы слушатель не смог заметить разницы. I3DL2 позволяет задавать вам громкости и задержки для всех трех путей (прямой путь, ранние отражения и позднее отражение). Также при отражении высокие частоты ведут себя не так, как низкие. Точнее, высокие частоты спадают гораздо быстрее. Также, разные помещения отражают звуки с различным рассеянием и значениями плотности. I3DL2 позволяет вам настраивать все эти параметры для создания множества различных звуковых окружений.
I3DL2: просто другой эффеКт В DirectX Audio I3DL2 реализован как эффект, через интерфейс, который называется !DirectSoundFXI3DL2Reverb8. Это значит, что вы можете использовать его так же, как вы используете любой другой эффект - тем же путем, что вы изучили в главе 12. Это замечательно, потому что использование I3DL2 становится очень простым - вы применяете эффект к звуку вашего источника, устанавливаете его параметры, и далее эффект позаботится об изменении вашего звука, основываясь на этих параметрах. Чтобы интегрировать эффект I3DL2 в звуковой движок, вам нужен новый класс эффекта. Вот его объявление. class CI3DL2Effect : public CEffect { public: CI3DL2Effeet() ) ZeroMemory{&m_Params, sizeof(DSFXI3DL2Reverb) ] ; ClearPreset(); } virtual ~CI3DL2Effect<) { } virtual void SetAllParameters (IDirectMusicAudioPathS *audiopat:i) ; virtual GUID GetGuidO { return(GUID_DSFX_STANDARD_I3DL2REVERB!; } virtual void SetPreset(DWORD i3d!2preset); virtual void ClearPreset () { m_Preset = -1,- } DSFXI3DL2Reverb m_Params; protected: DWORD mPreset;
264
Глава 15
Обратите внимание, что этот класс, хотя и аналогичен другим классам эффектов, имеет несколько дополнительных методов и членов. Это новые методы SetPreset и ClearPreset, а также новый защищенный член mJPreset. Вы узнаете обо всем этом через пару страниц в разделе о предустановленных эффектах.
Параметры отражений I3DL2 А сейчас посмотрите внимательно на структуру DSFXI3DL2Reverb (описанную в табл. 15.1). Это сердце процесса отражений I3DL2. Переменные в этой структуре описывают эффект DirectX, как изменяется звук. Вот, что значит каждый член (я специально перечисляю их не по порядку, потому что некоторые объяснения зависят от других). Все эти параметры имеют минимальное, максимальное значение и значение по умолчанию, определенные в заголовочных файлах DirectX. Для их названий используется следующее соглашение: DSFXJ3DL2REVERB_(parameter)_MIN, DSFXJ3DL2 REVERB_(parameter)_MAX и DSFX_13DL2REVERB_(parameter)_DEFAULT. Например, три определенных значения для IRoom: DSFX_I3DL2_REVERB_RO-OM_MTN, DSFX_ I3DL2_REVERB_ROOM_MAX и DSFX_I3DL2__REVERB_ ROOM_DEFAULT. Совет. Если вы не знаете, то милибел (мБ) - это одна сотая децибела. То есть 100 милибел - это то же самое, что и 1 децибел. Несколько параметров, которые нам встретятся, являются ослаблениями, измеряемыми в милибелах. Если 'вы зададите ослабление равным -50мБ, вы установите снижение громкости на полдецибела.. Таблица 15.1. Члены структуры DSFXI3DL2Reverb Имя параметра
Описание
flHFReference Определяет высокие частоты в Герцах. Несколько последующих параметров зависят от высоких частот. Этот параметр говорит DirectX, какие частоты считаются высокими. Например, если вы выставите эту величину равной ее значению по умолчанию, 5000Гц, вы сообщите DirectX, что любая частота, большая 5000Гц, считается высокой IRoom
Значение ослабления помещения (room attenuation) в милибелах (мБ). Этот параметр влияет на то, насколько громок эффект помещения. Не забывайте, что это ослабление, что значит, что полная громкость соответствует нулю, а пониженной громкости соответствуют отрицательные значения. Значение по умолчанию -10ООмБ {-10 децибел)
Продвинутые Возможности 3D a&yka
265
Таблица 15.1. Члены структуры DSFXI3DL2Reverb Имя параметра
Описание
IRoomHF
Значение ослабления помещения для высоких частот в милибелах. В реальном мире различные помещения заглушают высокие частоты иначе, чем низкие частоты. Используя эту переменную, вы можете сообщить DirectX, насколько сильно ослабить высокие частоты (частоты, лежащие выше параметра IHFReference). Например, вы можете выставить это значение равным -ЮОООмБ и IRoom равным -1000мБ, чтобы создать окружение, которое полностью гасит высокие частоты, но позволяет проходить низким. Это даст эффект нахождения под водой
fIRoomRolloffFactor
Фактор спада помещения говорит DirectX, насколько быстро затухают процессы отражения, при удалении приемника и источника. Обратите внимание, что этот параметр влияет только на фактор спада для отражений; фактор спада для основного звука по-прежнему контролируется приемником
fIDecayTime
Как долго (в секундах) существует эхо. Если вы выставите значение высоким, то звук будет такой, как будто вы в Большом Каньоне (много эхо!). Низкое значение создаст ощущение глухого помещения безо всякого эха
fIDecayHFRatio
Отношение времени затухания высоких частот к времени затухания низких. Еспи вы выставите этот параметр равным 1, высокие частоты и низкие частоты будут существовать одно и то же время. Если меньше 1, то высокие частоты затухнут раньше низких. Для значений больших 1 высокие частоты просуществуют дольше, чем низкие (обычно нереалистично). Значение по умолчанию 0,83
(Reflections
Ослабление ранних отражений относительно IRoom в мБ. Ранние отраженияэто первые эхо, которые слышно в помещении. Этот параметр говорит DirectX, совпадает ли громкость ранних эффектов отражения с IRoom или же они тише или громче. Значение по умолчанию -2602мБ делает ранние отражения тише
flReflectionsDelay Число секунд, которое разделяет прямой путь и ранние отражения. Проще говоря, насколько долго ждет DirectX после того, как звук воспроизведен, прежде чем появится эхо. По умолчанию 0,007 секунд. Для моделирования больших помещений, увеличьте значение; для моделирования небольших пространств уменьшите IReverb
Ослабление позднего отражения относительно IRoom в милибелах. Аналогично IReflections определяет, должны ли эффекты отражения быть громче или тише, чем ослабление IRoom. Значение по умолчанию 200мБ, что делает их чуть громче.
flReverbDelay Время между первым ранним отражением и поздним отражением в секундах. Другими словами, параметр говорит DirectX, насколько долго ждать, после того как было воспроизведено первое раннее отражение, до проигрывания позднего отражения fIDiffusion
Плотность эхо позднего отражения в процентах. По умолчанию 100%
Глава 15
266
Предустановленные (preset) эффеКты Требуется некоторая практика, чтобы научиться настраивать все эти различные величины так, чтобы получить хорошо звучащее окружение (т. е точно передать ощущение необходимого вам места). В качестве помощи вы можете использовать несколько предустановленных эффектов. Они охватывают широкий диапазон окружающих сред и освобождают вас от необходимости разбираться с отдельными переменными. Использование этих предустановок очень просто, просто вызовите метод SetPreset интерфейса IDirectSoundFXI3DL2Reverb8 и передайте ей GUID необходимой вам предустановки. Полный лист GUID включен в документацию DirectX по методу SetPreset. Конкретные значения переменных для каждой предустановки можно найти в заголовочном файле dsound.h.
Создание pegakmopa оКруЖения Как вы могли заметить, числа не самоочевидны. Чтобы правильно создавать завораживающие звуковые окружения, с помощью отражений I3DL2, вам необходима программа, которая позволит вам настраивать эти значения и незамедлительно слышать результат ваших настроек. Другими словами, вам нужен редактор окружения. К счастью переменных не так много. Простого диалога с 12 бегунками (или иначе, ползунками) должно хватить для простого редактора, так что его вы и научитесь создавать в этом разделе. Рис. 15.3 показывает изображение этого редактора. Как вы видите, это просто несколько бегунков, кнопки загрузки и сохранения плюс несколько кнопок для поиска и воспроизведения WAV файла.
~J— -J
-ЮМ я* -KCir*
- L L*e RtvebArtauafan
Рис. 15.3. Изображение примера программы Ch 15p2_BDL2EnvEditor
Продвинутые Возможности 3D aByka
267
Посмотрите, как работает пример программы Chi 5p2_I3DL2EnvEditor - последующие разделы объяснят вам эту программу. Если до сих пор у вас не было большого опыта работы написания для Win32 GUI, то этот пример может показать вам пару трюков в этой области.
Инициализация В Win 32 программировании с бегунками (trackbars) легко работать, только если вы не забыли вызвать функцию Win32 API InitCommonControls. Если вы не смогли ее вызвать, то у вас не получится создать ни одного окошка с бегунком в нем. Бегунки, в отличие от остальных элементов управления (кнопки, поля редактирования и т. д.), не были частью первоначального Windows GUI. Они были созданы позже, отсюд,а и необходимость в вызове функции.
Работа с бегунКами Я не собираюсь объяснять стандартный механизм создания немодального диалогового окна и цикла обработки сообщений для него — существует несколько хороших вводных книг по программированию в Windows, которые объясняют этот материал и объясняют его хорошо. Но я хочу потратить некоторое время на объяснение вам того, как работают выполняемые функции GUI. Бегунки, как и любой другой элемент контроля, работает, используя сообщения Windows. Три самых важных сообщения, которые вы можете послать бегунку, собраны в табл. 15.2. Таблица 15.2. Основные сообщения бегунка Сообщение бегунка
Описание
TBM^SETRANGE
Устанавливает максимальное и минимальное значение ползунка
TBM_SETPOS
Устанавливает текущую позицию бегунка
TBM_GEFPOS
Возвращает текущую позицию бегунка. Когда пользователь перемещает бегунок, он уведомляет родительское окно, посылая сообщение WM_HSCROLL (для бегунков, ориентированных горизонтально) или сообщение WM_VSCROLL (для бегунков, ориентированных вертикально)
Пока все выглядит очень просто, да? Все, что вам нужно, - это отправить каждому бегунку сообщения TBM_SETRANGE и TBM_SETPOS, чтобы инициализировать диалоговое окно. После этого, вы отслеживаете сообщения WM_HSCROLL и посылаете TBM_GETPOS, чтобы получить новую позицию бегунка.
268
Глава 15
Но есть одна маленькая ловушка - бегунки принимают только целочисленные значения. Для большинства приложений это не проблема, но этот пример программы имеет несколько значений, которые доллшы быть числами с плавающей точкой. Например, существует переменная задержки отражения, flReverbDelay, которая имеет минимальное значение 0,0 и максимальное 0,1. Чтобы обойти эту проблему, вам необходимо все масштабировать умножением. Например, вы можете решить умножать значения flReverbDelay на 10000, получая таким образом приличный целочисленный диапазон от 0 до 1000. Тогда все будет в порядке, если вы не будете забывать умножать значения flReverbDelay на пути к ползунку и делить позицию ползунка на эту величину, после получения позиции через TBMJ3ETPOS. В примере программы Chi 5p2_BDL2EnvEditor имеются две функции Refresh Slider. Функция, которая требует целочисленные значения, не делает ничего особенного; функция, требующая значения с плавающей точкой, также требует фактор масштабирования. Совет. Бегунки способны на гораздо большее, чем я здесь рассказал. Например, вы можете также поставить им в соответствие шкалу отсчета и автоматически управлять другими элементами управления. Для более подробной информации обращайтесь к документации по MSDN, конкретно, Platform SDK\User Interface Services\Shell and Common Controls\Trackbar Controls.
Saepyaka u сохранение okpyjkemiu Простым способом записи и сохранения чего-то, что лежит в структуре, является запись отпечатка памяти этой структуры в качестве бинарного файла напрямую на жесткий диск. И это именно то, что делает этот простой редактор. Вот функции для записи и загрузки с диска. void OnLoad(HWND hDlg) char filename[MAX_PATH] = { 0 }; OPENFILENAME ofn = { sizeof(OPENFILENAME}, hDlg, NULL, "Reverb Files\0*.reverb\OAll FilesXO*.*\0\D", NULL, 0, 1, filename, MAX_PATH, NULL, 0, NULL, "Open Reverb File", OFN_FILEMUSTEXIST|OFN_HIDEREADONLY, 0, 0, TEXT(".reverb"), 0, NULL, NULL }; //отображаем диалог OpenFileName и пытаемся загрузить //указанный файл if (GetOpenF:LleName(&ofn) ) {
int handle = open(filename, 0_RDONLY | 0_BINARY); if (handle =- -1) Throw("Can't open file!"}; read(handle, &g_ReverbValues, sizeof(DSFXI3DL2Reverb)};
Продвинутые возможности 3D sByka
269
close (handle) ; RefreshGUI (hDlg) ;
} } void OnSavefHWND hDlg) { char filename [MAX_PATH] = { 0 }; OPENFILENAME ofn = ( sizeof (OPENFILENAME) , hDlg, NULL, "Reverb Files\0* . reverb\OAll FilesXO* . *\0\0", NULL, 0, 1, filename, MAX_PATH, NULL, 0, NULL, "Save Reverb File", OFN_HIDEREADONLY, 0, 0, TEXT (". reverb" ), 0, NULL, NULL }; //отображаем диалог OpenFileName и пытаемся сохранить //указанный файл if (GetOpenFileName (fiofn) ) i int handle = open (filename, 0_RDWR I 0_BINARY | 0_CREAT ] 0_TRUNC) ; 1 if (handle == -1) Throw {"Can t save file!"); write (handle, &g_ReverbValues, sizeof (DSFXI3DL2Reverb) ) ; close (handle) ;
Как можно понять, весь этот код работает с обычными диалоговыми окнами загрузки и записи файла. Для использования этих диалогов, вы должны заполнить структуру OPENFILENAME. Вы можете посмотреть справку по функции GetOpenFileName в MSDN, чтобы выяснить, что значит каждое конкретное значение в структуре OPENFILENAME. После того как структура заполнена, отображение диалога сводится к вызову GetOpenFileName. Если пользователь щелкает на ОК, функция вернет true и далее последующий код позаботится о сохранении или загрузке файла.
Pegakmop: подытожим Конечно, это совсем простой редактор. Редактор для профессиональной игры будет гораздо сложнее этого - он будет брать в расчет позицию персонажа в игровом мире и также позволит разработчикам игры настраивать различные части уровня, каждая часть со своими параметрами звукового окружения. Чтобы вы знали к чему стремиться, приведу несколько путей улучшения программы, Q Заставить ее сохранять параметры в более стандартном формате, таком, как XMLr. LI Добавить поддержку предустановленных окружений - я представляю себе это в виде ниспадающего меню, которое позволяет вам выбрать предустановку, настроить ее и сохранить в качестве нового окружения.
270
Глава 15
Q Ввести элементы управления, позволяющие пользователю редактировать, кроме свойств окружения, еще и свойства 3D приемника и источника DirectSound. Достаточно часто настройка одного вызывает необходимость настройки другого.
I3DL2: подытожим Вам нужно знать, что I3DL2 позволяет большее, чем просто рассчитывать отражения. I3DL2 также определяет интерфейс для задания преград и помех, два понятия, которые одинаково важны в 3D звуке (далее будут даны их определения). Через расширения свойств DirectSound вы можете получить доступ к исполняемым функциям преград и помех и использовать их для создания еще более реалистичной атмосферы в вашей игре. Если вы хотите больше узнать о I3DL2, проверьте lA-SIG's 3D Audio Working Group, на сайте HTTP://www.lASlG.ORG.
ЕАХ Теперь, после того как вы познакомились с I3DL2, я хочу завершить эту главу, рассказав о двух других технологиях, которые могут оказаться полезными, если вы хотите до конца использовать возможности 3D звука. Первая технология, о которой я расскажу, называется ЕАХ. ЕАХ - это аббревиатура для Environmental Audio Extensions. Эта технология была разработана Creative Labs и претерпела изменения до второй версии (на момент написания книги, ЕАХ 2.0 юявляется последней версией). Чтобы понять, что делает ЕАХ, вы прежде должны разобраться с самыми важными аспектами изменения звука на основании его окружения. • Отражения. Как вы уже узнали, используя некоторые вычисления для моделирования отражений симулированных звуковых волн от симулированных стен, компьютер может создавать очень реалистичное эхо, что в свою очередь позволяет моделировать большое число окружений. • Преграды (occlusion) и помехи (obstruction). Преградой называют что-либо, что физически разделяет источник звука и приемник (к примеру, стена). Помеха, с другой стороны, - это когда источник и приемник находятся в одном и том же помещении, но существует объект (скажем, ящик), который располагается между ними. • Направленность источника. Когда вы, в реальной жизни, задаете направление источника звука, не все частоты соответствуют направлению одинаково. Нижние частоты соответствуют совсем не так сильно как высокие. Именно поэтому, когда вы настраиваете свой домашний кинотеатр, не так важно, куда вы поставите ваш савбуфер, но вот позиция и направленность ваших средне-
Продвинутые Возможности 3D sByka
271
и высокочастотных динамиков очень важна. Продвинутые API для 3D звука позволяют вам настраивать направленность источника для средних и высоких частот отдельно. Эти три понятия являются основными для моделирования окружения. ЕАХ работает со всеми тремя. ЕАХ доступен и для DirectSound, и для OpenAL, и интеграция с обоими достаточно незамысловата. В DirectSound вы просто получайте интерфейс для объекта ЕАХ, соответствующего конкретному источнику звука; в OpenAL ЕАХ является расширением (extension) и доступ к нему легко осуществляется через механизм расширений OpenAL. Вы можете больше узнать про ЕАХ на сайте разработчиков http://developer.creative.com.
KogupoBka Dolby Digital Если вы профессиональный разработчик, создающий профессиональную игру, вы обязаны познакомиться с кодировкой Dolby, особенно с кодировкой Dolby Digital. По существу, кодировка позволяет вам кодировать раздельные 5.1 каналы звука в один битовый поток. Так же, как вы можете кодировать два различных канала в стерео WAV файл, вы можете кодировать левый, правый, центральный каналы и канал правого и левого окружения (surround) в битовый поток Dolby. Битовый поток также поддерживает LFE (Low-Frequency Effects низкочастотные эффекты) канал, который содержит частоты ниже 120Гц. Для более подробной информации по кодировке Dolby Digital, смотрите сайт http://www.dolby.com.
Заключение Этой главой завершается ваше изучение 3D звука. Сейчас у вас должно быть достаточно знаний, чтобы реализовать 3D звук в ваших играх и, возможно, реализовать какую-нибудь из продвинутых технологий, такую как ЕАХ. Будущее 3D звука становится светлее и светлее с каждым днем. Уже появились звуковые карты, которые аппаратно моделируют отражения отдельных звуковых волн. Закон Мура продолжает предоставлять звуковую аппаратуру профессионального уровня покупателям, и каждое поколение звуковых карт обладает более мощными возможностями. Если вы считали, что качество игрового звука зависит только от числа каналов и частоты выборки, то я надеюсь, что эта глава изменила ваше мнение.
Глава 16
DirectPlay Voice Единственная цель этой главы - это показать вам, как использовать DirectPlay Voice API. Это малоизвестный компонент DirectPlay, который позволяет реализовать общение голосом в реальном времени в многопользовательской игре. Это одна из тех редких технологий, которые относительно просто реализуются, но при этом являются большим плюсом к игре.
DirectPlay Voice API Возможно, что DirectPlay Voice API возник из-за одного простого факта: многопользовательские игры гораздо более интересны, если вы можете слышать возгласы других игроков. Очень мало чувств могут сравниться с тем, которое вы испытывайте, слыша проклятия игрока, который понял, что он обречен, когда вы напали на него из засады. С помощью DirectPlay Voice вы можете реализовать это в вашей игре, написав всего лишь несколько строчек кода.
Основы DirectPlay Voice работает, отправляя голосовые данные через соединение DirectPlay. Он начинает с записи голоса игрока. Запись может начинаться вручную (например, пользователь должен нажать кнопку на джойстике или клавиатуре перед тем, как начать говорить) или автоматически (система начинает запись, когда она обнаруживает резкое изменение громкости на микрофоне). При самом простом использовании, когда игрок начинает говорить, DirectPlay Voice снимает его голос с микрофона (с низкой частотой выборки) и потом сжимает его, используя специальный алгоритм сжатия голоса. После DirectPlay Voice отсылает сжатые данные на другой компьютер, используя сетевой протокол DirectPlay. Другой компьютер
DirectPIay Voice
273
проводит декомпрессию голосовых данных и воспроизводит их, используя звуковую карту. В идеале все происходит достаточно быстро, и общение кажется мгновенным, но при активном использовании сети, может быть небольшая задержка между тем, как один игрок произнес фразу и как другой игрок ее услышал.
Топология сети DirectPIay Voice Я только что описал пример пиринговой (peer-to-peer) связи DirectPIay Voice. При такой топологии каждый компьютер отвечает за отправление голосовых данных игрока на все остальные компьютеры. Это значит, что если вы играете с пятью другими игроками, то когда вы говорите, ваш компьютер должен отправить ваши голосовые данные пяти различным компьютерам (рис. 16.1)
Рис. 16.1. При использовании прямой связи в игре с шестью игроками каждый раз, когда вы говорите, ваш голос должен быть отправлен на пять разных компьютеров
274
Глава 16
Как вы можете понять, это может сильно загрузить ваш входящий и исходящий каналы. Стандартный сжатый голос требует пропускной способности в 8кбит/с (хотя вы можете использовать различные алгоритмы сжатия с требованиями к пропускной способности от 1,2кбит/с до 13кбит/с). Это значит, что вам нужна пропускная способность сети равная 40кбит/с (8кбит/с х5), когда говорите вы, и вам необходимо еще скачивать как минимум 40кбит/с (в том случае, если одновременно с вами говорит кто-нибудь еще). Если вы играете по локальной сети, нет проблем - в локальной сети минимальная пропускная способность 10Мбит/с. Но если вы играете по модему или по широкополосной сети с небольшими скоростями загрузки, одновременная скорость 40кбит/с для загрузки(ир1оа<3)/скачивания(с!ошп1оас1) может быть недостижима.
рок 5
Рис. 16.2. В топологии эстафетного сервера вы отсылаете свои голосовые данные центральному серверу, а сервер берет на себя проблемы с их доставкой всем остальным
DirectPIay Voice
275
Для таких случаев DirectPIay Voice позволяет использовать вам две другие топологии - топологию эстафетного (перепосылающего) сервера (Forwarding Server Topology) или топологию смешивающего сервера (Mixing Server Topology). В топологии эстафетного сервера, когда вы говорите, ваш компьютер отсылает ваш голосовой поток центральному голосовому серверу. А сервер, в свою очередь, отправляет ваш голос всем остальным компьютерам в игре (рис. 16.2). Это освобождает ваш компьютер от необходимости посылать множество копий данных - голосовой сервер делает это за него. Таким образом, только голосовой сервер должен иметь высокие скорости загрузки. Обратите внимание, тем не менее, что клиенты все равно должны иметь высокие скорости скачивания, потому что, если пять человек заговорят разом, один клиент должен быть способен скачать одновременно пять потоков (рис. 16.3). Считайте, что топология эстафетного сервера - это топология «один вверх, много вниз» - даже в наихудшем случае каждому клиенту придется загружать один голосовой поток; тем не менее, может быть придется одновременно скачивать много голосовых потоков. Третья топология - топология смешивающего сервера - топология «один вверх, один вниз». В этой топологии сервер получает данные от клиентов, а потом проводит декомпрессию полученных данных. После этого каждому клиенту отправляется один, специально сделанный, поток, содержащий смесь всех голосов, относящихся к клиенту (рис. 16.4). Например, если игрок 1 и игрок 2 говорят одновременно, сервер отправит голосовой поток игрока 2 игроку 1, голосовой поток игрока 1 игроку 2 и смесь обоих голосовых потоков остальным игрокам. Таким образом, каждому клиенту нужно скачивать только один поток. Тем не менее, серверу нужно работать значительно больше: вместо того, чтобы просто заниматься перераспределением голосовых потоков, как это делается в топологии эстафетного сервера, теперь ему нужно проводить декомпрессию, смешивать и снова сжимать смешанные потоки для каждого клиента. Топология смешивающего сервера платит вычислительной мощностью за меньшую нагрузку на пропускную способность. Конечно, наилучшую топологию для использования следует выбирать, исходя из того, где и как проходит игра. Если игра идет по локальной сети, то наилучшим выбором будет просто соединение напрямую - у вас большая пропускная способность и прямое соединение гарантирует, что каждый компьютер делает одно и то же количество работы. Если игра идет через Интернет, то все зависит от того, есть ли выделенный сервер. Смешивающий сервер тратит большое количество вычислительных ресурсов - настолько большое, что для большинства игр не имеет
276
Глава 16
Игрок 5
Рис. 16.3. Если все говорят разом, даже с голосовым сервером, вам понадобится пропускная способность скачивания равная 40кбит/с, чтобы получить все пять потоков
DirectPlay Voice
277
Игрок 3
[Смесь гог'осовлервого ивторого игрока Смешивающий сервер
Игрок 4
Игрок 2
Игроки
Рис. 16.4. Смешивающий сервер посылает каждому компьютеру смесь из необходимых ему голосов
смысла запускать саму игру, потому что частота смены кадров будет очень маленькой. Если голосовой сервер одновременно является игроком, то лучшим выбором будет топология эстафетного сервера. Тем не менее, если голосовой сервер является выделенным и не должен рисовать треугольники или делать что-либо кроме сетевой работы, он легко справится со смешиванием голосов, и лучшим выбором будет топология смешивающего сервера, Совет. Вместо того чтобы просто предоставлять вашим игрокам выбор одной из топологий, вы можете написать код, который автоматически будет решать, какую топологию использовать, основываясь на тех настройках сети, которые установили у себя игроки. Например, если клиент подсоединяется к игре по модему, ваш код может автоматически решить использовать прямую топологию.
278
ГлаВа16
Для начала, kpamkuu kypc DirectPlay DirectPlay Voice работает над DirectPlay, Это значит, что если вы никогда раньше не писали код, который использует DirectPlay, вам нужно разобраться с основными принципами DirectPlay прежде, чем вы сможете общаться через DirectPlay Voice. Эта книга не про DirectPlay, так что я собираюсь сконцентрироваться на основах и буду рассказывать достаточно быстро. Тем не менее, очень важно для понимания принципов работы DirectPlay Voice, чтобы вы поняли, как работает ядро DirectPlay. Так что, если у вас проблемы с пониманием принципов из этого краткого описания или вы хотите узнать о тех вещах, про которые я специально не стал рассказывать (например, лобби (lobbies) или групп игроков (player groups), то вы сами должны подкрепить свои знания разбором некоторых примеров программ DirectPlay и/или использованием некоторых дополнительных источников информации, более подробно рассказывающих про DirectPlay и использование сетей.
riomok событий В сессии DirectPlay Некоторым будет тяжело разобраться с DirectPlay. Хитрость в том, чтобы рассмотреть достаточно высокоуровневые этапы, которые вам необходимы, чтобы подсоединиться и сыграть в многопользовательскую игру. Существует три основных этапа в любой многопользовательской игре. Сначала вы подсоединяетесь к игре. Потом, по мере того как вы играете, ваш код отсылает и получает сообщения о состоянии игры, например позиции боевых единиц в RTS или тот факт, что игрок только что выстрелил из оружия, и т. д. Большое количество времени вашего игрового кода будет потрачено на отсылку и прием сообщений. Вам необходимо создавать и получать ваши собственные типы сообщений, и также вам необходимо обрабатывать системные сообщения DirectPlay (игрок присоединился, игрок отсоединился и т. д.). Совет. 3В различных играх используются различные методы передачи информации самыми короткими сообщениями. Большинство игр синхронизуют состояние игры в самом начале, а потом просто посылают и принимают сообщения, содержащие определенные изменения игрового мира (например, сервер пошлет клиенту сообщение «танк номер 4 только что переместился на позицию (5,16)»). Наконец, существует обязательная фаза «деинициализации и завершения работы», Это. важно всегда, но особенно это важно здесь, потому что, если вы забудете это сделать, DirectPlay может не заметить, что ваша игра остановилась, и другие играющие люди не получат уведомления о том, что вы выбыли.
DircctPlay Voice
279
Теперь, когда вы изучили высокоуровневый обзор, давайте немного укрупним «изображение». Последующие разделы по порядку подробно объяснят шаги, которые вам нужно предпринять, чтобы использовать DirectPlay. Совет. DirectPlay, как и DirectMusic, испачъзует множество GUID (globally unique identifier). Они используются дня идентификации игр, с поставщиков услуг и некоторых других вещей в многопользовательской игре. GUID уникален и во времени и в пространстве - когда вы создаете GUID вы можете быть уверен, что он не совпадает ни с одтш из когда-либо созданных GU1D и ни с одним из тех, что когда-либо будут созданы.
Создание интерфейса DirectPlay Первое, что нужно сделать, - это создать интерфейс DirectPlay COM объекта. Интерфейс, который вам нужно использовать, зависит от типа топологии, который вы выбрали для своей игры - прямая или клиент-сервер. Табл. 16.1 объединяет все доступные интерфейсы. Таблица 16.1. Доступные интерфейсы DirectPlay Интерфейс
Описание
IDirectPlaySPeer
Используйте этот интерфейс для прямой топологии
IDirectPlaySCIient
Используйте этот интерфейс, когда подсоединяетесь в качестве клиента в топологии клиент-сервер.
IDirectPIayBServer
Используйте этот интерфейс, чтобы создать новый сервер в топологии клиент-сервер
Примеры программы этой книги используют прямую топологию, так что они используют интерфейс IDirectPlaySPeer. Начиная с этого места, я буду рассказывать все только относительно этого интерфейса. Несмотря на это, все, кроме процесса соединения, достаточно легко преобразовывается - все три интерфейса имеют примерно одинаковый набор методов.
Выбор поставщика услуг (service provider) После того как вы создали соответствующий вашей сетевой топологии интерфейс DirectPlay, вам нужно выбрать поставщика услуг. DirectPlay может работать с различными каналами связи - TCP/IP, модемы и последовательные подключения. Каждый канал связи имеет своего поставщика услуг. Вам необходимо будет получить список поставщиков услуг из DirectPlay и после предоставить игроку выбор поставщика услуг из этого списка. Вы можете получить список доступных поставщиков услуг, вызвав метод EnumServiceProviders выбранного вами интерфейса DirectPlay.
280
Глава 16
Создание игры или подключение k уАе созданной Вы получили список доступных поставщиков услуг, показали его игроку и узнали его выбор. В случае прямого соединения следующим шагом будет спросить игрока, хочет ли он создать игру или присоединиться к уже существующей. Вы можете определить, какие игры уже существуют, вызвав метод EnumHosts интерфейса IDirectPlaySPeer. На выходе вы получите список доступных хостов компьютеров, на которых запущена ваша игра. Вы, наверное, удивлены, откуда DirectPlay знает, запущена ли на компьютере ваша игра. Он знает благодаря возможностям GUID. Каждое приложение DirectPlay имеет свой собственный GUID, и внутри DirectPlay этот GUID - это имя вашей игры. Когда вы вызываете EnumHosts, вы можете себе представить, что DirectPlay передает всем компьютерам в вашем локальном мире просьбу: «Если у вас запущена игра с этим GUID в режиме ожидания соединения, пожалуйста, назовите себя». После этого DirectPlay собирает все ответы и передает их вашему коду. Считайте, что каждый хост, приславший GUID, который его идентифицирует, говорит: «Привет, это хост GUID, и я являюсь хостом той игры, которая вас интересует». Вы, в свою очередь, выводите этот список пользователю, который выбирает хост. После этого вы передаете GUID выбранного хоста DirectPlay путем вызова метода Connect интерфейса IDirectPlaySPeer и ждете, пока DirectPlay не сообщит вам об успехе или неудаче. Если все успешно, то вы подсоединились и можете начать отсылать и принимать сообщения! Если ваш пользователь решит создавать хост для новой игры, вместо того чтобы подсоединяться к уже существующей, то все проще. Все, что вам нужно сделать, - это вызвать метод Host интерфейса IDirectPlaySPeer, передав ему структуру DPN_APPLICATION_DESC, содержащую информацию о вашей новой игре (например, названии этого конкретного экземпляра игры, пароль, если игра закрытая, максимальное количество игроков и т. д.). Если метод Host успешно завершается, то вы готовы начать отправлять и получать сообщения.
Получение сообщений БОЛЬШИНСТВО данных, которые вы будете передавать туда и обратно, в течение стандартной многопользовательской игры будут в вашем собственном формате. Кроме того, существуют стандартные сообщения, которые посылает вам DirectPlay и на которые нужно отвечать. Полный список типов сообщений здесь не приводится (его можно найти в документации по DirectPlay), но в табл. 16.2 можно найти базовый список сообщений, за которыми вам нужно смотреть.
DirectPIay Voice
281
Эти четыре сообщения представляют собой самые стандартные сообщения, которые вы, вероятно, увидите в сессии DirectPIay. Таблица 16.2. Стандартные сообщения DirectPIay Имя сообщения
Описание
DPN MSGID CREATE PLAYER
Это сообщение отправляется вам DirectPIay, когда новый игрок присоединяется к вашей игре. Когда вы получаете это сообщение, вы должны запросить имя игрока и другие данные, вызвав метод GetPeerlnfo интерфейса IDirectPlaySPeer, и использовать эту информацию, чтобы инициализировать данные игрока, относящиеся к игре
DPN MSGID DESTROY PLAYER
DirectPIay отправляет это сообщение вашему коду, когда игрок отсоединяется от вашей игры. Вы должны убедиться, что ваш код больше не ссылается на этого игрока
DPN MSGID RECEIVE
DirectPIay отправляет это сообщение вашему коду, когда он получает какие-то данные, относящиеся к игре, от другого игрока. Внутри сообщения располагается указатель на полученные данные; ваша работа состоит в том, чтобы преобразовать эта данные в тот формат, который вы определили, и обработать эти данные
DPN MSGID SEND COMPLETE
DirectPIay отправляет это сообщение вашему коду, когда данные, которые вы отправили, на самом деле ушли по проводу. В DirectPIay отправление данных обычно происходит асинхронно; это значит, что после того, как вы их поставили на отправку, они отсылаются в фоновом режиме, пока ваша игра делает что-то другое
При инициализации интерфейса IDirectPlaySPeer, одним из параметров, который вы должны обеспечить, является указатель на функцию обратного вызова (callback function), которую будет вызывать DirectPIay при поступлении сообщения. В этом отношении программирование DirectPIay немного похоже на программирование диалоговых окон Windows. Ваша функция обратного вызова получает в качестве аргументов ID сообщения и указатель на буфер сообщений. Ваша функция обратного вызова должна смотреть на сообщение и решать, что делать. Обычно, это реализуется посредством оператора switch, как показано в следующем коде. HRESULT WINAPI OnDirectPlayMessage ( PVOID pvUserCoritext, DWORD dwMessageld, PVOID pMsgBuffer } {
switch ( dwMessageld ) { case D P N M S G I D C R E A T E P L A Y E R :
//было получено сообщение о создании игрока HRESULT hr;
282
Глава 16 PDPNMSG_CREATE_PLAYER pCreatePlayerMsg; pCreatePlayerMsg = reinterpret_cast(pMsgBuffer); DWORD dwSize = 0; DPN_PLAYER_INFO* pdpPlayerlnfо = NULL; //делаем здесь остальное...
} break; //рассматриваем остальные случаи }
return(S_OK);
}
Если ваша функция обратного вызова корректно распознает сообщение, то вы должны вернуть S_OK. Вы можете возвращать другие значения в ответ на конкретные сообщения, как описано в документации DirectX.
Отправление сообщений Чтобы отправить какие-то данные другому компьютеру в многопользовательской игре, вы должны использовать метод SendTo интерфейса IDirectPlaySPeer. В нем совсем просто разобраться - в качестве аргументов он принимает ID игрока, которому вы хотите отправить данные (или заранее определенную величину DPNID_ALL_PLAYERS_GROUP для всех игроков), структуру DPNJ3UFFER_DESC, содержащую указатель на данные и число отправляемых байтов. Также ей требуются некоторые флаги и другие аргументы, которые вы можете использовать, чтобы точнее определить поведение функции. Например, вы можете использовать флаг DPNSEND_GUARANTEED, чтобы заставить DirectPlay гарантировать, что ваши данные получены нужным адресатом.
riomoku u синхронизация Одним из самых трудных аспектов программирования, используя DirectPlay, является то, что DirectPlay - это многопоточньш компонент. Одним из самых важных для понимания следствий этого, является то, что DirectPlay может вызывать вашу функцию обратного вызова одновременно из нескольких различных потоков. Это значит, что вы должны обеспечить корректную работу вашей функции обратного вызова с потоками. Если вы меняете глобальные данные (например, все, что не объявлено на стеке или объявлено глобально), эти изменения должны производиться внутри критической секции или какого-либо другого примитива синхронизации потоков.
DirectPlay Voice
283
Отсоединение Когда ваш игрок решает отсоединиться от вашей игры, вы должны обеспечить вызов метода Close интерфейса IDirectPlaySPeer, чтобы DirectPlay имел возможность сообщить всем остальным, что этот игрок уходит. Также вам нужно вызвать метод Close для lobby интерфейса IDirectPlaySLobbiedApplication. После того как вы закрыли оба интерфейса, вы можете освобождать их, вызывая их методы Release или используя макрос SAFE_RELEASE.
Использование CNetConnectWizard Предыдущие три этапа могут потребовать написания большого объема кода. Поняв это, Microsoft включила как часть DirectX SDK пару классов C++, созданных специально для облегчения реализации стандартных процедур по настройке DirectPlay и подсоединении к игре. Класс, который нам нужен, называется CNetConnectWizard и доступен в директории Samples вашей инсталляции DirectX. Он прекрасно справляется с работой по обеспечению вашему пользователю выбора поставщика услуг и присоединения к существующей игре или созданию новой. Единственным недостатком является то, что все это делается с помощью серии стандартных диалоговых окон. Ваша игра может предоставить выбор этих опции игроку, используя собственный GUI, с красиво выглядящими кнопками и бегунками. Простого пути, чтобы сделать это с помощью «мастера» (wizard) не существует, поэтому в таких случаях вам придется писать код самим. Но если вы не имеете ничего против использования стандартных диалоговых окон внутри вашей игры, вы можете использовать CNetConnectWizard, чтобы сэкономить время.
Создание и инициализация Обычно CNetConnectWizard создается динамически, как в последующем кеде, взятом из Chl6pl_VoiceChat. CNetConnectWizard* g_pNetConnectWizard = NULL; //«1апоаб» fliaaeiaiee g_pNetConnectWizard = new CNetConnectWizard( hlnstance, NULL, "Chl6pl_VoiceChat", &g_GameGUID ); g_pNetConnectWizard->Init( g_pDPPeer, g_pDPLobbiedApp );
Код создает новый CNetConnectWizard и вызывает его метод Init. Метод Init требует два интерфейса - один IDirectPlaySPeer и один IDirectPlaySLobbiedApplication.
284
Глава 16
Я специально не хочу говорить о лобби в общем и о IDirectPlaySLobbiedApplication в частности; лобби - это продвинутая возможность DirectPIay, с которой вам нужно работать только в том случае, если вы хотите, чтобы ваша игра запускалась из программы просмотра игр (game-browsing). Лобби - это механизм для передачи соединений и настроек игры от одной программы другой. Если вы заинтересовались, то вы можете подробнее познакомиться с ним в документации DirectX, но все, что вам нужно знать для этой главы,-это то, что CNetConnectWizard требует интерфейс лоббируемого приложения.
OGpaGomka получения фунКции обратного Вызова CNetConnectWizard может работать только в том случае, если он может обрабатывать сообщения DirectPIay. Это значит, что, когда DirectPIay использует функцию обратного вызова, вы должны передать методу MessageHandler класса CNetConnectWizard те же самые аргументы, что DirectPIay передал вашей функции обратного вызова. Интерфейс IDirectPlaySLobbiedApplication требует функции обратного вызова так же, как и интерфейс IDirectPlaySPeer, и CNetConnectWizard должен аналогично отследить эту функцию. Обе процедуры отслеживания показаны в следующем коде, взятом из Chl6pl_VoiceChat. HRESULT WINAPI OnDirectPlayMessage{ PVOID pvUserContext, DWORD dwMessageld, PVOID pMsgBuffer ) { switch( dwMessageld ) { case DPN_MSGID_CREATE_PLAYER: //вырезано для экономии места break; case DPN_MSGID_DESTROY_PLAYER: //вырезано для экономии места break; //другие сообщения } //даем «мастеру» возможность отреагировать на сообщение return g_pNetConnectWizard->MessageHandler( pvUserContext, dwMessageld, pMsgBuffer ); } HRESULT WINAPI OnDirectPlayLobbyMessage( PVOID pvUserContext, DWORD dwMessageld, PVOID pMsgBuffer ) { return g_pNetConnectWizard->LobbyMessageHandler{ pvUserContext, dwMessageld, pMsgBuffer };
285
DirectPIay Voice
Функция обратного вызова OnDirectPlayLobbyMessage для интерфейса IDirectPlaySLobbiedApplication не делает никакой обработки вообще - она просто передает все, что получила, напрямую «мастеру». Функция обратного вызова для интерфейса IDirectPlaySPeer, тем не менее, отслеживает определенные типы сообщений, обрабатывает их и после дает «мастеру» возможность сделать то же самое и вернуть какоелибо значение.
Установка значений по умолчанию и отображение «мастера» После того как вы создали CNetConnectWizard и дали ему возможность отслеживать вызовы ваших функций обратного вызова интерфейсов IDirectPlaySPeer и IDirectPlaySLobb led Application, вы готовы к выставлению значений по умолчанию, отображению его диалогов и запросов к пользователю о выборе. Следующий код показывает, как это делается. //выставляем значения по умолчанию g_pNetConnectWizard->SetPlayerName( "Anonymous" ); g_pNetConnectWizard->SetSessionName( "New Game" ); g_pNetConnectWizard->SetPreferredFrovider( "DirectPlayS TCP/IP Service Provider" ); //отображаем «мастер» соединений hr = g_j>NetConnectWizard->DoConnectWizard( FALSE ); if (FAILED(hr)) throw(hr, "Connection wizard failed."); if( hr == NCW_S_QUIT ) { return(O); }
Как вы видите, код устанавливает значения по умолчанию для «мастера», вызывая методы SetPlayerName, SetSessionName и SetPreferredProvider. Эти величины по умолчанию будут выставлены в диалоговых окнах, отображаемых пользователю (рис, 16.5 и 16.6) . ' , - г...
,- ii Wail (я Lobt? Co
Рис. 16.5. Диалог соединений CNetConnectWizard
286
Глава 16
Рис. 16.6. Диалог новой игры CNetConnectWizard
Все важное происходит в DoCоnnectWizard. Когда вы вызываете этот метод, отображаются диалоги «мастера» и пользователь совершает свой выбор. Метод DoConnectWizard возвращает HRESULT; если работа завершилась с ошибкой, значит, что-то пошло не так. Если HRESULT равен NCW_S_QUIT, то это значит, что пользователь решил выйти или закрыть «мастер» до того, как он завершил свою работу. Обычно это значит, что пользователь решил совсем покинуть многопользовательскую игру, так что вам нужно будет вернуть его к основному меню вашей игры или выйти, как в случае примера программы Chl6pl_VoiceChat. Если пользователь сделал все до конца, то класс CNetConnectWizard автоматически вызовет один из двух методов Host или Connect интерфейса IDirectPlaySPeer. Другими словами, он все сделает за вас! Если вы хотите сохранить величины, которые выбрал пользователь, чтобы они стали значениями по умолчанию, вы можете получить их, вызвав методы GetPIayerName, Gets ess ionName и GetPreferredProvider. После этого просто сохраните их в регистре или в файле настроек и прочитайте перед следующим запуском «мастера». Также вы можете вызвать метод IsHost, чтобы определить, является ли пользователь хостом новой игры.
Завершение работы «мастера» Когда вы закончили работу с «мастером», завершение работы сводится к вызову метода Shutdown и последующего удаления собственно экземпляра объекта (через вызов delete или использование макроса SAFE_DELETE).
Программирование DirectPlay Voice После того как вы разобрались с коротким обзором DirectPlay, вы можете начать работать с целью этой главы - использованием DirectPlay Voice для реализации голосового общения между игроками в реальном времени в вашей игре.
DirectPlay Voice
287
Во многом DirectPlay Voice - это зеркальное отражение DirectPlay. Существует функция обратного вызова для сообщений DirectPlay Voice, так же, как и различные интерфейсы для голосовых клиентов (IDirectPlayVoiceClient) и серверов (IDirectPlay Voice Server). Также доступен маленький интерфейс IDirectPlayVoiceTest всего лишь с одним методом — CheckAudioSetup. Этот метод запускает «мастер» аудио настроек DirectPlay Voice, чтобы пользователи могли настроить громкости записи и воспроизведения и указать, какое аппаратное обеспечение (вход микрофона) использовать для записи (рис. 16.7). Welcome to the Sound Hardware Test Wizard This wizaid leitj you tound hardware 10 make sue il can play sounds and eapiuie you часе whfc you ав playing online garnet Voji cotrfiitefi sound haidviate Playback. USB Autfo Device C-Medu Wave Device Та begin Ihe lesi, cb;e any progisras that «e lurning, and Unn ckk He «I
Рис. 16.7. «Мастер» тестирования звукового аппаратного обеспечения
Инициализация DirectPlay Voice В DirectPlay Voice не существует пирингово (peer-to-peer) интерфейса; есть только IDirectPlayVoiceServer и IDirectPlayVoiceClient. Это значит, что перед инициализацией DirectPlay Voice вы должны выяснить, являйтесь ли вы голосовым сервером или клиентом. Нужно сказать, что DirectPlay сервер совсем не обязательно должен быть сервером DirectPlay Voice, и ничто не останавливает клиента DirectPlay от того, чтобы быть сервером DirectPlay Voice. Тем не менее, проще всего сделать выбор, используя вашу роль относительно DirectPlay: если вы хост при прямом соединении или вы сервер DirectPlay, то тогда вы и голосовой сервер; иначе вы голосовой клиент.
Глава 16
288
Голосовые сообщения При создании вашего интерфейса DirectPlay Voice вы указываете функцию обратного вызова для получения сообщений DirectPlay Voice. Табл. 16.3 показывает стандартные сообщения, которые вы будете получать. Таблица 16.3. Стандартные сообщения DirectPlay Voice ID сообщения DirectPlay Voice
Описание
DVMSGID CREATEVOICEPLAYER
Существующий игрок DirectPlay, только что активировал DirectPlay Voice и стал игроком DirectPlay Voice. Внутри сообщения содержится ID игрока, который начал использовать голос. Вам будет нужен этот ID для установки любых дополнительных параметров игрока для использования в вашей игре DirectPlay Voice
DVMSGID DELETEVOICEPLAYER
Существующий игрок DirectPlay только что прекратил использовать DirectPlay Voice. Обратите внимание, что это не значит, что он больше не играет; он может все еще оставаться нормальным игроком DirectPlay
DVMSGID PLAYERV01CESTAPJ
DirectPlay Voice отправляет вам это сообщение при начале воспроизведения входящего голосового потока
DVMSGID PLAYERVOICESTOP
DirectPlay Voice отправляет вам это сообщение при окончании воспроизведения входящего голосового потока
DVMSGID RECORDSTART
DirectPlay Voice отправляет вам это сообщение при начале записи голосового потока от локального игрока
DVMSGID RECORDSTOP
DirectPIay Voice отправляет вам это сообщение при окончании записи голосового потока от локального игрока
DVMSGID LOSTFOCUS
DirectPlay Voice отправляет вам это сообщение, когда локальный игрок потерял возможность записывать звук. Обычно это значит, что игрок переключился на другое приложение и ваше приложение больше не управляет аппаратурой звукозаписи
DVMSGID GAINFOCUS
DirectPlay Voice дает вам знать, посылая это сообщение, что игрок снова может отправлять звук. Обычно это значит, что игрок сделал вашу игру текущим заданием и у вас снова есть контроль над аппаратурой звукозаписи
DirectPIay Voice
289
npoBepka Ваших зВуКоВых nacmpoek Чтобы получить максимум от голосового общения в реальном времени, игроку очень важно правильно настроить свой микрофон и динамики. Вы можете запустить диалог настройки в любое время, создав интерфейс IDirectPlayVoiceTest и вызвав его единственный метод CheckAudioSetup. Задавая аргументы метода CheckAudioSetup, вы можете выбрать устройство для воспроизведения и для записи.
Полу- и полнодуплеКсные зВуКоВые Карты Аппаратное обеспечение для звукозаписи делится на две большие категории карты, которые поддерживают полнодуплексное аудио, и те, которые не поддерживают. Полнодуплексные карты могут записывать и воспроизводить звук одновременно. Полудуплексные карты не могут записывать, когда воспроизводят звук, и наоборот. К сожалению, DirectPIay Voice не поддерживает звукозапись полудуплексной аппаратурой. Все пользователи с полудуплексными звуковыми картами смогут слышать голоса других игроков, но не смогут говорить сами. Подавляющее большинство современных карт являются полнодуплексными, так что обычно это не проблема, но вы можете определить, использует пользователь полу- или полнодуплексную звуковую карту, используя метод GetSoundDeviceConfig клиентского или серверного интерфейса DirectPIay Voice. Этот метод возвращает структуру, заполненную всевозможной информацией об аппаратном обеспечении, включая ту, полу- или полнодуплексная карта используется.
Использование CNetVoice Как и CNetConnectionWizard, CNetVoice является вспомогательным классом, созданным Microsoft и распространяемым как часть примеров DirectX SDK. Он создан, чтобы скрыть все сложности работы с DirectPIay Voice, и он очень хорошо справляется со своей задачей. Для использования CNetVoice вы все нуждаетесь, что бы вам проиллюстрировали это, для этого выставим некоторые значения по умолчанию и вызовем его метод Init, как показано в следующем коде. g_pNetVoice = new CNetVoice( OnVoiceClientMessage, OnVoiceServerMessage ); //выставление значений по умолчанию опциям DirectPlayVoice DVCLIENTCONFIG config; 10-1-190
29О
Глава 16
SeroMemory( sconfig, sizeof(config} ); config.dwSize = sizeof(config); config.dwFlags = DVCLIENTCONFIG_AUTGVOICEACTIVATED I DVCLIENTCONFIG_AUTORECORDVOLUME; config.lPlaybackVolume = DVPLAYBACKVOLUME_DEFAULT; config.dwBufferQuality = DVBUFFERQUALITY_DEFAULT; config.dwBufferAggressiveness = DVBUFFERAGGRESSIVENESS_DEFAULT; config.dwThreshold = DVTHRESHOLD_UNUSED; config.lRecordVolume = DVRECORDVOLUME_LAST ; config.dwNotifyPeriod = 0; GUID codecguid = DPVCTGUID_DEFAULT; //инициализация голосового класса g_pNetVoice->Init( hDlg, g_pNetConnectWizard->IsHostPlayer(), TRUE, g_pDPPeer, DVSESSIONTYPE_PEER, ficodecguid, &config );
Как можно увидеть, вы передаете имена двух ваших функций обратного вызова конструктору CNetVoice. Также обратите внимание, что большинство членов структуры DVCLIENTCONF1G могут просто быть выставлены в их значения по умолчанию. После того как это было сделано, все, что остается, - это задать обработчики сообщений клиента и сервера. После не забудьте удалить ваш объект CNetVoice.
Подсчет ссылоК на объ-ekm uepoka В примере программы Chl6pl_VoiceChat вы увидите, что голосовые настройки игрока и стандартные настройки игрока объединены в один объекг - CPlayer. Это обычная вещь - большинство игр предпочитают использовать один класс или структуру, которая представляет игрока, включая основные свойства DirectPlay, такие, как его имя, вместе с голосовыми свойствами DirectPlay Voice (такими, как полу-/полнодуплексная звуковая карта). Единственная вещь, за которой вам нужно следить, — это то, что вы не удалите структуру игрока, когда он еще не прекратил играть, а только прекратил использовать DirectPlay Voice. Чтобы такого не случилось, пример программы Chl6pl_VoiceChat использует счетчик ссылок. При получении сообщения о новом игроке, пример программы создает новый объект игрока и устанавливает его счетчик ссылок равным единице. При получении сообщения «создать голос игрока» от DirectPlay Voice программа ищет соответствующую структуру игрока (сохраненную в STL карте DPNID для объектов CPlayer) и увеличивает ее счетчик ссылок. Таким образом, к тому времени, когда все создано, все объекты игроков имеют значение счетчика ссылок равным двум - одна ссылка для DirectPlay и одна для DirectPlay Voice.
DirectPIay Voice
291
Когда приходит время удалять объект, код начинает с уменьшения счетчика ссылок объекта. Только тогда, когда счетчик ссылок становится равным нолю, объект на самом деле удаляется и изымается из карты. Если вы построите все так, то не будет иметь значение, какое сообщение «удалить игрока» вы получите первым. Объект удаляется только тогда, когда и DirectPIay, и DirectPIay Voice приняли сообщения удаления.
Голоса kak буферы 3D aByka Одна из приятных особенностей DirectPIay Voice состоит в том, что у вас есть возможность привязать входящие голосовые потоки к источникам 3D звука. Таким образом, если другой игрок стоит слева от вас, когда он говорит, вам будет казаться, что его голос идет слева. Вы можете настроить это, вызвав метод Create3DsoundBuffer интерфейса клиента DirectPIay Voice (TDirectPlay VoiceCIient). Этот метод позволит вам привязать источник 3D звука к конкретному ID игрока. После этого вам нужно просто совместить позицию буфера с текущей позицией игрока, и DirectX Audio сделает остальное. Совет. Обратите внимание, что объединение DirectPIay Voice с 3D звуком может быть произведено только в там случае, если вы не используете топологию смешивающего сервера. В топологии смешивающего сервера все потоки смешиваются вместе до того, как они достигнут вашего компьютера, а сервер, который производит смешивание, не имеет никакого понятия, где, относительно вас, расположены игроки.
Заключение В этой главе я представил вам краткое введение в DirectPIay и DirectPIay Voice. Вы должны понять, что DirectPIay обладает множеством других полезных возможностей, которые могут сделать вашу игру еще лучше. Я считаю, что вы должны познакомиться с этими возможностями самостоятельно и опробовать их при написании ваших собственных приложений, использующих DirectPIay.
ГлаВа 17
Визуализация aByka Да, я знаю, о чем вы думаете: что глава про визуализацию делает в книге по программированию игрового аудио? Как никак, визуализация звука - будь это просто волна, анализатор спектра или декоративные, управляемые музыкой, эффекты, популярные в нынешних МРЗ проигрывателях, - это не то, что обычно ассоциируется с играми или игровым программированием. Тем не менее, визуализация звука включает в себя множество фундаментальных понятий игрового аудио. Методики, лежащие в основе визуализации звука, также лежат в основе некоторых продвинутых методик обработки звука, включая сжатие звука, фильтрацию звука и т. д. Правильное понимание визуализации звука, и особенно дискретного преобразования Фурье (Discrete Fourier Transform), жизненно важно для того, чтобы стать гуру в вопросах игрового аудио. Кроме того, это захватывающее путешествие через биологию, математику, битье посуды и поиски внеземного интеллекта. Когда вы осознаете, насколько мало вещей подходит под это описание, вы поймете, почему я не мог закончить книгу, не истратив немного времени на это. В этой главе вы узнаете, как написать анализатор спектра. По существу, анализатор спектра - это гистограмма, которая показывает вам, какие частоты включает в себя звук и насколько громкими являются эти частоты. Рис. 17.1 это изображение примера программы главы, анализатора спектра - вероятнее всего, вы видели подобную «частотную гистограмму» в вашем любимом МРЗ проигрывателе.
ДисКретное преобразование Фурье Основа анализатора спектра проста. У вас имеется последовательность чисел, представляющих собой ваши звуковые выборки (или, что более точно, представляющих собой амплитуду звука в различные моменты времени). Вы хотите получить другую последовательность чисел, которая представляет собой амплитуды раз-
Визуализация
sByka
293
Рис. 17.1. Изображение примера программы Chl7pl_Visuals личных частот, содержащихся в первоначальной выборке. После того как эти амплитуды получены, вы можете отобразить их любым желаемым способом. Вы можете нарисовать гистограмму, где каждый столбец соответствует определенной частоте, а высота этого столбца соответствует амплитуде частоты (как и делают большинство популярных МРЗ проигрывателей). Или вы можете нарисовать двухмерный график, откладывая частоту по вертикальной оси и время по горизонтальной и используя цвет для обозначения амплитуды. Повторю еще раз: после того как вы получили массив амплитуд частот, вы вольны делать, что угодно - это лишь вопрос программирования GUI. Одним из способов получения массива амплитуд частот является дискретное преобразование Фурье или ДПФ (DFT). Значительная часть этой главы будет посвящена разбору теории ДПФ. Совет. Говоря математическими терминами, когда вы пишете анализатор спектра, вы на самом деле преобразуете вашу выборку из временной области в частотную область. Другими словами, вы меняете ось х вашего графика. Вместо того чтобы откладывать по осих время, вы откладываете частоту.
Резонанс Я уверен, что вы знаете, что такое резонанс: оперный певец может разбить винный бокал, если будет петь на его резонансной частоте. По сути, колебания, вызванные голосом оперного певца, заставляют винный бокал колебаться все сильнее и сильнее, пока, в конце концов, сила колебаний не превысит прочность стекла и оно не разобьется. Вы можете не понимать, как сила наросла до такой степени, что стекло разбилось. Чтобы понять это, представьте себе большой, тяжелый серебряный шар, привязанный к потолку крепкой веревкой. Интуитивно, вы понимаете, что неболь-
294
Глава 17
шая сила, прилагаемая точно вовремя, может раскачивать шар все выше и выше. Неважно сколько весит шар, все, что вам нужно сделать, это чуть-чуть подталкивать его тогда, когда ему это нужно, и, в конце концов, вы его раскачаете. Теперь, представьте, что вместо шара постоянно подталкивают молекулу, внутри винного бокала, и подталкивают не парой человеческих рук, а волнами сжатого воздуха, также известными как звуковые волны, идущими сквозь воздух. Если каждая звуковая волна будет прибывать точно в нужное время, то они смогут «раскачивать» молекулу все дальше и дальше относительно ее первоначального расположения. К тому же все эти звуковые волны будут раскачивать множество молекул в стекле, заставляя их колебаться в разных направлениях, пока, в конце концов, стекло не разобьется.
Kak Вы слышите Именно это свойство резонанса дает вам возможность слышать. Ваше ухо выступает в качестве биологического анализатора спектра. Внутри вашего уха есть орган, который называется улитка, формой напоминающий раковину. Внутри улитки есть мембрана, которая называется основной. Основание основной мембраны тонкое и жесткое, что позволяет ему резонировать с высокочастотными звуками. Другой конец мембраны, верхушка, широкий и эластичный, что делает его резонансную частоту низкой. Между двумя концами различные части мембраны имеют различные резонансные частоты. Мембрана покрыта нервными окончаниями, и когда определенная частота заставляет резонировать определенный участок мембраны, эти колебания заставляют нерв послать импульс мозгу, и вы «слышите» эту частоту.
МатематичесКое задание резонанса Синусоидальные волны интересны тем, что сумма их полупериодов всегда ноль — другими словами, волны проводят столько же времени, находясь ниже нуля, сколько и выше нуля. Это значит, что в целом, если вы посмотрите значения амплитуды синусоидальной волны через определенные периоды времени, а потом сложите все эти значения вместе, то вы получите ответ, близкий к нулю. Положительные и отрицательные значения в целом скомпенсируют друг друга. Тем не менее, все меняется, если мы начинаем смотреть значения амплитуды с частотой равной частоте волны. Не важно, что вы выберете в качестве начальной точки, все равно в итоге вы получите ответ, отличающийся от нуля. Если вы выберете начальную точку так, чтобы волна была больше нуля, то тогда каждая следующая
Визуализация sByka
295
амплитуда, на которую вы посмотрите, будет в точности равняться амплипуде в начальной точке, что вызовет все больший и больший рост вашей суммы со временем. Аналогично, если вы выберите точку, лежащую ниже нуля, с каждой новой амплитудой ваша сумма будет становиться все меньше и меньше. Это тот же резонанс, но уже математически. Это значит, что если вы хотите выяснить, содержит ли данная волна волну с определенной частотой, все, что вам нужно сделать, - это умножить все точки одной волны на все точки другой и просуммировать результаты всех перемножений. Это может быть записано таким кодом. f l o a t sinAmp = 0 . O f ; //итоговое значение амплитуды f o r (int k = 0; k < numsamples; k++) { f l o a t x = 2 . O f * M_PI * ( f l o a t ) f r e q * ( f l o a t ) k / ( f l o a t ) n u m s a m p l e s ; sinAmp += s a m p l e [ k ] * s i n ( x ) ; }
Это фиктивный код, вы не найдете его в примере программы Chl7pl_Visuals. Мы считаем, что numsamples содержит полное число выборок исходной волны1 и что sample - это массив чисел с плавающей точкой, лежащих между -1 и 1. Код проходит по всему массиву выборок, умножая каждую выборку на sin(x) и прибавляя полученный результат к sinAmp. Когда проход завершен, sinAmp содержит амплитуду волны (частоты freq), содержащейся в источнике. Чтобы вычислить х, подаваемый на вход функции sin, код умножает иссле;гуемую частоту (freq) на 2л (удвоенное число «пи», или приближенно 6,28). Функция sin ожидает на вход угол в радианах (это определенная величина для измерения углов, при этом нулевой угол равен 0 радиан, а полный 2л радиан, вместо 0 и 360° соответственно). И в конце, код умножает полученную величину на k (наш счетчик цикла) и делит на общее число выборок (numsamples). Это тот множитель, которые позволяет нам получить значения в разных точках волны, когда мы проходим по циклу когда k равно нулю, х равен нулю. Когда k равняется numsamples, их отношении равно единице и х становится равен 2л:, умноженным на freq. Именно этот механизм позволяет нам получить точки, воображаемой волны частоты freq, которые вы потом умножаете на соответствующую точку исходной волны. После того как вы перемножили соответствующие точки исходной волны и воображаемой волны частоты freq и сложили их все вместе, вы (каким-то магическим способом) получаете амплитуды волны частотой freq, содержащейся в исходной волне. По сути, вы математически смоделировали резонанс! 1 Обратите внимание на это важное допущение, автор полагает numsamples априори содержащим корректное количество выборок. - Примеч. науч. ред.
296
Глава 17
Одним синусом нам не обойтись Важно понять, что когда вы «резонируете» исходную волну с синусоидальной волной, определенной амплитуды, конечная амплитуда зависит от фазы синусоидальной волны. Фаза - это мера сдвига графика функции вправо или влево. Представьте, что у вас есть две исходных синусоидальных волны с частотой 440Гц и амплитудой 5. Фаза волны 1 равняется нулю, а фаза волны 2 равняется «пи»/2 радиан. Если вы будете «резонировать» эти волны, то результирующие амплитуды получатся разными. Амплитуда для волны 1 получится больше амплитуды для волны 2. Волна 1 имеет экстремальные значения 5 и -5, при этом резонирующая волна принимает экстремальные значения 1 и -1, с другой стороны, в тех точках, где принимает свои экстремальные значения волна 2, резонирующая волна равна нулю. Другими словами, резонирующая волна «пропускает» экстремальные значения волны 2 из-за того, что она просто сдвинута (или, говоря техническим языком, имеет другую фазу). Когда исходная волна принимает максимальное значение, резонирующая волна равна нулю; когда принимает максимальное значение резонирующая волна, нулю равна исходная волна. Это приводит к тому, что итоговая амплитуда, меньше, чем должна бы быть. Чтобы решить, эту проблему, вы используете ко синусоидальную волну. Эта волна точно такая же, как и синусоидальная, за исключением того что она в нуле принимает значение единица, в то время как синус в нуле равен нулю (рис. 17.2). Другими словами, косинус - это синус с другой фазой. Совет. На сомам деле, можно математически доказать, что любая периодическая функция с постоянной частотой и амплитудой, вне зависимости от фазы, может быть представлена суммой синуса и косинуса. Я не буду предоставлять здесь доказательство этого факта, но предлагаю вам изучить ссылки приведенные на вашем CD.
Если вы «срезонируете» исходную волну, и с косинусом и с синусом, то вы гарантированно получите одну и ту же итоговую амплитуду, вне зависимости от фазы («величины сдвига») вашей исходной волны. Синус и косинус как бы прикрывают друг друга - когда один из них равен нулю, второй принимает максимальное значение, так что вы гарантированно никогда не «пропустите» максимумы вашей исходной волны. Используя и синус, и косинус, мы получаем следующий код. float sinamp = O.Of; float cosamp = O.Of; for (int k = 0; k < numsamples; k++) { float x = 2.Of * M_PI * (float)freq * (float)k / (float) numsamples; sinamp -= sample[k] * sin(x); cosamp += sample[k] * cos(x}; } float amp = sqrt(sinamp*sinamp + cosamp*cosamp);
Визуализация aByka Синусоидальная волна
297 Косинусоидал ьная волна
Рис. 17.2. Косинус - это синус с другой фазой
Этот код очень похож на тот, который вы видели немного раньше, за исключением того что кроме синуса используется еще и косинус (я вычитаю из sinamp, чтобы синус, как и косинус, начинался ниже нуля). Также добавилась строчка, которая вычисляет amp как квадратный корень из суммы квадратов sinamp и cosamp. На самом деле, это всего лишь замаскированное вычисление расстояния. Прецположим, что вы хотите найти расстояние от начала координат до точки (3, 4). Чтобы найти расстояние от начала координат до этой точки, вы возводите обе компоненты в квадрат, потом складываете их и извлекаете квадратный корень из суммы. 3 в квадрате - это 9,4 в квадрате - это 16,9 плюс 16-это 25, так что итоговое расстояние - это квадратный корень из 25, т. е. 5. Если вы нарисуете двухмерный график, откладывая sinamp по одной оси и cosamp по другой оси, то вы можете посчитать итоговое усиление как расстояние до точки (cosamp, sinamp). Если вы чего-то не понимаете, то есть множество математических рассуждений, объясняющих, почему это так - посмотрите ссылки, которые я поместил на ваш CD; разбираться в этом очень и очень интересно, при этом вы познакомитесь с использованием мнимых чисел lit некоторыми другими причудливыми понятиями. И теперь, наконец-то, у вас есть самые важные знания, необходимые для реализации анализатора спектра: вы знаете, как выяснить, «насколько много» конкретной частоты содержится в wave файле. Теперь, после того как вы это узнали,
298
Глава 17
все, что вам нужно сделать, - это произвести аналогичные действия, используя несколько различных значений freq, и вы получите на выходе массив, подходящий для графического отображения.
Число HaukBucma Единственное, что осталось решить, - это какие частоты использовать. Чтобы решить это, вы должны сначала понять, что такое число Найквиста. Число Найквиста, в данном случае, представляет собой максимальную частоту, которая может быть корректно захвачена с заданной заранее частотой дискретизации2. К примеру, представьте себе исходную волну с частотой 10Гц, захваченную с частотой дискретизации 20Гц. Я взял такие небольшие числа только для того, чтобы проиллюстрировать понятие - как вы знаете, волны обычно захватываются с частотой дискретизации 22,Q50Hz или 44,100Hz, но для этого примера давайте отбросим соображения качества и будем использовать частоту 20 Гц. Захват 10Гц волны с частотой дискретизации 20Гц вряд ли даст нам качественный звук, но, тем не менее, итоговый результат будет корректен. Теперь, представьте, что вы пытаетесь захватить волну частоты 30 Гц с частотой дискретизации 20Гц. Набор выборок, полученный вами, не будет иметь никакого смысла, потому что волна осциллирует быстрее, чем вы способны это заметить. Это называется эффект наложения (aliasing) и является звуковым аналогом визуального эффекта, который заключается в том, что быстро вращающиеся лопасти вертолета и автомобильные покрышки кажутся крутящимися назад. Конечно, на самом деле они назад не крутятся - просто частота визуальной дискретизации слишком мала, чтобы заметить настоящее движение, и из-за этого создается иллюзия вращения в противоположную сторону. Число Найквиста - это максимальная частота, которую вы можете дискретизировать на заданной частоте дискретизации без получения чересчур сильных эффектов наложения. Оно всегда равняется ровно половине частоты дискретизации. Таким образом, при частоте дискретизации 44100Гц максимальная частота, которую вы можете захватить, 22050Гц.
Вспомним теорему Котелышкова (Найквиста): произвольный сигнал, спектр которого не содержат частот выше FB, Гц, может быть полностью восстановлен, если известны отсчетные значения этого сигнала, взятые через равные промежутки времени 1/(2Рв) с. - Примеч. науч. ред.
Визуализация aByka
299
fluckperriHoe преобразование Фурье Теперь, в конце концов, вы собрали все кусочки головоломки вместе. Вы знаете, как выяснить, содержит ли волна определенную амплитуду, и теперь, благодаря числу Найквиста, вы знаете максимальную частоту, на которую вам нужно смотреть. Все, что осталось, - это добавить еще один цикл, к уже известному коду. for (int bin = 0; bin <= numsamples/2; bin++) { float cosAmp = O.Of; float sinAmp = O.Of; for (int k == 0; k < numsamples; k+ + ) ( float x = 2. Of * M_PI * (float)bin * (float) k / (floatlnumsamples; sinAmp += m_Input[k] * sin(x); cosAmp += m_Input[k] * cos (x); } m__AmpOutput [bin] = sqrt (sinAmp*sinAmp + cosAmp*cosAmp) ; }
Переменная freq теперь называется bin, цикл проходит по всем частотам от нуля до частоты Найквиста, и итоговая амплитуда теперь является массивом амплитуд для каждой частоты.
Анимация анализатора cnekmpa Этот код даст вам амплитуды различных частот, содержащихся во входной волне. Чтобы реализовать анимацию анализатора спектра, все, что вам нужно сделать, - это рассчитать амплитуды частот для небольшого кусочка входной волны, начиная от той выборки, что звучит в динамиках, и заканчивая несколькими байтами позже (512 будет хорошим выбором). Вы узнаете об этом подробнее в последующих разделах.
Пример программы Поздравляю, вы пробрались через дебри теории, окружающей анализатор спектра. Теперь настало время засучить рукава и сделать его! Последующие разделы продемонстрируют вам работу кода, содержащегося в примере программы Chl7pl_Visuals. Рис. 17.1 демонстрирует вам вид примера программы Chl7pl_Visuals. Как вы можете увидеть, графический интерфейс пользователя (GUI) минимален, он состоит всего из двух кнопок, просмотра и воспроизведения, расположенных сверху большого прямоугольника, где отображается анализатор спектра.
ЗОО
Глава 17
Saapyaka файла Пример программы, как и его собрат из главы 2, использует DirectSound, низкоуровневый интерфейс DirectX Audio. Я выбрал для использования DirectSound в основном по той причине, что в DirectSound гораздо проще, чем в DirectMusic, понять, какой байт выборки звучит в колонках в данный момент времени. К тому же так окончание книги получается симметричным - мы начали книгу с низкоуровневого API и в конце книги мы возвращаемся к низкоуровневому API. К сожалению, использование DirectSound подразумевает самостоятельную загрузку файла — вы не можете просто загрузить его в сегмент DirectMusic. Чтобы немного облегчить себе задачу, вы можете использовать код, содержащийся в CWAVFile, для прочтения файла. . CWAVFile wavfile; wavfile.Load(filename); CreateSecondaryBuffer(ig_pWAVBuffer, wavfile); unsigned char *ptrl = NULL; unsigned long lenl = 0; g_pWAVBuffer->Lock(0, 0, (void **}&ptrl, Slenl, NULL, NULL, DSBLOCK_ENTIREBUFFER); merocpy (ptrl, wavfile .GetData () , wavf ile.m_DataLen) ; g_pWAVBuffer->Unlock{ptrl, lenl, NULL, NULL);
Этот кусок кода был взят из функции LoadSound программы Chl7pl_Visual. Код загружает WAV в wavfile, закрывает вторичный буфер DirectSound, заполняет его содержимое и открывает его (обратитесь ко второй главе, если вам нужно вспомнить, как открыть и закрыть буфер DirectSound).
CDiscreteFourierTransform Chl7pl_Visuals основывается на новом классе - CDiscreteFourierTransform. Этот класс был создан, чтобы содержать все данные и методы необходимые для проведения дискретного преобразования Фурье. Вот его объявление. class CDiscreteFourierTransform { public: CDiscreteFourierTransform(int bufsize, int samplerate, int bitspersample, int channels); virtual ~CDiscreteFourierTransform(); void LoadlnputData(unsigned char *data); void PerformSlowFourierTransformO ; float GetAmp(int ndx) ( return(ndx > m_BufSize ? 0.0 : m_AmpOutput[ndx]); } float GetFreqfint ndx) ( return(ndx > m BufSize ? 0.0 :
Визуализация sByka
301
m_FreqOutput [ndx] ) ; } int m_SampleRate; int mJtfumChannels; int m_BitsPerSample; protected: int rn_BufSize; float *m_Input; f l o a t *rn_FreqOutput; f l o a t *m_AmpOutput; };
Ничего особенно сложного - основными членами являются три указателя: m_Input, m_FreqOutput и m_AmpOutput. Это динамические массивы чисел с плавающей точкой - их размер зависит от размера буфера (m_BufSize). Массив m_Input используется для хранения массива входных выборок. Массив m_AmpOutput используется для хранения амплитуд частот - сердца анализатора спектра. m_FreqOutput сообщает коду частоту по заданному номеру элемента массива. Это необходимо, чтобы добавлять метки в анализаторе спектра.
Создание массивов Массивы элементов с плавающей точкой создаются при создании объекта, что видно из следующего кода. CDiscreteFourierTransforra: :CDiscreteFourierTransform( int bufsize, int samplerate, int bitspersample, int channels) m_Buf Size (bufsize) , rn_SampleRate (samplerate) , m_BitsPerSample {bitspersample) , m_NumChannels (channels) i //занимаем место под буферы m_Input = new float [bufsize] ; m_FreqOutput = new f loat [ (bufsize/2 ) +1 ] ; m AmpOutput = new f loat [ (bufsize/2) +1] ; /7мы уже можем посчитать все необходимые частоты for (int bin=0; bin < m_BufSize/2; bin++) { m_FreqOutput [bin] = (float)bin * m_SarnpleRate / m_BufSize;
:
Также обратите внимание, что код использует возможность создания массива частот m_FreqOutput. Так как этот массив остается постоянным, это лучшее время для вычисления его составляющих.
ЗО2
Глава 17
ЗагрузКа значений Когда клиентский код хочет обновить анализатор спектра, он начинает с загрузки нового набора величин в CDiscreteFourierTransform. Эти значения - это часть wave файла, начинающаяся с выборки, звучащей в динамиках, и имеющая размер BUFFERSIZE выборок (целочисленная постоянная равная 512). Перед тем как производить расчет; ДПФ, класс CDiscreteFourierTransform должен преобразовать массив выборок в диапазоне 0-255 (0-65535 для 16-битных выборок) в величины с плавающей точкой в диапазоне -1,0 до 1,0. Это делается следующим образом. void CDiscreteFourierTransform::LoadInputData(unsigned char *data} { // преобразование из 0-255 в -1..1 if (m_BitsPerSample ==8) ( for {int q=0; q < m_BufSize; q++) { float d = data[q*m_NumChannels] ; m_Input[q] = (d-127.0f)/127.Of; if (m_Input[q] < -l.Of) m_Input[q] = -l.Of; if (iti_Input[q] > l.Of) m_Input[q] = l.Of; } } else if {m BitsPerSarnple == 16) ( short Tnt *sidata = reinterpret_cast<short int *>(data); for (int q=0; q < m_BufSize; q++) { float d = sidata[q*m_NumChannels]; m_Input[q] = (d-32767.0f)/32767.Of; if (m_Input[q] < -l.Of) m_Input[q] = -l.Of; if (m_Input[q] > l.Of} ra_Input[q] = l.Of; } } else { //не поддерживается for {int q=0; q < m_BufSize; q++J { m_Input[q] = O.Of,- ) } 1
Как можно увидеть, здесь используются некоторые фокусы с указателями. Код рассматривает входящую переменную data или как массив 8-битных значений, или как массив коротких целых (short int- 16-битные значения), в зависимости от величины m_BitsPerSample. Обратите внимание, что используется m_JNumChannels, так что при стереозвуке используется только первый канал.
Визуализация sByka
303
Расчет ДПф И вот, тот момент, которого все ждали. После того как массивы настроены, массив m_Input заполнен величинами с плавающей точкой, клиентский код вызывает метод PerformSlowFourier Trans form, чтобы рассчитать ДПФ и выдать на выход массив m_AmpOutput. void CDiscreteFourierTransform: : PerformSlowFourierTransforrn( ) { for (int bin = 0; bin <= m_BufSize/2; bin++) ( float cosAmp = O.Of; float sinAmp = O.Of; for (int k = 0; k < m_BufSize; k++) { float x = 2. Of * M_PI * (float)bin * (float)k / ( float }m_Euf Size; sinAmp += rn_Input[k] * sin(x); cosAmp += m_Input[k] * cos (x) ; } m_AnipOutput [bin] = sqrt (sinAmp*sinAmp + cos Amp* cosAmp );
К этому моменту для вас не должно быть ничего нового в этом коде. Он основан на теории, которую вы изучали в начале главы.
Визуализация
графиКа
И вот мы подошли к коду, который использует массив m_AmpOutput класса CDiscreteFourierTransform, чтобы визуализировать анализатор спектра. void RenderSpectrum(HDC hdc, HWND hDlg) { HWND framewin = GetDlgltem (hDlg, IDC_FRAME); RECT framerect; int framewidth, f rameheight; GetWindowRect (framewin, Sframerect) ; ScreenToClient (hDlg,- (POINT * ) if ramerect . left ) ; ScreenToClient (hDlg, (POINT *) Sframerect . right ); framewidth = framerect . right - framerect . left; frarneheight = framerect .bottom - framerect. top; //рисуем прямоугольники int n=0; for (float x=framerect . left; x < framerect . right ; x += framewidth / {( float ) BUFFERSISE/2 . Of ), n++) ( RECT barrect; barrect.left = x; barrect. right = x + (framewidth / ( (float) BUFFERSIZE/2 . Of )}; barrect .bottom = framerect .bottom;
304
Глава 17 barrect.top = framerect. bottom (g_FT->GetAmp(n) / 50. Of * {float} frameheight) ; if (barrect.top < framerect . top) { barrect.top = framerect „top; } FillRect (hdc, &barrect, (HBRUSH) GetStockObject (GRAY_BRUSH) ) ; //заполняем верхнюю часть черным if (barrect.top > framerect . top) { barrect .bottom = barrect.top; barrect.top = framerect .top; FillRect (hdc, Sbarrect, (HBRUSH) GetStockObject (BLACK_BRUSH) ) ; } 1 . }
Это в чистом виде код Win32 API GDI, так что он требует некоторых знаний, но в целом он не делает ничего сложного. Он просто проходит окно для рисования слева направо, вычисляя высоту каждого полоски и рисуя два прямоугольника: один серый (низ планки) и один черный (верх планки). Таким образом, мы немного понижаем мерцание, возникающее из-за того, что мы не очищаем прямоугольник, а просто рисуем поверх него. Если вам так не нравится, то я советую вам изменить внешний вид программы в лучшую стороргу. Обычно полоски закрашивают градиентом от красного к зеленому, делая анализатор спектра более красочным.
С тем кодом, который я представил в примере программы этой главы, существует одна большая проблема: он очень медленный! Я пытался сделать код как можно более доступным для понимания, но это потребовало от меня отказа от всевозможных оптимизаций, что сделало код пригодным только для обучения. Даже небольшое значение BUFFERSIZE равное 1024 загрузит работой все компьютеры, кроме самых новых! Чтобы получить какую-либо выгоду от ДПФ, необходимо его ускорить. К счастью, в 1965 году Кули и Турки (Cooley and Turkey) изобрели поразительный алгоритм, которые увеличивает скорость ДПФ на несколько порядков. Этот алгоритм называется быстрым преобразованием Фурье, или БПФ. БПФ делает то же, что и дискретное преобразование Фурье, только во много, много раз быстрее.
Визуализация sByka
3O5
Совет. Если вы используете популярную экранную заставку экрана SETI@Home, вы могли заметить, что она тратит .много времени на вычисление БПФ (FFT). На самом деле, эти БПФ заставка использует, чтобы получить амплитуды частот из входного сигнала источника.
Я не буду тратить место и рассказывать вам о нем здесь, но я поместил несколько ссылок на ваш CD, они помогут вам найти разъяснение работы и различные реализации БПФ. Вашим заданием, если у вас есть желание, будет добавление метода БПФ в класс CDiscreteFourierTransform, что сделает его пригодным для использования в приложениях в реальном времени.
Заключение Я надеюсь, что эта глава дала вам основные понятия, необходимые для реализации приятно выглядящего анализатора спектра. Также, и более поэтично, я надеюсь, что эта глава позволила вам оценить изящную простоту звуковых волн. Для меня поразительно, что все, что мы слышим - от оперы до взрывов и человеческих голосов - мы слышим из-за того, что волны различной частоты заставляют резонировать различные части нашей улитки. Дискретное преобразование Фурье — это много больше, чем я рассказал. Фактически, ДПФ — это одна из тех вещей, которые могут быть объяснены с различных точек зрения, некоторые из которых включают в себя высшую математику. Если вы из тех, кто любит общение с мнимыми числами и интегралами, я посоветую вам изучить математическую сторону ДПФ и БПФ. Дтя начала могу предложить вам ссылки на CD.
11-1490
3ak/uo4emie Вот и все. Я надеюсь, что вы подошли к этой точке с твердым пониманием основ программирования игрового звука, и что я сумел разбудить в вас желание реализовать то, что вы изучили, в ваших собственных играх. Звуковой движок, который вы разработали за время изучения этой книги, готов к работе и ждет не дождется использования в ваших проектах. Вы можете взять законченный звуковой движок на вашем CD, он называется «FinalAudioEngine», или вы можете взять любой из его вариантов из глав, если вам нужно только то, что было изучено до этой главы включительно. Я советую вам изучить ссылки на вашем CD. Эти ссылки - несколько для каждой главы или каждой главной темы - являются замечательным способом улучшить ваше понимание игрового аудио и добавить еще больше возможностей вашему звуковому движку. Движок ваш - берите, меняйте, улучшайте и, самое главное, используйте. Удачи!
Приложение А
Создание динамически музыКи Забудем на время, что мы программисты, и вспомним, что все мы немножко музыканты. Это приложение продемонстрирует вам создание всех необходимых частей простой партитуры динамической музыки. Это руководство рассказывает об основах создания динамической музыки, а не о том, как заставить работать графический интерфейс DirectMusic Producer. Вы можете использовать рисунки для того, чтобы сравнить мою работу с вашей, но если у вас есть проблемы с графическим интерфейсом DirectMusic Producer, вы должны использовать различные Интернет-руководства и/или руководство, которое Microsoft предоставляет вместе с программным обеспечением. Все начнется с того, что вы узнаете, как установить DirectMusic Producer и как создать статическую (неизменную) музыку с его использованием. После вы научитесь добавлять элементы, которые изменяют статическую музыку на динамическую.
УстаноВКа DirectMusic Producer Начнем с начата - программу установки для DirectMusic Producer вы можете найти на вашем CD, как часть DirectX SDK. Я не буду комментировать процесс установки - там нет ничего особенного. Если будут проблемы, обращайтесь к документации DirectMusic Producer.
Hacmpouka Вашей MIDI {клавиатуры Совет. Если вы собираетесь создавать серьезную музыкальную композицию и у вас нет MIDI клавиатуры, приостановитесь здесь и достаньте себе такую. Поверьте мне, попытка использования DirectMusic только с помощью мышки доведет вас до бешенства. Даже маленькая, в одну октаву, клавиатура, которую вы можете достать примерно за $120, стоит того.
Приложение А
308
В там случае если у вас нет никакой возможности достать MIDI клавиатуру, на вашем CD, в директории с полезными программами, есть программа, которая превратит вашу обычную QWERTY клавиатуру в MIDI клавиатуру, Чтобы настроить вашу MIDI клавиатуру в DirectMusic Producer, щелкните по кнопке на панели инструментов, которая выглядит, как MIDI порт (рис. АЛ). [Slanted Stereo iReveib^J & j !(! }
>•
Рис. А.1 . Щелкните по кнопке панели инструментов, выглядящей как MIDI порт, чтобы настроить конфигурацию MIDI
Самой важной настройкой в диалоге является MIDI вход. Нет необходимости изменять другие настройки (эхо, время ожидания и синтезатор), но если вам интересно, то помощь DirectMusic Producer расскажет вам, для чего они предназначены. Вы, наверное, думаете, что сможете проигрывать ноты на вашей клавиатуре и слышать их незамедлительно, но, из-за особенностей работы DirectMusic Producer, пока у вас нет такой возможности. Чтобы проверить вход вашей MIDI клавиатуры, предпримите следующие шаги (рис. А.2): ln- Slyki Paiicrnl] J £fc E* *™ jdd-i™ йэшы
D 1*>Э 0 У IS В Ш
ram *
~ jf 9*1
CMJCb.rt.fcl !CMJ
1(1); I
®
?м да
-rr-rf_r]_rr.rl_rr,f_rT_
r
Рис. А.2. Тестовый проект, который вы можете использовать для проверки работоспособности вашего MIDI входа
Создание динамически музъ&и
ЗО9
1. Щелкните по кнопке New панели инструментов и выберите Style. DirectMusic запросит у вас имя проекта — имя не имеет значения, но для этого примера я выбрал «MidiTest». 2. Теперь у вас с левой стороны экрана должна быть древовидная структура объектов. Перейдите к директории Patterns, откройте ее и дважды щелкните по Pattern 1. 3.
На правой стороне экрана есть синий квадрат с надписью «1 (1): 1» на нем. Дважды щелкните по нему, и внизу слева должна появиться клавиатура.
4.
Нажмите на клавишу на вашей клавиатуре. Вы должны услышать соответствующую ноту, и вся секция целиком должна стать светло-желтой. Теперь вы должны быть способны проигрывать ноты на вашей клавиатуре и слышать их.
5. Выберите Close Project из меню File и не сохраняйте ваши изменения. 6. Перейдите туда, где был создан на шаге 1 проект, и удалите его (если вы ничего не меняли, то проект должен быть в My Documents\DMUSProducer).
Если Ваша MIDI Клавиатура не работает Вот некоторые вещи, которые стоит проверить, если вы раньше не работали с MIDI и вам кажется, что DirectMusic не видит вашу клавиатуру. Q Убедитесь, что MIDI выход (MIDI Out) вашего компьютера соединен с MIDI входом (MIDI In) вашей клавиатуры, и наоборот. Не соединяйте вход с входом и выход с выходом. Эта наиболее часто встречающаяся ошибка. U Убедитесь, что DirectMusic использует правильный MIDI вход, как объяснено выше. Q
Убедитесь, что устройство вывода звука также выбрано верно (в том же диалоге) и что ничего случайно не заглушено (mute) (да-да, и так бывает!).
Q Проверьте в Device Manager в Control Panel\System, что ваш MIDI порт установлен правильно. а
Убедитесь, что ваша клавиатура не требует никаких специфических действий для работы с MIDI. Некоторым клавиатурам требуется переключение в MIDI режим работы, чтобы они начали посылать данные через MIDI порт.
G В целом, настройка MIDI под Windows - это несложная задача. Я никогда не встречался с проблемой с MIDI, связанной с чем-то низкоуровневым: IRQ, старые/неработающие драйвера, плохие кабели и т. д. Большинство моих проблем с MIDI были вызваны тем, что мое музыкальное программное обеспечение не обращало внимание на MIDI или моя клавиатура не отправляла данные через MIDI порт.
31О
Прило&ениеА
Создание нормальной музыКи После того как вы установили DirectMusic Producer и настроили вашу MIDI клавиатуру, пришло время создавать музыку! Мой процесс создания (которому вы вольны следовать) выглядит примерно так. 1. Выбор инструментов, которые будут использоваться. 2. Создание первого ансамбля (другими словами, установка панорамирования и громкости для инструментов). 3. Создание схем музыки (это обычно самый большой шаг) 4.
Сборка схем в сегмент.
5.
Настройка всего по необходимости.
6. Сохранение и глубокое удовлетворение от проделанной работы. Не забывайте, что этот раздел строго про создание нормальной музыки. Вы пройдете через все эти шаги на простейшем уровне (без динамической музыки); потом, в более поздних частях приложени, вы вернетесь к тому, что вы создали в этом разделе, и измените статическую музыку на динамическую. Совет. Если вы не хотите делать все это, я включил файл проекта этого приложения на ваш CD.
Создание o6i>ekmoB Каждое великое путешествие начинается с одного маленького шага, и каждый экземпляр динамической музыки начинается с нового проекта DirectMusic. Щелкните New в ниспадающем меню File и выберите Project. Далее создайте новый стиль, используя тот же диалог. Обратите внимание, что при создании нового стиля, вы автоматически получаете новый ансамбль и новую схему. Этот пример небольшой, так что вы можете не переименовывагь объекты, созданные DirectMusic, во что-то более содержательное, чем Bandl, Patternl и т. д. Тем не менее, когда вы будете создавать вашу музыку для каких-то реальных целей, вы должны завести привычку присваивать содержательные имена вашим схемам. Совет. Список объектов в диалоге New позволяет делать множественный выбор, так что в будущем вы можете создать новый проект и новый стиль за один раз, зажав Ctrl и щелкнув на Style и на Project.
Создание динамически музьЖи
311
ЗначКи, расширения и форматы файлов Прежде чем двигаться дальше, вы должны научиться узнавать объекты, которые только что создали. Если вы в замешательстве смотрите на все эти значки в отображении древовидной структуры DirectMusic, то этот раздел для вас. Табл. А.1 содержит в себе значки, расширения файлов и краткие описания объектах, доступных в DirectMusic Producer. Объекты DirectMusic можно сохранить на диске двумя различными путями: обычным или в Runtime формате. По мере того как вы создаете вашу музыку, вы сохраняете ее, как обычно, но когда вы закончили и хотите услышать вашу музыку в вашем приложении, вы должны сохранить ее в Runtime формате. В Runtime формате не сохраняется информация о редактировании, поэтому размер Runtime файлов немного меньше, чем размер обычных.
Создание DL8 koAAeku,uu Для этой мелодии вы будете использовать собственную выборку барабанной петли (drumloop), чтобы добавить некоторый колорит. Для реализации этого вам понадобится DLS коллекция; если же ваши потребности не выходят за пределы инструментов, предлагаемых звуковым набором General MIDI, то вам не понадобится создавать DLS коллекцию. Таблица А.1. Объекты DirectMusic Producer Значок
Объект
Обычное расширение
Runtime формат
Проект - объект высшего уровня
PRO
N/A
DLS коллекция - собственные инструменты и выборки (барабанные петли и т. д.) обитают в DLS коллекциях
DIP
DLS
Встроен в DLS
Встроен BDLS
Инструмент в DLS коллекции
312
Приложение А
Таблица А.1 . Объекты DirectMusic Producer Значок
Объект
Обычное расширение
Runtime формат
WAV файл в DLS коллекции
WAV
Встроен sDLS
Стиль - объединение схем, мотивов и ансамблей
STP
STY
•М '
Ансамбль - список инструментов, используемых в схеме
BNP
BND
(и схему внутри этого стиля) воспроизводить
SGP
SGT
-^ j
AUP
AUD
U
.. *ч
Звуковая дорожка - например, стерео, моно, стерео с эхом и т. д.
• "*•
Мотив - музыкальный звуковой эффект, воспроизводимый по триггеру в игре
Встроен в стиль
Встроен в стиль
Схема - некоторый отрезок музыки, от 1 до нескольких тактов длиной
Встроен в стиль
Встроен в стиль
•ь.Л1
•т*-*» ••.••
Создание динамически музыАи
313
Таблица А. 1. Объекты DirectMusic Producer Значок
Объект
Обычное расширение
Runtime формат
Скрипт - говорит DirectMusic, что сделать, чтобы воспроизвести звуковое эффект
SPP
SPT
WAV файл - используется для звуковых эффектов или выборорчной музыки (например, гитарные риффы)
WVP
WAV
Карта аккордов - используется для составления динамической музыки
СОР
CDM
Контейнер - содержит собственные (custom) объекты
COP
CON
Tool Graph- использование инструментов и эффектов для определенных PChannels
TGP
TGR
Создайте новую DLS коллекцию, щелкнув по кнопке New и выбрав DLS collection. Посмотрите на отображение вашей древовидной структуры - вы увидите, что она разбита на две директории: инструменты и wave выборки. Wave директория содержит WAV выборки, используемые для создания инструментов. Это значит, что вашим первым шагом будет добавление WAV выборки в wave директорию. Сделайте это, щелкнув правой клавишей мыши по директории и выбрав Insert Wave. Найдите и выберите wave файл, который вы хотите вставить (в данном случае, DrumLoop.wav, на вашем CD). После этого щелкните дважды по значку DrumBeat, который вы только что создали, и WAV файл откроется с правой стороны от вас.
314
Приложение А
DirectMusic Producer обеспечивает несколько простых функции WAV редактирования, но для всех задач, кроме простого управления, вы обнаружите, что проще загрузить ваш WAV в другое приложение (например, GoldWave или Sound Forge). Воспроизведите звук просто для того, чтобы убедиться в корректности загрузки. На вашей панели инструментов, ближе к центру экрана, есть две кнопки воспроизведения. Кнопка с полоской воспроизводит с начала, тогда как обычно выглядящая кнопка воспроизводит с текущей позиции курсора. Вам совсем необязательно отыскивать что-то, чтобы воспроизвести его; вы можете воспользоваться комбинированным окном, находящимся слева от кнопок воспроизведения, чтобы выбрать любой объект в вашем проекте. Теперь, после загрузки WAV файла вы можете создать инструмент. Заметьте, что инструмент создается автоматически, когда вы создаете его из WAV файла, так что ищите его в древовидной структуре. DirectMusic соответствует спецификации DLS, а эта спецификация обеспечивает множество возможностей по настройке того, как из WAV файла получить инструмент. Следуя спецификации, различным музыкальным диапазонам могут быть присвоены различные WAV файлы - например, вы можете использовать один WAV для нижних октав вашего инструмента и другой для верхних октав. Вы можете даже зайти настолько далеко, чтобы иметь различные WAV файлы для каждой ноты инструмента! В этом примере, раз вы используете барабанную петлю, вам не нужно заботиться о нотах. Вы хотите, чтобы WAV барабанной петли воспроизводился на корректной частоте, когда вы этого от него не потребовали. Поэтому выставьте и высокий, и низкий нотный диапазоны в С5, используя окно редактирования нотных диапазонов на основной странице редактирования WAV. C5 (или, по другому, С пятой октавы) - это нота, которая соответствует частоте выборки по умолчанию; т. е. DirectMusic полагает (пока вы не разубедите его), что наш WAV файл - это WAV файл чего-то, что играет ноту С5. Если вы сыграете ноту на полтона ниже (В4), то DirectMusic изменит частоту выборки соответственно этой ноте; если вы сыграете ноту Сб, то поднимет высоту на октаву вверх. Если вы сыграете С5, то DirectMusic не будет менять высоту выборки. Далее, обратите ваше внимание на группу элементов управления под заголовком Instrument (рис. А.З). Выставьте флаг Drums. Это укажет DirectMusic на то, что этот инструмент на самом деле набор ударных (drum kit). Набор ударных - это специальный инструмент, являющийся объединением различных типов ударных, каждая нота- это свой тип ударных. Например, С5 может быть bass drums, следующая по высоте нота (С#5) может быть snare drums и D5 может быть hi-hat. Вы можете создать десятки различных ударных и все вставить в один инструмент.
Создание динамически музь&и
315
М З В р ! Э LS6p>9~~3 Patohjo 3'" О'ЧйгсГ Мак level ("(ВО" .
Рис. А.З. Элементы управления для выставления ID инструмента, а также того, является ли он набором ударных
Это сильно упрощает процесс ввода дорожек ударных. Если бы для каждого ударного был свой инструмент, то вам пришлось бы для каждого из них заводить отдельную партию, что значит, что сначала бы вы записали bass drum, потом snare drum, потом hi-hat - все по отдельности. Однако, используя набор ударных, вы можете записать все ударные сразу, нажимая различные клавиши на вашей клавиатуре. Это, конечно, не то же самое, что играть на настоящих ударных, но гораздо проще, чем записывать каждый инструмент отдельно. Сразу слева от флага Drums расположены три числа, которые вместе определяют ID вашего инструмента. MSB - это аббревиатура для Most Significant Byte (старший байт); LSB - это Least Significant Byte (младший байт). Мой вам совет, если вы не собирайтесь использовать больше 127 собственных инструментов, оставьте MSB и patch numbers выставленными в ноль, а для ID инструмента используйте LSB. Удобно группировать схожие инструменты - например, пусть ваши струнные начинаются с LSB 10, ваши деревянные духовые инструменты с LSB 20 и так далее. В нашем примере ID инструмента не имеет никакого значения, так что я просто выбрал MSB О, LSB 1, patch 0. Сокращенно это записывается так (0,1, 0). Совет.Ддя более подробной информации по MSB, LSB и patch numbers обращайтесь к документации DirectMusic, точнее, Playback и Recording\Patch Numbers и Bank Select.
Следующий шаг в создании собственной барабанной петли - это работа с корректным выставлением артикуляции. Термин «артикуляция» связан с относительной громкостью ноты во времени. Различные инструменты имеют различную артикуляцию; нота, сыгранная на фортепиано, например, очень быстро набирает громкость, а потом медленно затихает. Нота, сыгранная на другом инструменте, может начать звучать тихо и набирать громкость со временем. Звуки аналогового синтезатора моментально появляются и моментально исчезают, как будто дергают за рубильник - у них нет периода затухания. Если бы вы создавали настоящий инструмент здесь, то вам нужно было бы настроить атаку, спад, поддержку, затухание (attack, decay, sustain, release - ADSR ) для громкости инструмента. Эти величины связаны с MIDI; атака соответствует 1 Формат описывающий огибающую генерируемого импульса. ADSR (attack, decay, sustain, release - нтака, спад, поддержка, затухание). Так же существует формат DAHDSR: delay, attack, hold, decay, sustain, release задержка, атака, удержание, спад, поддержка, затухание). - Примеч. науч. ред.
316
Приложение А
тому, насколько быстро нота достигает своей максимальной громкости, спад задает время, характеризующее насколько быстро она «спадет» с этой максимальной громкости, поддержка устанавливает громкость, которую нота «держит», и затухание задает, как быстро нота затухнет совсем (рис. А.4). Обратите внимание, что есть еще четыре ADSR величины, для высоты инструмента, но эти величины редко редактируются.
Поддержка
Время
Рис. А.4. Для каждого инструмента DLS1 коллекции устанавливаются точки для атаки, спада, поддержки, затухания (ADSR) Совет. На момент написания кнши существуют две версии стандарта DLS: DLSуровень I и DLSуровень .2. Ранняя спецификация DLS уровень I поддерживает для инструментов переменные attack, decay, sustain it release, но DLS уровень 2 дает возможность еще более точной настройки, добавляя к этому списку еще две переменные: Delay и Hold (рис. А. 5). Если вы хотите их использовать, то снимите флаг DLSI, но тогда ваш набор инструментов будет работать только в там случае, если программный синтезатор поддерживает DLS2 (DirectMusic, начиная с DirectX 8.0, поддерживает). Для более подробной информации по отличиям между DLSI и DLS2, обращайтесь к помощи по DirectMusic Producer, точнее Playback и Recording\DLS Designer\DLS Level I u Level 2.
Так как вы делаете барабанную петлю, вам не нужно делать ничего сложного, так что выставьте атаку, спад и затухание равными 0,001 и поддержку равной 100%. Это значит для DirectMusic, что нота появляется мгновенно, остается некоторое время на полной громкости и мгновенно исчезает - именно так должна вести себя барабанная петля.
Создание динамически музьЖи
317
И последнее перед тем, как вы закончите с созданием собственного инструмента, - снимете флаг Note Overlap Allowed. Это скажет DirectMusic, что если инструмент проигрывается и для него обнаружена еще одна нота, то DirectMusic должен прекратить играть первую ноту, вместо того,, чтобы играть ноты вместе. Другими словами, у инструмента есть только 1 голос полифонии - в любое время может воспроизводиться только один экземпляр звука. Это очень удобно для выборок ударных, но часто совсем не нужно для обычных инструментов.
Удержание
Поддержка
Время Рис. А.5. Инструменты DLS уровень 2 имеют возможность задания задержки, атаки, удержания, спада, поддержки и затухания
Нажмите клавишу С5 несколько раз на вашей клавиатуре и обратите внимание на то, как выборка начинается воспроизводиться по вашему нажатию и заканчивает воспроизводиться сразу же после того, как вы снимете палец с клавиши. Если see понятия ADSR для вас в новинку, то попробуйте изменять переменные и понаблюдать какое влияние это оказывает на то, что происходит, когда вы нажимаете С5. Поздравляю, вы только что создали собственный DLS инструмент для этого примера мелодии. Я не стал обсуждать часть опций этого диалогового окна, потому что они обычно не используются и/или не имеют значения для этого примера. Чтобы восполнить пробелы, обращайтесь к документации DirectMmsic Producer, точнее Playback и Recording\DLS Designer.
318
Приложение А Совет. Легко подумать, что вы можете настроить DLS коллекцию для того, чтобы использовать сегменты звуковых, эффектов, которые вы хотите воспроизводить по желанию. Вы можете создать DLS коллекцию, поместить в нее вашу выборку, а потам создать сегмент, который будет воспроизводить стиль, содержащий только одну схему, причем в этой схеме будет только одна нота - нота С5 звукового эффекта, который вы хотите воспроизвести. Это, конечно, сработает, но более легким способам является использование wave сегментов (т. е. сегментов, которые содержат wave файл, а не ноты). Это поддерживается Direct Music и является официальным и часто самым лучшим способом интегрирования звуковых эффектов в ваш проект. Про создание wave сегментов рассказывалось в главе 12.
Hacmpouka ансамбля После того как вы создали барабанную петлю как собственный DLS инструмент, настало время обратить свое внимание на объект стиля и, в особенности, на те инструменты, которые вы хотите использовать в его схемах, Сначала определение: ансамблем называется любое подмножество звукового набора General MIDI и любых DLS коллекций, которые у вас имеются; ансамбль также указывает DirectMusic значения по умолчанию для громкости панорамирования, октавы и транспозиции для инструментов стиля. Каждый инструмент в ансамбле принадлежит либо звуковому набору GM либо собственной DLS коллекции, но в ансамбле не обязательно присутствует звуковой набор GM и все DLS коллекции. Или, говоря языком программистов, ансамбль - это дополнительный уровень структурирования для выбора инструмента. К счастью, настройка ансамбля менее сложна, чем преобразование вашей барабанной петли WAV файла в DLS инструмент. Чтобы настроить ваш ансамбль, дважды щелкните по Bandl, внутри директории Bands вашего стиля. Вы должны увидеть Band Editor (редактор ансамбля), как показано на рис. А.6. Для начала привяжите вашу собственную выборку барабанной петли к PChannel 10. Почему именно к каналу 10? Спецификация MIDI устанавливает, что все наборы ударных должны быть на канале 10. Вы сказали DirectMusic, что ваша барабанная петля - это набор ударных, когда выставили флаг ударных в редакторе DLS, и таким образом DirectMusic даст вам возможность выбрать только этот канал. Дважды щелкните по PChannel 10 в левом окне списка (или щелкните по нему правой клавишей и выберите Properties, или нажмите F11 - это все одно и то же). Выставьте флаг инструмента, а потом щелкните на большой кнопке, находящейся прямо под ним. Появится всплывающее окно, выберите нижний вариант: Other DLS. Появится диалоговое окно, в котором вы можете выбрать DLS коллекцию и инструмент в этой коллекции, который будет привязан к данному каналу
Создание динамически музыки
319
Рис. А.6. Редактор ансамбля DirectMusic Producer
воспроизведения. Если вы точно следовали моим указаниям, то вы должны увидеть ваш (О, 1, 0) инструмент в списке. Щелкните ОК, чтобы привязать инструмент к PChannel 10. Закройте окно свойств и обратите внимание на то, что окно списка изменилось. Также вам нужно будет два других (реальных) инструмента, которые вы будете использовать в вашем примере музыки. Музыка будет делиться на две части —гармонию и мелодию. Гармония будет использовать PChannel 1, а мелодия PChannel 2. Так как вы будете вводить арпеджио для гармонии, я предлагаю использовать фортепиано; для мелодии я выбрал инструмент GM «Voice Oohs». Совет. По умолчанию в редакторе ансамблей отображаются только 16 PChannels. Вы можете увеличить их количество (или избавиться от ненужных), щелкая правой клавишей внутри списка).
Диаграмма справа позволяет вам настроить относительную громкость и панорамирование инструментов в вашем ансамбле. Громкость откладывается по вертикали, панорамирование по горизонтали. Если вы хотите, чтобы инструмент играл максимально громко и имел центральное панорамирование, расположите его сверху в центре. Все же, пока вам не нужно этого делать. Я предпочитаю
320
ПрилоЖениеА
настраивать панорамирование и относительные громкости уже после того, как я получил готовую схему, так что сейчас переместите все ваши инструменты точно в центр диаграммы; вы сможете вернуться и распределить их по местам позже, когда у вас уже будет что слушать. С ансамблями все!
Создание Вашей перВой схемы Вы создали ваши DLS инструменты и собрали ваши ансамбли - теперь можно создавать музыку, В этом разделе вы создадите одну часть примера мелодии, размером в четыре такта.
Pegakmop схем Дважды щелкните по Pattern 1 внутри директории Patterns вашего стиля, появится Pattern Editor (редактор схем). Привыкайте к этому редактору; именно в нем вы будете проводить основную часть времени. Я постараюсь все вам объяснить в этом редакторе, начиная сверху и спускаясь вниз. На самом верху расположена временная шкала (time signature). Толстые линии разграничивают bars (такты), темно-синие линии означают доли (beats) в такте. Красная линия это позиция воспроизведения. Вы наверняка думаете, что для изменения временной шкалы вам нужно щелкнуть по ней правой клавишей мыши, но вместо этого, вам нужно щелкнуть по самому узлу стиля и выбрать Properties. Ниже временной шкалы находится трек аккордов. Он говорит DirectMusic какой аккорд сопровождает каждый такт в вашей мелодии. Вы узнаете подробнее про трек аккордов чуть позже, когда вы дойдете до раздела о продвинутой динамической музыке. Далее идут партии инструментов. На данный момент, имеется только одна инструментальная партия. Обратите внимание на кнопки Maximize и Minimize для каждой партии, справа - попробуйте максимизировать партию, и вы увидите отображение Piano Roll (клавишный редактор) для этой партии, что позволит вам редактировать ноты и вариации. Сверху расположены 32 кнопки, организованные в виде сетки 16x2. Это кнопки вариаций; вы узнаете о них подробней, когда мы перейдем к динамической музыке. Совет. Обратите внимание, что вы можете растягивать основные окна по вертикали, подведя курсор к их вертикальным границам (следите за изменением значка) и перетащив его.
Создание динамически музьЖи
321
Слева от кнопок вариации находятся кнопки вертикального увеличения масштаба (vertical zoom in) и вертикального уменьшения масштаба (vertical zoom out); их горизонтальные аналоги расположены справа от горизонтальной полосы прокрутки внизу вашего окна. Табл. А.2 описывает горячие клавиши для изменения масштаба. Таблица А. 2. Горячие клавиши для изменения масштаба Горячая клавиша
Результат
А
Вертикальное увеличение масштаба
Z
Вертикальное уменьшение масштаба
I
Горизонтальное увеличение масштаба
О
Горизонтальное уменьшение масштаба
Слева от кнопок вертикального изменения масштаба находится кнопка, позволяющая вам изменять инструмент для данной конкретной партии. Обратите внимание, что на самом деле вы изменяете таким образом ансамбль; выбор нового инструмента здесь эквивалентен тому, чтобы перейти в редактор ансамбля и назначить новый инструмент для этого PChannel. Рядом с названием инструмента расположена маленькая кнопка ансамбля, которая работает как ярлык для вызова редактора ансамбля. Теперь мы переходим к окну Piano Roll. Это центральный элемент редактора схем. Время отложено по горизонтали, высота по вертикали, а ноты представляются прямоугольниками. Чем нота громче, тем прямоугольник выше, чем больше длительности ноты, тем больше длина прямоугольника. Вы можете нажать клавишу фортепиано с помощью вашей мышки и услышать звучание ноты; также вы можете вставить ноту в текущую позицию курсора (красная линия), нажав клавишу Insert. Вы можете выделять ноты и работать с ними, используя стандартные методы и функции выделения (вырезать, копировать, вставить и т. д.). Ниже Piano Roll расположен трек переключения вариаций. Этот трек говорит DirectMusic, когда можно безопасно переключать вариации посреди схемы. Подробнее об этом вы узнаете в разделе о продвинутой динамической музыке. Последний, самый нижний, трек - это трек УК (СС) или Управляющей Кривой (Control Curve). Именно здесь задаются команды управления, такие, как подтяжка частоты и модуляция. Вы узнаете, как настраивать управляющую кривую, в этом разделе.
322
Приложение А
Ввод нот Достаточно разговоров - настало время делать музыку. Вы начнете с того, что увеличите длительность Pattern 1 внутри директории Patterns вашего стиля. Вы можете сделать это, открыв свойства схемы и увеличив там ее длительность. Я предпочитаю делать длительность больше необходимой и потом, по необходимости, подстраивать ее, после того как я записал данные. Сверхдлительные схемы позволяют вам записать несколько попыток подряд, без необходимости остановки, перемотки и повторной записи. Потом, после того как вы записали несколько попыток, вы выбираете ту, которая вам нравится больше всего, удаляете все остальные, и перемещайте выбранную попытку в начало схемы (или, используя вырезать/вставить, или, выбрав и перетащив все ноты за один раз). Чтобы ввести ноты, максимизируйте первую партию и перейдите к Piano Roll, щелкнув где-нибудь внутри него (он подсветится светло-желтым). Далее, перемесите курсор на начало мелодии, щелкнув мышкой или нажав клавишу Ноте. И, наконец, нажмите кнопку записи для начала записи. Можно пойти другим путем и создавать ноты, щелкая мышкой по тому месту, где вы хотели бы создать ноту и нажимая клавишу Insert - это утомительно, но это единственный путь ввода музыки, если у вас нет MIDI клавиатуры. Совет. Во время записи вы можете заставить компьютер считать доли за вас, щелкнув намаленькой кнопке метронома, на панели инструментов. Призвав весь свой талант пианиста (у вас ведь он есть, правда?), введите восемь тактов, показанных на рис. А.7.
Рис. А.7. Фоновая восьмитактовая партия фортепиано для Pattern 1
Создание динамически музь&и
323
Люди, знакомые с теорией музыки, без труда узнают в последовательности аккордов на рис. А.7 простую последовательность I-VI-IV-V в ключе .С. Это значит,, что схема начинается в ключе С мажор, переходит в А минор, F мажор и потом в G мажор. Запомните эту последовательность; она нам понадобится в следующих разделах.
Квантование нот После того как вы введете ноты, вы заметите (только если вы не очень, очень хороший исполнитель), что некоторые ноты начинаются чуть раньше, чем нужно, а некоторые чуть позже. Вы должны подправить их так, чтобы все были выровнены по тем долям (beat) или грид (grid), по которым должны. Если вы хотите сохранить ощущение «живого выступления исполнителя», вы можете добавить случайные задержки, используя окно свойств ноты, показанное на рис. А.8 (уже после того как вы подровняли ноты). ExampleStyte - Pattern! - 1 Mote Properties Hotels! |v»ialions;F1aaij
В. StwiTme
Beat
Grid
Fid
Random
ID
-H
EndTkne Duation MaploChocd
T
Oveiiklelnv. Gioup JNone
Рис. А.8. Окно свойств ноты
Чтобы начать выравнивание нот, щелкните правой клавишей мыши в области Piano Roll и выберите Snap To None из появившегося контекстного меню. Это позволит вам свободно перемещать ноты. Вы можете перемещать ноту графически, перетаскивая ее середину (следите за изменением курсора) или с помощью ч исел, используя окно свойств ноты. Не старайтесь выстроить ноты идеально - просто расположите их достаточно близко к необходимой позиции. После того как вы сделали это, остальное можете доверить компьютеру. Это как мытье тарелок - вы доводите их до определенной чистоты, а потом посудомоечная машина отмывает их до блеска. Компьютер может заставить ваши ноты начинаться точно вовремя с помощью процедуры, называемой квантованием. Квантование -это просто умное слово для перемещения ноты к ближайшей доле (beat), указанной
Приложение А
324
вами. Вы можете выбрать квантование, основанное на четвертых долях, восьмых долях и множестве других вариантов. В этом примере вы будете использовать квантование на основании восьмых долей, так что переходите к ниспадающему меню Edit, выбирайте Quantize, настраивайте необходимые параметры и жмите ОК. Может потребоваться несколько попыток, чтобы все ноты были расставлены правильно. В процессе квантования DirectMusic Producer может переместить некоторые ноты неверно, так что вы должны воспроизвести свою текущую композицию после выравнивания нот, чтобы убедиться в ее правильном звучании.
Bcmafika дополнительных партий Сейчас вы ввели просто фоновые аккорды; теперь настало время для ввода мелодии. Сначала перейдите к редактору ансамбля и привяжите Voice Oohs (или любой другой инструмент, который будет вести мелодию) к PChannel 2. После этого вернитесь к редактору схем и щелкните правой клавишей в области Piano Roll. Щелкните на пункте Add Pattern Parts, расположенном внизу контекстного меню, чтобы добавить новую партию для PChannel 2. Совет. Можно пойти другим путем, записав и мелодию и аккорды в один трек фортепиано. Потом, после того как вы записали и то, и другое, вы можете вырезать мелодию и вставить ее в новую партию.
Теперь вы можете выделить Piano Roll для партии 2 и ввести мелодию, показанную на рис. А.9. После этого проведите квантование нот, как объяснялось в предыдущем разделе.
Рис. А.Э. Партия мелодии для Pattern!
Создание динамически музь&и
325
Завершение ВВода схем Теперь после подробного объяснения процесса ввода, вам нужно попрактиковаться. Создайте другую схему и введите две партии по восемь тактов каждая, как показано на рис. А. 10.
Рис. А.10. Партии фона и мелодии для Pattern2
Теперь у вас должно быть полных 16 тактов музыки. Совет. Если вы уже привыкли работать с каким-либо программным обеспечением для записи AUDI, вы можете использовать его для записи вашей игры, а после, просто импортировать MIDI файлы в DireciMusic Producer. В DirectMusic Producer выберите Import File из подменю Project меню File и начинайте работу с импортированными файлами.
Объединение схем В сегмент Чтобы закончить создание простой мелодии, нужно объединить эти пять схем вместе. Чтобы сделать это, вам нужен сегмент - создайте новый экземпляр, длиной в 16 тактов.
326
Приложение А
DirectMusic Producer автоматически попросит вас добавить треки; по умолчанию в сегменте есть только трек временной шкалы. Вам необходимо добавить трек стилей и трек ритма. Трек стилей говорит DirectMusic Producer, какие стили и когда воспроизводить. В этом примере, трек стилей совсем незамысловатый - вы хотите, чтобы он воспроизводил стиль, который вы создали в предыдущем разделе, на протяжении всего сегмента. Расположите ваш курсор воспроизведения на начале сегмента (нажмите клавишу Ноте) и нажмите клавишу Insert, чтобы вставить новый элемент в трек стилей. Появится диалог свойств стиля, позволяющий вам выбрать стиль. Вы должны увидеть свой стиль в ниспадающем списке; выберите его и настройте так, чтобы он действовал с такта 1. Также, обратите внимание, что при вставке вашего элемента, появляется новый трек ансамбля, настроенный на основании ансамбля вашего стиля. Теперь DirectMusic знает, что с такта 1 вашего сегмента, он должен использовать конкретную схему из стиля, указанного вами. Но откуда он знает, какую именно схему? Если вы сейчас воспроизведете сегмент, то вы услышите одну из своих схем, выбранную случайно. Это очень удобно в определенных ситуациях, но что, если вы хотите, чтобы ваши схемы проигрывались в определенном порядке? Тут в дело вступает трек ритма. Трек ритма обладает двумя важными параметрами: во-первых, он устанавливает ритмический уровень, который DirectMusic Producer использует для разделения схем в стиле (схемы с диапазоном ритма, не включающим текущий ритмический уровень, не могут воспроизводиться); второй важный параметр говорит DirectMusic что делать, когда может быть воспроизведена больше чем одна схема. По умолчанию DirectMusic воспроизведет случайную схему, но вы можете изменить это так, чтобы все возможные схемы воспроизводились по порядку - именно то, что вам нужно в этом примере мелодии. Щелкните правой клавишей мышки по треку ритма и выберите Properties. Из появившегося окна Properties выберите Sequential from First из ниспадающего меню Repeat Pattern. Теперь проиграйте мелодию - вы должны услышать все ваши схемы, воспроизведенные в нужном порядке.
Зацикливание сегмента И последнее - вам необходимо сделать так, чтобы восемнадцать тактов вашего сегмента шли по циклу до бесконечности. Щелкните правой кнопкой мышки по сегменту в древовидной структуре и выберите Properties. В окне свойств сегмента перейдите к закладке зацикливания (loop tab) и выставьте флаг Infinite. Как видите,
Создание динамически музы/си
327
присутствуют и другие продвинутые параметры зацикливания, которые вы можете использовать, но, в большинстве случаев, обычный бесконечный цикл вполне справляется с работой.
Сохранение В Runtime формате На данный момент вы уже сделали достаточно, чтобы использовать этот (коротенький) кусок музыки в вашей игре. Но, чтобы сделать это, вам нужно сохранить его в Runtime формате. В этом нет ничего сложного, просто выберите Runtime Save A l l из меню File. Когда вы так делаете, DirectMusic Producer сохраняет каждый файл вашего проекта в Runtime формате (гораздо быстрее, чем компилировать исходный код!). Он помещает Runtime файлы в директорию RuntimeFiles, внутри директории вашего проекта, обычно внутри My DocumentsVDMUSProducer. Совет. Вы можете вставить информацию об авторским праве и другую необходимую информацию в вашу музыку, используя закладку информации (info tab), которую можно найти практически во всех окнах свойств для ваших объектов. Закладка информации позволяет вам указать тему (subject), автора (author), информацию об авторских правах (copyright), версию (version), а также любой, необходимый вам, дополнительный текст,
flunaMUMeckanмузыКа Теперь, после того как вы разобрались с основами записи схем, создания ансамблей, компоновки стилей и объединения всего этого внутри трека сегмента, вы можете вернуться к примеру мелодии и начать добавлять некоторые динамические элементы. Этот раздел расскажет вам про основные вещи, которые вы можете сделать, чтобы ваша мелодия звучала по-разному каждый раз, когда ее проигрывают.
Добавление вариаций Самый простой способ сделать вашу музыку динамической - это добавление вариаций к партиям ваших схем. Вариация - это просто другой вариант трека, который проигрывается вместо основного. Секрет добавления вариаций к вашим партиям содержится в 32 кнопках, расположенных прямо над Piano Roll. Каждая партия может иметь до 32 вариаций, поэтому мы имеем два ряда по 16 кнопок. Каждая кнопка несет три важных информационных составляющих: является ли вариация активной, разрешена ли она, и содержит ли она ноты.
328
ПрилоЖениеА
Сначала самое простое - если вариация содержит ноты, то цвет ее номера будет черным. Если в этой вариации нет никаких нот, то номер будет белым. Далее, если кнопка нажата, вариация активна (это значит, что ее ноты сейчас отображаются в Piano Roll, и вы можете их редактировать). И, наконец, цвет фона кнопки говорит вам, разрешена она или нет. Светлый фон означает, что она разрешена, темный, что нет. Вариация разрешена и вариация активна это два разных состояния. Если вариация разрешена, то она может быть выбрана для случайного воспроизведения, а запрещенная вариация никогда не может быть выбрана DirectMusic. По умолчанию все вариации партии разрешены и активны. В следующий раз, когда вы будете создавать схему, обратите внимание, что все номера на кнопках вариаций белые, что значит, что не было введено никаких нот. Как только вы введете свою первую ноту, они станут черными. Все верно - вы и не знали, что редактируете сразу 32 вариации! Так сделано для того, чтобы вы могли записать вашу основную мелодию во все 32 вариации разом, а потом изменять отдельно каждую вариацию. Давайте так и поступим — для начала нажмите кнопку вариации 2 (вариация 1 будет оригинальной, неизмененной мелодией). Она отожмется, говоря тем самым, что все ноты, которые вы будете вводить, запишутся во все вариации кроме вариации 2. Стоп! Это не то, чего мы хотели! Чтобы инвертировать выделение, дважды щелкните по кнопке 2. Теперь все кнопки, кроме кнопки 2, должны быть не нажаты (вариация неактивна). Теперь вы можете добавлять небольшие изменения в партию. Может быть, вы захотите сыграть арпеджио в обратном порядке, добавить мелизм или синкопу что вашей душе угодно. Когда вы закончили изменять вторую вариацию, вы можете переходить к вариации три и так далее. Обычно двух или трех вариаций вполне достаточно. Очень редко используются все 32! Когда вы закончили, снова воспроизведите вашу схему. Обратите внимание, что DirectMusic выбирает вариации случайным образом. Если вы хотите изменить это (например, сыграть все вариации по порядку), вы можете задать новые указания через окно свойств партии (рис. АЛ]). Чтобы попасть в свойства партии, щелкните правой кнопкой мышки где угодно внутри Piano Roll, исключая ноты, и выберите Properties. В группе вариаций есть ниспадающее окно Play, которое позволяет вам выбрать один из возможных способов поведения (эти способы схожи с теми, что вы могли выбрать для поведения, связанного с ритмическим уровнем, - обращайтесь к документации DirectMusic Producer за подробной информацией).
Создание динамически музы/си
329
ЕкатЫе51у(е - Ра«егв2 - 2 Part properties Pert
- Invefsion Boundaries" ~ Г" Set automatically based on existing notes. LockЮ: [None High: G10 lirneSifl: '4^4 length:
8 I
NoteColoi:
j
Chord/Scale
.
Pickup: 1 Ext 1 Bais
j
cleoted
^
'"
I j - f Chord Level 1
3
Рис. А.11. Окно свойств партии
Создание ykpauietmu Украшение - это специальный тип схемы, обычно вступление, заполнитель, брейк или концовка. В этом разделе вы создадите вступительную схему и настроите ее для воспроизведения в начале сегмента. Начните с создания собственно вступительной схемы - создайте новую схему, затем перейдите к ее свойствам и нажмите кнопку Intro. Это скажет DirectMusic о том, что эта схема вступительная. Теперь осталось создать красивую вступительную схему (например, как на рис. А. 12) и назвать ее «Intro». Далее, проделайте то же самое, чтобы создать брейк схему. Введите такты, показанные на рис. А.13, пометьте схему как Break и переименуйте ее в «Break», Теперь вам нужно настроить ваш сегмент так, чтобы он проигрывал. ваши украшения: вступление и брейк. Щелкните правой кнопкой мыши по значку сегмента в отображении древовидной структуры и выберите Properties. Выставьте новую длительность равную 23 тактам, далее перейдите к закладке зацикливания и выберите начало области зацикливания на такте 4, а конец на такте 22. Убедитесь, что флаг Infinite выставлен, так что ваш сегмент будет вечно воспроизводиться по циклу. Хорошо, теперь сегмент достаточно длинный и вы можете поместить в него ваши такты вступления и брейка. Щелкните правой кнопкой мыши на начальном элементе в вашем треке ритма и выберите Properties. В окне свойств выделите элемент Intro списка украшений. Выставьте ваш ритмический уровень равным 1. Это сообщит DirectMusic, что он может воспроизводить любые схемы, помеченные как Intro и имеющие ритмический уровень 1.
ззо
Рис. А.12. Украшение «вступительная схема»
Рис. А.13. Украшение «брейк схема»
Приложение А
Создание динамически музыАи
331
Теперь перейдите к свойствам Pattern! и выставьте диапазон ритма 1-1. Задание такого диапазона приведет к тому, что единственной возможной, при ритмическом уровне 1, для воспроизведения DirectMusic схемой будет Patternl. Внесите аналогичные изменения для Pattern2, выставив ритмический диапазон 2-2. Вернитесь к окну редактирования сегмента. Так как ваше вступление длится три такта, расположите ваш курсор в виде красной вертикальной линии на начало четвертого такта и нажмите клавишу Insert. Теперь убедитесь, что ни один из элементов списка украшений не выделен, и убедитесь, что ваш ритмический уровень все еще равен 1. Также убедитесь, что ниспадающее окно повторения вашей схемы содержит Sequential from First. Это скажет DirectMusic, что в начале такта 4 он должен начать воспроизводить любую схему, не являющуюся украшением и имеющую ритмический уровень 1 (в данном случае это будет Patternl). Далее перейдите к такту 12 и добавьте в ваш трек ритма еще один элемент, устанавливающий ритмический уровень равным 2. Именно это заставит DirectMusic воспроизводить Pattern2. Pattern2 -это единственная схема, имеющая ритмический уровень 2. Наконец, перейдите к такту 20 и добавьте еще одно изменение трека ритма. На этот раз, уменьшите ритмический уровень обратно до 1 и выберите Break из окна списка украшений. Если вы сейчас воспроизведете сегмент, то вы должны услышать вашу схему Intro, затем Patternl и Pattern2, а потом схему Break. После вашей брейк схемы DirectMusic должен уйти на цикл и снова воспроизвести Patternl (обратите внимание, что диапазон цикла не включает в себя такты вступления, потому что вы хотите сыграть его только один раз). Вот и все! Все создание украшения состоит всего лишь в создании схемы и нажатии кнопки!
Добавление мотивов Динамическая музыка так популярна не только из-за того, что она может постоянно меняться и никогда не звучать одинаково, а еще и потому что она может реагировать на события в игре. Существует два основных типа реакции, которые предоставляет динамическая музыка. Первый тип - это полное изменение звучания мелодии, когда происходит что-то важное. Например, если на вашего игрока нападает большая группа врагов, динамическая музыка может резко повысить напряженно еть, а когда игрок всех побеждает, она может медленно понижать напряженность и в итоге вернуться на прежний уровень.
332
Приложение А
Это один тип поведения, но его мы отложим в сторону до тех пор, пока не дойдем до раздела про создание альтернативных сегментов. Второй тип реакции динамической музыки состоит в том, что она может на мгновение увеличить напряженность или быстро проиграть звуковой эффект. Например, если ваш игрок находит Шлем Енота вы можете сыграть несколько нот из темы Королевства Енотов. На самом деле, вы меняете музыку не так уж и сильно, вы просто проигрываете звуковой эффект поверх музыки; звуковой эффект, которому случилось состоять из нот. Обычно было тяжело создавать музыкальные звуковые эффекты, которые бы соответствовали музыке, потому что было сложно определить, что происходит с музыкальной точки зрения, когда вызывается событие. Если ваш игрок подбирает Шлем Енота, когда фоновая музыка в ключе А, а тема из Королевства Енотов сыграна в ключе В, то у вас нет абсолютно никакого желания смешивать их вместе! Вам было бы удобно, если бы ваш код транспонировал тему из Королевства Енотов на полтона ниже так, чтобы и он был в А, гармонируя с фоновой музыкой.
Akkopgbi Композиции и mpeku akkopgoB Чтобы DirectMusic сделал это, вам нужно сообщить ему немного больше информации о вашей музыке, чем просто ноты. DirectMusic не может автоматически определить в каком ключе ваша музыка, так что вам нужно будет ему это сказать. Посмотрите на трек, расположенный чуть выше Piano Roll первой партии в вашем редакторе схем. Этот трек называется «Аккорды композиции» («Chords for Composition»). Он сообщает DirectMusic, в каком ключе ваша музыка в каждый конкретный момент. Считайте, что этот трек - это эквивалент DirectMusic для записи ключа в обычной музыкальной нотации. Тем не менее, если играющая на текущий момент схемы была написана в С, это еще не значит, что на данный момент она играется в С. Вы можете заставить DirectMusic транспонировать ваши схемы, используя трек аккордов (Chord Track) внутри сегмента. Например, вы можете создать схему и выставить трек «аккордов композиции» в СМ7 (С мажор, септаккорд) для всей мелодии, а потом создать трек аккордов в вашем сегменте, который будет выставлен в DM7 (D мажор, септаккорд). Когда DirectMusic видит это DM7 в вашем треке аккордов, он про себя думает: «ага, эта схема была написана в СМ7, а они теперь хотят DM7, так что я транспонирую схему вверх на два полутона». Используя термины математики, вы можете считать, что DirectMusic «вычитает» один ключ из другого, чтобы найти разницу ключей, которую потом он использует для транспонирования вашей схемы.
Создание динамически музьЖи
333
По этой причине, если вы хотите, чтобы ваша музыка воспроизводилась именно так, как вы ее написали, то вы должны убедиться, что трек «аккордов композиции» вашей схемы и трек аккордов вашего сегмента совпадают. Это гарантирует, что нет разницы между ключами и DirectMusic не транспонирует вашу мелодию.
Ввод сегмента Это все теория. Теперь нужно найти ей применение. Для начала создайте новый мотив (щелкните правой клавишей мышки по директории Motifs и выберите New) и введите мотив, изображенный на рис. А. 14. Как вы можете увидеть, в нем нет ничего сложного — просто несколько нот, сыгранных достаточно быстро, чтобы быть больше похожими на звуковой эффект, чем на такт музыки.
Рис. A.I4. Простая схема мотива
Когда вы сделаете это, добавьте трек аккордов к вашему сегменту, щелкнув правой клавишей мыши и выбрав Add Tracks. Вы должны увидеть новый трек аккордов, появившийся в вашем редакторе сегмента и выставленный по умолчанию в 2СМ7.
334
Прило&ениеА Совет. Мотивы - это просто специально помеченные схемы, так что они могут иметь несколько партий и вариаций, так же, как и обычные схемы. Так как вы теперь знаете, как добавлять вариации, вы можете создать несколько вариаций для вашего мотива это сделает вашу музыку еще чуть более динамической. Только не перестарайтесь!
Hacmpouka akkopgoB Вспомните, как DirectMusic производит транспонирование ваших схем - он смотрит на разницу между треком «аккордов композиции» и текущим аккордом сегмента. То же самое он делает для мотивов. Когда ваш код говорит DirectMusic сыграть конкретный мотив, DirectMusic смотрит на разницу между «аккордами композиции» мотива и текущим значением трека аккордов сегмента. Если ваш мотив в ЕМ7, а сегмент на данный момент проигрывается в GM7, то DirectMusic транспонирует ваш мотив вверх на три полутона так, чтобы он получился в GM7, в гармонии с сегментом. Но на пути к этой точке Приложения вы не изменяли никаких аккордов в треке «аккордов композиции» или в треке аккордов сегмента. Так что все треки аккордов выставлены в свое значение по умолчанию СМ7. Таким образом, никакое транспонирование не производится и все в порядке. Если вы не хотите использовать мотивы, то вы можете ничего не менять, оставить везде СМ7, и все будет нормально работать. Но, если вам нужны мотивы, которые сами автоматически транспонируются в нужный ключ, то вам придется изменять трек «аккордов композиции» вашего мотива и трек аккордов вашего сегмента. Мотив, который вы ввели в последнем разделе, на самом деле, построен на СМ7. Так что нет необходимости изменять трек «аккордов композиции» мотива. Также, этот мотив отлично звучит, воспроизведенный поверх Pattern 1. Если вы мне не верите, то начните воспроизводить схему 1, а потом используйте панель инструментов вторичного воспроизведения (показанную на рис. А. 15), чтобы сыграть ваш мотив. Совет. Также во время ваших экспериментов, обратите внимание на тот факт, что когда бы вы ни нажали кнопку воспроизведения, DirectMusic всегда начнет ваш мотив по beat. Конечно, можно настроить, чтобы так не было, но в большинстве случаев, выравнивание по beat сильно помогает. I1 ±11I * I Exarr.pleStyle.stp: Motfl _£| Рис. А.15. Панель инструментов вторичною воспроизведения
Создание динамически музыАи
335
Проблемы появляются, когда вы воспроизводите ваш мотив поверх Pattern2. Pattern2 использует последовательность F-G-F-G и ваш, основанный на С мотив, не очень подходит. Конечно, это совсем не так плохо, как проигрывать аккорд В поверх F, но все равно ничего хорошего в этом нет. Предположим, что мы хотим, чтобы наш мотив проигрывался в ключе F или G соответственно, когда это необходимо. То есть нам нужно, чтобы он воспроизводился в F в такты 1, 3, 5 и 7, и в G в такты 2, 4, 6 и 8 (соответственно аккордам Pattern2). Чтобы реализовать это, перейдите к Pattern2 и обратите свое внимание на трек «аккордов композиции». В качестве аккорда для такта 1 вы должны увидеть 2СМ7. Щелкните на нем правой клавишей мыши и выберите Properties. Вы перейдете в окно свойств аккорда, показанное на рис. А. 16. В этом окне вам нужно сделать две вещи. Otord Properties Chord/Scale choid Inversion I
LF1 E
Sharps
Г Flats
Рис. А.16. Окно свойств аккорда
Сначала вам нужно изменить основной ключ и октаву с 2С на IF. Нажмите на нижнюю вращающуюся стрелочку семь раз, чтобы переместить ваш основной ключ вниз до IF. Обратите внимание, что по мере того как вы это делаете, красные клавиши движутся вниз следом за вами. Эти красные клавиши - это ноты аккорда, на котором основан мотив. Вам нужно убедиться, что четыре красные ноты каждого канала (1-4) образуют аккорд F мажор (F-A-C-F). Щелкните по клавише пианино, чтобы нажать ее. Совет. Когда вы изменяете ноты, соответствующие красным клавишам фортепиано, начинайте с нижнего канала, канала 1. При изменении вами красных нот на канале I, DirectMusic автоматически изменит и синхронизует каналы 2, 3 и 4. Обычно, вы будете хотеть, чтобы на всех четырех каналах красные клавиши были одни и те же, так что это поможет сэкономить вам немного времени.
336
Прило&ениеА
Текстовое поле, которое на данный момент содержит М7, - это просто место, где вы можете отметить, пользуясь музыкальными обозначениями, аккорд, который вы вводите, используя красные клавиши фортепиано. DirectMusic не обращает внимания на значение этого поля; оно используется только для удобства пользователя. Кроме этого, окно свойств аккорда предоставляет вам множество других возможностей, но для нашего простого примера, того, что мы сделали, вполне достаточно. Закройте окно свойств и переместите ваш курсор (красная вертикальная линия) на начало следующего такта. С выделенным треком «аккордов композиции» (он должен подсветиться светло-желтым), нажмите клавишу Insert на вашей клавиатуре. Теперь, проделав аналогичную работу в окне свойств аккорда, установите 1GM7 в качестве «аккорда композиции» этого такта. Продолжайте процесс для каждого такта, чередуя 1FM7 и 1GM7, для всех тактов Pattern! и Pattern2HighGroove! Для того чтобы не усложнять вещи, можно заменить М7 на М для каждого случая, при этом нужно не забыть убедиться, что красные ноты образуют мажорный аккорд, а не мажорный септаккорд. Пройдитесь по всем схемам и сегментам и, используя окно свойств аккорда, измените все величины 2СМ7, установленные по умолчанию, на 2СМ. Стоп! Мы еще не закончили! Если вы сейчас проиграете ваш сегмент, то вы услышите, что DirectMusic транспонирует Pattern2 и Pattern2HighGroove и звук получается отвратительный. Причина, по которой DirectMusic транспонирует, состоит в том, что вы поменяли трек «аккордов композиции» Pattern2, но не поменяли трек аккордов сегмента. Вам нужно перейти к сегменту и добавить в трек аккордов элементы, которые будут соответствовать аккордам воспроизводимой схемы. Это значит, что вы должны ввести величину 2СМ для тактов 1 и 20, 1FM для тактов 12, 14,16 и 18, и 1GM для тактов 13,15,17 и 19. Если вы сделаете это, то ваш аккорд сегмента всегда будет совпадать с аккордом схемы и DirectMusic не будет ничего транспонировать. Теперь, для окончательной проверки - проиграйте мотив в любой момент во время воспроизведения вашего сегмента, и обратите внимание на то, как DirectMusic автоматически транспонирует его в F или G в зависимости от трека сегмента. Отлично получилось, правда? Конечно, вы можете делать аккорды, схемы и мотивы гораздо более сложные (и интересные!), чем я привел здесь. DirectMusic может произвести транспонирование между практически любыми двумя аккордами. Например, он с легкостью транспонирует мажор в минор - все, что вам нужно сделать, это правильно выбрать красные клавиши фортепиано в окне свойств аккорда. Удачи в экспериментах!
Создание динамически музь&и
337
И напоследоК, немного про пути akkopgoB (chord paths) Перед тем как завершить этот раздел об аккордах, мне хотелось бы упомянуть еще одну мощную возможность DirectMusic - вы не должны вводить ему вручную аккорды для каждого такта. Вы можете заставить его подобрать аккорды самостоятельно! При правильном использовании, со схемами, которые поддерживают эту возможность, это приведет к интересной динамической музыке. По сути, все, что вам нужно сделать, это настроить «карту аккордов», которая укажет DirectMusic на то, какие последовательности аккордов он может выбирать для сегмента. DirectMiisic автоматически выберет путь, через эту карту аккордов, и вы получите отличную динамическую музыку. Для более подробной информации по реализации этого, обращайтесь к документации по DirectMusic Producer, и ознакомьтесь с учебным пособием (tutorial), прилагаемым к DirectMusic Producer.
Создание альтернативных схем Последнее, что осталось сделать, — это создать альтернативные схемы для больших значений ритмического уровня. Таким образом, ваша игра сможет контролировать напряженность музыки. Ваш код может изменять модификатор ритмического уровня, который по умолчанию равен нолю. Модификатор ритмического уровня и текущий ритмический уровень (выставляемый сегментом) складываются, чтобы определить итоговый ритмический уровень (и, таким образом, схемы доступные для воспроизведения). Вы уже создали две схемы с ритмическим уровнем 1 и 2. Теперь вы создадите другие две схемы с ритмическими уровнями 11 и 12. Эти две схемы (назовите их PatternIHighGroove и Pattern2HighGroove) будут содержать ту же музыку, что и Pattern 1 и Pattern2, но в более напряженном варианте. Ну, может быть, напряженность изменится не так уж и сильно - все, что добавится это трек ударных; но я думаю, вы поняли основную идею: когда что-то происходит в игре, ваш код выставляет модификатор ритма равным 10 и, как следствие, начинают проигрываться «высоко ритмичные», более напряженные схемы. В добавлении трека ударных, нет ничего нового. Скопируйте Patternl (используйте буфер обмена) и переименуйте в PatternlHighGroove. Добавьте новую партию и в качестве инструмента для нее выберите вашу собственную барабанную петлю. Далее, создайте в этой партии одну длинную ноту, соответствующую С5 и длящуюся четыре такта. После четвертого такта создайте еще одну ноту длиной в четыре такта так, чтобы ваша барабанная петля играла и в последних четырех тактах. 12-1490
338
ЛрчлоАениеА
Проделайте то же самое с Pattern2, назвав копию Pattern2HighGroove. He забудьте зайти в свойства схемы и изменить ритмические уровни этих схем на 11 и 12!
Сохранение В Runtime формате (опять) И вот, вы закончили (ура!). Сохраните вашу динамическую музыку в Runtime формате и поместите ее в ту директорию, где находится исходный код к главе 11. Вот и все!
Заключение Создание хорошей динамической музыки требует многого. Вы изучили основы - к примеру, вариации, мотивы, чередующиеся сегменты и составление карт аккордов, но все же существует менее используемые компоненты, о которых ничего не рассказано в этом приложении. Если вы действительно хотите разбираться в динамической музыке, то вам придется потратить много времени на создание динамических партитур и их отладку, до тех пор пока вы не охватите все возможности DirectMusic Producer. DirectMusic Producer - это достаточно сложная программа, так что не торопитесь и больше экспериментируйте. Также не забывайте, что динамическую музыку писать сложнее, чем обычную, точно так же, как и написать (хороший) интерактивный рассказ сложнее, чем обычный - вам нужно учитывать больше возможных путей событий. Очень просто увлечься и потратить много времени и усилий для достижения совсем небольшого результата. Если у вас есть часть игры, которая требует 30 секундного отрезка музыки, и игрок собирается только один раз побывать в этой части, то не тратьте ваше время на создание множества вариаций и альтернативных сегментов. Используйте многослойность динамической музыки только там, где это действительно нужно.
Приложение Б
Советы по созданию игрового аудио ЕСЛИ вы одновременно и программист, и звуковой дизайнер в вашей игре, то это приложение для вас. Я собрал ряд советов по созданию высококачественных звуковых эффектов и музыки для вашей игры и предоставляю их вам в надежде, что они помогут вам начать или дадут что-нибудь, чего вы раньше не знали.
МузыКа Часто игровая музыка - это самый игнорируемый аспект игры. Игровой звук, в целом, отодвигается на второй план по сравнению с графикой игры, а внутри звука, музыка часто отодвигается на второй план по сравнению со звуковыми эффектами и речью. Не позвольте, чтобы такое случилось в вашей игре! Игра с графикой ниже среднего, но с качественной музыкой, часто выглядит лучше, чем визуально очень красивая игра, обладающая блеклой и невыразительной музыкой (или совсем не имеющая музыки). В этом разделе я расскажу о нескольких приемах, которые позволят вашей игре получить высококачественную музыку.
MysbikamnoM моЖно и не быть Вам совсем не нужно быть профессиональным музыкантом, чтобы создавать игровую музыку, которая звучит более чем достойно. Проводя аналогии с миром графики, можно увидеть, что даже если у вас нет склонности к рисованию, существует большое количество программ, которые позволяют создавать вам хорошую, а иногда и поразительную графику. Такие программы, как Bryce, Photoshop, Kd's 12"
34О
Приложение Б
Power Tools, Vue d' Esprit могут временами вытащить вашу программу из трясины «рисунков программистов» (обратите внимание на слово «временами»). Аналогично, существует множество разнообразных программ, которые могут помочь вам в простом и быстром создании высококачественной музыки. Я поместил на ваш CD ссылки и демонстрационные версии некоторых программ. ,
Использование популярных мелодий В Вашей игре
В наши дни считается модным включать популярные мелодии в свои игры. Прежде чем вы радостно откроете свою МРЗ коллекцию и начнете создавать звуковую дорожку к вашей игре, вам нужно уяснить, что легальное использование этих мелодий стоит денег, и временами много денег (говоря конкретнее, эти деньги измеряются тысячами и десятками тысяч долларов). Конечно, технические требования для использования популярных мелодий в вашей игре невелики. Вам просто нужно найти требуемый МРЗ файл. Тем не менее, если вы будете распространять этот файл без разрешения людей, создавших это произведение, вы будете нарушать закон об авторских правах, что может вылиться в штрафы на суммы в сотни тысяч долларов. Даже если вы не собирались зарабатывать деньги на этой игре, распространение чьей-либо мелодии без согласия автора противозаконно. Чтобы использовать эти мелодии в игре легальный путем, вы должны связаться с владельцем прав на эту мелодию и поговорить с ним по поводу того, что вы собираетесь делать. Будьте готовы к необычным вопросам по поводу того, что вам нужно — например, нужна ли вам вся мелодия или только фрагмент?
Найм местного таланта Может быть, у вас есть друг, который играет в группе или пытается создать свою, или, может быть, есть местная группа, чей звук, по вашему мнению, идеально подойдет для игры, в этом случае подумайте о заключении с ними сделки. Это освободит вас от необходимости непосредственного написания и записи музыки, а им даст так необходимую раскрутку и цель для приложения их творческой энергии. Наличие еще кого-то, рассчитывающего на выпуск вашей игры, также помогает, когда вы в течение нескольких дней ищите какую-то ошибку и у вас появляется желание забросить этот проект куда подальше и заняться чем-нибудь новым. Не существует никаких жестких правил о том, что должна получить группа в качестве вознаграждения за вклад в ваш проект. Вознаграждение может изменяться от 6 ящиков пива до тысяч долларов, от многообещающего «Использована музыка...» в заглавных титрах игры до части прибыли от продаж. Вы должны сами разобраться
Советы по созданию игрового аудио
341
с деталями сделки; но вам нужно иметь представление о том, чего стоят ваши обещания и о том, чего стоит группа. Например, если вы никогда раньше не выпускали игру, то ваше обещание выплатить часть прибыли от игры немногого стоит. Некоторым группам этого хватит; другие могут захотеть часть денег вперед. После того как вы наняли группу, вашим следующим шагом будет подробно объяснить им, чего вы от них хотите. Для каждой необходимой вам мелодии опишите им эмоциональное состояние, которые вы хотите создать, окружение, длительность, необходимость точек зацикливания, для того чтобы ваша игра могла проигрывать мелодию бесконечно и т. д. Не забывайте, что в целом, у большинства игровой музыки нет текстов. В этом она похожа на музыку из фильмов и телевидения, которая используется только для того, чтобы создать определенное настроение. Тем не менее, в некоторых ситуациях текст может быть очень к месту следуйте вашей интуиции и обращайте внимание на то, как это делается в фильмах. В конечном счете, совместная работа с другом из группы может обогатить вас опытом. Вы оба обогатитесь в творческом плане (у него или нее могут оказаться великолепные идеи для воплощения в вашей игре!) и вы оба обнаружите, что проект гораздо сложнее забросить, если вы работаете над ним не в одиночку.
3BykoBbie эффеКты Конечно, вы можете воспроизвести WAV файл в ответ на действия вашего пользователя, но какой WAV файл выбрать? Именно для этого нужен этот раздел. Здесь вы узнаете некоторые методики, позволяющие вам получить высококачественные звуковые эффекты для вашей игры.
Kak определить, Когда использовать aBykoBou эффеКт Одна из тех вещей, которые наиболее часто встречаются среди независимых разработчиков, — это игры с «кривобокими звуковыми эффектами», как я люблю их называть. Кривобокие звуковые эффекты - это такое явление, когда в вашей игре есть всевозможные звуковые эффекты для вещей, которые на самом деле не имеют реального значения, но нет ключевых звуковых эффектов. Например, представьте себе игру в жанре fantasy action с героем, который машет мечом во все стороны и несколькими злыми по этому поводу импами. Верите вы мне или нет, но я видел любительские игры, в которых имелось несколько различных звуковых эффектов рычаний импов, ранений импов и смерти импов, но никакого свиста меча, звуков ранений героя или звуков ударов меча!
342
Приложение Б
Вы можете избежать участи такой игры, следуя основным принципам использования звуковых эффектов. Для начала, поймите, что первая и самая важная цель звукового эффекта - это донести до игрока, что что-то случилось. Они передают информацию игроку через звуки. Если они справляются с этим, то тогда можно начать думать об их второй цели, которая состоит в создании определенного настроения и ощущения погружения в игру. Хорошим правилом будет заводить звуковые эффекты для всего, что может сделать герой в игре. Другим хорошим правилом, о котором стоит упомянуть, будет то, что человек, не видящий монитора, должен суметь сказать, производит игрок определенные действия или нет.
КоллеКции aBykoBbix эффеКтоВ Итак, вы определились с тем, какие звуковые эффекты вам нужны и когда вы будете их использовать. Вы смотрите на ваш список и думаете о том, что так много эффектов создать просто невозможно. И тут вы понимаете - вам ведь и не нужно! Насколько много различных CD-ROM, содержащих иллюстративные вставки для использования в печатных изданиях и Web сайтах, настолько много «коллекций заготовок звуковых эффектов», которые вы можете купить и использовать в своих собственных приложениях, не платя за их использование. Большинство этих коллекций содержат стерео WAV файлы высокого качества, которые вы можете при желании преобразовать в моно, чтобы использовать в игре. Вы обнаружите, что стоимости таких коллекций сильно различаются. Профессиональные коллекции, созданные для профессиональных фильмов и трансляций, могут вам обойтись достаточно дорого; тем не менее, некоторые коллекции звуковых эффектов могут быть приобретены по стоимости обычной видеоигры (или даже по стоимости тех игр, которые предлагаются по сниженным ценам!). В качестве дополнительного бонуса некоторые из этих коллекций содержат свободные от дополнительной платы фрагменты музыки, которые идеально подходят для экрана завершения игры или для экрана опций, где все, что вам нужно, это короткая мелодия. На вашем CD имеются ссылки на компании, которые продают коллекции звуковых эффектов.
Советы по созданию игрового аудио
343
Создание Ваших собственных aBykoBbix эффеКтоВ Вы просмотрели все коллекции звуковых эффектов, но так и не нашли подходящий звуковой эффект для скрипучих ворот или бормочущего гоблина. Приведу несколько советов по записи ваших собственных звуковых эффектов . Сведите к минимуму фоновый шум. Это понятно, но если вы можете, то уберите ваш микрофон подальше от компьютера и его шумных поклонников, или используйте переносное устройство записи. Также, постарайтесь выбрать помещение с хорошими акустическими свойствами. Иногда удобно использовать ванные комнаты, потому что кафельные стены лучше отражают звуковые волны. Не забывайте, что вы можете обработать звук после записи. Нет никакого правила, которое запретило бы вам понизить высоту чьего-либо храпа, чтобы создать эффект храпа людоеда! Поэкспериментируйте с обработкой звука в вашем звуковом программном обеспечении - попробуйте поиграть звук задом наперед, изменить его высоту, использовать фланж или фильтр и так далее. Думайте наоборот. Не рассуждайте так: «мне нужен эффект скрипа ворот, так что мне нужно найти что-нибудь, что звучит так же как скрипучие ворота». Вместо этого, выберите какой-нибудь случайный объект, закроите глаза и подумайте: «похож ли этот звук на скрип ворот, или, быть может, я могу изменить его так, чтобы он стал похож?». Ваше игровое визуальное содержание сильно добавит реалистичности вашему звуковому эффекту. Да, может быть в реальной жизни, это мяукнула кошка, но когда ваша игра проигрывает этот звук в ответ на то, что игрок открывает старые, ржавые ворота, звук звучит гораздо более похоже на скрип таких ворот. До тех пор пока эффект не узнается чересчур просто, все в порядке. Просите других о помощи. «Охота за звуковыми эффектами» может иногда стать очень веселым занятием - «поиск звукового эффекта» может стать отличным поводом, чтобы сделать что-нибудь забавное, чего бы вы никогда не сделали просто так. Иногда единственный способ получить хороший «эффект ходьбы по снегу» это подняться в горы и записать кого-нибудь, идущего по снегу. Заручитесь поддержкой нескольких друзей и начинайте исследования!
1 Существует ряд книг по поводу записи шумовых эффектов в домашних условиях. Их вполне можно найти в библиотеках. - Примеч. науч. ред.
344
Приложение Б
СКачиВание зВуКовых эффеКтов из Интернета Одно слово: нет. Скачивание звуковых эффектов может показаться простым решением, но это очень рискованное занятие. Подавляющее большинство WAV файлов, которые вы можете скачать из Интернета бесплатно, были вырезаны из фильмов, телешоу или других видеоигр. Это значит, что их использование незаконно настолько лее, насколько незаконно использование популярной мелодии бесплатно. Даже если вы измените звук до неузнаваемости, у вас все равно нет прав распространять его без согласия автора. Также, у вас нет никакой возможности узнать, на самом ли деле бесплатен звуковой эффект, скачанный из Интернета. Конечно, на сайте сказано, что за него не нужно платить, но если он звучит подозрительно похоже на возглас Гомера «D'oh!» из мультсериала Симпсоны, то скорее всего, платить все-таки нужно. Последняя вещь, которую бы вы хотели получить после выпуска вашей игры, - это письмо от FOX Legal Team с вопросом, почему голос их персонажа используется в вашей игре!
Речь
i
Речь - это значительная часть игрового аудио в современных играх. В начале игры могли обходиться плохим озвучиванием голосов, потому что любое озвучивание уже считалось достижением и потому что, у игрока еще не было возможности привыкнуть к хорошему озвучиванию. Тем не менее, это время прошло, и сегодня иметь плохое озвучивание голосов - это выбор ничуть не лучше, чем вообще не иметь озвучивания. Этот раздел опишет некоторые пути получения высококачественной речи, встроенной в вашу игру. 1
Найм akmepoB и akmpuc для озучиВания Существует множество профессиональных и при этом желающих работать, актеров и актрис, которые могут озвучить ваших игровых персонажей. Обычно найм таких людей относительно доступен по цене - порядка нескольких сотен долларов — хотя, как и везде, вы можете заплатить много денег, чтобы нанять высококлассного актера или актрису. Технически, работать с озвучивающими актерами и актрисами очень легко, так как теоретически все они имеют собственные профессиональные студии записи и достаточно знают про компьютер, чтобы отправлять звуковые файлы по электронной почте. В большинстве случаев, вы просто отправляете вашему актеру или
Советы по созданию игрового аудио
345
актрисе сценарий, содержащий точные фразы, которые он или она должна произнести. Объяснение эмоционального состояния персонажа на этот момент, также может помочь (если оно не очевидно из простого прочтения сценария). Если вы не хотите платить за найм актеров или актрис, есть возможность воспользоваться помощью нескольких друзей. Переговорите с несколькими знакомыми, раздайте им их сценарии, подготовьте вашу звукозаписывающую аппаратуру, и приглашайте всех для прочтения фраз, пообщайтесь, пообедайте или займитесь чем-нибудь еще, чтобы приятно провести время. Многим людям нравится озвучивать персонажей, особенно, когда они знают, что их тяжело будет узнать по голосу. Попробуйте сами! После того как вы избавитесь от странного ощущения при прослушивании записи вашего голоса, вам понравится изображать голосом различные эмоциональные состояния и пытаться добавить реализма вашим персонажам.
Проблема локализации Одна из наиболее сложных проблем, когда дело доходит до игровой речи,это локализация. Если вы хотите, чтобы ваша игра была без проблем доступна неанглоязычным игрокам, то вам придется разбираться с проблемой локализации. Локализация может потребовать резкого увеличения количества строчек для ваших озвучивающих актеров или актрис. Также вам может потребоваться найм актеров, говорящих на нескольких языках. Сразу подумайте, на каких языках будет ваша игра и будут ли эти языки иметь свой собственный набор звуковых файлов. Если диалоги не имеют решающей роли в вашем игровом процессе, то вы можете оставить звуковые файлы на английском языке, а титры выводить на экран на любом другом.
Синхронизация gfkukemiu губ Одним из самых сложных аспектов игровой речи является совмещение артикуляции рта игровых персонажей и звука, который раздается из динамиков. Даже на сегодняшний день, большинство игр не могут реализовать это на таком уровне, чтобы игроки поверили, что персонажи на самом деле говорят. Тем не менее, технологии развиваются, и на рынке уже существуют программы2, специально созданные для решения проблемы с синхронизацией движений губ и звука для игровых персонажей или компьютерных визуализаций.
Например, одна из лучших программ этого жанра - LifeStudio: Head. - Примеч. науч. ред.
346
Приложение Б
Чтобы правильно синхронизовать губы, вы должны разбить ваш исходный звук на отдельные фонемы. Фонемы для речи аналогичны битам для данных; другими словами, фонемы - это элементарные единицы, из которых строится речь. Каждая фонема может быть привязана к определенной форме рта персонажа, позволяя вам легко анимировать его, после того как исходный звуковой файл разбит на набор фонем. Существуют программы, которые могут вам в этом помочь; я поместил ссылки на некоторые из них на ваш CD.
Зайлючение Я надеюсь, что мои советы окажутся вам полезны. Создание качественного игрового аудио, как и все остальное, становится тем проще, чем больше вы им занимаетесь, так что моим последним советом будет «не сдавайтесь!». Немного терпения и хороший критичный слух могут сильно помочь вам при создании наилучшей возможной музыки для вашей игры.
Предметный указатель "Sound Cards, Voice Management, and Driver Models" (Шмидт), 35 "Squeezing More Out of Assert" (Рабин), 64 «брейк» украшение, 180 «запирание» буфера, 46-47 3D звук, 228-245, си. также I3DL2; OpenAL DSSListener, 236 ЕАХ, 270-271 буферы, 230 в примере программы про дальний космос. 243-245 звуковые конусы, 232 класс C3DSoundEffect class, 239-240 минимальная/максимальная дистанция, 231 расположение, 230 резким работы относительно головы, 235 режим работы, 235 свойства, 230-235 звуковые конусы, 232 и экземпляры звука, 91 изменения в CAudioManager, 238-239 класс C3DSoimdEffect, 239-240 класс C3DSoundListener, 236-238 минимальная/максимальная дистанция для буферов, 231 моделирование окружения, 260-271 направление приемников, 232 немедленные изменения, 236 отложенные изменения, 236 поддержка в BASS, 28 приемники, 230 в примере программы про дальний космос, 243-245 направление, 232 расположение, 230 фактор расстояния, 234 фактор спада,232-234 эффект Доплера, 234 пример программы про дальний космос, 241-245 принципы,228 программирование, 236-241 расположение приемников и буферов, 230 режим работы относительно головы, 235 режим работы, 235 симуляция звуковых волн, 230 структура m_Props, 236 фактор расстояния, 234 фактор спада, 232-234
эффект Доплера, 230, 234 44100 Герц, 14 52nd Street (Joel), 26
А ACid, 42 ActiveX, поддержка ModPlug, 30 ADPCM, формат сжатия данных, 18 AIFF файлы, воспроизведение DirectSho ,v,
134 Amiga, формат музыки MOD, 20 AssembleErrorlnfoString, 211-212 AU файлы, воспроизведение DirectShow, 134 AudioVB синтакс, 205 скрипты,200 В BeOS и MikMod, 29 Вгусе программа, 339 C/C++, см. также обработку исключений поддержка в BASS, 28 поддержка в FMOD, 29 поддержка в ModPtug, 30 Cakewalk Pro Audio, 111 CAudioManager, 138 двухфазное создание, 67 для 3D звука, 238-239 объявление класса для, 67-69 поддержка MikMod, 171-172 построение, 66-71 функция LoadSound для, 70-71 функция Unlnit, 69 CD ID, 155 CD player инициализация, 163 пример программы, 163-165 CD аудио, 2525-26,149-150 Windows MCI для, 150-153 воспроизведение треков, 157 выдвигание CD, 160 детектирование смены, 160-162 команды состояния, 160-162 метод CCDPlayer::GetAvailableDevice, 153-155 метод Play для, 151-153 определение количества треков, 162
348 определение текущей позиции воспроизведения, 159-160 остановка, 157-158 приостановка воспроизведения, 158-159 продолжение воспроизведения, 158-159 синхронное или асинхронное выполнение команд, 162-163 управление громкостью, 163 флаг MCIJMOTIFY. 162 CD, см. также CD аудио длительность 84 минуты, 16 ссылки, 306 универсальный код изделия (UPC), 154 частота дискретизации 44100Гц, 14 CD-R/CD-RW, 26, 153 CLSID, 69 CNetConnectWizard, 283-287 DoConnectWizard. 286-286 завершение работы, 286 инициализация, 283 отображение, 285-286 получение функции обратного вызова, 284-285 создание, 283 установка значений по умолчанию, 285-287 CNetVoice, 289-290 COpenALManager, 253 CreateSoundBuffer для вторичного буфера, 44 для первичного буфера, 42 Creative Labs, 246 сайт, 271 CTrackedMusie, 170-171 CTrackMinSecFrame, 150 CWAVFile, 81-83 дополнительный оператор присваивания, 82 загрузка WA V файлов в фат, 82-83 звуков из файла, 85 сохранение, 83 скриншот Impulse Tracker, 167 D Delphi поддержка в BASS, 28 поддержка в ModPlug, 30 DevStudio, 37 метки-идентификаторы, 88 с библиотеками Ogg Vorbis, 144 создание файлов ресурсов, 87-88 сохранение собственных ресурсов, 88 DirectGraphics, 54 DirectMusic Producer
Предметный указатель DirectMusic Producer, 111 PChannel20,3l9 атака, спад, поддержка, затухание (ADSR) инструмента, 315-317 горячие клавиши масштабирования, 321 добавление вариаций, 327-329 дополнительные партии, 324 загрузка WAV файлов, 313-314 зацикливание сегментов, 326 звуковые эффекты, 215-217 значки, 311 использование редактора схем, 320-321 квантование нот; 323-324 кнопка метронома на панели инструментов, 322 настройка ансамбля, 318-320 настройка артикуляции, 315 настройка гармонии, 319,340 настройка мелодии, 321 объекты создание, 311 список, 311-313 редактирование диапазонов нот, 314 редактор ансамбля, 319-320 сегменты добавление схем, 325-326 зацикливание, 326 скрипты AudioVP, 200 создание DLS коллекций, 311-319 создание альтернативных схем, 337-338 создание мелодий с помошью, 310 сохранение в Runtime формате, 327, 338 схемы ввод нот, 322-323 добавление схем в сегмент, 325-326 завершение ввода, 325 квантование нот, 323-324 создание альтернативных схем, 337-338 создание, 320-321 трек аккордов, 320 установка громкости, 319 установка, 307-310 флаг Note Overlap Allowed, 317 число LSB, 315 число MSB, 315 элементы управления Instrument, 314-315 DirectMusic, 25, 32, см. также DirectMusic Producer; MIDI клавиатура DLS коллекции, 189 аккорды ,332-337 ввод сегментов настройка, 334-337 пути аккордов, 337 ансамбли, 177, 180-181, 183, 190
Предметный у&азатель вариации в схемах, 179, 327 воспроизведение мотивов, 195-196 сегментов, 193-194 детектирование ритма, 120-121 Загрузчик (Loader), 54-56 использование термина, 176 каналы воспроизведения, 177 контрольные сегменты, 193 многопоточность, 124-125 модификатор ритма, 194-195 мотивы,179 воспроизведение, 195-196 выравнивание по долям, 335 добавление, 331-332 различные партии, 334 набор инструментов General MIDI, 189 ноты, 175-174,186 обзор интерфейсов, 54 объект Performance, S6-57 описание, 33 партии, 186 определение, 177 ритмы и ритмические уровни, 186-189 диапазоны, 183-184 изменение ритмических уровней, 188-189, 194-195 ритмический модификатор, 194-195 сегменты, 182-183 воспроизведение, 193-194 для аккордов, 334 контрольные сегменты, 193 создание, 56 сложности, 5453-54 создание альтернативных схем, 337-338 создание сегментов, 56 сохранение в Runtime формате, 327, 338 сравнение с трекерной музыкой, 184-190 схемы, 177-179, 186 создание альтернативных схем, 337-338 трек «аккордов композиции», 335-337 трек Var Switch Point, 176 трек УК (Управляющей кривой),177 треки стилей, 176, 182-183 треки темпа, 183 треки, 176 украшения, 178-180 создание, 329-331 управление громкостью, 54 флаг IsSecondary, 205 DirectPlay Voice, 272-291 CNetVoice, 289-290 голоса, как буферы 3D звука, 290-291
349 звуковые карты, 289 инициализация, 287 метод CheckAudioSetup, 289 настройка микрофона и динамиков, 287 начало записи, 272 объект CPlayer, 290 основы, 272 пиринговая связь, 272-273 полнодуплексные звуковые карты, 289 полудуплексные звуковые карты, 289 программирование, 287-290 стандартные голосовые сообщения, 288 счетчик ссылок для объекта игрока, 290 топология сети, 272-278 топология смешивающего сервера, 273, 277, 290 топология эстафетного сервера, 273-277 функция обратного вызова, 287 DirectPlay, см. также DirectPlay Voice GU1D, 279-280 выбор поставщика услуг, 279279 лобби, 284 описание, 278-283 отправление сообщений, 282 отсоединение от игры, 283 пиринговая топология, 280 подсоединение к игре, 280 получение сообщений, 280-282 поток событий в сессии, 278-279 потоки, 282 создание игры, 280 создание интерфейса, 279 стандартные сообщения, 281 функция обратного вызова, 281-282 DirectShow, 32 ЕАХ.271 буфер,220 воспроизведение звуков, 72-73 вторичные буферы, 33-35 добавление WAV файлов, 54 инициализация, 41-42 описание, 33 первичные буферы, 33-35 DirectX 3 интерфейс, поддержка в BASS, 28 DirectX AppWizard, 40 DirectX Audio, см. также 3D звук; ToneGenerator вместе с DLS 1,25 настройка компилятора, 36-40 • настройка путей, 36-38 определения терминов, 33 основы, 32-36 примеры программ для воспроизведения wave файлов, 73
Предметный указатель
350 стриминг, 35-36 DLL, 69 nOgg Vorbis, 141 DLS коллекции, 175 ансамбли, 180 и DirectMusic Producer, 311-319 как списки сэмплов, 189 DLS1, 24-25, 316 DLS2, 25, 316 DoConnectWizard, 286-287 Dolby Digital кодировка, 271 сайт, 271 DS3Listener, 236 dwBufferBytes, 45 DWORD, 156-157 dynamic_cast, ключевое слово, 116
E EAX, 270-271 поддержка FMOD, 29 поддержка в BASS, 28 элементы, 270-271
редактор окружения, бегунки, 266-268 загрузка окружений, 268-269 создание, 266-270 сохранение окружений, 268-269 сохранение окружений, 268-269 структура DSFXI3DL2 Reverb, 264-262 термины, 261-263 типы звуковых волн, 261-263 усложнение программы, 268-269 ID классов, 69 IDirectPlaySLobbiedApplication, 284-285 IDirectPlaySPeer, 283 IDirectPlayVoiceClient, 287 IDirectPlayVoiceServer, 287 IDirectSoundS, 44 Interactive Audio Special Interest Group, 260261 IT, поддержка в BASS, 28 J
Java, MikMod на, 29
К
F
Kai's Power Tools, 340
FMOD на GameCube, 29 FMOD, 29 Fraunhofer IIS-A, 17 Future Crew, 20 F4M синтез, 19-20,168 об устарелости, 27
L LAN, DirectPlay Voice на, 273,278 Linux, Ogg Vorbis на, 140 Loader, 54-56 директории, 89 загрузка MIDI файлов, 113 LoadGameResources обработка исключительных ситуаций, 58-63 Loki Entertainment Software, 246 LPDIRECTSOUNDBUFFER, 46
G Game Programming Gems 1, 64 General MIDI Level 1 Sound Set (GM звуковой набор), 24 категории для, 23 GetNotificationPMsg, 120-121 I I3DL2,260-266 добавление в звуковой движок, 263 загрузка окружений. 268-269 интерфейс для помех, 270 интерфейс для преград, 270 как эффект, 263-263 описание, 260261 параметры отражений, 264-262 позднее отражение, 261-263, 262 понятия, 261-263 предустановленные эффекты, 266 прямой путь, 261-263 ранние отражения, 261-263
M
m_Script, 209-209 M ASM, поддержка в BASS, 28 MCI, CM. Windows MCI MCIJVIAKEJTIMSF, 156-157 MIDI клавиатуры, 307-310 если не работает, 309 MIDI, 20-24 MIDI файлы н звуковые движки, 111-113 RTTI (информация о типах в процессе выполнения), 115-116 воспроизведение DirectShow, 134 воспроизведение Windows MCI, 126 воспроизведение файлов, 113-116 детектирование ритма, 119-121 загрузка файлов, 113-116
Предметный указатель и звуковой движок, U 3-115 и темп, ИЗ, 116-119 каналы, 109 набор инструментов General MIDI, 175 низкоуровневые операции MIDI по вводу /выводу, 126 описание, 109-111 сообщение Note On, 109-110 MikMod на MacOS, 29 MikMod, 29 CTrackedMusic, 170-171 библиотека, 170 в звуковом движке, 170-172 для трекерной музыки, 169-172 завершение работы, 171 изменения CAudioManager для, 171-172 помошь, 170 установка, 169-170 Miles Sound System, 30 ММА (MIDI Manufacturer's Association), 25 ModPlug, 30
MP3
WAV файлы 8,17-18, 133-134 аудио теги,129,132 воспроизведение DirectShow, 134-138 добавление поддержки в звуковой движок, 137138 добавление популярных мелодий, 340-340 использование термина, 27 коды жанров, 130-131 описание, 127 поддержка в BASS, 28 проблемы с лицензированием, 27 размер, 127 формат файла, 128-132 фреймы, 128 MSDN, 73 N NoiseTracker, 20 О Ogg Vorbis, 18,27,140-148 библиотека Vorbisenc, 142 библиотека Vorbisfile, 142 библиотеки, 140-142 в звуковом движке, 146-148 директории, 142 документация, 142 закрытие файлов, 146 использование Vorbis, 144-148 использование Vorbisfile API, 145-146 открытие файлов, 145
351 поддержка в BASS, 28 структура, 141-142 установка библиотек, 142-144 функция ov_read, 145-146 Oktalyzer, 20 OpenAL COpenALBuffer instance, 256 COpenALManager, 253 EAX,271 буферы, 247-248 свойства, 249 создание и уничтожение. 255-257 включение редистрибутивов, 252 воспроизведение источников, 258 вспомогательные функции,252 добавление в звуковой движок. 251-258 загрузка WAV файлов, 255-257 инициал нзация/деиниинализация, 253-254 источники, 247-248 воспроизведение источников, 258 свойства, 249-251 создание и уничтожение, 257-258 класс COpenALSource, 257-258 контекстные функции, 252 левосторонняя система координат, 251 новые классы звукового движка, 253 обнаружение ошибок, 254-255 основные функции OpenAL, 252 приемники, 247-248 свойства, 252-252 пример программы Deep Space, 258 соглашение по наименованию, 248-249 структура библиотеки, 252 установка, 252 Р РСМ и МРЗ файл, 134 файлы Ogg Vorbis, 145-148 флаг WAVE_FORMAT_PCM, 45 чанк аудио формата, 79-80 Philips, CD прототип, 26 Photoshop, 339 PlaySegmentEx, 91 PlaySound и библиотека winmm.lib, 73 PMsg для ритма, 120 для темпа, 117 pos, 49 ProTracker, 20 PS2 и FMOD, 29 R
352 RAD Game Tools, 30 Redbook audio, 25-27 RefCountPtr.h, 71 RIFF, 76 главный чанк, 78 иМРЗфайлы, 134 чанк формат, 79-80 чанки, 77-78 RLE (групповое кодирование), 264 RTTI (информация о типах в процессе выполнения), 115-116 Runtime формат звуковые эффекты, 217-227 сохранение в DirectMusic Producer, 327, 338
S SAFE_RELEASE, 43, 51 Scream Tracker, 20 поддержка в BASS, 28 скриншот, 168 SetCooperativeLevel, 41 SND файлы, воспроизведение DirectShow, 134 Sony CD аудио, 26 CD проигрыватель, 26 Sound Recorder, воспроизведение МРЗ, 133134 SourceForge, 166 Special Effects Game Programming Using DirectX 8.0,241 StarTrekker, 20 Т TCP/IP для DireetPIay, 279 ThrowCError, объявление, 65 Tli row IfFai led, объявление, 66 TMT Pascal, поддержка в BASS, 28 ToneGenerator определение, 40-41 переключение заданий, 50 U UNIX и MikMod, 29 USB, 153
V Visual Basic и аудио скриптинг, 200 поддержка в BASS, 28 поддержка в FMOD, 29 поддержка в ModPlug, 30
Предметный указатель Visual Studio версии 18, 37 Vued'Esprit,339 W WAV файлы, см. также CWAVFile; DirectMusic Producer; OpenAL Loader, использование, 54-56 RIFF, 76 биты на сэмпл, 80 в МРЗ, 17-18,133-134 воспроизведение DirectShow, 134 выравнивание данных, 80 главный чанк, 78 загрузка в анализатор спектра, 299-300 загрузка из памяти, 84-85 загрузка из ресурсов, 85-88 и метод GetObject, 8484-85 кодеки, 18 количество каналов, 80 коллекции звуковых эффектов, 341 поддержка в BASS, 28 размер, 127 стандартные ID форматов, 79 формат, 77-78 чанк аудио формата, 79-80 чанк данных, 80 чанк формата, 79-80 чанки, 77-78 частоты дискретизаций, 80 Wave Table синтез, 20 Wave трек, см. аудио скриптинг Win32 API, 73 Winamp, 42 Windows MCI, см. также CD аудио DWORD, 156-157 MCI_MAKE_TIMSF, 156-157 MIDI на, 126 возобновление воспроизведения, 158-159 воспроизведение звуков, 73-63 выдвигание компакт диска, 160 детектирование смены, 160-162 для CD аудио, 150-153 команды состояния, 160-162 приостановка воспроизведения, 158-159 синхронное или асинхронное выполнение команд, 162-163' флаг MCI_NOTIFY, 163 флаги, расположение, 160 форматы времени, 156 WMA файлы, 27 воспроизведение DirectShow, 134 воспроизведение, 138 поддержка в BASS, 28
Предметный указатель
X файлы мешей, 241 ХЬох и FMOD, 29 Xiphophorus (xiph.org), 140 ХМ, поддержка в BASS, 28
Z ZIP файлы uOggVorbis, 142 для хранения WAV файлов, 84 А аккорды, см. DirectMusic актеры для озвучивания, найм, 344 актеры, найм, 344 алгоритм MPEG Layer 3 (МРЗ), см. МРЗ алгоритм сжатии с потерями, 17 алгоритмы сжатия звука, 16 анализаторы спектра, 292-293 визуализация графика, 303-304 загрузка WAV файлов, 299-300 загрузка величин, 302-303 класс CDiscreteFourierTransform, 300-301 массивы, создание, 301 пример программы, 299-304 расчет ДПФ, 303 ансамбли, 177 в DirectMusic Producer, 318-320 в DirectMusic, 180-181,183,190 выбор инструмента, 181 артикуляция в DirectMusic Producer, 315 асинхронное выполнение команд, 162-163 атака, спад, поддержка, затухание (attack, decay, sustain, and release - ADSR) инструмента, 175 в DirectMusic Producer, 315-317 аудио менеджер, 66 см. также CAudioManager с MIDI файлами, 113-115 аудио скриптинг, 196-215, см, также класс CAudioScript Time Signature Track, 201 директория Reference Runtime, 204 добавление поддержки в звуковой движок, 206-215 добавление трека(ов), 201 загрузка, 209-212 звуковые дизайнеры и, 200-206 инициализация скриптов, 209-212 интерфейс для, 199 основы, 199-200 переменные
353 получение значений, 213 получение имен, 212-213 установка значений, 213 подпрограммы (routines D) вызов, 214 получение имен, 212-213 подпрограммы (subroutines) вызов, 214 названия, 205 получение имен, 212-213 разработчики и, 206-215 синтакс для, 205 создание простого скрипта, 203-206 создание сегментов, 201-203 строковые значения в качестве переменных, 213 трек Wave добавление, 201 свойства, 203 аудио теги в МРЗ, 129,132 АЦП (аналого-цифровой преобразователь),
13-14 Б
байтовая система, 12 в WAV файлах, 80 байтовый указатель pbMemData, 84 бегунки в редакторе окружений, 266-268 функции, 268 библиотека MCI, 150 библиотека Ogg, 141 библиотека Vorbisenc, 142 библиотека Vorbisfite, 142 библиотека winmm.lib и PlaySound, 73 библиотеки, см. также OpenAL библиотека MCI, 150 библиотека winmm.lib с PlaySound, 73 библиотеки Ogg Vorbis, 140-142 в звуковых движках, 84 использование библиотеки MikMod, 170 проблемы с лицензированием для МРЗ, 27 биты на выборку, см. качество сэмплов буферы, см. также OpenAL; PCM; 3D звук буферы DirectSound, 33-35 быстрое преобразование Фурье (БПФ), 304-305
В вариации в схемах DirectMusic, 178, 327-329 вектор m_Effects, 225 векторы для звуковых эффектов, 225 из указателей CTrackedMusic, 171
Предметный указатель
354 визуализация звука, 292-305, см. также дискретное преобразование Фурье (ДПФ) анализаторы спектров включение популярных мелодии, 340 возобновление воспроизведения CD, 158-159 восприятие громкости, 22S воспроизведение, 14, см, также CD аудио; DirectMusic MIDI файлов, 113-116 OpenAL источников, 258 вторичных буферов, 50 одновременное воспроизведение звуков, 90-91 треков CD аудио, 157 восстановление буферов, 50 время цикла, 198 вторичные буферы «запирание», 46-47 «отпирание», 46-47 в DirectSound, 33-35 воспроизведение, 50-51 восстановление, 50 заполнение, 46-47 освобождение, 51 прокручивание, 47-48 создание, 44-46 выборка, определение, 13 выгрузка звуков, 72 выдвигание CD, 160 вызов mciSendCommand, 150-151 вызов PlaySegment, 72 и метод IsPlaying, 72 выравнивание данных в WAV файлах, 80 высота волны, 49 высота ноты, 175 Г Гарвей, Дэвид, 71 Герц (Гц), дискретизация с частотой 44100 Герц, 14 глобальная громкость, 103 глобальная конфигурация, 36-38 глушение, 228 горячие клавиши масштабирования в DirectMusic Producer, 321 громкости микшера, 103-108 архитектура API, 104-105 бесплатные классы микшеров, 108 изменение значений, 105-106 класс CMixer, 106-108
громкость, см. также звуковые дорожки; громкости микшера в DirectMusic Producer, 319 глобальная громкость, 102 громкости микшера, 103-108 громкость CD аудио, 163 диапазоны, 101-102 звуковые дорожки, 97-100 класс CMixer, 106-108 ослабление, 102
д
Девятая симфония Бетховена, 16 децибелы, 264 Джоел, Билли, 26 диалог Effect Properties, 217 диалог Groove Level Properties, 189 динамическая музыка, 174-177, см. также DirectMusic; DirectMusic Producer DLS коллекции, 175 воспроизведение, 193-194 ноты, 175-176 программирование, 190-193 создание сегментов, 56 учебное пособие по созданию, 308-338 элементы,174 директории для аудио данных, 89 директория поиска, использование, 89 Дискретное преобразование Фурье (ДПФ), 293, см. также анализаторы спектра визуализация графика, 303-304 класс CDiscreteFourierTransform, 300-301 расчет, 303 создание массивов, 302 число Найквиста, 298-299 Доплер, Кристиан, 230 Доплера эффект, 230, 234 в примере программы Deep Space, 241
3 заголовок окна, 41 загрузка звуков, 72 автоматическая загрузка, 72 звуковые эффекты, 344 загрузка, см. также CWAVFile; WAV файлы MIDI файлов, 113-116 WAV файлов в DirectMusic Producer, 313-314 WAV файлов в OpenAL, 255-257 аудио скриптов, 209-212 в редакторе окружения, 268-269 загрузка WAV файлов в анализаторе спектра, 299-300
Предметный указатель заставка SETI@Home, 305 зацикливание звуков, 95-96 метод SetRepeats, 96 сегменты DirectMusic Producer, 326 схемы, 177 экземпляры звука и, 91 -92 звуковая разница времен, 228 звуковая разница интенсивностей, 228 звуковые движки, 28 BASS, 28 CWAVEFile, 81-83 добавление I3DL2,263 добавление OpenAL, 251-258 добавление звуковых эффектов, 220-222 добавление поддержки МРЗ, 137-138 добавление поддержки Ogg Vorbis, 146-148 извещения, 121-124 исключительные ситуации, 63-66 использование MikMod, 169-172 класс CNotificationHandler, 123-124 многопоточность и, 125-126 обработка ошибок, 57-63 объект CError, 63-64 одновременное воспроизведение звуков, 91-93 с MIDI файлами, 111-113 с поддержкой аудио скриптинга, 206-215 список, 28-30 звуковые дизайнеры и аудио скриптинг, 200-206 звуковые дорожки, см. также DirectMusic громкость отдельного сегмента, 100-102 для настройки громкости, 97-101 запрашивание громкости, 101 звуковая дорожка по умолчанию, 97 каналы воспроизведения и, 177 метод Play для, 99 набор, 97 независимые звуковые дорожки, 98 разделяемая звуковая дорожка, 98 установка громкости, 100-101 звуковые карты, 14 для DirectPlay Voice, 289 звуковые конусы, 221-222 звуковые эффекты, 215, см. также аудио скриптинг в текущем времени, 217-227 и DirectMusic Producer, 216-217 и I3DL2, 263-263 и звуковой движок, 220-222 использование, 227 класс CMIDlMusic для, 113 класс эффекта эхо, 222
355 классы эффектов, 222 коллекции, 341343 кривобокие звуковые эффекты, 341 одновременно воспроизводимые эффекты, 90-91 параметры для эффектов, применение, 226 речь, запись, 344-345 скачивание из Интернета, 344 советы по записи, 343 значения ослабления помещения, 264 значки в DirectMusic Producer, 311 И игровые редакторы WYSIWYG, 199 измерители, 104 имена дисководов для CD устройств, 153-155 импульсно-кодовая модуляция (РСМ), см. РСМ инициализация, см. также метод Init; метод Unlnit CNetConnectWizard, 283 DirectPlay Voice, 287 DirectSound, 41-42 аудио скриптов, 209-212 инструменты DLS коллекции, 175 набор инструментов General MIDI, 175 интеллектуальные указатели, 71 Интернет WAV файлы скачанные из, 84 скачивание звуковых эффектов, 344 интерфейс IGfaphBuilder, 135—136 интерфейс IMediaControl, 135—136 интерфейс IMediaEvent, 135-136 история игрового аудио, 19-24 источники, см. также OpenAL ЕАХ и направленность, 270-271 каналы MIDI каналы, 109 в WAV файлах, 80 количество, 45 каналы воспроизведения в DirectMusic, 177, и звуковые дорожки, 177 совместное использование, 177 качество выборки, 224-16 в WAV файлах, 80 определение, 13 расчеты, 45-56 КилоГерц (КГц), 13
Предметный указатель
356 клавиатура для динамической музыки, см. MIDI клавиатуры класс CSDSoundEffect, 239-240 класс C3DSoundListener, 236-238 класс CAudioScript, 206 AssembleErrorlnfoStriag, 211-212 mJScript 209-209 и функция Init, 209 объект DMUS JSCRIPT_ERROR1NFO, 211-212 разработка указателя на, 206-208 создание, 208-209 класс CCDDevicelnfo, 150 класс CCDPlayer, 149-150 метод GetAvailable Device, 153-155 класс CDirectMusicSegment, 112 класс CDirectShowMusic, 137 класс CDiscreteFourierTransform, 300-301 класс CDynamicMusic, 191-193 класс CEchoEffect, 222 класс CEffect, 222 класс CMIDIMusic, I L 3 для темпа, 116-117 указатель, 115-116 класс CMixer, 106-103 класс CMP3AudioTag, 137 класс CNotificationHandler, 123-124 класс COpenALSource, 257-258 класс CSoundEffect, 113 класс CSoundlnstance, 91--93 добавление звуковых эффектов, 220-222 изменения, 223-225 метод AddEffect, 223 метод Init для, 92-93 метод RemoveAHEffects, 224-225 метод Unlnit для, 92-93 применение параметров эффекта, 226 создание, 92-93 класс CSoundPtr class, 206 класс CTrackMinSecFrame, 156 кодеки,18 кодек Vorbis, 142 коды жанров для МРЗ, 130-131 компилятор для DirectX Audio, 36—40 конечное время ноты, 176 конфигурации в DevStudio, 37 коси ну со ид ал ьные волны, 50 и резонанс, 296-298 кривобокие звуковые эффекты, 341 Кули и Турки,304
Л Л им, Джефри, 167 лобби в DirectPlay, 284 локальная конфигурация, 37 изменение, 38
м
Майлз, Джон, 30 макрос FAILED, 45 макрос в Windows MCI, 156-157 мастер темпа, 118-119 матрицы преобразования мирового пространства, 241 метка-идентификатор, 88 метод Download, 72 метод GetGuid, 222 метод GetObject, 8484-85 метод Init в OpenAL, 253-254 для CAudioManager, 67-69 для CAudioScript class, 209 для CNetConnectWizard, 283 для CSoundlnstance, 92-93 метод In it Audio, 69 метод IsPlaying, 72 для CSoundlnstance, 93 метод LoadMotifs, 191 метод LoadOggVorbis, 146-147 метод LoadScript method, 209-210 метод Lock, 46-48 метод Pause, 158—159 метод Play, 50-51, см. также объект CSound дли CD player, 151-153 для звуковых дорожек, 99 метод Resume, 158-159 метод SetAIIParameters, 222, 226 для интерфейса приемника, 236 метод SetFormat, 42 метод SetFX, 217-220 и вектор m_Effects, 225 метод SetLength для зацикливания звуков, 96 метод SetLoopPoints, 96 метод SetRepeats, 96 метод SetSearchDirectory, 89 метод SetStartPoint для зацикливания звуков, 96 метод SetVoIume, 100 метод Unlnit
Предметный у/сазатель в OpenAL, 253-254 для CAudioManager, 69 для CSoundlnstance, 92-93 методы воспроизведения, 176 метроном, кнопка панели инструментов в DirectMusic Producer, 322 Миккерс, Жан-Поль, 29 микрофоны для стерео звука, 16 микширование звука, 18-19 микширующий блок, 15 милибелы (мБ), определение, 264 Ми од Валлат, 29 многопоточность, 124-125 в DirectPlay, 282 моделирование окружения, 260-271 монофонический (моно) звук, 16 мотивы, см. DirectMusic музыка включение популярных мелодий, 340-340 найм местных музыкантов, 340 программное обеспечение для, 339 Н набор инструментов General MIDI, 175,189 кайм местных музыкантов, 340 начальное время ноты, 175 начальные байты, 47 небесные кубы, 241 ноты в DirectMusic, 175-176 в динамической музыке, 175-176 и трекерная музыка, 185-186 свойства, 175-176 скорости, 186
О обработка исключительных ситуаций в LoadWaveFile, 60-6I обработка исключительных ситуаций в Load Sound File, 60-61 обработка исключительных ситуаций в функции main, 60,63 обработка исключительных ситуаций, 57 в звуковом движке, 63-66 отлов неожиданных событий, 58-59 правила для, 58-63 правило для отлова ошибок, 58 обработка ошибок в CD player, 165 в OpenAL, 254-255 в звуковом движке, 63-66
357 в классе CAudioScript, 211-212 звуковые движки, 57-63 окно сообщения, 65 функция DXGetErrorSlringS, 45 обращение, 176 объект CError, 63-64 объект CPIayer для DirectPlay Voice, 290 объект CSound, 72 CAudioManager и, 71 PlaySegmentEx, 91 виртуальные методы, 113 корректировка, 94-95 метод Play, 72 $$ корректировка, 95 "Т метод SetVolume, 100 объект DMUS_SCRIPT_ERRORINFO, 211-212 объект Performance, 56-57 объекты в DirectMusic Producer, см. одновременные звуки воспроизведение звуковыми движками, 91-93 воспроизведение, 90-91 окна сообщений при обработке ошибок, 65 операция LoadMIDI, 114 определение высоких частот, 264 освобождение памяти, 51 ослабление 102 основная мембрана, 294 отражения, см. I3DL2 и ЕАХ, 270 П память и WAV файлы, 84-85 панорамирование сохранение, 106 управление, 104 параметры сэмплов в DirectMusic, 190 в трекерной музыке, 185 партии в DirectMusic, 176,186 первичные буферы в DirectSound, 33-35 настройка, 42-43 удаление, 43 переключение заданий, 50 переменные, см. аудио скриптинг пилообразные волны, 40 подпрограммы, см. аудио скриптинг позднее отражение, 262, 262 полиморфизм, 114
Предметный указатель
358 полнодуплексные звуковые карты для DirectPlay Voice, 289 полудуплексные звуковые карты для DirectPlay Voice, 289 помехи и ЕАХ, 270 и I3DL2, 270 поддержка в FMOD, 29 помощь документация по Ogg Vorbis, 142 помощь по MikMod, 170 поставщики услуг CNetConnectWizard для, 283-286 для DirectPlay, 279279 потоки в DirectPlay, 282 Предметный указатель препятствия и ЕАХ, 270
и I3DL2, 270 поддержка в FMOD, 29 приемники, см. OpenAL; 3D звук пример программы Deep Space, 241-245 в OpenAL, 258 загрузка звуков, 242 обновление буферов и источника, 243-245 приостановка CD проигрывателей, 158-159 программирование 3D звука, 236-241 DirectPlay Voice, 286-290 время цикла, 198 динамической музыки, 190-193 программное обеспечение для музыки, 339 программное обеспечение с открытыми исходниками, 140 программное обеспечение синтезатора, НО продолжительность ноты, 176 прокручивание буфера, 47-48 прямой путь, 261-263 прямоугольные волны, 40 Р Рабин, Стив, 64 рабочая среда Ch2pl_ToneGenerator, 38 ранние отражения, 261-263 расширения в DirectMusic Producer, 311 редактор звуковых дорожек, 217 редактор окружений., см. I3DL2 редактор схем в DirectMusic Producer, 320321 резонанс, 293
и синусоидальные волны, 294-298 и слух, 293-294 математическое задание, 294-298 ресурсы загрузка WAV файлов из, 85-88 создание файлов ресурсов, 87-88 управление, 71 речь актеры, найм, 344 запись, 344-345 проблемы с локализацией, 344-345 синхроннзация движений звука, 345 ритмический удар детектирование в MIDI, 119-121 отставание, 120 уведомления для, 120-124 ритмы и ритмические уровни, см. DirectMusic в DirectPlay, 279279 для звуковых эффектов, 217-218 для предустановок I3DL2 , 266 для уведомлений, 120-121 и вектор m_Enects, 225 метод GetGuid, 222 ряды в DirectMusic, 185-186 в трекерной музыке, 184
С сайт 3D Audio Working Group, 270 самосбрасывающиеся сообщения, 125 сегменты, см. также DirectMusic; DirectMusic Producer аудио скриптинг, 201-203 выгрузка звук а, 72 загрузка, 72 и уровень игры, 183 метод IsPlaying, 72 одновременно воспроизводимые звуки, 90-91 переключения, 183 сжатая, основанная на сэмплах музыка, 27 сжатые файлы и DirectShow, 134-I38 синусоидальные волны, 49 и резонанс, 294-298 и число Найквиста, 298 синхронизация движений губ, 345 синхронизация и отставание, 120 синхронное выполнение команд, 162-163 скорость ноты, 175 скриптинг, см. аудио скриптинг служба CDDB, 166
Предметный указатель события воспроизведения блок схема, 122 уведомления, 120-121 сообщение WM J>EVICECHANGE, 162 сохранение методы CWAVFile, 83 окружений BDL2, 268-269 списки порядка в DirectMusic, 186-188 в трекерной музыке, 184 списки сэмплов в DirectMusic, 189 в трекерной музыке, 185 стерео звук, 16 столбцы в трекерной музыке, 186 стриминг, 35-36 МРЗ, 135-136 в DirectSound, 54 флаг DSBLOCK_FROMWRITECURSOR. 48 структура DSBUFFERDESC, 45 структура DSFXI3DL2Reverb, 264-262 структура m_Props, 236 структура WAVEFORMATEX, 43 схемы, см. также DirectMusic; DirectMusic Producer в DireclMusic, 186 в трекерной музыке, 184,186 ритмы и ритмические уровни, 183-184 сэмплы • DLS1.25 микширование звука, 18-19
Т Таммилето, Сэми, 168 темп Perfomance настройки темпа, 116-118 в DirectMusic, 183 и MIDI, 113,116-119 мастер темпа, 118-119 масштабирующий множитель для, 118-119 настройки темпа с помощью дорожки, 116-118 получение, 118 топология смешивающего сервера, 273, 277,
290 топология эстафетного сервера, 273-277 трек Var Switch Point, 176 трек УК (Управляющей Кривой), 177 трекерная музыка, 20-23,167-172 MikMod для, 169-172 в сравнении с DirectMusic, 184-185 описание, 167
359 параметры сэмплов, 185 ряды,185 списки порядка, 184 списки сэмплов, 185 столбцы,186 схемы, 185 треки, 185 треки стилей в DirectMusic, 176, 182-183 треки,110 в DirectMusic, 176 в трекерной музыке, 184 трек Var Switch Point, 176 трек УК (Управляющей Кривой), 176
У уведомления GUID, 120-121 в звуковом движке, 120-124 для детектирования ритма, 120-124 удаление первичного буфера, 43 указатели байтовый указатель pbMemData, 84 интеллектуальные указатели, 71 класс CMlDlMusic, 115-116 указатели CTrackedMusic, 171 указатель CAudioScript, 206-208 функциональные указатели, 121-123 украшение «вступление», 180 украшение «заполнитель», 180 украшение «концовка», 180 украшения, см. DirectMusic управление заглушением в классе CMixeir, 106-108 установка DirectMusic Producer, 307-310 MikMod, 169-170 OpenAL, 252 библиотек Ogg Vorbis, 142-144 установка уровня кооперации, 42 Ф фактор расстояния, 234 фактор спада, 222-234 в I3DL2, 264 фильтры в DirectShow, 135 флаг DSBLOCK FROMWRITECURSOR, 48 флаг DSBPLAY^LOOPING, 51 флаг IsSecondary, 205 флаг m_Paused, 158-159 флаг MCI_NOTIFY, 163 флаг Standard Buffer, 217 флаг WAVE_FORMAT_PCM, 45
Предметный указатель
36О фляги флаг DSBLOCK._FROMWRITECURSOR, 48 флаг DSBPLAY_LOOPING, 51 флаг IsSecondary, 205 флаг m_Paused, 158-159 флаг MCI_NOTIFY, 163 флаг WAVE_FORMAT_PCM, 45 фонемы, 345 формат TMSF, 157 формат музыки MOD, 19-20 поддержка в BASS, 28 форматы времени MCI, 156-157 фреймы в МРЗ, 128 функции Waveform Audio functions, 73 функциональные указатели, 121-123 функция CoCreatelnstance, 68 функция CreateSecondaryBuffer, 45 функция DXGetErrorStringS, 45 функция GetConsoleWindowHandle, 41 функция LoadSegmentFromDisk, 114 функция LoadSegmentFromMemory, 114 функция LoadSegmentFromResource, 114 функция LoadSound для CAudioManager, 70-71 для MIDI файлов, 113-115 загрузка WAV файлов из памяти, 84 функция mmioFOURCC, 83 функция обратного вызова в CNetConnectWizard, 284-285 uDirectPlay Voice, 287 в DirectPlay, 281-282 чанк аудио формата, 79-80 чанк данных, 80 чанк формата, 79-80 чайки в RIFF файлах, 77-79 главный чанк, 78 чанк данных, 80 частота дискретизации, 224-16 в WAV файлах, 80 определение, 13 стандартные частоты дискретизации звука, 43 число Найквиста, 298 частотно-модулированный синтез, см. ЧМ синтез четырехбуквенный код FOURCC (four-character code), 76-79 число Найквиста, 298-299
Ш шина ISA, 34 шина PCI, 34 Шмидт, Брайан, 35
Э эквалайзеры, 104 экземпляр COpenALBuffer, 256 экземпляры звука, 91-93, см также класс CSoundlnstance эффект наложения, 298 эффекты, см. звуковые эффекты эхо I3DL2, настройка, 264 класс эффекта, 222-226
ю
языки программирования, см. также C/C++ поддержка в BASS, 28
Содержание Введение
10
Глава 1. Основы основ
12
Принципы и термины Компьютерного aByka Частота дискретизации (Sample Rate) и Качество выборКи (Sample Quality) Стереозвук
12 13 16
ЗВуКовые форматы, сЖатие и kogeku
16
Мшшшрование (mixing) звуКа КратКая история aByka в видеоиграх MIDI DLS1 DLS2 CD 3Byk (Redbook Audio) И Когда Же это все уйдет на nokou? Flokynka или создание зВуКового двшККа BASS MikMod FMOD
18 19 22 24 25 25 27 28 28 29 29
ModPlug
30
Miles Sound System ЗаКлючение
30 30
Часть I. Основы звуКового gBiukka
31
Глава 2. Введение 0 DirectAudio
32
Основы DirectX Audio DirectMusic, DirectSound или DirectX Audio? Первичный и вторичный буферы Стриминг Kak настроить ваш Компилятор для DirectX Audio Глобальная или лоКальнан Конфигурация проеКта Построение примеров в этой Книге Создание ваших собственных проеКтов ToneGenerator: простой пример DirectX Audio Что makoe ToneGenerator? Инициализация DirectSound Создание вторичного буфера
,
32 32 33 35 36 37 37 38 40 40 41 44
362
Содержание Создание шума Проигрывание буфера Освобождение памяти Заключение
Глава 3. Воспроизведение волнового звуКа
46 50 51 52
53
ЗВуКовые эффеКты с DirectMusic 53 Loader 54 Segment 56 Performance 56 Другие объеКты 57 O6pa5omka ouiuSok В зВуКоВом дВиЖКе 57 Стоит ли использовать o5pa6omky исключительных ситуаций?.... 58 Правила, чтобы вьиКить, используя обработку исключительных ситуаций 58 Основы зВу|£ового двшККа 63 Исключительные ситуации в звуКоВом дВшККе 63 Построение CAudioManager 66 Интеллектуальные указатели 71 CSound 72 Воспроизведение звуКа 72 ЗагрузКа зВуКоВ 72 Определение, воспроизводится ли звуК 73 ВыгрузКа звуКа 73 Альтернативные способы воспроизведения звуКа 73 DirectSound 73 Windows API Waveform Audio 74 Windows MCI 75 Заключение 75
Глава 4. ЗагрузКа WAV файлов Строение WAV файла RIFF Chunks Главный чанК ЧанК формата ЧанК данных
Воплощение всего этого в Жизнь Другие способы загрузить WAV файлы
76 76 76 77 78 79 80
81 84
ЗагрузКа из памяти
84
ЗагрузКа из CWAVFile ЗагрузКа из ресурсов
85 86
Содержание Определение gupekmopuu noucka Заключение
363 89 89
ГлаВа 5. Контролируемая неКонтролируемость
90
Одновременное Воспроизведение aBykoB Первичные и Вторичные сегменты DirectMusic Прощай, PlaySegment, здравствуй, PlaySegmentEx
90 90 91
Экземпляры aByka
Создание CSoundlnstance KoppekmupoBka методов CSound Воспроизведение sBykoB в ци)сле (Looping) Управление громКостью с помощью DirectX Audio SBykoBbie gopoAku FpOMkocmb отдельного сегмента Глобальная громКость Подытоживая способы управления громкостью с помощью DirectX Управление гродЛостью с помощью Mukuiupyiou^eBO API Apxumekmypa микширующего API , Kak изменять параметры Mukuiepa Построение CMixer Заключение
Глава 6. ВоспроизВедение MIDI Kak работает MIDI Борьба ЬлассоВ Saepyaka и воспроизведение MIDI файлов Улучшение зВуКоВого gBuAka Получение уКазателя CMIDIMusic
Изменение темпа Сравнение Hacmpoek темпа с помощью дороЖКи или объе!ста Performance
Получение темпа Масштабирующий мноЯштель темпа ДетеКтироВание ритма Уведомление DirectMusic BcmaBka извещений в зВуКоВой gBu^kok Организация многопоточной o6pa6omku gemekmupoBaHUH сообщений MIDI без DirectMusic Заключение .,
91
92 94 95 96 97 100 102 103 103 104 105 106 108
109 109 111 113 113 115
116 116
118 118 119 120 121 124 126 126
364
Содержание
Глава 7. ВоспроизВедение МРЗ и WMA
127
ВоспроизВедение MPEG Layer 3 (МРЗ) Kak работает МРЗ файловый формат МРЗ МРЗ в DlirectMusic, koaga WAV -- это не WAV ВоспроизВедение МРЗ с помощью DirectShow ВоспроизВедение Windows Media Audio (WMA) Заключение
127 127 128 132 134 138 139
Глава 8. Воспроизведение Одд Vorbis Cmpykmypa Ogg Vorbis
Библиотеки Одд и Vorbis
140 141
,.
141
Библиотека Vorbisenc
142
Библиотека Vorbisfile
142
Получение помощи От нуля k Vorbis Использование Vorbis использование Vorbisfile API Добавление Одд Vorbis В зВуКовой gBiukok Заключение
142 143 144 145 146 148
Глава 9. ВоспроизВедение аудио koMnakm-guckoB
149
Использование MCI для аудио komnakm-guckoB Нумерация, отКрытие и заКрытие приборов форматы Времени MCI
150 153 156
Воспроизведение треКа Остановка
157 157
Приостановка и Возобновление работы Определение позиции Воспроизведения
158 159
Выдвигание КомпаКт-дисКа
ДетеКширование смены КомпаКт-дисКа Выяснение содер&имого КомпаКт-дисКа Синхронное или асинхронное Выполнение Команд Управление громКостью аудио КомпаКт-дисКа
Образец программы CD Player Заключение
Глава 1О. ВоспроизВедение mpekepuou музьйЧи Введение в шреКерную музыКу
Использование MikMod в mpekepuou музыКе
Установка MikMod Помощь в MikMod
160
,
160 162 162 163
163 166
167 168 169
169 . 170
Содержание использование MikMod в зВуКоВом дВшККе Заключение
365 170 172
Часть II. Продвинутые возможности sBykoBoeo g&iukka
173
Глава 11. Динамическая музьнса
174
Элементы динамически муэыКи КоллеКции DLS Ноты Tpeku Партии Каналы Воспроизведения (PChannels) Схемы Вариации Мотивы Украшения (Embellishments) Ансамбли Стили Ритмы и ритмичесКие уровни DirectMusic В понятиях mpekepuou музыКи Ряды -- это ноты DirectMusic Столбцы — это партии DirectMusic Схемы - все те ike схемы Cnucku порядКа - это mpeku ритмичесКих уровней и стилей Cnucku ВыбороК -- это DLS Коллегии Параметры ВыбороК -- это ансамбли Подводим итоги: DirectMusic дает больше, чем треКерная музыКа Программирование динамически музыКи Класс CDinamicMusic Hacmpouka Воспроизведение динамической музыКи Изменение РитмичесКого уровня Воспроизведение мотивов Заключение
Глава 12. Ckpunmbi и эффеВсты Аудио сКриптинг Зачем использовать аудио сКриптинг? Основы аудио сКриптинга Tonka зрения зВуКобого дизайнера To4ka зрения разработчика
174 175 175 176 176 177 177 178 179 179 180 182 183 184 186 186 187 187 189 190 190 190 191 191 193 194 195 196
197 197 197 199 200 206
366
Содер&ание Аудио clkpunmuHa: подытожим ЭффеКты Добавление эффеКтоВ, используя DirectMusic Producer Добавление эффеКтов В теКущем времени
215 215 216 218
ЭффеКты: подытожим
ЗаКлючение
227
,
227
Глава 13. 3D sByk, используя DirectX Audio
228
Основы SDaByka IlpueMHuku и буферы ЭффеКт Доплера Свойства буферов и приемниКов Изменения немедленные и отложенные Программирование 3D зВуКа C3DsoundLJstener Изменения в CAudioManager C3DsoundEffect Пример программы про дальний kocMoc ЗаКлючение
228 229 230 230 236 236 236 238 239 241 245
Глава 14. 3D зву!с, используя OpenAL Различия meikgy OpenAL и 3D зВуКот В DirectX Audio ИсточыиКи, буферы и npueMmiku Больше не СОМ Свойства буфера Свойства источниКа Свойства npueMHuka Добавление OpenAL в sBykoBou дВиЖоК Инсталляция OpenAL Cmpykmypa библиотеки OpenAL Новые (классы OpenAL AudioEngine Инициализация и деинициализация OpenAL HpoBepka ошибоК в OpenAL Создание и уничтожение буферов Создание и уничтожение источников Воспроизведение источников Пример про дальний kocMoc в OpenAL Заключение
Глава 15. Продвинутые Возможности 3D звуКа Моделирование оКруЖаюш,ей среды Моделирование оКруАенин с использованием I3DL2
246 ,
247 247 248 249 249 251 251 252 252 253 254 255 255 257 258 258 258
260 260 261
368
Со&ер&ание Hacmpouka ансамбля
,
Создание Вашей первой схемы Завершение Ввода схем Объединение схем в сегмент ЗациКливание сегмента Сохранение в Runtime формате ДинамичесКая музыКа Добавление Вариаций Создание ykpauuemiu Добавление мотивов Akkopgbi Композиции и mpeku akkopgoB Создание альтернативных схем Сохранение в Runtime формате (опять) За1слючение
.••• 318
, ,
Приложение Б. Советы по созданию игрового аудио Mysbika .' МузыКантом моАно и не быть Использование популярных мелодий В Вашей игре Найм местного таланта , SBykoBbie эффеКты Kak определить, koetja использовать звуКовой эффеКт КоллеКции зВуКовых эффектов Создание ваших собственных звуКоВых эффектов '. СКачивание звуКоВых эффеКтоБ из Интернета Речь ..., Найм аКтероВ и аКтрис для озучиВания Проблема локализации
Синхронизация двшКений губ Заключение Предметный указатель
,
л
320 325 31а_ 326 327 327 327 329 331 332 337 338 338
339 339 339 340 340 341 341 342 343 344 344 344 345
345 346 347
367
Создание pegakmopa оКруАения ......................................................... 266 .' 13DL2: подытожим .............................................................................. 270 ЕАХ ............................................................................................................ 270 KogupoBka Dolby Digital ............................................................................ 271 Заключение ............................................................................................... 271
Глава 16. DirectPlay Voice ........................................................... 272 DirectPlay Voice API ................................................................................... 272 Основы ................................................................................................. 272 Топология сети DirectPlay Voice ......................................................... 273 Для начала, kpamkuu kypc DirectPlay .................................................. 278 Использование CNetConnectWlzard ..................................................... 283 Программирование DirectPlay Voice .................................................... 286 Голоса КаК буферы 3D звуКа ............................................................... 291 Заключение ............................................................................................... 291
Глава 17. Визуализация звуКа ................................................... 292 ДисКретное преобразование фурье ........................................................ 292 Резонанс ............................................................................................... 293 КаК вы слышите .................................................................................. 294 Математическое задание резонанса .................................................. 294 Одним синусом нам не обойтись ........................................................ 296 Число НайКвиста ................................................................................. 298 ДисКретное преобразование Фурье ................................................... 299 Анимация анализатора спеКтра ......................................................... 299 Пример программы ........................ , ................................................. , ........ 299 ЗагрузКа файла ......................................... . ....................................... ... 300 CDiscreteFourierTransform .................................................................... 300 Визуализация графиКа ......................................................................... 303 Быстрые преобразования Фурье (Fast Fourier Transform) 304 Заключение ............................................................................................... 305
Заключение ................................................................................... 306 Приложение А. Создание динамически музы!ш ....... .............. 307 Установка DtrectMusic Producer ....................... , ....................................... 307 НастройКа вашей MIDI Клавиатуры .................................................. 307 Если ваша MIDI Клавиатура не работает ......................................... 309 Создание нормальной муэыКи .................................................................. 310 Создание объеКтов ............................. . ............................................... 310 ЗначКи, расширения и форматы файлов ............................................. 311 Создание DLS КоллеКции ...................................................................... 311
ПРИГЛАШАЕМ АВТОРОВ КНИГ ПО КОМПЬЮТЕРНОЙ ТЕМАТИКЕ, ПЕРЕВОДЧИКОВ И НАУЧНЫХ РЕДАКТОРОВ ВОЗМОЖНО, ВАС ЗАИНТЕРЕСУЮТ ДРУГИЕ НАШИ ИЗДАНИЯ ЦИФРОВАЯ МОДЕЛЬ ЧЕЛОВЕКА I
К. Бриллиант
С, Перес
Цифровая модель человека
Jamagic: программирование игр и симуляторов
ISBN 5-9579-0006-0
ISBN 5-9579-0008-7
СЕКРЕТЫ ЕИЗНЕСА
Под ред. Ф. Д. Ларами
К. Е. Крукс II
Компьютерные игры: секреты бизнеса
gmax: настольная книга ISBN 5-9579-0026-5
ISBN 5-9579-0011-7
Дж. Адаме
Дж. Макар Секреты разработки игр в Macromedia Flash MX
DfRECTX
DirectX: продвинутая анимация
ISBN 5-93378-089-8
ISBN 5-9579-0025-7
у приобретения книг обращайтесь'в издательство по тел.: 333-82-117 с 11™ до 17ио, Наш адрес: ул.;Профсоюзная, д. 84/32, под7б, эТ. 11. З В О Н И Т Е !
ISBN 5 9579-0024-?
КУДИЦ-ОБРАЗ м: Тел./факс: (095) 333-82-11,333-65-67 С* E-mail: [email protected]; http://books.kudits.ru
5
*Щ2* '' 121354, Москва, а/я 18, "КУДИЦ-ОБРАЗ"