diff --git a/include/audio/channel.hpp b/include/audio/channel.hpp
new file mode 100644
index 0000000..beb9239
--- /dev/null
+++ b/include/audio/channel.hpp
@@ -0,0 +1,62 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_AUDIO_CHANNEL_HPP
+#define OUR_DICK_AUDIO_CHANNEL_HPP
+
+#include //size_t
+
+#include "mixdata.hpp"
+
+namespace audio{
+
+ namespace impl{
+ class channel;
+ }
+
+ //needs to be thread safe, lock free, and avoid false sharing
+ class channel
+ {
+ private:
+ impl::channel* m_impl;
+
+ public:
+ channel(impl::channel& c);
+ channel(const channel& c) = default;
+ channel(channel&& c) = default;
+ ~channel() = default;
+
+ channel& operator=(const channel&) = default;
+ channel& operator=(channel&&) = default;
+
+ void resize_buffer(size_t newsize);
+
+ void play(const mixdata& m);
+
+ void pause();
+ void resume();
+ void stop();
+
+ bool is_paused()const;
+ bool is_playing()const;
+ bool is_stopped()const;
+ };
+
+}
+
+#endif
diff --git a/include/audio/detail.hpp b/include/audio/detail.hpp
index 8c29d6a..eb62b50 100644
--- a/include/audio/detail.hpp
+++ b/include/audio/detail.hpp
@@ -53,7 +53,7 @@ namespace audio::detail{
callback_impl(Fn&& f, Ts&&... ts):
m_ch{std::forward(f), {std::forward(ts)...}}{}
int operator()(const void* input, void* output, unsigned long frame_count)override{
- return m_ch(input, output, frame_count, typename ::detail::sequence_gen::type{});
+ return m_ch(input, output, frame_count, typename ::util::sequence_gen::type{});
}
};
}
diff --git a/include/audio/error.hpp b/include/audio/error.hpp
index 0e4cb40..c3eaf13 100644
--- a/include/audio/error.hpp
+++ b/include/audio/error.hpp
@@ -1,18 +1,18 @@
/**
- This file is a part of the WyZ project
+ This file is a part of our_dick
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
+ it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
+ GNU Affero General Public License for more details.
- You should have received a copy of the GNU General Public License
+ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
diff --git a/include/audio/impl/channel.hpp b/include/audio/impl/channel.hpp
new file mode 100644
index 0000000..944911e
--- /dev/null
+++ b/include/audio/impl/channel.hpp
@@ -0,0 +1,82 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_AUDIO_CHANNEL_IMPL_HPP
+#define OUR_DICK_AUDIO_CHANNEL_IMPL_HPP
+
+#include
+#include //size_t
+#include "util/ring_buffer.hpp"
+#include "audio/mixdata.hpp"
+
+namespace audio::impl{
+
+ class channel
+ {
+ private:
+ util::mpmc_ring_buffer m_data;
+ std::atomic_bool m_paused;
+ std::atomic_bool m_active;
+ bool m_stopped;
+
+ //audio thread access only!
+ struct{
+ mixdata data;
+ size_t offset;
+ }m_playing_chunk = {};
+
+ public:
+ channel();
+ explicit channel(size_t maxsize);
+ channel(const channel& c);
+ channel(channel&& c);
+ ~channel() = default;
+
+ channel& operator=(const channel& c);
+ channel& operator=(channel&& c);
+
+ mixdata pop();
+ bool try_pop(mixdata& m);
+
+ void resize_buffer(size_t newsize);
+
+ void play(const mixdata& m);
+
+ void pause();
+ void resume();
+ void stop();
+
+ bool is_paused()const;
+ bool is_playing()const;
+ bool is_stopped()const;
+
+ mixdata& playing_chunk();
+ const mixdata& playing_chunk()const;
+ size_t& playing_offset();
+ const size_t& playing_offset()const;
+
+ bool is_active()const;
+ void set_active(bool b);
+
+ private:
+ void wait_for_consumer();
+ };
+
+}
+
+#endif
diff --git a/include/audio/impl/mixer.hpp b/include/audio/impl/mixer.hpp
new file mode 100644
index 0000000..1062c53
--- /dev/null
+++ b/include/audio/impl/mixer.hpp
@@ -0,0 +1,207 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_AUDIO_IMPL_MIXER_HPP
+#define OUR_DICK_AUDIO_IMPL_MIXER_HPP
+
+#include
+#include
+#include
+#include //size_t
+#include "channel.hpp"
+#include "audio/mixdata.hpp"
+#include "audio/stream.hpp"
+
+namespace audio::impl{
+
+ template
+ static constexpr T& min(T& left, T& right){
+ return (left < right) ? left : right;
+ }
+ //channel reservation needs to be thread safe and lock free
+ class mixer
+ {
+ private:
+ //all thread stuff
+ std::atomic_bool m_paused;
+ std::atomic_bool m_exit;
+ std::atomic_bool m_operating;
+ std::vector m_channels;
+
+ //producer thread stuff
+ std::mutex m_copy_lock;
+ std::unique_lock m_lk;
+ audio::stream m_output_sink;
+
+ public:
+ mixer(int output_channels, size_t channel_count):
+ m_paused(false),
+ m_exit(false),
+ m_operating(false),
+ m_channels(channel_count),
+ m_lk(m_copy_lock, std::defer_lock),
+ m_output_sink(0, output_channels, 44100, 512, callback, *this)
+ {
+ auto i = m_output_sink.last_error();
+ m_output_sink.start();
+ i = m_output_sink.last_error();
+ }
+ mixer(const mixer&) = delete;
+ mixer(mixer&&) = delete;
+ ~mixer(){
+ exit();
+ }
+
+ void reserve_channels(size_t count){
+ lock();
+ m_channels.resize(count);
+ unlock();
+ }
+ std::vector& channels(){
+ return m_channels;
+ }
+ const std::vector& channels()const{
+ return m_channels;
+ }
+ channel& get_channel(size_t i){
+ return m_channels[i];
+ }
+ const channel& get_channel(size_t i)const{
+ return m_channels[i];
+ }
+
+ void lock(){
+ m_lk.lock();
+ consumer_lock();
+ }
+ void consumer_lock(){
+ pause();
+ while(is_active()){}
+ }
+ void unlock(){
+ consumer_unlock();
+ m_lk.unlock();
+ }
+ void consumer_unlock(){
+ resume();
+ }
+ bool is_active()const{
+ return m_operating.load(std::memory_order_acquire);
+ }
+ void set_active(bool b){
+ m_operating.store(b, std::memory_order_release);
+ }
+ bool is_paused()const{
+ return m_paused.load(std::memory_order_acquire);
+ }
+ void pause(){
+ m_paused.store(true, std::memory_order_release);
+ }
+ void resume(){
+ m_paused.store(false, std::memory_order_release);
+ }
+ bool should_exit()const{
+ return m_exit.load(std::memory_order_acquire);
+ }
+ void exit(){
+ m_exit.store(true, std::memory_order_release);
+ while(is_active()){}
+ }
+ size_t output_channels()const{
+ return m_output_sink.output_count();
+ }
+ private:
+ static int callback(const void* /*input*/, void* output, unsigned long frame_count, mixer& mix){
+ //mark mixer data as being processed
+ mix.set_active(true);
+
+ float* foutput = reinterpret_cast(output);
+ size_t out_channels = mix.output_channels();
+
+ //exit when the exit flag is set
+ if(mix.should_exit()){
+ mix.set_active(false);
+ return paComplete;
+ }
+
+ //write 0 output when mixer is paused
+ memset(foutput, 0, frame_count * out_channels * sizeof(float));
+ if(mix.is_paused()){
+ mix.set_active(false);
+ return paContinue;
+ }
+
+ //combine channel data
+ for(size_t i = 0;i < mix.m_channels.size();++i){
+ channel& ch = mix.m_channels[i];
+ //mark channel as being processed
+ ch.set_active(true);
+ if(ch.is_paused()){
+ ch.set_active(false);
+ continue;
+ }
+ size_t remaining_output = frame_count;
+ //loop until the output buffer is saturated
+ while(remaining_output){
+ mixdata data;
+ size_t offset = 0;
+
+ //if we saved data from last iteration, use it before popping more
+ if(ch.playing_chunk().data){
+ data = ch.playing_chunk();
+ offset = ch.playing_offset();
+ ch.playing_chunk().data = nullptr;
+ }else{
+ if(!ch.try_pop(data))
+ break;
+ }
+
+ size_t real_framecount;
+ //if there is more data in frame than in remaining output buffer space, store it for next iteration
+ if(remaining_output < data.frames - offset){
+ ch.playing_chunk() = data;
+ ch.playing_offset() = offset + remaining_output;
+ real_framecount = remaining_output;
+ }else{
+ real_framecount = data.frames - offset;
+ }
+
+ //copy data into the output buffer
+ size_t real_floatcount = real_framecount * out_channels;
+ size_t offset_float = offset * data.channels;
+ for(size_t j = 0;j < real_floatcount;j += out_channels){
+ for(size_t k = 0;k < out_channels;++k){
+ foutput[j+k] += ((data.data + offset_float)[j + (k % data.channels)] * data.volume);
+ }
+ }
+
+ remaining_output -= real_framecount;
+ }
+ //mark channel as done processing
+ ch.set_active(false);
+ }
+
+ //mark mixer as done processing
+ mix.set_active(false);
+ return paContinue;
+ }
+ };
+
+}
+
+#endif
diff --git a/include/audio/mixdata.hpp b/include/audio/mixdata.hpp
new file mode 100644
index 0000000..056be3b
--- /dev/null
+++ b/include/audio/mixdata.hpp
@@ -0,0 +1,36 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_AUDIO_MIXDATA_HPP
+#define OUR_DICK_AUDIO_MIXDATA_HPP
+
+#include //size_t
+
+namespace audio{
+
+ struct mixdata {
+ float* data;
+ size_t frames;
+ size_t channels;
+ size_t samplerate;
+ float volume = 1;
+ };
+
+}
+
+#endif
diff --git a/include/audio/mixer.hpp b/include/audio/mixer.hpp
new file mode 100644
index 0000000..93dc23a
--- /dev/null
+++ b/include/audio/mixer.hpp
@@ -0,0 +1,62 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_AUDIO_MIXER_HPP
+#define OUR_DICK_AUDIO_MIXER_HPP
+
+#include //size_t
+#include "channel.hpp"
+
+namespace audio{
+
+ namespace impl{
+ class mixer;
+ }
+
+ class mixer
+ {
+ public:
+ enum class mode : int{
+ STEREO = 2,
+ };
+ private:
+ impl::mixer* m_mix;
+ public:
+ mixer(mode m, size_t channel_count);
+ mixer(const mixer&) = delete;
+ mixer(mixer&&) = delete;
+ ~mixer();
+
+ mixer& operator=(const mixer&) = delete;
+ mixer& operator=(mixer&&) = delete;
+
+ void reserve_channels(size_t count);
+ channel get_channel(size_t index);
+ channel get_channel();
+
+ bool is_paused()const;
+ bool is_terminated()const;
+
+ void pause();
+ void resume();
+ void terminate();
+ };
+
+}
+
+#endif
diff --git a/include/audio/stream.hpp b/include/audio/stream.hpp
index e49523e..6cc28f0 100644
--- a/include/audio/stream.hpp
+++ b/include/audio/stream.hpp
@@ -22,7 +22,7 @@
#include //forward
#include
-#include "detail/utility.hpp"
+#include "util/sequence.hpp"
#include "detail.hpp"
#include "error.hpp"
@@ -34,13 +34,17 @@ namespace audio{
detail::callback_iface* m_cb = nullptr;
PaStream* m_stream = nullptr;
mutable PaError m_err = paNoError;
+ int m_in_channels;
+ int m_out_channels;
public:
template
stream(int in_c, int out_c, double samplerate, size_t buffersize, Callback&& cb, Args&&... cbargs):
m_cb(new detail::callback_impl(std::forward(cb), std::forward(cbargs)...)),
m_stream(initialize_global_instance()),
- m_err(open_default_stream(&m_stream, in_c, out_c, paFloat32, samplerate, buffersize, m_cb)){}
+ m_err(open_default_stream(&m_stream, in_c, out_c, paFloat32, samplerate, buffersize, m_cb)),
+ m_in_channels(in_c),
+ m_out_channels(out_c){}
~stream();
@@ -52,6 +56,9 @@ namespace audio{
bool is_stopped()const;
bool is_active()const;
+ int input_count()const;
+ int output_count()const;
+
error last_error()const;
private:
diff --git a/include/util/ring_buffer.hpp b/include/util/ring_buffer.hpp
new file mode 100644
index 0000000..bf84996
--- /dev/null
+++ b/include/util/ring_buffer.hpp
@@ -0,0 +1,123 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_UTIL_RING_BUFFER_HPP
+#define OUR_DICK_UTIL_RING_BUFFER_HPP
+
+#include //vector (duh)
+#include //size_t
+#include //atomic (duh)
+
+#ifdef __cpp_lib_hardware_interference_size
+#include //hardware_destructive_interference_size
+#endif
+
+#ifndef __cpp_aligned_new
+//TODO: custom aligned allocator
+#error "Require aligned new allocation"
+#endif
+
+namespace util{
+
+ template
+ class mpmc_ring_buffer
+ {
+ public:
+ using value_type = T;
+ using size_type = size_t;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using rvalue_reference = value_type&&;
+ using const_reference = const value_type&;
+
+ private:
+ #ifdef __cpp_lib_hardware_interference_size
+ static constexpr size_t cacheline_size = std::hardware_destructive_interference_size;
+ #else
+ //Best guess
+ static constexpr size_t cacheline_size = 64;
+ #endif
+
+ class slot
+ {
+ public:
+ static constexpr size_type active_bit = 1;
+ private:
+ //ensure no false sharing with previous slot in queue
+ alignas(cacheline_size) std::atomic m_turn = {0};
+ alignas(alignof(value_type)) unsigned char m_data[sizeof(value_type)] = {};
+ //ensure no false sharing with following data
+ char cachline_padding[cacheline_size - (sizeof(m_data) + sizeof(m_turn))];
+ public:
+ slot() = default;
+ slot(const slot& s);
+ slot(slot&& s);
+ ~slot();
+ template
+ void construct(Args&&... args);
+ void destruct();
+
+ const_reference get()const&;
+ reference get()&;
+ rvalue_reference get()&&;
+ std::atomic& turn();
+ const std::atomic& turn()const;
+ };
+
+ private:
+ std::vector m_slots;
+ //keep head and tail on separate cache lines to prevent thread thrashing
+ alignas(cacheline_size) std::atomic m_head;
+ alignas(cacheline_size) std::atomic m_tail;
+
+ public:
+ explicit mpmc_ring_buffer(size_type capacity);
+ //copy/move NOT thread safe! requires external locking mechanism!
+ mpmc_ring_buffer(const mpmc_ring_buffer& m);
+ constexpr mpmc_ring_buffer(mpmc_ring_buffer&& m);
+ ~mpmc_ring_buffer() = default;
+ mpmc_ring_buffer& operator=(const mpmc_ring_buffer& m);
+ constexpr mpmc_ring_buffer& operator=(mpmc_ring_buffer&& m);
+
+ //NOT thread safe! requires external locking mechanism!
+ void resize(size_type newcap);
+ //NOT thread safe! requires external locking mechanism!
+ void clear();
+
+ template
+ void emplace(Args&&... args);
+ template
+ bool try_emplace(Args&&... args);
+
+ void push(const_reference t);
+ void push(rvalue_reference t);
+ bool try_push(const_reference t);
+ bool try_push(rvalue_reference t);
+
+ void pop(reference t);
+ bool try_pop(reference t);
+ private:
+ constexpr size_type rotation_cnt(size_type t);
+ };
+
+}
+
+#include "ring_buffer.tpp"
+
+#endif
diff --git a/include/util/ring_buffer.tpp b/include/util/ring_buffer.tpp
new file mode 100644
index 0000000..4e36dbb
--- /dev/null
+++ b/include/util/ring_buffer.tpp
@@ -0,0 +1,217 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef OUR_DICK_UTIL_RING_BUFFER_TPP
+#define OUR_DICK_UTIL_RING_BUFFER_TPP
+
+#include //forward, move
+#include //memory_order, atomic
+#include //memcpy
+
+namespace util{
+ template
+ mpmc_ring_buffer::slot::slot(const slot& s):
+ m_turn(s.m_turn.load(std::memory_order_acquire))
+ {
+ memcpy(m_data, s.m_data, sizeof(s.m_data));
+ }
+ template
+ mpmc_ring_buffer::slot::slot(slot&& s):
+ m_turn(s.m_turn.load(std::memory_order_acquire))
+ {
+ memcpy(m_data, s.m_data, sizeof(s.m_data));
+ }
+ template
+ mpmc_ring_buffer::slot::~slot(){
+ if(m_turn & active_bit){
+ destruct();
+ }
+ }
+ template
+ template
+ void mpmc_ring_buffer::slot::construct(Args&&... args){
+ new (&m_data) value_type(std::forward(args)...);
+ }
+ template
+ void mpmc_ring_buffer::slot::destruct(){
+ reinterpret_cast(&m_data)->~value_type();
+ }
+
+ template
+ auto mpmc_ring_buffer::slot::get()const& -> const_reference{
+ return reinterpret_cast(m_data);
+ }
+ template
+ auto mpmc_ring_buffer::slot::get()& -> reference{
+ return reinterpret_cast(m_data);
+ }
+ template
+ auto mpmc_ring_buffer::slot::get()&& -> rvalue_reference{
+ return std::move(reinterpret_cast(m_data));
+ }
+ template
+ auto mpmc_ring_buffer::slot::turn() -> std::atomic&{
+ return m_turn;
+ }
+ template
+ auto mpmc_ring_buffer::slot::turn()const -> const std::atomic&{
+ return m_turn;
+ }
+
+ template
+ mpmc_ring_buffer::mpmc_ring_buffer(size_type capacity):
+ m_slots(capacity),
+ m_head(0),
+ m_tail(0){}
+
+ template
+ mpmc_ring_buffer::mpmc_ring_buffer(const mpmc_ring_buffer& m):
+ m_slots(m.m_slots),
+ m_head(m.m_head.load()),
+ m_tail(m.m_tail.load()){}
+ template
+ constexpr mpmc_ring_buffer::mpmc_ring_buffer(mpmc_ring_buffer&& m):
+ m_slots(std::move(m.m_slots)),
+ m_head(m.m_head.load()),
+ m_tail(m.m_tail.load()){}
+ template
+ mpmc_ring_buffer& mpmc_ring_buffer::operator=(const mpmc_ring_buffer& m){
+ return (*this = mpmc_ring_buffer(m));
+ }
+ template
+ constexpr mpmc_ring_buffer& mpmc_ring_buffer::operator=(mpmc_ring_buffer&& m){
+ std::swap(m_slots, m.m_slots);
+ m_head = m.m_head.load();
+ m_tail = m.m_tail.load();
+ return *this;
+ }
+
+ template
+ void mpmc_ring_buffer::resize(size_type newcap){
+ mpmc_ring_buffer tmp(newcap);
+ size_type max = (m_head - m_tail) < newcap ? (m_head - m_tail) : newcap;
+ for(size_type i = m_tail, j = 0;j < max;++i,++j){
+ tmp.m_slots[j].get() = std::move(m_slots[i % m_slots.capacity()].get());
+ tmp.m_slots[j].turn() |= 1; //in-use bit
+ }
+ tmp.m_head = max;
+ tmp.m_tail = 0;
+ *this = std::move(tmp);
+ }
+ template
+ void mpmc_ring_buffer::clear(){
+ size_type head = m_head.load(std::memory_order_acquire);
+ for(size_type i = m_tail;i < head;++i){
+ m_slots[i].destruct();
+ m_slots[i].turn().store(0, std::memory_order_release);
+ }
+ m_head.store(0, std::memory_order_release);
+ m_tail.store(0, std::memory_order_release);
+ }
+ template
+ template
+ void mpmc_ring_buffer::emplace(Args&&... args){
+ const size_type head = m_head.fetch_add(1, std::memory_order_seq_cst);
+ slot& s = m_slots[head % m_slots.capacity()];
+ //lsb is in-use flag. wait for it to be 0
+ while(rotation_cnt(head) << 1 != s.turn().load(std::memory_order_acquire));
+
+ s.construct(std::forward(args)...);
+ //set in-use flag
+ s.turn().store((rotation_cnt(head) << 1) + 1, std::memory_order_release);
+ }
+ template
+ template
+ bool mpmc_ring_buffer::try_emplace(Args&&... args){
+ size_type head = m_head.load(std::memory_order_acquire);
+ while(1){
+ slot& s = m_slots[head % m_slots.capacity()];
+ if(rotation_cnt(head) << 1 == s.turn().load(std::memory_order_acquire)){
+ if(m_head.compare_exchange_strong(head, head+1, std::memory_order_seq_cst)){
+ s.construct(std::forward(args)...);
+ s.turn().store((rotation_cnt(head) << 1) + 1, std::memory_order_release);
+ return true;
+ }
+ }else{
+ const size_type prev_head = head;
+ head = m_head.load(std::memory_order_acquire);
+ if(head == prev_head)
+ return false;
+ }
+ }
+ }
+
+ template
+ void mpmc_ring_buffer::push(const_reference t){
+ emplace(t);
+ }
+ template
+ bool mpmc_ring_buffer::try_push(const_reference t){
+ return try_emplace(t);
+ }
+ template
+ void mpmc_ring_buffer::push(rvalue_reference t){
+ emplace(std::move(t));
+ }
+ template
+ bool mpmc_ring_buffer::try_push(rvalue_reference t){
+ return try_emplace(std::move(t));
+ }
+
+ template
+ void mpmc_ring_buffer::pop(reference t){
+ const size_type tail = m_tail.fetch_add(1, std::memory_order_seq_cst);
+ slot& s = m_slots[tail % m_slots.capacity()];
+
+ //lsb is in-use flag. wait for it to be 1
+ while((rotation_cnt(tail) << 1) + 1 != s.turn().load(std::memory_order_acquire));
+
+ t = std::move(s).get();
+ s.destruct();
+ s.turn().store((rotation_cnt(tail) << 1) + 2, std::memory_order_release);
+ }
+ template
+ bool mpmc_ring_buffer::try_pop(reference t){
+ size_type tail = m_tail.load(std::memory_order_acquire);
+ while(1){
+ slot& s = m_slots[tail % m_slots.capacity()];
+ if((rotation_cnt(tail) << 1) + 1 == s.turn().load(std::memory_order_acquire)){
+ if(m_tail.compare_exchange_strong(tail, tail+1, std::memory_order_seq_cst)){
+ t = std::move(s).get();
+ s.destruct();
+ s.turn().store((rotation_cnt(tail) << 1) + 2, std::memory_order_release);
+ return true;
+ }
+ }else{
+ //if the tail hasn't moved, then we're still waiting on producer.
+ //if it has moved, another consumer took our data. try again.
+ const size_type prev_tail = tail;
+ tail = m_tail.load(std::memory_order_acquire);
+ if(tail == prev_tail)
+ return false;
+ }
+ }
+ }
+ template
+ constexpr auto mpmc_ring_buffer::rotation_cnt(size_type t) -> size_type{
+ return (t / m_slots.capacity());
+ }
+
+}
+
+#endif
diff --git a/include/detail/utility.hpp b/include/util/sequence.hpp
similarity index 92%
rename from include/detail/utility.hpp
rename to include/util/sequence.hpp
index d0fb782..c7d4d0e 100644
--- a/include/detail/utility.hpp
+++ b/include/util/sequence.hpp
@@ -16,12 +16,12 @@
along with this program. If not, see .
*/
-#ifndef OUR_DICK_UTILITY_HPP
-#define OUR_DICK_UTILITY_HPP
+#ifndef OUR_DICK_SEQUENCE_HPP
+#define OUR_DICK_SEQUENCE_HPP
#include //integer_sequence
-namespace detail{
+namespace util{
template
struct sequence_gen{
using type = typename sequence_gen::type;
diff --git a/makefile b/makefile
index 5e5ebc6..559753b 100644
--- a/makefile
+++ b/makefile
@@ -19,7 +19,7 @@ ifeq ($(OS),Windows_NT)
WINDOWS::=1
endif
-SOURCE_DIRS::=src src/audio
+SOURCE_DIRS::=src src/audio src/audio/impl
SOURCES::=
OBJDIR::=obj
DEPDIR::=$(OBJDIR)/dep
diff --git a/src/audio/channel.cpp b/src/audio/channel.cpp
new file mode 100644
index 0000000..7b7187a
--- /dev/null
+++ b/src/audio/channel.cpp
@@ -0,0 +1,52 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#include "audio/channel.hpp"
+#include "audio/impl/channel.hpp"
+
+namespace audio{
+
+ //needs to be thread safe, lock free, and avoid false sharing
+ channel::channel(impl::channel& c):
+ m_impl(&c){}
+
+ void channel::play(const mixdata& m){
+ m_impl->play(m);
+ }
+
+ void channel::pause(){
+ m_impl->pause();
+ }
+ void channel::resume(){
+ m_impl->resume();
+ }
+ void channel::stop(){
+ m_impl->stop();
+ }
+
+ bool channel::is_paused()const{
+ return m_impl->is_paused();
+ }
+ bool channel::is_playing()const{
+ return m_impl->is_playing();
+ }
+ bool channel::is_stopped()const{
+ return m_impl->is_stopped();
+ }
+
+}
diff --git a/src/audio/error.cpp b/src/audio/error.cpp
index 3221cbc..41adcb0 100644
--- a/src/audio/error.cpp
+++ b/src/audio/error.cpp
@@ -1,18 +1,18 @@
/**
- This file is a part of the WyZ project
+ This file is a part of our_dick
Copyright (C) 2020 rexy712
This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
+ it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
+ GNU Affero General Public License for more details.
- You should have received a copy of the GNU General Public License
+ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
diff --git a/src/audio/impl/channel.cpp b/src/audio/impl/channel.cpp
new file mode 100644
index 0000000..124784a
--- /dev/null
+++ b/src/audio/impl/channel.cpp
@@ -0,0 +1,127 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#include "audio/impl/channel.hpp"
+#include "audio/impl/mixer.hpp"
+#include //swap, move
+
+namespace audio::impl{
+
+ channel::channel():
+ channel(64){}
+ channel::channel(size_t maxsize):
+ m_data(maxsize),
+ m_paused(false),
+ m_active(false),
+ m_stopped(false),
+ m_playing_chunk{}{}
+ channel::channel(const channel& c):
+ m_data(c.m_data),
+ m_paused(c.m_paused.load(std::memory_order_acquire)),
+ m_active(c.m_active.load(std::memory_order_acquire)),
+ m_stopped(c.m_stopped),
+ m_playing_chunk(c.m_playing_chunk){}
+ channel::channel(channel&& c):
+ m_data(std::move(c.m_data)),
+ m_paused(c.m_paused.load(std::memory_order_acquire)),
+ m_active(c.m_active.load(std::memory_order_acquire)),
+ m_stopped(c.m_stopped),
+ m_playing_chunk(c.m_playing_chunk){}
+
+ channel& channel::operator=(const channel& c){
+ return (*this = channel(c));
+ }
+ channel& channel::operator=(channel&& c){
+ std::swap(m_data, c.m_data);
+ m_paused = c.m_paused.load(std::memory_order_acquire);
+ m_active = c.m_active.load(std::memory_order_acquire);
+ m_stopped = c.m_stopped;
+ m_playing_chunk = c.m_playing_chunk;
+ return *this;
+ }
+
+ mixdata channel::pop(){
+ mixdata m;
+ m_data.pop(m);
+ return m;
+ }
+ bool channel::try_pop(mixdata& m){
+ return m_data.try_pop(m);
+ }
+
+ void channel::resize_buffer(size_t newsize){
+ bool was_paused = m_paused.load(std::memory_order_acquire);
+ pause();
+ wait_for_consumer();
+ m_data.resize(newsize);
+ if(!was_paused)
+ resume();
+ }
+
+ void channel::play(const mixdata& m){
+ m_data.push(m);
+ }
+
+ void channel::pause(){
+ m_paused.store(true, std::memory_order_release);
+ }
+ void channel::resume(){
+ m_paused.store(false, std::memory_order_release);
+ m_stopped = false;
+ }
+ void channel::stop(){
+ pause();
+ wait_for_consumer();
+ m_stopped = true;
+ m_data.clear();
+ }
+
+ bool channel::is_paused()const{
+ return m_paused.load(std::memory_order_acquire);
+ }
+ bool channel::is_playing()const{
+ return !m_paused.load(std::memory_order_acquire);
+ }
+ bool channel::is_stopped()const{
+ return m_stopped;
+ }
+
+ mixdata& channel::playing_chunk(){
+ return m_playing_chunk.data;
+ }
+ const mixdata& channel::playing_chunk()const{
+ return m_playing_chunk.data;
+ }
+ size_t& channel::playing_offset(){
+ return m_playing_chunk.offset;
+ }
+ const size_t& channel::playing_offset()const{
+ return m_playing_chunk.offset;
+ }
+ bool channel::is_active()const{
+ return m_active.load(std::memory_order_acquire);
+ }
+ void channel::set_active(bool b){
+ m_active.store(b, std::memory_order_release);
+ }
+
+ void channel::wait_for_consumer(){
+ while(m_active.load(std::memory_order_acquire)){}
+ }
+
+}
diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp
new file mode 100644
index 0000000..a1f40c7
--- /dev/null
+++ b/src/audio/mixer.cpp
@@ -0,0 +1,63 @@
+/**
+ This file is a part of our_dick
+ Copyright (C) 2020 rexy712
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+#include "audio/mixer.hpp"
+#include "audio/impl/mixer.hpp"
+
+namespace audio{
+
+ mixer::mixer(mode m, size_t channel_count):
+ m_mix(new impl::mixer(static_cast(m), channel_count)){}
+ mixer::~mixer(){
+ delete m_mix;
+ }
+
+ void mixer::reserve_channels(size_t count){
+ m_mix->reserve_channels(count);
+ }
+ channel mixer::get_channel(size_t index){
+ return channel(m_mix->channels()[index]);
+ }
+ //first free channel
+ channel mixer::get_channel(){
+ //TODO
+ for(size_t i = 0;i < m_mix->channels().size();++i){
+ if(m_mix->channels()[i].is_playing())
+ return channel(m_mix->channels()[i]);
+ }
+ return channel(m_mix->channels()[0]);
+ }
+
+ bool mixer::is_paused()const{
+ return m_mix->is_paused();
+ }
+ bool mixer::is_terminated()const{
+ return m_mix->should_exit();
+ }
+
+ void mixer::pause(){
+ m_mix->pause();
+ }
+ void mixer::resume(){
+ m_mix->resume();
+ }
+ void mixer::terminate(){
+ m_mix->exit();
+ }
+
+}
diff --git a/src/audio/stream.cpp b/src/audio/stream.cpp
index ce96f35..b9fb4ba 100644
--- a/src/audio/stream.cpp
+++ b/src/audio/stream.cpp
@@ -52,6 +52,12 @@ namespace audio{
bool stream::is_active()const{
return (m_err = Pa_IsStreamActive(m_stream)) == 1;
}
+ int stream::input_count()const{
+ return m_in_channels;
+ }
+ int stream::output_count()const{
+ return m_out_channels;
+ }
error stream::last_error()const{