Add audio system
This commit is contained in:
parent
0827103515
commit
94cccac9b2
14 changed files with 238 additions and 3 deletions
|
@ -14,6 +14,8 @@ find_package(SDL2 REQUIRED)
|
||||||
find_package(Vulkan REQUIRED)
|
find_package(Vulkan REQUIRED)
|
||||||
find_package(assimp REQUIRED)
|
find_package(assimp REQUIRED)
|
||||||
find_package(glm REQUIRED)
|
find_package(glm REQUIRED)
|
||||||
|
find_package(opus REQUIRED)
|
||||||
|
find_package(OpenAL REQUIRED)
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
add_definitions(-DDEBUG)
|
add_definitions(-DDEBUG)
|
||||||
|
@ -58,7 +60,8 @@ set(ENGINE_SRC
|
||||||
src/assetmanager.cpp
|
src/assetmanager.cpp
|
||||||
src/entityparser.cpp
|
src/entityparser.cpp
|
||||||
src/smaapass.cpp
|
src/smaapass.cpp
|
||||||
src/debugpass.cpp)
|
src/debugpass.cpp
|
||||||
|
src/audiosystem.cpp)
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
set(ENGINE_SRC
|
set(ENGINE_SRC
|
||||||
|
@ -78,7 +81,9 @@ target_link_libraries(Engine
|
||||||
assimp::assimp
|
assimp::assimp
|
||||||
nlohmann::json
|
nlohmann::json
|
||||||
stb::stb
|
stb::stb
|
||||||
SMAA::SMAA)
|
SMAA::SMAA
|
||||||
|
opus::opus
|
||||||
|
OpenAL::OpenAL)
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
target_link_libraries(Engine
|
target_link_libraries(Engine
|
||||||
|
@ -131,6 +136,7 @@ add_data(ITER
|
||||||
data/sphere.obj
|
data/sphere.obj
|
||||||
data/matpreview.world
|
data/matpreview.world
|
||||||
data/basic.material
|
data/basic.material
|
||||||
data/maticon.png)
|
data/maticon.png
|
||||||
|
data/music.opus)
|
||||||
|
|
||||||
add_subdirectory(tools)
|
add_subdirectory(tools)
|
||||||
|
|
47
cmake/Findopus.cmake
Normal file
47
cmake/Findopus.cmake
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# - FindOpus.cmake
|
||||||
|
# Find the native opus includes and libraries
|
||||||
|
#
|
||||||
|
# OPUS_INCLUDE_DIRS - where to find opus/opus.h, etc.
|
||||||
|
# OPUS_LIBRARIES - List of libraries when using libopus(file).
|
||||||
|
# OPUS_FOUND - True if libopus found.
|
||||||
|
|
||||||
|
if(OPUS_INCLUDE_DIR AND OPUS_LIBRARY AND OPUSFILE_LIBRARY)
|
||||||
|
# Already in cache, be silent
|
||||||
|
set(OPUS_FIND_QUIETLY TRUE)
|
||||||
|
endif(OPUS_INCLUDE_DIR AND OPUS_LIBRARY AND OPUSFILE_LIBRARY)
|
||||||
|
|
||||||
|
find_path(OPUS_INCLUDE_DIR
|
||||||
|
NAMES opusfile.h
|
||||||
|
PATH_SUFFIXES opus include/opus include
|
||||||
|
)
|
||||||
|
|
||||||
|
# MSVC built opus may be named opus_static
|
||||||
|
# The provided project files name the library with the lib prefix.
|
||||||
|
find_library(OPUS_LIBRARY
|
||||||
|
NAMES opus opus_static libopus libopus_static
|
||||||
|
PATH_SUFFIXES lib64 lib libs64 libs
|
||||||
|
)
|
||||||
|
find_library(OPUSFILE_LIBRARY
|
||||||
|
NAMES opusfile opusfile_static libopusfile libopusfile_static
|
||||||
|
PATH_SUFFIXES lib64 lib libs64 libs
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle the QUIETLY and REQUIRED arguments and set OPUS_FOUND
|
||||||
|
# to TRUE if all listed variables are TRUE.
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(OPUS DEFAULT_MSG
|
||||||
|
OPUSFILE_LIBRARY OPUS_LIBRARY OPUS_INCLUDE_DIR
|
||||||
|
)
|
||||||
|
|
||||||
|
if(OPUS_FOUND)
|
||||||
|
set(OPUS_LIBRARIES ${OPUSFILE_LIBRARY} ${OPUS_LIBRARY})
|
||||||
|
set(OPUS_INCLUDE_DIRS ${OPUS_INCLUDE_DIR})
|
||||||
|
|
||||||
|
add_library(opus::opus UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(opus::opus PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${OPUSFILE_LIBRARY}")
|
||||||
|
|
||||||
|
set_target_properties(opus::opus PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES ${OPUS_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
endif(OPUS_FOUND)
|
BIN
data/music.opus
Normal file
BIN
data/music.opus
Normal file
Binary file not shown.
|
@ -48,6 +48,9 @@
|
||||||
{
|
{
|
||||||
"type": "Camera",
|
"type": "Camera",
|
||||||
"fov": 75
|
"fov": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Listener"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -137,6 +140,9 @@
|
||||||
{
|
{
|
||||||
"type": "Camera",
|
"type": "Camera",
|
||||||
"fov": 75
|
"fov": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Listener"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
"type": "Mesh",
|
"type": "Mesh",
|
||||||
"path": "scene.obj",
|
"path": "scene.obj",
|
||||||
"material": "test.material"
|
"material": "test.material"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Speaker",
|
||||||
|
"file": "music.opus"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,18 +5,26 @@
|
||||||
struct MeshAsset;
|
struct MeshAsset;
|
||||||
struct MaterialAsset;
|
struct MaterialAsset;
|
||||||
class Renderer;
|
class Renderer;
|
||||||
|
struct AudioAsset;
|
||||||
|
class AudioSystem;
|
||||||
|
|
||||||
class AssetManager {
|
class AssetManager {
|
||||||
public:
|
public:
|
||||||
void setRenderer(Renderer* r) {
|
void setRenderer(Renderer* r) {
|
||||||
renderer = r;
|
renderer = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAudioSystem(AudioSystem* a) {
|
||||||
|
audioSystem = a;
|
||||||
|
}
|
||||||
|
|
||||||
MeshAsset* loadMesh(const std::string& path);
|
MeshAsset* loadMesh(const std::string& path);
|
||||||
MaterialAsset* loadMaterial(const std::string& path);
|
MaterialAsset* loadMaterial(const std::string& path);
|
||||||
|
AudioAsset* loadAudio(const std::string& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Renderer* renderer = nullptr;
|
Renderer* renderer = nullptr;
|
||||||
|
AudioSystem* audioSystem = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline AssetManager assetManager;
|
inline AssetManager assetManager;
|
||||||
|
|
12
include/audio.h
Normal file
12
include/audio.h
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct AudioAsset {
|
||||||
|
int16_t* data = nullptr;
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
|
int numChannels = 1;
|
||||||
|
|
||||||
|
unsigned int buffer = 0;
|
||||||
|
};
|
18
include/audiosystem.h
Normal file
18
include/audiosystem.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AL/alc.h>
|
||||||
|
|
||||||
|
struct AudioAsset;
|
||||||
|
|
||||||
|
class AudioSystem {
|
||||||
|
public:
|
||||||
|
AudioSystem();
|
||||||
|
|
||||||
|
void update();
|
||||||
|
|
||||||
|
void fillAudioBuffers(AudioAsset* asset);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ALCdevice* device = nullptr;
|
||||||
|
ALCcontext* context = nullptr;
|
||||||
|
};
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
@ -41,6 +42,18 @@ struct CameraComponent {
|
||||||
glm::vec3 target = glm::vec3(0);
|
glm::vec3 target = glm::vec3(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ListenerComponent {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioAsset;
|
||||||
|
|
||||||
|
struct SpeakerComponent {
|
||||||
|
AudioAsset* audio = nullptr;
|
||||||
|
unsigned int source = 0;
|
||||||
|
bool playing = false;
|
||||||
|
};
|
||||||
|
|
||||||
using EntityID = uint64_t;
|
using EntityID = uint64_t;
|
||||||
|
|
||||||
struct World;
|
struct World;
|
||||||
|
@ -52,6 +65,8 @@ namespace ECS {
|
||||||
inline std::map<EntityID, MeshComponent*> meshes;
|
inline std::map<EntityID, MeshComponent*> meshes;
|
||||||
inline std::map<EntityID, LightComponent*> lights;
|
inline std::map<EntityID, LightComponent*> lights;
|
||||||
inline std::map<EntityID, CameraComponent*> cameras;
|
inline std::map<EntityID, CameraComponent*> cameras;
|
||||||
|
inline std::map<EntityID, ListenerComponent*> listeners;
|
||||||
|
inline std::map<EntityID, SpeakerComponent*> speakers;
|
||||||
|
|
||||||
inline std::map<EntityID, ::World*> worlds;
|
inline std::map<EntityID, ::World*> worlds;
|
||||||
inline EntityID lastID = 1;
|
inline EntityID lastID = 1;
|
||||||
|
@ -71,6 +86,12 @@ namespace ECS {
|
||||||
|
|
||||||
for(const auto& [id, camera] : cameras)
|
for(const auto& [id, camera] : cameras)
|
||||||
delete camera;
|
delete camera;
|
||||||
|
|
||||||
|
for(const auto& [id, listener] : listeners)
|
||||||
|
delete listener;
|
||||||
|
|
||||||
|
for(const auto& [id, speaker] : speakers)
|
||||||
|
delete speaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline EntityID createEntity(World* world) {
|
static inline EntityID createEntity(World* world) {
|
||||||
|
@ -97,6 +118,10 @@ namespace ECS {
|
||||||
lights[id] = t;
|
lights[id] = t;
|
||||||
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
||||||
cameras[id] = t;
|
cameras[id] = t;
|
||||||
|
} else if constexpr(std::is_same<T, ListenerComponent>::value) {
|
||||||
|
listeners[id] = t;
|
||||||
|
} else if constexpr(std::is_same<T, SpeakerComponent>::value) {
|
||||||
|
speakers[id] = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
|
@ -114,6 +139,10 @@ namespace ECS {
|
||||||
return lights[id];
|
return lights[id];
|
||||||
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
||||||
return cameras[id];
|
return cameras[id];
|
||||||
|
} else if constexpr(std::is_same<T, ListenerComponent>::value) {
|
||||||
|
return listeners[id];
|
||||||
|
} else if constexpr(std::is_same<T, SpeakerComponent>::value) {
|
||||||
|
return speakers[id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +187,10 @@ namespace ECS {
|
||||||
lights.erase(lights.find(id), lights.end());
|
lights.erase(lights.find(id), lights.end());
|
||||||
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
||||||
cameras.erase(cameras.find(id), cameras.end());
|
cameras.erase(cameras.find(id), cameras.end());
|
||||||
|
} else if constexpr(std::is_same<T, ListenerComponent>::value) {
|
||||||
|
listeners.erase(listeners.find(id), listeners.end());
|
||||||
|
} else if constexpr(std::is_same<T, SpeakerComponent>::value) {
|
||||||
|
speakers.erase(speakers.find(id), speakers.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,11 +6,14 @@
|
||||||
#include <assimp/Importer.hpp>
|
#include <assimp/Importer.hpp>
|
||||||
#include <assimp/scene.h>
|
#include <assimp/scene.h>
|
||||||
#include <assimp/postprocess.h>
|
#include <assimp/postprocess.h>
|
||||||
|
#include <opus/opusfile.h>
|
||||||
|
|
||||||
#include "mesh.h"
|
#include "mesh.h"
|
||||||
#include "material.h"
|
#include "material.h"
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "stringutils.h"
|
#include "stringutils.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "audiosystem.h"
|
||||||
|
|
||||||
MeshAsset* AssetManager::loadMesh(const std::string& path) {
|
MeshAsset* AssetManager::loadMesh(const std::string& path) {
|
||||||
std::string fixedPath = "data/" + path;
|
std::string fixedPath = "data/" + path;
|
||||||
|
@ -78,3 +81,34 @@ MaterialAsset* AssetManager::loadMaterial(const std::string& path) {
|
||||||
|
|
||||||
return material;
|
return material;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioAsset* AssetManager::loadAudio(const std::string& path) {
|
||||||
|
std::string fixedPath = "data/" + path;
|
||||||
|
|
||||||
|
AudioAsset* audio = new AudioAsset();
|
||||||
|
|
||||||
|
OggOpusFile* file = op_open_file(fixedPath.c_str(), nullptr);
|
||||||
|
if(!file)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
audio->numChannels = op_channel_count(file, -1);
|
||||||
|
int pcmSize = op_pcm_total(file, -1);
|
||||||
|
|
||||||
|
audio->data = new int16_t[pcmSize * audio->numChannels];
|
||||||
|
|
||||||
|
int samplesRead = 0;
|
||||||
|
while(samplesRead < pcmSize) {
|
||||||
|
int ns = op_read(file, audio->data + samplesRead * audio->numChannels, pcmSize * audio->numChannels, 0);
|
||||||
|
|
||||||
|
samplesRead += ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
op_free(file);
|
||||||
|
|
||||||
|
audio->size = samplesRead * audio->numChannels * 2;
|
||||||
|
|
||||||
|
audioSystem->fillAudioBuffers(audio);
|
||||||
|
|
||||||
|
return audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
52
src/audiosystem.cpp
Normal file
52
src/audiosystem.cpp
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#include "audiosystem.h"
|
||||||
|
|
||||||
|
#include <AL/al.h>
|
||||||
|
|
||||||
|
#include "ecs.h"
|
||||||
|
#include "worldmanager.h"
|
||||||
|
#include "audio.h"
|
||||||
|
|
||||||
|
AudioSystem::AudioSystem() {
|
||||||
|
device = alcOpenDevice(nullptr);
|
||||||
|
context = alcCreateContext(device, 0);
|
||||||
|
|
||||||
|
alcMakeContextCurrent(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSystem::update() {
|
||||||
|
const auto listeners = ECS::getWorldComponents<ListenerComponent>(worldManager.getCurrentWorld());
|
||||||
|
|
||||||
|
if(listeners.size() > 0) {
|
||||||
|
const auto& [id, listener] = listeners[0];
|
||||||
|
const auto& transform = ECS::getComponent<TransformComponent>(id);
|
||||||
|
|
||||||
|
alListener3f(AL_POSITION, transform->position.x, transform->position.y, transform->position.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& speakers = ECS::getWorldComponents<SpeakerComponent>(worldManager.getCurrentWorld());
|
||||||
|
|
||||||
|
for(const auto& [id, speaker] : speakers) {
|
||||||
|
if(speaker->source == 0) {
|
||||||
|
alGenSources(1, &speaker->source);
|
||||||
|
alSourcei(speaker->source, AL_BUFFER, speaker->audio->buffer);
|
||||||
|
|
||||||
|
alSourcePlay(speaker->source);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& transform = ECS::getComponent<TransformComponent>(id);
|
||||||
|
|
||||||
|
alSource3f(speaker->source, AL_POSITION, transform->position.x, transform->position.y, transform->position.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioSystem::fillAudioBuffers(AudioAsset* asset) {
|
||||||
|
alGenBuffers(1, &asset->buffer);
|
||||||
|
|
||||||
|
ALenum format;
|
||||||
|
if(asset->numChannels == 1)
|
||||||
|
format = AL_FORMAT_MONO16;
|
||||||
|
else if(asset->numChannels == 2)
|
||||||
|
format = AL_FORMAT_STEREO16;
|
||||||
|
|
||||||
|
alBufferData(asset->buffer, format, asset->data, asset->size, 48000);
|
||||||
|
}
|
|
@ -37,6 +37,11 @@ EntityID parseEntity(const nlohmann::json& json, World* world) {
|
||||||
light->type = componentObject["kind"];
|
light->type = componentObject["kind"];
|
||||||
} else if(componentType == "Camera") {
|
} else if(componentType == "Camera") {
|
||||||
ECS::addComponent<CameraComponent>(entity);
|
ECS::addComponent<CameraComponent>(entity);
|
||||||
|
} else if(componentType == "Listener") {
|
||||||
|
ECS::addComponent<ListenerComponent>(entity);
|
||||||
|
} else if(componentType == "Speaker") {
|
||||||
|
auto listener = ECS::addComponent<SpeakerComponent>(entity);
|
||||||
|
listener->audio = assetManager.loadAudio(componentObject["file"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "stringutils.h"
|
#include "stringutils.h"
|
||||||
#include "worldmanager.h"
|
#include "worldmanager.h"
|
||||||
#include "assetmanager.h"
|
#include "assetmanager.h"
|
||||||
|
#include "audiosystem.h"
|
||||||
|
|
||||||
SDL_Window* window = nullptr;
|
SDL_Window* window = nullptr;
|
||||||
Renderer* renderer = nullptr;
|
Renderer* renderer = nullptr;
|
||||||
|
@ -141,7 +142,10 @@ int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
RenderTarget* target = renderer->createSurfaceRenderTarget(surface);
|
RenderTarget* target = renderer->createSurfaceRenderTarget(surface);
|
||||||
|
|
||||||
|
auto audio = new AudioSystem();
|
||||||
|
|
||||||
assetManager.setRenderer(renderer);
|
assetManager.setRenderer(renderer);
|
||||||
|
assetManager.setAudioSystem(audio);
|
||||||
|
|
||||||
AnimationSystem* animationSystem = new AnimationSystem();
|
AnimationSystem* animationSystem = new AnimationSystem();
|
||||||
|
|
||||||
|
@ -169,6 +173,8 @@ int main(int argc, char* argv[]) {
|
||||||
MeshComponent* mesh = ECS::addComponent<MeshComponent>(playerEntity);
|
MeshComponent* mesh = ECS::addComponent<MeshComponent>(playerEntity);
|
||||||
mesh->mesh = assetManager.loadMesh("player.obj");
|
mesh->mesh = assetManager.loadMesh("player.obj");
|
||||||
mesh->material = assetManager.loadMaterial("test.material");
|
mesh->material = assetManager.loadMaterial("test.material");
|
||||||
|
|
||||||
|
ListenerComponent* listener = ECS::addComponent<ListenerComponent>(playerEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
float currentTime = 0.0f, lastTime = 0.0f;
|
float currentTime = 0.0f, lastTime = 0.0f;
|
||||||
|
@ -238,6 +244,8 @@ int main(int argc, char* argv[]) {
|
||||||
cameraComponent->target = playerTransform->position;
|
cameraComponent->target = playerTransform->position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audio->update();
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "entityparser.h"
|
#include "entityparser.h"
|
||||||
#include "ecs.h"
|
#include "ecs.h"
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
|
#include "mesh.h"
|
||||||
|
#include "material.h"
|
||||||
|
|
||||||
void WorldManager::loadWorld(const std::string& path) {
|
void WorldManager::loadWorld(const std::string& path) {
|
||||||
std::ifstream file("data/" + path);
|
std::ifstream file("data/" + path);
|
||||||
|
|
Reference in a new issue