|
Главная
Статьи
Ссылки
Скачать
Скриншоты
Юмор
Почитать
Tools
Проекты
Обо мне
Гостевая
Форум
|
Довольно давно я уже писал про использование аудио-библиотек (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::set3DListenerAttributesSystem::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 ();
}