From 4db95f5728ed4505e73ec35bbe9a7e97684aa915 Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Tue, 13 Feb 2024 17:51:00 +0100 Subject: [PATCH] audio: first attempt at an audio streaming API --- include/lol/audio/stream | 17 +++ include/lol/private/audio/stream.h | 208 +++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 include/lol/audio/stream create mode 100644 include/lol/private/audio/stream.h diff --git a/include/lol/audio/stream b/include/lol/audio/stream new file mode 100644 index 00000000..8d2f3286 --- /dev/null +++ b/include/lol/audio/stream @@ -0,0 +1,17 @@ +// +// Lol Engine +// +// Copyright © 2010–2024 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#include "../private/push_macros.h" +#include "../private/audio/stream.h" +#include "../private/pop_macros.h" diff --git a/include/lol/private/audio/stream.h b/include/lol/private/audio/stream.h new file mode 100644 index 00000000..b1827f09 --- /dev/null +++ b/include/lol/private/audio/stream.h @@ -0,0 +1,208 @@ +// +// Lol Engine +// +// Copyright © 2010–2024 Sam Hocevar +// +// Lol Engine is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +// +// The audio stream interface +// —————————————————————————— +// Stream, mix, and apply audio effects. +// + +#include // std::function +#include // std::shared_ptr +#include // std::is_same_v +#include // std::unordered_set + +namespace lol::audio +{ + +template +class stream +{ +public: + using sample_type = T; + + virtual size_t get(T* buf, size_t frames) = 0; + + size_t channels() const { return m_channels; } + + int frequency() const { return m_frequency; } + + virtual ~stream() = default; + +protected: + stream(size_t channels, int frequency) + : m_channels(channels), + m_frequency(frequency) + {} + + size_t m_channels; + int m_frequency; +}; + +template +class generator : public stream +{ +public: + generator(std::function get, size_t channels, int frequency) + : stream(channels, frequency), + m_get(get) + {} + + virtual size_t get(T* buf, size_t frames) override + { + return m_get(buf, frames); + } + +protected: + std::function m_get; +}; + +template +class mixer : public stream +{ +public: + mixer(size_t channels, int frequency) + : stream(channels, frequency) + {} + + void add(std::shared_ptr> s) + { + // FIXME: check the channel count! + m_streamers.insert(s); + } + + void remove(std::shared_ptr> s) + { + m_streamers.erase(s); + } + + virtual size_t get(T *buf, size_t frames) override + { + memset(buf, 0, frames * this->channels() * sizeof(T)); + + std::vector tmp(frames * this->channels()); + for (auto s : m_streamers) + { + s->get(tmp.data(), frames); + for (size_t i = 0; i < tmp.size(); ++i) + buf[i] += tmp[i]; + } + + return frames; + } + +protected: + std::unordered_set>> m_streamers; +}; + +template +static inline T convert_sample(T0); + +template<> +inline float convert_sample(float x) { return x; } + +template<> +inline float convert_sample(int16_t x) { return x / 32768.0f; } + +template<> +inline float convert_sample(uint16_t x) { return x / 32767.0f - 1.0f; } + +template +class converter : public stream +{ +public: + converter(std::shared_ptr> s, size_t channels) + : stream(channels, s->frequency()), + s0(s) + {} + + virtual size_t get(T *buf, size_t frames) override + { + if constexpr(std::is_same_v) + if (this->channels() == s0->channels()) + return s0->get(buf, frames); + + std::vector tmp(frames * s0->channels()); + s0->get(tmp.data(), frames); + for (size_t f = 0; f < frames; ++f) + { + buf[f * this->channels()] = tmp[f * s0->channels()]; + for (size_t ch = 1; ch < this->channels(); ++ch) + { + size_t ch0 = std::max(size_t(0), s0->channels() - (this->channels() - ch)); + buf[f * this->channels() + ch] = convert_sample(tmp[f * s0->channels() + ch0]); + } + } + + return frames; + } + +protected: + std::shared_ptr> s0; +}; + +template +class resampler : public stream +{ +public: + resampler(std::shared_ptr> s, int frequency) + : stream(s->channels(), frequency), + s(s) + {} + + virtual size_t get(T *buf, size_t frames) override + { + if (s->frequency() == this->frequency()) + return s->get(buf, frames); + + // FIXME: implement resampling! + memset(buf, 0, frames * this->channels() * sizeof(T)); + return frames; + } + +protected: + std::shared_ptr> s; +}; + +template +static inline auto make_generator(std::function f, size_t channels, int frequency) +{ + return std::make_shared>(f, channels, frequency); +} + +template +static inline auto make_generator(F f, size_t channels, int frequency) +{ + return make_generator(std::function(f), channels, frequency); +} + +template +static inline auto make_converter(std::shared_ptr s, size_t channels) +{ + return std::make_shared>(std::shared_ptr>(s), channels); +} + +template +static inline auto make_resampler(std::shared_ptr s, int frequency) +{ + return std::make_shared>(std::shared_ptr>(s), frequency); +} + +template +static inline auto make_adapter(std::shared_ptr s, size_t channels, int frequency) +{ + return make_resampler(make_converter(s, channels), frequency); +} + +} // namespace lol::audio