From 4401a7674bf7a0384b2cbb4694366df8faaab481 Mon Sep 17 00:00:00 2001 From: James Ball Date: Sun, 28 Jan 2024 19:58:28 +0000 Subject: [PATCH] Record audio using audio directly from audio thread to guarantee no audio is missed --- Source/PluginProcessor.cpp | 9 ++++ Source/PluginProcessor.h | 5 ++ Source/components/AudioRecordingComponent.h | 52 +++++++-------------- 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 7cde0c0..a8d65b5 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -187,6 +187,11 @@ const juce::String OscirenderAudioProcessor::getName() const { return JucePlugin_Name; } +void OscirenderAudioProcessor::setAudioThreadCallback(std::function&)> 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& 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); } //============================================================================== diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index a9a262e..89bba05 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -51,6 +51,8 @@ public: const juce::String getName() const override; + void setAudioThreadCallback(std::function&)> 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&)> audioThreadCallback; + std::vector booleanParameters; std::vector floatParameters; std::vector intParameters; diff --git a/Source/components/AudioRecordingComponent.h b/Source/components/AudioRecordingComponent.h index 65d28a5..9f30bdd 100644 --- a/Source/components/AudioRecordingComponent.h +++ b/Source/components/AudioRecordingComponent.h @@ -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& buffer) { audioThreadCallback(buffer); }); } - ~AudioRecorder() override { + ~AudioRecorder() { + audioProcessor.setAudioThreadCallback(nullptr); stop(); - audioProcessor.consumerStop(consumer); - stopThread(1000); } //============================================================================== @@ -112,33 +111,20 @@ public: return activeWriter.load() != nullptr; } - void run() override { - while (!threadShouldExit()) { - consumer = audioProcessor.consumerRegister(buffer); - audioProcessor.consumerRead(consumer); - - if (nextSampleNum >= recordingLength * audioProcessor.currentSampleRate) { - stop(); - stopCallback(); - continue; - } + void audioThreadCallback(const juce::AudioBuffer& buffer) { + if (nextSampleNum >= recordingLength * audioProcessor.currentSampleRate) { + stop(); + stopCallback(); + return; + } - const juce::ScopedLock sl(writerLock); - int numSamples = buffer.size() / 2; + const juce::ScopedLock sl(writerLock); + int numSamples = buffer.getNumSamples(); - // convert 1D buffer to juce::AudioBuffer - juce::AudioBuffer 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]); - } - - - if (activeWriter.load() != nullptr) { - activeWriter.load()->write(audioBuffer.getArrayOfReadPointers(), numSamples); - thumbnail.addBlock(nextSampleNum, audioBuffer, 0, numSamples); - nextSampleNum += numSamples; - } + if (activeWriter.load() != nullptr) { + activeWriter.load()->write(buffer.getArrayOfReadPointers(), numSamples); + thumbnail.addBlock(nextSampleNum, buffer, 0, numSamples); + nextSampleNum += numSamples; } } @@ -155,8 +141,6 @@ private: juce::TimeSliceThread backgroundThread { "Audio Recorder Thread" }; // the thread that will write our audio data to disk std::unique_ptr threadedWriter; // the FIFO used to buffer the incoming data juce::int64 nextSampleNum = 0; - std::vector buffer = std::vector(2 << 12); - std::shared_ptr 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);