![]() |
Главная
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Довольно давно я уже писал про использование аудио-библиотек (Audierre и OpenAL), но с тех пор OpenAL практически не обновлялся и не развивался. Поэтому в этой статье я хочу рассказать о еще одной аудио-библиотек - FMOD от компании Firelight Technologies.
FMOD это одна из старейших аудиобиблиотек на рынке. Свою жизнь она начала в 1995 году но не как аудиобиблиотека, а как Firelight MOD Player - инструмент для воспроизведения MOD (или им подобных) файлов. В марте 1999 года FMOD впервые включила в с свой состав API и в декабре 1999 года вышла FMOD 3.0.
В 2013 году FMOD была полностью переписана и в ней почвилось два API - низкоуровневый FMOD API и высокоуровневый FMOD Studio API. При этом сам FMOD Studio API полностью реализован на основе низкоуровневого FMOD API. Далее мы будем рассматривать только низкоуровневый FMOD API.
Это не open-source библиотека, но она доступна сразу под несколькими лицензиями, включая и FMOD Non-commercial License, делающей ее бесплатной для некоммерческого применения. Эта библиотека активно развивается и поддерживается, она является кросс-платформенной. В число поддерживаемых платформ входят Microsoft Windows, mac OS, Linux и ряд игровых консолей.
FMOD поддерживает большое число проигрываемых аудио-форматов, включая AIFF, FLAC, MP3, Ogg Vorbis, WAV и ряд других. Целый ряд игровых движков, таких как Unity, Unreal Engine, Cry Engine используют FMOD в качестве своего звукового движка.
Хотя FMOD и написан на С++, он предоставляет сразу два API - C++ API и C API. Они не просто идентичны, но еще и интероперабельны - вы можете смешивать вызовы из них между собой. Мы будем далее рассматривать основные элементы их С++ API, он довольно прост и легко интегрируется в игровой движок.
В C++ API все классы живут в пространстве имен FMOD
и все методы возвращают значение типа FMOD_RESULT
,
успешному завершению вызова соответствует значение FMOD_OK
.
Необходимые файлы для подключения FMOD для своей платформы можно легко скачать с сайта www.fmod.com.
Обратите внимание, что в FMOD объекты не строятся традиционным путем с использованием конструктора, вместо это для
их создания служат методы create*
другоих объектов или же функция FMOD::System_Create
.
Для уничтожения объектов служит специальный метод release
, также возвращающий значение типа FMOD_RESULT.
Для сборки под Windows нам понадобится подключить заголовочный файл fmod.hpp
и библиотеку fmodex_vc.lib
.
В низкоуровневом API есть пять базовых объектов:
System
- основной объект, владеющий всем. Он отвечает за создание нитей, управление звуковой картой и создание всех остальных объектов.
Sound
- это просто контейнер для звуковых данных. Обычно эти данные грузятся из файла, но можно их брать из памяти или читать по сети (stream).
Channel
- это сам воспроизводимый звук. При инициализации объекта System
создается пул объектов Channel
и затем объекты из него просто раздаются. Ими владеет System
и поэтому их не надо уничтожать.
ChannelGroup
- группы каналов, фактически это точки, где происходит смешивание различных звуков.
DSP
- Digital Sound Processor, внутри библиотеки все реализовано как DSP. Это базовый блок обработки данных. Их можно использовать для генерации звуков. Можно также создавать и свои DSP.
Первое, что нужно для начала работы с библиотекой FMOD это создать экземпляр класса FMOD::System
как показано ниже.
FMOD::System * system = nullptr;
if ( FMOD::System_Create ( &system ) != FMOD_OK )
fatal () << "Error initializing FMOD::System !" << std::endl;
Далее мы проверяем на наличие подходящий драйверов и инициализируем систему, задавая число используемых звуковых каналов (максимальное число одновременно проигрываемых звуков).
int driverCount = 0;
system->getNumDrivers ( &driverCount );
if ( driverCount < 1 )
fatal () << "Cannot find required audio drivers !" << std::endl;
system->init ( 36, FMOD_INIT_NORMAL, nullptr );
Каждому проигрываемому звуку соответствует объект FMOD::Sound
, являющийся фактически контейнером аудио-данных.
Для создания такого объекта служит метод System::createSound
.
FMOD_RESULT System::createSound ( const char* name,
FMOD_MODE mode,
FMOD_CREATTESOUNDEXINFO * exinfo,
FMOD::Sound ** sound );
Параметр
Если
На самом деле
Базовым интерфейсом для управления воспроизведением звука является
Также у
Следующий фрагмент кода запускает на однократное воспроизведение заданного звука.
Ниже приводится описание метода
В 3D у каждого звука есть минимальное и максимальное расстояние, используемые для управления зависимостью интенсивности звука от расстояния для него.
Задаются они при помощи метода
Поддерживается несколько режимов вычисления того, как именно происходит ослабевание звука в зависимости от расстояния.
Какой режим следует использовать задается флагом параметра
Также для полной поддержки 3D-звука также необходимо:
Для задания положения, ориентации и скорости наблюдателя (камеры) служит метод
Параметры источника звука задаются не для
Для задания положения и скорости источника звука (
Создать новую группу каналов можно при помощи вызова
Также можно для уже существующих звуков задать содержащую их группу каналов при помощи следующего вызова:
FMOD Core API поддерживает задание геометрии (mesh) для создания в реальном времени эффекта закрывания звука
различными объектами.
Для этого используется специальный вызов
У класса
Библиотека FMOD также поддерживает большое количество различных DSP эффектов.
Эти эффекты создаются при помощи вызова
Параметр
Для задания параметров DSP служит метод
Созданный DSP-эффект можно подключить к
Здесь параметр
Кроме того, FMOD поддерживает 2 типа реверба и виртуальную систему 3D-реверба.
Наиболее простой и удобной в использовании является система 3D-реверба.
Она позволяет создавать в сцене (и притом довольное большие количество) реверб-сфер,
при попадании внутрь которой включается соответствующий эффект.
Для создания таких сфер служит следующий метод, создающий объект класса
Для задания атрибутов такой реверб-сферы служит метод
Для задания дополнительных свойств реверб-сфер служит метод
Сама структура
Можно включать и выключать отдельные реверб-сферы при помощи метода
Ниже приводится исходный код простого OpenGL приложения, демонстрирующего рендеринг геометрии вместе с 3D звукамим.
По этой
ссылке можно скачать весь исходный код к этой статье.
exinfo
передается nullptr
, а в качестве параметра mode
- FMOD_DEFAULT
(для использования трехмерного звука нужно задать флаг FMOD_3D
).
mode
содержит битовый флаг FMOD_OPENMEMORY
, то тогда параметр name
это просто указатель на область
памяти со звуковыми данными. Длина этой области передается через поле length
структуры, на которую указывает exinfo
,
при этом поле size
этой структуры должно быть равно sizeof(FMOD_CREATTESOUNDEXINFO)
.
FMOD::Sound * sound = nullptr;
system->createSound ( "test.wav", FMOD_3D, 0, &sound );
FMOD::Sound
это просто контейнер для звуковых данных, за воспроизведение данного звука отвечает
сама система, у которой есть для этого метод System::playSound
.
FMOD::ChannelControl
.
И FMOD::Channel
и FMOD::ChannelGroup
его поддерживают.
FMOD::ChannelControl
есть целая группа методов по управлению воспроизведением.
FMOD_RESULT ChannelControl::isPlaying ( bool * playing );
FMOD_RESULT ChannelControl::stop ();
FMOD_RESULT ChannelControl::setPaused ( bool paused );
FMOD_RESULT ChannelControl::setPitch ( float pitch );
FMOD_RESULT ChannelControl::setVolume ( float volume ); // [0,1]
FMOD_RESULT ChannelControl::setMute ( bool mute );
FMOD_RESULT ChannelControl::removeDSP ( DSP * dsp );
sound->setMode ( FMOD_LOOP_OFF );
system->playSound ( sound, nullptr, false, &channel );
playSound
.
FMOD_RESULT System::playSound ( Sound * sound, ChannelGroup * group, bool paused, Channel ** channel );
Sound::set3DMinMaxDistance
.
FMOD_RESULT Sound::set3DMinMaxDistance ( float minDistance, float maxDistance );
mode
.
По умолчанию используется флаг FMOD_3D_INVERSEROLLOF
, другими вариантами являются FMOD_3D_LINEARROLLOF
и FMOD_3D_LINEARSQUAREROLLOF
.
System::set3DListenerAttributes
System::update
для пересчета параметров звуковSystem::set3DListenerAttributes
.
FMOD_RESULT System::set3DListenerAttributes(
int listener,
const FMOD_VECTOR * pos,
const FMOD_VECTOR * vel,
const FMOD_VECTOR * forward,
const FMOD_VECTOR * up
);
FMOD::Sound
(который является просто контейнером звуковых данных), а для классов FMOD::Channel
и FMOD::ChannelGroup
.
Channel
или ChannelGroup
) служит метод ChannelControl::set3DAttributes
.
FMOD_RESULT ChannelControl::set3DAttributes (
const FMOD_VECTOR * pos,
const FMOD_VECTOR * vel
);
System::createChannelGroup
:
FMOD_RESULT System::createChannelGroup(
const char *name,
ChannelGroup **channelgroup
);
channel->setChannelGroup ( group );
System::createGeometry
, создающий объект класса FMOD::Geometry
.
FMOD_RESULT System::createGeometry ( int maxPolygons, int maxVertices, Geometry ** geometry );
FMOD::Geometry
есть рядом методов, позволяющих добавлять многоугольники к нему и управлять положением и ориентаций всего меша.
FMOD_RESULT Geometry::addPolygon(
float directocclusion,
float reverbocclusion,
bool doublesided,
int numvertices,
const FMOD_VECTOR *vertices,
int *polygonindex
);
FMOD_RESULT Geometry::setPolygonAttributes(
int index,
float directocclusion,
float reverbocclusion,
bool doublesided
);
FMOD_RESULT Geometry::setPosition(
const FMOD_VECTOR *position
);
FMOD_RESULT Geometry::setRotation(
const FMOD_VECTOR *forward,
const FMOD_VECTOR *up
);
System::createDSPByType
.
FMOD_RESULT System::createDSPByType(
FMOD_DSP_TYPE type,
DSP **dsp
);
type
задает тип создаваемого DSP-эффекта, ниже приводятся допустимые значение для него.
typedef enum FMOD_DSP_TYPE {
FMOD_DSP_TYPE_UNKNOWN,
FMOD_DSP_TYPE_MIXER,
FMOD_DSP_TYPE_OSCILLATOR,
FMOD_DSP_TYPE_LOWPASS,
FMOD_DSP_TYPE_ITLOWPASS,
FMOD_DSP_TYPE_HIGHPASS,
FMOD_DSP_TYPE_ECHO,
FMOD_DSP_TYPE_FADER,
FMOD_DSP_TYPE_FLANGE,
FMOD_DSP_TYPE_DISTORTION,
FMOD_DSP_TYPE_NORMALIZE,
FMOD_DSP_TYPE_LIMITER,
FMOD_DSP_TYPE_PARAMEQ,
FMOD_DSP_TYPE_PITCHSHIFT,
FMOD_DSP_TYPE_CHORUS,
FMOD_DSP_TYPE_VSTPLUGIN,
FMOD_DSP_TYPE_WINAMPPLUGIN,
FMOD_DSP_TYPE_ITECHO,
FMOD_DSP_TYPE_COMPRESSOR,
FMOD_DSP_TYPE_SFXREVERB,
FMOD_DSP_TYPE_LOWPASS_SIMPLE,
FMOD_DSP_TYPE_DELAY,
FMOD_DSP_TYPE_TREMOLO,
FMOD_DSP_TYPE_LADSPAPLUGIN,
FMOD_DSP_TYPE_SEND,
FMOD_DSP_TYPE_RETURN,
FMOD_DSP_TYPE_HIGHPASS_SIMPLE,
FMOD_DSP_TYPE_PAN,
FMOD_DSP_TYPE_THREE_EQ,
FMOD_DSP_TYPE_FFT,
FMOD_DSP_TYPE_LOUDNESS_METER,
FMOD_DSP_TYPE_ENVELOPEFOLLOWER,
FMOD_DSP_TYPE_CONVOLUTIONREVERB,
FMOD_DSP_TYPE_CHANNELMIX,
FMOD_DSP_TYPE_TRANSCEIVER,
FMOD_DSP_TYPE_OBJECTPAN,
FMOD_DSP_TYPE_MULTIBAND_EQ,
FMOD_DSP_TYPE_MAX
} FMOD_DSP_TYPE;
setParameterFloat
как показано ниже.
myDsp->setParameterFloat ( FMOD_DSP_LOWPASS_CUTOFF, 1200 );
FMOD::Channel
или FMOD::ChannelGroup
при помощи метода FMOD::ChannelControl::addDSP
.
FMOD_RESULT ChannelControl::addDSP (
int index,
DSP * dsp
);
index
задает положение данного DSP в цепочке DSP-эффектов данного канала.
FMOD::Reverb3D
:
FMOD_RESULT System::ceateReverb3D ( Reverb3D ** reverb );
Reverb3D::set3DAttributes
:
FMOD_RESULT Reverb3D::set3DAttributes(
const FMOD_VECTOR *position,
float mindistance,
float maxdistance
);
Reverb3D::setPropertiess
:
FMOD_RESULT Reverb3D::setProperties (
const FMOD_REVERB_PROPERTIES *properties
);
FMOD_REVERB_PROPERTIES
описывается следующим образом:
typedef struct FMOD_REVERB_PROPERTIES {
float DecayTime;
float EarlyDelay;
float LateDelay;
float HFReference;
float HFDecayRatio;
float Diffusion;
float Density;
float LowShelfFrequency;
float LowShelfGain;
float HighCut;
float EarlyLateMix;
float WetLevel;
} FMOD_REVERB_PROPERTIES;
setActive
, метод getActive
позволяет узнать включена ли в данный момент заданная реверб-сфера.
FMOD_RESULT Reverb3D::setActive ( bool active );
FMOD_RESULT Reverb3D::getActive ( bool * active );
//
// FMDO 3D sound example
//
// Author: Alexey V. Boreskov <steps3d@gmail.com>, <steps3d@narod.ru>
//
#include <algorithm>
#include <fmod.hpp>
#include "GlutCameraWindow.h"
#include "Program.h"
#include "BasicMesh.h"
#include "Texture.h"
#include "Framebuffer.h"
#include "ScreenQuad.h"
#include "randUtils.h"
#define SIZE 1000.0f
inline FMOD_VECTOR toFmod ( const glm::vec3& v )
{
return { v.x, v.y, v.z };
}
class SoundWindow : public GlutCameraWindow
{
Program program, program2;
VertexBuffer posBuf; // vector of vec4
VertexBuffer drawBuf; // vector of DrawElementsIndirectCommand
BasicMesh * room = nullptr;
BasicMesh * knot = nullptr;
Texture decalMap;
Texture stoneMap;
std::vector<BasicMesh *> boxes;
FMOD::System * system = nullptr;
FMOD::Sound * sound1 = nullptr;
FMOD::Sound * sound2 = nullptr;
FMOD::Sound * sound3 = nullptr;
FMOD::Channel * channel1 = nullptr;
FMOD::Channel * channel3 = nullptr;
FMOD::Geometry * geometry = nullptr;
public:
SoundWindow () : GlutCameraWindow ( 100, 100, 900, 900, "FMOD 3D sound example" )
{
if ( !program.loadProgram ( "decal.glsl" ) )
exit ( "Error loading shader: %s\n", program.getLog ().c_str () );
program.bind ();
program.setTexture ( "imageMap", 0 );
program.unbind ();
createBoxes ();
decalMap.load2D ( "../../Textures/oak.jpg" );
stoneMap.load2D ( "../../Textures/block.jpg" );
camera.moveTo ( glm::vec3 ( -0.5, 0.5, 5 ) );
if ( FMOD::System_Create ( &system ) != FMOD_OK )
exit ( "Error initializing FMOD::System !" );
int driverCount = 0;
system->getNumDrivers ( &driverCount );
printf ( "Driver count %d\n", driverCount );
if ( driverCount < 1 )
exit ( "Cannot find required audio drivers !" );
system->init ( 36, FMOD_INIT_NORMAL, nullptr );
// load all sounds
system->createSound ( "wavdata/Gun1.wav", FMOD_3D, 0, &sound1 );
system->createSound ( "wavdata/laser.wav", FMOD_3D, 0, &sound2 );
system->createSound ( "wavdata/meow1.wav", FMOD_3D, 0, &sound3 );
// create geometry
system->createGeometry ( 100, 1000, &geometry );
// setup meow sound
auto pos = toFmod ( glm::vec3 ( 1.0f, 1.0f, 2.0f) );
auto vel = toFmod ( glm::vec3 ( 0.0f) );
sound3->setMode ( FMOD_LOOP_NORMAL );
system->playSound ( sound3, nullptr, false, &channel3 );
channel3->set3DAttributes ( &pos, &vel );
}
~SoundWindow ()
{
system->release ();
}
void redisplay () override
{
//glEnable ( GL_DEPTH_TEST );
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
program.bind ();
program.setUniformMatrix ( "proj", camera.getProjection () );
program.setUniformMatrix ( "mv", camera.getModelView () );
decalMap.bind ();
room -> render ();
decalMap.unbind ();
stoneMap.bind ();
for ( auto * p : boxes )
p -> render ();
stoneMap.unbind ();
program.unbind ();
}
void keyTyped ( unsigned char key, int modifiers, int x, int y ) override
{
GlutWindow::keyTyped ( key, modifiers, x, y );
if ( key == 27 || key == 'q' || key == 'Q' ) // quit requested
::exit ( 0 );
glm::vec3 mv ( 0 );
if ( key == 'w' || key == 'W' )
mv = step * camera.getViewDir ();
else
if ( key == 'x' || key == 'X' )
mv = -sideStep * camera.getViewDir ();
else
if ( key == 'a' || key == 'A' )
mv = -step * camera.getSideDir ();
else
if ( key == 'd' || key == 'D' )
mv = sideStep * camera.getSideDir ();
else
if ( key == ' ' )
{
auto p = toFmod ( glm::vec3 ( -1.0f, 1.0, 3.0f ) );
auto v = toFmod ( glm::vec3 ( 0.0f ) );
sound1->setMode ( FMOD_LOOP_OFF ); // FMOD_LOOP_NORMAL
system->playSound ( sound1, nullptr, false, &channel1 );
channel1->set3DAttributes ( &p, &v );
}
camera.moveBy ( glm::vec3 ( mv.x, 0.0f, mv.z ) );
setListener ( camera.getPos (), glm::vec3 ( 0.0f ), camera.getViewDir (), camera.getUpDir () );
}
void mouseWheel ( int wheel, int dir, int x, int y ) override {}
void mousePassiveMotion ( int x, int y ) override
{
GlutCameraWindow::mousePassiveMotion ( x, y );
setListener ( camera.getPos (), glm::vec3 ( 0.0f ), camera.getViewDir (), camera.getUpDir () );
}
void setListener ( const glm::vec3& p, const glm::vec3& v, const glm::vec3& frw, const glm::vec3& u )
{
FMOD_VECTOR pos = toFmod ( p );
FMOD_VECTOR vel = toFmod ( v );
FMOD_VECTOR forward = toFmod ( frw );
FMOD_VECTOR up = toFmod ( u );
system->set3DListenerAttributes ( 0, &pos, &vel, &forward, &up );
}
void idle ()
{
GlutWindow::idle ();
system->update ();
}
protected:
void createBoxes ()
{
room = createHorQuad ( glm::vec3 ( -SIZE, -0.1, -SIZE ), SIZE*2, SIZE*2 );
boxes.push_back ( createBox ( glm::vec3 ( 0, -0.1, 2 ), glm::vec3 ( 2, 1, 3 ) ) );
boxes.push_back ( createBox ( glm::vec3 (-2, -0.1, -1 ), glm::vec3 (2, 1, 2) ) );
boxes.push_back ( createBox ( glm::vec3 ( 3, -0.1, 1), glm::vec3 (3, 1, 2) ) );
addBox ( glm::vec3 ( 0, -0.1, 2 ), glm::vec3 ( 2, 1, 3 ) );
addBox ( glm::vec3 (-2, -0.1, -1 ), glm::vec3 (2, 1, 2) );
addBox ( glm::vec3 ( 3, -0.1, 1), glm::vec3 (3, 1, 2) );
}
void addBox ( const glm::vec3& pos, const glm::vec3& size )
{
auto sx = glm::vec3 ( size.x, 0.0f, 0.0f );
auto sz = glm::vec3 ( 0.0f, 0.0f, size.z );
auto sy = glm::vec3 ( 0.0f, 1.0f, 0.0f );
addSide ( pos, pos + sz, sy );
addSide ( pos + sx, pos + sx + sz, sy );
addSide ( pos + sx + sz, pos + sz, sy );
addSide ( pos, pos + sz, sy );
}
void addSide ( const glm::vec3& p1, const glm::vec3& p2, const glm::vec3& sy )
{
FMOD_VECTOR v [] = { toFmod ( p1 ), toFmod ( p2 ), toFmod ( p2 + sy ), toFmod ( p1 + sy ) };
geometry->addPolygon( 1, 1, true, 4, v, nullptr );
}
};
int main ( int argc, char * argv [] )
{
GlutWindow::init ( argc, argv, 4, 6 );
SoundWindow win;
return GlutWindow::run ();
}