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

Звуковое ядро

Что за игра без музыки и звуков? Звуковое ядро — это решение для быстрого и легкого добавления в вашу игру звуков и музыки. В звуковое ядро входят шесть классов компонентов (см. таблицу 6.4).


Таблица 6.4. Классы звукового ядра



Класс Описание

cSound Содержит объекты DirectSound и DirectMusic и управляет звуковыми потоками.
cSoundData Класс хранит волновые данные, используемые для воспроизведения с cSoundChannel.
cSoundChannel Класс, используемый для воспроизведения отдельного звука. Вы можете использовать одновременно до 32 таких классов (это значит, что вы одновременно можете воспроизводить 32 звука)!
cMusicChannel Класс используется для воспроизведения одного файла песни в формате MIDI или родном формате DirectMusic. Одновременно можно использовать только один такой класс.
cDLS Объект класса загружаемых звуков (DownLoadable Sound). Этот класс позволяет вам загрузить свои инструменты в объект cMusicChannel.
cMP3 Объект класса воспроизведения MP3-музыки. Класс позволяет воспроизводить песни в формате MP3 и определять состояние процесса воспроизведения.


Давайте двинемся вперед и начнем с верха списка, взглянув сначала на объект cSound.

Управление DirectX Audio с cSound

Объект cSound управляет объектами DirectSound и DirectMusic и регулирует общую громкость воспроизведения. Он также управляет потоком уведомлений для потокового воспроизведения звуков. Взгляните на объявление класса:

class cSound
{
  protected:
    HWND m_hWnd;   // Дескриптор родительского окна
    long m_Volume; // Общая громкость

    // События для каждого звукового канала,
    // дополнительное событие используется для
    // выключения потока
    HANDLE m_Events[33];
    cSoundChannel *m_EventChannel[32];

    // Данные потока потокового воспроизведения
    HANDLE m_hThread;      // Дескриптор потока
    DWORD m_ThreadID;      // ID потока
    BOOL m_ThreadActive;   // Активность потока
    static DWORD HandleNotifications(LPVOID lpvoid);

    // COM-объекты DirectSound
    IDirectSound8 *m_pDS;
    IDirectSoundBuffer *m_pDSBPrimary;

    // Уровень кооперации, частота,
    // количество каналов и размер выборки
    long  m_CooperativeLevel;
    long  m_Frequency;
    short m_Channels;
    short m_BitsPerSample;

    // COM-объекты DirectMusic
    IDirectMusicPerformance8 *m_pDMPerformance;
    IDirectMusicLoader8 *m_pDMLoader;

  public:
    cSound();  // Конструктор
    ~cSound(); // Деструктор

    // Назначение и освобождение событий, используемых
    // при потоковом воспроизведении
    BOOL AssignEvent(cSoundChannel *Channel,
                     short *EventNum, HANDLE *EventHandle);
    BOOL ReleaseEvent(cSoundChannel *Channel, short *EventNum);

    // Функции для получения COM-интерфейсов
    IDirectSound8 *GetDirectSoundCOM();
    IDirectSoundBuffer *GetPrimaryBufferCOM();
    IDirectMusicPerformance8 *GetPerformanceCOM();
    IDirectMusicLoader8 *GetLoaderCOM();

    // Функции инициализации и завершения работы
    BOOL Init(HWND hWnd, long Frequency = 22050,
              short Channels = 1, short BitsPerSample = 16,
              long CooperativeLevel = DSSCL_PRIORITY);
    BOOL Shutdown();

    // Получение и установка общего уровня громкости
    long GetVolume();
    BOOL SetVolume(long Percent);

    // Восстановление системы в исходное состояние
    BOOL Restore();
};

Основные функции класса cSound, с которыми вы будете иметь дело, — это Init, Shutdown и SetVolume. Я уже упоминал, что каждый объект класса перед использованием нуждается в инициализации, и класс cSound в этом отношении не исключение.

