Implement TSR audio
This commit is contained in:
parent
1f50bf2888
commit
8f59d76cf9
@ -46,6 +46,19 @@ set(COMMON_SOURCES
|
|||||||
set(CLIENT_ONLY_SOURCES
|
set(CLIENT_ONLY_SOURCES
|
||||||
"src/assets/mesh_builder.hpp"
|
"src/assets/mesh_builder.hpp"
|
||||||
"src/assets/mesh_builder.cpp"
|
"src/assets/mesh_builder.cpp"
|
||||||
|
"src/audio/defs.hpp"
|
||||||
|
"src/audio/master.hpp"
|
||||||
|
"src/audio/master.cpp"
|
||||||
|
"src/audio/ogg.hpp"
|
||||||
|
"src/audio/ogg.cpp"
|
||||||
|
"src/audio/player.hpp"
|
||||||
|
"src/audio/player.cpp"
|
||||||
|
"src/audio/sound_source.hpp"
|
||||||
|
"src/audio/sound_source.cpp"
|
||||||
|
"src/audio/sound.hpp"
|
||||||
|
"src/audio/sound.cpp"
|
||||||
|
"src/audio/source.hpp"
|
||||||
|
"src/audio/source.cpp"
|
||||||
"src/client/app.hpp"
|
"src/client/app.hpp"
|
||||||
"src/client/app.cpp"
|
"src/client/app.cpp"
|
||||||
"src/client/gl.hpp"
|
"src/client/gl.hpp"
|
||||||
@ -117,10 +130,14 @@ endif()
|
|||||||
# Include directories
|
# Include directories
|
||||||
target_include_directories(${MAIN_NAME} PRIVATE "src")
|
target_include_directories(${MAIN_NAME} PRIVATE "src")
|
||||||
|
|
||||||
|
target_compile_definitions(${MAIN_NAME} PRIVATE CLIENT)
|
||||||
|
|
||||||
|
# add glm
|
||||||
add_subdirectory(external/glm)
|
add_subdirectory(external/glm)
|
||||||
target_link_libraries(${MAIN_NAME} PRIVATE glm)
|
target_link_libraries(${MAIN_NAME} PRIVATE glm)
|
||||||
target_include_directories(${MAIN_NAME} PRIVATE "external/stb")
|
target_include_directories(${MAIN_NAME} PRIVATE "external/stb")
|
||||||
|
|
||||||
|
# add bullet3
|
||||||
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||||
set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "" FORCE)
|
set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "" FORCE)
|
||||||
set(BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
|
set(BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
@ -131,7 +148,12 @@ add_subdirectory(external/bullet3)
|
|||||||
target_link_libraries(${MAIN_NAME} PRIVATE BulletDynamics BulletCollision LinearMath Bullet3Common)
|
target_link_libraries(${MAIN_NAME} PRIVATE BulletDynamics BulletCollision LinearMath Bullet3Common)
|
||||||
target_include_directories(${MAIN_NAME} PRIVATE "external/bullet3/src")
|
target_include_directories(${MAIN_NAME} PRIVATE "external/bullet3/src")
|
||||||
|
|
||||||
target_compile_definitions(${MAIN_NAME} PRIVATE CLIENT)
|
# add ogg
|
||||||
|
add_subdirectory(external/ogg)
|
||||||
|
|
||||||
|
# add vorbis
|
||||||
|
add_subdirectory(external/vorbis)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE vorbisfile vorbis)
|
||||||
|
|
||||||
# Platform-specific SDL2 handling
|
# Platform-specific SDL2 handling
|
||||||
if (CMAKE_SYSTEM_NAME STREQUAL Emscripten)
|
if (CMAKE_SYSTEM_NAME STREQUAL Emscripten)
|
||||||
@ -196,6 +218,11 @@ endif()
|
|||||||
|
|
||||||
target_link_libraries(${MAIN_NAME} PRIVATE easywsclient)
|
target_link_libraries(${MAIN_NAME} PRIVATE easywsclient)
|
||||||
|
|
||||||
|
# add openal
|
||||||
|
set(ALSOFT_EXAMPLES OFF)
|
||||||
|
add_subdirectory(external/openal-soft)
|
||||||
|
target_link_libraries(${MAIN_NAME} PRIVATE OpenAL)
|
||||||
|
|
||||||
# build server
|
# build server
|
||||||
set(ASIO_INCLUDE_DIR "external/asio/include")
|
set(ASIO_INCLUDE_DIR "external/asio/include")
|
||||||
include_directories(${ASIO_INCLUDE_DIR})
|
include_directories(${ASIO_INCLUDE_DIR})
|
||||||
|
|||||||
@ -6,3 +6,4 @@ assets::MapCache assets::CacheManager::map_cache_;
|
|||||||
assets::VehicleCache assets::CacheManager::vehicle_cache_;
|
assets::VehicleCache assets::CacheManager::vehicle_cache_;
|
||||||
|
|
||||||
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
|
CLIENT_ONLY(assets::TextureCache assets::CacheManager::texture_cache_;)
|
||||||
|
CLIENT_ONLY(assets::SoundCache assets::CacheManager::sound_cache_;)
|
||||||
@ -8,6 +8,7 @@
|
|||||||
#include "utils/defs.hpp"
|
#include "utils/defs.hpp"
|
||||||
|
|
||||||
#ifdef CLIENT
|
#ifdef CLIENT
|
||||||
|
#include "audio/sound.hpp"
|
||||||
#include "gfx/texture.hpp"
|
#include "gfx/texture.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -49,6 +50,12 @@ class TextureCache final : public Cache<gfx::Texture>
|
|||||||
protected:
|
protected:
|
||||||
PtrType Load(const std::string& key) override { return gfx::Texture::LoadFromFile(key); }
|
PtrType Load(const std::string& key) override { return gfx::Texture::LoadFromFile(key); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SoundCache final : public Cache<audio::Sound>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
PtrType Load(const std::string& key) override { return audio::Sound::LoadFromFile(key); }
|
||||||
|
};
|
||||||
#endif // CLIENT
|
#endif // CLIENT
|
||||||
|
|
||||||
class SkeletonCache final : public Cache<Skeleton>
|
class SkeletonCache final : public Cache<Skeleton>
|
||||||
@ -87,13 +94,21 @@ public:
|
|||||||
|
|
||||||
static std::shared_ptr<const Map> GetMap(const std::string& filename) { return map_cache_.Get(filename); }
|
static std::shared_ptr<const Map> GetMap(const std::string& filename) { return map_cache_.Get(filename); }
|
||||||
|
|
||||||
static std::shared_ptr<const VehicleModel> GetVehicleModel(const std::string& filename) { return vehicle_cache_.Get(filename); }
|
static std::shared_ptr<const VehicleModel> GetVehicleModel(const std::string& filename)
|
||||||
|
{
|
||||||
|
return vehicle_cache_.Get(filename);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CLIENT
|
#ifdef CLIENT
|
||||||
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename)
|
static std::shared_ptr<const gfx::Texture> GetTexture(const std::string& filename)
|
||||||
{
|
{
|
||||||
return texture_cache_.Get(filename);
|
return texture_cache_.Get(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<const audio::Sound> GetSound(const std::string& filename)
|
||||||
|
{
|
||||||
|
return sound_cache_.Get(filename);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -102,6 +117,7 @@ private:
|
|||||||
static MapCache map_cache_;
|
static MapCache map_cache_;
|
||||||
static VehicleCache vehicle_cache_;
|
static VehicleCache vehicle_cache_;
|
||||||
CLIENT_ONLY(static TextureCache texture_cache_;)
|
CLIENT_ONLY(static TextureCache texture_cache_;)
|
||||||
|
CLIENT_ONLY(static SoundCache sound_cache_;)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace assets
|
} // namespace assets
|
||||||
3
src/audio/defs.hpp
Normal file
3
src/audio/defs.hpp
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define AUDIO_DBG(...) __VA_ARGS__
|
||||||
125
src/audio/master.cpp
Normal file
125
src/audio/master.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#include "master.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
|
||||||
|
#include "defs.hpp"
|
||||||
|
#include "sound_source.hpp"
|
||||||
|
#include "source.hpp"
|
||||||
|
|
||||||
|
static const size_t MAX_CATEGORIES = 32;
|
||||||
|
|
||||||
|
audio::Master::Master()
|
||||||
|
{
|
||||||
|
std::cout << "Initializing audio master...\n";
|
||||||
|
|
||||||
|
categories_.reserve(MAX_CATEGORIES);
|
||||||
|
|
||||||
|
device_ = alcOpenDevice(nullptr);
|
||||||
|
|
||||||
|
if (!device_)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("(AL) Failed to open audio device");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get device info
|
||||||
|
const ALchar* info = alcGetString(device_, ALC_DEVICE_SPECIFIER);
|
||||||
|
std::cout << "Opened audio device: " << (info ? info : "Unknown") << std::endl;
|
||||||
|
|
||||||
|
context_ = alcCreateContext(device_, nullptr);
|
||||||
|
|
||||||
|
if (!context_)
|
||||||
|
{
|
||||||
|
alcCloseDevice(device_);
|
||||||
|
device_ = nullptr;
|
||||||
|
throw std::runtime_error("(AL) Failed to create audio context");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Created audio context" << std::endl;
|
||||||
|
|
||||||
|
if (!alcMakeContextCurrent(context_))
|
||||||
|
{
|
||||||
|
alcDestroyContext(context_);
|
||||||
|
alcCloseDevice(device_);
|
||||||
|
context_ = nullptr;
|
||||||
|
device_ = nullptr;
|
||||||
|
throw std::runtime_error("(AL) Failed to make audio context current");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Audio context is now current" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Master::SetListenerOrientation(const glm::vec3& position, const glm::vec3& forward, const glm::vec3& up)
|
||||||
|
{
|
||||||
|
alListener3f(AL_POSITION, position.x, position.y, position.z);
|
||||||
|
// alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||||
|
ALfloat orientation[] = {forward.x, forward.y, forward.z, up.x, up.y, up.z};
|
||||||
|
alListenerfv(AL_ORIENTATION, orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Master::SetListenerOrientation(const glm::mat4& world_matrix)
|
||||||
|
{
|
||||||
|
glm::vec3 position = glm::vec3(world_matrix[3]);
|
||||||
|
glm::vec3 forward = -glm::normalize(glm::vec3(world_matrix[2]));
|
||||||
|
glm::vec3 up = glm::normalize(glm::vec3(world_matrix[1]));
|
||||||
|
SetListenerOrientation(position, forward, up);
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::Category* audio::Master::GetCategory(const std::string& name)
|
||||||
|
{
|
||||||
|
auto it = category_map_.find(name);
|
||||||
|
if (it != category_map_.end())
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categories_.size() >= MAX_CATEGORIES)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("(AL) Maximum number of audio categories reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
Category* new_category = &categories_.emplace_back(name);
|
||||||
|
category_map_[name] = new_category;
|
||||||
|
|
||||||
|
AUDIO_DBG(std::cout << "Created new audio category: " << name << std::endl;)
|
||||||
|
|
||||||
|
return new_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Master::SetMasterVolume(float volume)
|
||||||
|
{
|
||||||
|
master_volume_ = volume;
|
||||||
|
alListenerf(AL_GAIN, master_volume_);
|
||||||
|
AUDIO_DBG(std::cout << "Set master volume to " << master_volume_ << std::endl;)
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::Master::~Master()
|
||||||
|
{
|
||||||
|
std::cout << "Shutting down audio master...\n";
|
||||||
|
|
||||||
|
if (context_)
|
||||||
|
{
|
||||||
|
alcMakeContextCurrent(nullptr);
|
||||||
|
alcDestroyContext(context_);
|
||||||
|
context_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_)
|
||||||
|
{
|
||||||
|
alcCloseDevice(device_);
|
||||||
|
device_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::Category::Category(const std::string& name) : name_(name) {}
|
||||||
|
|
||||||
|
void audio::Category::SetVolume(float volume)
|
||||||
|
{
|
||||||
|
volume_ = volume;
|
||||||
|
for (Source* source = first_source_; source; source = source->cat_next_)
|
||||||
|
{
|
||||||
|
source->UpdateVolume();
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/audio/master.hpp
Normal file
63
src/audio/master.hpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "utils/defs.hpp"
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
class ALCdevice;
|
||||||
|
class ALCcontext;
|
||||||
|
|
||||||
|
namespace audio
|
||||||
|
{
|
||||||
|
|
||||||
|
class Source;
|
||||||
|
|
||||||
|
class Category
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Category(const std::string& name);
|
||||||
|
DELETE_COPY_MOVE(Category)
|
||||||
|
|
||||||
|
const std::string& GetName() const { return name_; }
|
||||||
|
|
||||||
|
void SetVolume(float volume);
|
||||||
|
float GetVolume() const { return volume_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
float volume_ = 1.0f;
|
||||||
|
std::string name_;
|
||||||
|
|
||||||
|
Source* first_source_ = nullptr;
|
||||||
|
friend class Source;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Master
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Master();
|
||||||
|
DELETE_COPY_MOVE(Master)
|
||||||
|
|
||||||
|
void SetListenerOrientation(const glm::vec3& position, const glm::vec3& forward, const glm::vec3& up);
|
||||||
|
void SetListenerOrientation(const glm::mat4& world_matrix);
|
||||||
|
|
||||||
|
Category* GetCategory(const std::string& name);
|
||||||
|
|
||||||
|
void SetMasterVolume(float volume);
|
||||||
|
float GetMasterVolume() const { return master_volume_; }
|
||||||
|
|
||||||
|
~Master();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ALCdevice* device_ = nullptr;
|
||||||
|
ALCcontext* context_ = nullptr;
|
||||||
|
|
||||||
|
std::vector<Category> categories_;
|
||||||
|
std::map<std::string, Category*> category_map_;
|
||||||
|
|
||||||
|
float master_volume_ = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
85
src/audio/ogg.cpp
Normal file
85
src/audio/ogg.cpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#include "ogg.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
audio::OggFile::OggFile(const char* filename)
|
||||||
|
{
|
||||||
|
int result = ov_fopen(filename, &ogg_file_);
|
||||||
|
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("(OGG) Failed to open OGG file: " + std::string(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadInfo();
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ov_clear(&ogg_file_);
|
||||||
|
throw std::runtime_error(std::string("(OGG) Error loading OGG file info: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t audio::OggFile::GetNumChannels() const
|
||||||
|
{
|
||||||
|
return vorbis_info_->channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t audio::OggFile::GetSampleRate() const
|
||||||
|
{
|
||||||
|
return vorbis_info_->rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t audio::OggFile::GetNumSamples()
|
||||||
|
{
|
||||||
|
return ov_pcm_total(&ogg_file_, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t audio::OggFile::Read(char* buffer, size_t length)
|
||||||
|
{
|
||||||
|
long res = ov_read(&ogg_file_, buffer, static_cast<int>(length), 0, 2, 1, NULL);
|
||||||
|
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Error reading OGG file");
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<size_t>(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> audio::OggFile::ReadAll()
|
||||||
|
{
|
||||||
|
size_t total_size = ov_pcm_total(&ogg_file_, -1) * vorbis_info_->channels * 2;
|
||||||
|
std::vector<char> buffer(total_size);
|
||||||
|
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
while (bytes_read < total_size)
|
||||||
|
{
|
||||||
|
size_t bytes_to_read = total_size - bytes_read;
|
||||||
|
size_t read = Read(buffer.data() + bytes_read, bytes_to_read);
|
||||||
|
|
||||||
|
if (read == 0)
|
||||||
|
{
|
||||||
|
break; // End of file reached
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_read += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::OggFile::~OggFile()
|
||||||
|
{
|
||||||
|
ov_clear(&ogg_file_); // Clear the OGG file resources
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::OggFile::LoadInfo()
|
||||||
|
{
|
||||||
|
vorbis_info_ = ov_info(&ogg_file_, -1); // Initialize the OGG file info
|
||||||
|
|
||||||
|
if (vorbis_info_->channels <= 0 || vorbis_info_->channels > 2)
|
||||||
|
throw std::runtime_error("Unsupported number of channels in OGG file");
|
||||||
|
}
|
||||||
36
src/audio/ogg.hpp
Normal file
36
src/audio/ogg.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "utils/defs.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <vorbis/vorbisfile.h>
|
||||||
|
|
||||||
|
// class OggVorbis_File;
|
||||||
|
// class vorbis_info;
|
||||||
|
|
||||||
|
namespace audio
|
||||||
|
{
|
||||||
|
|
||||||
|
class OggFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OggFile(const char* filename);
|
||||||
|
DELETE_COPY_MOVE(OggFile)
|
||||||
|
|
||||||
|
size_t GetNumChannels() const;
|
||||||
|
size_t GetSampleRate() const;
|
||||||
|
size_t GetNumSamples();
|
||||||
|
|
||||||
|
size_t Read(char* buffer, size_t length);
|
||||||
|
std::vector<char> ReadAll();
|
||||||
|
|
||||||
|
~OggFile();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void LoadInfo();
|
||||||
|
|
||||||
|
private:
|
||||||
|
OggVorbis_File ogg_file_;
|
||||||
|
vorbis_info* vorbis_info_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
30
src/audio/player.cpp
Normal file
30
src/audio/player.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "player.hpp"
|
||||||
|
|
||||||
|
audio::Player::Player(Master& master) : master_(master) {}
|
||||||
|
|
||||||
|
audio::SoundSource* audio::Player::PlaySound(const std::shared_ptr<const Sound>& sound,
|
||||||
|
const glm::vec3* attach_position)
|
||||||
|
{
|
||||||
|
SoundSource* source = new SoundSource(this, sound);
|
||||||
|
source->AttachToPosition(attach_position);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Player::Update()
|
||||||
|
{
|
||||||
|
Source* current = first_source_.get();
|
||||||
|
while (current)
|
||||||
|
{
|
||||||
|
current->Update();
|
||||||
|
|
||||||
|
Source* next = current->player_next_.get();
|
||||||
|
|
||||||
|
if (current->ShouldBeDeleted())
|
||||||
|
{
|
||||||
|
current->Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/audio/player.hpp
Normal file
32
src/audio/player.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "master.hpp"
|
||||||
|
#include "sound_source.hpp"
|
||||||
|
|
||||||
|
namespace audio
|
||||||
|
{
|
||||||
|
|
||||||
|
class Player
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Player(Master& master);
|
||||||
|
DELETE_COPY_MOVE(Player)
|
||||||
|
|
||||||
|
SoundSource* PlaySound(const std::shared_ptr<const Sound>& sound, const glm::vec3* attach_position);
|
||||||
|
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
Master& GetMaster() const { return master_; }
|
||||||
|
|
||||||
|
~Player() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Master& master_;
|
||||||
|
|
||||||
|
std::unique_ptr<Source> first_source_;
|
||||||
|
friend class Source;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
66
src/audio/sound.cpp
Normal file
66
src/audio/sound.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "sound.hpp"
|
||||||
|
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
|
||||||
|
#include "assets/cmdfile.hpp"
|
||||||
|
#include "utils/files.hpp"
|
||||||
|
|
||||||
|
#include "ogg.hpp"
|
||||||
|
|
||||||
|
audio::Sound::Sound()
|
||||||
|
{
|
||||||
|
alGenBuffers(1, &buffer_);
|
||||||
|
|
||||||
|
if (!buffer_)
|
||||||
|
throw std::runtime_error("Failed to generate OpenAL buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void LoadBufferOGG(ALuint buffer, const char* path)
|
||||||
|
{
|
||||||
|
audio::OggFile ogg_file(path);
|
||||||
|
std::vector<char> data = ogg_file.ReadAll();
|
||||||
|
|
||||||
|
ALenum format = ogg_file.GetNumChannels() == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
||||||
|
ALsizei sample_rate = ogg_file.GetSampleRate();
|
||||||
|
|
||||||
|
alBufferData(buffer, format, data.data(), static_cast<ALsizei>(data.size()), sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const audio::Sound> audio::Sound::LoadFromFile(const std::string& path)
|
||||||
|
{
|
||||||
|
auto sound = std::make_shared<Sound>();
|
||||||
|
|
||||||
|
std::string ogg_path;
|
||||||
|
|
||||||
|
assets::LoadCMDFile(path, [&](const std::string& cmd, std::istringstream& iss) {
|
||||||
|
if (cmd == "ogg")
|
||||||
|
{
|
||||||
|
iss >> ogg_path;
|
||||||
|
}
|
||||||
|
else if (cmd == "category")
|
||||||
|
{
|
||||||
|
iss >> sound->category_name_;
|
||||||
|
}
|
||||||
|
else if (cmd == "volume")
|
||||||
|
{
|
||||||
|
iss >> sound->volume_;
|
||||||
|
}
|
||||||
|
else if (cmd == "pitch")
|
||||||
|
{
|
||||||
|
iss >> sound->pitch_;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sound->category_name_.empty())
|
||||||
|
sound->category_name_ = "default";
|
||||||
|
|
||||||
|
LoadBufferOGG(sound->GetBufferId(), ogg_path.c_str());
|
||||||
|
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::Sound::~Sound()
|
||||||
|
{
|
||||||
|
alDeleteBuffers(1, &buffer_);
|
||||||
|
}
|
||||||
37
src/audio/sound.hpp
Normal file
37
src/audio/sound.hpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "master.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace audio
|
||||||
|
{
|
||||||
|
|
||||||
|
class Sound
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Sound();
|
||||||
|
friend std::shared_ptr<Sound> std::make_shared<Sound>();
|
||||||
|
|
||||||
|
public:
|
||||||
|
static std::shared_ptr<const Sound> LoadFromFile(const std::string& path);
|
||||||
|
|
||||||
|
unsigned int GetBufferId() const { return buffer_; }
|
||||||
|
|
||||||
|
const std::string& GetCategoryName() const { return category_name_; }
|
||||||
|
|
||||||
|
float GetVolume() const { return volume_; }
|
||||||
|
float GetPitch() const { return pitch_; }
|
||||||
|
|
||||||
|
~Sound();
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned int buffer_ = 0;
|
||||||
|
|
||||||
|
std::string category_name_;
|
||||||
|
|
||||||
|
float volume_ = 1.0f;
|
||||||
|
float pitch_ = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
67
src/audio/sound_source.cpp
Normal file
67
src/audio/sound_source.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "sound_source.hpp"
|
||||||
|
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
|
||||||
|
audio::SoundSource::SoundSource(Player* player, std::shared_ptr<const Sound> sound)
|
||||||
|
: Super(sound->GetCategoryName(), player), sound_(std::move(sound))
|
||||||
|
{
|
||||||
|
SetVolume(1.0f);
|
||||||
|
SetPitch(1.0f);
|
||||||
|
|
||||||
|
alSourcei(source_, AL_BUFFER, sound->GetBufferId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::SoundSource::SetLooping(bool looping)
|
||||||
|
{
|
||||||
|
alSourcei(source_, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
|
||||||
|
// TsrDebugf(DML_2, "Set source %p looping to %s\n", this, looping ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::SoundSource::SetPitch(float pitch)
|
||||||
|
{
|
||||||
|
Super::SetSourcePitch(sound_->GetPitch() * pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::SoundSource::SetVolume(float volume)
|
||||||
|
{
|
||||||
|
Super::SetSourceVolume(sound_->GetVolume() * volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::SoundSource::Update()
|
||||||
|
{
|
||||||
|
Super::Update();
|
||||||
|
|
||||||
|
ALint state;
|
||||||
|
alGetSourcei(source_, AL_SOURCE_STATE, &state);
|
||||||
|
|
||||||
|
finished_ = state == AL_STOPPED;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case AL_PLAYING: {
|
||||||
|
if (!should_play_)
|
||||||
|
alSourcePause(source_);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AL_INITIAL:
|
||||||
|
case AL_STOPPED:
|
||||||
|
case AL_PAUSED: {
|
||||||
|
if (should_play_)
|
||||||
|
alSourcePlay(source_);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::SoundSource::~SoundSource()
|
||||||
|
{
|
||||||
|
alSourceStop(source_);
|
||||||
|
alSourcei(source_, AL_BUFFER, 0);
|
||||||
|
}
|
||||||
29
src/audio/sound_source.hpp
Normal file
29
src/audio/sound_source.hpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sound.hpp"
|
||||||
|
#include "source.hpp"
|
||||||
|
|
||||||
|
namespace audio
|
||||||
|
{
|
||||||
|
|
||||||
|
class SoundSource : public Source
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
SoundSource(Player* player, std::shared_ptr<const Sound> sound);
|
||||||
|
|
||||||
|
virtual void SetLooping(bool looping) override;
|
||||||
|
virtual void SetPitch(float pitch) override;
|
||||||
|
virtual void SetVolume(float volume) override;
|
||||||
|
|
||||||
|
virtual void Update() override;
|
||||||
|
|
||||||
|
virtual ~SoundSource() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Super = Source;
|
||||||
|
|
||||||
|
std::shared_ptr<const Sound> sound_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
122
src/audio/source.cpp
Normal file
122
src/audio/source.cpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
#include "source.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
|
||||||
|
#include "defs.hpp"
|
||||||
|
#include "player.hpp"
|
||||||
|
|
||||||
|
audio::Source::Source(const std::string& category_name, Player* player)
|
||||||
|
{
|
||||||
|
alGenSources(1, &source_);
|
||||||
|
|
||||||
|
if (!source_)
|
||||||
|
throw std::runtime_error("Failed to create audio source");
|
||||||
|
|
||||||
|
// link to category
|
||||||
|
category_ = player->GetMaster().GetCategory(category_name);
|
||||||
|
cat_next_ = category_->first_source_;
|
||||||
|
category_->first_source_ = this;
|
||||||
|
cat_prev_next_ = &category_->first_source_;
|
||||||
|
if (cat_next_)
|
||||||
|
cat_next_->cat_prev_next_ = &cat_next_;
|
||||||
|
|
||||||
|
// link to player
|
||||||
|
player_ = player;
|
||||||
|
player_next_ = std::move(player->first_source_);
|
||||||
|
player->first_source_.reset(this);
|
||||||
|
player_prev_next_ = &player->first_source_;
|
||||||
|
if (player_next_)
|
||||||
|
player_next_->player_prev_next_ = &player_next_;
|
||||||
|
|
||||||
|
// TsrDebugf(DML_2, "Created audio source %p in category '%s'\n", this, category->GetName().c_str());
|
||||||
|
|
||||||
|
// alSourcef(m_source, AL_ROLLOFF_FACTOR, 0.0f); // disable rolloff
|
||||||
|
// alSourcef(m_source, AL_REFERENCE_DISTANCE, 1.0f); // set reference distance
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::SetPosition(const glm::vec3& position)
|
||||||
|
{
|
||||||
|
alSource3f(source_, AL_POSITION, position.x, position.y, position.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::SetVelocity(const glm::vec3& velocity)
|
||||||
|
{
|
||||||
|
alSource3f(source_, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::AttachToPosition(const glm::vec3* position)
|
||||||
|
{
|
||||||
|
attach_position_ = position;
|
||||||
|
if (attach_position_)
|
||||||
|
SetPosition(*attach_position_);
|
||||||
|
// TsrDebugf(DML_2, "Attached source %p to position %p\n", this, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::SetRelativeToListener(bool relative)
|
||||||
|
{
|
||||||
|
if (relative)
|
||||||
|
{
|
||||||
|
alSourcei(source_, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||||
|
// TsrDebugf(DML_2, "Set source %p to be relative to listener\n", this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alSourcei(source_, AL_SOURCE_RELATIVE, AL_FALSE);
|
||||||
|
// TsrDebugf(DML_2, "Set source %p to be absolute\n", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::Update()
|
||||||
|
{
|
||||||
|
if (attach_position_)
|
||||||
|
SetPosition(*attach_position_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::Delete()
|
||||||
|
{
|
||||||
|
// unlink from player (causes destruction)
|
||||||
|
if (player_next_)
|
||||||
|
{
|
||||||
|
player_next_->player_prev_next_ = player_prev_next_;
|
||||||
|
}
|
||||||
|
std::unique_ptr<Source>& player_prev_next = *player_prev_next_;
|
||||||
|
player_prev_next = std::move(player_next_);
|
||||||
|
|
||||||
|
// TsrDebugf(DML_2, "Deleted audio source %p\n", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::SetSourceVolume(float volume)
|
||||||
|
{
|
||||||
|
volume_ = volume;
|
||||||
|
UpdateVolume();
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::SetSourcePitch(float pitch)
|
||||||
|
{
|
||||||
|
alSourcef(source_, AL_PITCH, pitch);
|
||||||
|
// TsrDebugf(DML_2, "Set source %p pitch to %.2f\n", this, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::Source::UpdateVolume()
|
||||||
|
{
|
||||||
|
float cat_volume = category_->GetVolume();
|
||||||
|
float result_volume = volume_ * cat_volume;
|
||||||
|
alSourcef(source_, AL_GAIN, result_volume);
|
||||||
|
// TsrDebugf(DML_2, "Set source %p volume to %.2f (this %.2f, category %.2f)\n", this, result_volume, m_volume,
|
||||||
|
// cat_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::Source::~Source()
|
||||||
|
{
|
||||||
|
alDeleteSources(1, &source_);
|
||||||
|
|
||||||
|
// unlink from category
|
||||||
|
if (cat_next_)
|
||||||
|
{
|
||||||
|
cat_next_->cat_prev_next_ = cat_prev_next_;
|
||||||
|
}
|
||||||
|
*cat_prev_next_ = cat_next_;
|
||||||
|
}
|
||||||
69
src/audio/source.hpp
Normal file
69
src/audio/source.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "master.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace audio
|
||||||
|
{
|
||||||
|
|
||||||
|
class Player;
|
||||||
|
|
||||||
|
class Source
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Source(const std::string& category_name, Player* player);
|
||||||
|
|
||||||
|
public:
|
||||||
|
DELETE_COPY_MOVE(Source)
|
||||||
|
|
||||||
|
void SetPlay(bool play) { should_play_ = play; }
|
||||||
|
void Play() { SetPlay(true); }
|
||||||
|
void Stop() { SetPlay(false); }
|
||||||
|
|
||||||
|
void SetDeleteOnFinish(bool destroy) { delete_on_finish_ = destroy; }
|
||||||
|
|
||||||
|
void SetPosition(const glm::vec3& position);
|
||||||
|
void SetVelocity(const glm::vec3& velocity);
|
||||||
|
void AttachToPosition(const glm::vec3* position);
|
||||||
|
void SetRelativeToListener(bool relative);
|
||||||
|
|
||||||
|
virtual void SetLooping(bool looping) = 0;
|
||||||
|
virtual void SetPitch(float pitch) = 0;
|
||||||
|
virtual void SetVolume(float volume) = 0;
|
||||||
|
|
||||||
|
virtual void Update();
|
||||||
|
|
||||||
|
bool ShouldBeDeleted() { return finished_ && delete_on_finish_; }
|
||||||
|
|
||||||
|
void Delete();
|
||||||
|
|
||||||
|
virtual ~Source();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SetSourceVolume(float volume);
|
||||||
|
void SetSourcePitch(float pitch);
|
||||||
|
|
||||||
|
unsigned int source_ = 0;
|
||||||
|
|
||||||
|
const glm::vec3* attach_position_ = nullptr;
|
||||||
|
bool should_play_ = true; // auto play when created
|
||||||
|
bool finished_ = false;
|
||||||
|
bool delete_on_finish_ = true; // auto delete when finished
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdateVolume(); // on this or category volume change
|
||||||
|
|
||||||
|
friend class Category;
|
||||||
|
Category* category_ = nullptr;
|
||||||
|
Source** cat_prev_next_ = nullptr;
|
||||||
|
Source* cat_next_ = nullptr;
|
||||||
|
|
||||||
|
friend class Player;
|
||||||
|
Player* player_ = nullptr;
|
||||||
|
std::unique_ptr<Source>* player_prev_next_ = nullptr;
|
||||||
|
std::unique_ptr<Source> player_next_ = nullptr;
|
||||||
|
|
||||||
|
float volume_ = 1.0f; // volume set by source
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
Loading…
x
Reference in New Issue
Block a user