#include #include #include #include #include #include #include #include #include #include #include namespace psemek::audio { namespace { struct track_data { util::span samples; std::vector storage; }; struct track_stream_impl : stream { track_stream_impl(std::shared_ptr data) : data_(std::move(data)) {} std::optional length() const override { return data_->samples.size(); } std::size_t read(float * data, std::size_t sample_count) override { auto played = played_.load(); std::size_t result = std::min(sample_count, data_->samples.size() - played); std::copy(data_->samples.data() + played, data_->samples.data() + played + result, data); played_.fetch_add(result); return result; } std::size_t played() const override { return played_.load(); } private: std::shared_ptr data_; std::atomic played_{0}; }; struct track_impl : track { track_impl(std::shared_ptr data) : data_(std::move(data)) {} stream_ptr stream() const override { return std::make_shared(data_); } std::size_t length() const override { return data_->samples.size(); } private: std::shared_ptr data_; }; } struct engine::impl { std::shared_ptr sdl_init; SDL_AudioDeviceID device; std::vector buffer; bool thread_registered = false; channel_ptr output; impl(); ~impl(); static void callback(void * userdata, std::uint8_t * stream, int len); }; engine::impl::impl() : sdl_init(sdl2::init(SDL_INIT_AUDIO)) { SDL_AudioSpec desired, obtained; desired.freq = frequency; desired.channels = 2; desired.format = AUDIO_S16SYS; desired.samples = 256; desired.callback = &callback; desired.userdata = this; if (device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); device == 0) sdl2::fail("SDL_OpenAudioDevice failed:"); buffer.resize(obtained.samples * obtained.channels); SDL_PauseAudioDevice(device, 0); log::info() << "Initialized audio: " << static_cast(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples"; output = std::make_shared(); } engine::impl::~impl() { SDL_CloseAudioDevice(device); } void engine::impl::callback(void * userdata, std::uint8_t * dst_u8, int len) { static std::string const profiler_str = "audio"; prof::profiler prof(profiler_str); auto self = static_cast(userdata); stream_ptr output = self->output->stream(); std::int16_t * dst = reinterpret_cast(dst_u8); if (!self->thread_registered) { log::register_thread("audio"); self->thread_registered = true; } std::size_t const size = len / 2; std::size_t read = 0; if (output) read = output->read(self->buffer.data(), size); std::fill(self->buffer.data() + read, self->buffer.data() + size, 0.f); for (auto s : self->buffer) *dst++ = static_cast(std::max(std::min((65535.f * s - 1.f) / 2.f, 32767.f), -32768.f)); } engine::engine() : pimpl_(make_impl()) {} engine::~engine() = default; track_ptr engine::load_raw(float const * data, std::size_t sample_count, bool copy) { if ((sample_count % 2) != 0) throw std::runtime_error("bad sample count"); auto tdata = std::make_shared(); if (copy) { tdata->storage.assign(data, data + sample_count); tdata->samples = tdata->storage; } else tdata->samples = {data, data + sample_count}; return std::make_shared(std::move(tdata)); } track_ptr engine::load_raw(util::span data, bool copy) { return load_raw(data.data(), data.size(), copy); } channel_ptr engine::output() { return impl().output; } }