psemek/libs/audio/source/combine/mixer.cpp

123 lines
2.3 KiB
C++

#include <psemek/audio/combine/mixer.hpp>
#include <memory>
#include <vector>
#include <atomic>
#include <mutex>
namespace psemek::audio
{
namespace
{
struct mixer_impl final
: mixer
, std::enable_shared_from_this<mixer_impl>
{
channel_ptr add(stream_ptr stream) override;
std::size_t stream_count() const override
{
return stream_count_.load();
}
std::size_t read(util::span<float> samples) override;
std::optional<std::size_t> length() const override
{
return std::nullopt;
}
std::size_t played() const override
{
return played_.load();
}
private:
std::vector<channel_ptr> channels_;
std::vector<channel_ptr> alive_channels_;
std::vector<float> buffer_;
std::mutex new_channels_mutex_;
std::vector<channel_ptr> new_channels_;
std::atomic<std::size_t> played_{0};
std::atomic<std::size_t> stream_count_{0};
};
channel_ptr mixer_impl::add(stream_ptr stream)
{
auto result = std::make_shared<channel>(std::move(stream));
{
std::lock_guard lock{new_channels_mutex_};
new_channels_.push_back(result);
}
return result;
}
std::size_t mixer_impl::read(util::span<float> samples)
{
{
std::vector<channel_ptr> 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(samples.begin(), samples.end(), 0.f);
buffer_.resize(samples.size());
for (auto & ch : channels_)
{
auto stream = ch->stream();
if (!stream)
continue;
auto read = stream->read(buffer_);
for (auto src = buffer_.data(), dst = samples.begin(); src != buffer_.data() + read; ++src, ++dst)
*dst += *src;
if (read < buffer_.size())
{
ch->stop();
continue;
}
alive_channels_.push_back(std::move(ch));
}
stream_count_ = alive_channels_.size();
std::swap(channels_, alive_channels_);
alive_channels_.clear();
played_.fetch_add(samples.size());
return samples.size();
}
}
mixer_ptr make_mixer()
{
return std::make_shared<mixer_impl>();
}
mixer_ptr mix(std::vector<stream_ptr> const & streams)
{
auto mixer = make_mixer();
for (auto const & stream : streams)
mixer->add(stream);
return mixer;
}
}