Audio subsystem (wip)
This commit is contained in:
parent
89a608e35f
commit
6549c2f907
7 changed files with 390 additions and 0 deletions
19
cmake/modules/FindSDL2_mixer.cmake
Normal file
19
cmake/modules/FindSDL2_mixer.cmake
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
if(SDL2_MIXER_FOUND)
|
||||||
|
set(SDL2_MIXER_FIND_QUIETLY TRUE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_path(SDL2_MIXER_INCLUDE_DIRS NAMES "SDL2/SDL_mixer.h" PATHS "${SDL2_MIXER_ROOT}/include")
|
||||||
|
find_library(SDL2_MIXER_LIBRARIES NAMES "SDL2_mixer" PATHS "${SDL2_MIXER_ROOT}/lib")
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(SDL2_MIXER DEFAULT_MSG SDL2_MIXER_INCLUDE_DIRS SDL2_MIXER_LIBRARIES)
|
||||||
|
|
||||||
|
if(SDL2_MIXER_FOUND AND NOT TARGET SDL2_mixer)
|
||||||
|
add_library(SDL2_mixer SHARED IMPORTED)
|
||||||
|
set_target_properties(SDL2_mixer PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${SDL2_MIXER_LIBRARIES}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIRS}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
mark_as_advanced(SDL2_MIXER_INCLUDE_DIRS SDL2_MIXER_LIBRARIES)
|
||||||
8
libs/audio/CMakeLists.txt
Normal file
8
libs/audio/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
find_package(SDL2_mixer REQUIRED)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE PSEMEK_AUDIO_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
|
||||||
|
file(GLOB_RECURSE PSEMEK_AUDIO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
|
||||||
|
|
||||||
|
add_library(psemek-audio ${PSEMEK_AUDIO_HEADERS} ${PSEMEK_AUDIO_SOURCES})
|
||||||
|
target_include_directories(psemek-audio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(psemek-audio PUBLIC psemek-sdl2 psemek-log psemek-util SDL2_mixer)
|
||||||
19
libs/audio/include/psemek/audio/effect.hpp
Normal file
19
libs/audio/include/psemek/audio/effect.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace psemek::audio
|
||||||
|
{
|
||||||
|
|
||||||
|
struct effect
|
||||||
|
{
|
||||||
|
virtual std::string_view name() const = 0;
|
||||||
|
|
||||||
|
virtual void operator()(std::int16_t * data, std::size_t count) = 0;
|
||||||
|
|
||||||
|
virtual ~effect() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
30
libs/audio/include/psemek/audio/engine.hpp
Normal file
30
libs/audio/include/psemek/audio/engine.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/audio/effect.hpp>
|
||||||
|
#include <psemek/audio/stream.hpp>
|
||||||
|
#include <psemek/audio/sample.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace psemek::audio
|
||||||
|
{
|
||||||
|
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
engine();
|
||||||
|
~engine();
|
||||||
|
|
||||||
|
std::shared_ptr<sample> load_raw(std::int16_t const * data, std::size_t sample_count, bool copy = true);
|
||||||
|
std::shared_ptr<sample> load_wav(char const * data, std::size_t size);
|
||||||
|
|
||||||
|
std::shared_ptr<stream> play(std::shared_ptr<sample> s, bool loop = false);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct impl;
|
||||||
|
std::shared_ptr<impl> pimpl_;
|
||||||
|
struct impl & impl() { return *pimpl_; }
|
||||||
|
struct impl const & impl() const { return *pimpl_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
16
libs/audio/include/psemek/audio/sample.hpp
Normal file
16
libs/audio/include/psemek/audio/sample.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace psemek::audio
|
||||||
|
{
|
||||||
|
|
||||||
|
struct sample
|
||||||
|
{
|
||||||
|
virtual std::int16_t const * data() const = 0;
|
||||||
|
virtual std::size_t size() const = 0;
|
||||||
|
|
||||||
|
virtual ~sample(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
28
libs/audio/include/psemek/audio/stream.hpp
Normal file
28
libs/audio/include/psemek/audio/stream.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/audio/effect.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace psemek::audio
|
||||||
|
{
|
||||||
|
|
||||||
|
struct stream
|
||||||
|
{
|
||||||
|
// value \in [0, 1]
|
||||||
|
virtual void volume(float value) = 0;
|
||||||
|
|
||||||
|
virtual void push_effect(std::shared_ptr<effect> e) = 0;
|
||||||
|
virtual void clear_effects() = 0;
|
||||||
|
|
||||||
|
// NB: stop() effectively destroys the stream
|
||||||
|
virtual void start() = 0;
|
||||||
|
virtual void pause() = 0;
|
||||||
|
virtual void resume() = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
virtual bool is_playing() const = 0;
|
||||||
|
|
||||||
|
virtual ~stream() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
270
libs/audio/source/engine.cpp
Normal file
270
libs/audio/source/engine.cpp
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
#include <psemek/audio/engine.hpp>
|
||||||
|
#include <psemek/sdl2/init.hpp>
|
||||||
|
#include <psemek/log/log.hpp>
|
||||||
|
#include <psemek/util/not_implemented.hpp>
|
||||||
|
#include <psemek/util/unused.hpp>
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <SDL2/SDL_mixer.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace psemek::audio
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
static const int frequency = 44100;
|
||||||
|
|
||||||
|
[[noreturn]] void mix_fail(std::string const & message)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(message + Mix_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sdl2_mixer_initializer
|
||||||
|
{
|
||||||
|
sdl2_mixer_initializer()
|
||||||
|
{
|
||||||
|
Mix_Init(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
~sdl2_mixer_initializer()
|
||||||
|
{
|
||||||
|
Mix_Quit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sample_impl
|
||||||
|
: sample
|
||||||
|
{
|
||||||
|
Mix_Chunk * chunk;
|
||||||
|
|
||||||
|
sample_impl(Mix_Chunk * chunk)
|
||||||
|
: chunk(chunk)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::int16_t const * data() const override
|
||||||
|
{
|
||||||
|
return reinterpret_cast<std::int16_t const *>(chunk->abuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const override
|
||||||
|
{
|
||||||
|
return chunk->alen;
|
||||||
|
}
|
||||||
|
|
||||||
|
~sample_impl()
|
||||||
|
{
|
||||||
|
Mix_FreeChunk(chunk);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct stream_impl
|
||||||
|
: stream
|
||||||
|
{
|
||||||
|
bool playing = false;
|
||||||
|
|
||||||
|
int channel;
|
||||||
|
std::shared_ptr<sample_impl> sample;
|
||||||
|
bool loop;
|
||||||
|
|
||||||
|
stream_impl(int channel, std::shared_ptr<sample_impl> sample, bool loop)
|
||||||
|
: channel(channel)
|
||||||
|
, sample(sample)
|
||||||
|
, loop(loop)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void volume(float value)
|
||||||
|
{
|
||||||
|
int v = std::min(128, std::max(0, static_cast<int>(std::round(value * MIX_MAX_VOLUME))));
|
||||||
|
Mix_Volume(channel, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_effect(std::shared_ptr<effect> e) override
|
||||||
|
{
|
||||||
|
unused(e);
|
||||||
|
util::not_implemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_effects() override
|
||||||
|
{
|
||||||
|
util::not_implemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() override
|
||||||
|
{
|
||||||
|
playing = true;
|
||||||
|
Mix_PlayChannel(channel, sample->chunk, loop ? -1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause() override
|
||||||
|
{
|
||||||
|
playing = false;
|
||||||
|
Mix_Pause(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resume() override
|
||||||
|
{
|
||||||
|
Mix_Resume(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() override
|
||||||
|
{
|
||||||
|
Mix_HaltChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_playing() const override
|
||||||
|
{
|
||||||
|
return playing;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct engine::impl
|
||||||
|
{
|
||||||
|
std::shared_ptr<void> sdl_init;
|
||||||
|
sdl2_mixer_initializer mix_init;
|
||||||
|
|
||||||
|
struct channel
|
||||||
|
{
|
||||||
|
std::shared_ptr<stream_impl> stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex channels_mutex;
|
||||||
|
std::vector<channel> channels;
|
||||||
|
|
||||||
|
static std::mutex instance_mutex;
|
||||||
|
static std::weak_ptr<impl> instance_ptr;
|
||||||
|
static std::shared_ptr<impl> instance();
|
||||||
|
|
||||||
|
impl();
|
||||||
|
~impl();
|
||||||
|
|
||||||
|
std::shared_ptr<stream> play(std::shared_ptr<sample> s, bool loop);
|
||||||
|
|
||||||
|
static void channel_finished(int ch);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex engine::impl::instance_mutex;
|
||||||
|
std::weak_ptr<struct engine::impl> engine::impl::instance_ptr;
|
||||||
|
|
||||||
|
std::shared_ptr<struct engine::impl> engine::impl::instance()
|
||||||
|
{
|
||||||
|
std::lock_guard lock{instance_mutex};
|
||||||
|
|
||||||
|
if (auto p = instance_ptr.lock(); p)
|
||||||
|
return p;
|
||||||
|
|
||||||
|
auto p = std::make_shared<struct impl>();
|
||||||
|
instance_ptr = p;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine::impl::impl()
|
||||||
|
: sdl_init(sdl2::init(SDL_INIT_AUDIO))
|
||||||
|
{
|
||||||
|
if (Mix_OpenAudio(frequency, AUDIO_S16SYS, 2, 4096) != 0)
|
||||||
|
mix_fail("Mix_OpenAudio: ");
|
||||||
|
|
||||||
|
Mix_ChannelFinished(&channel_finished);
|
||||||
|
|
||||||
|
log::info() << "Initialized audio";
|
||||||
|
}
|
||||||
|
|
||||||
|
engine::impl::~impl()
|
||||||
|
{
|
||||||
|
for (auto & ch : channels)
|
||||||
|
if (ch.stream)
|
||||||
|
ch.stream->stop();
|
||||||
|
Mix_CloseAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<stream> engine::impl::play(std::shared_ptr<sample> s, bool loop)
|
||||||
|
{
|
||||||
|
auto ss = std::dynamic_pointer_cast<sample_impl>(s);
|
||||||
|
if (!ss)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to play sample: unknown sample type");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::lock_guard lock{channels_mutex};
|
||||||
|
|
||||||
|
std::optional<int> ch;
|
||||||
|
|
||||||
|
int c;
|
||||||
|
for (c = 0; c < static_cast<int>(channels.size()); ++c)
|
||||||
|
{
|
||||||
|
if (!channels[c].stream)
|
||||||
|
{
|
||||||
|
ch = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ch)
|
||||||
|
{
|
||||||
|
channels.resize(channels.empty() ? 16 : channels.size() * 2);
|
||||||
|
Mix_AllocateChannels(channels.size());
|
||||||
|
ch = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto str = std::make_shared<stream_impl>(*ch, std::move(ss), loop);
|
||||||
|
|
||||||
|
channels[*ch].stream = str;
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void engine::impl::channel_finished(int ch)
|
||||||
|
{
|
||||||
|
std::shared_ptr<impl> self;
|
||||||
|
{
|
||||||
|
std::lock_guard lock{instance_mutex};
|
||||||
|
self = instance_ptr.lock();
|
||||||
|
}
|
||||||
|
if (!self) return;
|
||||||
|
|
||||||
|
std::lock_guard lock{self->channels_mutex};
|
||||||
|
self->channels[ch].stream = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine::engine()
|
||||||
|
: pimpl_{impl::instance()}
|
||||||
|
{}
|
||||||
|
|
||||||
|
engine::~engine()
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::shared_ptr<sample> engine::load_wav(char const * data, std::size_t size)
|
||||||
|
{
|
||||||
|
return std::make_shared<sample_impl>(Mix_LoadWAV_RW(SDL_RWFromConstMem(data, size), 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<sample> engine::load_raw(std::int16_t const * data, std::size_t sample_count, bool copy)
|
||||||
|
{
|
||||||
|
Mix_Chunk * chunk = static_cast<Mix_Chunk *>(malloc(sizeof(Mix_Chunk)));
|
||||||
|
chunk->allocated = copy ? 1 : 0;
|
||||||
|
chunk->alen = sample_count * 2;
|
||||||
|
chunk->volume = 128;
|
||||||
|
if (copy)
|
||||||
|
{
|
||||||
|
chunk->abuf = static_cast<Uint8 *>(malloc(sample_count * 2));
|
||||||
|
std::copy(data, data + sample_count, reinterpret_cast<std::int16_t *>(chunk->abuf));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chunk->abuf = const_cast<Uint8 *>(reinterpret_cast<Uint8 const *>(data));
|
||||||
|
}
|
||||||
|
return std::make_shared<sample_impl>(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<stream> engine::play(std::shared_ptr<sample> s, bool loop)
|
||||||
|
{
|
||||||
|
return impl().play(std::move(s), loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue