audio system basic interface

This commit is contained in:
MihailRis 2024-02-27 03:31:57 +03:00
parent 7cde1e8ac9
commit 97539aa9f4
12 changed files with 528 additions and 320 deletions

197
src/audio/ALAudio.cpp Normal file
View File

@ -0,0 +1,197 @@
#include "ALAudio.h"
#include "alutil.h"
#include <string>
#include <iostream>
using namespace audio;
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
: device(device), context(context)
{
ALCint size;
alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size);
std::vector<ALCint> attrs(size);
alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0]);
for (size_t i = 0; i < attrs.size(); ++i){
if (attrs[i] == ALC_MONO_SOURCES) {
std::cout << "AL: max mono sources: " << attrs[i+1] << std::endl;
maxSources = attrs[i+1];
}
}
auto devices = getAvailableDevices();
std::cout << "AL devices:" << std::endl;
for (auto& name : devices) {
std::cout << " " << name << std::endl;
}
}
ALAudio::~ALAudio() {
for (ALSource* source : allsources) {
if (source->isPlaying()){
alSourceStop(source->id);
alCheckErrorsMacro();
}
alDeleteSources(1, &source->id);
alCheckErrorsMacro();
}
for (ALBuffer* buffer : allbuffers){
alDeleteBuffers(1, &buffer->id);
alCheckErrorsMacro();
}
alcMakeContextCurrent(context);
alcDestroyContext(context);
if (!alcCloseDevice(device)) {
std::cerr << "AL: device not closed!" << std::endl;
}
device = nullptr;
context = nullptr;
}
bool ALSource::setBuffer(ALBuffer* buffer) {
alSourcei(id, AL_BUFFER, buffer->id);
return alCheckErrorsMacro();
}
bool ALSource::play(){
alSourcePlay(id);
return alCheckErrorsMacro();
}
bool ALSource::isPlaying() {
int state;
alGetSourcei(id, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
bool ALSource::setPosition(glm::vec3 position) {
alSource3f(id, AL_POSITION, position.x, position.y, position.z);
return alCheckErrorsMacro();
}
bool ALSource::setVelocity(glm::vec3 velocity) {
alSource3f(id, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
return alCheckErrorsMacro();
}
bool ALSource::setLoop(bool loop) {
alSourcei(id, AL_LOOPING, AL_TRUE ? loop : AL_FALSE);
return alCheckErrorsMacro();
}
bool ALSource::setGain(float gain) {
alSourcef(id, AL_GAIN, gain);
return alCheckErrorsMacro();
}
bool ALSource::setPitch(float pitch) {
alSourcef(id, AL_PITCH, pitch);
return alCheckErrorsMacro();
}
bool ALBuffer::load(int format, const char* data, int size, int freq) {
alBufferData(id, format, data, size, freq);
return alCheckErrorsMacro();
}
Sound* ALAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
// TODO: implement
return nullptr;
}
ALAudio* ALAudio::create() {
ALCdevice* device = alcOpenDevice(nullptr);
if (device == nullptr)
return nullptr;
ALCcontext* context = alcCreateContext(device, nullptr);
if (!alcMakeContextCurrent(context)){
alcCloseDevice(device);
return nullptr;
}
if (!alCheckErrorsMacro()) {
return nullptr;
}
std::cout << "AL: initialized" << std::endl;
return new ALAudio(device, context);
}
ALSource* ALAudio::getFreeSource(){
if (!freesources.empty()){
ALSource* source = freesources.back();
freesources.pop_back();
return source;
}
if (allsources.size() == maxSources){
std::cerr << "attempted to create new source, but limit is " << maxSources << std::endl;
return nullptr;
}
ALuint id;
alGenSources(1, &id);
if (!alCheckErrorsMacro())
return nullptr;
ALSource* source = new ALSource(id);
allsources.push_back(source);
return source;
}
ALBuffer* ALAudio::getFreeBuffer(){
if (!freebuffers.empty()){
ALBuffer* buffer = freebuffers.back();
freebuffers.pop_back();
return buffer;
}
if (allbuffers.size() == maxBuffers){
std::cerr << "attempted to create new ALbuffer, but limit is " << maxBuffers << std::endl;
return nullptr;
}
ALuint id;
alGenBuffers(1, &id);
if (!alCheckErrorsMacro()) {
return nullptr;
}
ALBuffer* buffer = new ALBuffer(id);
allbuffers.push_back(buffer);
return buffer;
}
void ALAudio::freeSource(ALSource* source){
freesources.push_back(source);
}
void ALAudio::freeBuffer(ALBuffer* buffer){
freebuffers.push_back(buffer);
}
std::vector<std::string> ALAudio::getAvailableDevices() const {
std::vector<std::string> devicesVec;
const ALCchar* devices;
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
if (!alCheckErrorsMacro()) {
return devicesVec;
}
const char* ptr = devices;
do {
devicesVec.push_back(std::string(ptr));
ptr += devicesVec.back().size() + 1;
}
while (ptr[0]);
return devicesVec;
}
void ALAudio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up){
ALfloat listenerOri[] = { at.x, at.y, at.z, up.x, up.y, up.z };
alListener3f(AL_POSITION, position.x, position.y, position.z);
alCheckErrorsMacro();
alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z);
alCheckErrorsMacro();
alListenerfv(AL_ORIENTATION, listenerOri);
alCheckErrorsMacro();
}

