Merge pull request #167 from MihailRis/audio

Audio (WIP)
This commit is contained in:
MihailRis 2024-03-03 15:31:54 +03:00 committed by GitHub
commit 90a4922201
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2006 additions and 560 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev cmake squashfs-tools
sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev cmake squashfs-tools
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
- name: configure

View File

@ -35,7 +35,7 @@ jobs:
# make && make install INSTALL_INC=/usr/include/lua
run: |
sudo apt-get update
sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev
sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev
sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua

View File

@ -89,10 +89,12 @@ if (WIN32)
find_package(glfw3 REQUIRED)
find_package(spng REQUIRED)
find_package(glm REQUIRED)
find_package(vorbis REQUIRED)
set(PNGLIB spng::spng)
else()
find_package(Lua REQUIRED)
set(PNGLIB spng)
set(VORBISLIB vorbis vorbisfile) # not tested
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw)
endif()
else()
@ -100,6 +102,7 @@ else()
find_package(Lua REQUIRED)
find_package(PNG REQUIRED)
set(PNGLIB PNG::PNG)
set(VORBISLIB vorbis vorbisfile)
endif()
if (APPLE)
@ -118,7 +121,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
endif()
include_directories(${LUA_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS})
target_link_libraries(${PROJECT_NAME} ${LIBS} glfw OpenGL::GL ${OPENAL_LIBRARY} GLEW::GLEW ZLIB::ZLIB ${VORBISLIB} ${PNGLIB} ${LUA_LIBRARIES} ${CMAKE_DL_LIBS})
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/res DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@ -21,6 +21,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
libpng-dev \
libopenal-dev \
libluajit-5.1-dev \
libvorbis-dev \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

View File

@ -33,7 +33,7 @@ cmake --build .
#### Debian-based distro:
```sh
sudo apt install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev
sudo apt install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev
```
CMake missing LUA_INCLUDE_DIR and LUA_LIBRARIES fix:
@ -44,7 +44,7 @@ sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
#### RHEL-based distro:
```sh
sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel openal-devel
sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel libvorbis-devel openal-devel
```
\+ install LuaJIT
@ -52,12 +52,12 @@ sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel openal-devel
#### Arch-based distro:
If you use X11
```sh
sudo pacman -S glfw-x11 glew glm libpng openal
sudo pacman -S glfw-x11 glew glm libpng libvorbis openal
```
If you use Wayland
```sh
sudo pacman -S glfw-wayland glew glm libpng openal
sudo pacman -S glfw-wayland glew glm libpng libvorbis openal
```
\+ install LuaJIT
@ -72,7 +72,7 @@ make && sudo make install INSTALL_INC=/usr/include/lua
#### macOS:
```
brew install glfw3 glew glm libpng lua luajit openal-soft
brew install glfw3 glew glm libpng libvorbis lua luajit openal-soft
```
If homebrew for some reason could not install the necessary packages: ```lua luajit openal-soft```, then download, install and compile them manually (Lua, LuaJIT and OpenAL).

View File

@ -29,6 +29,9 @@ AppDir:
- libopengl0
- libasound2
- libglx0
- libogg0
- libvorbis0a
- libvorbisfile3
exclude:
- hicolor-icon-theme
- sound-theme-freedesktop

View File