Для использования Init вы должны передать ей дескриптор родительского окна, а также необязательные параметры микширования (по умолчанию система устанавливает частоту дискретизации 22 050 Гц, монофонический звук, 16-разрядную выборку с уровнем кооперации DSSCL_PRIORITY). За информацией о резличных форматах воспроизведения и уровняк кооперации, которые вы можете использовать, обращайтесь к главе 4, «Воспроизведение звуков с DirectX Audio и DirectShow». За вызовом Init всегда должен следовать вызов Shutdown, делаемый когда вы завершаете работу со звуковой системой.

Для изменения уровня громкости вызовите cSound::SetVolume с параметром Percent, которому присвоено значение из диапазона от 0 (тишина) до 100 (полная громкость).

Использование волновых данных и cSoundData

Вы используете объект класса cSoundData для хранения отдельного звука (волновых данных) и его описания. В объявление класса помещены данные о частоте дискретизации звука, размере выборки, количестве каналов, размере и источнике:

class cSoundData
{
    friend class cSoundChannel; // Пусть у звукового канала
                                // будет доступ к моему классу

  protected:
    long m_Frequency;      // Частота дискретизации.
    short m_Channels;      // Количество каналов.
    short m_BitsPerSample; // Размер выборки.
    FILE *m_fp;            // Указатель на файл-источник звука.
    char *m_Ptr;           // Указатель на источник звука в памяти.
    char *m_Buf;           // Буфер исходного звука.
    long m_Size;           // Размер звука (в байтах).
    long m_Left;           // Оставшиеся данные потока.
    long m_StartPos;       // Начальная позиция звука в источнике.
    long m_Pos;            // Текущая позиция звука в источнике.

  public:
    cSoundData();   // Конструктор
    ~cSoundData();  // Деструктор

    char *GetPtr(); // Получение указателя на
                    // звуковой буфер в памяти.
    long GetSize(); // Получение размера звука.

    BOOL Create();          // Создание звука, используя
                            // загруженный размер.
    BOOL Create(long Size); // Создание звука с указанием размера.
    BOOL Free();            // Освобождение звукового буфера

    // Установка формата воспроизведения загруженного звука.
    BOOL SetFormat(long Frequency, short Channels, short BitsPerSample);

    // Установка файла или объекта в памяти в качестве источника
    // с указанием смещения начальной позиции и длины звука
    BOOL SetSource(FILE *fp, long Pos = -1, long Size = -1);
    BOOL SetSource(void *Ptr, long Pos = -1, long Size = -1);

    // Загрузка WAV-файла в память и настройка воспроизведения
    BOOL LoadWAV(char *Filename, FILE *fp = NULL);

    // Загрузка заголовка WAV-файла и конфигурирование формата.
    BOOL LoadWAVHeader(char *Filename, FILE *fp = NULL);

    // Копирование внутренних данных в другой объект cSoundData
    BOOL Copy(cSoundData *Source);
};

Рассматриваемый далее объект cSoundChanel использует класс cSoundData для воспроизведения звука. Однако, перед тем, как вы сможете воспроизводить звук, необходимо использовать объект класса cSoundData для сохранения формата воспроизведения и источника звуковых данных. Звуки могут поступать из двух источников: из файла или из буфера в памяти. Кроме того, для звуков, которые слишком велики, чтобы поместиться в память, можно сконфигурировать потоковое воспроизведение источника.

Самый быстрый способ загрузить отдельный WAV-файл — использование функции LoadWAV. У нее два параметра: имя загружаемого файла и указатель на файл-источник. Использовать надо только один параметр, а для другого указывать NULL. Указатель на файл-источник позволяет упаковать несколько WAV-файлов в один файл и при этом иметь возможность, загружать их по отдельности.

Чтобы загрузить отдельный WAV-файл, используйте следующий код:

cSoundData Data;

// Загрузка звука из файла
Data.LoadWAV("sound.wav");

