@@ -1,7 +1,7 @@ | |||||
ARCH ?= linux | ARCH ?= linux | ||||
CFLAGS = -MMD -g -Wall -O2 | CFLAGS = -MMD -g -Wall -O2 | ||||
CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math \ | CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math \ | ||||
-I./lib -I./src | |||||
-I./lib -I./include | |||||
LDFLAGS = | LDFLAGS = | ||||
SOURCES = $(wildcard src/*.cpp src/*/*.cpp) \ | SOURCES = $(wildcard src/*.cpp src/*/*.cpp) \ | ||||
@@ -15,7 +15,7 @@ CXX = g++ | |||||
SOURCES += lib/noc/noc_file_dialog.c | SOURCES += lib/noc/noc_file_dialog.c | ||||
CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | ||||
CXXFLAGS += -DLINUX | 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) | $(shell pkg-config --libs gtk+-2.0) | ||||
TARGET = Rack | TARGET = Rack | ||||
endif | endif | ||||
@@ -27,7 +27,7 @@ CXX = clang++ | |||||
SOURCES += lib/noc/noc_file_dialog.m | SOURCES += lib/noc/noc_file_dialog.m | ||||
CFLAGS += -DNOC_FILE_DIALOG_OSX | CFLAGS += -DNOC_FILE_DIALOG_OSX | ||||
CXXFLAGS += -DAPPLE -stdlib=libc++ -I$(HOME)/local/include | 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 | TARGET = Rack | ||||
endif | endif | ||||
@@ -37,20 +37,19 @@ CC = x86_64-w64-mingw32-gcc | |||||
CXX = x86_64-w64-mingw32-g++ | CXX = x86_64-w64-mingw32-g++ | ||||
SOURCES += lib/noc/noc_file_dialog.c | SOURCES += lib/noc/noc_file_dialog.c | ||||
CFLAGS += -DNOC_FILE_DIALOG_WIN32 | 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 \ | -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 | TARGET = Rack.exe | ||||
endif | endif | ||||
all: $(TARGET) | all: $(TARGET) | ||||
$(MAKE) -C plugins/Simple | |||||
$(MAKE) -C plugins/AudibleInstruments | |||||
include Makefile.inc | include Makefile.inc |
@@ -6,10 +6,4 @@ Virtual modular synthesizer engine | |||||
## Building | ## 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` |
@@ -2,11 +2,13 @@ | |||||
#include <string> | #include <string> | ||||
#include <list> | #include <list> | ||||
#include <vector> | |||||
#include <memory> | #include <memory> | ||||
#include "widgets.hpp" | #include "widgets.hpp" | ||||
#include "rack.hpp" | |||||
namespace rack { | |||||
extern Scene *gScene; | extern Scene *gScene; | ||||
extern RackWidget *gRackWidget; | extern RackWidget *gRackWidget; | ||||
@@ -20,9 +22,9 @@ struct Model; | |||||
struct Plugin { | struct Plugin { | ||||
virtual ~Plugin(); | virtual ~Plugin(); | ||||
// A unique identifier for your plugin, e.g. "simple" | |||||
// A unique identifier for your plugin, e.g. "foo" | |||||
std::string slug; | 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; | std::string name; | ||||
// A list of the models made available by this plugin | // A list of the models made available by this plugin | ||||
std::list<Model*> models; | std::list<Model*> models; | ||||
@@ -44,17 +46,6 @@ extern std::list<Plugin*> gPlugins; | |||||
void pluginInit(); | void pluginInit(); | ||||
void pluginDestroy(); | void pluginDestroy(); | ||||
//////////////////// | |||||
// midi.cpp | |||||
//////////////////// | |||||
void midiInit(); | |||||
void midiDestroy(); | |||||
int midiPortCount(); | |||||
std::string midiPortName(int portId); | |||||
void midiPortOpen(int portId); | |||||
void midiPortClose(); | |||||
//////////////////// | //////////////////// | ||||
// gui.cpp | // gui.cpp | ||||
//////////////////// | //////////////////// | ||||
@@ -77,6 +68,34 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId); | |||||
// rack.cpp | // rack.cpp | ||||
//////////////////// | //////////////////// | ||||
// TODO Find a clean way to make this a variable | |||||
#define SAMPLE_RATE 44100 | |||||
struct Wire; | |||||
struct Module { | |||||
std::vector<float> params; | |||||
// Pointers to voltage values at each port | |||||
// If value is NULL, the input/output is disconnected | |||||
std::vector<float*> inputs; | |||||
std::vector<float*> 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 rackInit(); | ||||
void rackDestroy(); | void rackDestroy(); | ||||
void rackStart(); | void rackStart(); | ||||
@@ -87,19 +106,8 @@ void rackRemoveModule(Module *module); | |||||
// Does not transfer ownership | // Does not transfer ownership | ||||
void rackConnectWire(Wire *wire); | void rackConnectWire(Wire *wire); | ||||
void rackDisconnectWire(Wire *wire); | void rackDisconnectWire(Wire *wire); | ||||
long rackGetFrame(); | |||||
void rackRequestFrame(long frame); | |||||
void rackSetParamSmooth(Module *module, int paramId, float value); | 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 | // Optional helpers for plugins | ||||
//////////////////// | //////////////////// | ||||
@@ -133,47 +141,50 @@ Model *createModel(Plugin *plugin, std::string slug, std::string name) { | |||||
} | } | ||||
template <class TParam> | template <class TParam> | ||||
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(); | ParamWidget *param = new TParam(); | ||||
param->moduleWidget = moduleWidget; | |||||
param->box.pos = pos; | |||||
param->module = module; | |||||
param->paramId = paramId; | param->paramId = paramId; | ||||
param->setLimits(minValue, maxValue); | param->setLimits(minValue, maxValue); | ||||
param->setDefaultValue(defaultValue); | 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; | return param; | ||||
} | } | ||||
inline | inline | ||||
InputPort *createInputPort(ModuleWidget *moduleWidget, int inputId, Vec pos) { | |||||
InputPort *createInput(Vec pos, Module *module, int inputId) { | |||||
InputPort *port = new InputPort(); | InputPort *port = new InputPort(); | ||||
port->moduleWidget = moduleWidget; | |||||
port->inputId = inputId; | |||||
port->box.pos = pos; | 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; | return port; | ||||
} | } | ||||
inline | inline | ||||
OutputPort *createOutputPort(ModuleWidget *moduleWidget, int outputId, Vec pos) { | |||||
OutputPort *createOutput(Vec pos, Module *module, int outputId) { | |||||
OutputPort *port = new OutputPort(); | OutputPort *port = new OutputPort(); | ||||
port->moduleWidget = moduleWidget; | |||||
port->outputId = outputId; | |||||
port->box.pos = pos; | 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; | return port; | ||||
} | } | ||||
inline | inline | ||||
Screw *createScrew(ModuleWidget *moduleWidget, Vec pos) { | |||||
Screw *createScrew(Vec pos) { | |||||
Screw *screw = new Screw(); | Screw *screw = new Screw(); | ||||
screw->box.pos = pos; | screw->box.pos = pos; | ||||
moduleWidget->addChild(screw); | |||||
return 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(); |
@@ -1,17 +1,15 @@ | |||||
#pragma once | #pragma once | ||||
#include <stdint.h> | |||||
#include <math.h> | #include <math.h> | ||||
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) { | inline float clampf(float x, float min, float max) { | ||||
return fmaxf(min, fminf(max, x)); | 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) { | 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) { | inline int mini(int a, int b) { | ||||
@@ -39,14 +37,36 @@ inline int eucMod(int a, int base) { | |||||
return mod < 0 ? mod + base : mod; | 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; | return p ? *p : v; | ||||
} | } | ||||
inline void setf(float *p, float 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 { | struct Vec { | ||||
float x, y; | float x, y; | ||||
@@ -115,3 +135,6 @@ struct Rect { | |||||
return pos.plus(size); | return pos.plus(size); | ||||
} | } | ||||
}; | }; | ||||
} // namespace rack |
@@ -3,6 +3,7 @@ | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <vector> | |||||
#include <list> | #include <list> | ||||
#include <map> | #include <map> | ||||
@@ -13,11 +14,14 @@ | |||||
#include "../lib/nanovg/src/nanovg.h" | #include "../lib/nanovg/src/nanovg.h" | ||||
#include "../lib/oui/blendish.h" | #include "../lib/oui/blendish.h" | ||||
#include "rack.hpp" | |||||
#include "util.hpp" | #include "util.hpp" | ||||
struct MenuEntry; | |||||
namespace rack { | |||||
struct Module; | |||||
struct Wire; | |||||
struct RackWidget; | struct RackWidget; | ||||
struct ParamWidget; | struct ParamWidget; | ||||
struct InputPort; | struct InputPort; | ||||
@@ -73,6 +77,11 @@ struct Widget { | |||||
virtual void onChange() {} | 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 | // Widget that does not respond to events, but allows its children to | ||||
struct TranslucentWidget : virtual Widget { | struct TranslucentWidget : virtual Widget { | ||||
Widget *pick(Vec pos) { | 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 { | struct SpriteWidget : virtual Widget { | ||||
Vec spriteOffset; | Vec spriteOffset; | ||||
Vec spriteSize; | Vec spriteSize; | ||||
@@ -230,6 +234,11 @@ struct ModuleWidget : Widget { | |||||
ModuleWidget(Module *module); | ModuleWidget(Module *module); | ||||
~ModuleWidget(); | ~ModuleWidget(); | ||||
// Convenience functions for adding special widgets (calls addChild()) | |||||
void addInput(InputPort *input); | |||||
void addOutput(OutputPort *output); | |||||
void addParam(ParamWidget *param); | |||||
json_t *toJson(); | json_t *toJson(); | ||||
void fromJson(json_t *root); | void fromJson(json_t *root); | ||||
void disconnectPorts(); | void disconnectPorts(); | ||||
@@ -270,10 +279,14 @@ struct RackWidget : Widget { | |||||
WireWidget *activeWire = NULL; | WireWidget *activeWire = NULL; | ||||
RackWidget(); | RackWidget(); | ||||
~RackWidget(); | |||||
void clear(); | void clear(); | ||||
void savePatch(std::string filename); | |||||
void loadPatch(std::string filename); | |||||
json_t *toJson(); | json_t *toJson(); | ||||
void fromJson(json_t *root); | void fromJson(json_t *root); | ||||
int frame = 0; | |||||
void repositionModule(ModuleWidget *module); | void repositionModule(ModuleWidget *module); | ||||
void step(); | void step(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
@@ -296,8 +309,7 @@ struct Screw : TransparentWidget, SpriteWidget { | |||||
}; | }; | ||||
struct ParamWidget : QuantityWidget { | struct ParamWidget : QuantityWidget { | ||||
// Ancestor ModuleWidget, used for accessing the Module | |||||
ModuleWidget *moduleWidget; | |||||
Module *module = NULL; | |||||
int paramId; | int paramId; | ||||
json_t *toJson(); | json_t *toJson(); | ||||
@@ -325,12 +337,13 @@ struct ToggleSwitch : virtual Switch { | |||||
index = 0; | index = 0; | ||||
} | } | ||||
void onDragDrop(Widget *origin) { | 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 { | struct Port : Widget { | ||||
// Ancestor ModuleWidget, used for accessing the Module | |||||
ModuleWidget *moduleWidget; | |||||
Module *module = NULL; | |||||
WireWidget *connectedWire = NULL; | WireWidget *connectedWire = NULL; | ||||
Port(); | Port(); | ||||
@@ -361,6 +373,7 @@ struct Port : Widget { | |||||
int type; | int type; | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
void drawGlow(NVGcontext *vg); | void drawGlow(NVGcontext *vg); | ||||
void onMouseDown(int button); | |||||
void onDragEnd(); | void onDragEnd(); | ||||
}; | }; | ||||
@@ -396,3 +409,6 @@ struct Scene : Widget { | |||||
Scene(); | Scene(); | ||||
void onResize(); | void onResize(); | ||||
}; | }; | ||||
} // namespace rack |
@@ -1,11 +1,13 @@ | |||||
#include "core.hpp" | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <mutex> | #include <mutex> | ||||
#include <condition_variable> | |||||
#include <rtaudio/RtAudio.h> | |||||
#include <portaudio.h> | |||||
#include "core.hpp" | |||||
using namespace rack; | |||||
static bool audioInitialized = false; | |||||
#define AUDIO_BUFFER_SIZE 16384 | |||||
struct AudioInterface : Module { | struct AudioInterface : Module { | ||||
enum ParamIds { | enum ParamIds { | ||||
@@ -20,27 +22,21 @@ struct AudioInterface : Module { | |||||
NUM_OUTPUTS | 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::mutex mutex; | ||||
std::condition_variable cv; | |||||
bool running; | |||||
AudioInterface(); | AudioInterface(); | ||||
~AudioInterface(); | ~AudioInterface(); | ||||
void step(); | void step(); | ||||
int getDeviceCount(); | |||||
std::string getDeviceName(int deviceId); | |||||
// Use -1 as the deviceId to close the current device | |||||
void openDevice(int deviceId); | 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); | params.resize(NUM_PARAMS); | ||||
inputs.resize(NUM_INPUTS); | inputs.resize(NUM_INPUTS); | ||||
outputs.resize(NUM_OUTPUTS); | 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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; | AudioInterface *audioInterface; | ||||
int deviceId; | int deviceId; | ||||
void onAction() { | void onAction() { | ||||
audioInterface->closeDevice(); | |||||
audioInterface->openDevice(deviceId); | audioInterface->openDevice(deviceId); | ||||
} | } | ||||
}; | }; | ||||
@@ -156,23 +154,17 @@ struct AudioChoice : ChoiceButton { | |||||
Menu *menu = new Menu(); | Menu *menu = new Menu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | ||||
int deviceCount = audioInterface->audio.getDeviceCount(); | |||||
int deviceCount = audioInterface->getDeviceCount(); | |||||
if (deviceCount == 0) { | if (deviceCount == 0) { | ||||
MenuLabel *label = new MenuLabel(); | MenuLabel *label = new MenuLabel(); | ||||
label->text = "No audio devices"; | label->text = "No audio devices"; | ||||
menu->pushChild(label); | menu->pushChild(label); | ||||
} | } | ||||
for (int deviceId = 0; deviceId < deviceCount; deviceId++) { | 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 *audioItem = new AudioItem(); | ||||
audioItem->audioInterface = audioInterface; | audioItem->audioInterface = audioInterface; | ||||
audioItem->deviceId = deviceId; | audioItem->deviceId = deviceId; | ||||
std::string text = audioInterface->getDeviceName(deviceId); | |||||
audioItem->text = text; | audioItem->text = text; | ||||
menu->pushChild(audioItem); | menu->pushChild(audioItem); | ||||
} | } | ||||
@@ -184,17 +176,18 @@ struct AudioChoice : ChoiceButton { | |||||
AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()) { | AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()) { | ||||
box.size = Vec(15*8, 380); | 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<AudioInterface*>(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<AudioInterface*>(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) { | void AudioInterfaceWidget::draw(NVGcontext *vg) { | ||||
@@ -1,8 +1,13 @@ | |||||
#include "core.hpp" | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <rtmidi/RtMidi.h> | |||||
#include <list> | #include <list> | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <portmidi.h> | |||||
#include "core.hpp" | |||||
using namespace rack; | |||||
static bool midiInitialized = false; | |||||
struct MidiInterface : Module { | struct MidiInterface : Module { | ||||
@@ -18,7 +23,7 @@ struct MidiInterface : Module { | |||||
NUM_OUTPUTS | NUM_OUTPUTS | ||||
}; | }; | ||||
RtMidiIn midi; | |||||
PortMidiStream *stream = NULL; | |||||
std::list<int> notes; | std::list<int> notes; | ||||
bool pedal = false; | bool pedal = false; | ||||
bool gate = false; | bool gate = false; | ||||
@@ -29,35 +34,45 @@ struct MidiInterface : Module { | |||||
~MidiInterface(); | ~MidiInterface(); | ||||
void step(); | void step(); | ||||
int getPortCount(); | |||||
std::string getPortName(int portId); | |||||
// -1 will close the port | |||||
void openPort(int portId); | void openPort(int portId); | ||||
void closePort(); | |||||
void pressNote(int note); | void pressNote(int note); | ||||
void releaseNote(int note); | void releaseNote(int note); | ||||
void processMidi(long msg); | void processMidi(long msg); | ||||
}; | }; | ||||
void midiCallback(double timeStamp, std::vector<unsigned char> *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() { | MidiInterface::MidiInterface() { | ||||
params.resize(NUM_PARAMS); | params.resize(NUM_PARAMS); | ||||
inputs.resize(NUM_INPUTS); | inputs.resize(NUM_INPUTS); | ||||
outputs.resize(NUM_OUTPUTS); | 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() { | MidiInterface::~MidiInterface() { | ||||
closePort(); | |||||
openPort(-1); | |||||
} | } | ||||
void MidiInterface::step() { | void MidiInterface::step() { | ||||
if (stream) { | |||||
// Read MIDI events | |||||
PmEvent event; | |||||
while (Pm_Read(stream, &event, 1) > 0) { | |||||
processMidi(event.message); | |||||
} | |||||
} | |||||
if (outputs[GATE_OUTPUT]) { | if (outputs[GATE_OUTPUT]) { | ||||
*outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; | *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) { | 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) { | void MidiInterface::pressNote(int note) { | ||||
@@ -155,7 +185,6 @@ struct MidiItem : MenuItem { | |||||
MidiInterface *midiInterface; | MidiInterface *midiInterface; | ||||
int portId; | int portId; | ||||
void onAction() { | void onAction() { | ||||
midiInterface->closePort(); | |||||
midiInterface->openPort(portId); | midiInterface->openPort(portId); | ||||
} | } | ||||
}; | }; | ||||
@@ -167,7 +196,7 @@ struct MidiChoice : ChoiceButton { | |||||
Menu *menu = new Menu(); | Menu *menu = new Menu(); | ||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | ||||
int portCount = midiInterface->midi.getPortCount(); | |||||
int portCount = midiInterface->getPortCount(); | |||||
if (portCount == 0) { | if (portCount == 0) { | ||||
MenuLabel *label = new MenuLabel(); | MenuLabel *label = new MenuLabel(); | ||||
label->text = "No MIDI devices"; | label->text = "No MIDI devices"; | ||||
@@ -177,7 +206,7 @@ struct MidiChoice : ChoiceButton { | |||||
MidiItem *midiItem = new MidiItem(); | MidiItem *midiItem = new MidiItem(); | ||||
midiItem->midiInterface = midiInterface; | midiItem->midiInterface = midiInterface; | ||||
midiItem->portId = portId; | midiItem->portId = portId; | ||||
midiItem->text = midiInterface->midi.getPortName(); | |||||
midiItem->text = midiInterface->getPortName(portId); | |||||
menu->pushChild(midiItem); | menu->pushChild(midiItem); | ||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
@@ -188,17 +217,18 @@ struct MidiChoice : ChoiceButton { | |||||
MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { | MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { | ||||
box.size = Vec(15*8, 380); | 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<MidiInterface*>(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<MidiInterface*>(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) { | void MidiInterfaceWidget::draw(NVGcontext *vg) { | ||||
@@ -1,6 +1,8 @@ | |||||
#include "core.hpp" | #include "core.hpp" | ||||
using namespace rack; | |||||
Plugin *coreInit() { | Plugin *coreInit() { | ||||
Plugin *plugin = createPlugin("Core", "Core"); | Plugin *plugin = createPlugin("Core", "Core"); | ||||
createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface"); | createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface"); | ||||
@@ -1,18 +1,18 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
Plugin *coreInit(); | |||||
rack::Plugin *coreInit(); | |||||
//////////////////// | //////////////////// | ||||
// module widgets | // module widgets | ||||
//////////////////// | //////////////////// | ||||
struct AudioInterfaceWidget : ModuleWidget { | |||||
struct AudioInterfaceWidget : rack::ModuleWidget { | |||||
AudioInterfaceWidget(); | AudioInterfaceWidget(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
struct MidiInterfaceWidget : ModuleWidget { | |||||
struct MidiInterfaceWidget : rack::ModuleWidget { | |||||
MidiInterfaceWidget(); | MidiInterfaceWidget(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; |
@@ -1,5 +1,5 @@ | |||||
#include "5V.hpp" | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include "Rack.hpp" | |||||
// #define NANOVG_GLEW | // #define NANOVG_GLEW | ||||
#define NANOVG_IMPLEMENTATION | #define NANOVG_IMPLEMENTATION | ||||
@@ -10,13 +10,18 @@ | |||||
#include "../lib/oui/blendish.h" | #include "../lib/oui/blendish.h" | ||||
static GLFWwindow *window; | |||||
static NVGcontext *vg = NULL; | |||||
namespace rack { | |||||
Scene *gScene = NULL; | |||||
RackWidget *gRackWidget = NULL; | |||||
Vec gMousePos; | Vec gMousePos; | ||||
Widget *gHoveredWidget = NULL; | Widget *gHoveredWidget = NULL; | ||||
Widget *gDraggedWidget = NULL; | Widget *gDraggedWidget = NULL; | ||||
static GLFWwindow *window; | |||||
static NVGcontext *vg = NULL; | |||||
void windowSizeCallback(GLFWwindow* window, int width, int height) { | void windowSizeCallback(GLFWwindow* window, int width, int height) { | ||||
gScene->box.size = Vec(width, 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() { | void renderGui() { | ||||
int width, height; | 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 | // Update and render | ||||
glViewport(0, 0, width, height); | 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. | // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here. | ||||
glGetError(); | glGetError(); | ||||
glfwSetWindowSizeLimits(window, 240, 160, GLFW_DONT_CARE, GLFW_DONT_CARE); | |||||
// Set up NanoVG | // Set up NanoVG | ||||
vg = nvgCreateGL2(NVG_ANTIALIAS); | vg = nvgCreateGL2(NVG_ANTIALIAS); | ||||
@@ -195,10 +203,14 @@ void guiInit() { | |||||
// Set up Blendish | // Set up Blendish | ||||
bndSetFont(loadFont("res/DejaVuSans.ttf")); | bndSetFont(loadFont("res/DejaVuSans.ttf")); | ||||
bndSetIconImage(loadImage("res/blender_icons16.png")); | |||||
// bndSetIconImage(loadImage("res/blender_icons16.png")); | |||||
gScene = new Scene(); | |||||
} | } | ||||
void guiDestroy() { | void guiDestroy() { | ||||
delete gScene; | |||||
nvgDeleteGL2(vg); | nvgDeleteGL2(vg); | ||||
glfwDestroyWindow(window); | glfwDestroyWindow(window); | ||||
glfwTerminate(); | glfwTerminate(); | ||||
@@ -292,3 +304,6 @@ void drawImage(NVGcontext *vg, Vec pos, int imageId) { | |||||
nvgRect(vg, pos.x, pos.y, width, height); | nvgRect(vg, pos.x, pos.y, width, height); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
} // namespace rack |
@@ -1,33 +1,42 @@ | |||||
#include "5V.hpp" | |||||
#include <time.h> | |||||
#include <unistd.h> | |||||
#if defined(APPLE) | |||||
#include "CoreFoundation/CoreFoundation.h" | |||||
#include <unistd.h> | |||||
#include <libgen.h> | |||||
#endif | |||||
#include "Rack.hpp" | |||||
Scene *gScene = NULL; | |||||
RackWidget *gRackWidget = NULL; | |||||
using namespace rack; | |||||
int main() { | 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(); | pluginInit(); | ||||
rackInit(); | rackInit(); | ||||
guiInit(); | guiInit(); | ||||
gScene = new Scene(); | |||||
// audioInit(); | |||||
// audioDeviceOpen(); | |||||
// midiInit(); | |||||
rackStart(); | |||||
gRackWidget->loadPatch("autosave.json"); | |||||
// Blocks until user exits | |||||
rackStart(); | |||||
guiRun(); | guiRun(); | ||||
rackStop(); | |||||
// Cleanup | |||||
// midiDestroy(); | |||||
// audioDeviceClose(); | |||||
// audioDestroy(); | |||||
delete gScene; | |||||
gRackWidget->savePatch("autosave.json"); | |||||
guiDestroy(); | guiDestroy(); | ||||
rackStop(); | |||||
rackDestroy(); | rackDestroy(); | ||||
pluginDestroy(); | pluginDestroy(); | ||||
return 0; | |||||
return 0; | |||||
} | } |
@@ -7,10 +7,12 @@ | |||||
#include <glob.h> | #include <glob.h> | ||||
#endif | #endif | ||||
#include "5V.hpp" | |||||
#include "Rack.hpp" | |||||
#include "core/core.hpp" | #include "core/core.hpp" | ||||
namespace rack { | |||||
std::list<Plugin*> gPlugins; | std::list<Plugin*> gPlugins; | ||||
int loadPlugin(const char *path) { | int loadPlugin(const char *path) { | ||||
@@ -62,6 +64,7 @@ void pluginInit() { | |||||
// Search for plugin libraries | // Search for plugin libraries | ||||
#if defined(WINDOWS) | #if defined(WINDOWS) | ||||
// TODO | |||||
// WIN32_FIND_DATA ffd; | // WIN32_FIND_DATA ffd; | ||||
// HANDLE hFind = FindFirstFile("plugins/*/plugin.dll", &ffd); | // HANDLE hFind = FindFirstFile("plugins/*/plugin.dll", &ffd); | ||||
// if (hFind != INVALID_HANDLE_VALUE) { | // if (hFind != INVALID_HANDLE_VALUE) { | ||||
@@ -101,3 +104,6 @@ Plugin::~Plugin() { | |||||
delete model; | delete model; | ||||
} | } | ||||
} | } | ||||
} // namespace rack |
@@ -2,35 +2,56 @@ | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <assert.h> | #include <assert.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <list> | |||||
#include <set> | |||||
#include <chrono> | |||||
#include <thread> | #include <thread> | ||||
#include <mutex> | #include <mutex> | ||||
#include <condition_variable> | #include <condition_variable> | ||||
#include "rack.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
static std::thread thread; | static std::thread thread; | ||||
static std::mutex mutex; | static std::mutex mutex; | ||||
static std::condition_variable cv; | |||||
static long frame; | |||||
static long frameLimit; | |||||
static bool running; | static bool running; | ||||
static std::set<Module*> modules; | static std::set<Module*> 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 | // 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<Wire*> wires; | static std::set<Wire*> wires; | ||||
// Parameter interpolation | |||||
static Module *smoothModule = NULL; | static Module *smoothModule = NULL; | ||||
static int smoothParamId; | static int smoothParamId; | ||||
static float smoothValue; | static float smoothValue; | ||||
// HACK | |||||
static std::mutex requestMutex; | |||||
static std::condition_variable requestCv; | |||||
static bool request = false; | |||||
struct RackRequest { | |||||
RackRequest() { | |||||
std::unique_lock<std::mutex> lock(requestMutex); | |||||
request = true; | |||||
} | |||||
~RackRequest() { | |||||
std::unique_lock<std::mutex> lock(requestMutex); | |||||
request = false; | |||||
lock.unlock(); | |||||
requestCv.notify_all(); | |||||
} | |||||
}; | |||||
// END HACK | |||||
void rackInit() { | void rackInit() { | ||||
} | } | ||||
void rackDestroy() { | 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(wires.empty()); | ||||
assert(modules.empty()); | assert(modules.empty()); | ||||
} | } | ||||
@@ -39,24 +60,17 @@ void rackStep() { | |||||
// Param interpolation | // Param interpolation | ||||
if (smoothModule) { | if (smoothModule) { | ||||
float value = smoothModule->params[smoothParamId]; | 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 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; | smoothModule = NULL; | ||||
} | } | ||||
else { | |||||
value += delta * lambda / SAMPLE_RATE; | |||||
smoothModule->params[smoothParamId] = value; | |||||
} | |||||
} | } | ||||
// Step all modules | // Step all modules | ||||
for (Module *module : modules) { | for (Module *module : modules) { | ||||
@@ -64,41 +78,44 @@ void rackStep() { | |||||
} | } | ||||
} | } | ||||
void rackRun() { | |||||
while (1) { | |||||
std::unique_lock<std::mutex> 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<std::mutex> lock(requestMutex); | |||||
while (request) | |||||
requestCv.wait(lock); | |||||
} | |||||
{ | |||||
std::lock_guard<std::mutex> 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() { | void rackStart() { | ||||
frame = 0; | |||||
frameLimit = 0; | |||||
running = true; | running = true; | ||||
thread = std::thread(rackRun); | thread = std::thread(rackRun); | ||||
} | } | ||||
void rackStop() { | void rackStop() { | ||||
{ | |||||
std::unique_lock<std::mutex> lock(mutex); | |||||
running = false; | |||||
} | |||||
cv.notify_all(); | |||||
running = false; | |||||
thread.join(); | thread.join(); | ||||
} | } | ||||
void rackAddModule(Module *module) { | void rackAddModule(Module *module) { | ||||
assert(module); | assert(module); | ||||
std::unique_lock<std::mutex> lock(mutex); | |||||
RackRequest rm; | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// Check that the module is not already added | // Check that the module is not already added | ||||
assert(modules.find(module) == modules.end()); | assert(modules.find(module) == modules.end()); | ||||
modules.insert(module); | modules.insert(module); | ||||
@@ -106,7 +123,8 @@ void rackAddModule(Module *module) { | |||||
void rackRemoveModule(Module *module) { | void rackRemoveModule(Module *module) { | ||||
assert(module); | assert(module); | ||||
std::unique_lock<std::mutex> lock(mutex); | |||||
RackRequest rm; | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// Remove parameter interpolation which point to this module | // Remove parameter interpolation which point to this module | ||||
if (module == smoothModule) { | if (module == smoothModule) { | ||||
smoothModule = NULL; | smoothModule = NULL; | ||||
@@ -124,7 +142,8 @@ void rackRemoveModule(Module *module) { | |||||
void rackConnectWire(Wire *wire) { | void rackConnectWire(Wire *wire) { | ||||
assert(wire); | assert(wire); | ||||
std::unique_lock<std::mutex> lock(mutex); | |||||
RackRequest rm; | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// It would probably be good to reset the wire voltage | // It would probably be good to reset the wire voltage | ||||
wire->value = 0.0; | wire->value = 0.0; | ||||
// Check that the wire is not already added | // Check that the wire is not already added | ||||
@@ -145,7 +164,8 @@ void rackConnectWire(Wire *wire) { | |||||
void rackDisconnectWire(Wire *wire) { | void rackDisconnectWire(Wire *wire) { | ||||
assert(wire); | assert(wire); | ||||
std::unique_lock<std::mutex> lock(mutex); | |||||
RackRequest rm; | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// Disconnect wire from inputModule | // Disconnect wire from inputModule | ||||
wire->inputModule->inputs[wire->inputId] = NULL; | wire->inputModule->inputs[wire->inputId] = NULL; | ||||
wire->outputModule->outputs[wire->outputId] = NULL; | wire->outputModule->outputs[wire->outputId] = NULL; | ||||
@@ -155,19 +175,6 @@ void rackDisconnectWire(Wire *wire) { | |||||
wires.erase(it); | wires.erase(it); | ||||
} | } | ||||
long rackGetFrame() { | |||||
return frame; | |||||
} | |||||
void rackRequestFrame(long f) { | |||||
std::unique_lock<std::mutex> lock(mutex); | |||||
if (f > frameLimit) { | |||||
frameLimit = f; | |||||
lock.unlock(); | |||||
cv.notify_all(); | |||||
} | |||||
} | |||||
void rackSetParamSmooth(Module *module, int paramId, float value) { | void rackSetParamSmooth(Module *module, int paramId, float value) { | ||||
// Check existing parameter interpolation | // Check existing parameter interpolation | ||||
if (smoothModule) { | if (smoothModule) { | ||||
@@ -180,3 +187,6 @@ void rackSetParamSmooth(Module *module, int paramId, float value) { | |||||
smoothParamId = paramId; | smoothParamId = paramId; | ||||
smoothValue = value; | smoothValue = value; | ||||
} | } | ||||
} // namespace rack |
@@ -1,34 +0,0 @@ | |||||
#pragma once | |||||
#include <stdlib.h> | |||||
#include <set> | |||||
#include <vector> | |||||
// TODO Find a clean way to make this a variable | |||||
#define SAMPLE_RATE 44100 | |||||
struct Wire; | |||||
struct Module { | |||||
std::vector<float> params; | |||||
// Pointers to voltage values at each port | |||||
// If value is NULL, the input/output is disconnected | |||||
std::vector<float*> inputs; | |||||
std::vector<float*> 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; | |||||
}; |
@@ -0,0 +1,77 @@ | |||||
#include "util.hpp" | |||||
#include <stdint.h> | |||||
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 <http://creativecommons.org/publicdomain/zero/1.0/>. */ | |||||
/* 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 |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
Button::Button() { | Button::Button() { | ||||
box.size.y = BND_WIDGET_HEIGHT; | box.size.y = BND_WIDGET_HEIGHT; | ||||
} | } | ||||
@@ -22,3 +24,6 @@ void Button::onDragDrop(Widget *origin) { | |||||
onAction(); | onAction(); | ||||
} | } | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,11 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void ChoiceButton::draw(NVGcontext *vg) { | 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()); | bndChoiceButton(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str()); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void InputPort::draw(NVGcontext *vg) { | void InputPort::draw(NVGcontext *vg) { | ||||
Port::draw(vg); | Port::draw(vg); | ||||
if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { | if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { | ||||
@@ -32,3 +34,6 @@ void InputPort::onDragDrop(Widget *origin) { | |||||
connectedWire = gRackWidget->activeWire; | connectedWire = gRackWidget->activeWire; | ||||
} | } | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
#define KNOB_SENSITIVITY 0.001 | #define KNOB_SENSITIVITY 0.001 | ||||
@@ -19,3 +21,6 @@ void Knob::onDragMove(Vec mouseRel) { | |||||
void Knob::onDragEnd() { | void Knob::onDragEnd() { | ||||
guiCursorUnlock(); | guiCursorUnlock(); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,11 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void Label::draw(NVGcontext *vg) { | void Label::draw(NVGcontext *vg) { | ||||
bndLabel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, -1, text.c_str()); | bndLabel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, -1, text.c_str()); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void Light::draw(NVGcontext *vg) { | void Light::draw(NVGcontext *vg) { | ||||
SpriteWidget::draw(vg); | SpriteWidget::draw(vg); | ||||
@@ -8,7 +10,7 @@ void Light::draw(NVGcontext *vg) { | |||||
return; | return; | ||||
// Draw glow | // Draw glow | ||||
Vec c = box.getCenter(); | Vec c = box.getCenter(); | ||||
float radius = box.size.x / 2; | |||||
float radius = box.size.x / 2.0; | |||||
NVGcolor icol, ocol; | NVGcolor icol, ocol; | ||||
NVGpaint paint; | NVGpaint paint; | ||||
// Inner glow | // Inner glow | ||||
@@ -16,20 +18,23 @@ void Light::draw(NVGcontext *vg) { | |||||
icol.a = clampf(color.a, 0.0, 1.0); | icol.a = clampf(color.a, 0.0, 1.0); | ||||
ocol = color; | ocol = color; | ||||
ocol.a = clampf(color.a, 0.0, 1.0); | 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); | nvgFillPaint(vg, paint); | ||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgCircle(vg, c.x, c.y, radius); | nvgCircle(vg, c.x, c.y, radius); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
// Outer glow | // Outer glow | ||||
icol = color; | 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 = color; | ||||
ocol.a = 0.0; | ocol.a = 0.0; | ||||
float oradius = radius + 20; | |||||
float oradius = radius + 20.0; | |||||
paint = nvgRadialGradient(vg, c.x, c.y, radius, oradius, icol, ocol); | paint = nvgRadialGradient(vg, c.x, c.y, radius, oradius, icol, ocol); | ||||
nvgFillPaint(vg, paint); | nvgFillPaint(vg, paint); | ||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, c.x - oradius, c.y - oradius, 2*oradius, 2*oradius); | nvgRect(vg, c.x - oradius, c.y - oradius, 2*oradius, 2*oradius); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void Menu::pushChild(Widget *child) { | void Menu::pushChild(Widget *child) { | ||||
child->box.pos = Vec(0, box.size.y); | child->box.pos = Vec(0, box.size.y); | ||||
addChild(child); | addChild(child); | ||||
@@ -25,3 +27,6 @@ void Menu::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,11 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
float MenuEntry::computeMinWidth(NVGcontext *vg) { | float MenuEntry::computeMinWidth(NVGcontext *vg) { | ||||
return bndLabelWidth(vg, -1, text.c_str()); | return bndLabelWidth(vg, -1, text.c_str()); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void MenuItem::draw(NVGcontext *vg) { | void MenuItem::draw(NVGcontext *vg) { | ||||
bndMenuItem(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, state, -1, text.c_str()); | 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; | delete overlay; | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,11 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void MenuLabel::draw(NVGcontext *vg) { | void MenuLabel::draw(NVGcontext *vg) { | ||||
bndMenuLabel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, -1, text.c_str()); | bndMenuLabel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, -1, text.c_str()); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void MenuOverlay::onMouseDown(int button) { | void MenuOverlay::onMouseDown(int button) { | ||||
if (parent) { | if (parent) { | ||||
parent->removeChild(this); | parent->removeChild(this); | ||||
@@ -8,3 +10,6 @@ void MenuOverlay::onMouseDown(int button) { | |||||
// Commit sudoku | // Commit sudoku | ||||
delete this; | delete this; | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
ModuleWidget::ModuleWidget(Module *module) { | ModuleWidget::ModuleWidget(Module *module) { | ||||
this->module = module; | this->module = module; | ||||
if (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 *ModuleWidget::toJson() { | ||||
json_t *root = json_object(); | json_t *root = json_object(); | ||||
@@ -161,3 +178,6 @@ void ModuleWidget::onMouseDown(int button) { | |||||
gScene->addChild(overlay); | gScene->addChild(overlay); | ||||
} | } | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void OutputPort::draw(NVGcontext *vg) { | void OutputPort::draw(NVGcontext *vg) { | ||||
Port::draw(vg); | Port::draw(vg); | ||||
if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { | if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { | ||||
@@ -32,3 +34,6 @@ void OutputPort::onDragDrop(Widget *origin) { | |||||
connectedWire = gRackWidget->activeWire; | connectedWire = gRackWidget->activeWire; | ||||
} | } | ||||
} | } | ||||
} // namespace rack |
@@ -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; | return paramJ; | ||||
} | } | ||||
void ParamWidget::fromJson(json_t *root) { | 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) { | void ParamWidget::onMouseDown(int button) { | ||||
@@ -22,8 +19,10 @@ void ParamWidget::onMouseDown(int button) { | |||||
} | } | ||||
void ParamWidget::onChange() { | void ParamWidget::onChange() { | ||||
assert(moduleWidget); | |||||
assert(moduleWidget->module); | |||||
assert(module); | |||||
// moduleWidget->module->params[paramId] = value; | // moduleWidget->module->params[paramId] = value; | ||||
rackSetParamSmooth(moduleWidget->module, paramId, value); | |||||
rackSetParamSmooth(module, paramId, value); | |||||
} | } | ||||
} // namespace rack |
@@ -1,9 +1,11 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
Port::Port() { | Port::Port() { | ||||
box.size = Vec(20, 20); | box.size = Vec(20, 20); | ||||
type = rand() % 5; | |||||
type = randomi64() % 5; | |||||
} | } | ||||
Port::~Port() { | Port::~Port() { | ||||
@@ -42,6 +44,12 @@ void Port::drawGlow(NVGcontext *vg) { | |||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
void Port::onMouseDown(int button) { | |||||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
disconnect(); | |||||
} | |||||
} | |||||
void Port::onDragEnd() { | void Port::onDragEnd() { | ||||
WireWidget *w = gRackWidget->activeWire; | WireWidget *w = gRackWidget->activeWire; | ||||
assert(w); | assert(w); | ||||
@@ -52,3 +60,6 @@ void Port::onDragEnd() { | |||||
} | } | ||||
gRackWidget->activeWire = NULL; | gRackWidget->activeWire = NULL; | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void QuantityWidget::setValue(float value) { | void QuantityWidget::setValue(float value) { | ||||
this->value = clampf(value, minValue, maxValue); | this->value = clampf(value, minValue, maxValue); | ||||
onChange(); | onChange(); | ||||
@@ -15,3 +17,6 @@ void QuantityWidget::setDefaultValue(float defaultValue) { | |||||
this->defaultValue = defaultValue; | this->defaultValue = defaultValue; | ||||
setValue(defaultValue); | setValue(defaultValue); | ||||
} | } | ||||
} // namespace rack |
@@ -1,7 +1,9 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
namespace rack { | |||||
RackWidget::RackWidget() { | RackWidget::RackWidget() { | ||||
moduleContainer = new TranslucentWidget(); | moduleContainer = new TranslucentWidget(); | ||||
addChild(moduleContainer); | addChild(moduleContainer); | ||||
@@ -10,12 +12,58 @@ RackWidget::RackWidget() { | |||||
addChild(wireContainer); | addChild(wireContainer); | ||||
} | } | ||||
RackWidget::~RackWidget() { | |||||
} | |||||
void RackWidget::clear() { | void RackWidget::clear() { | ||||
activeWire = NULL; | activeWire = NULL; | ||||
wireContainer->clearChildren(); | wireContainer->clearChildren(); | ||||
moduleContainer->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<ModuleWidget*>(w); | |||||
if (m) return m; | |||||
return getAncestorModuleWidget(w->parent); | |||||
} | |||||
json_t *RackWidget::toJson() { | json_t *RackWidget::toJson() { | ||||
// root | // root | ||||
json_t *root = json_object(); | json_t *root = json_object(); | ||||
@@ -48,11 +96,18 @@ json_t *RackWidget::toJson() { | |||||
for (Widget *w : wireContainer->children) { | for (Widget *w : wireContainer->children) { | ||||
WireWidget *wireWidget = dynamic_cast<WireWidget*>(w); | WireWidget *wireWidget = dynamic_cast<WireWidget*>(w); | ||||
assert(wireWidget); | assert(wireWidget); | ||||
// Only serialize WireWidgets connected on both ends | |||||
if (!(wireWidget->outputPort && wireWidget->inputPort)) | |||||
continue; | |||||
// wire | // wire | ||||
json_t *wire = json_object(); | 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, "outputModuleId", json_integer(outputModuleId)); | ||||
json_object_set_new(wire, "outputId", json_integer(wireWidget->outputPort->outputId)); | json_object_set_new(wire, "outputId", json_integer(wireWidget->outputPort->outputId)); | ||||
json_object_set_new(wire, "inputModuleId", json_integer(inputModuleId)); | json_object_set_new(wire, "inputModuleId", json_integer(inputModuleId)); | ||||
@@ -141,7 +196,7 @@ void RackWidget::fromJson(json_t *root) { | |||||
inputPort->connectedWire = wireWidget; | inputPort->connectedWire = wireWidget; | ||||
wireWidget->updateWire(); | wireWidget->updateWire(); | ||||
// Add wire to rack | // 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(); | Widget::step(); | ||||
} | } | ||||
@@ -262,3 +323,6 @@ void RackWidget::onMouseDown(int button) { | |||||
gScene->addChild(overlay); | gScene->addChild(overlay); | ||||
} | } | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
Scene::Scene() { | Scene::Scene() { | ||||
scrollWidget = new ScrollWidget(); | scrollWidget = new ScrollWidget(); | ||||
{ | { | ||||
@@ -20,3 +22,6 @@ void Scene::onResize() { | |||||
scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); | scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); | ||||
scrollWidget->onResize(); | scrollWidget->onResize(); | ||||
} | } | ||||
} // namespace rack |
@@ -1,10 +1,15 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
Screw::Screw() { | Screw::Screw() { | ||||
box.size = Vec(15, 15); | box.size = Vec(15, 15); | ||||
spriteOffset = Vec(-7, -7); | spriteOffset = Vec(-7, -7); | ||||
spriteSize = Vec(29, 29); | spriteSize = Vec(29, 29); | ||||
spriteFilename = "res/screw.png"; | spriteFilename = "res/screw.png"; | ||||
index = rand() % 5; | |||||
index = randomi64() % 5; | |||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
ScrollBar::ScrollBar() { | ScrollBar::ScrollBar() { | ||||
box.size.x = BND_SCROLLBAR_WIDTH; | box.size.x = BND_SCROLLBAR_WIDTH; | ||||
box.size.y = BND_SCROLLBAR_HEIGHT; | box.size.y = BND_SCROLLBAR_HEIGHT; | ||||
@@ -36,3 +38,6 @@ void ScrollBar::onDragEnd() { | |||||
state = BND_DEFAULT; | state = BND_DEFAULT; | ||||
guiCursorUnlock(); | guiCursorUnlock(); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
ScrollWidget::ScrollWidget() { | ScrollWidget::ScrollWidget() { | ||||
container = new Widget(); | container = new Widget(); | ||||
addChild(container); | addChild(container); | ||||
@@ -40,3 +42,6 @@ void ScrollWidget::onScroll(Vec scrollRel) { | |||||
hScrollBar->move(scrollRel.x); | hScrollBar->move(scrollRel.x); | ||||
vScrollBar->move(scrollRel.y); | vScrollBar->move(scrollRel.y); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
#define SLIDER_SENSITIVITY 0.001 | #define SLIDER_SENSITIVITY 0.001 | ||||
Slider::Slider() { | Slider::Slider() { | ||||
@@ -27,3 +29,6 @@ void Slider::onDragEnd() { | |||||
state = BND_DEFAULT; | state = BND_DEFAULT; | ||||
guiCursorUnlock(); | guiCursorUnlock(); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void SpriteWidget::draw(NVGcontext *vg) { | void SpriteWidget::draw(NVGcontext *vg) { | ||||
int imageId = loadImage(spriteFilename); | int imageId = loadImage(spriteFilename); | ||||
if (imageId < 0) { | if (imageId < 0) { | ||||
@@ -24,3 +26,6 @@ void SpriteWidget::draw(NVGcontext *vg) { | |||||
nvgRect(vg, pos.x, pos.y, spriteSize.x, spriteSize.y); | nvgRect(vg, pos.x, pos.y, spriteSize.x, spriteSize.y); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
} // namespace rack |
@@ -1,10 +1,12 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
extern "C" { | extern "C" { | ||||
#include "../lib/noc/noc_file_dialog.h" | #include "../lib/noc/noc_file_dialog.h" | ||||
} | } | ||||
namespace rack { | |||||
static const char *filters = "JSON Patch\0*.json\0"; | static const char *filters = "JSON Patch\0*.json\0"; | ||||
@@ -17,17 +19,8 @@ struct NewItem : MenuItem { | |||||
struct SaveItem : MenuItem { | struct SaveItem : MenuItem { | ||||
void onAction() { | void onAction() { | ||||
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, "Untitled.json"); | const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, "Untitled.json"); | ||||
if (path) { | 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 { | struct OpenItem : MenuItem { | ||||
void onAction() { | void onAction() { | ||||
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL); | const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL); | ||||
if (path) { | 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); | Widget::draw(vg); | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void Tooltip::step() { | void Tooltip::step() { | ||||
// Follow the mouse | // Follow the mouse | ||||
box.pos = gMousePos.minus(parent->getAbsolutePos()); | 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); | bndTooltipBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
} | } | ||||
} // namespace rack |
@@ -1,7 +1,9 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
namespace rack { | |||||
Widget::~Widget() { | Widget::~Widget() { | ||||
// You should only delete orphaned widgets | // You should only delete orphaned widgets | ||||
assert(!parent); | assert(!parent); | ||||
@@ -87,3 +89,6 @@ Widget *Widget::pick(Vec pos) { | |||||
} | } | ||||
return this; | return this; | ||||
} | } | ||||
} // namespace rack |
@@ -1,6 +1,8 @@ | |||||
#include "../5V.hpp" | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color) { | void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color) { | ||||
float dist = pos1.minus(pos2).norm(); | float dist = pos1.minus(pos2).norm(); | ||||
Vec slump = Vec(0, 100.0 + 0.5*dist); | Vec slump = Vec(0, 100.0 + 0.5*dist); | ||||
@@ -72,9 +74,9 @@ void WireWidget::updateWire() { | |||||
} | } | ||||
if (inputPort && outputPort) { | if (inputPort && outputPort) { | ||||
wire = new Wire(); | wire = new Wire(); | ||||
wire->outputModule = outputPort->moduleWidget->module; | |||||
wire->outputModule = outputPort->module; | |||||
wire->outputId = outputPort->outputId; | wire->outputId = outputPort->outputId; | ||||
wire->inputModule = inputPort->moduleWidget->module; | |||||
wire->inputModule = inputPort->module; | |||||
wire->inputId = inputPort->inputId; | wire->inputId = inputPort->inputId; | ||||
rackConnectWire(wire); | rackConnectWire(wire); | ||||
} | } | ||||
@@ -110,3 +112,6 @@ void WireWidget::draw(NVGcontext *vg) { | |||||
} | } | ||||
nvgRestore(vg); | nvgRestore(vg); | ||||
} | } | ||||
} // namespace rack |