79
src/audio/ALAudio.h Normal file
View File

@ -0,0 +1,79 @@
#ifndef SRC_AUDIO_AUDIO_H_
#define SRC_AUDIO_AUDIO_H_
#include <vector>
#include <string>
#include <glm/glm.hpp>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#include "audio.h"
#include "../typedefs.h"
namespace audio {
struct ALBuffer;
struct ALSource {
uint id;
ALSource(uint id) : id(id) {}
bool isPlaying();
bool setPosition(glm::vec3 position);
bool setVelocity(glm::vec3 velocity);
bool setBuffer(ALBuffer* buffer);
bool setLoop(bool loop);
bool setGain(float gain);
bool setPitch(float pitch);
bool play();
};
struct ALBuffer {
uint id;
ALBuffer(uint id) : id(id) {}
bool load(int format, const char* data, int size, int freq);
};
class ALAudio : public Backend {
ALCdevice* device;
ALCcontext* context;
std::vector<ALSource*> allsources;
std::vector<ALSource*> freesources;
std::vector<ALBuffer*> allbuffers;
std::vector<ALBuffer*> freebuffers;
uint maxSources;
uint maxBuffers;
ALAudio(ALCdevice* device, ALCcontext* context);
public:
~ALAudio();
ALSource* getFreeSource();
ALBuffer* getFreeBuffer();
void freeSource(ALSource* source);
void freeBuffer(ALBuffer* buffer);
std::vector<std::string> getAvailableDevices() const;
Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) override;
static ALAudio* create();
};
}
#endif /* SRC_AUDIO_AUDIO_H_ */

View File