// Загрузка звука через указатель на файл
FILE *fp = fopen("sound.wav", "rb");
Data.LoadWAV(NULL, fp);
fclose(fp);

Помимо загрузки отдельного WAV-файла у вас есть возможность установить источник ваших звуковых данных. Это полезно, если ваши звуки слишком велики для звукового буфера (больше 64 Кбайт). Вы можете использовать потоковое воспроизведение звука из файла или из буфера в памяти. Это цель функции SoundData::SetSource, у которой есть две версии:

BOOL cSoundData::SetSource(FILE *fp, long Pos = -1,
                           long Size = -1);
BOOL cSoundData::SetSource(void *Ptr, long Pos = -1,
                           long Size = -1);

Как видите, можно выбирать, передать ли указатель на файл-источник или указатель на буфер в памяти. Параметр Pos передает классу местоположение начала звуковых данных (смещение). Параметр Size задает общее количество байт в потоке (размер звука).

ВНИМАНИЕ!
Когда вы используете буфер в памяти, вы должны всегда освобождать его вызовом cSoundData::Free. Также помните, что если вы выполняете потоковое воспроизведение звука из файла, ответственность за закрытие файла лежит на вас.

Обратите внимание, что по умолчанию аргументам Pos и Size присваивается значение –1, что позволяет классу самому установить эти значения. Чтобы это произошло, вы должны сперва установить формат воспроизведения с помощью функции SetFormat, которая является самодокументируемой. Затем вы должны проанализировать заголовок волнового файла, воспользовавшись функцией LoadWAVHeader, в отношении параметров аналогичной LoadWAV.

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

В качестве примера предположим, что вы хотите воспроизвести большой WAV-файл с именем BigSound.wav. Для инициализации класса cSoundData можно использовать следующий код:

cSoundData Data;

FILE *fp = fopen("BigSound.wav", "rb");

Data.LoadWAVHeader(NULL, fp); // Получаем параметры воспроизведения
Data.SetSource(fp);           // Устанавливаем файл источника

// Воспроизводим звук и по завершении закрываем файл
fclose(fp);

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

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

// Фиксированные размеры буферов звукового канала
const long g_SoundBufferSize  = 65536;
const long g_SoundBufferChunk = g_SoundBufferSize / 4;

Перед тем, как углубляться в код, я хочу поговорить о двух глобальных константах. Первая, g_SoundBufferSize, представляет количество байтов, выделяемых для каждого буфера DirectSound, используемого при воспроизведении звука. Я использую 65 536 байт, чего достаточно для хранения нескольких секунд воспроизводимых данных даже в форматах с высоким качеством.

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

Не следует менять эти значения, только если вы не хотите сэкономить память; в этом случае измените значение g_SoundBufferSize на меньшее. Есть несколько преимуществ наличия звуковых буферов большего размера — чем больше размер, тем реже ядро должно подгружать в поток новые данные. Конечно, это означает больший расход памяти, но что такое для современной системы с сотнями мегабайт памяти, пара мегабайт, потраченные для звуковых данных?

Давайте вернемся к объявлению класса cSoundChannel:

class cSoundChannel
{
    friend class cSound; // Предоставить доступ классу cSound

  protected:
    cSound *m_Sound;                  // Родительский класс cSound
    IDirectSoundBuffer8 *m_pDSBuffer; // Звуковой буфер DS
    IDirectSoundNotify8 *m_pDSNotify; // Объект уведомления

    short       m_Event;   // Клоичество событий для уведомлений
    long        m_Volume;  // Текущая громкость 0-100%
    signed long m_Pan;     // Позиционирование от -100 до +100
    BOOL        m_Playing; // Флаг воспроизведения канала
    long        m_Loop;    // Количество повторов воспроизведения

    long m_Frequency;      // Формат воспроизведения
    short m_BitsPerSample; // для инициализированного
    short m_Channels;      // канала.
    cSoundData m_Desc;     // Описание источника звука