@ -1,5 +1,6 @@
#include "Assets.h"
#include "../audio/audio.h"
#include "../graphics/Texture.h"
#include "../graphics/Shader.h"
#include "../graphics/Atlas.h"
@ -18,7 +19,7 @@ Texture* Assets::getTexture(std::string name) const {
}
void Assets::store(Texture* texture, std::string name){
textures[name].reset(texture);
textures.emplace(name, texture);
}
@ -30,7 +31,7 @@ Shader* Assets::getShader(std::string name) const{
}
void Assets::store(Shader* shader, std::string name){
shaders[name].reset(shader);
shaders.emplace(name, shader);
}
@ -42,7 +43,7 @@ Font* Assets::getFont(std::string name) const {
}
void Assets::store(Font* font, std::string name){
fonts[name].reset(font);
fonts.emplace(name, font);
}
Atlas* Assets::getAtlas(std::string name) const {
@ -53,7 +54,18 @@ Atlas* Assets::getAtlas(std::string name) const {
}
void Assets::store(Atlas* atlas, std::string name){
atlases[name].reset(atlas);
atlases.emplace(name, atlas);
}
audio::Sound* Assets::getSound(std::string name) const {
auto found = sounds.find(name);
if (found == sounds.end())
return nullptr;
return found->second.get();
}
void Assets::store(audio::Sound* sound, std::string name) {
sounds.emplace(name, sound);
}
const std::vector<TextureAnimation>& Assets::getAnimations() {
@ -72,7 +84,7 @@ UiDocument* Assets::getLayout(std::string name) const {
}
void Assets::store(UiDocument* layout, std::string name) {
layouts[name].reset(layout);
layouts.emplace(name, layout);
}
void Assets::extend(const Assets& assets) {
@ -91,6 +103,9 @@ void Assets::extend(const Assets& assets) {
for (auto entry : assets.layouts) {
layouts[entry.first] = entry.second;
}
for (auto entry : assets.sounds) {
sounds[entry.first] = entry.second;
}
animations.clear();
for (auto entry : assets.animations) {
animations.emplace_back(entry);

View File

@ -14,11 +14,9 @@ class Font;
class Atlas;
class UiDocument;
struct LayoutCfg {
int env;
LayoutCfg(int env) : env(env) {}
};
namespace audio {
class Sound;
}
class Assets {
std::unordered_map<std::string, std::shared_ptr<Texture>> textures;
@ -26,6 +24,7 @@ class Assets {
std::unordered_map<std::string, std::shared_ptr<Font>> fonts;
std::unordered_map<std::string, std::shared_ptr<Atlas>> atlases;
std::unordered_map<std::string, std::shared_ptr<UiDocument>> layouts;
std::unordered_map<std::string, std::shared_ptr<audio::Sound>> sounds;
std::vector<TextureAnimation> animations;
public:
~Assets();
@ -41,6 +40,9 @@ public:
Atlas* getAtlas(std::string name) const;
void store(Atlas* atlas, std::string name);
audio::Sound* getSound(std::string name) const;
void store(audio::Sound* sound, std::string name);
const std::vector<TextureAnimation>& getAnimations();
void store(const TextureAnimation& animation);

View File

@ -24,7 +24,7 @@ void AssetsLoader::addLoader(int tag, aloader_func func) {
loaders[tag] = func;
}
void AssetsLoader::add(int tag, const std::string filename, const std::string alias, std::shared_ptr<void> settings) {
void AssetsLoader::add(int tag, const std::string filename, const std::string alias, std::shared_ptr<AssetCfg> settings) {
entries.push(aloader_entry{ tag, filename, alias, settings});
}

View File

@ -7,24 +7,41 @@
#include <map>
#include <queue>
const short ASSET_TEXTURE = 1;
const short ASSET_SHADER = 2;
const short ASSET_FONT = 3;
const short ASSET_ATLAS = 4;
const short ASSET_LAYOUT = 5;
inline constexpr short ASSET_TEXTURE = 1;
inline constexpr short ASSET_SHADER = 2;
inline constexpr short ASSET_FONT = 3;
inline constexpr short ASSET_ATLAS = 4;
inline constexpr short ASSET_LAYOUT = 5;
inline constexpr short ASSET_SOUND = 6;
class ResPaths;
class Assets;
class AssetsLoader;
class Content;
using aloader_func = std::function<bool(AssetsLoader&, Assets*, const ResPaths*, const std::string&, const std::string&, std::shared_ptr<void>)>;
struct AssetCfg {
virtual ~AssetCfg() {}
};
struct LayoutCfg : AssetCfg {
int env;
LayoutCfg(int env) : env(env) {}
};
struct SoundCfg : AssetCfg {
bool keepPCM;
SoundCfg(bool keepPCM) : keepPCM(keepPCM) {}
};
using aloader_func = std::function<bool(AssetsLoader&, Assets*, const ResPaths*, const std::string&, const std::string&, std::shared_ptr<AssetCfg>)>;
struct aloader_entry {
int tag;
const std::string filename;
const std::string alias;
std::shared_ptr<void> config;
std::shared_ptr<AssetCfg> config;
};
class AssetsLoader {
@ -39,7 +56,7 @@ public:
int tag,
const std::string filename,
const std::string alias,
std::shared_ptr<void> settings=nullptr
std::shared_ptr<AssetCfg> settings=nullptr
);

View File

@ -4,6 +4,7 @@
#include <filesystem>
#include "Assets.h"
#include "AssetsLoader.h"
#include "../audio/audio.h"
#include "../files/files.h"
#include "../files/engine_paths.h"
#include "../coders/png.h"
@ -25,7 +26,7 @@ bool assetload::texture(
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void>
std::shared_ptr<AssetCfg>
) {
std::unique_ptr<Texture> texture(
png::load_texture(paths->find(filename).u8string())
@ -44,7 +45,7 @@ bool assetload::shader(
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void>
std::shared_ptr<AssetCfg>
) {
fs::path vertexFile = paths->find(filename+".glslv");
fs::path fragmentFile = paths->find(filename+".glslf");
@ -92,7 +93,7 @@ bool assetload::atlas(
const ResPaths* paths,
const std::string directory,
const std::string name,
std::shared_ptr<void>
std::shared_ptr<AssetCfg>
) {
AtlasBuilder builder;
for (const auto& file : paths->listdir(directory)) {
@ -112,7 +113,7 @@ bool assetload::font(
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void>
std::shared_ptr<AssetCfg>
) {
std::vector<std::unique_ptr<Texture>> pages;
for (size_t i = 0; i <= 4; i++) {
@ -138,10 +139,10 @@ bool assetload::layout(
const ResPaths* paths,
const std::string file,
const std::string name,
std::shared_ptr<void> config
std::shared_ptr<AssetCfg> config
) {
try {
LayoutCfg* cfg = reinterpret_cast<LayoutCfg*>(config.get());
auto cfg = dynamic_cast<LayoutCfg*>(config.get());
auto document = UiDocument::read(loader, cfg->env, name, file);
assets->store(document.release(), name);
return true;
@ -152,6 +153,25 @@ bool assetload::layout(
}
}
bool assetload::sound(
AssetsLoader& loader,
Assets* assets,
const ResPaths* paths,
const std::string file,
const std::string name,
std::shared_ptr<AssetCfg> config
) {
auto cfg = dynamic_cast<SoundCfg*>(config.get());
auto sound = audio::loadSound(paths->find(file), cfg->keepPCM);
if (sound == nullptr) {
std::cerr << "failed to load sound '" << name << "' from '";
std::cerr << file << "'" << std::endl;
return false;
}
assets->store(sound, name);
return true;
}
bool assetload::animation(Assets* assets,
const ResPaths* paths,
const std::string directory,

View File

@ -8,6 +8,7 @@ class ResPaths;
class Assets;
class AssetsLoader;
class Atlas;
struct AssetCfg;
namespace assetload {
bool texture(
@ -16,7 +17,7 @@ namespace assetload {
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void> settings
std::shared_ptr<AssetCfg> settings
);
bool shader(
AssetsLoader&,
@ -24,7 +25,7 @@ namespace assetload {
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void> settings
std::shared_ptr<AssetCfg> settings
);
bool atlas(
AssetsLoader&,
@ -32,7 +33,7 @@ namespace assetload {
const ResPaths* paths,
const std::string directory,
const std::string name,
std::shared_ptr<void> settings
std::shared_ptr<AssetCfg> settings
);
bool font(
AssetsLoader&,
@ -40,7 +41,7 @@ namespace assetload {
const ResPaths* paths,
const std::string filename,
const std::string name,
std::shared_ptr<void> settings
std::shared_ptr<AssetCfg> settings
);
bool layout(
AssetsLoader&,
@ -48,7 +49,16 @@ namespace assetload {
const ResPaths* paths,
const std::string file,
const std::string name,
std::shared_ptr<void> settings
std::shared_ptr<AssetCfg> settings
);
bool sound(
AssetsLoader&,
Assets*,
const ResPaths* paths,
const std::string file,
const std::string name,
std::shared_ptr<AssetCfg> settings
);
bool animation(

365
src/audio/AL/ALAudio.cpp Normal file
View File

@ -0,0 +1,365 @@
#include "ALAudio.h"
#include "alutil.h"
#include <string>
#include <iostream>
using namespace audio;
ALSound::ALSound(ALAudio* al, uint buffer, std::shared_ptr<PCM> pcm, bool keepPCM)
: al(al), buffer(buffer)
{
duration = pcm->getDuration();
if (keepPCM) {
this->pcm = pcm;
}
}
ALSound::~ALSound() {
al->freeBuffer(buffer);
buffer = 0;
}
Speaker* ALSound::newInstance(int priority) const {
uint source = al->getFreeSource();
if (source == 0) {
return nullptr;
}
AL_CHECK(alSourcei(source, AL_BUFFER, buffer));
return new ALSpeaker(al, source, priority);
}
ALStream::ALStream(ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource)
: al(al), source(source), keepSource(keepSource) {
}
ALStream::~ALStream() {
bindSpeaker(0);
source = nullptr;
}
std::shared_ptr<PCMStream> ALStream::getSource() const {
if (keepSource) {
return source;
} else {
return nullptr;
}
}
bool ALStream::preloadBuffer(uint buffer, bool loop) {
size_t read = source->readFully(this->buffer, BUFFER_SIZE, loop);
if (!read)
return false;
ALenum format = AL::to_al_format(source->getChannels(), source->getBitsPerSample());
AL_CHECK(alBufferData(buffer, format, this->buffer, read, source->getSampleRate()));
return true;
}
Speaker* ALStream::createSpeaker(bool loop) {
this->loop = loop;
uint source = al->getFreeSource();
if (source == 0) {
return nullptr;
}
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
uint buffer = al->getFreeBuffer();
if (!preloadBuffer(buffer, loop)) {
break;
}
AL_CHECK(alSourceQueueBuffers(source, 1, &buffer));
}
return new ALSpeaker(al, source, PRIORITY_HIGH);
}
void ALStream::bindSpeaker(speakerid_t speaker) {
auto sp = audio::get(this->speaker);
if (sp) {
sp->stop();
}
this->speaker = speaker;
}
speakerid_t ALStream::getSpeaker() const {
return speaker;
}
void ALStream::update(double delta) {
if (this->speaker == 0) {
return;
}
Speaker* speaker = audio::get(this->speaker);
if (speaker == nullptr) {
speaker = 0;
return;
}
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
uint source = alspeaker->source;
uint processed = AL::getSourcei(source, AL_BUFFERS_PROCESSED);
while (processed--) {
uint buffer;
AL_CHECK(alSourceUnqueueBuffers(source, 1, &buffer));
unusedBuffers.push(buffer);
}
uint preloaded = 0;
if (!unusedBuffers.empty()) {
uint buffer = unusedBuffers.front();
if (preloadBuffer(buffer, loop)) {
preloaded++;
unusedBuffers.pop();
AL_CHECK(alSourceQueueBuffers(source, 1, &buffer));
}
}
if (speaker->isStopped() && !speaker->isStoppedManually()) {
if (preloaded) {
speaker->play();
} else {
speaker->stop();
}
}
}
void ALStream::setTime(duration_t time) {
// TODO: implement
}
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority) : al(al), priority(priority), source(source) {
}
ALSpeaker::~ALSpeaker() {
if (source) {
stop();
}
}
State ALSpeaker::getState() const {
int state = AL::getSourcei(source, AL_SOURCE_STATE, AL_STOPPED);
switch (state) {
case AL_PLAYING: return State::playing;
case AL_PAUSED: return State::paused;
default: return State::stopped;
}
}
float ALSpeaker::getVolume() const {
return AL::getSourcef(source, AL_GAIN);
}
void ALSpeaker::setVolume(float volume) {
AL_CHECK(alSourcef(source, AL_GAIN, volume));
}
float ALSpeaker::getPitch() const {
return AL::getSourcef(source, AL_PITCH);
}
void ALSpeaker::setPitch(float pitch) {
AL_CHECK(alSourcef(source, AL_PITCH, pitch));
}
bool ALSpeaker::isLoop() const {
return AL::getSourcei(source, AL_LOOPING) == AL_TRUE;
}
void ALSpeaker::setLoop(bool loop) {
AL_CHECK(alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE));
}
void ALSpeaker::play() {
stoppedManually = false;
AL_CHECK(alSourcePlay(source));
}
void ALSpeaker::pause() {
AL_CHECK(alSourcePause(source));
}
void ALSpeaker::stop() {
stoppedManually = true;
if (source) {
AL_CHECK(alSourceStop(source));
al->freeSource(source);
source = 0;
}
}
bool ALSpeaker::isStoppedManually() const {
return stoppedManually;
}
duration_t ALSpeaker::getTime() const {
return static_cast<duration_t>(AL::getSourcef(source, AL_SEC_OFFSET));
}
void ALSpeaker::setTime(duration_t time) {
AL_CHECK(alSourcef(source, AL_SEC_OFFSET, static_cast<float>(time)));
}
void ALSpeaker::setPosition(glm::vec3 pos) {
AL_CHECK(alSource3f(source, AL_POSITION, pos.x, pos.y, pos.z));
}
glm::vec3 ALSpeaker::getPosition() const {
return AL::getSource3f(source, AL_POSITION);
}
void ALSpeaker::setVelocity(glm::vec3 vel) {
AL_CHECK(alSource3f(source, AL_VELOCITY, vel.x, vel.y, vel.z));
}
glm::vec3 ALSpeaker::getVelocity() const {
return AL::getSource3f(source, AL_VELOCITY);
}
void ALSpeaker::setRelative(bool relative) {
AL_CHECK(alSourcei(source, AL_SOURCE_RELATIVE, relative ? AL_TRUE : AL_FALSE));
}
bool ALSpeaker::isRelative() const {
return AL::getSourcei(source, AL_SOURCE_RELATIVE) == AL_TRUE;
}
int ALSpeaker::getPriority() const {
return priority;
}
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 (uint source : allsources) {
int state = AL::getSourcei(source, AL_SOURCE_STATE);
if (state == AL_PLAYING || state == AL_PAUSED) {
AL_CHECK(alSourceStop(source));
}
AL_CHECK(alDeleteSources(1, &source));
}
for (uint buffer : allbuffers){
AL_CHECK(alDeleteBuffers(1, &buffer));
}
AL_CHECK(alcMakeContextCurrent(context));
alcDestroyContext(context);
if (!alcCloseDevice(device)) {
std::cerr << "AL: device not closed!" << std::endl;
}
device = nullptr;
context = nullptr;
}
Sound* ALAudio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
auto format = AL::to_al_format(pcm->channels, pcm->bitsPerSample);
uint buffer = getFreeBuffer();
AL_CHECK(alBufferData(buffer, format, pcm->data.data(), pcm->data.size(), pcm->sampleRate));
return new ALSound(this, buffer, pcm, keepPCM);
}
Stream* ALAudio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
return new ALStream(this, stream, keepSource);
}
ALAudio* ALAudio::create() {
ALCdevice* device = alcOpenDevice(nullptr);
if (device == nullptr)
return nullptr;
ALCcontext* context = alcCreateContext(device, nullptr);
if (!alcMakeContextCurrent(context)){
alcCloseDevice(device);
return nullptr;
}
AL_CHECK();
std::cout << "AL: initialized" << std::endl;
return new ALAudio(device, context);
}
uint ALAudio::getFreeSource(){
if (!freesources.empty()){
uint 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 0;
}
ALuint id;
alGenSources(1, &id);
if (!AL_GET_ERROR())
return 0;
allsources.push_back(id);
return id;
}
uint ALAudio::getFreeBuffer(){
if (!freebuffers.empty()){
uint buffer = freebuffers.back();
freebuffers.pop_back();
return buffer;
}
ALuint id;
alGenBuffers(1, &id);
if (!AL_GET_ERROR()) {
return 0;
}
allbuffers.push_back(id);
return id;
}
void ALAudio::freeSource(uint source){
freesources.push_back(source);
}
void ALAudio::freeBuffer(uint 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 (!AL_GET_ERROR()) {
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 };
AL_CHECK(alListener3f(AL_POSITION, position.x, position.y, position.z));
AL_CHECK(alListener3f(AL_VELOCITY, velocity.x, velocity.y, velocity.z));
AL_CHECK(alListenerfv(AL_ORIENTATION, listenerOri));
}
void ALAudio::update(double delta) {
}

157
src/audio/AL/ALAudio.h Normal file
View File

@ -0,0 +1,157 @@
#ifndef SRC_AUDIO_AUDIO_H_
#define SRC_AUDIO_AUDIO_H_
#include <queue>
#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <unordered_map>
#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;
class ALAudio;
class PCMStream;
class ALSound : public Sound {
ALAudio* al;
uint buffer;
std::shared_ptr<PCM> pcm;
duration_t duration;
public:
ALSound(ALAudio* al, uint buffer, std::shared_ptr<PCM> pcm, bool keepPCM);
~ALSound();
duration_t getDuration() const override {
return duration;
}
std::shared_ptr<PCM> getPCM() const override {
return pcm;
}
Speaker* newInstance(int priority) const override;
};
class ALStream : public Stream {
static inline constexpr size_t BUFFER_SIZE = 44100;
ALAudio* al;
std::shared_ptr<PCMStream> source;
std::queue<uint> unusedBuffers;
speakerid_t speaker = 0;
bool keepSource;
char buffer[BUFFER_SIZE];
bool loop = false;
bool preloadBuffer(uint buffer, bool loop);
public:
ALStream(ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource);
~ALStream();
std::shared_ptr<PCMStream> getSource() const override;
void bindSpeaker(speakerid_t speaker) override;
Speaker* createSpeaker(bool loop) override;
speakerid_t getSpeaker() const override;
void update(double delta) override;
void setTime(duration_t time) override;
static inline constexpr uint STREAM_BUFFERS = 3;
};
/// @brief AL source adapter
class ALSpeaker : public Speaker {
ALAudio* al;
int priority;
bool stoppedManually = false;
public:
uint source;
ALSpeaker(ALAudio* al, uint source, int priority);
~ALSpeaker();
State getState() const override;
float getVolume() const override;
void setVolume(float volume) override;
float getPitch() const override;
void setPitch(float pitch) override;
bool isLoop() const override;
void setLoop(bool loop) override;
void play() override;
void pause() override;
void stop() override;
bool isStoppedManually() const override;
duration_t getTime() const override;
void setTime(duration_t time) override;
void setPosition(glm::vec3 pos) override;
glm::vec3 getPosition() const override;
void setVelocity(glm::vec3 vel) override;
glm::vec3 getVelocity() const override;
void setRelative(bool relative) override;
bool isRelative() const override;
int getPriority() const override;
};
class ALAudio : public Backend {
ALCdevice* device;
ALCcontext* context;
std::vector<uint> allsources;
std::vector<uint> freesources;
std::vector<uint> allbuffers;
std::vector<uint> freebuffers;
uint maxSources;
ALAudio(ALCdevice* device, ALCcontext* context);
public:
~ALAudio();
uint getFreeSource();
uint getFreeBuffer();
void freeSource(uint source);
void freeBuffer(uint buffer);
std::vector<std::string> getAvailableDevices() const;
Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource) override;
void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) override;
void update(double delta) override;
bool isDummy() const override {
return false;
}
static ALAudio* create();
};
}
#endif /* SRC_AUDIO_AUDIO_H_ */

44
src/audio/AL/alutil.cpp Normal file
View File

@ -0,0 +1,44 @@
#include "alutil.h"
#include <iostream>
#include <fstream>
#include <cstring>
#include <memory>
#include <type_traits>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
bool AL::check_errors(const std::string& filename, const std::uint_fast32_t line){
ALenum error = alGetError();
if(error != AL_NO_ERROR){
std::cerr << "OpenAL ERROR (" << filename << ": " << line << ")\n" ;
switch(error){
case AL_INVALID_NAME:
std::cerr << "AL_INVALID_NAME: a bad name (ID) was passed to an OpenAL function";
break;
case AL_INVALID_ENUM:
std::cerr << "AL_INVALID_ENUM: an invalid enum value was passed to an OpenAL function";
break;
case AL_INVALID_VALUE:
std::cerr << "AL_INVALID_VALUE: an invalid value was passed to an OpenAL function";
break;
case AL_INVALID_OPERATION:
std::cerr << "AL_INVALID_OPERATION: the requested operation is not valid";
break;
case AL_OUT_OF_MEMORY:
std::cerr << "AL_OUT_OF_MEMORY: the requested operation resulted in OpenAL running out of memory";
break;
default:
std::cerr << "UNKNOWN AL ERROR: " << error;
}
std::cerr << std::endl;
return false;
}
return true;
}

76
src/audio/AL/alutil.h Normal file
View File

@ -0,0 +1,76 @@
#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
#include <glm/glm.hpp>
#include "../../typedefs.h"
#define AL_CHECK(STATEMENT) STATEMENT; AL::check_errors(__FILE__, __LINE__)
#define AL_GET_ERROR() AL::check_errors(__FILE__, __LINE__)
namespace AL {
bool check_errors(const std::string& filename, const std::uint_fast32_t line);
/// @brief alGetSourcef wrapper
/// @param source target source
/// @param field enum value
/// @param def default value will be returned in case of error
/// @return field value or default
inline float getSourcef(uint source, ALenum field, float def=0.0f) {
float value = def;
AL_CHECK(alGetSourcef(source, field, &value));
return value;
}
/// @brief alGetSource3f wrapper
/// @param source target source
/// @param field enum value
/// @param def default value will be returned in case of error
/// @return field value or default
inline glm::vec3 getSource3f(uint source, ALenum field, glm::vec3 def={}) {
glm::vec3 value = def;
AL_CHECK(alGetSource3f(source, field, &value.x, &value.y, &value.z));
return value;
}
/// @brief alGetSourcei wrapper
/// @param source target source
/// @param field enum value
/// @param def default value will be returned in case of error
/// @return field value or default
inline float getSourcei(uint source, ALenum field, int def=0) {
int value = def;
AL_CHECK(alGetSourcei(source, field, &value));
return value;
}
static inline ALenum to_al_format(short channels, short bitsPerSample){
bool stereo = (channels > 1);
switch (bitsPerSample) {
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

@ -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_ */

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

@ -0,0 +1,22 @@
#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);
}
Stream* NoAudio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
return new NoStream(stream, keepSource);
}
NoAudio* NoAudio::create() {
return new NoAudio();
}

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

@ -0,0 +1,84 @@
#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;
}
Speaker* newInstance(int priority) const override {
return nullptr;
}
};
class NoStream : public Stream {
std::shared_ptr<PCMStream> source;
duration_t duration;
public:
NoStream(std::shared_ptr<PCMStream> source, bool keepSource) {
duration = source->getTotalDuration();
if (keepSource) {
this->source = source;
}
}
std::shared_ptr<PCMStream> getSource() const override {
return source;
}
void bindSpeaker(speakerid_t speaker) override {
}
Speaker* createSpeaker(bool loop) override{
return nullptr;
}
speakerid_t getSpeaker() const override {
return 0;
}
void update(double delta) override {
}
void setTime(duration_t time) override {
}
};
class NoAudio : public Backend {
public:
~NoAudio() {}
Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) override;
Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource) override;
void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 at,
glm::vec3 up
) override {}
void update(double delta) override {}
bool isDummy() const override {
return true;
}
static NoAudio* create();
};
}
#endif // AUDIO_NOAUDIO_H_

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

@ -0,0 +1,310 @@
#include "audio.h"
#include <iostream>
#include <stdexcept>
#include "NoAudio.h"
#include "AL/ALAudio.h"
#include "../coders/wav.h"
#include "../coders/ogg.h"
namespace audio {
static speakerid_t nextId = 1;
static Backend* backend;
static std::unordered_map<speakerid_t, std::unique_ptr<Speaker>> speakers;
static std::unordered_map<speakerid_t, std::shared_ptr<Stream>> streams;
}
using namespace audio;
size_t PCMStream::readFully(char* buffer, size_t bufferSize, bool loop) {
if (!isOpen()) {
return 0;
}
long bytes = 0;
size_t size = 0;
do {
do {
bytes = read(buffer, bufferSize);
if (bytes < 0) {
return size;
}
size += bytes;
bufferSize -= bytes;
buffer += bytes;
} while (bytes > 0 && bufferSize > 0);
if (bufferSize == 0) {
break;
}
if (loop) {
seek(0);
}
if (bufferSize == 0) {
return size;
}
} while (loop);
return size;
}
/// @brief pcm source that does not initialize buffer
class PCMVoidSource : public PCMStream {
size_t totalSamples;
size_t remain;
uint sampleRate;
bool seekable;
bool closed = false;
public:
PCMVoidSource(size_t totalSamples, uint sampleRate, bool seekable)
: totalSamples(totalSamples),
remain(totalSamples),
sampleRate(sampleRate),
seekable(seekable)
{}
size_t read(char* buffer, size_t bufferSize) override {
if (closed) {
return 0;
}
if (!seekable) {
return bufferSize;
}
size_t n = std::min(bufferSize, totalSamples);
remain -= n;
return n;
}
void close() override {
closed = true;
}
bool isOpen() const override {
return !closed;
}
size_t getTotalSamples() const override {
return totalSamples;
}
duration_t getTotalDuration() const override {
return static_cast<duration_t>(totalSamples) /
static_cast<duration_t>(sampleRate);
}
uint getChannels() const override {
return 1;
}
uint getSampleRate() const override {
return sampleRate;
}
uint getBitsPerSample() const override {
return 8;
}
bool isSeekable() const override {
return seekable;
}
void seek(size_t position) override {
if (closed || !seekable) {
return;
}
position %= totalSamples;
remain = totalSamples - position;
}
};
void audio::initialize(bool enabled) {
if (enabled) {
backend = ALAudio::create();
}
if (backend == nullptr) {
std::cerr << "could not to initialize audio" << std::endl;
backend = NoAudio::create();
}
}
PCM* audio::loadPCM(const fs::path& file, bool headerOnly) {
if (!fs::exists(file)) {
throw std::runtime_error("file not found '"+file.u8string()+"'");
}
std::string ext = file.extension().u8string();
if (ext == ".wav" || ext == ".WAV") {
return wav::load_pcm(file, headerOnly);
} else if (ext == ".ogg" || ext == ".OGG") {
return ogg::load_pcm(file, headerOnly);
}
throw std::runtime_error("unsupported audio format");
}
Sound* audio::loadSound(const fs::path& file, bool keepPCM) {
std::shared_ptr<PCM> pcm(loadPCM(file, !keepPCM && backend->isDummy()));
return createSound(pcm, keepPCM);
}
Sound* audio::createSound(std::shared_ptr<PCM> pcm, bool keepPCM) {
return backend->createSound(pcm, keepPCM);
}
PCMStream* audio::openPCMStream(const fs::path& file) {
std::string ext = file.extension().u8string();
if (ext == ".wav" || ext == ".WAV") {
return wav::create_stream(file);
} else if (ext == ".ogg" || ext == ".OGG") {
return ogg::create_stream(file);
}
throw std::runtime_error("unsupported audio stream format");
}
Stream* audio::openStream(const fs::path& file, bool keepSource) {
if (!keepSource && backend->isDummy()) {
auto header = loadPCM(file, true);
// using void source sized as audio instead of actual audio file
return openStream(
std::make_shared<PCMVoidSource>(header->totalSamples, header->sampleRate, header->seekable),
keepSource
);
}
return openStream(
std::shared_ptr<PCMStream>(openPCMStream(file)),
keepSource
);
}
Stream* audio::openStream(std::shared_ptr<PCMStream> stream, bool keepSource) {
return backend->openStream(stream, keepSource);
}
void audio::setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) {
backend->setListener(position, velocity, lookAt, up);
}
void remove_lower_priority_speaker(int priority) {
for (auto it = speakers.begin(); it != speakers.end();) {
if (it->second->getPriority() < priority && it->second->isPaused()) {
it->second->stop();
it = speakers.erase(it);
return;
}
it++;
}
for (auto it = speakers.begin(); it != speakers.end();) {
if (it->second->getPriority() < priority) {
it->second->stop();
it = speakers.erase(it);
return;
}
it++;
}
}
speakerid_t audio::play(
Sound* sound,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int priority
) {
Speaker* speaker = sound->newInstance(priority);
if (speaker == nullptr) {
remove_lower_priority_speaker(priority);
speaker = sound->newInstance(priority);
}
if (speaker == nullptr) {
return 0;
}
speakerid_t id = nextId++;
speakers.emplace(id, speaker);
speaker->setPosition(position);
speaker->setVolume(volume);
speaker->setPitch(pitch);
speaker->setLoop(loop);
speaker->setRelative(relative);
speaker->play();
return id;
}
speakerid_t audio::play(
std::shared_ptr<Stream> stream,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop
) {
Speaker* speaker = stream->createSpeaker(loop);
if (speaker == nullptr) {
remove_lower_priority_speaker(PRIORITY_HIGH);
speaker = stream->createSpeaker(loop);
}
if (speaker == nullptr) {
return 0;
}
speakerid_t id = nextId++;
streams.emplace(id, stream);
speakers.emplace(id, speaker);
stream->bindSpeaker(id);
speaker->setPosition(position);
speaker->setVolume(volume);
speaker->setPitch(pitch);
speaker->setLoop(false);
speaker->setRelative(relative);
speaker->play();
return id;
}
speakerid_t audio::playStream(
const fs::path& file,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop
) {
std::shared_ptr<Stream> stream (openStream(file, false));
return play(stream, position, relative, volume, pitch, loop);
}
Speaker* audio::get(speakerid_t id) {
auto found = speakers.find(id);
if (found == speakers.end()) {
return nullptr;
}
return found->second.get();
}
void audio::update(double delta) {
backend->update(delta);
for (auto& entry : streams) {
entry.second->update(delta);
}
for (auto it = speakers.begin(); it != speakers.end();) {
if (it->second->isStoppedManually()) {
streams.erase(it->first);
it = speakers.erase(it);
} else {
it++;
}
}
}
void audio::close() {
speakers.clear();
delete backend;
backend = nullptr;
}

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

@ -0,0 +1,404 @@
#ifndef AUDIO_AUDIO_H_
#define AUDIO_AUDIO_H_
#include <vector>
#include <memory>
#include <filesystem>
#include <glm/glm.hpp>
#include "../typedefs.h"
namespace fs = std::filesystem;
namespace audio {
using speakerid_t = int64_t;
/// @brief duration unit is second
using duration_t = double;
constexpr inline int PRIORITY_LOW = 0;
constexpr inline int PRIORITY_NORMAL = 5;
constexpr inline int PRIORITY_HIGH = 10;
class Speaker;
/// @brief Audio speaker states
enum class State {
playing,
paused,
stopped
};
/// @brief Pulse-code modulation data
struct PCM {
/// @brief May contain 8 bit and 16 bit PCM data
std::vector<char> data;
size_t totalSamples;
uint8_t channels;
uint8_t bitsPerSample;
uint sampleRate;
bool seekable;
PCM(
std::vector<char> data,
size_t totalSamples,
uint8_t channels,
uint8_t bitsPerSample,
uint sampleRate,
bool seekable
) : data(std::move(data)),
totalSamples(totalSamples),
channels(channels),
bitsPerSample(bitsPerSample),
sampleRate(sampleRate),
seekable(seekable) {}
inline size_t countSamplesMono() const {
return totalSamples / channels;
}
inline duration_t getDuration() const {
return static_cast<duration_t>(countSamplesMono()) /
static_cast<duration_t>(sampleRate);
}
};
/// @brief audio::PCMStream is a data source for audio::Stream
class PCMStream {
public:
virtual ~PCMStream() {};
/// @brief Read samples data to buffer
/// @param buffer destination buffer
/// @param bufferSize destination buffer size
/// @param loop loop stream (seek to start when end reached)
/// @return size of data received
/// (always equals bufferSize if seekable and looped)
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
virtual size_t read(char* buffer, size_t bufferSize) = 0;
/// @brief Close stream
virtual void close()=0;
/// @brief Check if stream is open
virtual bool isOpen() const=0;
/// @brief Get total samples number if seekable or 0
virtual size_t getTotalSamples() const=0;
/// @brief Get total audio track duration if seekable or 0.0
virtual duration_t getTotalDuration() const=0;
/// @brief Get number of audio channels
/// @return 1 if mono, 2 if stereo
virtual uint getChannels() const=0;
/// @brief Get audio sampling frequency
/// @return number of mono samples per second
virtual uint getSampleRate() const=0;
/// @brief Get number of bits per mono sample
/// @return 8 or 16
virtual uint getBitsPerSample() const=0;
/// @brief Check if the stream does support seek feature
virtual bool isSeekable() const=0;
/// @brief Move playhead to the selected sample number
/// @param position selected sample number
virtual void seek(size_t position) = 0;
};
/// @brief Audio streaming interface
class Stream {
public:
virtual ~Stream() {};
/// @brief Get pcm data source
/// @return PCM stream or nullptr if audio::openStream
/// keepSource argument is set to false
virtual std::shared_ptr<PCMStream> getSource() const = 0;
/// @brief Create new speaker bound to the Stream
/// and having high priority
/// @param loop is stream looped (required for correct buffers preload)
/// @return speaker id or 0
virtual Speaker* createSpeaker(bool loop) = 0;
/// @brief Unbind previous speaker and bind new speaker to the stream
/// @param speaker speaker id or 0 if all you need is unbind speaker
virtual void bindSpeaker(speakerid_t speaker) = 0;
/// @brief Get id of the bound speaker
/// @return speaker id or 0 if no speaker bound
virtual speakerid_t getSpeaker() const = 0;
/// @brief Update stream state (preload samples if needed)
/// @param delta time elapsed since the last update
virtual void update(double delta) = 0;
/// @brief Set playhead to the selected time
/// @param time selected time
virtual void setTime(duration_t time) = 0;
};
/// @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 with sound bound or nullptr
/// if all speakers are in use
virtual Speaker* newInstance(int priority) const = 0;
};
/// @brief Audio source controller interface
class Speaker {
public:
virtual ~Speaker() {}
/// @brief Get current speaker state
/// @return speaker state
virtual State getState() const = 0;
/// @brief Get speaker audio gain
/// @return speaker audio gain value
virtual float getVolume() const = 0;
/// @brief Set speaker audio gain (must be positive)
/// @param volume new gain value
virtual void setVolume(float volume) = 0;
/// @brief Get speaker pitch multiplier
/// @return pitch multiplier
virtual float getPitch() const = 0;
/// @brief Set speaker pitch multiplier
/// @param pitch new pitch multiplier (must be positive)
virtual void setPitch(float pitch) = 0;
/// @brief Check if speaker audio is in loop
/// @return true if audio is in loop
virtual bool isLoop() const = 0;
/// @brief Enable/disable audio loop
/// @param loop loop mode
virtual void setLoop(bool loop) = 0;
/// @brief Play, replay or resume audio
virtual void play() = 0;
/// @brief Pause playing audio and keep speaker alive
virtual void pause() = 0;
/// @brief Stop and destroy speaker
virtual void stop() = 0;
/// @brief Check if the speaker has stopped by calling stop()
virtual bool isStoppedManually() const = 0;
/// @brief Get current time position of playing audio
/// @return time position in seconds
virtual duration_t getTime() const = 0;
/// @brief Set playing audio time position
/// @param time time position in seconds
virtual void setTime(duration_t time) = 0;
/// @brief Set speaker 3D position in the world
/// @param pos new position
virtual void setPosition(glm::vec3 pos) = 0;
/// @brief Get speaker 3D position in the world
/// @return position
virtual glm::vec3 getPosition() const = 0;
/// @brief Set speaker movement velocity used for Doppler effect
/// @param vel velocity vector
virtual void setVelocity(glm::vec3 vel) = 0;
/// @brief Get speaker movement velocity used for Doppler effect
/// @return velocity vector
virtual glm::vec3 getVelocity() const = 0;
/// @brief Get speaker priority
/// @return speaker priority value
virtual int getPriority() const = 0;
/// @brief Determines if the position is relative to the listener
/// @param relative true - relative to the listener (default: false)
virtual void setRelative(bool relative) = 0;
/// @brief Determines if the position is relative to the listener
virtual bool isRelative() const = 0;
/// @brief Check if speaker is playing
inline bool isPlaying() const {
return getState() == State::playing;
}
/// @brief Check if speaker is paused
inline bool isPaused() const {
return getState() == State::paused;
}
/// @brief Check if speaker is stopped
inline bool isStopped() const {
return getState() == State::stopped;
}
};
class Backend {
public:
virtual ~Backend() {};
virtual Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM) = 0;
virtual Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource) = 0;
virtual void setListener(
glm::vec3 position,
glm::vec3 velocity,
glm::vec3 lookAt,
glm::vec3 up
) = 0;
virtual void update(double delta) = 0;
/// @brief Check if backend is an abstraction that does not internally
/// work with actual audio data or play anything
virtual bool isDummy() const = 0;
};
/// @brief Initialize audio system or use no audio mode
/// @param enabled try to initialize actual audio
extern void initialize(bool enabled);
/// @brief Load audio file info and PCM data
/// @param file audio file
/// @param headerOnly read header only
/// @throws std::runtime_error if I/O error ocurred or format is unknown
/// @return PCM audio data
extern PCM* loadPCM(const fs::path& file, bool headerOnly);
/// @brief Load sound from file
/// @param file audio file path
/// @param keepPCM store PCM data in sound to make it accessible with Sound::getPCM
/// @throws std::runtime_error if I/O error ocurred or format is unknown
/// @return new Sound instance
extern Sound* loadSound(const fs::path& file, bool keepPCM);
/// @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
extern Sound* createSound(std::shared_ptr<PCM> pcm, bool keepPCM);
/// @brief Open new PCM stream from file
/// @param file audio file path
/// @throws std::runtime_error if I/O error ocurred or format is unknown
/// @return new PCMStream instance
extern PCMStream* openPCMStream(const fs::path& file);
/// @brief Open new audio stream from file
/// @param file audio file path
/// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource
/// @return new Stream instance
extern Stream* openStream(const fs::path& file, bool keepSource);
/// @brief Open new audio stream from source
/// @param stream PCM data source
/// @param keepSource store PCMStream in stream to make it accessible with Stream::getSource
/// @return new Stream instance
extern Stream* openStream(std::shared_ptr<PCMStream> stream, bool keepSource);
/// @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 Play 3D sound in the world
/// @param sound target sound
/// @param position sound world position
/// @param relative position speaker relative to listener
/// @param volume sound volume [0.0-1.0]
/// @param pitch sound pitch multiplier [0.0-...]
/// @param loop loop sound
/// @param priority sound priority
/// (PRIORITY_LOW, PRIORITY_NORMAL, PRIORITY_HIGH)
/// @return speaker id or 0
extern speakerid_t play(
Sound* sound,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop,
int priority
);
/// @brief Play stream
/// @param stream target stream
/// @param position stream world position
/// @param relative position speaker relative to listener
/// @param volume stream volume [0.0-1.0]
/// @param pitch stream pitch multiplier [0.0-...]
/// @param loop loop stream
/// @return speaker id or 0
extern speakerid_t play(
std::shared_ptr<Stream> stream,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop
);
/// @brief Play stream from file
/// @param file audio file path
/// @param position stream world position
/// @param relative position speaker relative to listener
/// @param volume stream volume [0.0-1.0]
/// @param pitch stream pitch multiplier [0.0-...]
/// @param loop loop stream
/// @return speaker id or 0
/// @return
extern speakerid_t playStream(
const fs::path& file,
glm::vec3 position,
bool relative,
float volume,
float pitch,
bool loop
);
/// @brief Get speaker by id
/// @param id speaker id
/// @return speaker or nullptr
extern Speaker* get(speakerid_t id);
/// @brief Update audio streams and sound instanced
/// @param delta time elapsed since the last update (seconds)
extern void update(double delta);
/// @brief Finalize audio system
extern void close();
};
#endif // AUDIO_AUDIO_H_

View File

@ -1,207 +0,0 @@
#include "audioutil.h"
#include <iostream>
#include <fstream>
#include <cstring>
#include <memory>
#include <type_traits>
#ifdef __APPLE__
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif
bool is_big_endian(void){
uint32_t ui32_v = 0x01020304;
char bytes[sizeof(uint32_t)];
std::memcpy(bytes, &ui32_v, sizeof(uint32_t));
return bytes[0] == 1;
}
std::int32_t convert_to_int(char* buffer, std::size_t len){
std::int32_t a = 0;
if (!is_big_endian()) {
std::memcpy(&a, buffer, len);
}
else {
for (std::size_t i = 0; i < len; ++i) {
reinterpret_cast<char*>(&a)[3 - i] = buffer[i];
}
}
return a;
}
bool check_al_errors(const std::string& filename, const std::uint_fast32_t line){
ALenum error = alGetError();
if(error != AL_NO_ERROR){
std::cerr << "OpenAL ERROR (" << filename << ": " << line << ")\n" ;
switch(error){
case AL_INVALID_NAME:
std::cerr << "AL_INVALID_NAME: a bad name (ID) was passed to an OpenAL function";
break;
case AL_INVALID_ENUM:
std::cerr << "AL_INVALID_ENUM: an invalid enum value was passed to an OpenAL function";
break;
case AL_INVALID_VALUE:
std::cerr << "AL_INVALID_VALUE: an invalid value was passed to an OpenAL function";
break;
case AL_INVALID_OPERATION:
std::cerr << "AL_INVALID_OPERATION: the requested operation is not valid";
break;
case AL_OUT_OF_MEMORY:
std::cerr << "AL_OUT_OF_MEMORY: the requested operation resulted in OpenAL running out of memory";
break;
default:
std::cerr << "UNKNOWN AL ERROR: " << error;
}
std::cerr << std::endl;
return false;
}
return true;
}
bool load_wav_file_header(std::ifstream& file,
std::uint8_t& channels,
std::int32_t& sampleRate,
std::uint8_t& bitsPerSample,
ALsizei& size){
char buffer[4];
if(!file.is_open())
return false;
// the RIFF
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read RIFF" << std::endl;
return false;
}
if(std::strncmp(buffer, "RIFF", 4) != 0){
std::cerr << "ERROR: file is not a valid WAVE file (header doesn't begin with RIFF)" << std::endl;
return false;
}
// the size of the file
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read size of file" << std::endl;
return false;
}
// the WAVE
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read WAVE" << std::endl;
return false;
}
if(std::strncmp(buffer, "WAVE", 4) != 0){
std::cerr << "ERROR: file is not a valid WAVE file (header doesn't contain WAVE)" << std::endl;
return false;
}
// "fmt/0"
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read fmt/0" << std::endl;
return false;
}
// this is always 16, the size of the fmt data chunk
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read the 16" << std::endl;
return false;
}
// PCM should be 1?
if(!file.read(buffer, 2)){
std::cerr << "ERROR: could not read PCM" << std::endl;
return false;
}
// the number of channels
if(!file.read(buffer, 2)){
std::cerr << "ERROR: could not read number of channels" << std::endl;
return false;
}
channels = convert_to_int(buffer, 2);
// sample rate
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read sample rate" << std::endl;
return false;
}
sampleRate = convert_to_int(buffer, 4);
// (sampleRate * bitsPerSample * channels) / 8
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read (sampleRate * bitsPerSample * channels) / 8" << std::endl;
return false;
}
// ?? dafaq
if(!file.read(buffer, 2)){
std::cerr << "ERROR: could not read dafaq" << std::endl;
return false;
}
// bitsPerSample
if(!file.read(buffer, 2)){
std::cerr << "ERROR: could not read bits per sample" << std::endl;
return false;
}
bitsPerSample = convert_to_int(buffer, 2);
// data chunk header "data"
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read data chunk header" << std::endl;
return false;
}
if(std::strncmp(buffer, "data", 4) != 0){
std::cerr << "ERROR: file is not a valid WAVE file (doesn't have 'data' tag)" << std::endl;
return false;
}
// size of data
if(!file.read(buffer, 4)){
std::cerr << "ERROR: could not read data size" << std::endl;
return false;
}
size = convert_to_int(buffer, 4);
/* cannot be at the end of file */
if(file.eof()){
std::cerr << "ERROR: reached EOF on the file" << std::endl;
return false;
}
if(file.fail()){
std::cerr << "ERROR: fail state set on the file" << std::endl;
return false;
}
return true;
}
// 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){
std::ifstream in(filename, std::ios::binary);
if(!in.is_open()){
std::cerr << "ERROR: Could not open \"" << filename << "\"" << std::endl;
return nullptr;
}
if(!load_wav_file_header(in, channels, sampleRate, bitsPerSample, size)){
std::cerr << "ERROR: Could not load wav header of \"" << filename << "\"" << std::endl;
return nullptr;
}
std::unique_ptr<char[]> data (new char[size]);
try {
in.read(data.get(), size);
return data.release();
}
catch (const std::exception&) {
std::cerr << "ERROR: Could not load wav data of \"" << filename << "\"" << std::endl;
return nullptr;
}
}

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_ */

