Add basic audio mixer

This commit is contained in:
rexy712 2020-08-21 20:39:43 -07:00
parent 03693c6d06
commit 723a42537a
17 changed files with 1059 additions and 15 deletions

62
include/audio/channel.hpp Normal file
View 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

View File

@ -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{});
}
};
}

View File

@ -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/>.
*/

View 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

View 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
View 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
View 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

View File

@ -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:

View 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

View 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

View File

@ -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;

View File

@ -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
View 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();
}
}

View File

@ -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
View 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
View 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();
}
}

View File

@ -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{