Separate impl::mixer declaration and definition

This commit is contained in:
rexy712 2020-08-22 02:04:53 -07:00
parent 723a42537a
commit 1b0cf9fb0e
2 changed files with 198 additions and 151 deletions

View File

@ -24,15 +24,10 @@
#include <mutex> #include <mutex>
#include <cstdlib> //size_t #include <cstdlib> //size_t
#include "channel.hpp" #include "channel.hpp"
#include "audio/mixdata.hpp"
#include "audio/stream.hpp" #include "audio/stream.hpp"
namespace audio::impl{ 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 //channel reservation needs to be thread safe and lock free
class mixer class mixer
{ {
@ -49,157 +44,31 @@ namespace audio::impl{
audio::stream m_output_sink; audio::stream m_output_sink;
public: public:
mixer(int output_channels, size_t channel_count): 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(const mixer&) = delete;
mixer(mixer&&) = delete; mixer(mixer&&) = delete;
~mixer(){ ~mixer();
exit();
}
void reserve_channels(size_t count){ void reserve_channels(size_t count);
lock(); std::vector<channel>& channels();
m_channels.resize(count); const std::vector<channel>& channels()const;
unlock(); channel& get_channel(size_t i);
} const channel& get_channel(size_t i)const;
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(){ void lock();
m_lk.lock(); void consumer_lock();
consumer_lock(); void unlock();
} void consumer_unlock();
void consumer_lock(){ bool is_active()const;
pause(); void set_active(bool b);
while(is_active()){} bool is_paused()const;
} void pause();
void unlock(){ void resume();
consumer_unlock(); bool should_exit()const;
m_lk.unlock(); void exit();
} size_t output_channels()const;
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: private:
static int callback(const void* /*input*/, void* output, unsigned long frame_count, mixer& mix){ 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;
}
}; };
} }

178
src/audio/impl/mixer.cpp Normal file
View File

@ -0,0 +1,178 @@
/**
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/mixer.hpp"
#include "audio/impl/channel.hpp"
#include "audio/mixdata.hpp"
namespace audio::impl{
//prevent inclusion of <algorithm> since that kills compile times
template<typename T>
static constexpr T& min(T& left, T& right){
return (left < right) ? left : right;
}
mixer::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)
{
m_output_sink.start();
}
mixer::~mixer(){
exit();
}
void mixer::reserve_channels(size_t count){
lock();
m_channels.resize(count);
unlock();
}
std::vector<channel>& mixer::channels(){
return m_channels;
}
const std::vector<channel>& mixer::channels()const{
return m_channels;
}
channel& mixer::get_channel(size_t i){
return m_channels[i];
}
const channel& mixer::get_channel(size_t i)const{
return m_channels[i];
}
void mixer::lock(){
m_lk.lock();
consumer_lock();
}
void mixer::consumer_lock(){
pause();
while(is_active()){}
}
void mixer::unlock(){
consumer_unlock();
m_lk.unlock();
}
void mixer::consumer_unlock(){
resume();
}
bool mixer::is_active()const{
return m_operating.load(std::memory_order_acquire);
}
void mixer::set_active(bool b){
m_operating.store(b, std::memory_order_release);
}
bool mixer::is_paused()const{
return m_paused.load(std::memory_order_acquire);
}
void mixer::pause(){
m_paused.store(true, std::memory_order_release);
}
void mixer::resume(){
m_paused.store(false, std::memory_order_release);
}
bool mixer::should_exit()const{
return m_exit.load(std::memory_order_acquire);
}
void mixer::exit(){
m_exit.store(true, std::memory_order_release);
while(is_active()){}
}
size_t mixer::output_channels()const{
return m_output_sink.output_count();
}
int mixer::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;
}
}