149
src/coders/ogg.cpp Normal file
View File

@ -0,0 +1,149 @@
#include "ogg.h"
#include <iostream>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include "../audio/audio.h"
#include "../typedefs.h"
using namespace audio;
static inline const char* vorbis_error_message(int code) {
switch (code) {
case 0: return "no error";
case OV_EREAD: return "a read from media returned an error";
case OV_ENOTVORBIS: return "bitstream does not contain any Vorbis data";
case OV_EVERSION: return "vorbis version mismatch";
case OV_EBADHEADER: return "invalid Vorbis bitstream header";
case OV_EFAULT: return "internal logic fault";
case OV_EINVAL: return "invalid read operation";
default:
return "unknown";
}
}
audio::PCM* ogg::load_pcm(const std::filesystem::path& file, bool headerOnly) {
OggVorbis_File vf;
int code;
if ((code = ov_fopen(file.u8string().c_str(), &vf))) {
throw std::runtime_error(vorbis_error_message(code));
}
std::vector<char> data;
vorbis_info* info = ov_info(&vf, -1);
uint channels = info->channels;
uint sampleRate = info->rate;
bool seekable = ov_seekable(&vf);
size_t totalSamples = seekable ? ov_pcm_total(&vf, -1) : 0;
if (!headerOnly) {
const int bufferSize = 4096;
int section = 0;
char buffer[bufferSize];
bool eof = false;
while (!eof) {
long ret = ov_read(&vf, buffer, bufferSize, 0, 2, true, &section);
if (ret == 0) {
eof = true;
} else if (ret < 0) {
std::cerr << "ogg::load_pcm: " << vorbis_error_message(ret) << std::endl;
} else {
data.insert(data.end(), std::begin(buffer), std::begin(buffer)+ret);
}
}
totalSamples = data.size();
}
ov_clear(&vf);
return new PCM(std::move(data), totalSamples, channels, 16, sampleRate, seekable);
}
class OggStream : public PCMStream {
OggVorbis_File vf;
bool closed = false;
uint channels;
uint sampleRate;
size_t totalSamples = 0;
bool seekable;
public:
OggStream(OggVorbis_File vf) : vf(std::move(vf)) {
vorbis_info* info = ov_info(&vf, -1);
channels = info->channels;
sampleRate = info->rate;
seekable = ov_seekable(&vf);
if (seekable) {
totalSamples = ov_pcm_total(&vf, -1);
}
}
~OggStream() {
if (!closed) {
close();
}
}
size_t read(char* buffer, size_t bufferSize) override {
if (closed) {
return 0;
}
int bitstream = 0;
long bytes = ov_read(&vf, buffer, bufferSize, 0, 2, true, &bitstream);
if (bytes < 0) {
std::cerr << "ogg::load_pcm: " << vorbis_error_message(bytes) << " " << bytes << std::endl;
return 0;
}
return bytes;
}
void close() override {
if (!closed) {
ov_clear(&vf);
closed = true;
}
}
bool isOpen() const override {
return !closed;
}
size_t getTotalSamples() const override {
return totalSamples;
}
duration_t getTotalDuration() const override {
return static_cast<duration_t>(totalSamples) /
static_cast<duration_t>(sampleRate);
}
uint getChannels() const override {
return channels;
}
uint getSampleRate() const override {
return sampleRate;
}
uint getBitsPerSample() const override {
return 16;
}
bool isSeekable() const override {
return seekable;
}
void seek(size_t position) override {
if (!closed && seekable) {
ov_raw_seek(&vf, position);
}
}
};
PCMStream* ogg::create_stream(const std::filesystem::path& file) {
OggVorbis_File vf;
int code;
if ((code = ov_fopen(file.u8string().c_str(), &vf))) {
throw std::runtime_error(vorbis_error_message(code));
}
return new OggStream(std::move(vf));
}

