commit
90a4922201
2
.github/workflows/appimage.yml
vendored
2
.github/workflows/appimage.yml
vendored
@ -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
|
||||
|
||||
2
.github/workflows/cmake.yml
vendored
2
.github/workflows/cmake.yml
vendored
@ -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
|
||||
|
||||
|
||||
@ -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})
|
||||
|
||||
|
||||
@ -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/*
|
||||
|
||||
|
||||
10
README.md
10
README.md
@ -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).
|
||||
|
||||
@ -29,6 +29,9 @@ AppDir:
|
||||
- libopengl0
|
||||
- libasound2
|
||||
- libglx0
|
||||
- libogg0
|
||||
- libvorbis0a
|
||||
- libvorbisfile3
|
||||
exclude:
|
||||
- hicolor-icon-theme
|
||||
- sound-theme-freedesktop
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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});
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
365
src/audio/AL/ALAudio.cpp
Normal 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
157
src/audio/AL/ALAudio.h
Normal 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
44
src/audio/AL/alutil.cpp
Normal 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
76
src/audio/AL/alutil.h
Normal 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_ */
|
||||
@ -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();
|
||||
}
|
||||
@ -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
22
src/audio/NoAudio.cpp
Normal 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
84
src/audio/NoAudio.h
Normal 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
310
src/audio/audio.cpp
Normal 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
404
src/audio/audio.h
Normal 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_
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
149
src/coders/ogg.cpp
Normal 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, §ion);
|
||||
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
16
src/coders/ogg.h
Normal 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
236
src/coders/wav.cpp
Normal 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
16
src/coders/wav.h
Normal 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_
|
||||
@ -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();
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
"glm",
|
||||
"libspng",
|
||||
"zlib",
|
||||
"luajit"
|
||||
"luajit",
|
||||
"libvorbis"
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user