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{