    // Переменные для потокового воспроизведения
    short m_LoadSection;  // Следующий загружаемый фрагмент
    short m_StopSection;  // На каком фрагменте остановиться
    short m_NextNotify;   // Какой фрагмент следующий

    BOOL BufferData();    // Входной буфер потоковых данных
    BOOL Update();        // Обновление воспроизведения канала

  public:
    cSoundChannel();  // Конструктор
    ~cSoundChannel(); // Деструктор

    // Функции для получения COM-объектов
    IDirectSoundBuffer8 *GetSoundBufferCOM();
    IDirectSoundNotify8 *GetNotifyCOM();

    // Создание и освобождение звукового канала
    BOOL Create(cSound *Sound, long Frequency = 22050,
                short Channels = 1, short BitsPerSample = 16);
    BOOL Create(cSound *Sound, cSoundData *SoundDesc);
    BOOL Free();

    // Воспроизведение и остановка канала
    BOOL Play(cSoundData *Desc, long VolumePercent = 100,
              long Loop = 1);
    BOOL Stop();

    // Получение и установка уровня громкости (0-100%)
    long GetVolume();
    BOOL SetVolume(long Percent);

    // Получение и установка позиционирования
    // (от -100 слева до +100 справа)
    signed long GetPan();
    BOOL SetPan(signed long Level);

    // Получение и установка частоты воспроизведения
    long GetFrequency();
    BOOL SetFrequency(long Level);

    BOOL IsPlaying(); // Возвращает TRUE если звук воспроизводится
};

На фоне простого класса cSoundData размер класса cSoundChannel впечатляет. Вы можете создать 32 экземпляра этого класса, а это значит, что вы одновременно можете воспроизводить 32 канала (звуковое ядро не позволяет иметь одновременно более 32 экземпляров класса — инициализация класса после первых 32 будет вызывать ошибку). Каждый звуковой канал инициализируется вызовом cSoundChannel::Create.

При вызове Create вы предоставляете ранее инициализированный класс cSound и формат воспроизведения. Чтобы жизнь была легче, вы можете создать звуковой канал, используя формат воспроизведения, хранящийся в классе cSoundData. Когда вы завершите работу с классом cSoundChannel, освободите его ресурсы, вызвав cSoundChannel::Free.

Скорее всего, вы будете работать с cSoundChannel для воспроизведения и остановки звуков и, возможно, будете менять их громкость. Для воспроизведения звука передайте cSoundChannel объект cSoundData, содержащий требуемый звук, вместе с уровнем громкости и количеством повторов воспроизведения. Чтобы звук воспроизводился в бесконечном цикле, укажите в параметре Loop значение 0.

Остальные функции самодокументируемы. Для уровня громкости и позиционирования используются процентные значения от –100% (тишина или крайняя левая позиция) до +100% (полная громкость или крайняя правая позиция). Вызов cSoundChannel::IsPlaying возвращает TRUE, если звук воспроизводится, и FALSE — если нет.

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

// Глобальные объявления
cSound        g_Sound;
cSoundData    g_Data[2];
cSoundChannel g_Channel[2];

// Инициализация звуковой системы
// Подразумеваем, что hWnd уже содержит
// инициализированный дескриптор окна
g_Sound.Init(hWnd);

// Загрузка звуков
g_Data[0].LoadWAV("SmallSound.wav");
FILE *fp = fopen("BigSound.wav", "rb");
g_Data[1].LoadWAVHeader(NULL, fp);
g_Data[1].SetSource(fp);

// Создание звуковых каналов
g_Channels[0].Create(&g_Sound, &g_Data[0]);
g_Channels[1].Create(&g_Sound, &g_Data[1]);

// Начало воспроизведения
g_Channels[0].Play(&g_Data[0]);         // Один раз воспроизводим
                                        // первый звук
g_Channels[1].Play(&g_Data[1], 100, 0); // Воспроизводим второй звук
                                        // в бесконечном цикле

