По туториалам сделал систему звука. Файлы загружаются, буфера заполняются (:) ), сурсы создаются и им присваиваются эти саме буферы. Теперь вопрос: Как организовать работу со звуком? Вопрос ламерский, знаю, сорри, но критически нету времени искать в нете примеры, сорри ещё раз.
Я предпологаю что делать нада так: Загружаем столько, скока нам нада звуков (при этом создаются столько буферов сколько звуков загрузили). Далее создаём определённое количество сурсов. Дальше есть два варьянта до которых я допёр, и которые мне не совсем понятны:
1). Каждому сурсу присваиваем свой буфер(таким образом, номер сурса проигрывает звук, с тем-же номером) (частично бредовая на мой взгляд идея, так как сурсы вроде небесконечны)
2). При необходимости проиграть звук, система ищет свободный (неиграющий) сурс, присваивает ему нужный буфер, и проигрывает его. Тут возникает вопрос: что если нету свободных сурсов?
Плиз, если у кого-то есть чем помочь, буду признателен. Пасиба!
program OpenALDelphiDemo;
{$APPTYPE CONSOLE}
uses
OpenAL; //подключённый модуль с http://www.noeska.com/doal/
var
MainDevice: TALCdevice; //переменная которая отвечает за устройство воспроизведения
MainContext: TALCcontext; //переменная отвечающая за контекст
FirstBuffer: TALuint; //через эту переменную будем получать доступ к буферу
FirstSource: TALuint; //а через эту – к источнику
// если нужно несколько буферов/источников, лучше всего
//сделать массивы буферов/источников
//Процедуры, которые мы будем использовать
// для установки позиции источников (и т.п.)
//хотят в качестве аргументов, массивы из трёх элементов типа TALfloat
//поэтому определяем такие переменные
sourcepos: array [0..2] of TALfloat= ( 0.0, 0.0, 0.0 ); //позиция источника
sourcevel: array [0..2] of TALfloat= ( 0.0, 0.0, 0.0 ); //движение источника
// 5 переменных ниже нужны будут нам для загрузки звука в буфер
format: TALenum;
data: TALvoid;
size: TALsizei;
freq: TALsizei;
loop: TALint;
//та же история что и с источником, X,Y,Z в виде массива…
listenerpos: array [0..2] of TALfloat= ( 0.0, 0.0, 0.0); //позиция
listenervel: array [0..2] of TALfloat= ( 0.0, 0.0, 0.0); //движение
listenerori: array [0..5] of TALfloat= ( 0.0, 0.0, -1.0, 0.0, 1.0, 0.0); //ориентация
begin
InitOpenAL; //инициализируем OpenAL
MainDevice := alcOpenDevice(nil); //получаем доступ к устройству, nil – означает что мы
//берём первое удобное устройство в системе
MainContext := alcCreateContext(MainDevice,nil);
//получаем контекст для нашего
//устройства, второй параметр - ссылка
//атрибуты устройства, пока что не
//трогаем, ставим nil
alcMakeContextCurrent(MainContext); //делаем контекст текущим
alGenBuffers(1, @FirstBuffer);
//просим выделить нам один буфер и вернуть на него
//указатель в FirstBuffer
alutLoadWAVFile('TestSound.wav',format,data,size,freq,loop);
//функция загружает данные
//из wav-файла в память…
alBufferData(FirstBuffer,format,data,size,freq);
//переносим загружённые данные в
//выделенный ранее буфер
alutUnloadWAV(format,data,size,freq); //выгружаем звук
alGenSources(1,@FirstSource); // просим выделить нам один источник
//функции alSourcei, alSourcefv работают следующим способом, первый аргумент –
//источник, у которого мы будем что-то изменять, второй – параметр источника, который
//мы будем изменять, третий - новое значения для изменяемого параметра
alSourcei ( FirstSource, AL_BUFFER, FirstBuffer); //связываем наш источник с буфером
alSourcefv ( FirstSource, AL_POSITION, @sourcepos); //устанавливаем позицию
alSourcefv ( FirstSource, AL_VELOCITY, @sourcevel); //устанавливаем движение
alSourcei ( FirstSource, AL_LOOPING, AL_TRUE);
//просим чтобы источник
//воспроизводил данные из буфера - по кругу
//устанавливаем параметры слушателя, всё также как и с источниками, только указывать
//слушателя не нужно, так как он у нас один
alListenerfv(AL_POSITION,@listenerPos); //позиция
alListenerfv(AL_VELOCITY,@listenerVel); //движение
alListenerfv(AL_ORIENTATION,@listenerOri); // ориентация
alSourcePlay(FirstSource); //функция просит источник FirstSource воспроизводить звук)
readln; // делаем паузу пока кто нить не нажмёт [ENTER]
//освобождаем устройство, контекст, и т.д. и т.п.
alcMakeContextCurrent(nil);
alcDestroyContext(MainContext);
alcCloseDevice(MainDevice);
end.
Да, но я-ж написал, это у меня есть и работает, а что делать если звуков 150? Создавать 150 источников? Разве они не ограничены там 32 или 64? Или это не сюда ваще?
Мне нужно решение например такой ситуации. 3 ракеты взрываются с промежутком в 3 секунды. Если у меня будет один источник под каждый звук, и мне нужно три раза вызвать звук взрыва, значит 3 раза, с промежутком в 3 секунды проиграется источник взрыва. Если звук по длине превосходит эту разницу в 3 секунды, то варианта тут 2: Или на втором взрыве первый звук взрыва прервётся и начнёт играть с начала для второго взрыва(то что у меня сейчас реализованно) или ничего не произойдёт и звук первого взрыва будет звучать дальше, а второй проигнорируется.
Как мне именно это решить. Может стоит, как я уже писал, не присваивать каждому источнику свой звук при инициализации, а делать это в нужный момент, выбрав любой из неактивных источников, присвоив ему буфер необходимого звука и проиграв его? А источник который уже закончил проигрывать свой звук обнулять и помещать в стэк свободных источников?
Я когда-то родил звуковую подсистему со следующими основными классами: CSoundData, CSound, CSoundManager. Таким образом вся подсистема разделена на звуковые данные, источник и менеджер источников данных. Хотя это сделано для DS, очень легко перекладывается на OpenAL который фактически уже разделён на такие сущности.
Первый собственно обеспечивает доступ к wave даным. Основная функция класса:
int Read(void* where, unsigned int size, unsigned int from); void GetInfo( s16_snd_data_info* res)
Суть первой в получении непрерывных звуковых данных (сэмплов) с указанного сэмпла некоторое количество сэмплов. Для ogg данных (и других компрессированных форматов) это не очень оптимально, поэтому обычно при загрузке источника данных проводится распаковка данных, если длина звука меньше определённого значения. (очевидно что для фоновой музыки нету особого резона проводить распаковку, поскольку источник звука будет всего один и данные будут читаться последовательно, а потому особого оверхеда не будет)
Вторая возвращает характеристики этих данных.
struct s16_snd_data_info { char channels; char bits; long frequency; long size; wchar_t* file_name; unsigned int hash; };
Хэш-функция (по имени файла) используется для быстрого поиска в уже загруженных данных.
Второй класс является также интерфейсно независимым (DS, OAL или ещё что) и содержит пространственные характеристики источника: положение, направление, скорость движения и т.п. а также указатель на специфичные для менеджера данные.
Тпиовой пример создания источника звука:
CSound2* sws3=new CSound2("123.wav");
При этом происходят следующие операции:
1. из имени файла получается хэш.
2. в кэше данных (CSoundDataManager) ищутся данные (CSoundData) с указанным хэшем. Если найдено, источнику сопоставляются эти данные. Если не найдены, создаётся новый источник данных и помещается в кэш данных. Этот источник данных можно использовать соместно несколькими источниками звука.
Основную работу выполняет менеджер. Именно он выделяет необходимые буфера, перемещает источник звука, запускает его воспроизведение и т.п.
class CSoundManager2 { public: virtual void Manage(CSound2* sndToAdd)=0; virtual void UnManage( CSound2* sndToAdd)=0; // starts playing of sound virtual void Play( CSound2* sndToPlay)=0; // Process sounds virtual void Process( unsigned long realTime, float gameTime)=0; virtual void Move( CSound2* snd, float x, float y, float z)=0; virtual void SetVel( CSound2* snd, float x, float y, float z)=0; ... };
Связь источника данных с буфером осуществляется посредством Manage() функции.
Порядок связи:
1. Получаются характеристики
2. Запрашивается кэш буферов данных на свободный буфер с подходящими характеристиками. Если буфер соответсвует, он связывается с указанным источником данных. Его использовать до вызова UnManage() сможет только этот источник звука.
UnManage вызывается если:
1. воспроизведение звука окончено и не указано, что источник звука обязан сохранить за собой буфер до уничтожения объекта CSound. Такой флаг лучше не выставлять не для чего кроме длительных фоновых звуков (речь, музыка и т.п.)
2. источник звука за пределами слышимости и указан флаг моментальной развязки. Т.е. если источник вернётся в область слышимости для него будет заново запрошен буфер.
UnManage() не уничтожает буфер и данные хранящиеся в нём. Она помещает буферы в кэш. И если при вызове Manage() существует буфер с хэшем источника данных необходимого для данного источника звука, то этот буфер и возвращается, иначе ищется подходящий буфер по длительности и частоте дискретизации (в идеале она одинаковая для всех звуков должна быть).
Если хранящийся в буфере кэш не используется длительное время и число буферов ограничено, то при создании нового буфера из кэша удаляется самое слабое звено согласно приоритету звука.
Источники звука могут быть не связаны с буфером до момента его использования. Т.е. буфер запрашивается только в самый последний момент, перед воспроизведением. Вообще, более корректно сделать так - связать с буфером, но при длительном неиспользовании забрать буфер и отдать только по требованию.
Вот таким макаром происходит процесс создания источника данных и управление им.
CSound2* sws3=new CSound2("123.wav"); manager->Manage( sws3); ... manager->Play( sws3); manager->SetPos( x,y,z);
В игровом процессе:
while(true) { .... // snd tick manager->Process( lastTime, lastTime); sndVault.FreeUnused( lastTime); }
Вот такой вот подход.
> Если звук по длине превосходит эту разницу в 3 секунды, то варианта тут 2: Или на втором взрыве первый звук взрыва
> прервётся и начнёт играть с начала для второго взрыва(то что у меня сейчас реализованно) или ничего не произойдёт и звук
> первого взрыва будет звучать дальше, а второй проигнорируется.
Буфер не может использоваться до тех пор пока он не освобождён. Смотришь свободные буферы, если их нет - создаёшь. Если нет возможности создать (лимит буферов достигнут) - то это уже тебе решать какой из двух вариантов тебе больше нравится. Я бы выбрал такой вариант: посмотреть какой из источников звука имеет меньший приоритет и отобрать у него буфер, и импользовать его или создать вместо него новый.
> Может стоит, как я уже писал, не присваивать каждому источнику свой звук при инициализации, а делать это в нужный
> момент, выбрав любой из неактивных источников, присвоив ему буфер необходимого звука и проиграв его?
В моей терминологии это будет: при запуске воспроизведения звука (manager->Play()) проверить связан ли он с буфером и если нет, вызвать Manage(). Собственно это было одной из причин, почему на воспроизведение звук отправляется не CSound->Play() а через менеджер.
У меня используется 18 источников, если не хватает для ещё одного эффекта - не играю его. В принципе 18 звуков одновременно - это такая катавасия, что не отличишь от 150.
1) часто бывает только 16 sorces.
2) ALdevices можно энумерейтить
p.s. кстати, креатив упоминал, что в новой sdk есть софтовый eax. пробовал кто?
2Glorg: Спасибо за информацию!
2Kvap: Ок, тоесть может сделать так?: Для некоторых звуков сделать статичный сурс и буфер и присвоить один другому в начале, а для некоторых - выделить 5-6 сурсов, которые будут играть-ся при необходимости? Думаю в 16 уложится получится.
Devil_Inside
НЕТ! сделай так!
Создаёшь звуковой сорс со звуком полёта ракеты допустим Source[0] и ещё один со звуком взрыва Source[1]
(Естественно загружаешь в них файлы со звуком)
Далее Каждый раз при запуске ракеты у тебя создаёться источник и привязываеться к Source[0]
А при взрыве этот источник привязываеться к Source[0] потом ставишь после последней привязки что после проигрыша этого источника он уничтожааеться.
Для этого нужно масив источников и сорсов!
Так сделано у меня и всё чудесно работает и не тормозит!!!
Если будет какая проблема, могу исходник вывалить.
Devil_Inside
лучше для всех сделать динамические, т.е. любая жёсткая привязка - зло.
p.s. насчёт выделять сразу сорсы или по мере необходимости в OpenAL - не проверял. В DS быстрее работают минимальные количества буферов, там даже не играющиеся обсчитываются.
Ок, Спасибо всем.
Теперь такой вопросик: Насчёт максимального количества источников, Eugene писал что часто их 16. Если делать так как написали сверху, то скажем будут возникать ситуации что ракеты будут взрываться и взрываться, а мне нужно будет например проиграть какой-то более важный звук, например звук связанный с интерфейсом, или к примеру сигнал о завершении уровня. Как поступать в таком случае? Для меня как-бы важнее проиграть именно звук о завершении уровня чем очередной по счёты взрыв? Как поступать в таком случае (Тоесть 16 звуков уже начало играть, и в этот момент нада проиграть 17-ый, более важный)?
Devil_Inside
Есть несколько стратегий. Для стартовой точки почитай dxsdk на предемет флагов dsb::Play() - там они немного затрагиваются. Приоритеты, оставшееся до окончания звука время и т.д.
Всё, я сделал щас так: выделил 5 источников на случайный выбор (то-есть для того случая что я описывал - повторные проигрывания с маленьким промежутком), а за остальные забил звуки которые имеют более высокий приоритет, как то звуки пользовательского интерфейса, оповещения различные и так далее. Сейчас события которые должны проиграться - 100-%но проигрываются, а для остальных выделяется свободный сурс, если такой найдётся. Меня такая система пока вполне устраивает. Если вдруг возникнут вопросы, буду задавать. Всем спасиба.
Тема в архиве.