245 lines
5.5 KiB
C++
245 lines
5.5 KiB
C++
/*
|
|
AudioOutputMixer
|
|
Simple mixer which can combine multiple inputs to a single output stream
|
|
|
|
Copyright (C) 2018 Earle F. Philhower, III
|
|
|
|
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
|
|
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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include "AudioOutputMixer.h"
|
|
|
|
AudioOutputMixerStub::AudioOutputMixerStub(AudioOutputMixer *sink, int id) : AudioOutput()
|
|
{
|
|
this->id = id;
|
|
this->parent = sink;
|
|
SetGain(1.0);
|
|
}
|
|
|
|
AudioOutputMixerStub::~AudioOutputMixerStub()
|
|
{
|
|
parent->RemoveInput(id);
|
|
}
|
|
|
|
bool AudioOutputMixerStub::SetRate(int hz)
|
|
{
|
|
return parent->SetRate(hz, id);
|
|
}
|
|
|
|
bool AudioOutputMixerStub::SetBitsPerSample(int bits)
|
|
{
|
|
return parent->SetBitsPerSample(bits, id);
|
|
}
|
|
|
|
bool AudioOutputMixerStub::SetChannels(int channels)
|
|
{
|
|
return parent->SetChannels(channels, id);
|
|
}
|
|
|
|
bool AudioOutputMixerStub::begin()
|
|
{
|
|
return parent->begin(id);
|
|
}
|
|
|
|
bool AudioOutputMixerStub::ConsumeSample(int16_t sample[2])
|
|
{
|
|
int16_t amp[2];
|
|
amp[LEFTCHANNEL] = Amplify(sample[LEFTCHANNEL]);
|
|
amp[RIGHTCHANNEL] = Amplify(sample[RIGHTCHANNEL]);
|
|
return parent->ConsumeSample(amp, id);
|
|
}
|
|
|
|
bool AudioOutputMixerStub::stop()
|
|
{
|
|
return parent->stop(id);
|
|
}
|
|
|
|
|
|
|
|
AudioOutputMixer::AudioOutputMixer(int buffSizeSamples, AudioOutput *dest) : AudioOutput()
|
|
{
|
|
buffSize = buffSizeSamples;
|
|
leftAccum = (int32_t*)calloc(sizeof(int32_t), buffSize);
|
|
rightAccum = (int32_t*)calloc(sizeof(int32_t), buffSize);
|
|
for (int i=0; i<maxStubs; i++) {
|
|
stubAllocated[i] = false;
|
|
stubRunning[i] = false;
|
|
writePtr[i] = 0;
|
|
}
|
|
readPtr = 0;
|
|
sink = dest;
|
|
sinkStarted = false;
|
|
}
|
|
|
|
AudioOutputMixer::~AudioOutputMixer()
|
|
{
|
|
free(leftAccum);
|
|
free(rightAccum);
|
|
}
|
|
|
|
|
|
// Most "standard" interfaces should fail, only MixerStub should be able to talk to us
|
|
bool AudioOutputMixer::SetRate(int hz)
|
|
{
|
|
(void) hz;
|
|
return false;
|
|
}
|
|
|
|
bool AudioOutputMixer::SetBitsPerSample(int bits)
|
|
{
|
|
(void) bits;
|
|
return false;
|
|
}
|
|
|
|
bool AudioOutputMixer::SetChannels(int channels)
|
|
{
|
|
(void) channels;
|
|
return false;
|
|
}
|
|
|
|
bool AudioOutputMixer::ConsumeSample(int16_t sample[2])
|
|
{
|
|
(void) sample;
|
|
return false;
|
|
}
|
|
|
|
bool AudioOutputMixer::begin()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool AudioOutputMixer::stop()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
// TODO - actually ensure all samples are same speed, size, channels, rate
|
|
bool AudioOutputMixer::SetRate(int hz, int id)
|
|
{
|
|
(void) id;
|
|
return sink->SetRate(hz);
|
|
}
|
|
|
|
bool AudioOutputMixer::SetBitsPerSample(int bits, int id)
|
|
{
|
|
(void) id;
|
|
return sink->SetBitsPerSample(bits);
|
|
}
|
|
|
|
bool AudioOutputMixer::SetChannels(int channels, int id)
|
|
{
|
|
(void) id;
|
|
return sink->SetChannels(channels);
|
|
}
|
|
|
|
bool AudioOutputMixer::begin(int id)
|
|
{
|
|
stubRunning[id] = true;
|
|
|
|
if (!sinkStarted) {
|
|
sinkStarted = true;
|
|
return sink->begin();
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
AudioOutputMixerStub *AudioOutputMixer::NewInput()
|
|
{
|
|
for (int i=0; i<maxStubs; i++) {
|
|
if (!stubAllocated[i]) {
|
|
stubAllocated[i] = true;
|
|
stubRunning[i] = false;
|
|
writePtr[i] = readPtr; // TODO - should it be 1 before readPtr?
|
|
AudioOutputMixerStub *stub = new AudioOutputMixerStub(this, i);
|
|
return stub;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void AudioOutputMixer::RemoveInput(int id)
|
|
{
|
|
stubAllocated[id] = false;
|
|
stubRunning[id] = false;
|
|
}
|
|
|
|
bool AudioOutputMixer::loop()
|
|
{
|
|
// First, try and fill I2S...
|
|
// This is not optimal, but algorithmically should work fine
|
|
bool avail;
|
|
do {
|
|
avail = true;
|
|
for (int i=0; i<maxStubs && avail; i++) {
|
|
if (stubRunning[i] && writePtr[i] == readPtr) {
|
|
avail = false; // The read pointer is touching an active writer, can't advance
|
|
}
|
|
}
|
|
if (avail) {
|
|
int16_t s[2];
|
|
if (leftAccum[readPtr] > 32767) {
|
|
s[LEFTCHANNEL] = 32767;
|
|
} else if (leftAccum[readPtr] < -32767) {
|
|
s[LEFTCHANNEL] = -32767;
|
|
} else {
|
|
s[LEFTCHANNEL] = leftAccum[readPtr];
|
|
}
|
|
if (rightAccum[readPtr] > 32767) {
|
|
s[RIGHTCHANNEL] = 32767;
|
|
} else if (rightAccum[readPtr] < -32767) {
|
|
s[RIGHTCHANNEL] = -32767;
|
|
} else {
|
|
s[RIGHTCHANNEL] = rightAccum[readPtr];
|
|
}
|
|
// s[LEFTCHANNEL] = Amplify(s[LEFTCHANNEL]);
|
|
// s[RIGHTCHANNEL] = Amplify(s[RIGHTCHANNEL]);
|
|
if (!sink->ConsumeSample(s)) {
|
|
break; // Can't stuff any more in I2S...
|
|
}
|
|
// Clear the accums and advance the pointer to next potential sample
|
|
leftAccum[readPtr] = 0;
|
|
rightAccum[readPtr] = 0;
|
|
readPtr = (readPtr + 1) % buffSize;
|
|
}
|
|
} while (avail);
|
|
return true;
|
|
}
|
|
|
|
bool AudioOutputMixer::ConsumeSample(int16_t sample[2], int id)
|
|
{
|
|
loop(); // Send any pre-existing, completed I2S data we can fit
|
|
|
|
// Now, do we have space for a new sample?
|
|
int nextWritePtr = (writePtr[id] + 1) % buffSize;
|
|
if (nextWritePtr == readPtr) {
|
|
return false;
|
|
}
|
|
|
|
leftAccum[writePtr[id]] += sample[LEFTCHANNEL];
|
|
rightAccum[writePtr[id]] += sample[RIGHTCHANNEL];
|
|
writePtr[id] = nextWritePtr;
|
|
return true;
|
|
}
|
|
|
|
bool AudioOutputMixer::stop(int id)
|
|
{
|
|
stubRunning[id] = false;
|
|
return true;
|
|
}
|
|
|
|
|