Separate impl::mixer declaration and definition
This commit is contained in:
parent
723a42537a
commit
1b0cf9fb0e
@ -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
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