@ -1,193 +0,0 @@
#include "Audio.h"
#include "audioutil.h"
#include <string>
#include <iostream>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
ALCdevice* Audio::device;
ALCcontext* Audio::context;
unsigned Audio::maxSources;
unsigned Audio::maxBuffers = 1024;
std::vector<ALSource*> Audio::allsources;
std::vector<ALSource*> Audio::freesources;
std::vector<ALBuffer*> Audio::allbuffers;
std::vector<ALBuffer*> Audio::freebuffers;
bool ALSource::setBuffer(ALBuffer* buffer) {
alSourcei(id, AL_BUFFER, buffer->id);
return alCheckErrorsMacro();
}
bool ALSource::play(){
alSourcePlay(id);
return alCheckErrorsMacro();
}
bool ALSource::isPlaying() {
int state;
alGetSourcei(id, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
bool ALSource::setPosition(glm::vec3 position) {
alSource3f(id, AL_POSITION, position.x, position.y, position.z);
return alCheckErrorsMacro();
}
bool ALSource::setVelocity(glm::vec3 velocity) {
alSource3f(id, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
return alCheckErrorsMacro();
}
bool ALSource::setLoop(bool loop) {
alSourcei(id, AL_LOOPING, AL_TRUE ? loop : AL_FALSE);
return alCheckErrorsMacro();
}
bool ALSource::setGain(float gain) {
alSourcef(id, AL_GAIN, gain);
return alCheckErrorsMacro();
}
bool ALSource::setPitch(float pitch) {
alSourcef(id, AL_PITCH, pitch);
return alCheckErrorsMacro();
}
bool ALBuffer::load(int format, const char* data, int size, int freq) {
alBufferData(id, format, data, size, freq);
return alCheckErrorsMacro();
}
bool Audio::initialize() {
device = alcOpenDevice(nullptr);
if (device == nullptr)
return false;
context = alcCreateContext(device, nullptr);
if (!alcMakeContextCurrent(context)){
alcCloseDevice(device);
return false;
}
if (!alCheckErrorsMacro())
return false;
ALCint size;
alcGetIntegerv(device, ALC_ATTRIBUTES_SIZE, 1, &size);
std::vector<ALCint> attrs(size);
alcGetIntegerv(device, ALC_ALL_ATTRIBUTES, size, &attrs[0]);
for(size_t i=0; i<attrs.size(); ++i){
if (attrs[i] == ALC_MONO_SOURCES){
std::cout << "max mono sources: " << attrs[i+1] << std::endl;
maxSources = attrs[i+1];
}
}
return true;
}
void Audio::finalize(){
for (ALSource* source : allsources){
if (source->isPlaying()){
alSourceStop(source->id); alCheckErrorsMacro();
}
alDeleteSources(1, &source->id); alCheckErrorsMacro();
}
for (ALBuffer* buffer : allbuffers){
alDeleteBuffers(1, &buffer->id); alCheckErrorsMacro();
}
alcMakeContextCurrent(context);
alcDestroyContext(context);
if (!alcCloseDevice(device)){
std::cerr << "device not closed!" << std::endl;
}
device = nullptr;
context = nullptr;
}
ALSource* Audio::getFreeSource(){
if (!freesources.empty()){
ALSource* source = freesources.back();
freesources.pop_back();
return source;
}
if (allsources.size() == maxSources){
std::cerr << "attempted to create new source, but limit is " << maxSources << std::endl;
return nullptr;
}
ALuint id;
alGenSources(1, &id);
if (!alCheckErrorsMacro())
return nullptr;
ALSource* source = new ALSource(id);
allsources.push_back(source);
return source;
}
ALBuffer* Audio::getFreeBuffer(){
if (!freebuffers.empty()){
ALBuffer* buffer = freebuffers.back();
freebuffers.pop_back();
return buffer;
}
if (allbuffers.size() == maxBuffers){
std::cerr << "attempted to create new ALbuffer, but limit is " << maxBuffers << std::endl;
return nullptr;
}
ALuint id;
alGenBuffers(1, &id);
if (!alCheckErrorsMacro())
return nullptr;
ALBuffer* buffer = new ALBuffer(id);
allbuffers.push_back(buffer);
return buffer;
}
void Audio::freeSource(ALSource* source){
freesources.push_back(source);
}
void Audio::freeBuffer(ALBuffer* buffer){
freebuffers.push_back(buffer);
}
bool Audio::get_available_devices(std::vector<std::string>& devicesVec){
const ALCchar* devices;
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
if (!alCheckErrorsMacro())
return false;
const char* ptr = devices;
devicesVec.clear();
do {
devicesVec.push_back(std::string(ptr));
ptr += devicesVec.back().size() + 1;
}
while(*(ptr + 1) != '\0');
return true;
}
void Audio::setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up){
ALfloat listenerOri[] = { at.x, at.y, at.z, up.x, up.y, up.z };
alListener3f(AL_POSITION, position.x, position.y, position.z);
alCheckErrorsMacro();
alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z);
alCheckErrorsMacro();
alListenerfv(AL_ORIENTATION, listenerOri);
alCheckErrorsMacro();
}

View File

@ -1,67 +0,0 @@
#ifndef SRC_AUDIO_AUDIO_H_
#define SRC_AUDIO_AUDIO_H_
#include <vector>
#include <string>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
#include <glm/glm.hpp>
struct ALBuffer;
struct ALSource {
ALuint id;
ALSource(ALuint id) : id(id) {}
bool isPlaying();
bool setPosition(glm::vec3 position);
bool setVelocity(glm::vec3 velocity);
bool setBuffer(ALBuffer* buffer);
bool setLoop(bool loop);
bool setGain(float gain);
bool setPitch(float pitch);
bool play();
};
struct ALBuffer {
ALuint id;
ALBuffer(ALuint id) : id(id) {}
bool load(int format, const char* data, int size, int freq);
};
class Audio {
static ALCdevice* device;
static ALCcontext* context;
static std::vector<ALSource*> allsources;
static std::vector<ALSource*> freesources;
static std::vector<ALBuffer*> allbuffers;
static std::vector<ALBuffer*> freebuffers;
static unsigned maxSources;
static unsigned maxBuffers;
public:
static ALSource* getFreeSource();
static ALBuffer* getFreeBuffer();
static void freeSource(ALSource* source);
static void freeBuffer(ALBuffer* buffer);
static bool initialize();
static void finalize();
static bool get_available_devices(std::vector<std::string>& devicesVec);
static void setListener(glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up);
};
#endif /* SRC_AUDIO_AUDIO_H_ */

18
src/audio/NoAudio.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "NoAudio.h"
using namespace audio;
NoSound::NoSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
duration = pcm->getDuration();
if (keepPCM) {
this->pcm = pcm;
}
}
Sound* NoAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
return new NoSound(pcm, keepPCM);
}
NoAudio* NoAudio::create() {
return new NoAudio();
}

