#include #include #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::optional length() const override { return data_->samples.size(); } private: std::shared_ptr data_; }; std::vector convert_audio(SDL_AudioSpec const & spec, std::uint8_t * samples, std::size_t length) { if (spec.channels > 2) throw std::runtime_error(util::to_string("Can't convert audio with ", static_cast(spec.channels), " channels")); if (spec.freq != 44100) throw std::runtime_error(util::to_string("Can't convert audio with frequency ", spec.freq)); if (spec.format != AUDIO_S16SYS) throw std::runtime_error(util::to_string("Can't convert audio with format ", spec.format)); auto p = reinterpret_cast(samples); std::vector result; if (spec.channels == 1) { result.resize(length); for (std::size_t i = 0; i < length / 2; ++i) { float v = (p[i] * 2.f + 1.f) / 65536.f; result[2 * i + 0] = v; result[2 * i + 1] = v; } } else { result.resize(length / 2); for (std::size_t i = 0; i < length / 2; ++i) result[i] = (p[i] * 2.f + 1.f) / 65536.f; } return result; } } 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(util::span data, bool copy) { if ((data.size() % 2) != 0) throw std::runtime_error("bad sample count"); auto tdata = std::make_shared(); if (copy) { tdata->storage.assign(data.begin(), data.end()); tdata->samples = tdata->storage; } else tdata->samples = data; return std::make_shared(std::move(tdata)); } track_ptr engine::load_wav(util::span data) { SDL_AudioSpec spec; std::uint8_t * samples; std::uint32_t length; if (!SDL_LoadWAV_RW(SDL_RWFromConstMem(data.data(), data.size()), 1, &spec, &samples, &length)) sdl2::fail("SDL_LoadWAV_RW failed:"); util::at_scope_exit release_samples([samples]{ SDL_FreeWAV(samples); }); auto tdata = std::make_shared(); tdata->storage = convert_audio(spec, samples, length); tdata->samples = tdata->storage; return std::make_shared(std::move(tdata)); } channel_ptr engine::output() { return impl().output; } }