16
src/coders/ogg.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef CODERS_OGG_H_
#define CODERS_OGG_H_
#include <filesystem>
namespace audio {
struct PCM;
class PCMStream;
}
namespace ogg {
extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly);
extern audio::PCMStream* create_stream(const std::filesystem::path& file);
}
#endif // CODERS_OGG_H_

236
src/coders/wav.cpp Normal file
View File

@ -0,0 +1,236 @@
#include "wav.h"
#include <vector>
#include <cstring>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include "../audio/audio.h"
bool is_big_endian() {
uint32_t ui32_v = 0x01020304;
char bytes[sizeof(uint32_t)];
std::memcpy(bytes, &ui32_v, sizeof(uint32_t));
return bytes[0] == 1;
}
std::int32_t convert_to_int(char* buffer, std::size_t len){
std::int32_t a = 0;
if (!is_big_endian()) {
std::memcpy(&a, buffer, len);
}
else {
for (std::size_t i = 0; i < len; ++i) {
reinterpret_cast<char*>(&a)[3 - i] = buffer[i];
}
}
return a;
}
/// @brief Seekable WAV-file PCM stream
class WavStream : public audio::PCMStream {
std::ifstream in;
uint channels;
uint bytesPerSample;
uint sampleRate;
size_t totalSize;
size_t totalSamples;
size_t initialPosition;
public:
WavStream(
std::ifstream in,
uint channels,
uint bitsPerSample,
uint sampleRate,
size_t size,
size_t initialPosition
) : in(std::move(in)),
channels(channels),
bytesPerSample(bitsPerSample/8),
sampleRate(sampleRate),
totalSize(size)
{
totalSamples = totalSize / channels / bytesPerSample;
this->initialPosition = initialPosition;
}
size_t read(char* buffer, size_t bufferSize) override {
if (!isOpen()) {
return 0;
}
in.read(buffer, bufferSize);
if (in.eof()) {
return 0;
}
if (in.fail()) {
std::cerr << "Wav::load_pcm: I/O error ocurred" << std::endl;
return -1;
}
return in.gcount();
}
void close() override {
if (!isOpen())
return;
in.close();
}
bool isOpen() const override {
return in.is_open();
}
size_t getTotalSamples() const override {
return totalSamples;
}
audio::duration_t getTotalDuration() const override {
return totalSamples / static_cast<audio::duration_t>(sampleRate);
}
uint getChannels() const override {
return channels;
}
uint getSampleRate() const override {
return sampleRate;
}
uint getBitsPerSample() const override {
return bytesPerSample * 8;
}
bool isSeekable() const override {
return true;
}
void seek(size_t position) override {
if (!isOpen())
return;
position %= totalSamples;
in.clear();
in.seekg(initialPosition + position * channels * bytesPerSample, std::ios_base::beg);
}
};
audio::PCMStream* wav::create_stream(const std::filesystem::path& file) {
std::ifstream in(file, std::ios::binary);
if(!in.is_open()){
throw std::runtime_error("could not to open file '"+file.u8string()+"'");
}
char buffer[6];
// the RIFF
if(!in.read(buffer, 4)){
throw std::runtime_error("could not to read RIFF");
}
if(std::strncmp(buffer, "RIFF", 4) != 0){
throw std::runtime_error("file is not a valid WAVE file (header doesn't begin with RIFF)");
}
// the size of the file
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read size of file");
}
// the WAVE
if(!in.read(buffer, 4)){
throw std::runtime_error("could not to read WAVE");
}
if(std::strncmp(buffer, "WAVE", 4) != 0){
throw std::runtime_error("file is not a valid WAVE file (header doesn't contain WAVE)");
}
// "fmt/0"
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read fmt/0");
}
// this is always 16, the size of the fmt data chunk
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read the 16");
}
// PCM should be 1?
if(!in.read(buffer, 2)){
throw std::runtime_error("could not read PCM");
}
// the number of channels
if(!in.read(buffer, 2)){
throw std::runtime_error("could not read number of channels");
}
int channels = convert_to_int(buffer, 2);
// sample rate
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read sample rate");
}
int sampleRate = convert_to_int(buffer, 4);
if (!in.read(buffer, 6)) {
throw std::runtime_error("could not to read WAV header");
}
// bitsPerSample
if(!in.read(buffer, 2)){
throw std::runtime_error("could not read bits per sample");
}
int bitsPerSample = convert_to_int(buffer, 2);
if (bitsPerSample >= 24) {
throw std::runtime_error(std::to_string(bitsPerSample)+" bit depth is not supported by OpenAL");
}
// data chunk header "data"
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read data chunk header");
}
size_t initialOffset = 44;
// skip garbage in WAV
if (std::strncmp(buffer, "LIST", 4) == 0) {
// chunk size
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read comment chunk size");
}
int chunkSize = convert_to_int(buffer, 4);
in.seekg(chunkSize, std::ios_base::cur);
initialOffset += chunkSize + 4;
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read data chunk header");
}
}
if(std::strncmp(buffer, "data", 4) != 0){
std::cerr << buffer << std::endl;
throw std::runtime_error("file is not a valid WAVE file (doesn't have 'data' tag)");
}
// size of data
if(!in.read(buffer, 4)){
throw std::runtime_error("could not read data size");
}
size_t size = convert_to_int(buffer, 4);
/* cannot be at the end of file */
if(in.eof()){
throw std::runtime_error("reached EOF on the file");
}
if(in.fail()){
throw std::runtime_error("fail state set on the file");
}
return new WavStream(std::move(in), channels, bitsPerSample, sampleRate, size, initialOffset);
}
audio::PCM* wav::load_pcm(const std::filesystem::path& file, bool headerOnly) {
std::unique_ptr<audio::PCMStream> stream(wav::create_stream(file));
size_t totalSamples = stream->getTotalSamples();
uint channels = stream->getChannels();
uint bitsPerSample = stream->getBitsPerSample();
uint sampleRate = stream->getSampleRate();
std::vector<char> data;
if (!headerOnly) {
size_t size = stream->getTotalSamples() *
(stream->getBitsPerSample()/8) *
stream->getChannels();
data.resize(size);
stream->readFully(data.data(), size, false);
}
return new audio::PCM(std::move(data), totalSamples, channels, bitsPerSample, sampleRate, true);
}