44
src/audio/NoAudio.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef AUDIO_NOAUDIO_H_
#define AUDIO_NOAUDIO_H_
#include "audio.h"
namespace audio {
class NoSound : public Sound {
std::shared_ptr<PCM> pcm;
duration_t duration;
public:
NoSound(std::shared_ptr<PCM> pcm, bool keepPCM);
~NoSound() {}
duration_t getDuration() const override {
return duration;
}
std::shared_ptr<PCM> getPCM() const override {
return pcm;
}
speakerid_t newInstance(int priority) const override {
return 0;
}
};
class NoAudio : public Backend {
public:
~NoAudio() {}
Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 at,
glm::vec3 up
) override {}
static NoAudio* create();
};
}
#endif // AUDIO_NOAUDIO_H_

View File

@ -1,4 +1,4 @@
#include "audioutil.h"
#include "alutil.h"
#include <iostream>
#include <fstream>
@ -180,11 +180,13 @@ bool load_wav_file_header(std::ifstream& file,
}
// after that user must free returned memory by himself!
char* load_wav(const std::string& filename,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size){
char* load_wav(
const std::string& filename,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size
){
std::ifstream in(filename, std::ios::binary);
if(!in.is_open()){
std::cerr << "ERROR: Could not open \"" << filename << "\"" << std::endl;

53
src/audio/alutil.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef SRC_AUDIO_AUDIOUTIL_H_
#define SRC_AUDIO_AUDIOUTIL_H_
#include <string>
#include <type_traits>
#include <cstdint>
#ifdef __APPLE__
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#endif
#define alCheckErrorsMacro() check_al_errors(__FILE__, __LINE__)
bool check_al_errors(const std::string& filename, const std::uint_fast32_t line);
bool load_wav_file_header(
std::ifstream& file,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size
);
char* load_wav(
const std::string& filename,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size
);
static inline ALenum to_al_format(short channels, short samples){
bool stereo = (channels > 1);
switch (samples) {
case 16:
if (stereo)
return AL_FORMAT_STEREO16;
else
return AL_FORMAT_MONO16;
case 8:
if (stereo)
return AL_FORMAT_STEREO8;
else
return AL_FORMAT_MONO8;
default:
return -1;
}
}
#endif /* SRC_AUDIO_AUDIOUTIL_H_ */

32
src/audio/audio.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "audio.h"
#include <iostream>
#include "ALAudio.h"
#include "NoAudio.h"
audio::Backend* audio::backend = nullptr;
void audio::initialize(bool enabled) {
if (enabled) {
audio::backend = ALAudio::create();
}
if (audio::backend == nullptr) {
std::cerr << "could not to initialize audio" << std::endl;
audio::backend = NoAudio::create();
}
}
void audio::setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) {
audio::backend->setListener(position, velocity, lookAt, up);
}
void audio::close() {
delete audio::backend;
audio::backend = nullptr;
}

94
src/audio/audio.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef AUDIO_AUDIO_H_
#define AUDIO_AUDIO_H_
#include <vector>
#include <memory>
#include <glm/glm.hpp>
#include "../typedefs.h"
namespace audio {
using speakerid_t = int64_t;
/// @brief duration unit is second
using duration_t = float;
/// @brief Pulse-code modulation data
struct PCM {
/// @brief May contain 8 bit and 16 bit PCM data
std::vector<char> data;
uint8_t channels;
uint8_t bitsPerSample;
uint sampleRate;
constexpr inline size_t countSamples() const {
return data.size() / channels / (bitsPerSample / 8);
}
constexpr inline duration_t getDuration() const {
return countSamples() / static_cast<duration_t>(sampleRate);
}
};
/// @brief Sound is an audio asset that supposed to support many
/// simultaneously playing instances with different sources.
/// So it's audio data is stored in memory.
class Sound {
public:
virtual ~Sound() {}
/// @brief Get sound duration
/// @return duration in seconds (>= 0.0)
virtual duration_t getDuration() const = 0;
/// @brief Get sound PCM data
/// @return PCM data or nullptr
virtual std::shared_ptr<PCM> getPCM() const = 0;
/// @brief Create new sound instance
/// @param priority instance priority. High priority instance can
/// take out speaker from low priority instance
/// @return new speaker id with sound bound or 0
/// if all speakers are in use
virtual speakerid_t newInstance(int priority) const = 0;
};
class Backend {
public:
virtual ~Backend() {};
/// @brief Create new sound from PCM data
/// @param pcm PCM data
/// @param keepPCM store PCM data in sound to make it accessible with
/// Sound::getPCM
/// @return new Sound instance
virtual Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) = 0;
virtual void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) = 0;
};
extern Backend* backend;
/// @brief Initialize audio system or use no audio mode
/// @param enabled try to initialize actual audio
extern void initialize(bool enabled);
/// @brief Configure 3D listener
/// @param position listener position
/// @param velocity listener velocity (used for Doppler effect)
/// @param lookAt point the listener look at
/// @param up camera up vector
extern void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
);
/// @brief Finalize audio system
extern void close();
};
#endif // AUDIO_AUDIO_H_

