Audio library rewrite: re-implement engine using SDL_Audio & change API

This commit is contained in:
Nikita Lisitsa 2022-10-05 12:28:27 +03:00
parent d95807e320
commit b3e583283a
23 changed files with 454 additions and 606 deletions

70
examples/audio.cpp Normal file
View file

@ -0,0 +1,70 @@
#include <psemek/audio/engine.hpp>
#include <psemek/audio/wave/sine.hpp>
#include <psemek/audio/wave/sawtooth.hpp>
#include <psemek/audio/wave/square.hpp>
#include <psemek/audio/wave/triangle.hpp>
#include <psemek/audio/mixer.hpp>
#include <psemek/app/app.hpp>
#include <psemek/app/main.hpp>
#include <map>
using namespace psemek;
static std::map<SDL_Keycode, int> 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<SDL_Keycode, audio::mixer::channel_ptr> channels_;
};
int main()
{
return app::main<audio_app>();
}

View file

@ -1,30 +0,0 @@
#pragma once
#include <psemek/audio/effect.hpp>
#include <psemek/geom/point.hpp>
#include <memory>
#include <optional>
#include <mutex>
namespace psemek::audio
{
struct listener_3d_mono
{
void set_position(geom::point<float, 3> const & position);
struct mono_effect
: effect
{
virtual void set_position(geom::point<float, 3> const & position) = 0;
};
virtual std::shared_ptr<mono_effect> make_effect(geom::point<float, 3> const & position, float distance_factor);
private:
geom::point<float, 3> position_;
std::mutex position_mutex_;
};
}

View file

@ -0,0 +1,9 @@
#pragma once
namespace psemek::audio
{
constexpr int frequency = 44100;
constexpr float inv_frequency = 1.f / frequency;
}

View file

@ -1,12 +0,0 @@
#pragma once
#include <psemek/audio/effect.hpp>
#include <memory>
namespace psemek::audio
{
std::shared_ptr<effect> echo(float delay, float volume);
}

View file

@ -1,22 +0,0 @@
#pragma once
#include <psemek/audio/sample.hpp>
#include <string_view>
#include <cstddef>
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() {}
};
}

View file

@ -1,11 +1,10 @@
#pragma once #pragma once
#include <psemek/audio/sample.hpp>
#include <psemek/audio/effect.hpp>
#include <psemek/audio/stream.hpp> #include <psemek/audio/stream.hpp>
#include <psemek/audio/track.hpp> #include <psemek/audio/track.hpp>
#include <psemek/util/pimpl.hpp> #include <psemek/util/pimpl.hpp>
#include <psemek/util/span.hpp>
#include <memory> #include <memory>
#include <string_view> #include <string_view>
@ -19,31 +18,13 @@ namespace psemek::audio
engine(); engine();
~engine(); ~engine();
int frequency() const; track_ptr load(float const * data, std::size_t sample_count, bool copy = true);
int channels() const; track_ptr load(util::span<float> data, bool copy = true);
std::shared_ptr<track> load_raw(sample const * data, std::size_t sample_count, bool copy = true); stream_ptr output(stream_ptr stream);
std::shared_ptr<track> load(char const * data, std::size_t size);
std::shared_ptr<track> load_raw(std::basic_string_view<sample> data, bool copy = true)
{
return load_raw(data.data(), data.size(), copy);
}
std::shared_ptr<track> load(std::string_view data)
{
return load(data.data(), data.size());
}
std::shared_ptr<track> load_raw(std::vector<sample> data);
std::shared_ptr<stream> play(std::shared_ptr<track> s, bool start = true, bool loop = false);
void lock();
void unlock();
private: private:
psemek_declare_shared_pimpl psemek_declare_pimpl
}; };
} }

View file

@ -0,0 +1,31 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <memory>
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<channel>;
virtual channel_ptr add(stream_ptr stream) = 0;
};
using mixer_ptr = std::shared_ptr<mixer>;
mixer_ptr make_mixer();
}

View file

