#include #include #include #include #include #include #include #include #include #include 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(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; bool loop; stream_impl(int channel, std::shared_ptr sample, bool loop) : channel(channel) , sample(sample) , loop(loop) {} void volume(float value) { int v = std::min(128, std::max(0, static_cast(std::round(value * MIX_MAX_VOLUME)))); Mix_Volume(channel, v); } void push_effect(std::shared_ptr 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 sdl_init; sdl2_mixer_initializer mix_init; struct channel { std::shared_ptr stream; }; std::mutex channels_mutex; std::vector channels; static std::mutex instance_mutex; static std::weak_ptr instance_ptr; static std::shared_ptr instance(); impl(); ~impl(); std::shared_ptr play(std::shared_ptr s, bool loop); static void channel_finished(int ch); }; std::mutex engine::impl::instance_mutex; std::weak_ptr engine::impl::instance_ptr; std::shared_ptr engine::impl::instance() { std::lock_guard lock{instance_mutex}; if (auto p = instance_ptr.lock(); p) return p; auto p = std::make_shared(); 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 engine::impl::play(std::shared_ptr s, bool loop) { auto ss = std::dynamic_pointer_cast(s); if (!ss) { throw std::runtime_error("Failed to play sample: unknown sample type"); } std::lock_guard lock{channels_mutex}; std::optional ch; int c; for (c = 0; c < static_cast(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(*ch, std::move(ss), loop); channels[*ch].stream = str; return str; } void engine::impl::channel_finished(int ch) { std::shared_ptr 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 engine::load_wav(char const * data, std::size_t size) { return std::make_shared(Mix_LoadWAV_RW(SDL_RWFromConstMem(data, size), 1)); } std::shared_ptr engine::load_raw(std::int16_t const * data, std::size_t sample_count, bool copy) { Mix_Chunk * chunk = static_cast(malloc(sizeof(Mix_Chunk))); chunk->allocated = copy ? 1 : 0; chunk->alen = sample_count * 2; chunk->volume = 128; if (copy) { chunk->abuf = static_cast(malloc(sample_count * 2)); std::copy(data, data + sample_count, reinterpret_cast(chunk->abuf)); } else { chunk->abuf = const_cast(reinterpret_cast(data)); } return std::make_shared(chunk); } std::shared_ptr engine::play(std::shared_ptr s, bool loop) { return impl().play(std::move(s), loop); } }