commit
90a4922201
2
.github/workflows/appimage.yml
vendored
2
.github/workflows/appimage.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: install dependencies
|
- name: install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
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/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
|
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||||
- name: configure
|
- 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
|
# make && make install INSTALL_INC=/usr/include/lua
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
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/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
|
sudo ln -s /usr/include/luajit-2.1 /usr/include/lua
|
||||||
|
|
||||||
|
|||||||
@ -89,10 +89,12 @@ if (WIN32)
|
|||||||
find_package(glfw3 REQUIRED)
|
find_package(glfw3 REQUIRED)
|
||||||
find_package(spng REQUIRED)
|
find_package(spng REQUIRED)
|
||||||
find_package(glm REQUIRED)
|
find_package(glm REQUIRED)
|
||||||
|
find_package(vorbis REQUIRED)
|
||||||
set(PNGLIB spng::spng)
|
set(PNGLIB spng::spng)
|
||||||
else()
|
else()
|
||||||
find_package(Lua REQUIRED)
|
find_package(Lua REQUIRED)
|
||||||
set(PNGLIB spng)
|
set(PNGLIB spng)
|
||||||
|
set(VORBISLIB vorbis vorbisfile) # not tested
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw)
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/libs/glfw)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
@ -100,6 +102,7 @@ else()
|
|||||||
find_package(Lua REQUIRED)
|
find_package(Lua REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
set(PNGLIB PNG::PNG)
|
set(PNGLIB PNG::PNG)
|
||||||
|
set(VORBISLIB vorbis vorbisfile)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
@ -118,7 +121,7 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
include_directories(${LUA_INCLUDE_DIR})
|
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})
|
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 \
|
libpng-dev \
|
||||||
libopenal-dev \
|
libopenal-dev \
|
||||||
libluajit-5.1-dev \
|
libluajit-5.1-dev \
|
||||||
|
libvorbis-dev \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|||||||
10
README.md
10
README.md
@ -33,7 +33,7 @@ cmake --build .
|
|||||||
|
|
||||||
#### Debian-based distro:
|
#### Debian-based distro:
|
||||||
```sh
|
```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:
|
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:
|
#### RHEL-based distro:
|
||||||
```sh
|
```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
|
\+ install LuaJIT
|
||||||
@ -52,12 +52,12 @@ sudo dnf install glfw-devel glfw glew-devel glm-devel libpng-devel openal-devel
|
|||||||
#### Arch-based distro:
|
#### Arch-based distro:
|
||||||
If you use X11
|
If you use X11
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -S glfw-x11 glew glm libpng openal
|
sudo pacman -S glfw-x11 glew glm libpng libvorbis openal
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use Wayland
|
If you use Wayland
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -S glfw-wayland glew glm libpng openal
|
sudo pacman -S glfw-wayland glew glm libpng libvorbis openal
|
||||||
```
|
```
|
||||||
|
|
||||||
\+ install LuaJIT
|
\+ install LuaJIT
|
||||||
@ -72,7 +72,7 @@ make && sudo make install INSTALL_INC=/usr/include/lua
|
|||||||
#### macOS:
|
#### 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).
|
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
|
- libopengl0
|
||||||
- libasound2
|
- libasound2
|
||||||
- libglx0
|
- libglx0
|
||||||
|
- libogg0
|
||||||
|
- libvorbis0a
|
||||||
|
- libvorbisfile3
|
||||||
exclude:
|
exclude:
|
||||||
- hicolor-icon-theme
|
- hicolor-icon-theme
|
||||||
- sound-theme-freedesktop
|
- sound-theme-freedesktop
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#include "Assets.h"
|
#include "Assets.h"
|
||||||
|
|
||||||
|
#include "../audio/audio.h"
|
||||||
#include "../graphics/Texture.h"
|
#include "../graphics/Texture.h"
|
||||||
#include "../graphics/Shader.h"
|
#include "../graphics/Shader.h"
|
||||||
#include "../graphics/Atlas.h"
|
#include "../graphics/Atlas.h"
|
||||||
@ -18,7 +19,7 @@ Texture* Assets::getTexture(std::string name) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Assets::store(Texture* texture, std::string name){
|
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){
|
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){
|
void Assets::store(Font* font, std::string name){
|
||||||
fonts[name].reset(font);
|
fonts.emplace(name, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
Atlas* Assets::getAtlas(std::string name) const {
|
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){
|
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() {
|
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) {
|
void Assets::store(UiDocument* layout, std::string name) {
|
||||||
layouts[name].reset(layout);
|
layouts.emplace(name, layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Assets::extend(const Assets& assets) {
|
void Assets::extend(const Assets& assets) {
|
||||||
@ -91,6 +103,9 @@ void Assets::extend(const Assets& assets) {
|
|||||||
for (auto entry : assets.layouts) {
|
for (auto entry : assets.layouts) {
|
||||||
layouts[entry.first] = entry.second;
|
layouts[entry.first] = entry.second;
|
||||||
}
|
}
|
||||||
|
for (auto entry : assets.sounds) {
|
||||||
|
sounds[entry.first] = entry.second;
|
||||||
|
}
|
||||||
animations.clear();
|
animations.clear();
|
||||||
for (auto entry : assets.animations) {
|
for (auto entry : assets.animations) {
|
||||||
animations.emplace_back(entry);
|
animations.emplace_back(entry);
|
||||||
|
|||||||
@ -14,11 +14,9 @@ class Font;
|
|||||||
class Atlas;
|
class Atlas;
|
||||||
class UiDocument;
|
class UiDocument;
|
||||||
|
|
||||||
struct LayoutCfg {
|
namespace audio {
|
||||||
int env;
|
class Sound;
|
||||||
|
}
|
||||||
LayoutCfg(int env) : env(env) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Assets {
|
class Assets {
|
||||||
std::unordered_map<std::string, std::shared_ptr<Texture>> textures;
|
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<Font>> fonts;
|
||||||
std::unordered_map<std::string, std::shared_ptr<Atlas>> atlases;
|
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<UiDocument>> layouts;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<audio::Sound>> sounds;
|
||||||
std::vector<TextureAnimation> animations;
|
std::vector<TextureAnimation> animations;
|
||||||
public:
|
public:
|
||||||
~Assets();
|
~Assets();
|
||||||
@ -41,6 +40,9 @@ public:
|
|||||||
Atlas* getAtlas(std::string name) const;
|
Atlas* getAtlas(std::string name) const;
|
||||||
void store(Atlas* atlas, std::string name);
|
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();
|
const std::vector<TextureAnimation>& getAnimations();
|
||||||
void store(const TextureAnimation& animation);
|
void store(const TextureAnimation& animation);
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ void AssetsLoader::addLoader(int tag, aloader_func func) {
|
|||||||
loaders[tag] = 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});
|
entries.push(aloader_entry{ tag, filename, alias, settings});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,24 +7,41 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
const short ASSET_TEXTURE = 1;
|
inline constexpr short ASSET_TEXTURE = 1;
|
||||||
const short ASSET_SHADER = 2;
|
inline constexpr short ASSET_SHADER = 2;
|
||||||
const short ASSET_FONT = 3;
|
inline constexpr short ASSET_FONT = 3;
|
||||||
const short ASSET_ATLAS = 4;
|
inline constexpr short ASSET_ATLAS = 4;
|
||||||
const short ASSET_LAYOUT = 5;
|
inline constexpr short ASSET_LAYOUT = 5;
|
||||||
|
inline constexpr short ASSET_SOUND = 6;
|
||||||
|
|
||||||
class ResPaths;
|
class ResPaths;
|
||||||
class Assets;
|
class Assets;
|
||||||
class AssetsLoader;
|
class AssetsLoader;
|
||||||
class Content;
|
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 {
|
struct aloader_entry {
|
||||||
int tag;
|
int tag;
|
||||||
const std::string filename;
|
const std::string filename;
|
||||||
const std::string alias;
|
const std::string alias;
|
||||||
std::shared_ptr<void> config;
|
std::shared_ptr<AssetCfg> config;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AssetsLoader {
|
class AssetsLoader {
|
||||||
@ -39,7 +56,7 @@ public:
|
|||||||
int tag,
|
int tag,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string alias,
|
const std::string alias,
|
||||||
std::shared_ptr<void> settings=nullptr
|
std::shared_ptr<AssetCfg> settings=nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "Assets.h"
|
#include "Assets.h"
|
||||||
#include "AssetsLoader.h"
|
#include "AssetsLoader.h"
|
||||||
|
#include "../audio/audio.h"
|
||||||
#include "../files/files.h"
|
#include "../files/files.h"
|
||||||
#include "../files/engine_paths.h"
|
#include "../files/engine_paths.h"
|
||||||
#include "../coders/png.h"
|
#include "../coders/png.h"
|
||||||
@ -25,7 +26,7 @@ bool assetload::texture(
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void>
|
std::shared_ptr<AssetCfg>
|
||||||
) {
|
) {
|
||||||
std::unique_ptr<Texture> texture(
|
std::unique_ptr<Texture> texture(
|
||||||
png::load_texture(paths->find(filename).u8string())
|
png::load_texture(paths->find(filename).u8string())
|
||||||
@ -44,7 +45,7 @@ bool assetload::shader(
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void>
|
std::shared_ptr<AssetCfg>
|
||||||
) {
|
) {
|
||||||
fs::path vertexFile = paths->find(filename+".glslv");
|
fs::path vertexFile = paths->find(filename+".glslv");
|
||||||
fs::path fragmentFile = paths->find(filename+".glslf");
|
fs::path fragmentFile = paths->find(filename+".glslf");
|
||||||
@ -92,7 +93,7 @@ bool assetload::atlas(
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string directory,
|
const std::string directory,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void>
|
std::shared_ptr<AssetCfg>
|
||||||
) {
|
) {
|
||||||
AtlasBuilder builder;
|
AtlasBuilder builder;
|
||||||
for (const auto& file : paths->listdir(directory)) {
|
for (const auto& file : paths->listdir(directory)) {
|
||||||
@ -112,7 +113,7 @@ bool assetload::font(
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void>
|
std::shared_ptr<AssetCfg>
|
||||||
) {
|
) {
|
||||||
std::vector<std::unique_ptr<Texture>> pages;
|
std::vector<std::unique_ptr<Texture>> pages;
|
||||||
for (size_t i = 0; i <= 4; i++) {
|
for (size_t i = 0; i <= 4; i++) {
|
||||||
@ -138,10 +139,10 @@ bool assetload::layout(
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string file,
|
const std::string file,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void> config
|
std::shared_ptr<AssetCfg> config
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
LayoutCfg* cfg = reinterpret_cast<LayoutCfg*>(config.get());
|
auto cfg = dynamic_cast<LayoutCfg*>(config.get());
|
||||||
auto document = UiDocument::read(loader, cfg->env, name, file);
|
auto document = UiDocument::read(loader, cfg->env, name, file);
|
||||||
assets->store(document.release(), name);
|
assets->store(document.release(), name);
|
||||||
return true;
|
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,
|
bool assetload::animation(Assets* assets,
|
||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string directory,
|
const std::string directory,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ class ResPaths;
|
|||||||
class Assets;
|
class Assets;
|
||||||
class AssetsLoader;
|
class AssetsLoader;
|
||||||
class Atlas;
|
class Atlas;
|
||||||
|
struct AssetCfg;
|
||||||
|
|
||||||
namespace assetload {
|
namespace assetload {
|
||||||
bool texture(
|
bool texture(
|
||||||
@ -16,7 +17,7 @@ namespace assetload {
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void> settings
|
std::shared_ptr<AssetCfg> settings
|
||||||
);
|
);
|
||||||
bool shader(
|
bool shader(
|
||||||
AssetsLoader&,
|
AssetsLoader&,
|
||||||
@ -24,7 +25,7 @@ namespace assetload {
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void> settings
|
std::shared_ptr<AssetCfg> settings
|
||||||
);
|
);
|
||||||
bool atlas(
|
bool atlas(
|
||||||
AssetsLoader&,
|
AssetsLoader&,
|
||||||
@ -32,7 +33,7 @@ namespace assetload {
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string directory,
|
const std::string directory,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void> settings
|
std::shared_ptr<AssetCfg> settings
|
||||||
);
|
);
|
||||||
bool font(
|
bool font(
|
||||||
AssetsLoader&,
|
AssetsLoader&,
|
||||||
@ -40,7 +41,7 @@ namespace assetload {
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string filename,
|
const std::string filename,
|
||||||
const std::string name,
|
const std::string name,
|
||||||
std::shared_ptr<void> settings
|
std::shared_ptr<AssetCfg> settings
|
||||||
);
|
);
|
||||||
bool layout(
|
bool layout(
|
||||||
AssetsLoader&,
|
AssetsLoader&,
|
||||||
@ -48,7 +49,16 @@ namespace assetload {
|
|||||||
const ResPaths* paths,
|
const ResPaths* paths,
|
||||||
const std::string file,
|
const std::string file,
|
||||||
const std::string name,
|
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(
|
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>
|
#include <functional>
|
||||||
#define GLEW_STATIC
|
#define GLEW_STATIC
|
||||||
|
|
||||||
#include "audio/Audio.h"
|
#include "audio/audio.h"
|
||||||
#include "assets/Assets.h"
|
#include "assets/Assets.h"
|
||||||
#include "assets/AssetsLoader.h"
|
#include "assets/AssetsLoader.h"
|
||||||
#include "world/WorldGenerators.h"
|
#include "world/WorldGenerators.h"
|
||||||
@ -56,6 +56,7 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths)
|
|||||||
if (Window::initialize(settings.display)){
|
if (Window::initialize(settings.display)){
|
||||||
throw initialize_error("could not initialize window");
|
throw initialize_error("could not initialize window");
|
||||||
}
|
}
|
||||||
|
audio::initialize(true);
|
||||||
|
|
||||||
auto resdir = paths->getResources();
|
auto resdir = paths->getResources();
|
||||||
scripting::initialize(this);
|
scripting::initialize(this);
|
||||||
@ -66,7 +67,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths)
|
|||||||
resPaths = std::make_unique<ResPaths>(resdir, roots);
|
resPaths = std::make_unique<ResPaths>(resdir, roots);
|
||||||
assets = std::make_unique<Assets>();
|
assets = std::make_unique<Assets>();
|
||||||
|
|
||||||
|
|
||||||
AssetsLoader loader(assets.get(), resPaths.get());
|
AssetsLoader loader(assets.get(), resPaths.get());
|
||||||
AssetsLoader::addDefaults(loader, nullptr);
|
AssetsLoader::addDefaults(loader, nullptr);
|
||||||
|
|
||||||
@ -79,8 +79,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths)
|
|||||||
throw initialize_error("could not to load assets");
|
throw initialize_error("could not to load assets");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Audio::initialize();
|
|
||||||
gui = std::make_unique<gui::GUI>();
|
gui = std::make_unique<gui::GUI>();
|
||||||
if (settings.ui.language == "auto") {
|
if (settings.ui.language == "auto") {
|
||||||
settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources());
|
settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources());
|
||||||
@ -124,6 +122,8 @@ void Engine::mainloop() {
|
|||||||
updateTimers();
|
updateTimers();
|
||||||
updateHotkeys();
|
updateHotkeys();
|
||||||
|
|
||||||
|
audio::update(delta);
|
||||||
|
|
||||||
gui->act(delta);
|
gui->act(delta);
|
||||||
screen->update(delta);
|
screen->update(delta);
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ Engine::~Engine() {
|
|||||||
screen.reset();
|
screen.reset();
|
||||||
content.reset();
|
content.reset();
|
||||||
|
|
||||||
Audio::finalize();
|
audio::close();
|
||||||
assets.reset();
|
assets.reset();
|
||||||
scripting::close();
|
scripting::close();
|
||||||
Window::terminate();
|
Window::terminate();
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "../audio/audio.h"
|
||||||
#include "../window/Camera.h"
|
#include "../window/Camera.h"
|
||||||
#include "../window/Events.h"
|
#include "../window/Events.h"
|
||||||
#include "../window/input.h"
|
#include "../window/input.h"
|
||||||
@ -19,6 +20,7 @@
|
|||||||
#include "../world/Level.h"
|
#include "../world/Level.h"
|
||||||
#include "../world/World.h"
|
#include "../world/World.h"
|
||||||
#include "../objects/Player.h"
|
#include "../objects/Player.h"
|
||||||
|
#include "../physics/Hitbox.h"
|
||||||
#include "../logic/ChunksController.h"
|
#include "../logic/ChunksController.h"
|
||||||
#include "../logic/LevelController.h"
|
#include "../logic/LevelController.h"
|
||||||
#include "../logic/scripting/scripting.h"
|
#include "../logic/scripting/scripting.h"
|
||||||
@ -149,6 +151,15 @@ void LevelScreen::update(float delta) {
|
|||||||
updateHotkeys();
|
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
|
// TODO: subscribe for setting change
|
||||||
EngineSettings& settings = engine->getSettings();
|
EngineSettings& settings = engine->getSettings();
|
||||||
controller->getPlayer()->camera->setFov(glm::radians(settings.camera.fov));
|
controller->getPlayer()->camera->setFov(glm::radians(settings.camera.fov));
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
"glm",
|
"glm",
|
||||||
"libspng",
|
"libspng",
|
||||||
"zlib",
|
"zlib",
|
||||||
"luajit"
|
"luajit",
|
||||||
|
"libvorbis"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user