@@ -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 |
@@ -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` |
@@ -2,11 +2,13 @@ | |||
#include <string> | |||
#include <list> | |||
#include <vector> | |||
#include <memory> | |||
#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<Model*> models; | |||
@@ -44,17 +46,6 @@ extern std::list<Plugin*> 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<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 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 <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(); | |||
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(); |
@@ -1,17 +1,15 @@ | |||
#pragma once | |||
#include <stdint.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) { | |||
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 |
@@ -3,6 +3,7 @@ | |||
#include <assert.h> | |||
#include <stdio.h> | |||
#include <math.h> | |||
#include <vector> | |||
#include <list> | |||
#include <map> | |||
@@ -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 |
@@ -1,11 +1,13 @@ | |||
#include "core.hpp" | |||
#include <assert.h> | |||
#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 { | |||
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<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; | |||
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<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) { | |||
@@ -1,8 +1,13 @@ | |||
#include "core.hpp" | |||
#include <assert.h> | |||
#include <rtmidi/RtMidi.h> | |||
#include <list> | |||
#include <algorithm> | |||
#include <portmidi.h> | |||
#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<int> 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<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() { | |||
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<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) { | |||
@@ -1,6 +1,8 @@ | |||
#include "core.hpp" | |||
using namespace rack; | |||
Plugin *coreInit() { | |||
Plugin *plugin = createPlugin("Core", "Core"); | |||
createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface"); | |||
@@ -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); | |||
}; |
@@ -1,5 +1,5 @@ | |||
#include "5V.hpp" | |||
#include <unistd.h> | |||
#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 |
@@ -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() { | |||
// 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; | |||
} |
@@ -7,10 +7,12 @@ | |||
#include <glob.h> | |||
#endif | |||
#include "5V.hpp" | |||
#include "Rack.hpp" | |||
#include "core/core.hpp" | |||
namespace rack { | |||
std::list<Plugin*> 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 |
@@ -2,35 +2,56 @@ | |||
#include <stdlib.h> | |||
#include <assert.h> | |||
#include <math.h> | |||
#include <list> | |||
#include <set> | |||
#include <chrono> | |||
#include <thread> | |||
#include <mutex> | |||
#include <condition_variable> | |||
#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<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 | |||
static std::set<Wire*> 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<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 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<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() { | |||
frame = 0; | |||
frameLimit = 0; | |||
running = true; | |||
thread = std::thread(rackRun); | |||
} | |||
void rackStop() { | |||
{ | |||
std::unique_lock<std::mutex> lock(mutex); | |||
running = false; | |||
} | |||
cv.notify_all(); | |||
running = false; | |||
thread.join(); | |||
} | |||
void rackAddModule(Module *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 | |||
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<std::mutex> lock(mutex); | |||
RackRequest rm; | |||
std::lock_guard<std::mutex> 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<std::mutex> lock(mutex); | |||
RackRequest rm; | |||
std::lock_guard<std::mutex> 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<std::mutex> lock(mutex); | |||
RackRequest rm; | |||
std::lock_guard<std::mutex> 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<std::mutex> 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 |
@@ -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() { | |||
box.size.y = BND_WIDGET_HEIGHT; | |||
} | |||
@@ -22,3 +24,6 @@ void Button::onDragDrop(Widget *origin) { | |||
onAction(); | |||
} | |||
} | |||
} // namespace rack |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -1,7 +1,9 @@ | |||
#include "../5V.hpp" | |||
#include "Rack.hpp" | |||
#include <algorithm> | |||
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<ModuleWidget*>(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<WireWidget*>(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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -1,7 +1,9 @@ | |||
#include "../5V.hpp" | |||
#include "Rack.hpp" | |||
#include <algorithm> | |||
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 |
@@ -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 |