View File

@ -1,49 +0,0 @@
#ifndef SRC_AUDIO_AUDIOUTIL_H_
#define SRC_AUDIO_AUDIOUTIL_H_
#include <string>
#include <type_traits>
#include <cstdint>
#ifdef __APPLE__
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#endif
#define alCheckErrorsMacro() check_al_errors(__FILE__, __LINE__)
bool check_al_errors(const std::string& filename, const std::uint_fast32_t line);
bool load_wav_file_header(std::ifstream& file,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size);
char* load_wav(const std::string& filename,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size);
static inline ALenum to_al_format(short channels, short samples){
bool stereo = (channels > 1);
switch (samples) {
case 16:
if (stereo)
return AL_FORMAT_STEREO16;
else
return AL_FORMAT_MONO16;
case 8:
if (stereo)
return AL_FORMAT_STEREO8;
else
return AL_FORMAT_MONO8;
default:
return -1;
}
}
#endif /* SRC_AUDIO_AUDIOUTIL_H_ */

View File

@ -10,7 +10,7 @@
#include <functional>
#define GLEW_STATIC
#include "audio/Audio.h"
#include "audio/audio.h"
#include "assets/Assets.h"
#include "assets/AssetsLoader.h"
#include "world/WorldGenerators.h"
@ -56,6 +56,7 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths)
if (Window::initialize(settings.display)){
throw initialize_error("could not initialize window");
}
audio::initialize(true);
auto resdir = paths->getResources();
scripting::initialize(this);
@ -66,7 +67,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths)
resPaths = std::make_unique<ResPaths>(resdir, roots);
assets = std::make_unique<Assets>();
AssetsLoader loader(assets.get(), resPaths.get());
AssetsLoader::addDefaults(loader, nullptr);
@ -79,8 +79,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths)
throw initialize_error("could not to load assets");
}
}
Audio::initialize();
gui = std::make_unique<gui::GUI>();
if (settings.ui.language == "auto") {
settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources());
@ -148,7 +146,7 @@ Engine::~Engine() {
screen.reset();
content.reset();
Audio::finalize();
audio::close();
assets.reset();
scripting::close();
Window::terminate();