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(assimp REQUIRED)
|
||||
find_package(glm REQUIRED)
|
||||
find_package(opus REQUIRED)
|
||||
find_package(OpenAL REQUIRED)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_definitions(-DDEBUG)
|
||||
|
@ -58,7 +60,8 @@ set(ENGINE_SRC
|
|||
src/assetmanager.cpp
|
||||
src/entityparser.cpp
|
||||
src/smaapass.cpp
|
||||
src/debugpass.cpp)
|
||||
src/debugpass.cpp
|
||||
src/audiosystem.cpp)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(ENGINE_SRC
|
||||
|
@ -78,7 +81,9 @@ target_link_libraries(Engine
|
|||
assimp::assimp
|
||||
nlohmann::json
|
||||
stb::stb
|
||||
SMAA::SMAA)
|
||||
SMAA::SMAA
|
||||
opus::opus
|
||||
OpenAL::OpenAL)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_link_libraries(Engine
|
||||
|
@ -131,6 +136,7 @@ add_data(ITER
|
|||
data/sphere.obj
|
||||
data/matpreview.world
|
||||
data/basic.material
|
||||
data/maticon.png)
|
||||
data/maticon.png
|
||||
data/music.opus)
|
||||
|
||||
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",
|
||||
"fov": 75
|
||||
},
|
||||
{
|
||||
"type": "Listener"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -137,6 +140,9 @@
|
|||
{
|
||||
"type": "Camera",
|
||||
"fov": 75
|
||||
},
|
||||
{
|
||||
"type": "Listener"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
"type": "Mesh",
|
||||
"path": "scene.obj",
|
||||
"material": "test.material"
|
||||
},
|
||||
{
|
||||
"type": "Speaker",
|
||||
"file": "music.opus"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -5,18 +5,26 @@
|
|||
struct MeshAsset;
|
||||
struct MaterialAsset;
|
||||
class Renderer;
|
||||
struct AudioAsset;
|
||||
class AudioSystem;
|
||||
|
||||
class AssetManager {
|
||||
public:
|
||||
void setRenderer(Renderer* r) {
|
||||
renderer = r;
|
||||
}
|
||||
|
||||
void setAudioSystem(AudioSystem* a) {
|
||||
audioSystem = a;
|
||||
}
|
||||
|
||||
MeshAsset* loadMesh(const std::string& path);
|
||||
MaterialAsset* loadMaterial(const std::string& path);
|
||||
AudioAsset* loadAudio(const std::string& path);
|
||||
|
||||
private:
|
||||
Renderer* renderer = nullptr;
|
||||
AudioSystem* audioSystem = nullptr;
|
||||
};
|
||||
|
||||
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 <map>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
#include <type_traits>
|
||||
|
@ -41,6 +42,18 @@ struct CameraComponent {
|
|||
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;
|
||||
|
||||
struct World;
|
||||
|
@ -52,6 +65,8 @@ namespace ECS {
|
|||
inline std::map<EntityID, MeshComponent*> meshes;
|
||||
inline std::map<EntityID, LightComponent*> lights;
|
||||
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 EntityID lastID = 1;
|
||||
|
@ -71,6 +86,12 @@ namespace ECS {
|
|||
|
||||
for(const auto& [id, camera] : cameras)
|
||||
delete camera;
|
||||
|
||||
for(const auto& [id, listener] : listeners)
|
||||
delete listener;
|
||||
|
||||
for(const auto& [id, speaker] : speakers)
|
||||
delete speaker;
|
||||
}
|
||||
|
||||
static inline EntityID createEntity(World* world) {
|
||||
|
@ -97,6 +118,10 @@ namespace ECS {
|
|||
lights[id] = t;
|
||||
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
||||
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;
|
||||
|
@ -114,6 +139,10 @@ namespace ECS {
|
|||
return lights[id];
|
||||
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
||||
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());
|
||||
} else if constexpr(std::is_same<T, CameraComponent>::value) {
|
||||
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/scene.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <opus/opusfile.h>
|
||||
|
||||
#include "mesh.h"
|
||||
#include "material.h"
|
||||
#include "renderer.h"
|
||||
#include "stringutils.h"
|
||||
#include "audio.h"
|
||||
#include "audiosystem.h"
|
||||
|
||||
MeshAsset* AssetManager::loadMesh(const std::string& path) {
|
||||
std::string fixedPath = "data/" + path;
|
||||
|
@ -78,3 +81,34 @@ MaterialAsset* AssetManager::loadMaterial(const std::string& path) {
|
|||
|
||||
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"];
|
||||
} else if(componentType == "Camera") {
|
||||
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 "worldmanager.h"
|
||||
#include "assetmanager.h"
|
||||
#include "audiosystem.h"
|
||||
|
||||
SDL_Window* window = nullptr;
|
||||
Renderer* renderer = nullptr;
|
||||
|
@ -141,7 +142,10 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
RenderTarget* target = renderer->createSurfaceRenderTarget(surface);
|
||||
|
||||
auto audio = new AudioSystem();
|
||||
|
||||
assetManager.setRenderer(renderer);
|
||||
assetManager.setAudioSystem(audio);
|
||||
|
||||
AnimationSystem* animationSystem = new AnimationSystem();
|
||||
|
||||
|
@ -169,6 +173,8 @@ int main(int argc, char* argv[]) {
|
|||
MeshComponent* mesh = ECS::addComponent<MeshComponent>(playerEntity);
|
||||
mesh->mesh = assetManager.loadMesh("player.obj");
|
||||
mesh->material = assetManager.loadMaterial("test.material");
|
||||
|
||||
ListenerComponent* listener = ECS::addComponent<ListenerComponent>(playerEntity);
|
||||
}
|
||||
|
||||
float currentTime = 0.0f, lastTime = 0.0f;
|
||||
|
@ -238,6 +244,8 @@ int main(int argc, char* argv[]) {
|
|||
cameraComponent->target = playerTransform->position;
|
||||
}
|
||||
|
||||
audio->update();
|
||||
|
||||
#ifdef DEBUG
|
||||
ImGui::NewFrame();
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "entityparser.h"
|
||||
#include "ecs.h"
|
||||
#include "renderer.h"
|
||||
#include "mesh.h"
|
||||
#include "material.h"
|
||||
|
||||
void WorldManager::loadWorld(const std::string& path) {
|
||||
std::ifstream file("data/" + path);
|
||||
|
|
Reference in a new issue