psemek/libs/audio/source/engine.cpp

286 lines
5.6 KiB
C++

#include <psemek/audio/engine.hpp>
#include <psemek/sdl2/init.hpp>
#include <psemek/log/log.hpp>
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include <mutex>
#include <vector>
#include <optional>
#include <cmath>
namespace psemek::audio
{
namespace
{
[[noreturn]] void mix_fail(std::string const & message)
{
throw std::runtime_error(message + Mix_GetError());
}
struct sdl2_mixer_initializer
{
sdl2_mixer_initializer()
{
if (Mix_Init(MIX_INIT_MP3) != MIX_INIT_MP3)
mix_fail("Failed to load MP3 support: ");
}
~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;
std::vector<std::shared_ptr<effect>> effects;
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
{
effects.push_back(e);
Mix_RegisterEffect(channel, &effect_func, nullptr, e.get());
}
void clear_effects() override
{
effects.clear();
Mix_UnregisterAllEffects(channel);
}
static void effect_func(int, void * data, int len, void * udata)
{
auto e = reinterpret_cast<effect *>(udata);
if ((len % 2) != 0)
{
log::error() << "Cannot apply " << e->name() << " effect: number of bytes not even";
return;
}
(*e)(reinterpret_cast<std::int16_t *>(data), len / 2);
}
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(engine::frequency, AUDIO_S16SYS, engine::channels, 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 * sizeof(std::int16_t);
chunk->volume = 128;
if (copy)
{
chunk->abuf = static_cast<Uint8 *>(malloc(sample_count * sizeof(std::int16_t)));
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 start, bool loop)
{
auto str = impl().play(std::move(s), loop);
if (start) str->start();
return str;
}
}