From 86e2fabf20fa90de03c104ff457ca0909084896a Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 2 Dec 2016 02:18:53 -0500 Subject: [PATCH] A few days of work --- Makefile | 21 ++-- README.txt | 8 +- src/5V.hpp => include/Rack.hpp | 101 +++++++++------- {src => include}/util.hpp | 43 +++++-- {src => include}/widgets.hpp | 52 +++++--- src/core/AudioInterface.cpp | 209 ++++++++++++++++----------------- src/core/MidiInterface.cpp | 106 +++++++++++------ src/core/core.cpp | 2 + src/core/core.hpp | 8 +- src/gui.cpp | 25 +++- src/main.cpp | 45 ++++--- src/plugin.cpp | 8 +- src/rack.cpp | 126 +++++++++++--------- src/rack.hpp | 34 ------ src/util.cpp | 77 ++++++++++++ src/widgets/Button.cpp | 7 +- src/widgets/ChoiceButton.cpp | 7 +- src/widgets/InputPort.cpp | 7 +- src/widgets/Knob.cpp | 7 +- src/widgets/Label.cpp | 7 +- src/widgets/Light.cpp | 15 ++- src/widgets/Menu.cpp | 7 +- src/widgets/MenuEntry.cpp | 7 +- src/widgets/MenuItem.cpp | 7 +- src/widgets/MenuLabel.cpp | 7 +- src/widgets/MenuOverlay.cpp | 7 +- src/widgets/ModuleWidget.cpp | 22 +++- src/widgets/OutputPort.cpp | 7 +- src/widgets/ParamWidget.cpp | 21 ++-- src/widgets/Port.cpp | 15 ++- src/widgets/QuantityWidget.cpp | 7 +- src/widgets/RackWidget.cpp | 72 +++++++++++- src/widgets/Scene.cpp | 7 +- src/widgets/Screw.cpp | 9 +- src/widgets/ScrollBar.cpp | 7 +- src/widgets/ScrollWidget.cpp | 7 +- src/widgets/Slider.cpp | 7 +- src/widgets/SpriteWidget.cpp | 7 +- src/widgets/Toolbar.cpp | 35 ++---- src/widgets/Tooltip.cpp | 7 +- src/widgets/Widget.cpp | 7 +- src/widgets/WireWidget.cpp | 11 +- 42 files changed, 767 insertions(+), 431 deletions(-) rename src/5V.hpp => include/Rack.hpp (67%) rename {src => include}/util.hpp (77%) rename {src => include}/widgets.hpp (92%) delete mode 100644 src/rack.hpp create mode 100644 src/util.cpp diff --git a/Makefile b/Makefile index d10f4945..62640721 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ ARCH ?= linux CFLAGS = -MMD -g -Wall -O2 CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math \ - -I./lib -I./src + -I./lib -I./include LDFLAGS = SOURCES = $(wildcard src/*.cpp src/*/*.cpp) \ @@ -15,7 +15,7 @@ CXX = g++ SOURCES += lib/noc/noc_file_dialog.c CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) CXXFLAGS += -DLINUX -LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -lrtaudio -lrtmidi -lprofiler -ljansson \ +LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -lprofiler -ljansson -lportaudio -lportmidi \ $(shell pkg-config --libs gtk+-2.0) TARGET = Rack endif @@ -27,7 +27,7 @@ CXX = clang++ SOURCES += lib/noc/noc_file_dialog.m 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 -lrtaudio -lrtmidi -ljansson +LDFLAGS += -stdlib=libc++ -L$(HOME)/local/lib -lpthread -lglew -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -ldl -ljansson -lportaudio -lportmidi TARGET = Rack endif @@ -37,20 +37,19 @@ CC = x86_64-w64-mingw32-gcc CXX = x86_64-w64-mingw32-g++ SOURCES += lib/noc/noc_file_dialog.c CFLAGS += -DNOC_FILE_DIALOG_WIN32 -CXXFLAGS += -DWINDOWS -LDFLAGS += -lpthread -ljansson \ - ./lib/nanovg/build/libnanovg.a -lglfw3 -lgdi32 -lopengl32 -lglew32 \ - ./lib/rtaudio/librtaudio.a -lksuser -luuid \ - ./lib/rtmidi/librtmidi.a -lwinmm \ +CXXFLAGS += -DWINDOWS -D_USE_MATH_DEFINES \ + -I$(HOME)/pkg/portaudio-r1891-build/include +LDFLAGS += -lpthread \ + -lglfw3 -lgdi32 -lopengl32 -lglew32 \ -lcomdlg32 -lole32 \ - -Wl,--export-all-symbols,--out-implib,lib5V.a -mwindows + -ljansson -lportmidi \ + -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ + -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows TARGET = Rack.exe endif all: $(TARGET) - $(MAKE) -C plugins/Simple - $(MAKE) -C plugins/AudibleInstruments include Makefile.inc \ No newline at end of file diff --git a/README.txt b/README.txt index cdc34706..e33e3b8a 100644 --- a/README.txt +++ b/README.txt @@ -6,10 +6,4 @@ Virtual modular synthesizer engine ## Building -The build system is a mess, but I'll add instructions before I announce the software. - -## License - -All source code in this repository is licensed under the LGPLv3. - -LGPL was chosen over GPL to allow plugins to call functions and inherit object classes from this source code without being forced to use the GPL license. You are thus free to develop open source or commercial plugins which link to and use the API of this software. +`make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` diff --git a/src/5V.hpp b/include/Rack.hpp similarity index 67% rename from src/5V.hpp rename to include/Rack.hpp index 3e1a16f8..5393bbd8 100644 --- a/src/5V.hpp +++ b/include/Rack.hpp @@ -2,11 +2,13 @@ #include #include +#include #include #include "widgets.hpp" -#include "rack.hpp" +namespace rack { + extern Scene *gScene; extern RackWidget *gRackWidget; @@ -20,9 +22,9 @@ struct Model; struct Plugin { virtual ~Plugin(); - // A unique identifier for your plugin, e.g. "simple" + // A unique identifier for your plugin, e.g. "foo" std::string slug; - // Human readable name for your plugin, e.g. "Simple Modular" + // Human readable name for your plugin, e.g. "Foo Modular" std::string name; // A list of the models made available by this plugin std::list models; @@ -44,17 +46,6 @@ extern std::list gPlugins; void pluginInit(); void pluginDestroy(); -//////////////////// -// midi.cpp -//////////////////// - -void midiInit(); -void midiDestroy(); -int midiPortCount(); -std::string midiPortName(int portId); -void midiPortOpen(int portId); -void midiPortClose(); - //////////////////// // gui.cpp //////////////////// @@ -77,6 +68,34 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId); // rack.cpp //////////////////// +// TODO Find a clean way to make this a variable +#define SAMPLE_RATE 44100 + + +struct Wire; + +struct Module { + std::vector params; + // Pointers to voltage values at each port + // If value is NULL, the input/output is disconnected + std::vector inputs; + std::vector outputs; + + virtual ~Module() {} + + // Always called on each sample frame before calling getOutput() + virtual void step() {} +}; + +struct Wire { + Module *outputModule = NULL; + int outputId; + Module *inputModule = NULL; + int inputId; + // The voltage which is pointed to by module inputs/outputs + float value = 0.0; +}; + void rackInit(); void rackDestroy(); void rackStart(); @@ -87,19 +106,8 @@ void rackRemoveModule(Module *module); // Does not transfer ownership void rackConnectWire(Wire *wire); void rackDisconnectWire(Wire *wire); -long rackGetFrame(); -void rackRequestFrame(long frame); void rackSetParamSmooth(Module *module, int paramId, float value); -//////////////////// -// Implemented by plugin -//////////////////// - -// Called once to initialize and return Plugin. -// Plugin is destructed by the 5V engine when it closes -extern "C" -Plugin *init(); - //////////////////// // Optional helpers for plugins //////////////////// @@ -133,47 +141,50 @@ Model *createModel(Plugin *plugin, std::string slug, std::string name) { } template -ParamWidget *createParamWidget(ModuleWidget *moduleWidget, int paramId, float minValue, float maxValue, float defaultValue, Vec pos) { +ParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { ParamWidget *param = new TParam(); - param->moduleWidget = moduleWidget; + param->box.pos = pos; + param->module = module; param->paramId = paramId; param->setLimits(minValue, maxValue); param->setDefaultValue(defaultValue); - param->box.pos = pos; - // Create bi-directional association between the Param and ModelWidget - moduleWidget->params[paramId] = param; - moduleWidget->addChild(param); return param; } inline -InputPort *createInputPort(ModuleWidget *moduleWidget, int inputId, Vec pos) { +InputPort *createInput(Vec pos, Module *module, int inputId) { InputPort *port = new InputPort(); - port->moduleWidget = moduleWidget; - port->inputId = inputId; port->box.pos = pos; - // Create bi-directional association between the InputPort and ModelWidget - moduleWidget->inputs[inputId] = port; - moduleWidget->addChild(port); + port->module = module; + port->inputId = inputId; return port; } inline -OutputPort *createOutputPort(ModuleWidget *moduleWidget, int outputId, Vec pos) { +OutputPort *createOutput(Vec pos, Module *module, int outputId) { OutputPort *port = new OutputPort(); - port->moduleWidget = moduleWidget; - port->outputId = outputId; port->box.pos = pos; - // Create bi-directional association between the OutputPort and ModelWidget - moduleWidget->outputs[outputId] = port; - moduleWidget->addChild(port); + port->module = module; + port->outputId = outputId; return port; } inline -Screw *createScrew(ModuleWidget *moduleWidget, Vec pos) { +Screw *createScrew(Vec pos) { Screw *screw = new Screw(); screw->box.pos = pos; - moduleWidget->addChild(screw); return screw; } + + +} // namespace rack + + +//////////////////// +// Implemented by plugin +//////////////////// + +// Called once to initialize and return Plugin. +// Plugin is destructed when Rack closes +extern "C" +rack::Plugin *init(); diff --git a/src/util.hpp b/include/util.hpp similarity index 77% rename from src/util.hpp rename to include/util.hpp index 521fda08..9533c606 100644 --- a/src/util.hpp +++ b/include/util.hpp @@ -1,17 +1,15 @@ #pragma once +#include #include + +namespace rack { + //////////////////// -// Utilities -// A header-only file with handy inline functions +// Math //////////////////// -#ifndef M_PI - #define M_PI 3.141592653589793238462643383 - #define M_E 2.718281828459045235360287471 -#endif - inline float clampf(float x, float min, float max) { return fmaxf(min, fminf(max, x)); } @@ -21,7 +19,7 @@ inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { } inline float crossf(float a, float b, float frac) { - return (1 - frac) * a + frac * b; + return (1.0 - frac) * a + frac * b; } inline int mini(int a, int b) { @@ -39,14 +37,36 @@ inline int eucMod(int a, int base) { return mod < 0 ? mod + base : mod; } -inline float getf(float *p, float v=0.0) { +inline float getf(const float *p, float v = 0.0) { return p ? *p : v; } inline void setf(float *p, float v) { - if (p) *p = v; + if (p) + *p = v; +} + +// Linearly interpolate an array `p` with index `x` +inline float interpf(float *p, float x) { + int i = x; + x -= i; + return crossf(p[i], p[i+1], x); } +//////////////////// +// RNG +//////////////////// + +uint64_t randomi64(void); + +// Return a uniform random number on [0.0, 1.0) +inline float randomf(void) { + return (float)randomi64() / UINT64_MAX; +} + +//////////////////// +// 2D float vector +//////////////////// struct Vec { float x, y; @@ -115,3 +135,6 @@ struct Rect { return pos.plus(size); } }; + + +} // namespace rack diff --git a/src/widgets.hpp b/include/widgets.hpp similarity index 92% rename from src/widgets.hpp rename to include/widgets.hpp index 73b64a5d..c3af657f 100644 --- a/src/widgets.hpp +++ b/include/widgets.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -13,11 +14,14 @@ #include "../lib/nanovg/src/nanovg.h" #include "../lib/oui/blendish.h" -#include "rack.hpp" #include "util.hpp" -struct MenuEntry; +namespace rack { + +struct Module; +struct Wire; + struct RackWidget; struct ParamWidget; struct InputPort; @@ -73,6 +77,11 @@ struct Widget { virtual void onChange() {} }; +// Widget that does not respond to events +struct TransparentWidget : virtual Widget { + Widget *pick(Vec pos) { return NULL; } +}; + // Widget that does not respond to events, but allows its children to struct TranslucentWidget : virtual Widget { Widget *pick(Vec pos) { @@ -84,11 +93,6 @@ struct TranslucentWidget : virtual Widget { } }; -// Widget that does not respond to events -struct TransparentWidget : virtual Widget { - Widget *pick(Vec pos) { return NULL; } -}; - struct SpriteWidget : virtual Widget { Vec spriteOffset; Vec spriteSize; @@ -230,6 +234,11 @@ struct ModuleWidget : Widget { ModuleWidget(Module *module); ~ModuleWidget(); + // Convenience functions for adding special widgets (calls addChild()) + void addInput(InputPort *input); + void addOutput(OutputPort *output); + void addParam(ParamWidget *param); + json_t *toJson(); void fromJson(json_t *root); void disconnectPorts(); @@ -270,10 +279,14 @@ struct RackWidget : Widget { WireWidget *activeWire = NULL; RackWidget(); - + ~RackWidget(); void clear(); + void savePatch(std::string filename); + void loadPatch(std::string filename); json_t *toJson(); void fromJson(json_t *root); + + int frame = 0; void repositionModule(ModuleWidget *module); void step(); void draw(NVGcontext *vg); @@ -296,8 +309,7 @@ struct Screw : TransparentWidget, SpriteWidget { }; struct ParamWidget : QuantityWidget { - // Ancestor ModuleWidget, used for accessing the Module - ModuleWidget *moduleWidget; + Module *module = NULL; int paramId; json_t *toJson(); @@ -325,12 +337,13 @@ struct ToggleSwitch : virtual Switch { index = 0; } void onDragDrop(Widget *origin) { - if (origin == this) { - // Cycle through modes - // e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. - float v = value + 1.0; - setValue(v > maxValue ? minValue : v); - } + if (origin != this) + return; + + // Cycle through modes + // e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. + float v = value + 1.0; + setValue(v > maxValue ? minValue : v); } }; @@ -350,8 +363,7 @@ struct MomentarySwitch : virtual Switch { //////////////////// struct Port : Widget { - // Ancestor ModuleWidget, used for accessing the Module - ModuleWidget *moduleWidget; + Module *module = NULL; WireWidget *connectedWire = NULL; Port(); @@ -361,6 +373,7 @@ struct Port : Widget { int type; void draw(NVGcontext *vg); void drawGlow(NVGcontext *vg); + void onMouseDown(int button); void onDragEnd(); }; @@ -396,3 +409,6 @@ struct Scene : Widget { Scene(); void onResize(); }; + + +} // namespace rack diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index 8bc91163..ce78bca9 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -1,11 +1,13 @@ -#include "core.hpp" #include #include -#include -#include +#include +#include "core.hpp" + +using namespace rack; + +static bool audioInitialized = false; -#define AUDIO_BUFFER_SIZE 16384 struct AudioInterface : Module { enum ParamIds { @@ -20,27 +22,21 @@ struct AudioInterface : Module { 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 + PaStream *stream = NULL; + float *buffer; + int bufferFrames; + int bufferFrame; + // Used because the GUI thread and Rack thread can both interact with this class std::mutex mutex; - std::condition_variable cv; - bool running; AudioInterface(); ~AudioInterface(); void step(); + int getDeviceCount(); + std::string getDeviceName(int deviceId); + // Use -1 as the deviceId to close the current device void openDevice(int deviceId); - void closeDevice(); - // Blocks until the buffer has enough samples - void processAudio(float *outputBuffer, int frameCount); }; @@ -48,94 +44,97 @@ AudioInterface::AudioInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); -} -AudioInterface::~AudioInterface() { - closeDevice(); -} + buffer = new float[1<<14]; -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(); + // Lazy initialize PulseAudio + if (!audioInitialized) { + PaError err = Pa_Initialize(); + if (err) { + printf("Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err)); + return; + } + audioInitialized = true; } } -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; +AudioInterface::~AudioInterface() { + openDevice(-1); + delete[] buffer; } -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 = 512; +void AudioInterface::step() { + std::lock_guard lock(mutex); - audioFrame = -1; - running = true; + if (!stream) + return; - 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()); + buffer[2*bufferFrame + 0] = getf(inputs[AUDIO1_INPUT]) / 5.0; + buffer[2*bufferFrame + 1] = getf(inputs[AUDIO2_INPUT]) / 5.0; + + if (++bufferFrame >= bufferFrames) { + bufferFrame = 0; + PaError err = Pa_WriteStream(stream, buffer, bufferFrames); + if (err) { + // Ignore buffer underflows + if (err != paOutputUnderflowed) { + printf("Failed to write buffer to audio stream: %s\n", Pa_GetErrorText(err)); + return; + } + } } } -void AudioInterface::closeDevice() { - if (!audio.isStreamOpen()) { - return; - } - { - std::unique_lock lock(mutex); - running = false; - } - cv.notify_all(); +int AudioInterface::getDeviceCount() { + return Pa_GetDeviceCount(); +} - try { - // Blocks until stream thread exits - audio.stopStream(); - audio.closeStream(); - } - catch (RtAudioError &e) { - printf("Could not close audio stream: %s\n", e.what()); - } +std::string AudioInterface::getDeviceName(int deviceId) { + const PaDeviceInfo *info = Pa_GetDeviceInfo(deviceId); + return info ? std::string(info->name) : ""; } -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); +void AudioInterface::openDevice(int deviceId) { + PaError err; + std::lock_guard lock(mutex); + + // Close existing device + if (stream) { + err = Pa_CloseStream(stream); + if (err) { + printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err)); + } + stream = NULL; } - // 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++; + + // Open new device + bufferFrames = 256; + bufferFrame = 0; + if (deviceId >= 0) { + const PaDeviceInfo *info = Pa_GetDeviceInfo(deviceId); + if (!info) { + printf("Failed to query audio device\n"); + return; + } + + PaStreamParameters outputParameters; + outputParameters.device = deviceId; + outputParameters.channelCount = 2; + outputParameters.sampleFormat = paFloat32; + outputParameters.suggestedLatency = info->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + err = Pa_OpenStream(&stream, NULL, &outputParameters, SAMPLE_RATE, bufferFrames, paNoFlag, NULL, NULL); + if (err) { + printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err)); + return; + } + + err = Pa_StartStream(stream); + if (err) { + printf("Failed to start audio stream: %s\n", Pa_GetErrorText(err)); + return; + } } } @@ -144,7 +143,6 @@ struct AudioItem : MenuItem { AudioInterface *audioInterface; int deviceId; void onAction() { - audioInterface->closeDevice(); audioInterface->openDevice(deviceId); } }; @@ -156,23 +154,17 @@ struct AudioChoice : ChoiceButton { Menu *menu = new Menu(); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); - int deviceCount = audioInterface->audio.getDeviceCount(); + int deviceCount = audioInterface->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; + std::string text = audioInterface->getDeviceName(deviceId); audioItem->text = text; menu->pushChild(audioItem); } @@ -184,17 +176,18 @@ struct AudioChoice : ChoiceButton { AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()) { box.size = Vec(15*8, 380); - inputs.resize(AudioInterface::NUM_INPUTS); - createInputPort(this, AudioInterface::AUDIO1_INPUT, Vec(15, 100)); - createInputPort(this, AudioInterface::AUDIO2_INPUT, Vec(70, 100)); + addInput(createInput(Vec(15, 100), module, AudioInterface::AUDIO1_INPUT)); + addInput(createInput(Vec(70, 100), module, AudioInterface::AUDIO2_INPUT)); - 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); + { + 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) { diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 2b6afa7a..91433eb7 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -1,8 +1,13 @@ -#include "core.hpp" #include -#include #include #include +#include +#include "core.hpp" + + +using namespace rack; + +static bool midiInitialized = false; struct MidiInterface : Module { @@ -18,7 +23,7 @@ struct MidiInterface : Module { NUM_OUTPUTS }; - RtMidiIn midi; + PortMidiStream *stream = NULL; std::list notes; bool pedal = false; bool gate = false; @@ -29,35 +34,45 @@ struct MidiInterface : Module { ~MidiInterface(); void step(); + int getPortCount(); + std::string getPortName(int portId); + // -1 will close the port void openPort(int portId); - void closePort(); void pressNote(int note); void releaseNote(int note); void processMidi(long msg); }; -void midiCallback(double timeStamp, std::vector *message, void *userData) { - MidiInterface *that = (MidiInterface*) userData; - if (message->size() < 3) - return; - - long msg = (message->at(0)) | (message->at(1) << 8) | (message->at(2) << 16); - that->processMidi(msg); -} - MidiInterface::MidiInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); - midi.setCallback(midiCallback, this); + + // 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() { - closePort(); + openPort(-1); } void MidiInterface::step() { + if (stream) { + // Read MIDI events + PmEvent event; + while (Pm_Read(stream, &event, 1) > 0) { + processMidi(event.message); + } + } + if (outputs[GATE_OUTPUT]) { *outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; } @@ -66,20 +81,35 @@ void MidiInterface::step() { } } +int MidiInterface::getPortCount() { + return Pm_CountDevices(); +} + +std::string MidiInterface::getPortName(int portId) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(portId); + return info ? std::string(info->name) : ""; +} + void MidiInterface::openPort(int portId) { - closePort(); - try { - midi.openPort(portId); - } - catch (RtMidiError &e) { - printf("Could not open midi port: %s\n", e.what()); + PmError err; + + // Close existing port + if (stream) { + err = Pm_Close(stream); + if (err) { + printf("Failed to close MIDI port: %s\n", Pm_GetErrorText(err)); + } + stream = NULL; } -} -void MidiInterface::closePort() { - if (!midi.isPortOpen()) - return; - midi.closePort(); + // Open new port + if (portId >= 0) { + err = Pm_OpenInput(&stream, portId, NULL, 128, NULL, NULL); + if (err) { + printf("Failed to open MIDI port: %s\n", Pm_GetErrorText(err)); + return; + } + } } void MidiInterface::pressNote(int note) { @@ -155,7 +185,6 @@ struct MidiItem : MenuItem { MidiInterface *midiInterface; int portId; void onAction() { - midiInterface->closePort(); midiInterface->openPort(portId); } }; @@ -167,7 +196,7 @@ struct MidiChoice : ChoiceButton { Menu *menu = new Menu(); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); - int portCount = midiInterface->midi.getPortCount(); + int portCount = midiInterface->getPortCount(); if (portCount == 0) { MenuLabel *label = new MenuLabel(); label->text = "No MIDI devices"; @@ -177,7 +206,7 @@ struct MidiChoice : ChoiceButton { MidiItem *midiItem = new MidiItem(); midiItem->midiInterface = midiInterface; midiItem->portId = portId; - midiItem->text = midiInterface->midi.getPortName(); + midiItem->text = midiInterface->getPortName(portId); menu->pushChild(midiItem); } overlay->addChild(menu); @@ -188,17 +217,18 @@ struct MidiChoice : ChoiceButton { MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { box.size = Vec(15*8, 380); - outputs.resize(MidiInterface::NUM_OUTPUTS); - createOutputPort(this, MidiInterface::GATE_OUTPUT, Vec(15, 100)); - createOutputPort(this, MidiInterface::PITCH_OUTPUT, Vec(70, 100)); + addOutput(createOutput(Vec(15, 100), module, MidiInterface::GATE_OUTPUT)); + addOutput(createOutput(Vec(70, 100), module, MidiInterface::PITCH_OUTPUT)); - 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; - addChild(midiChoice); + { + 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; + addChild(midiChoice); + } } void MidiInterfaceWidget::draw(NVGcontext *vg) { diff --git a/src/core/core.cpp b/src/core/core.cpp index c2ad345e..06b28272 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -1,6 +1,8 @@ #include "core.hpp" +using namespace rack; + Plugin *coreInit() { Plugin *plugin = createPlugin("Core", "Core"); createModel(plugin, "AudioInterface", "Audio Interface"); diff --git a/src/core/core.hpp b/src/core/core.hpp index f3b7dbde..d5a46d7a 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -1,18 +1,18 @@ -#include "../5V.hpp" +#include "Rack.hpp" -Plugin *coreInit(); +rack::Plugin *coreInit(); //////////////////// // module widgets //////////////////// -struct AudioInterfaceWidget : ModuleWidget { +struct AudioInterfaceWidget : rack::ModuleWidget { AudioInterfaceWidget(); void draw(NVGcontext *vg); }; -struct MidiInterfaceWidget : ModuleWidget { +struct MidiInterfaceWidget : rack::ModuleWidget { MidiInterfaceWidget(); void draw(NVGcontext *vg); }; diff --git a/src/gui.cpp b/src/gui.cpp index 77b01531..c1c0e472 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,5 +1,5 @@ -#include "5V.hpp" #include +#include "Rack.hpp" // #define NANOVG_GLEW #define NANOVG_IMPLEMENTATION @@ -10,13 +10,18 @@ #include "../lib/oui/blendish.h" -static GLFWwindow *window; -static NVGcontext *vg = NULL; +namespace rack { + +Scene *gScene = NULL; +RackWidget *gRackWidget = NULL; Vec gMousePos; Widget *gHoveredWidget = NULL; Widget *gDraggedWidget = NULL; +static GLFWwindow *window; +static NVGcontext *vg = NULL; + void windowSizeCallback(GLFWwindow* window, int width, int height) { gScene->box.size = Vec(width, height); @@ -146,7 +151,9 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods void renderGui() { int width, height; - glfwGetFramebufferSize(window, &width, &height); + // The docs say to use the framebuffer size to get pixel-perfect matching for high-DPI displays, but I actually don't want this. A "screen" pixel + // glfwGetFramebufferSize(window, &width, &height); + glfwGetWindowSize(window, &width, &height); // Update and render glViewport(0, 0, width, height); @@ -188,6 +195,7 @@ void guiInit() { // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here. glGetError(); + glfwSetWindowSizeLimits(window, 240, 160, GLFW_DONT_CARE, GLFW_DONT_CARE); // Set up NanoVG vg = nvgCreateGL2(NVG_ANTIALIAS); @@ -195,10 +203,14 @@ void guiInit() { // Set up Blendish bndSetFont(loadFont("res/DejaVuSans.ttf")); - bndSetIconImage(loadImage("res/blender_icons16.png")); + // bndSetIconImage(loadImage("res/blender_icons16.png")); + + gScene = new Scene(); } void guiDestroy() { + delete gScene; + nvgDeleteGL2(vg); glfwDestroyWindow(window); glfwTerminate(); @@ -292,3 +304,6 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId) { nvgRect(vg, pos.x, pos.y, width, height); nvgFill(vg); } + + +} // namespace rack diff --git a/src/main.cpp b/src/main.cpp index c026fdc8..44261d32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,33 +1,42 @@ -#include "5V.hpp" -#include -#include +#if defined(APPLE) + #include "CoreFoundation/CoreFoundation.h" + #include + #include +#endif +#include "Rack.hpp" -Scene *gScene = NULL; -RackWidget *gRackWidget = NULL; +using namespace rack; int main() { + // Set working directory +#if defined(APPLE) + { + CFBundleRef bundle = CFBundleGetMainBundle(); + CFURLRef bundleURL = CFBundleCopyBundleURL(bundle); + char path[PATH_MAX]; + Boolean success = CFURLGetFileSystemRepresentation(bundleURL, TRUE, (UInt8 *)path, PATH_MAX); + assert(success); + CFRelease(bundleURL); + + // chdir(dirname(path)); + } +#endif + + pluginInit(); rackInit(); guiInit(); - gScene = new Scene(); - // audioInit(); - // audioDeviceOpen(); - // midiInit(); - rackStart(); + gRackWidget->loadPatch("autosave.json"); - // Blocks until user exits + rackStart(); guiRun(); + rackStop(); - // Cleanup - // midiDestroy(); - // audioDeviceClose(); - // audioDestroy(); - delete gScene; + gRackWidget->savePatch("autosave.json"); guiDestroy(); - rackStop(); rackDestroy(); pluginDestroy(); - return 0; + return 0; } diff --git a/src/plugin.cpp b/src/plugin.cpp index 58189ddc..30442f77 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -7,10 +7,12 @@ #include #endif -#include "5V.hpp" +#include "Rack.hpp" #include "core/core.hpp" +namespace rack { + std::list gPlugins; int loadPlugin(const char *path) { @@ -62,6 +64,7 @@ void pluginInit() { // Search for plugin libraries #if defined(WINDOWS) + // TODO // WIN32_FIND_DATA ffd; // HANDLE hFind = FindFirstFile("plugins/*/plugin.dll", &ffd); // if (hFind != INVALID_HANDLE_VALUE) { @@ -101,3 +104,6 @@ Plugin::~Plugin() { delete model; } } + + +} // namespace rack diff --git a/src/rack.cpp b/src/rack.cpp index 695388af..6c6c88dd 100644 --- a/src/rack.cpp +++ b/src/rack.cpp @@ -2,35 +2,56 @@ #include #include #include -#include +#include +#include #include #include #include -#include "rack.hpp" +#include "Rack.hpp" + + +namespace rack { static std::thread thread; static std::mutex mutex; -static std::condition_variable cv; -static long frame; -static long frameLimit; static bool running; static std::set modules; // Merely used for keeping track of which module inputs point to which module outputs, to prevent pointer mistakes and make the rack API rigorous static std::set wires; - +// Parameter interpolation static Module *smoothModule = NULL; static int smoothParamId; static float smoothValue; +// HACK +static std::mutex requestMutex; +static std::condition_variable requestCv; +static bool request = false; + +struct RackRequest { + RackRequest() { + std::unique_lock lock(requestMutex); + request = true; + } + ~RackRequest() { + std::unique_lock lock(requestMutex); + request = false; + lock.unlock(); + requestCv.notify_all(); + } +}; +// END HACK + + void rackInit() { } void rackDestroy() { - // Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself when the GUI was destroyed. + // Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the GUI was destroyed. assert(wires.empty()); assert(modules.empty()); } @@ -39,24 +60,17 @@ void rackStep() { // Param interpolation if (smoothModule) { float value = smoothModule->params[smoothParamId]; - const float minSpeed = 0.001 * 60.0 / SAMPLE_RATE; - const float lpCoeff = 60.0 / SAMPLE_RATE / 1.0; // decay rate is 1 graphics frame + const float lambda = 60.0; // decay rate is 1 graphics frame + const float snap = 0.0001; float delta = smoothValue - value; - float speed = fmaxf(fabsf(delta) * lpCoeff, minSpeed); - - if (delta < 0) { - value -= speed; - if (value < smoothValue) value = smoothValue; - } - else if (delta > 0) { - value += speed; - if (value > smoothValue) value = smoothValue; - } - - smoothModule->params[smoothParamId] = value; - if (value == smoothValue) { + if (fabsf(delta) < snap) { + smoothModule->params[smoothParamId] = smoothValue; smoothModule = NULL; } + else { + value += delta * lambda / SAMPLE_RATE; + smoothModule->params[smoothParamId] = value; + } } // Step all modules for (Module *module : modules) { @@ -64,41 +78,44 @@ void rackStep() { } } -void rackRun() { - while (1) { - std::unique_lock lock(mutex); - if (!running) - break; - if (frame >= frameLimit) { - // Delay for at most 1ms if there are no needed frames - cv.wait_for(lock, std::chrono::milliseconds(1)); +static void rackRun() { + // Every time the rack waits and locks a mutex, it steps this many frames + const int stepSize = 32; + + while (running) { + auto start = std::chrono::high_resolution_clock::now(); + // This lock is to make sure the GUI gets higher priority than this thread + { + std::unique_lock lock(requestMutex); + while (request) + requestCv.wait(lock); + } + { + std::lock_guard lock(mutex); + for (int i = 0; i < stepSize; i++) { + rackStep(); + } } - frame++; - // Speed up - for (int i = 0; i < 16; i++) - rackStep(); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / SAMPLE_RATE)) - (end - start); + std::this_thread::sleep_for(duration); } } void rackStart() { - frame = 0; - frameLimit = 0; running = true; thread = std::thread(rackRun); } void rackStop() { - { - std::unique_lock lock(mutex); - running = false; - } - cv.notify_all(); + running = false; thread.join(); } void rackAddModule(Module *module) { assert(module); - std::unique_lock lock(mutex); + RackRequest rm; + std::lock_guard lock(mutex); // Check that the module is not already added assert(modules.find(module) == modules.end()); modules.insert(module); @@ -106,7 +123,8 @@ void rackAddModule(Module *module) { void rackRemoveModule(Module *module) { assert(module); - std::unique_lock lock(mutex); + RackRequest rm; + std::lock_guard lock(mutex); // Remove parameter interpolation which point to this module if (module == smoothModule) { smoothModule = NULL; @@ -124,7 +142,8 @@ void rackRemoveModule(Module *module) { void rackConnectWire(Wire *wire) { assert(wire); - std::unique_lock lock(mutex); + RackRequest rm; + std::lock_guard lock(mutex); // It would probably be good to reset the wire voltage wire->value = 0.0; // Check that the wire is not already added @@ -145,7 +164,8 @@ void rackConnectWire(Wire *wire) { void rackDisconnectWire(Wire *wire) { assert(wire); - std::unique_lock lock(mutex); + RackRequest rm; + std::lock_guard lock(mutex); // Disconnect wire from inputModule wire->inputModule->inputs[wire->inputId] = NULL; wire->outputModule->outputs[wire->outputId] = NULL; @@ -155,19 +175,6 @@ void rackDisconnectWire(Wire *wire) { wires.erase(it); } -long rackGetFrame() { - return frame; -} - -void rackRequestFrame(long f) { - std::unique_lock lock(mutex); - if (f > frameLimit) { - frameLimit = f; - lock.unlock(); - cv.notify_all(); - } -} - void rackSetParamSmooth(Module *module, int paramId, float value) { // Check existing parameter interpolation if (smoothModule) { @@ -180,3 +187,6 @@ void rackSetParamSmooth(Module *module, int paramId, float value) { smoothParamId = paramId; smoothValue = value; } + + +} // namespace rack diff --git a/src/rack.hpp b/src/rack.hpp deleted file mode 100644 index 94358706..00000000 --- a/src/rack.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include - -// TODO Find a clean way to make this a variable -#define SAMPLE_RATE 44100 - - -struct Wire; - -struct Module { - std::vector params; - // Pointers to voltage values at each port - // If value is NULL, the input/output is disconnected - std::vector inputs; - std::vector outputs; - - virtual ~Module() {} - - // Always called on each sample frame before calling getOutput() - virtual void step() {} -}; - - -struct Wire { - Module *outputModule = NULL; - int outputId; - Module *inputModule = NULL; - int inputId; - // The voltage which is pointed to by module inputs/outputs - float value = 0.0; -}; diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 00000000..ea06996b --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,77 @@ +#include "util.hpp" +#include + + +namespace rack { + + +/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org) + +To the extent possible under law, the author has dedicated all copyright +and related and neighboring rights to this software to the public domain +worldwide. This software is distributed without any warranty. + +See . */ + +/* This is the successor to xorshift128+. It is the fastest full-period + generator passing BigCrush without systematic failures, but due to the + relatively short period it is acceptable only for applications with a + mild amount of parallelism; otherwise, use a xorshift1024* generator. + + Beside passing BigCrush, this generator passes the PractRand test suite + up to (and included) 16TB, with the exception of binary rank tests, + which fail due to the lowest bit being an LFSR; all other bits pass all + tests. We suggest to use a sign test to extract a random Boolean value. + + Note that the generator uses a simulated rotate operation, which most C + compilers will turn into a single instruction. In Java, you can use + Long.rotateLeft(). In languages that do not make low-level rotation + instructions accessible xorshift128+ could be faster. + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. */ + +static uint64_t s[2] = {15879747621982183911ULL, 4486682751732329651ULL}; + +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t randomi64(void) { + const uint64_t s0 = s[0]; + uint64_t s1 = s[1]; + const uint64_t result = s0 + s1; + + s1 ^= s0; + s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b + s[1] = rotl(s1, 36); // c + + return result; +} + + +/* This is the jump function for the generator. It is equivalent + to 2^64 calls to next(); it can be used to generate 2^64 + non-overlapping subsequences for parallel computations. */ + +void randomjump(void) { + static const uint64_t JUMP[] = { 0xbeac0467eba5facb, 0xd86b048b86aa9922 }; + + uint64_t s0 = 0; + uint64_t s1 = 0; + for(unsigned int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) + for(int b = 0; b < 64; b++) { + if (JUMP[i] & 1ULL << b) { + s0 ^= s[0]; + s1 ^= s[1]; + } + randomi64(); + } + + s[0] = s0; + s[1] = s1; +} + + +} // namespace rack diff --git a/src/widgets/Button.cpp b/src/widgets/Button.cpp index 9d4cd908..94de1b74 100644 --- a/src/widgets/Button.cpp +++ b/src/widgets/Button.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + Button::Button() { box.size.y = BND_WIDGET_HEIGHT; } @@ -22,3 +24,6 @@ void Button::onDragDrop(Widget *origin) { onAction(); } } + + +} // namespace rack diff --git a/src/widgets/ChoiceButton.cpp b/src/widgets/ChoiceButton.cpp index 9dab2a6f..c126af8f 100644 --- a/src/widgets/ChoiceButton.cpp +++ b/src/widgets/ChoiceButton.cpp @@ -1,6 +1,11 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void ChoiceButton::draw(NVGcontext *vg) { bndChoiceButton(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str()); } + + +} // namespace rack diff --git a/src/widgets/InputPort.cpp b/src/widgets/InputPort.cpp index d147e305..ab3f0bb3 100644 --- a/src/widgets/InputPort.cpp +++ b/src/widgets/InputPort.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void InputPort::draw(NVGcontext *vg) { Port::draw(vg); if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { @@ -32,3 +34,6 @@ void InputPort::onDragDrop(Widget *origin) { connectedWire = gRackWidget->activeWire; } } + + +} // namespace rack diff --git a/src/widgets/Knob.cpp b/src/widgets/Knob.cpp index f0a36cef..55247807 100644 --- a/src/widgets/Knob.cpp +++ b/src/widgets/Knob.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + #define KNOB_SENSITIVITY 0.001 @@ -19,3 +21,6 @@ void Knob::onDragMove(Vec mouseRel) { void Knob::onDragEnd() { guiCursorUnlock(); } + + +} // namespace rack diff --git a/src/widgets/Label.cpp b/src/widgets/Label.cpp index c13af97d..740708f2 100644 --- a/src/widgets/Label.cpp +++ b/src/widgets/Label.cpp @@ -1,6 +1,11 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void Label::draw(NVGcontext *vg) { bndLabel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, -1, text.c_str()); } + + +} // namespace rack diff --git a/src/widgets/Light.cpp b/src/widgets/Light.cpp index 844d3065..1e9edefe 100644 --- a/src/widgets/Light.cpp +++ b/src/widgets/Light.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void Light::draw(NVGcontext *vg) { SpriteWidget::draw(vg); @@ -8,7 +10,7 @@ void Light::draw(NVGcontext *vg) { return; // Draw glow Vec c = box.getCenter(); - float radius = box.size.x / 2; + float radius = box.size.x / 2.0; NVGcolor icol, ocol; NVGpaint paint; // Inner glow @@ -16,20 +18,23 @@ void Light::draw(NVGcontext *vg) { icol.a = clampf(color.a, 0.0, 1.0); ocol = color; ocol.a = clampf(color.a, 0.0, 1.0); - paint = nvgRadialGradient(vg, c.x+1, c.y+3, 0, radius, icol, ocol); + paint = nvgRadialGradient(vg, c.x+1, c.y+3, 0.0, radius, icol, ocol); nvgFillPaint(vg, paint); nvgBeginPath(vg); nvgCircle(vg, c.x, c.y, radius); nvgFill(vg); // Outer glow icol = color; - icol.a = clampf(0.1 * color.a, 0.0, 1.0); + icol.a = clampf(color.a / 10.0, 0.0, 1.0); ocol = color; ocol.a = 0.0; - float oradius = radius + 20; + float oradius = radius + 20.0; paint = nvgRadialGradient(vg, c.x, c.y, radius, oradius, icol, ocol); nvgFillPaint(vg, paint); nvgBeginPath(vg); nvgRect(vg, c.x - oradius, c.y - oradius, 2*oradius, 2*oradius); nvgFill(vg); } + + +} // namespace rack diff --git a/src/widgets/Menu.cpp b/src/widgets/Menu.cpp index 12bb0dbb..bd950358 100644 --- a/src/widgets/Menu.cpp +++ b/src/widgets/Menu.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void Menu::pushChild(Widget *child) { child->box.pos = Vec(0, box.size.y); addChild(child); @@ -25,3 +27,6 @@ void Menu::draw(NVGcontext *vg) { Widget::draw(vg); } + + +} // namespace rack diff --git a/src/widgets/MenuEntry.cpp b/src/widgets/MenuEntry.cpp index 280fe631..15cfd560 100644 --- a/src/widgets/MenuEntry.cpp +++ b/src/widgets/MenuEntry.cpp @@ -1,6 +1,11 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + float MenuEntry::computeMinWidth(NVGcontext *vg) { return bndLabelWidth(vg, -1, text.c_str()); } + + +} // namespace rack diff --git a/src/widgets/MenuItem.cpp b/src/widgets/MenuItem.cpp index 33eedd1d..c2fd8107 100644 --- a/src/widgets/MenuItem.cpp +++ b/src/widgets/MenuItem.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void MenuItem::draw(NVGcontext *vg) { bndMenuItem(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, state, -1, text.c_str()); } @@ -24,3 +26,6 @@ void MenuItem::onMouseUp(int button) { } delete overlay; } + + +} // namespace rack diff --git a/src/widgets/MenuLabel.cpp b/src/widgets/MenuLabel.cpp index 48761522..e62691c5 100644 --- a/src/widgets/MenuLabel.cpp +++ b/src/widgets/MenuLabel.cpp @@ -1,6 +1,11 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void MenuLabel::draw(NVGcontext *vg) { bndMenuLabel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, -1, text.c_str()); } + + +} // namespace rack diff --git a/src/widgets/MenuOverlay.cpp b/src/widgets/MenuOverlay.cpp index 178959b9..8bba288f 100644 --- a/src/widgets/MenuOverlay.cpp +++ b/src/widgets/MenuOverlay.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void MenuOverlay::onMouseDown(int button) { if (parent) { parent->removeChild(this); @@ -8,3 +10,6 @@ void MenuOverlay::onMouseDown(int button) { // Commit sudoku delete this; } + + +} // namespace rack diff --git a/src/widgets/ModuleWidget.cpp b/src/widgets/ModuleWidget.cpp index 84bc71c0..db665bec 100644 --- a/src/widgets/ModuleWidget.cpp +++ b/src/widgets/ModuleWidget.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + ModuleWidget::ModuleWidget(Module *module) { this->module = module; if (module) { @@ -17,6 +19,21 @@ ModuleWidget::~ModuleWidget() { } } +void ModuleWidget::addInput(InputPort *input) { + inputs.push_back(input); + addChild(input); +} + +void ModuleWidget::addOutput(OutputPort *output) { + outputs.push_back(output); + addChild(output); +} + +void ModuleWidget::addParam(ParamWidget *param) { + params.push_back(param); + addChild(param); +} + json_t *ModuleWidget::toJson() { json_t *root = json_object(); @@ -161,3 +178,6 @@ void ModuleWidget::onMouseDown(int button) { gScene->addChild(overlay); } } + + +} // namespace rack diff --git a/src/widgets/OutputPort.cpp b/src/widgets/OutputPort.cpp index 88f17699..8e9dd1af 100644 --- a/src/widgets/OutputPort.cpp +++ b/src/widgets/OutputPort.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void OutputPort::draw(NVGcontext *vg) { Port::draw(vg); if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { @@ -32,3 +34,6 @@ void OutputPort::onDragDrop(Widget *origin) { connectedWire = gRackWidget->activeWire; } } + + +} // namespace rack diff --git a/src/widgets/ParamWidget.cpp b/src/widgets/ParamWidget.cpp index a12690ba..3b494583 100644 --- a/src/widgets/ParamWidget.cpp +++ b/src/widgets/ParamWidget.cpp @@ -1,18 +1,15 @@ -#include "../5V.hpp" +#include "Rack.hpp" -json_t *ParamWidget::toJson() { - json_t *paramJ = json_object(); - - json_t *valueJ = json_real(value); - json_object_set_new(paramJ, "value", valueJ); +namespace rack { +json_t *ParamWidget::toJson() { + json_t *paramJ = json_real(value); return paramJ; } void ParamWidget::fromJson(json_t *root) { - json_t *valueJ = json_object_get(root, "value"); - setValue(json_number_value(valueJ)); + setValue(json_number_value(root)); } void ParamWidget::onMouseDown(int button) { @@ -22,8 +19,10 @@ void ParamWidget::onMouseDown(int button) { } void ParamWidget::onChange() { - assert(moduleWidget); - assert(moduleWidget->module); + assert(module); // moduleWidget->module->params[paramId] = value; - rackSetParamSmooth(moduleWidget->module, paramId, value); + rackSetParamSmooth(module, paramId, value); } + + +} // namespace rack diff --git a/src/widgets/Port.cpp b/src/widgets/Port.cpp index d315a4b3..5f6edb5a 100644 --- a/src/widgets/Port.cpp +++ b/src/widgets/Port.cpp @@ -1,9 +1,11 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + Port::Port() { box.size = Vec(20, 20); - type = rand() % 5; + type = randomi64() % 5; } Port::~Port() { @@ -42,6 +44,12 @@ void Port::drawGlow(NVGcontext *vg) { nvgFill(vg); } +void Port::onMouseDown(int button) { + if (button == GLFW_MOUSE_BUTTON_RIGHT) { + disconnect(); + } +} + void Port::onDragEnd() { WireWidget *w = gRackWidget->activeWire; assert(w); @@ -52,3 +60,6 @@ void Port::onDragEnd() { } gRackWidget->activeWire = NULL; } + + +} // namespace rack diff --git a/src/widgets/QuantityWidget.cpp b/src/widgets/QuantityWidget.cpp index 53b7a319..07504c0a 100644 --- a/src/widgets/QuantityWidget.cpp +++ b/src/widgets/QuantityWidget.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void QuantityWidget::setValue(float value) { this->value = clampf(value, minValue, maxValue); onChange(); @@ -15,3 +17,6 @@ void QuantityWidget::setDefaultValue(float defaultValue) { this->defaultValue = defaultValue; setValue(defaultValue); } + + +} // namespace rack diff --git a/src/widgets/RackWidget.cpp b/src/widgets/RackWidget.cpp index 375b8f20..3956bebf 100644 --- a/src/widgets/RackWidget.cpp +++ b/src/widgets/RackWidget.cpp @@ -1,7 +1,9 @@ -#include "../5V.hpp" +#include "Rack.hpp" #include +namespace rack { + RackWidget::RackWidget() { moduleContainer = new TranslucentWidget(); addChild(moduleContainer); @@ -10,12 +12,58 @@ RackWidget::RackWidget() { addChild(wireContainer); } +RackWidget::~RackWidget() { +} + void RackWidget::clear() { activeWire = NULL; wireContainer->clearChildren(); moduleContainer->clearChildren(); } +void RackWidget::savePatch(std::string filename) { + printf("Saving patch %s\n", filename.c_str()); + FILE *file = fopen(filename.c_str(), "w"); + if (!file) + return; + + json_t *root = toJson(); + if (root) { + json_dumpf(root, file, JSON_INDENT(2)); + json_decref(root); + } + + fclose(file); +} + +void RackWidget::loadPatch(std::string filename) { + printf("Loading patch %s\n", filename.c_str()); + FILE *file = fopen(filename.c_str(), "r"); + if (!file) + return; + + json_error_t error; + json_t *root = json_loadf(file, 0, &error); + if (root) { + clear(); + fromJson(root); + json_decref(root); + } + else { + printf("JSON parsing error at %s %d:%d %s\n", error.source, error.line, error.column, error.text); + } + + fclose(file); +} + +// Recursive function to get the ModuleWidget eventually containing a widget +static ModuleWidget *getAncestorModuleWidget(Widget *w) { + if (!w) return NULL; + ModuleWidget *m = dynamic_cast(w); + if (m) return m; + return getAncestorModuleWidget(w->parent); +} + json_t *RackWidget::toJson() { // root json_t *root = json_object(); @@ -48,11 +96,18 @@ json_t *RackWidget::toJson() { for (Widget *w : wireContainer->children) { WireWidget *wireWidget = dynamic_cast(w); assert(wireWidget); + // Only serialize WireWidgets connected on both ends + if (!(wireWidget->outputPort && wireWidget->inputPort)) + continue; // wire json_t *wire = json_object(); { - int outputModuleId = moduleIds[wireWidget->outputPort->moduleWidget]; - int inputModuleId = moduleIds[wireWidget->inputPort->moduleWidget]; + ModuleWidget *outputModuleWidget = getAncestorModuleWidget(wireWidget->outputPort); + assert(outputModuleWidget); + ModuleWidget *inputModuleWidget = getAncestorModuleWidget(wireWidget->inputPort); + assert(inputModuleWidget); + int outputModuleId = moduleIds[outputModuleWidget]; + int inputModuleId = moduleIds[inputModuleWidget]; json_object_set_new(wire, "outputModuleId", json_integer(outputModuleId)); json_object_set_new(wire, "outputId", json_integer(wireWidget->outputPort->outputId)); json_object_set_new(wire, "inputModuleId", json_integer(inputModuleId)); @@ -141,7 +196,7 @@ void RackWidget::fromJson(json_t *root) { inputPort->connectedWire = wireWidget; wireWidget->updateWire(); // Add wire to rack - gRackWidget->wireContainer->addChild(wireWidget); + wireContainer->addChild(wireWidget); } } @@ -198,6 +253,12 @@ void RackWidget::step() { } } + // Autosave every 2 minutes + if (++frame >= 60*60*2) { + frame = 0; + savePatch("autosave.json"); + } + Widget::step(); } @@ -262,3 +323,6 @@ void RackWidget::onMouseDown(int button) { gScene->addChild(overlay); } } + + +} // namespace rack diff --git a/src/widgets/Scene.cpp b/src/widgets/Scene.cpp index 3d8efdb9..8214ce02 100644 --- a/src/widgets/Scene.cpp +++ b/src/widgets/Scene.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + Scene::Scene() { scrollWidget = new ScrollWidget(); { @@ -20,3 +22,6 @@ void Scene::onResize() { scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); scrollWidget->onResize(); } + + +} // namespace rack diff --git a/src/widgets/Screw.cpp b/src/widgets/Screw.cpp index b3853571..66812f5b 100644 --- a/src/widgets/Screw.cpp +++ b/src/widgets/Screw.cpp @@ -1,10 +1,15 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + Screw::Screw() { box.size = Vec(15, 15); spriteOffset = Vec(-7, -7); spriteSize = Vec(29, 29); spriteFilename = "res/screw.png"; - index = rand() % 5; + index = randomi64() % 5; } + + +} // namespace rack diff --git a/src/widgets/ScrollBar.cpp b/src/widgets/ScrollBar.cpp index c8d73b7f..9d96bd89 100644 --- a/src/widgets/ScrollBar.cpp +++ b/src/widgets/ScrollBar.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + ScrollBar::ScrollBar() { box.size.x = BND_SCROLLBAR_WIDTH; box.size.y = BND_SCROLLBAR_HEIGHT; @@ -36,3 +38,6 @@ void ScrollBar::onDragEnd() { state = BND_DEFAULT; guiCursorUnlock(); } + + +} // namespace rack diff --git a/src/widgets/ScrollWidget.cpp b/src/widgets/ScrollWidget.cpp index 91ac5bc4..7a26a257 100644 --- a/src/widgets/ScrollWidget.cpp +++ b/src/widgets/ScrollWidget.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + ScrollWidget::ScrollWidget() { container = new Widget(); addChild(container); @@ -40,3 +42,6 @@ void ScrollWidget::onScroll(Vec scrollRel) { hScrollBar->move(scrollRel.x); vScrollBar->move(scrollRel.y); } + + +} // namespace rack diff --git a/src/widgets/Slider.cpp b/src/widgets/Slider.cpp index 067d2204..68a43212 100644 --- a/src/widgets/Slider.cpp +++ b/src/widgets/Slider.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + #define SLIDER_SENSITIVITY 0.001 Slider::Slider() { @@ -27,3 +29,6 @@ void Slider::onDragEnd() { state = BND_DEFAULT; guiCursorUnlock(); } + + +} // namespace rack diff --git a/src/widgets/SpriteWidget.cpp b/src/widgets/SpriteWidget.cpp index f56dc0de..7abfcbab 100644 --- a/src/widgets/SpriteWidget.cpp +++ b/src/widgets/SpriteWidget.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void SpriteWidget::draw(NVGcontext *vg) { int imageId = loadImage(spriteFilename); if (imageId < 0) { @@ -24,3 +26,6 @@ void SpriteWidget::draw(NVGcontext *vg) { nvgRect(vg, pos.x, pos.y, spriteSize.x, spriteSize.y); nvgFill(vg); } + + +} // namespace rack diff --git a/src/widgets/Toolbar.cpp b/src/widgets/Toolbar.cpp index 44f7bb48..8e97ec95 100644 --- a/src/widgets/Toolbar.cpp +++ b/src/widgets/Toolbar.cpp @@ -1,10 +1,12 @@ -#include "../5V.hpp" +#include "Rack.hpp" extern "C" { #include "../lib/noc/noc_file_dialog.h" } +namespace rack { + static const char *filters = "JSON Patch\0*.json\0"; @@ -17,17 +19,8 @@ struct NewItem : MenuItem { struct SaveItem : MenuItem { void onAction() { const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, "Untitled.json"); - if (path) { - printf("Saving patch %s\n", path); - FILE *file = fopen(path, "w"); - - json_t *root = gRackWidget->toJson(); - assert(root); - json_dumpf(root, file, JSON_INDENT(2)); - json_decref(root); - - fclose(file); + gRackWidget->savePatch(path); } } }; @@ -35,23 +28,8 @@ struct SaveItem : MenuItem { struct OpenItem : MenuItem { void onAction() { const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL); - if (path) { - printf("Loading patch %s\n", path); - FILE *file = fopen(path, "r"); - - json_error_t error; - json_t *root = json_loadf(file, 0, &error); - if (root) { - gRackWidget->clear(); - gRackWidget->fromJson(root); - json_decref(root); - } - else { - printf("JSON parsing error at %s %d:%d %s\n", error.source, error.line, error.column, error.text); - } - - fclose(file); + gRackWidget->loadPatch(path); } } }; @@ -169,3 +147,6 @@ void Toolbar::draw(NVGcontext *vg) { Widget::draw(vg); } + + +} // namespace rack diff --git a/src/widgets/Tooltip.cpp b/src/widgets/Tooltip.cpp index e2e55558..5266e122 100644 --- a/src/widgets/Tooltip.cpp +++ b/src/widgets/Tooltip.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void Tooltip::step() { // Follow the mouse box.pos = gMousePos.minus(parent->getAbsolutePos()); @@ -14,3 +16,6 @@ void Tooltip::draw(NVGcontext *vg) { bndTooltipBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); Widget::draw(vg); } + + +} // namespace rack diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index a3c7f59a..195cc91f 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -1,7 +1,9 @@ -#include "../5V.hpp" +#include "Rack.hpp" #include +namespace rack { + Widget::~Widget() { // You should only delete orphaned widgets assert(!parent); @@ -87,3 +89,6 @@ Widget *Widget::pick(Vec pos) { } return this; } + + +} // namespace rack diff --git a/src/widgets/WireWidget.cpp b/src/widgets/WireWidget.cpp index 1d0746dd..adcedf1c 100644 --- a/src/widgets/WireWidget.cpp +++ b/src/widgets/WireWidget.cpp @@ -1,6 +1,8 @@ -#include "../5V.hpp" +#include "Rack.hpp" +namespace rack { + void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color) { float dist = pos1.minus(pos2).norm(); Vec slump = Vec(0, 100.0 + 0.5*dist); @@ -72,9 +74,9 @@ void WireWidget::updateWire() { } if (inputPort && outputPort) { wire = new Wire(); - wire->outputModule = outputPort->moduleWidget->module; + wire->outputModule = outputPort->module; wire->outputId = outputPort->outputId; - wire->inputModule = inputPort->moduleWidget->module; + wire->inputModule = inputPort->module; wire->inputId = inputPort->inputId; rackConnectWire(wire); } @@ -110,3 +112,6 @@ void WireWidget::draw(NVGcontext *vg) { } nvgRestore(vg); } + + +} // namespace rack