Browse Source

A few days of work

tags/v0.3.0
Andrew Belt 8 years ago
parent
commit
86e2fabf20
42 changed files with 767 additions and 431 deletions
  1. +10
    -11
      Makefile
  2. +1
    -7
      README.txt
  3. +56
    -45
      include/Rack.hpp
  4. +33
    -10
      include/util.hpp
  5. +34
    -18
      include/widgets.hpp
  6. +101
    -108
      src/core/AudioInterface.cpp
  7. +68
    -38
      src/core/MidiInterface.cpp
  8. +2
    -0
      src/core/core.cpp
  9. +4
    -4
      src/core/core.hpp
  10. +20
    -5
      src/gui.cpp
  11. +27
    -18
      src/main.cpp
  12. +7
    -1
      src/plugin.cpp
  13. +68
    -58
      src/rack.cpp
  14. +0
    -34
      src/rack.hpp
  15. +77
    -0
      src/util.cpp
  16. +6
    -1
      src/widgets/Button.cpp
  17. +6
    -1
      src/widgets/ChoiceButton.cpp
  18. +6
    -1
      src/widgets/InputPort.cpp
  19. +6
    -1
      src/widgets/Knob.cpp
  20. +6
    -1
      src/widgets/Label.cpp
  21. +10
    -5
      src/widgets/Light.cpp
  22. +6
    -1
      src/widgets/Menu.cpp
  23. +6
    -1
      src/widgets/MenuEntry.cpp
  24. +6
    -1
      src/widgets/MenuItem.cpp
  25. +6
    -1
      src/widgets/MenuLabel.cpp
  26. +6
    -1
      src/widgets/MenuOverlay.cpp
  27. +21
    -1
      src/widgets/ModuleWidget.cpp
  28. +6
    -1
      src/widgets/OutputPort.cpp
  29. +10
    -11
      src/widgets/ParamWidget.cpp
  30. +13
    -2
      src/widgets/Port.cpp
  31. +6
    -1
      src/widgets/QuantityWidget.cpp
  32. +68
    -4
      src/widgets/RackWidget.cpp
  33. +6
    -1
      src/widgets/Scene.cpp
  34. +7
    -2
      src/widgets/Screw.cpp
  35. +6
    -1
      src/widgets/ScrollBar.cpp
  36. +6
    -1
      src/widgets/ScrollWidget.cpp
  37. +6
    -1
      src/widgets/Slider.cpp
  38. +6
    -1
      src/widgets/SpriteWidget.cpp
  39. +8
    -27
      src/widgets/Toolbar.cpp
  40. +6
    -1
      src/widgets/Tooltip.cpp
  41. +6
    -1
      src/widgets/Widget.cpp
  42. +8
    -3
      src/widgets/WireWidget.cpp

+ 10
- 11
Makefile View File

@@ -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

+ 1
- 7
README.txt View File

@@ -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`

src/5V.hpp → include/Rack.hpp View File

@@ -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();

src/util.hpp → include/util.hpp View File

@@ -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

src/widgets.hpp → include/widgets.hpp View File

@@ -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

+ 101
- 108
src/core/AudioInterface.cpp View File

@@ -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) {


+ 68
- 38
src/core/MidiInterface.cpp View File

@@ -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) {


+ 2
- 0
src/core/core.cpp View File

@@ -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");


+ 4
- 4
src/core/core.hpp View File

@@ -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);
}; };

+ 20
- 5
src/gui.cpp View File

@@ -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

+ 27
- 18
src/main.cpp View File

@@ -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
- 1
src/plugin.cpp View File

@@ -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

+ 68
- 58
src/rack.cpp View File

@@ -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

+ 0
- 34
src/rack.hpp View File

@@ -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;
};

+ 77
- 0
src/util.cpp View File

@@ -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

+ 6
- 1
src/widgets/Button.cpp View File

@@ -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

+ 6
- 1
src/widgets/ChoiceButton.cpp View File

@@ -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

+ 6
- 1
src/widgets/InputPort.cpp View File

@@ -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

+ 6
- 1
src/widgets/Knob.cpp View File

@@ -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

+ 6
- 1
src/widgets/Label.cpp View File

@@ -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

+ 10
- 5
src/widgets/Light.cpp View File

@@ -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

+ 6
- 1
src/widgets/Menu.cpp View File

@@ -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

+ 6
- 1
src/widgets/MenuEntry.cpp View File

@@ -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

+ 6
- 1
src/widgets/MenuItem.cpp View File

@@ -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

+ 6
- 1
src/widgets/MenuLabel.cpp View File

@@ -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

+ 6
- 1
src/widgets/MenuOverlay.cpp View File

@@ -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

+ 21
- 1
src/widgets/ModuleWidget.cpp View File

@@ -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

+ 6
- 1
src/widgets/OutputPort.cpp View File

@@ -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

+ 10
- 11
src/widgets/ParamWidget.cpp View File

@@ -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

+ 13
- 2
src/widgets/Port.cpp View File

@@ -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

+ 6
- 1
src/widgets/QuantityWidget.cpp View File

@@ -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

+ 68
- 4
src/widgets/RackWidget.cpp View File

@@ -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

+ 6
- 1
src/widgets/Scene.cpp View File

@@ -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

+ 7
- 2
src/widgets/Screw.cpp View File

@@ -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

+ 6
- 1
src/widgets/ScrollBar.cpp View File

@@ -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

+ 6
- 1
src/widgets/ScrollWidget.cpp View File

@@ -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

+ 6
- 1
src/widgets/Slider.cpp View File

@@ -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

+ 6
- 1
src/widgets/SpriteWidget.cpp View File

@@ -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

+ 8
- 27
src/widgets/Toolbar.cpp View File

@@ -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

+ 6
- 1
src/widgets/Tooltip.cpp View File

@@ -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

+ 6
- 1
src/widgets/Widget.cpp View File

@@ -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

+ 8
- 3
src/widgets/WireWidget.cpp View File

@@ -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

Loading…
Cancel
Save