// Когда все готово, останавливаем все и завершаем работу
g_Channels[0].Stop();
g_Channels[0].Free();
g_Channels[1].Stop();
g_Channels[1].Free();
g_Data[0].Free();
g_Data[1].Free();
fclose(fp);
g_Sound.Shutdown();

Слушаем музыку с cMusicChannel

Повторю еще раз, что за удовольствие от игры без музыки? Пришло время нанести удар с помощью класса cMusicChannel, который воспроизводит MIDI-файлы и песни в родном формате DirectMusic (*.SGT):

class cMusicChannel
{
    friend class cSound; // Разрешаем классу cSound доступ к данным

  protected:
    cSound *m_Sound;                    // Родительский класс cSound
    IDirectMusicSegment8 *m_pDMSegment; // Объект сегмента DM
    long m_Volume;                      // Уровень громкости 0-100%

  public:
    cMusicChannel();  // Конструктор
    ~cMusicChannel(); // Деструктор

    IDirectMusicSegment8 *GetSegmentCOM(); // Получить COM сегмента

    BOOL Create(cSound *Sound); // Инициализация класса
    BOOL Load(char *Filename);  // Загрузка музыкального файла
    BOOL Free();                // Освобождение музыкального файла
    BOOL SetDLS(cDLS *DLS);     // Установка нового DLS

    // Воспроизведение и остановка музыки
    BOOL Play(long VolumePercent = 100, long Loop = 1);
    BOOL Stop();

    // Получение и установка уровня громкости (0-100%)
    long GetVolume();
    BOOL SetVolume(long Percent = 100);

    BOOL SetTempo(long Percent = 100); // Установка темпа

    BOOL IsPlaying(); // TRUE если песня воспроизводится,
                      // FALSE - если нет
};

Пусть размер класса cMusicChannel не обманывает вас — он выполняет всю необходимую работу. Отличие cMusicChannel от других классов заключается в том, что его нужно инициализировать только один раз вызовом cMusicChannel::Create.

Функция cMusicChannel::Free выгружает песню из памяти, освобождая место для загрузки следующей песни. Загружая песню вы указываете имя файла, который должен быть MIDI-файлом или песней в родном формате DirectMusic (.SGT). У MIDI-файла должно быть расширение .MID, иначе функции cMusicChannel не смогут сконфигурировать DirectMusic для правильного воспроизведения. Если вы используете только MIDI-файлы, то можете изменить функции, чтобы всегда выполнялось принудительное конфигурирование объекта сегмента песни DirectMusic для воспроизведения MIDI (как было показано в главе 4).

Когда песня загружена, вы можете начать воспроизведение, используя функцию cMusicChannel::Play, параметры Volume и Loop которой работают точно так же, как одноименные параметры cSoundChannel::Play. Оставшиеся функции просты для понимания — за исключением cMusicChannel::SetDLS, которая меняет инструменты, использующиеся для воспроизведения.

Я доберусь до материала о DLS в следующем разделе («Смешивание инструментов с cDLS»), а сейчас взгляните на класс cMusicChannel в действии:

// Глобальные объявления
cSound        g_Sound;
cMusicChannel g_Music;

// Инициализация звуковой истемы
// Подразумеваем, что hWnd уже содержит
// инициализированный дескриптор окна
g_Sound.Init(hWnd);

// Инициализация музыкального канала
g_Music.Create(&g_Sound);

// Загрузка и воспроизведение песни (бесконечный цикл)
g_Music.Load("song.mid");
g_Music.Play(100,0);

