Add basic audio mixer
This commit is contained in:
parent
03693c6d06
commit
723a42537a
62
include/audio/channel.hpp
Normal file
62
include/audio/channel.hpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_AUDIO_CHANNEL_HPP
|
||||
#define OUR_DICK_AUDIO_CHANNEL_HPP
|
||||
|
||||
#include <cstdlib> //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
|
||||
@ -53,7 +53,7 @@ namespace audio::detail{
|
||||
callback_impl(Fn&& f, Ts&&... ts):
|
||||
m_ch{std::forward<Fn>(f), {std::forward<Ts>(ts)...}}{}
|
||||
int operator()(const void* input, void* output, unsigned long frame_count)override{
|
||||
return m_ch(input, output, frame_count, typename ::detail::sequence_gen<sizeof...(Args)>::type{});
|
||||
return m_ch(input, output, frame_count, typename ::util::sequence_gen<sizeof...(Args)>::type{});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
82
include/audio/impl/channel.hpp
Normal file
82
include/audio/impl/channel.hpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_AUDIO_CHANNEL_IMPL_HPP
|
||||
#define OUR_DICK_AUDIO_CHANNEL_IMPL_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib> //size_t
|
||||
#include "util/ring_buffer.hpp"
|
||||
#include "audio/mixdata.hpp"
|
||||
|
||||
namespace audio::impl{
|
||||
|
||||
class channel
|
||||
{
|
||||
private:
|
||||
util::mpmc_ring_buffer<mixdata> 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
|
||||
207
include/audio/impl/mixer.hpp
Normal file
207
include/audio/impl/mixer.hpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_AUDIO_IMPL_MIXER_HPP
|
||||
#define OUR_DICK_AUDIO_IMPL_MIXER_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <cstdlib> //size_t
|
||||
#include "channel.hpp"
|
||||
#include "audio/mixdata.hpp"
|
||||
#include "audio/stream.hpp"
|
||||
|
||||
namespace audio::impl{
|
||||
|
||||
template<typename T>
|
||||
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<channel> m_channels;
|
||||
|
||||
//producer thread stuff
|
||||
std::mutex m_copy_lock;
|
||||
std::unique_lock<std::mutex> 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<channel>& channels(){
|
||||
return m_channels;
|
||||
}
|
||||
const std::vector<channel>& 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<float*>(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
|
||||
36
include/audio/mixdata.hpp
Normal file
36
include/audio/mixdata.hpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_AUDIO_MIXDATA_HPP
|
||||
#define OUR_DICK_AUDIO_MIXDATA_HPP
|
||||
|
||||
#include <cstdlib> //size_t
|
||||
|
||||
namespace audio{
|
||||
|
||||
struct mixdata {
|
||||
float* data;
|
||||
size_t frames;
|
||||
size_t channels;
|
||||
size_t samplerate;
|
||||
float volume = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
62
include/audio/mixer.hpp
Normal file
62
include/audio/mixer.hpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_AUDIO_MIXER_HPP
|
||||
#define OUR_DICK_AUDIO_MIXER_HPP
|
||||
|
||||
#include <cstdlib> //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
|
||||
@ -22,7 +22,7 @@
|
||||
#include <utility> //forward
|
||||
#include <portaudio.h>
|
||||
|
||||
#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<typename Callback, typename... Args>
|
||||
stream(int in_c, int out_c, double samplerate, size_t buffersize, Callback&& cb, Args&&... cbargs):
|
||||
m_cb(new detail::callback_impl<Callback,Args...>(std::forward<Callback>(cb), std::forward<Args>(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:
|
||||
|
||||
123
include/util/ring_buffer.hpp
Normal file
123
include/util/ring_buffer.hpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_UTIL_RING_BUFFER_HPP
|
||||
#define OUR_DICK_UTIL_RING_BUFFER_HPP
|
||||
|
||||
#include <vector> //vector (duh)
|
||||
#include <cstdlib> //size_t
|
||||
#include <atomic> //atomic (duh)
|
||||
|
||||
#ifdef __cpp_lib_hardware_interference_size
|
||||
#include <new> //hardware_destructive_interference_size
|
||||
#endif
|
||||
|
||||
#ifndef __cpp_aligned_new
|
||||
//TODO: custom aligned allocator
|
||||
#error "Require aligned new allocation"
|
||||
#endif
|
||||
|
||||
namespace util{
|
||||
|
||||
template<typename T>
|
||||
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<size_type> 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<typename... Args>
|
||||
void construct(Args&&... args);
|
||||
void destruct();
|
||||
|
||||
const_reference get()const&;
|
||||
reference get()&;
|
||||
rvalue_reference get()&&;
|
||||
std::atomic<size_type>& turn();
|
||||
const std::atomic<size_type>& turn()const;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<slot> m_slots;
|
||||
//keep head and tail on separate cache lines to prevent thread thrashing
|
||||
alignas(cacheline_size) std::atomic<size_type> m_head;
|
||||
alignas(cacheline_size) std::atomic<size_type> 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<typename... Args>
|
||||
void emplace(Args&&... args);
|
||||
template<typename... Args>
|
||||
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
|
||||
217
include/util/ring_buffer.tpp
Normal file
217
include/util/ring_buffer.tpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_UTIL_RING_BUFFER_TPP
|
||||
#define OUR_DICK_UTIL_RING_BUFFER_TPP
|
||||
|
||||
#include <utility> //forward, move
|
||||
#include <atomic> //memory_order, atomic
|
||||
#include <cstring> //memcpy
|
||||
|
||||
namespace util{
|
||||
template<typename T>
|
||||
mpmc_ring_buffer<T>::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<typename T>
|
||||
mpmc_ring_buffer<T>::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<typename T>
|
||||
mpmc_ring_buffer<T>::slot::~slot(){
|
||||
if(m_turn & active_bit){
|
||||
destruct();
|
||||
}
|
||||
}
|
||||
template<typename T>
|
||||
template<typename... Args>
|
||||
void mpmc_ring_buffer<T>::slot::construct(Args&&... args){
|
||||
new (&m_data) value_type(std::forward<Args>(args)...);
|
||||
}
|
||||
template<typename T>
|
||||
void mpmc_ring_buffer<T>::slot::destruct(){
|
||||
reinterpret_cast<pointer>(&m_data)->~value_type();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
auto mpmc_ring_buffer<T>::slot::get()const& -> const_reference{
|
||||
return reinterpret_cast<const_reference>(m_data);
|
||||
}
|
||||
template<typename T>
|
||||
auto mpmc_ring_buffer<T>::slot::get()& -> reference{
|
||||
return reinterpret_cast<reference>(m_data);
|
||||
}
|
||||
template<typename T>
|
||||
auto mpmc_ring_buffer<T>::slot::get()&& -> rvalue_reference{
|
||||
return std::move(reinterpret_cast<reference>(m_data));
|
||||
}
|
||||
template<typename T>
|
||||
auto mpmc_ring_buffer<T>::slot::turn() -> std::atomic<size_type>&{
|
||||
return m_turn;
|
||||
}
|
||||
template<typename T>
|
||||
auto mpmc_ring_buffer<T>::slot::turn()const -> const std::atomic<size_type>&{
|
||||
return m_turn;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
mpmc_ring_buffer<T>::mpmc_ring_buffer(size_type capacity):
|
||||
m_slots(capacity),
|
||||
m_head(0),
|
||||
m_tail(0){}
|
||||
|
||||
template<typename T>
|
||||
mpmc_ring_buffer<T>::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<typename T>
|
||||
constexpr mpmc_ring_buffer<T>::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<typename T>
|
||||
mpmc_ring_buffer<T>& mpmc_ring_buffer<T>::operator=(const mpmc_ring_buffer& m){
|
||||
return (*this = mpmc_ring_buffer(m));
|
||||
}
|
||||
template<typename T>
|
||||
constexpr mpmc_ring_buffer<T>& mpmc_ring_buffer<T>::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<typename T>
|
||||
void mpmc_ring_buffer<T>::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<typename T>
|
||||
void mpmc_ring_buffer<T>::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<typename T>
|
||||
template<typename... Args>
|
||||
void mpmc_ring_buffer<T>::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>(args)...);
|
||||
//set in-use flag
|
||||
s.turn().store((rotation_cnt(head) << 1) + 1, std::memory_order_release);
|
||||
}
|
||||
template<typename T>
|
||||
template<typename... Args>
|
||||
bool mpmc_ring_buffer<T>::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>(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<typename T>
|
||||
void mpmc_ring_buffer<T>::push(const_reference t){
|
||||
emplace(t);
|
||||
}
|
||||
template<typename T>
|
||||
bool mpmc_ring_buffer<T>::try_push(const_reference t){
|
||||
return try_emplace(t);
|
||||
}
|
||||
template<typename T>
|
||||
void mpmc_ring_buffer<T>::push(rvalue_reference t){
|
||||
emplace(std::move(t));
|
||||
}
|
||||
template<typename T>
|
||||
bool mpmc_ring_buffer<T>::try_push(rvalue_reference t){
|
||||
return try_emplace(std::move(t));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void mpmc_ring_buffer<T>::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<typename T>
|
||||
bool mpmc_ring_buffer<T>::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<typename T>
|
||||
constexpr auto mpmc_ring_buffer<T>::rotation_cnt(size_type t) -> size_type{
|
||||
return (t / m_slots.capacity());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -16,12 +16,12 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef OUR_DICK_UTILITY_HPP
|
||||
#define OUR_DICK_UTILITY_HPP
|
||||
#ifndef OUR_DICK_SEQUENCE_HPP
|
||||
#define OUR_DICK_SEQUENCE_HPP
|
||||
|
||||
#include <utility> //integer_sequence
|
||||
|
||||
namespace detail{
|
||||
namespace util{
|
||||
template<int I, int... Is>
|
||||
struct sequence_gen{
|
||||
using type = typename sequence_gen<I-1, Is..., sizeof...(Is)>::type;
|
||||
2
makefile
2
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
|
||||
|
||||
52
src/audio/channel.cpp
Normal file
52
src/audio/channel.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
127
src/audio/impl/channel.cpp
Normal file
127
src/audio/impl/channel.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "audio/impl/channel.hpp"
|
||||
#include "audio/impl/mixer.hpp"
|
||||
#include <utility> //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)){}
|
||||
}
|
||||
|
||||
}
|
||||
63
src/audio/mixer.cpp
Normal file
63
src/audio/mixer.cpp
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<int>(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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user