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

+ 1
- 7
README.txt View File

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

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

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

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

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

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

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

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

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


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

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


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

@@ -1,6 +1,8 @@
#include "core.hpp"


using namespace rack;

Plugin *coreInit() {
Plugin *plugin = createPlugin("Core", "Core");
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
////////////////////

struct AudioInterfaceWidget : ModuleWidget {
struct AudioInterfaceWidget : rack::ModuleWidget {
AudioInterfaceWidget();
void draw(NVGcontext *vg);
};

struct MidiInterfaceWidget : ModuleWidget {
struct MidiInterfaceWidget : rack::ModuleWidget {
MidiInterfaceWidget();
void draw(NVGcontext *vg);
};

+ 20
- 5
src/gui.cpp View File

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

+ 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() {
// 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
- 1
src/plugin.cpp View File

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

+ 68
- 58
src/rack.cpp View File

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

+ 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() {
box.size.y = BND_WIDGET_HEIGHT;
}
@@ -22,3 +24,6 @@ void Button::onDragDrop(Widget *origin) {
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) {
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) {
Port::draw(vg);
if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) {
@@ -32,3 +34,6 @@ void InputPort::onDragDrop(Widget *origin) {
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


@@ -19,3 +21,6 @@ void Knob::onDragMove(Vec mouseRel) {
void Knob::onDragEnd() {
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) {
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) {
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

+ 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) {
child->box.pos = Vec(0, box.size.y);
addChild(child);
@@ -25,3 +27,6 @@ void Menu::draw(NVGcontext *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) {
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) {
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

+ 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) {
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) {
if (parent) {
parent->removeChild(this);
@@ -8,3 +10,6 @@ void MenuOverlay::onMouseDown(int button) {
// Commit sudoku
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) {
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

+ 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) {
Port::draw(vg);
if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) {
@@ -32,3 +34,6 @@ void OutputPort::onDragDrop(Widget *origin) {
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;
}

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

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

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

+ 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) {
this->value = clampf(value, minValue, maxValue);
onChange();
@@ -15,3 +17,6 @@ void QuantityWidget::setDefaultValue(float defaultValue) {
this->defaultValue = 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>


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

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

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

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

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

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

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

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

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

+ 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

Slider::Slider() {
@@ -27,3 +29,6 @@ void Slider::onDragEnd() {
state = BND_DEFAULT;
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) {
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

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

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

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

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

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

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

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

Loading…
Cancel
Save