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