mirror of
https://github.com/AxioDL/boo.git
synced 2025-05-17 04:41:37 +00:00
Overhauled audio system, now with internal mixing and sample-rate-conversion
This commit is contained in:
parent
5b275866a7
commit
1eb46301c0
@ -179,7 +179,10 @@ add_library(boo
|
|||||||
lib/inputdev/DeviceBase.cpp include/boo/inputdev/DeviceBase.hpp
|
lib/inputdev/DeviceBase.cpp include/boo/inputdev/DeviceBase.hpp
|
||||||
lib/inputdev/DeviceSignature.cpp include/boo/inputdev/DeviceSignature.hpp
|
lib/inputdev/DeviceSignature.cpp include/boo/inputdev/DeviceSignature.hpp
|
||||||
lib/inputdev/IHIDDevice.hpp
|
lib/inputdev/IHIDDevice.hpp
|
||||||
|
lib/audiodev/AudioMatrix.hpp
|
||||||
lib/audiodev/AudioMatrix.cpp
|
lib/audiodev/AudioMatrix.cpp
|
||||||
|
lib/audiodev/AudioVoiceEngine.hpp
|
||||||
|
lib/audiodev/AudioVoiceEngine.cpp
|
||||||
lib/audiodev/AudioVoice.hpp
|
lib/audiodev/AudioVoice.hpp
|
||||||
lib/audiodev/AudioVoice.cpp
|
lib/audiodev/AudioVoice.cpp
|
||||||
include/boo/inputdev/IHIDListener.hpp
|
include/boo/inputdev/IHIDListener.hpp
|
||||||
@ -187,7 +190,7 @@ add_library(boo
|
|||||||
include/boo/graphicsdev/IGraphicsDataFactory.hpp
|
include/boo/graphicsdev/IGraphicsDataFactory.hpp
|
||||||
include/boo/graphicsdev/IGraphicsCommandQueue.hpp
|
include/boo/graphicsdev/IGraphicsCommandQueue.hpp
|
||||||
include/boo/audiodev/IAudioVoice.hpp
|
include/boo/audiodev/IAudioVoice.hpp
|
||||||
include/boo/audiodev/IAudioVoiceAllocator.hpp
|
include/boo/audiodev/IAudioVoiceEngine.hpp
|
||||||
include/boo/IWindow.hpp
|
include/boo/IWindow.hpp
|
||||||
include/boo/IApplication.hpp
|
include/boo/IApplication.hpp
|
||||||
include/boo/System.hpp
|
include/boo/System.hpp
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
#ifndef BOO_AUDIOMATRIX_HPP
|
|
||||||
#define BOO_AUDIOMATRIX_HPP
|
|
||||||
|
|
||||||
#include "IAudioVoice.hpp"
|
|
||||||
#include <vector>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace boo
|
|
||||||
{
|
|
||||||
|
|
||||||
class AudioMatrixMono
|
|
||||||
{
|
|
||||||
AudioChannelSet m_setOut = AudioChannelSet::Stereo;
|
|
||||||
float m_coefs[8];
|
|
||||||
std::vector<int16_t> m_interleaveBuf;
|
|
||||||
public:
|
|
||||||
AudioMatrixMono() {setDefaultMatrixCoefficients();}
|
|
||||||
|
|
||||||
AudioChannelSet audioChannelSet() const {return m_setOut;}
|
|
||||||
void setAudioChannelSet(AudioChannelSet set) {m_setOut = set;}
|
|
||||||
void setDefaultMatrixCoefficients();
|
|
||||||
void setMatrixCoefficients(const float coefs[8])
|
|
||||||
{
|
|
||||||
for (int i=0 ; i<8 ; ++i)
|
|
||||||
m_coefs[i] = coefs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
void bufferMonoSampleData(IAudioVoice& voice, const int16_t* data, size_t samples);
|
|
||||||
};
|
|
||||||
|
|
||||||
class AudioMatrixStereo
|
|
||||||
{
|
|
||||||
AudioChannelSet m_setOut = AudioChannelSet::Stereo;
|
|
||||||
float m_coefs[8][2];
|
|
||||||
std::vector<int16_t> m_interleaveBuf;
|
|
||||||
public:
|
|
||||||
AudioMatrixStereo() {setDefaultMatrixCoefficients();}
|
|
||||||
|
|
||||||
AudioChannelSet audioChannelSet() const {return m_setOut;}
|
|
||||||
void setAudioChannelSet(AudioChannelSet set) {m_setOut = set;}
|
|
||||||
void setDefaultMatrixCoefficients();
|
|
||||||
void setMatrixCoefficients(const float coefs[8][2])
|
|
||||||
{
|
|
||||||
for (int i=0 ; i<8 ; ++i)
|
|
||||||
{
|
|
||||||
m_coefs[i][0] = coefs[i][0];
|
|
||||||
m_coefs[i][1] = coefs[i][1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void bufferStereoSampleData(IAudioVoice& voice, const int16_t* data, size_t frames);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // BOO_AUDIOMATRIX_HPP
|
|
@ -56,18 +56,33 @@ struct IAudioVoice
|
|||||||
{
|
{
|
||||||
virtual ~IAudioVoice() = default;
|
virtual ~IAudioVoice() = default;
|
||||||
|
|
||||||
/** Get voice's actual channel-map based on client request and HW capabilities */
|
/** Reset channel-gains to voice defaults */
|
||||||
virtual const ChannelMap& channelMap() const=0;
|
virtual void setDefaultMatrixCoefficients()=0;
|
||||||
|
|
||||||
/** Called by client in response to IAudioVoiceCallback::needsNextBuffer()
|
/** Set channel-gains for mono audio source (AudioChannel enum for array index) */
|
||||||
* Supplying channel-interleaved sample data */
|
virtual void setMonoMatrixCoefficients(const float coefs[8])=0;
|
||||||
virtual void bufferSampleData(const int16_t* data, size_t frames)=0;
|
|
||||||
|
/** Set channel-gains for stereo audio source (AudioChannel enum for array index) */
|
||||||
|
virtual void setStereoMatrixCoefficients(const float coefs[8][2])=0;
|
||||||
|
|
||||||
|
/** Called by client to dynamically adjust the pitch of voices with dynamic pitch enabled */
|
||||||
|
virtual void setPitchRatio(double ratio)=0;
|
||||||
|
|
||||||
/** Instructs platform to begin consuming sample data; invoking callback as needed */
|
/** Instructs platform to begin consuming sample data; invoking callback as needed */
|
||||||
virtual void start()=0;
|
virtual void start()=0;
|
||||||
|
|
||||||
/** Instructs platform to stop consuming sample data */
|
/** Instructs platform to stop consuming sample data */
|
||||||
virtual void stop()=0;
|
virtual void stop()=0;
|
||||||
|
|
||||||
|
/** Invalidates this voice by removing it from the AudioVoiceEngine */
|
||||||
|
virtual void unbindVoice()=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IAudioVoiceCallback
|
||||||
|
{
|
||||||
|
/** boo calls this on behalf of the audio platform to request more audio
|
||||||
|
* frames from the client */
|
||||||
|
virtual size_t supplyAudio(IAudioVoice& voice, size_t frames, int16_t* data)=0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
#ifndef BOO_IAUDIOVOICEALLOCATOR_HPP
|
|
||||||
#define BOO_IAUDIOVOICEALLOCATOR_HPP
|
|
||||||
|
|
||||||
#include "IAudioVoice.hpp"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace boo
|
|
||||||
{
|
|
||||||
|
|
||||||
struct IAudioVoiceCallback
|
|
||||||
{
|
|
||||||
/** boo calls this on behalf of the audio platform to request more audio
|
|
||||||
* frames from the client */
|
|
||||||
virtual void needsNextBuffer(IAudioVoice& voice, size_t frames)=0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IAudioVoiceAllocator
|
|
||||||
{
|
|
||||||
virtual ~IAudioVoiceAllocator() = default;
|
|
||||||
|
|
||||||
/** Client calls this to request allocation of new mixer-voice.
|
|
||||||
* Returns empty unique_ptr if necessary resources aren't available.
|
|
||||||
* ChannelLayout automatically reduces to maximum-supported layout by HW.
|
|
||||||
*
|
|
||||||
* Client must be prepared to supply audio frames via the callback when this is called;
|
|
||||||
* the backing audio-buffers are primed with initial data for low-latency playback start */
|
|
||||||
virtual std::unique_ptr<IAudioVoice> allocateNewVoice(AudioChannelSet layoutOut,
|
|
||||||
unsigned sampleRate,
|
|
||||||
IAudioVoiceCallback* cb)=0;
|
|
||||||
|
|
||||||
/** Client may use this to determine current speaker-setup */
|
|
||||||
virtual AudioChannelSet getAvailableSet()=0;
|
|
||||||
|
|
||||||
/** Ensure all voices' platform buffers are filled as much as possible */
|
|
||||||
virtual void pumpVoices()=0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Obtain host platform's voice allocator */
|
|
||||||
std::unique_ptr<IAudioVoiceAllocator> NewAudioVoiceAllocator();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // BOO_IAUDIOVOICEALLOCATOR_HPP
|
|
43
include/boo/audiodev/IAudioVoiceEngine.hpp
Normal file
43
include/boo/audiodev/IAudioVoiceEngine.hpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#ifndef BOO_IAUDIOVOICEENGINE_HPP
|
||||||
|
#define BOO_IAUDIOVOICEENGINE_HPP
|
||||||
|
|
||||||
|
#include "IAudioVoice.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace boo
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Mixing and sample-rate-conversion system. Allocates voices and mixes them
|
||||||
|
* before sending the final samples to an OS-supplied audio-queue */
|
||||||
|
struct IAudioVoiceEngine
|
||||||
|
{
|
||||||
|
virtual ~IAudioVoiceEngine() = default;
|
||||||
|
|
||||||
|
/** Client calls this to request allocation of new mixer-voice.
|
||||||
|
* Returns empty unique_ptr if necessary resources aren't available.
|
||||||
|
* ChannelLayout automatically reduces to maximum-supported layout by HW.
|
||||||
|
*
|
||||||
|
* Client must be prepared to supply audio frames via the callback when this is called;
|
||||||
|
* the backing audio-buffers are primed with initial data for low-latency playback start */
|
||||||
|
virtual std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
|
||||||
|
IAudioVoiceCallback* cb,
|
||||||
|
bool dynamicPitch=false)=0;
|
||||||
|
|
||||||
|
/** Same as allocateNewMonoVoice, but source audio is stereo-interleaved */
|
||||||
|
virtual std::unique_ptr<IAudioVoice> allocateNewStereoVoice(double sampleRate,
|
||||||
|
IAudioVoiceCallback* cb,
|
||||||
|
bool dynamicPitch=false)=0;
|
||||||
|
|
||||||
|
/** Client may use this to determine current speaker-setup */
|
||||||
|
virtual AudioChannelSet getAvailableSet()=0;
|
||||||
|
|
||||||
|
/** Ensure backing platform buffer is filled as much as possible with mixed samples */
|
||||||
|
virtual void pumpAndMixVoices()=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Construct host platform's voice engine */
|
||||||
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOO_IAUDIOVOICEENGINE_HPP
|
@ -1,217 +1,36 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include "boo/audiodev/IAudioVoiceAllocator.hpp"
|
#include "AudioVoiceEngine.hpp"
|
||||||
#include "logvisor/logvisor.hpp"
|
#include "logvisor/logvisor.hpp"
|
||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
static logvisor::Module Log("boo::ALSA");
|
static logvisor::Module Log("boo::ALSA");
|
||||||
struct ALSAAudioVoiceAllocator;
|
|
||||||
|
|
||||||
struct ALSAAudioVoice : IAudioVoice
|
struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
|
||||||
{
|
{
|
||||||
ALSAAudioVoiceAllocator& m_parent;
|
snd_pcm_t* m_pcm;
|
||||||
std::list<ALSAAudioVoice*>::iterator m_parentIt;
|
|
||||||
|
|
||||||
ChannelMap m_map;
|
|
||||||
IAudioVoiceCallback* m_cb;
|
|
||||||
snd_pcm_t* m_pcm = nullptr;
|
|
||||||
snd_pcm_uframes_t m_bufSize;
|
snd_pcm_uframes_t m_bufSize;
|
||||||
snd_pcm_uframes_t m_periodSize;
|
snd_pcm_uframes_t m_periodSize;
|
||||||
|
|
||||||
const ChannelMap& channelMap() const {return m_map;}
|
std::vector<int16_t> m_final16;
|
||||||
|
std::vector<int32_t> m_final32;
|
||||||
|
std::vector<float> m_finalFlt;
|
||||||
|
|
||||||
ALSAAudioVoice(ALSAAudioVoiceAllocator& parent, AudioChannelSet set,
|
~ALSAAudioVoiceEngine()
|
||||||
unsigned sampleRate, IAudioVoiceCallback* cb)
|
|
||||||
: m_parent(parent), m_cb(cb)
|
|
||||||
{
|
{
|
||||||
if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC) < 0)
|
|
||||||
{
|
|
||||||
Log.report(logvisor::Error, "unable to allocate ALSA voice");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned chCount = ChannelCount(set);
|
|
||||||
int err;
|
|
||||||
while ((err = snd_pcm_set_params(m_pcm, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED,
|
|
||||||
chCount, sampleRate, 1, 100000)) < 0)
|
|
||||||
{
|
|
||||||
if (set == AudioChannelSet::Stereo)
|
|
||||||
break;
|
|
||||||
set = AudioChannelSet(int(set) - 1);
|
|
||||||
chCount = ChannelCount(set);
|
|
||||||
}
|
|
||||||
if (err < 0)
|
|
||||||
{
|
|
||||||
snd_pcm_close(m_pcm);
|
|
||||||
m_pcm = nullptr;
|
|
||||||
Log.report(logvisor::Error, "unable to set ALSA voice params");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm);
|
|
||||||
if (chmaps)
|
|
||||||
{
|
|
||||||
snd_pcm_chmap_t* foundChmap = nullptr;
|
|
||||||
for (snd_pcm_chmap_query_t** chmap = chmaps ; *chmap != nullptr ; ++chmap)
|
|
||||||
{
|
|
||||||
if ((*chmap)->map.channels == chCount)
|
|
||||||
{
|
|
||||||
snd_pcm_chmap_t* chm = &(*chmap)->map;
|
|
||||||
uint64_t chBits = 0;
|
|
||||||
for (int c=0 ; c<chm->channels ; ++c)
|
|
||||||
chBits |= 1 << chm->pos[c];
|
|
||||||
|
|
||||||
bool good = false;
|
|
||||||
switch (set)
|
|
||||||
{
|
|
||||||
case AudioChannelSet::Stereo:
|
|
||||||
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_FR)) != 0)
|
|
||||||
good = true;
|
|
||||||
break;
|
|
||||||
case AudioChannelSet::Quad:
|
|
||||||
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_FR)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_RL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_RR)) != 0)
|
|
||||||
good = true;
|
|
||||||
break;
|
|
||||||
case AudioChannelSet::Surround51:
|
|
||||||
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_FR)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_RL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_RR)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_FC)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_LFE)) != 0)
|
|
||||||
good = true;
|
|
||||||
break;
|
|
||||||
case AudioChannelSet::Surround71:
|
|
||||||
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_FR)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_RL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_RR)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_FC)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_LFE)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_SL)) != 0 &&
|
|
||||||
(chBits & (1 << SND_CHMAP_SR)) != 0)
|
|
||||||
good = true;
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (good)
|
|
||||||
{
|
|
||||||
foundChmap = chm;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundChmap)
|
|
||||||
{
|
|
||||||
snd_pcm_close(m_pcm);
|
|
||||||
m_pcm = nullptr;
|
|
||||||
snd_pcm_free_chmaps(chmaps);
|
|
||||||
Log.report(logvisor::Error, "unable to find matching ALSA voice chmap");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_map.m_channelCount = chCount;
|
|
||||||
for (int c=0 ; c<foundChmap->channels ; ++c)
|
|
||||||
m_map.m_channels[c] = AudioChannel(foundChmap->pos[c] - 3);
|
|
||||||
snd_pcm_set_chmap(m_pcm, foundChmap);
|
|
||||||
snd_pcm_free_chmaps(chmaps);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_map.m_channelCount = 2;
|
|
||||||
m_map.m_channels[0] = AudioChannel::FrontLeft;
|
|
||||||
m_map.m_channels[1] = AudioChannel::FrontRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_pcm_get_params(m_pcm, &m_bufSize, &m_periodSize);
|
|
||||||
snd_pcm_prepare(m_pcm);
|
|
||||||
|
|
||||||
pump();
|
|
||||||
}
|
|
||||||
|
|
||||||
~ALSAAudioVoice();
|
|
||||||
|
|
||||||
void bufferSampleData(const int16_t* data, size_t frames)
|
|
||||||
{
|
|
||||||
if (m_pcm)
|
|
||||||
snd_pcm_writei(m_pcm, data, frames);
|
|
||||||
}
|
|
||||||
|
|
||||||
void start()
|
|
||||||
{
|
|
||||||
if (m_pcm)
|
|
||||||
snd_pcm_start(m_pcm);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop()
|
|
||||||
{
|
|
||||||
if (m_pcm)
|
|
||||||
snd_pcm_drain(m_pcm);
|
snd_pcm_drain(m_pcm);
|
||||||
|
snd_pcm_close(m_pcm);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pump()
|
AudioChannelSet _getAvailableSet()
|
||||||
{
|
{
|
||||||
snd_pcm_sframes_t frames = snd_pcm_avail(m_pcm);
|
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm);
|
||||||
if (frames < 0)
|
|
||||||
{
|
|
||||||
snd_pcm_state_t st = snd_pcm_state(m_pcm);
|
|
||||||
if (st == SND_PCM_STATE_XRUN)
|
|
||||||
{
|
|
||||||
snd_pcm_prepare(m_pcm);
|
|
||||||
frames = snd_pcm_avail(m_pcm);
|
|
||||||
fprintf(stderr, "REC %ld\n", frames);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (frames < 0)
|
|
||||||
return;
|
|
||||||
snd_pcm_sframes_t buffers = frames / m_periodSize;
|
|
||||||
for (snd_pcm_sframes_t b=0 ; b<buffers ; ++b)
|
|
||||||
m_cb->needsNextBuffer(*this, m_periodSize);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ALSAAudioVoiceAllocator : IAudioVoiceAllocator
|
|
||||||
{
|
|
||||||
std::list<ALSAAudioVoice*> m_allocatedVoices;
|
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoice> allocateNewVoice(AudioChannelSet layoutOut,
|
|
||||||
unsigned sampleRate,
|
|
||||||
IAudioVoiceCallback* cb)
|
|
||||||
{
|
|
||||||
ALSAAudioVoice* newVoice = new ALSAAudioVoice(*this, layoutOut, sampleRate, cb);
|
|
||||||
newVoice->m_parentIt = m_allocatedVoices.insert(m_allocatedVoices.end(), newVoice);
|
|
||||||
std::unique_ptr<IAudioVoice> ret(newVoice);
|
|
||||||
if (!newVoice->m_pcm)
|
|
||||||
return {};
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioChannelSet getAvailableSet()
|
|
||||||
{
|
|
||||||
snd_pcm_t* pcm;
|
|
||||||
if (snd_pcm_open(&pcm, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC) < 0)
|
|
||||||
{
|
|
||||||
Log.report(logvisor::Error, "unable to allocate ALSA voice");
|
|
||||||
return AudioChannelSet::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(pcm);
|
|
||||||
if (!chmaps)
|
if (!chmaps)
|
||||||
{
|
|
||||||
snd_pcm_close(pcm);
|
|
||||||
return AudioChannelSet::Stereo;
|
return AudioChannelSet::Stereo;
|
||||||
}
|
|
||||||
static const std::array<AudioChannelSet, 4> testSets =
|
static const std::array<AudioChannelSet, 4> testSets =
|
||||||
{AudioChannelSet::Surround71, AudioChannelSet::Surround51,
|
{AudioChannelSet::Surround71, AudioChannelSet::Surround51,
|
||||||
AudioChannelSet::Quad, AudioChannelSet::Stereo};
|
AudioChannelSet::Quad, AudioChannelSet::Stereo};
|
||||||
@ -279,23 +98,242 @@ struct ALSAAudioVoiceAllocator : IAudioVoiceAllocator
|
|||||||
return AudioChannelSet::Unknown;
|
return AudioChannelSet::Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pumpVoices()
|
AudioVoiceEngineMixInfo _getEngineMixInfo()
|
||||||
{
|
{
|
||||||
for (ALSAAudioVoice* vox : m_allocatedVoices)
|
if (snd_pcm_open(&m_pcm, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0)
|
||||||
vox->pump();
|
{
|
||||||
|
Log.report(logvisor::Error, "unable to allocate ALSA voice");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioVoiceEngineMixInfo ret = {};
|
||||||
|
|
||||||
|
/* Query audio card for best supported format amd sample-rate */
|
||||||
|
snd_pcm_hw_params_t* hwParams;
|
||||||
|
snd_pcm_hw_params_malloc(&hwParams);
|
||||||
|
snd_pcm_hw_params_any(m_pcm, hwParams);
|
||||||
|
|
||||||
|
snd_pcm_format_t bestFmt;
|
||||||
|
if (!snd_pcm_hw_params_test_format(m_pcm, hwParams, SND_PCM_FORMAT_S32))
|
||||||
|
{
|
||||||
|
bestFmt = SND_PCM_FORMAT_S32;
|
||||||
|
ret.m_sampleFormat = SOXR_INT32_I;
|
||||||
|
ret.m_bitsPerSample = 32;
|
||||||
|
}
|
||||||
|
else if (!snd_pcm_hw_params_test_format(m_pcm, hwParams, SND_PCM_FORMAT_S16))
|
||||||
|
{
|
||||||
|
bestFmt = SND_PCM_FORMAT_S16;
|
||||||
|
ret.m_sampleFormat = SOXR_INT16_I;
|
||||||
|
ret.m_bitsPerSample = 16;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snd_pcm_close(m_pcm);
|
||||||
|
m_pcm = nullptr;
|
||||||
|
Log.report(logvisor::Fatal, "unsupported audio formats on default ALSA device");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int bestRate;
|
||||||
|
if (!snd_pcm_hw_params_test_rate(m_pcm, hwParams, 96000, 0))
|
||||||
|
{
|
||||||
|
bestRate = 96000;
|
||||||
|
ret.m_sampleRate = 96000.0;
|
||||||
|
}
|
||||||
|
else if (!snd_pcm_hw_params_test_rate(m_pcm, hwParams, 48000, 0))
|
||||||
|
{
|
||||||
|
bestRate = 48000;
|
||||||
|
ret.m_sampleRate = 48000.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
snd_pcm_close(m_pcm);
|
||||||
|
m_pcm = nullptr;
|
||||||
|
Log.report(logvisor::Fatal, "unsupported audio sample rates on default ALSA device");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
snd_pcm_hw_params_free(hwParams);
|
||||||
|
|
||||||
|
/* Query audio card for channel map */
|
||||||
|
ret.m_channels = _getAvailableSet();
|
||||||
|
|
||||||
|
/* Populate channel map */
|
||||||
|
unsigned chCount = ChannelCount(ret.m_channels);
|
||||||
|
int err;
|
||||||
|
while ((err = snd_pcm_set_params(m_pcm, bestFmt, SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||||
|
chCount, bestRate, 0, 100000)) < 0)
|
||||||
|
{
|
||||||
|
if (ret.m_channels == AudioChannelSet::Stereo)
|
||||||
|
break;
|
||||||
|
ret.m_channels = AudioChannelSet(int(ret.m_channels) - 1);
|
||||||
|
chCount = ChannelCount(ret.m_channels);
|
||||||
|
}
|
||||||
|
if (err < 0)
|
||||||
|
{
|
||||||
|
snd_pcm_close(m_pcm);
|
||||||
|
m_pcm = nullptr;
|
||||||
|
Log.report(logvisor::Error, "unable to set ALSA voice params");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
snd_pcm_chmap_query_t** chmaps = snd_pcm_query_chmaps(m_pcm);
|
||||||
|
ChannelMap& chmapOut = ret.m_channelMap;
|
||||||
|
if (chmaps)
|
||||||
|
{
|
||||||
|
snd_pcm_chmap_t* foundChmap = nullptr;
|
||||||
|
for (snd_pcm_chmap_query_t** chmap = chmaps ; *chmap != nullptr ; ++chmap)
|
||||||
|
{
|
||||||
|
if ((*chmap)->map.channels == chCount)
|
||||||
|
{
|
||||||
|
snd_pcm_chmap_t* chm = &(*chmap)->map;
|
||||||
|
uint64_t chBits = 0;
|
||||||
|
for (int c=0 ; c<chm->channels ; ++c)
|
||||||
|
chBits |= 1 << chm->pos[c];
|
||||||
|
|
||||||
|
bool good = false;
|
||||||
|
switch (ret.m_channels)
|
||||||
|
{
|
||||||
|
case AudioChannelSet::Stereo:
|
||||||
|
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_FR)) != 0)
|
||||||
|
good = true;
|
||||||
|
break;
|
||||||
|
case AudioChannelSet::Quad:
|
||||||
|
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_FR)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_RL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_RR)) != 0)
|
||||||
|
good = true;
|
||||||
|
break;
|
||||||
|
case AudioChannelSet::Surround51:
|
||||||
|
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_FR)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_RL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_RR)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_FC)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_LFE)) != 0)
|
||||||
|
good = true;
|
||||||
|
break;
|
||||||
|
case AudioChannelSet::Surround71:
|
||||||
|
if ((chBits & (1 << SND_CHMAP_FL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_FR)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_RL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_RR)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_FC)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_LFE)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_SL)) != 0 &&
|
||||||
|
(chBits & (1 << SND_CHMAP_SR)) != 0)
|
||||||
|
good = true;
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (good)
|
||||||
|
{
|
||||||
|
foundChmap = chm;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundChmap)
|
||||||
|
{
|
||||||
|
snd_pcm_close(m_pcm);
|
||||||
|
m_pcm = nullptr;
|
||||||
|
snd_pcm_free_chmaps(chmaps);
|
||||||
|
Log.report(logvisor::Error, "unable to find matching ALSA voice chmap");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
chmapOut.m_channelCount = chCount;
|
||||||
|
for (int c=0 ; c<foundChmap->channels ; ++c)
|
||||||
|
chmapOut.m_channels[c] = AudioChannel(foundChmap->pos[c] - 3);
|
||||||
|
snd_pcm_set_chmap(m_pcm, foundChmap);
|
||||||
|
snd_pcm_free_chmaps(chmaps);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
chmapOut.m_channelCount = 2;
|
||||||
|
chmapOut.m_channels[0] = AudioChannel::FrontLeft;
|
||||||
|
chmapOut.m_channels[1] = AudioChannel::FrontRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALSAAudioVoiceEngine()
|
||||||
|
: BaseAudioVoiceEngine(std::bind(&ALSAAudioVoiceEngine::_getEngineMixInfo, this))
|
||||||
|
{
|
||||||
|
/* Base class will call _getEngineMixInfo first */
|
||||||
|
snd_pcm_get_params(m_pcm, &m_bufSize, &m_periodSize);
|
||||||
|
snd_pcm_prepare(m_pcm);
|
||||||
|
m_mixInfo.m_periodFrames = m_periodSize;
|
||||||
|
|
||||||
|
/* Allocate master mix space */
|
||||||
|
switch (m_mixInfo.m_sampleFormat)
|
||||||
|
{
|
||||||
|
case SOXR_INT16_I:
|
||||||
|
m_final16.resize(m_periodSize * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
break;
|
||||||
|
case SOXR_INT32_I:
|
||||||
|
m_final32.resize(m_periodSize * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
break;
|
||||||
|
case SOXR_FLOAT32_I:
|
||||||
|
m_finalFlt.resize(m_periodSize * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pumpAndMixVoices()
|
||||||
|
{
|
||||||
|
snd_pcm_sframes_t frames = snd_pcm_avail_update(m_pcm);
|
||||||
|
if (frames < 0)
|
||||||
|
{
|
||||||
|
snd_pcm_state_t st = snd_pcm_state(m_pcm);
|
||||||
|
if (st == SND_PCM_STATE_XRUN)
|
||||||
|
{
|
||||||
|
snd_pcm_prepare(m_pcm);
|
||||||
|
frames = snd_pcm_avail_update(m_pcm);
|
||||||
|
Log.report(logvisor::Warning, "ALSA underrun %ld frames", frames);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (frames < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
snd_pcm_sframes_t buffers = frames / m_periodSize;
|
||||||
|
for (snd_pcm_sframes_t b=0 ; b<buffers ; ++b)
|
||||||
|
{
|
||||||
|
switch (m_mixInfo.m_sampleFormat)
|
||||||
|
{
|
||||||
|
case SOXR_INT16_I:
|
||||||
|
_pumpAndMixVoices(m_periodSize, m_final16.data());
|
||||||
|
snd_pcm_writei(m_pcm, m_final16.data(), m_periodSize);
|
||||||
|
break;
|
||||||
|
case SOXR_INT32_I:
|
||||||
|
_pumpAndMixVoices(m_periodSize, m_final32.data());
|
||||||
|
snd_pcm_writei(m_pcm, m_final32.data(), m_periodSize);
|
||||||
|
break;
|
||||||
|
case SOXR_FLOAT32_I:
|
||||||
|
_pumpAndMixVoices(m_periodSize, m_finalFlt.data());
|
||||||
|
snd_pcm_writei(m_pcm, m_finalFlt.data(), m_periodSize);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ALSAAudioVoice::~ALSAAudioVoice()
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceEngine()
|
||||||
{
|
{
|
||||||
if (m_pcm)
|
std::unique_ptr<IAudioVoiceEngine> ret = std::make_unique<ALSAAudioVoiceEngine>();
|
||||||
snd_pcm_close(m_pcm);
|
if (!static_cast<ALSAAudioVoiceEngine&>(*ret).m_pcm)
|
||||||
m_parent.m_allocatedVoices.erase(m_parentIt);
|
return {};
|
||||||
}
|
return ret;
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoiceAllocator> NewAudioVoiceAllocator()
|
|
||||||
{
|
|
||||||
return std::make_unique<ALSAAudioVoiceAllocator>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ struct AQSAudioVoice : IAudioVoice
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AQSAudioVoiceAllocator : IAudioVoiceAllocator
|
struct AQSAudioVoiceAllocator : IAudioVoiceEngine
|
||||||
{
|
{
|
||||||
static void DummyCallback(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {}
|
static void DummyCallback(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {}
|
||||||
|
|
||||||
@ -283,7 +283,7 @@ struct AQSAudioVoiceAllocator : IAudioVoiceAllocator
|
|||||||
void pumpVoices() {}
|
void pumpVoices() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<IAudioVoiceAllocator> NewAudioVoiceAllocator()
|
std::unique_ptr<IAudioVoiceEngine> NewAudioVoiceAllocator()
|
||||||
{
|
{
|
||||||
return std::make_unique<AQSAudioVoiceAllocator>();
|
return std::make_unique<AQSAudioVoiceAllocator>();
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,42 @@
|
|||||||
#include "boo/audiodev/AudioMatrix.hpp"
|
#include "AudioMatrix.hpp"
|
||||||
|
#include "AudioVoiceEngine.hpp"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
|
|
||||||
void AudioMatrixMono::setDefaultMatrixCoefficients()
|
static inline int16_t Clamp16(float in)
|
||||||
|
{
|
||||||
|
if (in < SHRT_MIN)
|
||||||
|
return SHRT_MIN;
|
||||||
|
else if (in > SHRT_MAX)
|
||||||
|
return SHRT_MAX;
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int32_t Clamp32(float in)
|
||||||
|
{
|
||||||
|
if (in < INT_MIN)
|
||||||
|
return INT_MIN;
|
||||||
|
else if (in > INT_MAX)
|
||||||
|
return INT_MAX;
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float ClampFlt(float in)
|
||||||
|
{
|
||||||
|
if (in < -1.f)
|
||||||
|
return -1.f;
|
||||||
|
else if (in > 1.f)
|
||||||
|
return 1.f;
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMatrixMono::setDefaultMatrixCoefficients(AudioChannelSet acSet)
|
||||||
{
|
{
|
||||||
memset(m_coefs, 0, sizeof(m_coefs));
|
memset(m_coefs, 0, sizeof(m_coefs));
|
||||||
switch (m_setOut)
|
switch (acSet)
|
||||||
{
|
{
|
||||||
case AudioChannelSet::Stereo:
|
case AudioChannelSet::Stereo:
|
||||||
case AudioChannelSet::Quad:
|
case AudioChannelSet::Quad:
|
||||||
@ -22,27 +51,58 @@ void AudioMatrixMono::setDefaultMatrixCoefficients()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMatrixMono::bufferMonoSampleData(IAudioVoice& voice, const int16_t* data, size_t samples)
|
void AudioMatrixMono::mixMonoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int16_t* dataIn, int16_t* dataOut, size_t samples) const
|
||||||
{
|
{
|
||||||
const ChannelMap& chmap = voice.channelMap();
|
const ChannelMap& chmap = info.m_channelMap;
|
||||||
m_interleaveBuf.clear();
|
for (size_t s=0 ; s<samples ; ++s, ++dataIn)
|
||||||
m_interleaveBuf.reserve(samples * chmap.m_channelCount);
|
|
||||||
for (size_t s=0 ; s<samples ; ++s, ++data)
|
|
||||||
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
||||||
{
|
{
|
||||||
AudioChannel ch = chmap.m_channels[c];
|
AudioChannel ch = chmap.m_channels[c];
|
||||||
if (ch == AudioChannel::Unknown)
|
if (ch != AudioChannel::Unknown)
|
||||||
m_interleaveBuf.push_back(0);
|
{
|
||||||
else
|
*dataOut = Clamp16(*dataOut + *dataIn * m_coefs[int(ch)]);
|
||||||
m_interleaveBuf.push_back(data[0] * m_coefs[int(ch)]);
|
++dataOut;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
voice.bufferSampleData(m_interleaveBuf.data(), samples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMatrixStereo::setDefaultMatrixCoefficients()
|
void AudioMatrixMono::mixMonoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int32_t* dataIn, int32_t* dataOut, size_t samples) const
|
||||||
|
{
|
||||||
|
const ChannelMap& chmap = info.m_channelMap;
|
||||||
|
for (size_t s=0 ; s<samples ; ++s, ++dataIn)
|
||||||
|
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
||||||
|
{
|
||||||
|
AudioChannel ch = chmap.m_channels[c];
|
||||||
|
if (ch != AudioChannel::Unknown)
|
||||||
|
{
|
||||||
|
*dataOut = Clamp32(*dataOut + *dataIn * m_coefs[int(ch)]);
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMatrixMono::mixMonoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const float* dataIn, float* dataOut, size_t samples) const
|
||||||
|
{
|
||||||
|
const ChannelMap& chmap = info.m_channelMap;
|
||||||
|
for (size_t s=0 ; s<samples ; ++s, ++dataIn)
|
||||||
|
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
||||||
|
{
|
||||||
|
AudioChannel ch = chmap.m_channels[c];
|
||||||
|
if (ch != AudioChannel::Unknown)
|
||||||
|
{
|
||||||
|
*dataOut = ClampFlt(*dataOut + *dataIn * m_coefs[int(ch)]);
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMatrixStereo::setDefaultMatrixCoefficients(AudioChannelSet acSet)
|
||||||
{
|
{
|
||||||
memset(m_coefs, 0, sizeof(m_coefs));
|
memset(m_coefs, 0, sizeof(m_coefs));
|
||||||
switch (m_setOut)
|
switch (acSet)
|
||||||
{
|
{
|
||||||
case AudioChannelSet::Stereo:
|
case AudioChannelSet::Stereo:
|
||||||
case AudioChannelSet::Quad:
|
case AudioChannelSet::Quad:
|
||||||
@ -58,22 +118,58 @@ void AudioMatrixStereo::setDefaultMatrixCoefficients()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioMatrixStereo::bufferStereoSampleData(IAudioVoice& voice, const int16_t* data, size_t frames)
|
void AudioMatrixStereo::mixStereoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int16_t* dataIn, int16_t* dataOut, size_t frames) const
|
||||||
{
|
{
|
||||||
const ChannelMap& chmap = voice.channelMap();
|
const ChannelMap& chmap = info.m_channelMap;
|
||||||
m_interleaveBuf.clear();
|
for (size_t f=0 ; f<frames ; ++f, dataIn += 2)
|
||||||
m_interleaveBuf.reserve(frames * chmap.m_channelCount);
|
|
||||||
for (size_t f=0 ; f<frames ; ++f, data += 2)
|
|
||||||
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
||||||
{
|
{
|
||||||
AudioChannel ch = chmap.m_channels[c];
|
AudioChannel ch = chmap.m_channels[c];
|
||||||
if (ch == AudioChannel::Unknown)
|
if (ch != AudioChannel::Unknown)
|
||||||
m_interleaveBuf.push_back(0);
|
{
|
||||||
else
|
*dataOut = Clamp16(*dataOut +
|
||||||
m_interleaveBuf.push_back(data[0] * m_coefs[int(ch)][0] +
|
dataIn[0] * m_coefs[int(ch)][0] +
|
||||||
data[1] * m_coefs[int(ch)][1]);
|
dataIn[1] * m_coefs[int(ch)][1]);
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMatrixStereo::mixStereoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int32_t* dataIn, int32_t* dataOut, size_t frames) const
|
||||||
|
{
|
||||||
|
const ChannelMap& chmap = info.m_channelMap;
|
||||||
|
for (size_t f=0 ; f<frames ; ++f, dataIn += 2)
|
||||||
|
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
||||||
|
{
|
||||||
|
AudioChannel ch = chmap.m_channels[c];
|
||||||
|
if (ch != AudioChannel::Unknown)
|
||||||
|
{
|
||||||
|
*dataOut = Clamp32(*dataOut +
|
||||||
|
dataIn[0] * m_coefs[int(ch)][0] +
|
||||||
|
dataIn[1] * m_coefs[int(ch)][1]);
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioMatrixStereo::mixStereoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const float* dataIn, float* dataOut, size_t frames) const
|
||||||
|
{
|
||||||
|
const ChannelMap& chmap = info.m_channelMap;
|
||||||
|
for (size_t f=0 ; f<frames ; ++f, dataIn += 2)
|
||||||
|
for (unsigned c=0 ; c<chmap.m_channelCount ; ++c)
|
||||||
|
{
|
||||||
|
AudioChannel ch = chmap.m_channels[c];
|
||||||
|
if (ch != AudioChannel::Unknown)
|
||||||
|
{
|
||||||
|
*dataOut = ClampFlt(*dataOut +
|
||||||
|
dataIn[0] * m_coefs[int(ch)][0] +
|
||||||
|
dataIn[1] * m_coefs[int(ch)][1]);
|
||||||
|
++dataOut;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
voice.bufferSampleData(m_interleaveBuf.data(), frames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
59
lib/audiodev/AudioMatrix.hpp
Normal file
59
lib/audiodev/AudioMatrix.hpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#ifndef BOO_AUDIOMATRIX_HPP
|
||||||
|
#define BOO_AUDIOMATRIX_HPP
|
||||||
|
|
||||||
|
#include "boo/audiodev/IAudioVoice.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace boo
|
||||||
|
{
|
||||||
|
struct AudioVoiceEngineMixInfo;
|
||||||
|
|
||||||
|
class AudioMatrixMono
|
||||||
|
{
|
||||||
|
float m_coefs[8];
|
||||||
|
public:
|
||||||
|
AudioMatrixMono() {setDefaultMatrixCoefficients(AudioChannelSet::Stereo);}
|
||||||
|
|
||||||
|
void setDefaultMatrixCoefficients(AudioChannelSet acSet);
|
||||||
|
void setMatrixCoefficients(const float coefs[8])
|
||||||
|
{
|
||||||
|
for (int i=0 ; i<8 ; ++i)
|
||||||
|
m_coefs[i] = coefs[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void mixMonoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int16_t* dataIn, int16_t* dataOut, size_t samples) const;
|
||||||
|
void mixMonoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int32_t* dataIn, int32_t* dataOut, size_t samples) const;
|
||||||
|
void mixMonoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const float* dataIn, float* dataOut, size_t samples) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AudioMatrixStereo
|
||||||
|
{
|
||||||
|
float m_coefs[8][2];
|
||||||
|
public:
|
||||||
|
AudioMatrixStereo() {setDefaultMatrixCoefficients(AudioChannelSet::Stereo);}
|
||||||
|
|
||||||
|
void setDefaultMatrixCoefficients(AudioChannelSet acSet);
|
||||||
|
void setMatrixCoefficients(const float coefs[8][2])
|
||||||
|
{
|
||||||
|
for (int i=0 ; i<8 ; ++i)
|
||||||
|
{
|
||||||
|
m_coefs[i][0] = coefs[i][0];
|
||||||
|
m_coefs[i][1] = coefs[i][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mixStereoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int16_t* dataIn, int16_t* dataOut, size_t frames) const;
|
||||||
|
void mixStereoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const int32_t* dataIn, int32_t* dataOut, size_t frames) const;
|
||||||
|
void mixStereoSampleData(const AudioVoiceEngineMixInfo& info,
|
||||||
|
const float* dataIn, float* dataOut, size_t frames) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOO_AUDIOMATRIX_HPP
|
@ -0,0 +1,239 @@
|
|||||||
|
#include "AudioVoice.hpp"
|
||||||
|
#include "AudioVoiceEngine.hpp"
|
||||||
|
#include "logvisor/logvisor.hpp"
|
||||||
|
|
||||||
|
namespace boo
|
||||||
|
{
|
||||||
|
static logvisor::Module Log("boo::AudioVoice");
|
||||||
|
|
||||||
|
static std::vector<int16_t> scratchIn;
|
||||||
|
static std::vector<int16_t> scratch16;
|
||||||
|
static std::vector<int32_t> scratch32;
|
||||||
|
static std::vector<float> scratchFlt;
|
||||||
|
|
||||||
|
AudioVoice::AudioVoice(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb, bool dynamicRate)
|
||||||
|
: m_parent(parent), m_cb(cb), m_dynamicRate(dynamicRate) {}
|
||||||
|
|
||||||
|
AudioVoice::~AudioVoice()
|
||||||
|
{
|
||||||
|
unbindVoice();
|
||||||
|
soxr_delete(m_src);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoice::setPitchRatio(double ratio)
|
||||||
|
{
|
||||||
|
if (m_dynamicRate)
|
||||||
|
{
|
||||||
|
soxr_error_t err = soxr_set_io_ratio(m_src, ratio, m_parent.mixInfo().m_periodFrames);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Fatal, "unable to set resampler rate: %s", soxr_strerror(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoice::start()
|
||||||
|
{
|
||||||
|
m_running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoice::stop()
|
||||||
|
{
|
||||||
|
m_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoice::unbindVoice()
|
||||||
|
{
|
||||||
|
if (m_bound)
|
||||||
|
{
|
||||||
|
m_parent.m_activeVoices.erase(m_parentIt);
|
||||||
|
m_bound = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioVoiceMono::AudioVoiceMono(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
|
||||||
|
double sampleRate, bool dynamicRate)
|
||||||
|
: AudioVoice(parent, cb, dynamicRate)
|
||||||
|
{
|
||||||
|
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_INT16_I, parent.mixInfo().m_sampleFormat);
|
||||||
|
soxr_quality_spec_t qSpec = soxr_quality_spec(SOXR_20_BITQ, dynamicRate ? SOXR_VR : 0);
|
||||||
|
|
||||||
|
soxr_error_t err;
|
||||||
|
m_src = soxr_create(sampleRate, parent.mixInfo().m_sampleRate, 1,
|
||||||
|
&err, &ioSpec, &qSpec, nullptr);
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Fatal, "unable to create soxr resampler: %s", soxr_strerror(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
soxr_set_input_fn(m_src, soxr_input_fn_t(SRCCallback), this, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceMono::SRCCallback(AudioVoiceMono* ctx, int16_t** data, size_t frames)
|
||||||
|
{
|
||||||
|
if (scratchIn.size() < frames)
|
||||||
|
scratchIn.resize(frames);
|
||||||
|
*data = scratchIn.data();
|
||||||
|
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceMono::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
||||||
|
size_t frames, int16_t* buf)
|
||||||
|
{
|
||||||
|
if (scratch16.size() < frames)
|
||||||
|
scratch16.resize(frames);
|
||||||
|
|
||||||
|
size_t oDone = soxr_output(m_src, scratch16.data(), frames);
|
||||||
|
|
||||||
|
m_matrix.mixMonoSampleData(mixInfo, scratch16.data(), buf, oDone);
|
||||||
|
return oDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceMono::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
||||||
|
size_t frames, int32_t* buf)
|
||||||
|
{
|
||||||
|
if (scratch32.size() < frames)
|
||||||
|
scratch32.resize(frames);
|
||||||
|
|
||||||
|
size_t oDone = soxr_output(m_src, scratch32.data(), frames);
|
||||||
|
|
||||||
|
m_matrix.mixMonoSampleData(mixInfo, scratch32.data(), buf, oDone);
|
||||||
|
return oDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceMono::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
||||||
|
size_t frames, float* buf)
|
||||||
|
{
|
||||||
|
if (scratchFlt.size() < frames)
|
||||||
|
scratchFlt.resize(frames);
|
||||||
|
|
||||||
|
size_t oDone = soxr_output(m_src, scratchFlt.data(), frames);
|
||||||
|
|
||||||
|
m_matrix.mixMonoSampleData(mixInfo, scratchFlt.data(), buf, oDone);
|
||||||
|
return oDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoiceMono::setDefaultMatrixCoefficients()
|
||||||
|
{
|
||||||
|
m_matrix.setDefaultMatrixCoefficients(m_parent.mixInfo().m_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoiceMono::setMonoMatrixCoefficients(const float coefs[8])
|
||||||
|
{
|
||||||
|
m_matrix.setMatrixCoefficients(coefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoiceMono::setStereoMatrixCoefficients(const float coefs[8][2])
|
||||||
|
{
|
||||||
|
float newCoefs[8] =
|
||||||
|
{
|
||||||
|
coefs[0][0],
|
||||||
|
coefs[1][0],
|
||||||
|
coefs[2][0],
|
||||||
|
coefs[3][0],
|
||||||
|
coefs[4][0],
|
||||||
|
coefs[5][0],
|
||||||
|
coefs[6][0],
|
||||||
|
coefs[7][0]
|
||||||
|
};
|
||||||
|
m_matrix.setMatrixCoefficients(newCoefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioVoiceStereo::AudioVoiceStereo(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
|
||||||
|
double sampleRate, bool dynamicRate)
|
||||||
|
: AudioVoice(parent, cb, dynamicRate)
|
||||||
|
{
|
||||||
|
soxr_io_spec_t ioSpec = soxr_io_spec(SOXR_INT16_I, parent.mixInfo().m_sampleFormat);
|
||||||
|
soxr_quality_spec_t qSpec = soxr_quality_spec(SOXR_20_BITQ, dynamicRate ? SOXR_VR : 0);
|
||||||
|
|
||||||
|
soxr_error_t err;
|
||||||
|
m_src = soxr_create(sampleRate, parent.mixInfo().m_sampleRate, 2,
|
||||||
|
&err, &ioSpec, &qSpec, nullptr);
|
||||||
|
|
||||||
|
if (!m_src)
|
||||||
|
{
|
||||||
|
Log.report(logvisor::Fatal, "unable to create soxr resampler: %s", soxr_strerror(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
soxr_set_input_fn(m_src, soxr_input_fn_t(SRCCallback), this, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceStereo::SRCCallback(AudioVoiceStereo* ctx, int16_t** data, size_t frames)
|
||||||
|
{
|
||||||
|
size_t samples = frames * 2;
|
||||||
|
if (scratchIn.size() < samples)
|
||||||
|
scratchIn.resize(samples);
|
||||||
|
*data = scratchIn.data();
|
||||||
|
return ctx->m_cb->supplyAudio(*ctx, frames, scratchIn.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceStereo::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
||||||
|
size_t frames, int16_t* buf)
|
||||||
|
{
|
||||||
|
size_t samples = frames * 2;
|
||||||
|
if (scratch16.size() < samples)
|
||||||
|
scratch16.resize(samples);
|
||||||
|
|
||||||
|
size_t oDone = soxr_output(m_src, scratch16.data(), frames);
|
||||||
|
|
||||||
|
m_matrix.mixStereoSampleData(mixInfo, scratch16.data(), buf, oDone);
|
||||||
|
return oDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceStereo::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
||||||
|
size_t frames, int32_t* buf)
|
||||||
|
{
|
||||||
|
size_t samples = frames * 2;
|
||||||
|
if (scratch32.size() < samples)
|
||||||
|
scratch32.resize(samples);
|
||||||
|
|
||||||
|
size_t oDone = soxr_output(m_src, scratch32.data(), frames);
|
||||||
|
|
||||||
|
m_matrix.mixStereoSampleData(mixInfo, scratch32.data(), buf, oDone);
|
||||||
|
return oDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AudioVoiceStereo::pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo,
|
||||||
|
size_t frames, float* buf)
|
||||||
|
{
|
||||||
|
size_t samples = frames * 2;
|
||||||
|
if (scratchFlt.size() < samples)
|
||||||
|
scratchFlt.resize(samples);
|
||||||
|
|
||||||
|
size_t oDone = soxr_output(m_src, scratchFlt.data(), frames);
|
||||||
|
|
||||||
|
m_matrix.mixStereoSampleData(mixInfo, scratchFlt.data(), buf, oDone);
|
||||||
|
return oDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoiceStereo::setDefaultMatrixCoefficients()
|
||||||
|
{
|
||||||
|
m_matrix.setDefaultMatrixCoefficients(m_parent.mixInfo().m_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoiceStereo::setMonoMatrixCoefficients(const float coefs[8])
|
||||||
|
{
|
||||||
|
float newCoefs[8][2] =
|
||||||
|
{
|
||||||
|
{coefs[0], coefs[0]},
|
||||||
|
{coefs[1], coefs[1]},
|
||||||
|
{coefs[2], coefs[2]},
|
||||||
|
{coefs[3], coefs[3]},
|
||||||
|
{coefs[4], coefs[4]},
|
||||||
|
{coefs[5], coefs[5]},
|
||||||
|
{coefs[6], coefs[6]},
|
||||||
|
{coefs[7], coefs[7]}
|
||||||
|
};
|
||||||
|
m_matrix.setMatrixCoefficients(newCoefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioVoiceStereo::setStereoMatrixCoefficients(const float coefs[8][2])
|
||||||
|
{
|
||||||
|
m_matrix.setMatrixCoefficients(coefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,13 +2,90 @@
|
|||||||
#define BOO_AUDIOVOICE_HPP
|
#define BOO_AUDIOVOICE_HPP
|
||||||
|
|
||||||
#include <soxr.h>
|
#include <soxr.h>
|
||||||
|
#include <list>
|
||||||
|
#include "boo/audiodev/IAudioVoice.hpp"
|
||||||
|
#include "AudioMatrix.hpp"
|
||||||
|
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
|
class BaseAudioVoiceEngine;
|
||||||
|
struct AudioVoiceEngineMixInfo;
|
||||||
|
|
||||||
class AudioVoice
|
class AudioVoice : public IAudioVoice
|
||||||
{
|
{
|
||||||
soxr_t m_voice;
|
friend class BaseAudioVoiceEngine;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* Mixer-engine relationships */
|
||||||
|
BaseAudioVoiceEngine& m_parent;
|
||||||
|
std::list<AudioVoice*>::iterator m_parentIt;
|
||||||
|
bool m_bound = false;
|
||||||
|
void bindVoice(std::list<AudioVoice*>::iterator pIt)
|
||||||
|
{
|
||||||
|
m_bound = true;
|
||||||
|
m_parentIt = pIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callback (audio source) */
|
||||||
|
IAudioVoiceCallback* m_cb;
|
||||||
|
|
||||||
|
/* Sample-rate converter */
|
||||||
|
soxr_t m_src;
|
||||||
|
bool m_dynamicRate;
|
||||||
|
|
||||||
|
/* Running bool */
|
||||||
|
bool m_running = false;
|
||||||
|
|
||||||
|
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf)=0;
|
||||||
|
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf)=0;
|
||||||
|
virtual size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf)=0;
|
||||||
|
AudioVoice(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb, bool dynamicRate);
|
||||||
|
|
||||||
|
public:
|
||||||
|
~AudioVoice();
|
||||||
|
|
||||||
|
void setPitchRatio(double ratio);
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void unbindVoice();
|
||||||
|
};
|
||||||
|
|
||||||
|
class AudioVoiceMono : public AudioVoice
|
||||||
|
{
|
||||||
|
AudioMatrixMono m_matrix;
|
||||||
|
|
||||||
|
static size_t SRCCallback(AudioVoiceMono* ctx,
|
||||||
|
int16_t** data, size_t requestedLen);
|
||||||
|
|
||||||
|
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf);
|
||||||
|
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf);
|
||||||
|
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AudioVoiceMono(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
|
||||||
|
double sampleRate, bool dynamicRate);
|
||||||
|
void setDefaultMatrixCoefficients();
|
||||||
|
void setMonoMatrixCoefficients(const float coefs[8]);
|
||||||
|
void setStereoMatrixCoefficients(const float coefs[8][2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
class AudioVoiceStereo : public AudioVoice
|
||||||
|
{
|
||||||
|
AudioMatrixStereo m_matrix;
|
||||||
|
|
||||||
|
static size_t SRCCallback(AudioVoiceStereo* ctx,
|
||||||
|
int16_t** data, size_t requestedLen);
|
||||||
|
|
||||||
|
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int16_t* buf);
|
||||||
|
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, int32_t* buf);
|
||||||
|
size_t pumpAndMix(const AudioVoiceEngineMixInfo& mixInfo, size_t frames, float* buf);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AudioVoiceStereo(BaseAudioVoiceEngine& parent, IAudioVoiceCallback* cb,
|
||||||
|
double sampleRate, bool dynamicRate);
|
||||||
|
void setDefaultMatrixCoefficients();
|
||||||
|
void setMonoMatrixCoefficients(const float coefs[8]);
|
||||||
|
void setStereoMatrixCoefficients(const float coefs[8][2]);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
61
lib/audiodev/AudioVoiceEngine.cpp
Normal file
61
lib/audiodev/AudioVoiceEngine.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include "AudioVoiceEngine.hpp"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace boo
|
||||||
|
{
|
||||||
|
|
||||||
|
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int16_t* dataOut)
|
||||||
|
{
|
||||||
|
memset(dataOut, 0, sizeof(int16_t) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
|
if (vox->m_running)
|
||||||
|
vox->pumpAndMix(m_mixInfo, frames, dataOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, int32_t* dataOut)
|
||||||
|
{
|
||||||
|
memset(dataOut, 0, sizeof(int32_t) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
|
if (vox->m_running)
|
||||||
|
vox->pumpAndMix(m_mixInfo, frames, dataOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseAudioVoiceEngine::_pumpAndMixVoices(size_t frames, float* dataOut)
|
||||||
|
{
|
||||||
|
memset(dataOut, 0, sizeof(float) * frames * m_mixInfo.m_channelMap.m_channelCount);
|
||||||
|
for (AudioVoice* vox : m_activeVoices)
|
||||||
|
if (vox->m_running)
|
||||||
|
vox->pumpAndMix(m_mixInfo, frames, dataOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseAudioVoiceEngine::BaseAudioVoiceEngine
|
||||||
|
(const std::function<AudioVoiceEngineMixInfo()>& getEngineMixInfo)
|
||||||
|
{
|
||||||
|
m_mixInfo = getEngineMixInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IAudioVoice>
|
||||||
|
BaseAudioVoiceEngine::allocateNewMonoVoice(double sampleRate,
|
||||||
|
IAudioVoiceCallback* cb,
|
||||||
|
bool dynamicPitch)
|
||||||
|
{
|
||||||
|
std::unique_ptr<IAudioVoice> ret =
|
||||||
|
std::make_unique<AudioVoiceMono>(*this, cb, sampleRate, dynamicPitch);
|
||||||
|
AudioVoiceMono* retMono = static_cast<AudioVoiceMono*>(ret.get());
|
||||||
|
retMono->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retMono));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<IAudioVoice>
|
||||||
|
BaseAudioVoiceEngine::allocateNewStereoVoice(double sampleRate,
|
||||||
|
IAudioVoiceCallback* cb,
|
||||||
|
bool dynamicPitch)
|
||||||
|
{
|
||||||
|
std::unique_ptr<IAudioVoice> ret =
|
||||||
|
std::make_unique<AudioVoiceStereo>(*this, cb, sampleRate, dynamicPitch);
|
||||||
|
AudioVoiceStereo* retStereo = static_cast<AudioVoiceStereo*>(ret.get());
|
||||||
|
retStereo->bindVoice(m_activeVoices.insert(m_activeVoices.end(), retStereo));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
lib/audiodev/AudioVoiceEngine.hpp
Normal file
51
lib/audiodev/AudioVoiceEngine.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef BOO_AUDIOVOICEENGINE_HPP
|
||||||
|
#define BOO_AUDIOVOICEENGINE_HPP
|
||||||
|
|
||||||
|
#include "boo/audiodev/IAudioVoiceEngine.hpp"
|
||||||
|
#include "AudioVoice.hpp"
|
||||||
|
|
||||||
|
namespace boo
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Pertinent information from audio backend about optimal mixed-audio representation */
|
||||||
|
struct AudioVoiceEngineMixInfo
|
||||||
|
{
|
||||||
|
double m_sampleRate;
|
||||||
|
soxr_datatype_t m_sampleFormat;
|
||||||
|
unsigned m_bitsPerSample;
|
||||||
|
AudioChannelSet m_channels;
|
||||||
|
ChannelMap m_channelMap;
|
||||||
|
size_t m_periodFrames;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Base class for managing mixing and sample-rate-conversion amongst active voices */
|
||||||
|
class BaseAudioVoiceEngine : public IAudioVoiceEngine
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
friend class AudioVoice;
|
||||||
|
AudioVoiceEngineMixInfo m_mixInfo;
|
||||||
|
std::list<AudioVoice*> m_activeVoices;
|
||||||
|
|
||||||
|
void _pumpAndMixVoices(size_t frames, int16_t* dataOut);
|
||||||
|
void _pumpAndMixVoices(size_t frames, int32_t* dataOut);
|
||||||
|
void _pumpAndMixVoices(size_t frames, float* dataOut);
|
||||||
|
|
||||||
|
public:
|
||||||
|
BaseAudioVoiceEngine(const std::function<AudioVoiceEngineMixInfo()>& getEngineMixInfo);
|
||||||
|
|
||||||
|
std::unique_ptr<IAudioVoice> allocateNewMonoVoice(double sampleRate,
|
||||||
|
IAudioVoiceCallback* cb,
|
||||||
|
bool dynamicPitch=false);
|
||||||
|
|
||||||
|
std::unique_ptr<IAudioVoice> allocateNewStereoVoice(double sampleRate,
|
||||||
|
IAudioVoiceCallback* cb,
|
||||||
|
bool dynamicPitch=false);
|
||||||
|
|
||||||
|
const AudioVoiceEngineMixInfo& mixInfo() const {return m_mixInfo;}
|
||||||
|
AudioChannelSet getAvailableSet() {return m_mixInfo.m_channels;}
|
||||||
|
void pumpAndMixVoices() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BOO_AUDIOVOICEENGINE_HPP
|
Loading…
x
Reference in New Issue
Block a user