@ -1,39 +0,0 @@
#pragma once
#include <cmath>
#include <limits>
#include <cstdint>
#include <algorithm>
namespace psemek::audio
{
using sample = std::int16_t;
inline sample clamp(float v)
{
static auto const min = std::numeric_limits<sample>::min();
static auto const max = std::numeric_limits<sample>::max();
return static_cast<sample>(std::min<int>(max, std::max<int>(min, std::round(v))));
}
// maps [-1, 1] range to samples
inline sample pack(float v)
{
static auto const min = static_cast<float>(std::numeric_limits<sample>::min());
static auto const max = static_cast<float>(std::numeric_limits<sample>::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<float>(std::numeric_limits<sample>::min());
static auto const max = static_cast<float>(std::numeric_limits<sample>::max());
return 2.f * (s - min) / (max - min) - 1.f;
}
}

View file

@ -1,9 +1,6 @@
#pragma once #pragma once
#include <psemek/audio/effect.hpp> #include <cstddef>
#include <psemek/util/function.hpp>
#include <memory> #include <memory>
namespace psemek::audio namespace psemek::audio
@ -11,22 +8,12 @@ namespace psemek::audio
struct stream struct stream
{ {
// value \in [0, 1] // Return value less than sample count means end of stream
virtual void volume(float value) = 0; virtual std::size_t read(float * data, std::size_t sample_count) = 0;
virtual void push_effect(std::shared_ptr<effect> 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<void()> callback) = 0;
virtual ~stream() {} virtual ~stream() {}
}; };
using stream_ptr = std::shared_ptr<stream>;
} }

View file

@ -1,16 +1,18 @@
#pragma once #pragma once
#include <psemek/audio/sample.hpp> #include <psemek/audio/stream.hpp>
namespace psemek::audio namespace psemek::audio
{ {
struct track struct track
{ {
virtual sample const * data() const = 0; virtual stream_ptr stream() const = 0;
virtual std::size_t size() const = 0; virtual std::size_t length() const = 0;
virtual ~track(){} virtual ~track(){}
}; };
using track_ptr = std::shared_ptr<track>;
} }

View file

@ -0,0 +1,36 @@
#pragma once
#include <psemek/audio/stream.hpp>
#include <psemek/audio/constants.hpp>
namespace psemek::audio
{
template <typename Func>
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_;
};
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <psemek/audio/stream.hpp>
namespace psemek::audio
{
stream_ptr sawtooth_wave(float frequency);
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <psemek/audio/stream.hpp>
namespace psemek::audio
{
stream_ptr sine_wave(float frequency);
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <psemek/audio/stream.hpp>
namespace psemek::audio
{
stream_ptr square_wave(float frequency);
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <psemek/audio/stream.hpp>
namespace psemek::audio
{
stream_ptr triangle_wave(float frequency);
}

View file

@ -1,83 +0,0 @@
#include <psemek/audio/3d.hpp>
#include <psemek/util/not_implemented.hpp>
#include <optional>
#include <mutex>
namespace psemek::audio
{
namespace
{
struct mono_effect_impl
: listener_3d_mono::mono_effect
{
mono_effect_impl(geom::point<float, 3> const & source_position, geom::point<float, 3> & 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<float, 3> const & position) override
{
std::lock_guard lock{source_position_mutex_};
source_position_ = position;
}
private:
std::mutex source_position_mutex_;
geom::point<float, 3> source_position_;
geom::point<float, 3> cached_source_position_;
geom::point<float, 3> & target_position_;
geom::point<float, 3> cached_target_position_;
std::mutex & target_position_mutex_;
float distance_factor_;
};
}
void listener_3d_mono::set_position(geom::point<float, 3> const & position)
{
std::lock_guard lock{position_mutex_};
position_ = position;
}
std::shared_ptr<listener_3d_mono::mono_effect> listener_3d_mono::make_effect(const geom::point<float, 3> &position, float distance_factor)
{
return std::make_shared<mono_effect_impl>(position, position_, position_mutex_, distance_factor);
}
}

View file

@ -1,73 +0,0 @@
#include <psemek/audio/echo.hpp>
#include <psemek/audio/engine.hpp>
#include <psemek/audio/sample.hpp>
#include <cmath>
#include <vector>
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<sample> samples_;
std::size_t pos_ = 0;
float delay_;
float volume_;
};
}
std::shared_ptr<effect> echo(float delay, float volume)
{
return std::make_shared<echo_impl>(delay, volume);
}
}