// Завершив работу останавливаем и выгружаем песню
g_Music.Stop(;
g_Music.Free();

// Выключение звуковой системы
g_Sound.Shutdown();

Смешивание инструментов с cDLS

Наконец-то мы добрались до конца информации об использовании звукового ядра. Чтобы улучшить возможности воспроизведения музыки класса cMusicChannel, показанного в предыдущем разделе, вы можете использовать представленный здесь класс cDLS (обратитесь к главе 4 за описанием преимуществ, которые дает использование загружаемых звуков, называемых DLS):

// Макросы, помогающие работать с модификаторами
#define PATCH(m,l,p) ((m << 16) | (l << 8) | p)
#define PATCHMSB(x) ((x >> 16) & 255)
#define PATCHLSB(x) ((x >> 8) & 255)
#define PATCHNUM(x) (x & 255)

class cDLS
{
  protected:
    cSound *m_Sound; // Родительский объект cSound

    // Объект DLS-коллекции DM
    IDirectMusicCollection *m_pDMCollection;

  public:
    cDLS();  // Конструктор
    ~cDLS(); // Деструктор

    // Возвращает COM коллекции
    IDirectMusicCollection8 *GetCollectionCOM();

    BOOL Create(cSound *Sound); // Инициализация класса

    // Загрузка и освобождение DLS
    // (NULL = загрузка набора по умолчанию)
    BOOL Load(char *Filename = NULL);
    BOOL Free();

    long GetNumPatches();      // Возвращает количество
                               // модификаторов в наборе
    long GetPatch(long Index); // Возвращает модификатор
                               // с указанным номером
    BOOL Exists(long Patch);   // Проверяет, существует ли
                               // модификатор с заданным номером
};

Как видите, единственная цель класса cDLS — хранить отдельный набор DLS. Как и в случае с cMusicChannel, вы вызываете cDLS::Create только один раз, поскольку cDLS::Free освобождает только загруженный набор. Обратите внимание, что для параметра Filename в cDLS::Load по умолчанию устанавливается значение NULL, указывающее, что надо загрузить используемый по умолчанию набор DLS. Загрузка используемого по умолчанию набора DLS очень удобна при восстановлении оригинального звучания инструментов.

Последние три функции показывают инструменты, которые содержит набор DLS. Это также назначение макросов PATCH, находящихся в начале объявления класса. Чтобы увидеть, сколько инструментов содержится в классе, вызовите cDLS::GetNumPatches.

Теперь вы можете в цикле перебрать все инструменты для получения их номеров модификаторов с помощью функции cDLS::GetPatch, или, воспользовавшись cDLS::Exist, проверить существует ли указанный модификатор в наборе. Функция возвращает TRUE, если модификатор существует, и FALSE, если нет.

При использовании cDLS с cMusicChannel вы загружаете требуемый DLS и вызываете cMusicChannel::SetDLS для использования этого набора инструментов:

// Подразумевается наличие ранее загруженного объекта c_Music
// и ранее инициализированного объекта g_Sound.
cDLS g_DLS;

g_DLS.Create(&g_Sound);
g_DLS.Load("custom.dls");

g_Music.SetDLS(&g_DLS);

// Завершив работу с DLS, освободите его
g_DLS.Free();

Воспроизведение MP3 с cMP3

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

class cMP3
{
  private:
    IGraphBuilder *m_pDSGraphBuilder;
    IMediaControl *m_pDSMediaControl;
    IMediaEvent   *m_pDSMediaEvent;

  public:
    cMP3();
    ~cMP3();

    // Инициализация и выключение DirectShow
    BOOL Init();
    BOOL Shutdown();

    // Визуализация файла для использования
    BOOL Render(char *Filename);

    // Управление воспроизведением
    BOOL Play();
    BOOL Stop();
    BOOL Pause();

    // Состояние воспроизведения
    // (TRUE = идет воспроизведение)
    BOOL Playing();
};

Чтобы применить предоставляемую данным классом возможность воспроизведения MP3, воспользуйтесь следующим фрагментом кода:

cMP3 MP3;

// Инициализация объекта класса
MP3.Init();

// Визуализация файла и начало воспроизведения
if(MP3.Render("song.mp3") == TRUE)
    MP3.Play();

// Ждем завершения песни
while(MP3.Playing() == TRUE);

// Выключаем все
MP3.Shutdown();

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

Сайт управляется системой uCoz