16
src/coders/wav.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef CODERS_WAV_H_
#define CODERS_WAV_H_
#include <filesystem>
namespace audio {
struct PCM;
class PCMStream;
}
namespace wav {
extern audio::PCM* load_pcm(const std::filesystem::path& file, bool headerOnly);
extern audio::PCMStream* create_stream(const std::filesystem::path& file);
}
#endif // CODERS_WAV_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());
@ -123,6 +121,8 @@ void Engine::mainloop() {
assert(screen != nullptr);
updateTimers();
updateHotkeys();
audio::update(delta);
gui->act(delta);
screen->update(delta);
@ -148,7 +148,7 @@ Engine::~Engine() {
screen.reset();
content.reset();
Audio::finalize();
audio::close();
assets.reset();
scripting::close();
Window::terminate();

View File

@ -8,6 +8,7 @@
#include <filesystem>
#include <stdexcept>
#include "../audio/audio.h"
#include "../window/Camera.h"
#include "../window/Events.h"
#include "../window/input.h"
@ -19,6 +20,7 @@
#include "../world/Level.h"
#include "../world/World.h"
#include "../objects/Player.h"
#include "../physics/Hitbox.h"
#include "../logic/ChunksController.h"
#include "../logic/LevelController.h"
#include "../logic/scripting/scripting.h"
@ -149,6 +151,15 @@ void LevelScreen::update(float delta) {
updateHotkeys();
}
auto player = controller->getPlayer();
auto camera = player->camera;
audio::setListener(
camera->position,
player->hitbox->velocity,
camera->position+camera->dir,
camera->up
);
// TODO: subscribe for setting change
EngineSettings& settings = engine->getSettings();
controller->getPlayer()->camera->setFov(glm::radians(settings.camera.fov));

View File

@ -9,6 +9,7 @@
"glm",
"libspng",
"zlib",
"luajit"
"luajit",
"libvorbis"
]
}