From 0669bf5dd71e144e92fd6f07f68130b2674e308e Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sun, 25 Dec 2016 02:39:20 -0500 Subject: [PATCH] Made AudioInterface and MidiInterface a bit prettier, added audio input to AudioInterface --- Makefile | 3 + README.md | 4 +- include/widgets.hpp | 15 +++- src/core/AudioInterface.cpp | 155 ++++++++++++++++++++++++++++-------- src/core/MidiInterface.cpp | 61 ++++++++++---- src/core/core.cpp | 3 + src/core/core.hpp | 3 + src/widgets/Button.cpp | 4 - src/widgets/ScrollBar.cpp | 5 -- src/widgets/Slider.cpp | 4 - 10 files changed, 188 insertions(+), 69 deletions(-) diff --git a/Makefile b/Makefile index b127126d..590724ea 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,9 @@ CFLAGS += -DNOC_FILE_DIALOG_OSX CXXFLAGS += -DAPPLE -stdlib=libc++ -I$(HOME)/local/include LDFLAGS += -stdlib=libc++ -L$(HOME)/local/lib -lpthread -lglew -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -ldl -ljansson -lportaudio -lportmidi TARGET = Rack + +Rack.app: $(TARGET) + ./bundle.sh endif # Windows diff --git a/README.md b/README.md index 4de9035f..6a5dba6a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ *Note: This software is in semi-public alpha. If you have stumbled upon this project, feel free to try it out and report bugs to the GitHub issue tracker. However, with more users it becomes difficult to make breaking changes, so please don't spread this to your friends and the Internet just yet, until the official announcement has been made.* - ░█▀▄░█▀█░█▀▀░█░█ - ░█▀▄░█▀█░█░░░█▀▄ - ░▀░▀░▀░▀░▀▀▀░▀░▀ +# Rack Eurorack-style modular DAW diff --git a/include/widgets.hpp b/include/widgets.hpp index 177af686..548244d6 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -162,6 +162,9 @@ struct QuantityWidget : virtual Widget { struct Label : Widget { std::string text; + Label() { + box.size.y = BND_WIDGET_HEIGHT; + } void draw(NVGcontext *vg); }; @@ -209,7 +212,9 @@ struct Button : OpaqueWidget { std::string text; BNDwidgetState state = BND_DEFAULT; - Button(); + Button() { + box.size.y = BND_WIDGET_HEIGHT; + } void draw(NVGcontext *vg); void onMouseEnter(); void onMouseLeave() ; @@ -223,7 +228,9 @@ struct ChoiceButton : Button { struct Slider : OpaqueWidget, QuantityWidget { BNDwidgetState state = BND_DEFAULT; - Slider(); + Slider() { + box.size.y = BND_WIDGET_HEIGHT; + } void draw(NVGcontext *vg); void onDragStart(); void onDragMove(Vec mouseRel); @@ -236,7 +243,9 @@ struct ScrollBar : OpaqueWidget { float containerSize = 0.0; BNDwidgetState state = BND_DEFAULT; - ScrollBar(); + ScrollBar() { + box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); + } void draw(NVGcontext *vg); void move(float delta); void onDragStart(); diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 03f4efa1..a015f7e3 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -3,10 +3,16 @@ #include #include "core.hpp" - using namespace rack; -static bool audioInitialized = false; + +void audioInit() { + PaError err = Pa_Initialize(); + if (err) { + printf("Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err)); + return; + } +} struct AudioInterface : Module { @@ -16,15 +22,24 @@ struct AudioInterface : Module { enum InputIds { AUDIO1_INPUT, AUDIO2_INPUT, + AUDIO3_INPUT, + AUDIO4_INPUT, NUM_INPUTS }; enum OutputIds { + AUDIO1_OUTPUT, + AUDIO2_OUTPUT, + AUDIO3_OUTPUT, + AUDIO4_OUTPUT, NUM_OUTPUTS }; PaStream *stream = NULL; - float *buffer; - int bufferFrames; + int numOutputs; + int numInputs; + int numFrames; + float outputBuffer[1<<14] = {}; + float inputBuffer[1<<14] = {}; int bufferFrame; // Used because the GUI thread and Rack thread can both interact with this class std::mutex mutex; @@ -44,42 +59,52 @@ AudioInterface::AudioInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); - - buffer = new float[1<<14]; - - // Lazy initialize PulseAudio - if (!audioInitialized) { - PaError err = Pa_Initialize(); - if (err) { - printf("Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err)); - return; - } - audioInitialized = true; - } } AudioInterface::~AudioInterface() { openDevice(-1); - delete[] buffer; } void AudioInterface::step() { std::lock_guard lock(mutex); - if (!stream) + if (!stream) { + setf(inputs[AUDIO1_OUTPUT], 0.0); + setf(inputs[AUDIO2_OUTPUT], 0.0); + setf(inputs[AUDIO3_OUTPUT], 0.0); + setf(inputs[AUDIO4_OUTPUT], 0.0); return; + } - buffer[2*bufferFrame + 0] = getf(inputs[AUDIO1_INPUT]) / 5.0; - buffer[2*bufferFrame + 1] = getf(inputs[AUDIO2_INPUT]) / 5.0; + // Input ports -> Output buffer + for (int i = 0; i < numOutputs; i++) { + outputBuffer[numOutputs * bufferFrame + i] = getf(inputs[i]) / 5.0; + } + // Input buffer -> Output ports + for (int i = 0; i < numInputs; i++) { + setf(outputs[i], inputBuffer[numOutputs * bufferFrame + i] * 5.0); + } - if (++bufferFrame >= bufferFrames) { + if (++bufferFrame >= numFrames) { bufferFrame = 0; - PaError err = Pa_WriteStream(stream, buffer, bufferFrames); + PaError err; + + // Input + // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) + err = Pa_ReadStream(stream, inputBuffer, numFrames); + if (err) { + // Ignore buffer underflows + if (err != paInputOverflowed) { + printf("Audio input buffer underflow\n"); + } + } + + // Output + err = Pa_WriteStream(stream, outputBuffer, numFrames); if (err) { // Ignore buffer underflows if (err != paOutputUnderflowed) { - printf("Failed to write buffer to audio stream: %s\n", Pa_GetErrorText(err)); - return; + printf("Audio output buffer underflow\n"); } } } @@ -91,40 +116,59 @@ int AudioInterface::getDeviceCount() { std::string AudioInterface::getDeviceName(int deviceId) { const PaDeviceInfo *info = Pa_GetDeviceInfo(deviceId); - return info ? std::string(info->name) : ""; + if (!info) + return ""; + const PaHostApiInfo *apiInfo = Pa_GetHostApiInfo(info->hostApi); + char name[1024]; + snprintf(name, sizeof(name), "%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels); + return name; } void AudioInterface::openDevice(int deviceId) { - PaError err; std::lock_guard lock(mutex); // Close existing device if (stream) { + PaError err; err = Pa_CloseStream(stream); if (err) { + // This shouldn't happen: printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err)); } stream = NULL; } + numOutputs = 0; + numInputs = 0; - // Open new device - bufferFrames = 256; + numFrames = 256; bufferFrame = 0; + // Open new device if (deviceId >= 0) { + PaError err; const PaDeviceInfo *info = Pa_GetDeviceInfo(deviceId); if (!info) { printf("Failed to query audio device\n"); return; } + numOutputs = mini(info->maxOutputChannels, 4); + numInputs = mini(info->maxInputChannels, 4); + PaStreamParameters outputParameters; outputParameters.device = deviceId; - outputParameters.channelCount = 2; + outputParameters.channelCount = numOutputs; outputParameters.sampleFormat = paFloat32; outputParameters.suggestedLatency = info->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; - err = Pa_OpenStream(&stream, NULL, &outputParameters, SAMPLE_RATE, bufferFrames, paNoFlag, NULL, NULL); + PaStreamParameters inputParameters; + inputParameters.device = deviceId; + inputParameters.channelCount = numInputs; + inputParameters.sampleFormat = paFloat32; + inputParameters.suggestedLatency = info->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream(&stream, &inputParameters, &outputParameters, SAMPLE_RATE, numFrames, paNoFlag, NULL, NULL); if (err) { printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err)); return; @@ -177,17 +221,60 @@ struct AudioChoice : ChoiceButton { AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()) { box.size = Vec(15*8, 380); - addInput(createInput(Vec(15, 100), module, AudioInterface::AUDIO1_INPUT)); - addInput(createInput(Vec(70, 100), module, AudioInterface::AUDIO2_INPUT)); + float margin = 5; + float yPos = margin; + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Audio Interface"; + addChild(label); + yPos += label->box.size.y + margin; + } { 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; + audioChoice->box.pos = Vec(margin, yPos); + audioChoice->box.size.x = box.size.x - 10; addChild(audioChoice); + yPos += audioChoice->box.size.y + 2*margin; + } + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Outputs"; + addChild(label); + yPos += label->box.size.y + margin; } + + yPos += 5; + addInput(createInput(Vec(25, yPos), module, AudioInterface::AUDIO1_INPUT)); + addInput(createInput(Vec(75, yPos), module, AudioInterface::AUDIO2_INPUT)); + yPos += 35 + margin; + + addInput(createInput(Vec(25, yPos), module, AudioInterface::AUDIO3_INPUT)); + addInput(createInput(Vec(75, yPos), module, AudioInterface::AUDIO4_INPUT)); + yPos += 35 + margin; + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "Inputs"; + addChild(label); + yPos += label->box.size.y + margin; + } + + yPos += 5; + addOutput(createOutput(Vec(25, yPos), module, AudioInterface::AUDIO1_OUTPUT)); + addOutput(createOutput(Vec(75, yPos), module, AudioInterface::AUDIO2_OUTPUT)); + yPos += 35 + margin; + + addOutput(createOutput(Vec(25, yPos), module, AudioInterface::AUDIO3_OUTPUT)); + addOutput(createOutput(Vec(75, yPos), module, AudioInterface::AUDIO4_OUTPUT)); + yPos += 35 + margin; } void AudioInterfaceWidget::draw(NVGcontext *vg) { diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 89164317..d751efca 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -7,7 +7,14 @@ using namespace rack; -static bool midiInitialized = false; + +void midiInit() { + PmError err = Pm_Initialize(); + if (err) { + printf("Failed to initialize PortMidi: %s\n", Pm_GetErrorText(err)); + return; + } +} struct MidiInterface : Module { @@ -49,16 +56,6 @@ MidiInterface::MidiInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); - - // Lazy initialize PortMidi - if (!midiInitialized) { - PmError err = Pm_Initialize(); - if (err) { - printf("Failed to initialize PortMidi: %s\n", Pm_GetErrorText(err)); - return; - } - midiInitialized = true; - } } MidiInterface::~MidiInterface() { @@ -93,7 +90,11 @@ int MidiInterface::getPortCount() { std::string MidiInterface::getPortName(int portId) { const PmDeviceInfo *info = Pm_GetDeviceInfo(portId); - return info ? std::string(info->name) : ""; + if (!info) + return ""; + char name[1024]; + snprintf(name, sizeof(name), "%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output"); + return name; } void MidiInterface::openPort(int portId) { @@ -223,16 +224,44 @@ struct MidiChoice : ChoiceButton { MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { box.size = Vec(15*8, 380); - addOutput(createOutput(Vec(15, 100), module, MidiInterface::GATE_OUTPUT)); - addOutput(createOutput(Vec(70, 100), module, MidiInterface::PITCH_OUTPUT)); + float margin = 5; + float yPos = margin; + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "MIDI Interface"; + addChild(label); + yPos += label->box.size.y + margin; + } { MidiChoice *midiChoice = new MidiChoice(); midiChoice->midiInterface = dynamic_cast(module); midiChoice->text = "MIDI Interface"; - midiChoice->box.pos = Vec(0, 0); - midiChoice->box.size.x = box.size.x; + midiChoice->box.pos = Vec(margin, yPos); + midiChoice->box.size.x = box.size.x - 10; addChild(midiChoice); + yPos += midiChoice->box.size.y + margin; + } + + yPos += 5; + addOutput(createOutput(Vec(25, yPos), module, MidiInterface::PITCH_OUTPUT)); + addOutput(createOutput(Vec(75, yPos), module, MidiInterface::GATE_OUTPUT)); + yPos += 25 + margin; + + { + Label *pitchLabel = new Label(); + pitchLabel->box.pos = Vec(25-12, yPos); + pitchLabel->text = "Pitch"; + addChild(pitchLabel); + + Label *gateLabel = new Label(); + gateLabel->box.pos = Vec(75-12, yPos); + gateLabel->text = "Gate"; + addChild(gateLabel); + + yPos += pitchLabel->box.size.y + margin; } } diff --git a/src/core/core.cpp b/src/core/core.cpp index 06b28272..d6a5aff5 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -4,6 +4,9 @@ using namespace rack; Plugin *coreInit() { + audioInit(); + midiInit(); + Plugin *plugin = createPlugin("Core", "Core"); createModel(plugin, "AudioInterface", "Audio Interface"); createModel(plugin, "MidiInterface", "MIDI Interface"); diff --git a/src/core/core.hpp b/src/core/core.hpp index d5a46d7a..d99ad211 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -3,6 +3,9 @@ rack::Plugin *coreInit(); +void audioInit(); +void midiInit(); + //////////////////// // module widgets //////////////////// diff --git a/src/widgets/Button.cpp b/src/widgets/Button.cpp index 94de1b74..87e46565 100644 --- a/src/widgets/Button.cpp +++ b/src/widgets/Button.cpp @@ -3,10 +3,6 @@ namespace rack { -Button::Button() { - box.size.y = BND_WIDGET_HEIGHT; -} - void Button::draw(NVGcontext *vg) { bndToolButton(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str()); } diff --git a/src/widgets/ScrollBar.cpp b/src/widgets/ScrollBar.cpp index 9d96bd89..b013fad1 100644 --- a/src/widgets/ScrollBar.cpp +++ b/src/widgets/ScrollBar.cpp @@ -3,11 +3,6 @@ namespace rack { -ScrollBar::ScrollBar() { - box.size.x = BND_SCROLLBAR_WIDTH; - box.size.y = BND_SCROLLBAR_HEIGHT; -} - void ScrollBar::draw(NVGcontext *vg) { float boxSize = (orientation == VERTICAL ? box.size.y : box.size.x); float maxOffset = containerSize - boxSize; diff --git a/src/widgets/Slider.cpp b/src/widgets/Slider.cpp index c9f68a31..65db5f02 100644 --- a/src/widgets/Slider.cpp +++ b/src/widgets/Slider.cpp @@ -5,10 +5,6 @@ namespace rack { #define SLIDER_SENSITIVITY 0.001 -Slider::Slider() { - box.size.y = BND_WIDGET_HEIGHT; -} - void Slider::draw(NVGcontext *vg) { float progress = mapf(value, minValue, maxValue, 0.0, 1.0); bndSlider(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, getText().c_str(), NULL);