View file

@ -1,11 +1,12 @@
#include <psemek/audio/engine.hpp> #include <psemek/audio/engine.hpp>
#include <psemek/audio/constants.hpp>
#include <psemek/sdl2/init.hpp> #include <psemek/sdl2/init.hpp>
#include <psemek/log/log.hpp> #include <psemek/log/log.hpp>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
#include <mutex> #include <mutex>
#include <atomic>
#include <vector> #include <vector>
#include <optional> #include <optional>
#include <cmath> #include <cmath>
@ -13,349 +14,96 @@
namespace psemek::audio 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<sample> buffer;
track_impl(Mix_Chunk * chunk)
: chunk(chunk)
{}
track_impl(Mix_Chunk * chunk, std::vector<sample> buffer)
: chunk(chunk)
, buffer(std::move(buffer))
{}
sample const * data() const override
{
return reinterpret_cast<sample const *>(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_impl> track;
bool loop;
spec_t spec;
std::vector<std::shared_ptr<effect>> effects;
util::function<void()> callback;
stream_impl(int channel, std::shared_ptr<track_impl> 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<int>(std::round(value * MIX_MAX_VOLUME))));
Mix_Volume(channel, v);
}
void push_effect(std::shared_ptr<effect> 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<effect *>(udata);
if ((len % 2) != 0)
{
log::error() << "Cannot apply " << e->name() << " effect: number of bytes not even";
return;
}
(*e)(reinterpret_cast<sample *>(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<void()> callback) override
{
this->callback = std::move(callback);
}
};
}
struct engine::impl struct engine::impl
{ {
std::shared_ptr<void> sdl_init; std::shared_ptr<void> sdl_init;
sdl2_mixer_initializer mix_init;
spec_t spec; SDL_AudioDeviceID device;
struct channel std::vector<float> buffer;
{ bool thread_registered = false;
std::shared_ptr<stream_impl> stream;
};
std::mutex channels_mutex; stream_ptr output;
std::vector<channel> channels;
static std::mutex instance_mutex;
static std::weak_ptr<impl> instance_ptr;
static std::shared_ptr<impl> instance();
impl(); impl();
~impl(); ~impl();
std::shared_ptr<stream> play(std::shared_ptr<track> s, bool loop); static void callback(void * userdata, std::uint8_t * stream, int len);
static void channel_finished(int ch);
}; };
std::mutex engine::impl::instance_mutex;
std::weak_ptr<struct engine::impl> engine::impl::instance_ptr;
std::shared_ptr<struct engine::impl> 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() engine::impl::impl()
: sdl_init(sdl2::init(SDL_INIT_AUDIO)) : sdl_init(sdl2::init(SDL_INIT_AUDIO))
{ {
if (Mix_OpenAudio(spec.frequency, AUDIO_S16SYS, spec.channels, 256) != 0) SDL_AudioSpec desired, obtained;
mix_fail("Mix_OpenAudio: "); 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; buffer.resize(obtained.samples * obtained.channels);
Mix_QuerySpec(&spec.frequency, &format, &spec.channels);
if (format != AUDIO_S16SYS) SDL_PauseAudioDevice(device, 0);
throw std::runtime_error("Failed to initialize audio with int16 samples");
Mix_ChannelFinished(&channel_finished); log::info() << "Initialized audio: " << static_cast<int>(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples";
log::info() << "Initialized audio: " << spec.channels << " channels, " << spec.frequency << " Hz";
} }
engine::impl::~impl() engine::impl::~impl()
{ {
for (auto & ch : channels) SDL_CloseAudioDevice(device);
if (ch.stream)
ch.stream->stop();
Mix_CloseAudio();
} }
std::shared_ptr<stream> engine::impl::play(std::shared_ptr<track> s, bool loop) void engine::impl::callback(void * userdata, std::uint8_t * dst_u8, int len)
{ {
auto ss = std::dynamic_pointer_cast<track_impl>(s); auto self = static_cast<impl *>(userdata);
if (!ss) stream_ptr output = std::atomic_load(&(self->output));
std::int16_t * dst = reinterpret_cast<std::int16_t *>(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<int> ch; for (auto s : self->buffer)
*dst++ = static_cast<std::int16_t>(std::max(std::min((65535.f * s - 1.f) / 2.f, 32767.f), -32768.f));
int c;
for (c = 0; c < static_cast<int>(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<stream_impl>(*ch, std::move(ss), loop, spec);
channels[*ch].stream = str;
return str;
}
void engine::impl::channel_finished(int ch)
{
std::shared_ptr<impl> self;
{
std::lock_guard lock{instance_mutex};
self = instance_ptr.lock();
}
if (!self) return;
util::function<void()> 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();
} }
engine::engine() 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<float> data, bool copy)
{ {
return impl::instance()->spec.channels; return load(data.data(), data.size(), copy);
} }
std::shared_ptr<track> engine::load(char const * data, std::size_t size) stream_ptr engine::output(stream_ptr stream)
{ {
return std::make_shared<track_impl>(Mix_LoadWAV_RW(SDL_RWFromConstMem(data, size), 1)); return std::atomic_exchange(&(impl().output), std::move(stream));
}
std::shared_ptr<track> engine::load_raw(sample const * data, std::size_t sample_count, bool copy)
{
Mix_Chunk * chunk = static_cast<Mix_Chunk *>(malloc(sizeof(Mix_Chunk)));
chunk->allocated = copy ? 1 : 0;
chunk->alen = sample_count * sizeof(sample);
chunk->volume = 128;
if (copy)
{
chunk->abuf = static_cast<Uint8 *>(malloc(sample_count * sizeof(sample)));
std::copy(data, data + sample_count, reinterpret_cast<sample *>(chunk->abuf));
}
else
{
chunk->abuf = const_cast<Uint8 *>(reinterpret_cast<Uint8 const *>(data));
}
return std::make_shared<track_impl>(chunk);
}
std::shared_ptr<track> engine::load_raw(std::vector<sample> data)
{
Mix_Chunk * chunk = static_cast<Mix_Chunk *>(malloc(sizeof(Mix_Chunk)));
chunk->allocated = 0;
chunk->alen = data.size() * sizeof(sample);
chunk->volume = 128;
chunk->abuf = const_cast<Uint8 *>(reinterpret_cast<Uint8 const *>(data.data()));
return std::make_shared<track_impl>(chunk, std::move(data));
}
std::shared_ptr<stream> engine::play(std::shared_ptr<track> 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();
} }
} }

118
libs/audio/source/mixer.cpp Normal file
View file

@ -0,0 +1,118 @@
#include <psemek/audio/mixer.hpp>
#include <memory>
#include <vector>
#include <atomic>
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<mixer_impl>
{
channel_ptr add(stream_ptr stream) override;
std::size_t read(float * data, std::size_t sample_count) override;
private:
std::vector<std::shared_ptr<channel_impl>> channels_;
std::vector<std::shared_ptr<channel_impl>> alive_channels_;
std::vector<float> buffer_;
std::mutex new_channels_mutex_;
std::vector<std::shared_ptr<channel_impl>> new_channels_;
};
mixer::channel_ptr mixer_impl::add(stream_ptr stream)
{
auto result = std::make_shared<channel_impl>();
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<std::shared_ptr<channel_impl>> 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<mixer_impl>();
}
}

View file

@ -0,0 +1,21 @@
#include <psemek/audio/wave/sawtooth.hpp>
#include <psemek/audio/wave/generator.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/util/to_shared.hpp>
#include <cmath>
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));
}
}

View file

@ -0,0 +1,22 @@
#include <psemek/audio/wave/sine.hpp>
#include <psemek/audio/wave/generator.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/util/to_shared.hpp>
#include <cmath>
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));
}
}

View file

@ -0,0 +1,21 @@
#include <psemek/audio/wave/square.hpp>
#include <psemek/audio/wave/generator.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/util/to_shared.hpp>
#include <cmath>
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));
}
}

View file

@ -0,0 +1,21 @@
#include <psemek/audio/wave/triangle.hpp>
#include <psemek/audio/wave/generator.hpp>
#include <psemek/geom/math.hpp>
#include <psemek/util/to_shared.hpp>
#include <cmath>
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));
}
}