#include "core.hpp" #include #include #include #include #define AUDIO_BUFFER_SIZE 16384 struct AudioInterface : Module { enum ParamIds { NUM_PARAMS }; enum InputIds { AUDIO1_INPUT, AUDIO2_INPUT, NUM_INPUTS }; enum OutputIds { NUM_OUTPUTS }; float audio1Buffer[AUDIO_BUFFER_SIZE] = {}; float audio2Buffer[AUDIO_BUFFER_SIZE] = {}; // Current frame for step(), called by the rack thread long bufferFrame = 0; // Current frame for processAudio(), called by audio thread long audioFrame = 0; long audioFrameNeeded = -1; RtAudio audio; // The audio thread should wait on the rack thread until the buffer has enough samples std::mutex mutex; std::condition_variable cv; bool running; AudioInterface(); ~AudioInterface(); void step(); void openDevice(int deviceId); void closeDevice(); // Blocks until the buffer has enough samples void processAudio(float *outputBuffer, int frameCount); }; AudioInterface::AudioInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); } AudioInterface::~AudioInterface() { closeDevice(); } void AudioInterface::step() { int i = bufferFrame % AUDIO_BUFFER_SIZE; audio1Buffer[i] = getf(inputs[AUDIO1_INPUT]); audio2Buffer[i] = getf(inputs[AUDIO2_INPUT]); std::unique_lock lock(mutex); bufferFrame++; if (bufferFrame == audioFrameNeeded) { lock.unlock(); cv.notify_all(); } } int audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData) { AudioInterface *that = (AudioInterface*) userData; that->processAudio((float*) outputBuffer, nBufferFrames); return 0; } void AudioInterface::openDevice(int deviceId) { assert(!audio.isStreamOpen()); if (deviceId < 0) { deviceId = audio.getDefaultOutputDevice(); } RtAudio::StreamParameters streamParams; streamParams.deviceId = deviceId; streamParams.nChannels = 2; streamParams.firstChannel = 0; unsigned int sampleRate = SAMPLE_RATE; unsigned int bufferFrames = 256; audioFrame = -1; running = true; try { audio.openStream(&streamParams, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &audioCallback, this); audio.startStream(); } catch (RtAudioError &e) { printf("Could not open audio stream: %s\n", e.what()); } } void AudioInterface::closeDevice() { if (!audio.isStreamOpen()) { return; } { std::unique_lock lock(mutex); running = false; } cv.notify_all(); try { // Blocks until stream thread exits audio.stopStream(); audio.closeStream(); } catch (RtAudioError &e) { printf("Could not close audio stream: %s\n", e.what()); } } void AudioInterface::processAudio(float *outputBuffer, int frameCount) { std::unique_lock lock(mutex); if (audioFrame < 0) { // This audio thread is new. Reset the frame positions audioFrame = rackGetFrame(); bufferFrame = audioFrame; } audioFrameNeeded = audioFrame + frameCount; rackRequestFrame(audioFrameNeeded); // Wait for needed frames while (running && bufferFrame < audioFrameNeeded) { cv.wait(lock); } // Copy values from internal buffer to audio buffer, while holding the mutex just in case our audio buffer wraps around for (int frame = 0; frame < frameCount; frame++) { int i = audioFrame % AUDIO_BUFFER_SIZE; outputBuffer[2*frame + 0] = audio1Buffer[i] / 5.0; outputBuffer[2*frame + 1] = audio2Buffer[i] / 5.0; audioFrame++; } } struct AudioItem : MenuItem { AudioInterface *audioInterface; int deviceId; void onAction() { audioInterface->closeDevice(); audioInterface->openDevice(deviceId); } }; struct AudioChoice : ChoiceButton { AudioInterface *audioInterface; void onAction() { MenuOverlay *overlay = new MenuOverlay(); Menu *menu = new Menu(); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); int deviceCount = audioInterface->audio.getDeviceCount(); if (deviceCount == 0) { MenuLabel *label = new MenuLabel(); label->text = "No audio devices"; menu->pushChild(label); } for (int deviceId = 0; deviceId < deviceCount; deviceId++) { RtAudio::DeviceInfo info = audioInterface->audio.getDeviceInfo(deviceId); if (!info.probed) continue; char text[100]; snprintf(text, 100, "%s (%d in, %d out)", info.name.c_str(), info.inputChannels, info.outputChannels); AudioItem *audioItem = new AudioItem(); audioItem->audioInterface = audioInterface; audioItem->deviceId = deviceId; audioItem->text = text; menu->pushChild(audioItem); } overlay->addChild(menu); gScene->addChild(overlay); } }; AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()) { box.size = Vec(15*4, 380); inputs.resize(AudioInterface::NUM_INPUTS); createInputPort(this, AudioInterface::AUDIO1_INPUT, Vec(15, 120)); createInputPort(this, AudioInterface::AUDIO2_INPUT, Vec(15, 170)); AudioChoice *audioChoice = new AudioChoice(); audioChoice->audioInterface = dynamic_cast(module); audioChoice->text = "Audio Interface"; audioChoice->box.pos = Vec(0, 0); audioChoice->box.size.x = box.size.x; addChild(audioChoice); } void AudioInterfaceWidget::draw(NVGcontext *vg) { bndBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); ModuleWidget::draw(vg); }