diff --git a/examples/audio.cpp b/examples/audio.cpp new file mode 100644 index 00000000..932b783e --- /dev/null +++ b/examples/audio.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace psemek; + +static std::map const key_to_midi +{ + {SDLK_a, 69}, + {SDLK_s, 70}, + {SDLK_d, 71}, + {SDLK_f, 72}, + {SDLK_g, 73}, + {SDLK_h, 74}, + {SDLK_j, 75}, + {SDLK_k, 76}, + {SDLK_l, 77}, + {SDLK_SEMICOLON, 78}, + {SDLK_QUOTE, 79}, +}; + +struct audio_app + : app::app +{ + audio_app() + : app::app("Audio example") + { + mixer_ = audio::make_mixer(); + engine_.output(mixer_); + } + + void on_key_down(SDL_Keycode key) override + { + app::app::on_key_down(key); + + if (key_to_midi.contains(key) && !channels_.contains(key)) + { + int midi = key_to_midi.at(key); + channels_[key] = mixer_->add(audio::sine_wave(440.f * std::pow(2.f, (midi - 69) / 12.f))); + } + } + + void on_key_up(SDL_Keycode key) override + { + app::app::on_key_up(key); + + if (channels_.contains(key)) + { + channels_[key]->stop(); + channels_.erase(key); + } + } + +private: + audio::engine engine_; + audio::mixer_ptr mixer_; + std::map channels_; +}; + +int main() +{ + return app::main(); +} diff --git a/libs/audio/include/psemek/audio/3d.hpp b/libs/audio/include/psemek/audio/3d.hpp deleted file mode 100644 index 0e1d27df..00000000 --- a/libs/audio/include/psemek/audio/3d.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -namespace psemek::audio -{ - - struct listener_3d_mono - { - void set_position(geom::point const & position); - - struct mono_effect - : effect - { - virtual void set_position(geom::point const & position) = 0; - }; - - virtual std::shared_ptr make_effect(geom::point const & position, float distance_factor); - - private: - geom::point position_; - std::mutex position_mutex_; - }; - -} diff --git a/libs/audio/include/psemek/audio/constants.hpp b/libs/audio/include/psemek/audio/constants.hpp new file mode 100644 index 00000000..ac5ae90e --- /dev/null +++ b/libs/audio/include/psemek/audio/constants.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace psemek::audio +{ + + constexpr int frequency = 44100; + constexpr float inv_frequency = 1.f / frequency; + +} diff --git a/libs/audio/include/psemek/audio/echo.hpp b/libs/audio/include/psemek/audio/echo.hpp deleted file mode 100644 index d21fa65a..00000000 --- a/libs/audio/include/psemek/audio/echo.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#include - -namespace psemek::audio -{ - - std::shared_ptr echo(float delay, float volume); - -} diff --git a/libs/audio/include/psemek/audio/effect.hpp b/libs/audio/include/psemek/audio/effect.hpp deleted file mode 100644 index ef49401d..00000000 --- a/libs/audio/include/psemek/audio/effect.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace psemek::audio -{ - - struct effect - { - virtual std::string_view name() const = 0; - - virtual void init(int frequency, int channels) = 0; - - virtual void operator()(sample * data, std::size_t count) = 0; - - virtual ~effect() {} - }; - -} diff --git a/libs/audio/include/psemek/audio/engine.hpp b/libs/audio/include/psemek/audio/engine.hpp index d33cac7d..ab51e87b 100644 --- a/libs/audio/include/psemek/audio/engine.hpp +++ b/libs/audio/include/psemek/audio/engine.hpp @@ -1,11 +1,10 @@ #pragma once -#include -#include #include #include #include +#include #include #include @@ -19,31 +18,13 @@ namespace psemek::audio engine(); ~engine(); - int frequency() const; - int channels() const; + track_ptr load(float const * data, std::size_t sample_count, bool copy = true); + track_ptr load(util::span data, bool copy = true); - std::shared_ptr load_raw(sample const * data, std::size_t sample_count, bool copy = true); - std::shared_ptr load(char const * data, std::size_t size); - - std::shared_ptr load_raw(std::basic_string_view data, bool copy = true) - { - return load_raw(data.data(), data.size(), copy); - } - - std::shared_ptr load(std::string_view data) - { - return load(data.data(), data.size()); - } - - std::shared_ptr load_raw(std::vector data); - - std::shared_ptr play(std::shared_ptr s, bool start = true, bool loop = false); - - void lock(); - void unlock(); + stream_ptr output(stream_ptr stream); private: - psemek_declare_shared_pimpl + psemek_declare_pimpl }; } diff --git a/libs/audio/include/psemek/audio/mixer.hpp b/libs/audio/include/psemek/audio/mixer.hpp new file mode 100644 index 00000000..7f401c88 --- /dev/null +++ b/libs/audio/include/psemek/audio/mixer.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +namespace psemek::audio +{ + + struct mixer + : stream + { + struct channel + { + virtual void stop() = 0; + + virtual bool is_stopped() const = 0; + + virtual ~channel() {} + }; + + using channel_ptr = std::shared_ptr; + + virtual channel_ptr add(stream_ptr stream) = 0; + }; + + using mixer_ptr = std::shared_ptr; + + mixer_ptr make_mixer(); + +} diff --git a/libs/audio/include/psemek/audio/sample.hpp b/libs/audio/include/psemek/audio/sample.hpp deleted file mode 100644 index fa821a3c..00000000 --- a/libs/audio/include/psemek/audio/sample.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace psemek::audio -{ - - using sample = std::int16_t; - - inline sample clamp(float v) - { - static auto const min = std::numeric_limits::min(); - static auto const max = std::numeric_limits::max(); - - return static_cast(std::min(max, std::max(min, std::round(v)))); - } - - // maps [-1, 1] range to samples - inline sample pack(float v) - { - static auto const min = static_cast(std::numeric_limits::min()); - static auto const max = static_cast(std::numeric_limits::max()); - - return clamp(min + (v * 0.5f + 0.5f) * (max - min)); - } - - // maps samples to [-1, 1] range - inline float unpack(sample s) - { - static auto const min = static_cast(std::numeric_limits::min()); - static auto const max = static_cast(std::numeric_limits::max()); - - return 2.f * (s - min) / (max - min) - 1.f; - } - -} diff --git a/libs/audio/include/psemek/audio/stream.hpp b/libs/audio/include/psemek/audio/stream.hpp index e76b2d8c..323d6d24 100644 --- a/libs/audio/include/psemek/audio/stream.hpp +++ b/libs/audio/include/psemek/audio/stream.hpp @@ -1,9 +1,6 @@ #pragma once -#include - -#include - +#include #include namespace psemek::audio @@ -11,22 +8,12 @@ namespace psemek::audio struct stream { - // value \in [0, 1] - virtual void volume(float value) = 0; - - virtual void push_effect(std::shared_ptr e) = 0; - virtual void clear_effects() = 0; - - // NB: stop() effectively destroys the stream - virtual void start() = 0; - virtual void pause() = 0; - virtual void resume() = 0; - virtual void stop() = 0; - virtual bool is_playing() const = 0; - - virtual void on_stop(util::function callback) = 0; + // Return value less than sample count means end of stream + virtual std::size_t read(float * data, std::size_t sample_count) = 0; virtual ~stream() {} }; + using stream_ptr = std::shared_ptr; + } diff --git a/libs/audio/include/psemek/audio/track.hpp b/libs/audio/include/psemek/audio/track.hpp index 7ad6bf27..e2aad14e 100644 --- a/libs/audio/include/psemek/audio/track.hpp +++ b/libs/audio/include/psemek/audio/track.hpp @@ -1,16 +1,18 @@ #pragma once -#include +#include namespace psemek::audio { struct track { - virtual sample const * data() const = 0; - virtual std::size_t size() const = 0; + virtual stream_ptr stream() const = 0; + virtual std::size_t length() const = 0; virtual ~track(){} }; + using track_ptr = std::shared_ptr; + } diff --git a/libs/audio/include/psemek/audio/wave/generator.hpp b/libs/audio/include/psemek/audio/wave/generator.hpp new file mode 100644 index 00000000..441b0f3b --- /dev/null +++ b/libs/audio/include/psemek/audio/wave/generator.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace psemek::audio +{ + + template + struct generator_stream + : stream + { + generator_stream(Func func) + : func_(std::move(func)) + , phase_(0.f) + {} + + std::size_t read(float * data, std::size_t sample_count) override + { + auto end = data + sample_count; + for (auto p = data; p != end;) + { + float v = func_(phase_); + *p++ = v; + *p++ = v; + phase_ += inv_frequency; + } + return sample_count; + } + + private: + Func func_; + float phase_; + }; + +} diff --git a/libs/audio/include/psemek/audio/wave/sawtooth.hpp b/libs/audio/include/psemek/audio/wave/sawtooth.hpp new file mode 100644 index 00000000..652f298b --- /dev/null +++ b/libs/audio/include/psemek/audio/wave/sawtooth.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace psemek::audio +{ + + stream_ptr sawtooth_wave(float frequency); + +} diff --git a/libs/audio/include/psemek/audio/wave/sine.hpp b/libs/audio/include/psemek/audio/wave/sine.hpp new file mode 100644 index 00000000..c083c276 --- /dev/null +++ b/libs/audio/include/psemek/audio/wave/sine.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace psemek::audio +{ + + stream_ptr sine_wave(float frequency); + +} diff --git a/libs/audio/include/psemek/audio/wave/square.hpp b/libs/audio/include/psemek/audio/wave/square.hpp new file mode 100644 index 00000000..83cc9575 --- /dev/null +++ b/libs/audio/include/psemek/audio/wave/square.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace psemek::audio +{ + + stream_ptr square_wave(float frequency); + +} diff --git a/libs/audio/include/psemek/audio/wave/triangle.hpp b/libs/audio/include/psemek/audio/wave/triangle.hpp new file mode 100644 index 00000000..69bb6e0b --- /dev/null +++ b/libs/audio/include/psemek/audio/wave/triangle.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace psemek::audio +{ + + stream_ptr triangle_wave(float frequency); + +} diff --git a/libs/audio/source/3d.cpp b/libs/audio/source/3d.cpp deleted file mode 100644 index f58f8852..00000000 --- a/libs/audio/source/3d.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include - -#include -#include - -namespace psemek::audio -{ - - namespace - { - - struct mono_effect_impl - : listener_3d_mono::mono_effect - { - mono_effect_impl(geom::point const & source_position, geom::point & target_position, std::mutex & target_position_mutex, float distance_factor) - : source_position_(source_position) - , target_position_(target_position) - , cached_target_position_(target_position) - , target_position_mutex_(target_position_mutex) - , distance_factor_(distance_factor) - {} - - std::string_view name() const override - { - return "mono_3d"; - } - - void init(int, int) override - { } - - void operator()(sample * data, std::size_t count) override - { - { - std::unique_lock lock{target_position_mutex_, std::try_to_lock}; - if (lock.owns_lock()) - cached_target_position_ = target_position_; - } - - { - std::unique_lock lock{source_position_mutex_, std::try_to_lock}; - if (lock.owns_lock()) - cached_source_position_ = source_position_; - } - - float distance = geom::distance(cached_target_position_, cached_source_position_) / distance_factor_; - float factor = std::min(1.f, 1.f / (1.f + distance * distance)); - - for (std::size_t i = 0; i < count; ++i) - data[i] = pack(unpack(data[i]) * factor); - } - - void set_position(geom::point const & position) override - { - std::lock_guard lock{source_position_mutex_}; - source_position_ = position; - } - - private: - std::mutex source_position_mutex_; - geom::point source_position_; - geom::point cached_source_position_; - - geom::point & target_position_; - geom::point cached_target_position_; - std::mutex & target_position_mutex_; - float distance_factor_; - }; - - } - - void listener_3d_mono::set_position(geom::point const & position) - { - std::lock_guard lock{position_mutex_}; - position_ = position; - } - - std::shared_ptr listener_3d_mono::make_effect(const geom::point &position, float distance_factor) - { - return std::make_shared(position, position_, position_mutex_, distance_factor); - } - -} diff --git a/libs/audio/source/echo.cpp b/libs/audio/source/echo.cpp deleted file mode 100644 index c4a58c62..00000000 --- a/libs/audio/source/echo.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include -#include - -#include -#include - -namespace psemek::audio -{ - - namespace - { - - struct echo_impl - : audio::effect - { - echo_impl(float delay, float volume) - : delay_(delay) - , volume_(volume) - {} - - void init(int frequency, int channels) override - { - int sample_count = std::round(delay_ * frequency) * channels; - samples_.reserve(sample_count); - } - - std::string_view name() const override - { - return "echo"; - } - - void operator()(sample * data, std::size_t size) override - { - if (samples_.size() < samples_.capacity()) - { - auto count = std::min(samples_.capacity() - samples_.size(), size); - std::copy(data, data + count, std::back_inserter(samples_)); - pos_ += count; - data += count; - size -= count; - - if (pos_ == samples_.capacity()) - pos_ = 0; - } - - for (; size > 0; ++data, --size) - { - float s = clamp((*data) + samples_[pos_] * volume_); - samples_[pos_] = *data; - *data = s; - - ++pos_; - if (pos_ == samples_.capacity()) - pos_ = 0; - } - } - - private: - std::vector samples_; - std::size_t pos_ = 0; - float delay_; - float volume_; - }; - - } - - std::shared_ptr echo(float delay, float volume) - { - return std::make_shared(delay, volume); - } - -} diff --git a/libs/audio/source/engine.cpp b/libs/audio/source/engine.cpp index c726cbfb..8aeba41d 100644 --- a/libs/audio/source/engine.cpp +++ b/libs/audio/source/engine.cpp @@ -1,11 +1,12 @@ #include +#include #include #include #include -#include #include +#include #include #include #include @@ -13,349 +14,96 @@ 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 spec_t - { - int frequency = 44100; - int channels = 2; - }; - - struct track_impl - : track - { - Mix_Chunk * chunk; - std::vector buffer; - - track_impl(Mix_Chunk * chunk) - : chunk(chunk) - {} - - track_impl(Mix_Chunk * chunk, std::vector buffer) - : chunk(chunk) - , buffer(std::move(buffer)) - {} - - sample const * data() const override - { - return reinterpret_cast(chunk->abuf); - } - - std::size_t size() const override - { - return chunk->alen / 2; - } - - ~track_impl() - { - Mix_FreeChunk(chunk); - } - }; - - struct stream_impl - : stream - { - bool playing = false; - - int channel; - std::shared_ptr track; - bool loop; - - spec_t spec; - - std::vector> effects; - - util::function callback; - - stream_impl(int channel, std::shared_ptr track, bool loop, spec_t spec) - : channel(channel) - , track(track) - , loop(loop) - , spec(spec) - {} - - void volume(float value) override - { - int v = std::min(MIX_MAX_VOLUME, std::max(0, static_cast(std::round(value * MIX_MAX_VOLUME)))); - Mix_Volume(channel, v); - } - - void push_effect(std::shared_ptr e) override - { - e->init(spec.frequency, spec.channels); - 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(udata); - - if ((len % 2) != 0) - { - log::error() << "Cannot apply " << e->name() << " effect: number of bytes not even"; - return; - } - - (*e)(reinterpret_cast(data), len / 2); - } - - void start() override - { - playing = true; - Mix_PlayChannel(channel, track->chunk, loop ? -1 : 0); - } - - void pause() override - { - playing = false; - Mix_Pause(channel); - } - - void resume() override - { - playing = true; - Mix_Resume(channel); - } - - void stop() override - { - playing = false; - Mix_HaltChannel(channel); - } - - bool is_playing() const override - { - return playing; - } - - void on_stop(util::function callback) override - { - this->callback = std::move(callback); - } - }; - - } - struct engine::impl { std::shared_ptr sdl_init; - sdl2_mixer_initializer mix_init; - spec_t spec; + SDL_AudioDeviceID device; - struct channel - { - std::shared_ptr stream; - }; + std::vector buffer; + bool thread_registered = false; - std::mutex channels_mutex; - std::vector channels; - - static std::mutex instance_mutex; - static std::weak_ptr instance_ptr; - static std::shared_ptr instance(); + stream_ptr output; impl(); ~impl(); - std::shared_ptr play(std::shared_ptr s, bool loop); - - static void channel_finished(int ch); + static void callback(void * userdata, std::uint8_t * stream, int len); }; - 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 = make_impl(); - instance_ptr = p; - return p; - } - engine::impl::impl() : sdl_init(sdl2::init(SDL_INIT_AUDIO)) { - if (Mix_OpenAudio(spec.frequency, AUDIO_S16SYS, spec.channels, 256) != 0) - mix_fail("Mix_OpenAudio: "); + 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:"); - Uint16 format; - Mix_QuerySpec(&spec.frequency, &format, &spec.channels); + buffer.resize(obtained.samples * obtained.channels); - if (format != AUDIO_S16SYS) - throw std::runtime_error("Failed to initialize audio with int16 samples"); + SDL_PauseAudioDevice(device, 0); - Mix_ChannelFinished(&channel_finished); - - log::info() << "Initialized audio: " << spec.channels << " channels, " << spec.frequency << " Hz"; + log::info() << "Initialized audio: " << static_cast(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples"; } engine::impl::~impl() { - for (auto & ch : channels) - if (ch.stream) - ch.stream->stop(); - Mix_CloseAudio(); + SDL_CloseAudioDevice(device); } - std::shared_ptr engine::impl::play(std::shared_ptr s, bool loop) + void engine::impl::callback(void * userdata, std::uint8_t * dst_u8, int len) { - auto ss = std::dynamic_pointer_cast(s); - if (!ss) + auto self = static_cast(userdata); + stream_ptr output = std::atomic_load(&(self->output)); + std::int16_t * dst = reinterpret_cast(dst_u8); + + if (!self->thread_registered) { - throw std::runtime_error("Failed to play sample: unknown sample type"); + log::register_thread("audio"); + self->thread_registered = true; } - std::lock_guard lock{channels_mutex}; + 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); - 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); - log::info() << "Allocating " << channels.size() << " mixing channels"; - Mix_AllocateChannels(channels.size()); - ch = c; - } - - auto str = std::make_shared(*ch, std::move(ss), loop, spec); - - 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; - - util::function callback; - { - // callback will probably lock something as well, - // leading to a deadlock, so we have to call the - // callback outside of the lock - std::lock_guard lock{self->channels_mutex}; - self->channels[ch].stream->playing = false; - callback = std::move(self->channels[ch].stream->callback); - self->channels[ch].stream = nullptr; - } - - if (callback) - callback(); + 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_{impl::instance()} + : pimpl_(make_impl()) {} - engine::~engine() - {} + engine::~engine() = default; - int engine::frequency() const + track_ptr engine::load(float const * data, std::size_t sample_count, bool copy) { - return impl::instance()->spec.frequency; + if ((sample_count % 2) != 0) + throw std::runtime_error("bad sample count"); + + (void)data; + (void)sample_count; + (void)copy; + + return nullptr; } - int engine::channels() const + track_ptr engine::load(util::span data, bool copy) { - return impl::instance()->spec.channels; + return load(data.data(), data.size(), copy); } - std::shared_ptr engine::load(char const * data, std::size_t size) + stream_ptr engine::output(stream_ptr stream) { - return std::make_shared(Mix_LoadWAV_RW(SDL_RWFromConstMem(data, size), 1)); - } - - std::shared_ptr engine::load_raw(sample 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 * sizeof(sample); - chunk->volume = 128; - if (copy) - { - chunk->abuf = static_cast(malloc(sample_count * sizeof(sample))); - 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::load_raw(std::vector data) - { - Mix_Chunk * chunk = static_cast(malloc(sizeof(Mix_Chunk))); - chunk->allocated = 0; - chunk->alen = data.size() * sizeof(sample); - chunk->volume = 128; - chunk->abuf = const_cast(reinterpret_cast(data.data())); - return std::make_shared(chunk, std::move(data)); - } - - std::shared_ptr engine::play(std::shared_ptr s, bool start, bool loop) - { - auto str = impl().play(std::move(s), loop); - if (start) str->start(); - return str; - } - - void engine::lock() - { - SDL_LockAudio(); - } - - void engine::unlock() - { - SDL_UnlockAudio(); + return std::atomic_exchange(&(impl().output), std::move(stream)); } } diff --git a/libs/audio/source/mixer.cpp b/libs/audio/source/mixer.cpp new file mode 100644 index 00000000..07379b85 --- /dev/null +++ b/libs/audio/source/mixer.cpp @@ -0,0 +1,118 @@ +#include + +#include +#include +#include + +namespace psemek::audio +{ + + namespace + { + + struct channel_impl final + : mixer::channel + { + stream_ptr stream; + + void stop() override; + + bool is_stopped() const override; + }; + + void channel_impl::stop() + { + std::atomic_store(&stream, stream_ptr()); + } + + bool channel_impl::is_stopped() const + { + return std::atomic_load(&stream) != nullptr; + } + + struct mixer_impl final + : mixer + , std::enable_shared_from_this + { + channel_ptr add(stream_ptr stream) override; + + std::size_t read(float * data, std::size_t sample_count) override; + + private: + std::vector> channels_; + std::vector> alive_channels_; + + std::vector buffer_; + + std::mutex new_channels_mutex_; + std::vector> new_channels_; + }; + + mixer::channel_ptr mixer_impl::add(stream_ptr stream) + { + auto result = std::make_shared(); + result->stream = std::move(stream); + + { + std::lock_guard lock{new_channels_mutex_}; + new_channels_.push_back(result); + } + + return result; + } + + std::size_t mixer_impl::read(float * data, std::size_t sample_count) + { + { + std::vector> new_channels; + { + std::lock_guard lock{new_channels_mutex_}; + new_channels = std::move(new_channels_); + } + for (auto & ch : new_channels) + channels_.push_back(std::move(ch)); + } + + std::fill(data, data + sample_count, 0.f); + + buffer_.resize(sample_count); + + for (auto & ch : channels_) + { + auto stream = std::atomic_load(&(ch->stream)); + if (!stream) + continue; + + auto read = stream->read(buffer_.data(), sample_count); + + { + auto begin = buffer_.data(); + auto end = begin + read; + auto dst = data; + for (; begin < end; ) + *dst++ += *begin++; + } + + if (read < sample_count) + { + ch->stop(); + continue; + } + + alive_channels_.push_back(std::move(ch)); + } + + std::swap(channels_, alive_channels_); + alive_channels_.clear(); + + return sample_count; + } + + } + + mixer_ptr make_mixer() + { + return std::make_shared(); + } + +} diff --git a/libs/audio/source/wave/sawtooth.cpp b/libs/audio/source/wave/sawtooth.cpp new file mode 100644 index 00000000..e3d9d387 --- /dev/null +++ b/libs/audio/source/wave/sawtooth.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +#include + +namespace psemek::audio +{ + + stream_ptr sawtooth_wave(float frequency) + { + auto func = [frequency](float t){ + t *= frequency; + return (t - std::floor(t)) * 2.f - 1.f; + }; + + return util::to_shared(generator_stream(func)); + } + +} diff --git a/libs/audio/source/wave/sine.cpp b/libs/audio/source/wave/sine.cpp new file mode 100644 index 00000000..c014fc08 --- /dev/null +++ b/libs/audio/source/wave/sine.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +#include + +namespace psemek::audio +{ + + stream_ptr sine_wave(float frequency) + { + float angular_frequency = frequency * 2.f * geom::pi; + + auto func = [angular_frequency](float t){ + return std::sin(angular_frequency * t); + }; + + return util::to_shared(generator_stream(func)); + } + +} diff --git a/libs/audio/source/wave/square.cpp b/libs/audio/source/wave/square.cpp new file mode 100644 index 00000000..59225377 --- /dev/null +++ b/libs/audio/source/wave/square.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +#include + +namespace psemek::audio +{ + + stream_ptr square_wave(float frequency) + { + auto func = [frequency](float t){ + t *= frequency; + return (t - std::floor(t)) < 0.5f ? 1.f : -1.f; + }; + + return util::to_shared(generator_stream(func)); + } + +} diff --git a/libs/audio/source/wave/triangle.cpp b/libs/audio/source/wave/triangle.cpp new file mode 100644 index 00000000..6d36d405 --- /dev/null +++ b/libs/audio/source/wave/triangle.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + +#include + +namespace psemek::audio +{ + + stream_ptr triangle_wave(float frequency) + { + auto func = [frequency](float t){ + t *= frequency; + return 4.f * std::abs(t - std::floor(t + 0.5f)) - 1.f; + }; + + return util::to_shared(generator_stream(func)); + } + +}