Separate impl::mixer declaration and definition
This commit is contained in:
parent
723a42537a
commit
1b0cf9fb0e
@ -24,15 +24,10 @@
|
||||
#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
|
||||
{
|
||||
@ -49,157 +44,31 @@ namespace audio::impl{
|
||||
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(int output_channels, size_t channel_count);
|
||||
mixer(const mixer&) = delete;
|
||||
mixer(mixer&&) = delete;
|
||||
~mixer(){
|
||||
exit();
|
||||
}
|
||||
~mixer();
|
||||
|
||||
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 reserve_channels(size_t count);
|
||||
std::vector<channel>& channels();
|
||||
const std::vector<channel>& channels()const;
|
||||
channel& get_channel(size_t i);
|
||||
const channel& get_channel(size_t i)const;
|
||||
|
||||
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();
|
||||
}
|
||||
void lock();
|
||||
void consumer_lock();
|
||||
void unlock();
|
||||
void consumer_unlock();
|
||||
bool is_active()const;
|
||||
void set_active(bool b);
|
||||
bool is_paused()const;
|
||||
void pause();
|
||||
void resume();
|
||||
bool should_exit()const;
|
||||
void exit();
|
||||
size_t output_channels()const;
|
||||
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;
|
||||
}
|
||||
static int callback(const void* /*input*/, void* output, unsigned long frame_count, mixer& mix);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
178
src/audio/impl/mixer.cpp
Normal file
178
src/audio/impl/mixer.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user