118 lines
4.0 KiB
C
118 lines
4.0 KiB
C
|
|
//
|
||
|
|
// Created by david on 01.02.2026.
|
||
|
|
//
|
||
|
|
|
||
|
|
#ifndef LOCKSTEP_MIXINGPLAYER_H
|
||
|
|
#define LOCKSTEP_MIXINGPLAYER_H
|
||
|
|
|
||
|
|
#include <oboe/Oboe.h>
|
||
|
|
#include <math.h>
|
||
|
|
using namespace oboe;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Plays event sounds, potentially overlapping in time.
|
||
|
|
* Use <c>setStartBeat()</c> to trigger playing sound for an event.
|
||
|
|
*/
|
||
|
|
class MixingPlayer: public oboe::AudioStreamDataCallback {
|
||
|
|
protected:
|
||
|
|
std::vector<float> beatSound;
|
||
|
|
std::vector<int> beatIdx;
|
||
|
|
std::atomic<int> startBeat;
|
||
|
|
int numBeatsPlaying;
|
||
|
|
public:
|
||
|
|
explicit MixingPlayer(std::vector<float> beatSound) : beatSound(beatSound), startBeat(0), numBeatsPlaying(0) {}
|
||
|
|
|
||
|
|
virtual ~MixingPlayer() = default;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Play a beat sound by setting the startBeat register.
|
||
|
|
* A delay of the audio callback interval will be added.
|
||
|
|
*/
|
||
|
|
void setStartBeat() {
|
||
|
|
startBeat.store(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Call this from Activity onResume()
|
||
|
|
int32_t startAudio() {
|
||
|
|
std::lock_guard<std::mutex> lock(mLock);
|
||
|
|
oboe::AudioStreamBuilder builder;
|
||
|
|
// The builder set methods can be chained for convenience.
|
||
|
|
Result result = builder.setSharingMode(oboe::SharingMode::Exclusive)
|
||
|
|
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||
|
|
->setChannelCount(kChannelCount)
|
||
|
|
->setSampleRate(kSampleRate)
|
||
|
|
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)
|
||
|
|
->setFormat(oboe::AudioFormat::Float)
|
||
|
|
->setDataCallback(this)
|
||
|
|
->openStream(mStream);
|
||
|
|
if (result != Result::OK) return (int32_t) result;
|
||
|
|
|
||
|
|
// Typically, start the stream after querying some stream information, as well as some input from the user
|
||
|
|
result = mStream->requestStart();
|
||
|
|
return (int32_t) result;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Call this from Activity onPause()
|
||
|
|
void stopAudio() {
|
||
|
|
// Stop, close and delete in case not already closed.
|
||
|
|
std::lock_guard<std::mutex> lock(mLock);
|
||
|
|
if (mStream) {
|
||
|
|
mStream->stop();
|
||
|
|
mStream->close();
|
||
|
|
mStream.reset();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
|
||
|
|
// fetch startBeat register
|
||
|
|
int isStartBeat = startBeat.load();
|
||
|
|
if(isStartBeat) {
|
||
|
|
if(beatIdx.size() < numBeatsPlaying+1) beatIdx.resize(numBeatsPlaying+1);
|
||
|
|
beatIdx[numBeatsPlaying++] = 0;
|
||
|
|
startBeat.store(0); // reset startBeat register
|
||
|
|
}
|
||
|
|
//
|
||
|
|
// mix audio events, each event has a separate idx counter in beatIdx
|
||
|
|
//
|
||
|
|
float *floatData = (float *) audioData;
|
||
|
|
int N = (int) beatSound.size();
|
||
|
|
for (int i = 0; i < numFrames; i++) {
|
||
|
|
float sample = 0.0;
|
||
|
|
float norm = (float) numBeatsPlaying;
|
||
|
|
for (int k = 0; k < numBeatsPlaying; k++) {
|
||
|
|
sample += beatSound[beatIdx[k]];
|
||
|
|
beatIdx[k] += 1;
|
||
|
|
}
|
||
|
|
// reduce beatIdx to the events which have not completed playing
|
||
|
|
int l = 0;
|
||
|
|
int m = 0;
|
||
|
|
for (; m < numBeatsPlaying; l++, m++) {
|
||
|
|
while(m < numBeatsPlaying && beatIdx[m] >= N) m++;
|
||
|
|
if(m < numBeatsPlaying)
|
||
|
|
beatIdx[l] = beatIdx[m];
|
||
|
|
else
|
||
|
|
break; // avoid incrementing l
|
||
|
|
}
|
||
|
|
//beatIdx.resize(l); // we avoid re-allocating
|
||
|
|
numBeatsPlaying = l;
|
||
|
|
// set left and right output channels
|
||
|
|
for (int j = 0; j < kChannelCount; j++) {
|
||
|
|
// normalize sample by numBeatsPlaying (avoids clipping)
|
||
|
|
floatData[i * kChannelCount + j] = sample / norm;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return oboe::DataCallbackResult::Continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
std::mutex mLock;
|
||
|
|
std::shared_ptr<oboe::AudioStream> mStream;
|
||
|
|
|
||
|
|
// Stream params
|
||
|
|
static int constexpr kChannelCount = 2;
|
||
|
|
static int constexpr kSampleRate = 48000;
|
||
|
|
};
|
||
|
|
|
||
|
|
#endif //LOCKSTEP_MIXINGPLAYER_H
|