Record audio using audio directly from audio thread to guarantee no audio is missed

pull/213/head
James Ball 2024-01-28 19:58:28 +00:00 zatwierdzone przez James H Ball
rodzic 95ab90ad63
commit 4401a7674b
3 zmienionych plików z 32 dodań i 34 usunięć

Wyświetl plik

@ -187,6 +187,11 @@ const juce::String OscirenderAudioProcessor::getName() const {
return JucePlugin_Name;
}
void OscirenderAudioProcessor::setAudioThreadCallback(std::function<void(const juce::AudioBuffer<float>&)> callback) {
juce::SpinLock::ScopedLockType lock(audioThreadCallbackLock);
audioThreadCallback = callback;
}
bool OscirenderAudioProcessor::acceptsMidi() const {
#if JucePlugin_WantsMidiInput
return true;
@ -660,6 +665,10 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
}
}
}
// used for any callback that must guarantee all audio is recieved (e.g. when recording to a file)
juce::SpinLock::ScopedLockType lock(audioThreadCallbackLock);
audioThreadCallback(buffer);
}
//==============================================================================

Wyświetl plik

@ -51,6 +51,8 @@ public:
const juce::String getName() const override;
void setAudioThreadCallback(std::function<void(const juce::AudioBuffer<float>&)> callback);
bool acceptsMidi() const override;
bool producesMidi() const override;
bool isMidiEffect() const override;
@ -316,6 +318,9 @@ private:
bool prevMidiEnabled = !midiEnabled->getBoolValue();
juce::SpinLock audioThreadCallbackLock;
std::function<void(const juce::AudioBuffer<float>&)> audioThreadCallback;
std::vector<BooleanParameter*> booleanParameters;
std::vector<FloatParameter*> floatParameters;
std::vector<IntParameter*> intParameters;

Wyświetl plik

@ -50,18 +50,17 @@
#include "DoubleTextBox.h"
//==============================================================================
class AudioRecorder final : public juce::Thread {
class AudioRecorder final {
public:
AudioRecorder(OscirenderAudioProcessor& p, juce::AudioThumbnail& thumbnailToUpdate)
: audioProcessor(p), thumbnail(thumbnailToUpdate), juce::Thread("Audio Recorder") {
: audioProcessor(p), thumbnail(thumbnailToUpdate) {
backgroundThread.startThread();
startThread();
audioProcessor.setAudioThreadCallback([this](const juce::AudioBuffer<float>& buffer) { audioThreadCallback(buffer); });
}
~AudioRecorder() override {
~AudioRecorder() {
audioProcessor.setAudioThreadCallback(nullptr);
stop();
audioProcessor.consumerStop(consumer);
stopThread(1000);
}
//==============================================================================
@ -112,35 +111,22 @@ public:
return activeWriter.load() != nullptr;
}
void run() override {
while (!threadShouldExit()) {
consumer = audioProcessor.consumerRegister(buffer);
audioProcessor.consumerRead(consumer);
void audioThreadCallback(const juce::AudioBuffer<float>& buffer) {
if (nextSampleNum >= recordingLength * audioProcessor.currentSampleRate) {
stop();
stopCallback();
continue;
return;
}
const juce::ScopedLock sl(writerLock);
int numSamples = buffer.size() / 2;
// convert 1D buffer to juce::AudioBuffer
juce::AudioBuffer<float> audioBuffer(2, numSamples);
for (int i = 0; i < numSamples; i++) {
audioBuffer.setSample(0, i, buffer[i * 2]);
audioBuffer.setSample(1, i, buffer[i * 2 + 1]);
}
int numSamples = buffer.getNumSamples();
if (activeWriter.load() != nullptr) {
activeWriter.load()->write(audioBuffer.getArrayOfReadPointers(), numSamples);
thumbnail.addBlock(nextSampleNum, audioBuffer, 0, numSamples);
activeWriter.load()->write(buffer.getArrayOfReadPointers(), numSamples);
thumbnail.addBlock(nextSampleNum, buffer, 0, numSamples);
nextSampleNum += numSamples;
}
}
}
void setRecordLength(double recordLength) {
recordingLength = recordLength;
@ -155,8 +141,6 @@ private:
juce::TimeSliceThread backgroundThread { "Audio Recorder Thread" }; // the thread that will write our audio data to disk
std::unique_ptr<juce::AudioFormatWriter::ThreadedWriter> threadedWriter; // the FIFO used to buffer the incoming data
juce::int64 nextSampleNum = 0;
std::vector<float> buffer = std::vector<float>(2 << 12);
std::shared_ptr<BufferConsumer> consumer;
double recordingLength = 99999999999.0;
@ -220,7 +204,7 @@ public:
addAndMakeVisible(recordLength);
recordButton.setTooltip("Start recording audio to a WAV file. Press again to stop and save the recording.");
timedRecord.setTooltip("Record for a set amount of time. When enabled, the recording will automatically stop once the time is reached.");
timedRecord.setTooltip("Record for a set amount of time in seconds. When enabled, the recording will automatically stop once the time is reached.");
recordLength.setValue(1);