Skip to content

Instantly share code, notes, and snippets.

@p-i-
Created October 12, 2024 16:36
Show Gist options
  • Save p-i-/cfdfba223b4a758dd2b97b06438fa839 to your computer and use it in GitHub Desktop.
Save p-i-/cfdfba223b4a758dd2b97b06438fa839 to your computer and use it in GitHub Desktop.
Broken: kAudioUnitSubType_VoiceProcessingIO / macOS
#include "MainComponent.h"
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>
#include <cmath>
// SineWaveGenerator methods
SineWaveGenerator::SineWaveGenerator() : currentAngle(0.0), angleDelta(0.0) {}
void SineWaveGenerator::setFrequency(float frequency, double sampleRate)
{
angleDelta = 2.0 * M_PI * frequency / sampleRate;
}
float SineWaveGenerator::getNextSample()
{
auto sample = std::sin(currentAngle);
currentAngle += angleDelta;
if (currentAngle > 2.0 * M_PI)
currentAngle -= 2.0 * M_PI;
return sample;
}
// MainComponent constructor
MainComponent::MainComponent() : currentSampleRate(0.0), audioUnit(nullptr)
{
setSize(400, 300);
setAudioChannels(1, 2); // Mono input, stereo output
micSamples.reserve(maxSamples); // Reserve space for 15s of audio
if (initVoiceProcessingIO()) {
DBG("Audio Unit initialized and started successfully.");
} else {
DBG("Failed to initialize Audio Unit.");
}
}
// Destructor
MainComponent::~MainComponent()
{
shutdownAudio();
if (audioUnit) {
AudioOutputUnitStop(audioUnit);
AudioUnitUninitialize(audioUnit);
AudioComponentInstanceDispose(audioUnit);
}
}
// Prepare to play
void MainComponent::prepareToPlay(int samplesPerBlockExpected, double sampleRate)
{
currentSampleRate = sampleRate;
sineWaveGenerator.setFrequency(440.0f, sampleRate); // Set frequency to 440Hz
}
// Audio Unit render callback function
static OSStatus audioUnitRenderCallback(void* inRefCon,
AudioUnitRenderActionFlags* ioActionFlags,
const AudioTimeStamp* inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList* ioData)
{
MainComponent* self = static_cast<MainComponent*>(inRefCon);
// Debug message for callback hit
//DBG("RENDER CALLBACK - inBusNumber: " + juce::String(inBusNumber) + ", inNumberFrames: " + juce::String(inNumberFrames));
// ^ inBusNumber is always 0, inNumberFrames is 470 or 471
// Retrieve the microphone input (from bus 1)
if (inBusNumber == 1)
{
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mDataByteSize = inNumberFrames * sizeof(float);
bufferList.mBuffers[0].mData = malloc(inNumberFrames * sizeof(float)); // Allocate memory for input
// Capture the mic input from the audio unit
OSStatus status = AudioUnitRender(self->audioUnit,
ioActionFlags,
inTimeStamp,
1, // Bus 1 is for mic input
inNumberFrames,
&bufferList);
// Add debug log for AudioUnitRender status
DBG("AudioUnitRender status: " << status);
if (status == noErr)
{
// Copy mic input samples to the micSamples buffer
float* micData = static_cast<float*>(bufferList.mBuffers[0].mData);
DBG("Capturing mic data. Number of frames: " << juce::String(inNumberFrames));
self->micSamples.insert(self->micSamples.end(), micData, micData + inNumberFrames);
// Debug log for checking micSamples buffer size
DBG("micSamples size after capture: " << self->micSamples.size());
}
else
{
DBG("AudioUnitRender failed with status: " << status);
}
free(bufferList.mBuffers[0].mData); // Free the allocated memory
}
// Output to speakers (from bus 0)
if (inBusNumber == 0)
{
float* outLeft = static_cast<float*>(ioData->mBuffers[0].mData);
float* outRight = static_cast<float*>(ioData->mBuffers[1].mData);
for (UInt32 frame = 0; frame < inNumberFrames; ++frame)
{
float sineSample = self->sineWaveGenerator.getNextSample();
outLeft[frame] = sineSample;
outRight[frame] = sineSample;
}
}
return noErr;
}
void MainComponent::getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill)
{
// Capture mic input and check the size
// DBG("Current micSamples size: " << micSamples.size()); // always 0
if (micSamples.size() >= maxSamples)
{
saveToWav();
juce::JUCEApplication::quit();
}
}
void MainComponent::saveToWav()
{
// File location for saving
juce::File file(juce::File::getSpecialLocation(juce::File::userDesktopDirectory).getChildFile("recorded_audio.wav"));
// Check if file exists and delete it if necessary
if (file.existsAsFile())
{
file.deleteFile(); // Delete existing file
DBG("Existing file deleted.");
}
juce::WavAudioFormat wavFormat;
std::unique_ptr<juce::AudioFormatWriter> writer (wavFormat.createWriterFor(new juce::FileOutputStream(file),
currentSampleRate,
1, // Mono
24, // Bits per sample
{}, // No metadata
0));
if (writer != nullptr)
{
const float* channels[] = { micSamples.data() }; // Create an array of pointers
writer->writeFromFloatArrays(channels, 1, micSamples.size()); // Pass the array of channels
DBG("Audio saved to " + file.getFullPathName());
}
else
{
DBG("Failed to save WAV.");
}
}
// Release resources (JUCE method)
void MainComponent::releaseResources()
{
}
// Paint (required override)
void MainComponent::paint(juce::Graphics& g)
{
g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
g.setColour(juce::Colours::white);
g.setFont(15.0f);
g.drawFittedText("VoiceProcessingIO Test", getLocalBounds(), juce::Justification::centred, 1);
}
// Resized (required override)
void MainComponent::resized()
{
}
bool MainComponent::initVoiceProcessingIO()
{
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent component = AudioComponentFindNext(nullptr, &desc);
if (!component) {
DBG("Failed to find VoiceProcessingIO component.");
return false;
}
OSStatus status = AudioComponentInstanceNew(component, &audioUnit);
if (status != noErr) {
DBG("Failed to create Audio Unit instance. Status: " << status);
return false;
}
// Disable AEC bypass to enable voice processing
UInt32 bypassAEC = 0;
status = AudioUnitSetProperty(audioUnit,
kAUVoiceIOProperty_BypassVoiceProcessing,
kAudioUnitScope_Global,
0,
&bypassAEC,
sizeof(bypassAEC));
if (status != noErr) {
DBG("Failed to set AEC property. Status: " << status);
return false;
}
// Set up render callback for output (bus 0)
AURenderCallbackStruct outputCallback;
outputCallback.inputProc = audioUnitRenderCallback;
outputCallback.inputProcRefCon = this;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, // Input scope for output (bus 0 sends to speaker)
0, // Bus 0 for speaker output
&outputCallback,
sizeof(outputCallback));
if (status != noErr) {
DBG("Failed to set render callback for output. Status: " << status);
return false;
}
// Set up render callback for input (bus 1)
AURenderCallbackStruct inputCallback;
inputCallback.inputProc = audioUnitRenderCallback;
inputCallback.inputProcRefCon = this;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global, // Global scope for input
1, // Bus 1 for mic input
&inputCallback,
sizeof(inputCallback));
if (status != noErr) {
DBG("Failed to set render callback for input. Status: " << status);
return false;
}
// Initialize and start the audio unit
status = AudioUnitInitialize(audioUnit);
if (status != noErr) {
DBG("Failed to initialize Audio Unit. Status: " << status);
return false;
}
status = AudioOutputUnitStart(audioUnit);
if (status != noErr) {
DBG("Failed to start Audio Unit. Status: " << status);
return false;
}
return true;
}
#pragma once
#include <JuceHeader.h>
#include <vector>
class SineWaveGenerator
{
public:
SineWaveGenerator();
void setFrequency(float frequency, double sampleRate);
float getNextSample();
private:
double currentAngle = 0.0, angleDelta = 0.0;
};
class MainComponent : public juce::AudioAppComponent
{
public:
MainComponent();
~MainComponent() override;
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override;
void releaseResources() override;
void paint (juce::Graphics&) override;
void resized() override;
bool initVoiceProcessingIO();
void stopAudioUnit();
void saveToWav();
// Make these members public so the callback function can access them
public:
AudioUnit audioUnit = nullptr;
SineWaveGenerator sineWaveGenerator;
std::vector<float> micSamples; // Buffer for mic input samples
double currentSampleRate = 0.0;
int maxSamples = 5 * 48000; // Capacity for 5 seconds at 48000Hz
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
JUCE v8.0.2
AddInstanceForFactory: No factory registered for id <CFUUID 0x60000183fbc0> F8BB1C28-BAE8-11D6-9C31-00039315CD46
vpPlatformUtil.mm:312 Cannot retrieve theDeviceBoardID string...
vpPlatformUtil.mm:312 Cannot retrieve theDeviceBoardID string...
66307 HALC_ProxyIOContext.cpp:1354 HALC_ProxyIOContext::IOWorkLoop: context 13862 received an out of order message (got -1 want: 11)
AUVPAggregate.cpp:2567 AggInpStreamsChanged wait failed
AUVoiceIO can't set vp bypass state for null bundleID
KeystrokeSuppressorCore.cpp:44 ERROR: KeystrokeSuppressor initialization was unsuccessful. Invalid or no plist was provided. AU will be bypassed.
vpStrategyManager.mm:486 Error code 2003332927 reported at GetPropertyInfo
Audio Unit initialized and started successfully.
Message from debugger: killed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment