Browse Source

Large refactor to modularize include files, add NanoSVG dependency, added Image/Font/SVG loader

tags/v0.3.0
Andrew Belt 7 years ago
parent
commit
aad709e62c
46 changed files with 539 additions and 425 deletions
  1. +51
    -0
      include/engine.hpp
  2. +17
    -0
      include/gui.hpp
  3. +51
    -0
      include/plugin.hpp
  4. +4
    -141
      include/rack.hpp
  5. +26
    -12
      include/scene.hpp
  6. +6
    -0
      include/util.hpp
  7. +51
    -6
      include/widgets.hpp
  8. +2
    -2
      src/core/AudioInterface.cpp
  9. +13
    -5
      src/core/core.cpp
  10. +0
    -2
      src/core/core.hpp
  11. +104
    -103
      src/engine.cpp
  12. +75
    -61
      src/gui.cpp
  13. +6
    -14
      src/main.cpp
  14. +4
    -3
      src/plugin.cpp
  15. +22
    -0
      src/scene.cpp
  16. +13
    -0
      src/widgets.cpp
  17. +1
    -1
      src/widgets/Button.cpp
  18. +1
    -1
      src/widgets/ChoiceButton.cpp
  19. +1
    -1
      src/widgets/InputPort.cpp
  20. +2
    -1
      src/widgets/Knob.cpp
  21. +1
    -1
      src/widgets/Label.cpp
  22. +1
    -1
      src/widgets/Light.cpp
  23. +1
    -1
      src/widgets/Menu.cpp
  24. +1
    -1
      src/widgets/MenuEntry.cpp
  25. +1
    -1
      src/widgets/MenuItem.cpp
  26. +1
    -1
      src/widgets/MenuLabel.cpp
  27. +1
    -2
      src/widgets/MenuOverlay.cpp
  28. +4
    -5
      src/widgets/ModulePanel.cpp
  29. +6
    -4
      src/widgets/ModuleWidget.cpp
  30. +1
    -1
      src/widgets/OutputPort.cpp
  31. +3
    -2
      src/widgets/ParamWidget.cpp
  32. +1
    -1
      src/widgets/Port.cpp
  33. +1
    -1
      src/widgets/QuantityWidget.cpp
  34. +28
    -0
      src/widgets/RackScene.cpp
  35. +13
    -8
      src/widgets/RackWidget.cpp
  36. +1
    -1
      src/widgets/RadioButton.cpp
  37. +1
    -17
      src/widgets/Scene.cpp
  38. +2
    -2
      src/widgets/Screw.cpp
  39. +2
    -1
      src/widgets/ScrollBar.cpp
  40. +1
    -1
      src/widgets/ScrollWidget.cpp
  41. +2
    -1
      src/widgets/Slider.cpp
  42. +3
    -9
      src/widgets/SpriteWidget.cpp
  43. +5
    -3
      src/widgets/Toolbar.cpp
  44. +1
    -1
      src/widgets/Tooltip.cpp
  45. +1
    -1
      src/widgets/Widget.cpp
  46. +6
    -5
      src/widgets/WireWidget.cpp

+ 51
- 0
include/engine.hpp View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>


namespace rack {


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;
/** For CPU usage */
float cpuTime = 0.0;

virtual ~Module() {}

/** Advances the module by 1 audio frame with duration 1.0 / gSampleRate */
virtual void step() {}
};

struct Wire {
Module *outputModule = NULL;
int outputId;
Module *inputModule = NULL;
int inputId;
/** The voltage connected to input ports */
float inputValue = 0.0;
/** The voltage connected to output ports */
float outputValue = 0.0;
};

void engineInit();
void engineDestroy();
/** Launches engine thread */
void engineStart();
void engineStop();
/** Does not transfer pointer ownership */
void engineAddModule(Module *module);
void engineRemoveModule(Module *module);
/** Does not transfer pointer ownership */
void engineAddWire(Wire *wire);
void engineRemoveWire(Wire *wire);
void engineSetParamSmooth(Module *module, int paramId, float value);

extern float gSampleRate;


} // namespace rack

+ 17
- 0
include/gui.hpp View File

@@ -0,0 +1,17 @@
#pragma once
#include "scene.hpp"


namespace rack {


void guiInit();
void guiDestroy();
void guiRun();
void guiCursorLock();
void guiCursorUnlock();
const char *guiSaveDialog(const char *filters, const char *filename);
const char *guiOpenDialog(const char *filters, const char *filename);


} // namespace rack

+ 51
- 0
include/plugin.hpp View File

@@ -0,0 +1,51 @@
#pragma once
#include <string>
#include <list>


namespace rack {


struct ModuleWidget;
struct Model;

// Subclass this and return a pointer to a new one when init() is called
struct Plugin {
virtual ~Plugin();

// A unique identifier for your plugin, e.g. "foo"
std::string slug;
// 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;
};

struct Model {
virtual ~Model() {}

Plugin *plugin;
// A unique identifier for the model in this plugin, e.g. "vco"
std::string slug;
// Human readable name for your model, e.g. "VCO"
std::string name;
virtual ModuleWidget *createModuleWidget() { return NULL; }
};

extern std::list<Plugin*> gPlugins;

void pluginInit();
void pluginDestroy();

} // namespace rack


////////////////////
// Implemented by plugin
////////////////////

/** Called once to initialize and return Plugin.
Plugin is destructed when Rack closes
*/
extern "C"
rack::Plugin *init();

+ 4
- 141
include/rack.hpp View File

@@ -1,130 +1,14 @@
#pragma once

#include <string>
#include <list>
#include <vector>
#include <memory>
#include <set>
#include <thread>
#include <mutex>
#include "rackwidgets.hpp"
#include "plugin.hpp"
#include "engine.hpp"
#include "gui.hpp"


namespace rack {

////////////////////
// Plugin manager
////////////////////

struct Model;

// Subclass this and return a pointer to a new one when init() is called
struct Plugin {
virtual ~Plugin();

// A unique identifier for your plugin, e.g. "foo"
std::string slug;
// 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;
};

struct Model {
virtual ~Model() {}

Plugin *plugin;
// A unique identifier for the model in this plugin, e.g. "vco"
std::string slug;
// Human readable name for your model, e.g. "VCO"
std::string name;
virtual ModuleWidget *createModuleWidget() { return NULL; }
};

extern std::list<Plugin*> gPlugins;

void pluginInit();
void pluginDestroy();

////////////////////
// gui.cpp
////////////////////

extern Vec gMousePos;
extern Widget *gHoveredWidget;
extern Widget *gDraggedWidget;
extern Widget *gSelectedWidget;
extern int gGuiFrame;

void guiInit();
void guiDestroy();
void guiRun();
void guiCursorLock();
void guiCursorUnlock();
const char *guiSaveDialog(const char *filters, const char *filename);
const char *guiOpenDialog(const char *filters, const char *filename);

int loadFont(std::string filename);
int loadImage(std::string filename);
void drawImage(NVGcontext *vg, Vec pos, int imageId);

////////////////////
// rack.cpp
////////////////////

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;
/** For CPU usage */
float cpuTime = 0.0;

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 connected to input ports */
float inputValue = 0.0;
/** The voltage connected to output ports */
float outputValue = 0.0;
};

struct Rack {
Rack();
~Rack();
/** Launches rack thread */
void start();
void stop();
void run();
void step();
/** Does not transfer pointer ownership */
void addModule(Module *module);
void removeModule(Module *module);
/** Does not transfer pointer ownership */
void addWire(Wire *wire);
void removeWire(Wire *wire);
void setParamSmooth(Module *module, int paramId, float value);

float sampleRate;

struct Impl;
Impl *impl;
};

////////////////////
// Optional helpers for plugins
// helpers
////////////////////

inline
@@ -191,26 +75,5 @@ Screw *createScrew(Vec pos) {
return screw;
}

////////////////////
// Globals
////////////////////

extern std::string gApplicationName;
extern std::string gApplicationVersion;

extern Scene *gScene;
extern RackWidget *gRackWidget;

extern Rack *gRack;

} // namespace rack


////////////////////
// Implemented by plugin
////////////////////

// Called once to initialize and return Plugin.
// Plugin is destructed when Rack closes
extern "C"
rack::Plugin *init();

include/rackwidgets.hpp → include/scene.hpp View File

@@ -1,7 +1,7 @@
#pragma once

#include "widgets.hpp"
#include <vector>
#include <jansson.h>
#include "widgets.hpp"


namespace rack {
@@ -14,7 +14,7 @@ struct RackWidget;
struct ParamWidget;
struct InputPort;
struct OutputPort;
struct Scene;

////////////////////
// module
@@ -76,6 +76,7 @@ struct RackWidget : OpaqueWidget {
// Only put WireWidgets in here
Widget *wireContainer;
WireWidget *activeWire = NULL;
std::shared_ptr<Image> railsImage;

RackWidget();
~RackWidget();
@@ -94,7 +95,7 @@ struct RackWidget : OpaqueWidget {

struct ModulePanel : TransparentWidget {
NVGcolor backgroundColor;
std::string imageFilename;
std::shared_ptr<Image> backgroundImage;
void draw(NVGcontext *vg);
};

@@ -205,18 +206,17 @@ struct Toolbar : OpaqueWidget {
void draw(NVGcontext *vg);
};

struct Scene : OpaqueWidget {
struct RackScene : Scene {
Toolbar *toolbar;
ScrollWidget *scrollWidget;
Widget *overlay = NULL;

Scene();
void setOverlay(Widget *w);
RackScene();
void step();
};


////////////////////
// component library
// Component Library
////////////////////

struct PJ301M : SpriteWidget {
@@ -224,7 +224,7 @@ struct PJ301M : SpriteWidget {
box.size = Vec(24, 24);
spriteOffset = Vec(-10, -10);
spriteSize = Vec(48, 48);
spriteFilename = "res/ComponentLibrary/PJ301M.png";
spriteImage = Image::load("res/ComponentLibrary/PJ301M.png");
}
};
struct InputPortPJ301M : InputPort, PJ301M {};
@@ -235,7 +235,7 @@ struct PJ3410 : SpriteWidget {
box.size = Vec(31, 31);
spriteOffset = Vec(-9, -9);
spriteSize = Vec(54, 54);
spriteFilename = "res/ComponentLibrary/PJ3410.png";
spriteImage = Image::load("res/ComponentLibrary/PJ3410.png");
}
};
struct InputPortPJ3410 : InputPort, PJ3410 {};
@@ -246,10 +246,24 @@ struct CL1362 : SpriteWidget {
box.size = Vec(33, 29);
spriteOffset = Vec(-10, -10);
spriteSize = Vec(57, 54);
spriteFilename = "res/ComponentLibrary/CL1362.png";
spriteImage = Image::load("res/ComponentLibrary/CL1362.png");
}
};
struct InputPortCL1362 : InputPort, CL1362 {};
struct OutputPortCL1362 : OutputPort, CL1362 {};


////////////////////
// globals
////////////////////

extern std::string gApplicationName;
extern std::string gApplicationVersion;

extern Scene *gScene;
extern RackWidget *gRackWidget;

void sceneInit();
void sceneDestroy();

} // namespace rack

+ 6
- 0
include/util.hpp View File

@@ -1,6 +1,12 @@
#pragma once

// Include most of the C standard library for convenience
// (C++ programmers will hate me)
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <assert.h>

#include <string>




+ 51
- 6
include/widgets.hpp View File

@@ -1,13 +1,10 @@
#pragma once

#include <assert.h>
#include <stdio.h>
#include <math.h>
#include <list>
#include <map>
#include <memory>

#include "../ext/nanovg/src/nanovg.h"
#include "../ext/oui/blendish.h"
#include "../ext/nanosvg/src/nanosvg.h"

#include "math.hpp"
#include "util.hpp"
@@ -16,6 +13,35 @@
namespace rack {


////////////////////
// resources
////////////////////

// Constructing these directly will load from the disk each time. Use the load() functions to load from disk and cache them as long as the shared_ptr is held.
// Implemented in gui.cpp

struct Font {
int handle;
Font(const std::string &filename);
~Font();
static std::shared_ptr<Font> load(const std::string &filename);
};

struct Image {
int handle;
Image(const std::string &filename);
~Image();
static std::shared_ptr<Image> load(const std::string &filename);
};

struct SVG {
NSVGimage *image;
SVG(const std::string &filename);
~SVG();
static std::shared_ptr<SVG> load(const std::string &filename);
};


////////////////////
// base class and traits
////////////////////
@@ -122,7 +148,7 @@ struct OpaqueWidget : virtual Widget {
struct SpriteWidget : virtual Widget {
Vec spriteOffset;
Vec spriteSize;
std::string spriteFilename;
std::shared_ptr<Image> spriteImage;
int index = 0;
void draw(NVGcontext *vg);
};
@@ -276,5 +302,24 @@ struct Tooltip : Widget {
void draw(NVGcontext *vg);
};

struct Scene : OpaqueWidget {
Widget *overlay = NULL;
void setOverlay(Widget *w);
void step();
};


////////////////////
// globals
////////////////////

extern Vec gMousePos;
extern Widget *gHoveredWidget;
extern Widget *gDraggedWidget;
extern Widget *gSelectedWidget;
extern int gGuiFrame;

extern Scene *gScene;


} // namespace rack

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

@@ -99,7 +99,7 @@ void AudioInterface::step() {

// Once full, sample rate convert the input
if (inputBuffer.full()) {
inputSrc.setRatio(sampleRate / gRack->sampleRate);
inputSrc.setRatio(sampleRate / gSampleRate);
int inLen = inputBuffer.size();
int outLen = inputSrcBuffer.capacity();
inputSrc.process((const float*) inputBuffer.startData(), &inLen, (float*) inputSrcBuffer.endData(), &outLen);
@@ -127,7 +127,7 @@ void AudioInterface::step() {
}

// Pass output through sample rate converter
outputSrc.setRatio(gRack->sampleRate / sampleRate);
outputSrc.setRatio(gSampleRate / sampleRate);
int inLen = blockSize;
int outLen = outputBuffer.capacity();
outputSrc.process((float*) buf, &inLen, (float*) outputBuffer.endData(), &outLen);


+ 13
- 5
src/core/core.cpp View File

@@ -3,12 +3,20 @@

using namespace rack;

Plugin *coreInit() {

struct CorePlugin : Plugin {
CorePlugin() {
slug = "Core";
name = "Core";
createModel<AudioInterfaceWidget>(this, "AudioInterface", "Audio Interface");
createModel<MidiInterfaceWidget>(this, "MidiInterface", "MIDI Interface");
}
};


Plugin *init() {
audioInit();
midiInit();

Plugin *plugin = createPlugin("Core", "Core");
createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface");
createModel<MidiInterfaceWidget>(plugin, "MidiInterface", "MIDI Interface");
return plugin;
return new CorePlugin();
}

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

@@ -3,8 +3,6 @@

using namespace rack;

Plugin *coreInit();

void audioInit();
void midiInit();



src/Rack.cpp → src/engine.cpp View File

@@ -2,15 +2,20 @@
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <set>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <xmmintrin.h>

#include "rack.hpp"
#include "engine.hpp"


namespace rack {

float gSampleRate;

/** Threads which obtain a VIPLock will cause wait() to block for other less important threads.
This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread.
*/
@@ -42,90 +47,51 @@ struct VIPLock {
};


struct Rack::Impl {
bool running = false;
static bool running = false;

std::mutex mutex;
std::thread audioThread;
VIPMutex vipMutex;
static std::mutex mutex;
static std::thread thread;
static VIPMutex vipMutex;

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 more rigorous
std::set<Wire*> wires;
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 more rigorous
static std::set<Wire*> wires;

// Parameter interpolation
Module *smoothModule = NULL;
int smoothParamId;
float smoothValue;
};
// Parameter interpolation
static Module *smoothModule = NULL;
static int smoothParamId;
static float smoothValue;


Rack::Rack() {
impl = new Rack::Impl();
sampleRate = 44100.0;
void engineInit() {
gSampleRate = 44100.0;
}

Rack::~Rack() {
void engineDestroy() {
// 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(impl->wires.empty());
assert(impl->modules.empty());
delete impl;
}

void Rack::start() {
impl->running = true;
impl->audioThread = std::thread(&Rack::run, this);
}

void Rack::stop() {
impl->running = false;
impl->audioThread.join();
}

void Rack::run() {
// Set CPU to denormals-are-zero mode
// http://carlh.net/plugins/denormals.php
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

// Every time the rack waits and locks a mutex, it steps this many frames
const int stepSize = 32;

while (impl->running) {
impl->vipMutex.wait();

auto start = std::chrono::high_resolution_clock::now();
{
std::lock_guard<std::mutex> lock(impl->mutex);
for (int i = 0; i < stepSize; i++) {
step();
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / sampleRate)) - (end - start);
// Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate
std::this_thread::sleep_for(duration);
}
assert(wires.empty());
assert(modules.empty());
}

void Rack::step() {
static void engineStep() {
// Param interpolation
if (impl->smoothModule) {
float value = impl->smoothModule->params[impl->smoothParamId];
if (smoothModule) {
float value = smoothModule->params[smoothParamId];
const float lambda = 60.0; // decay rate is 1 graphics frame
const float snap = 0.0001;
float delta = impl->smoothValue - value;
float delta = smoothValue - value;
if (fabsf(delta) < snap) {
impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue;
impl->smoothModule = NULL;
smoothModule->params[smoothParamId] = smoothValue;
smoothModule = NULL;
}
else {
value += delta * lambda / sampleRate;
impl->smoothModule->params[impl->smoothParamId] = value;
value += delta * lambda / gSampleRate;
smoothModule->params[smoothParamId] = value;
}
}
// Step all modules
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
for (Module *module : impl->modules) {
for (Module *module : modules) {
// Start clock for CPU usage
start = std::chrono::high_resolution_clock::now();
// Step module by one frame
@@ -133,91 +99,126 @@ void Rack::step() {
// Stop clock and smooth step time value
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> diff = end - start;
float elapsed = diff.count() * sampleRate;
float elapsed = diff.count() * gSampleRate;
const float lambda = 1.0;
module->cpuTime += (elapsed - module->cpuTime) * lambda / sampleRate;
module->cpuTime += (elapsed - module->cpuTime) * lambda / gSampleRate;
}
// Step cables by moving their output values to inputs
for (Wire *wire : impl->wires) {
for (Wire *wire : wires) {
wire->inputValue = wire->outputValue;
wire->outputValue = 0.0;
}
}

void Rack::addModule(Module *module) {
static void engineRun() {
// Set CPU to denormals-are-zero mode
// http://carlh.net/plugins/denormals.php
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

// Every time the engine waits and locks a mutex, it steps this many frames
const int stepSize = 32;

while (running) {
vipMutex.wait();

auto start = std::chrono::high_resolution_clock::now();
{
std::lock_guard<std::mutex> lock(mutex);
for (int i = 0; i < stepSize; i++) {
engineStep();
}
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::nanoseconds((long) (0.9 * 1e9 * stepSize / gSampleRate)) - (end - start);
// Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate
std::this_thread::sleep_for(duration);
}
}

void engineStart() {
running = true;
thread = std::thread(engineRun);
}

void engineStop() {
running = false;
thread.join();
}

void engineAddModule(Module *module) {
assert(module);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
VIPLock vipLock(vipMutex);
std::lock_guard<std::mutex> lock(mutex);
// Check that the module is not already added
assert(impl->modules.find(module) == impl->modules.end());
impl->modules.insert(module);
assert(modules.find(module) == modules.end());
modules.insert(module);
}

void Rack::removeModule(Module *module) {
void engineRemoveModule(Module *module) {
assert(module);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
VIPLock vipLock(vipMutex);
std::lock_guard<std::mutex> lock(mutex);
// Remove parameter interpolation which point to this module
if (module == impl->smoothModule) {
impl->smoothModule = NULL;
if (module == smoothModule) {
smoothModule = NULL;
}
// Check that all wires are disconnected
for (Wire *wire : impl->wires) {
for (Wire *wire : wires) {
assert(wire->outputModule != module);
assert(wire->inputModule != module);
}
auto it = impl->modules.find(module);
if (it != impl->modules.end()) {
impl->modules.erase(it);
auto it = modules.find(module);
if (it != modules.end()) {
modules.erase(it);
}
}

void Rack::addWire(Wire *wire) {
void engineAddWire(Wire *wire) {
assert(wire);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
VIPLock vipLock(vipMutex);
std::lock_guard<std::mutex> lock(mutex);
// Check that the wire is not already added
assert(impl->wires.find(wire) == impl->wires.end());
assert(wires.find(wire) == wires.end());
assert(wire->outputModule);
assert(wire->inputModule);
// Check that the inputs/outputs are not already used by another cable
for (Wire *wire2 : impl->wires) {
for (Wire *wire2 : wires) {
assert(wire2 != wire);
assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId));
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId));
}
// Connect the wire to inputModule
impl->wires.insert(wire);
wires.insert(wire);
wire->inputModule->inputs[wire->inputId] = &wire->inputValue;
wire->outputModule->outputs[wire->outputId] = &wire->outputValue;
}

void Rack::removeWire(Wire *wire) {
void engineRemoveWire(Wire *wire) {
assert(wire);
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
VIPLock vipLock(vipMutex);
std::lock_guard<std::mutex> lock(mutex);
// Disconnect wire from inputModule
wire->inputModule->inputs[wire->inputId] = NULL;
wire->outputModule->outputs[wire->outputId] = NULL;

auto it = impl->wires.find(wire);
assert(it != impl->wires.end());
impl->wires.erase(it);
auto it = wires.find(wire);
assert(it != wires.end());
wires.erase(it);
}

void Rack::setParamSmooth(Module *module, int paramId, float value) {
VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex);
void engineSetParamSmooth(Module *module, int paramId, float value) {
VIPLock vipLock(vipMutex);
std::lock_guard<std::mutex> lock(mutex);
// Check existing parameter interpolation
if (impl->smoothModule) {
if (!(impl->smoothModule == module && impl->smoothParamId == paramId)) {
if (smoothModule) {
if (!(smoothModule == module && smoothParamId == paramId)) {
// Jump param value to smooth value
impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue;
smoothModule->params[smoothParamId] = smoothValue;
}
}
impl->smoothModule = module;
impl->smoothParamId = paramId;
impl->smoothValue = value;
smoothModule = module;
smoothParamId = paramId;
smoothValue = value;
}



+ 75
- 61
src/gui.cpp View File

@@ -1,16 +1,19 @@
#include <unistd.h>
#include "rack.hpp"
#include <map>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

// #define NANOVG_GLEW
#define NANOVG_IMPLEMENTATION
#include "../ext/nanovg/src/nanovg.h"
#include "gui.hpp"
#include "scene.hpp"

// Include implementations here
// By the way, please stop packaging your libraries like this. It's easiest to use a single source file (e.g. foo.c) and a single header (e.g. foo.h)
#define NANOVG_GL2_IMPLEMENTATION
#include "../ext/nanovg/src/nanovg_gl.h"
#define BLENDISH_IMPLEMENTATION
#include "../ext/oui/blendish.h"
#define NANOSVG_IMPLEMENTATION
#include "../ext/nanosvg/src/nanosvg.h"

extern "C" {
#include "../ext/noc/noc_file_dialog.h"
@@ -19,18 +22,9 @@ extern "C" {

namespace rack {

Scene *gScene = NULL;
RackWidget *gRackWidget = NULL;

Vec gMousePos;
Widget *gHoveredWidget = NULL;
Widget *gDraggedWidget = NULL;
Widget *gSelectedWidget = NULL;

int gGuiFrame;

static GLFWwindow *window = NULL;
static NVGcontext *vg = NULL;
static std::shared_ptr<Font> defaultFont;


void windowSizeCallback(GLFWwindow* window, int width, int height) {
@@ -178,7 +172,7 @@ void guiInit() {

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
window = glfwCreateWindow(1020, 700, gApplicationName.c_str(), NULL, NULL);
window = glfwCreateWindow(1000, 750, gApplicationName.c_str(), NULL, NULL);
assert(window);
glfwMakeContextCurrent(window);

@@ -206,15 +200,13 @@ void guiInit() {
assert(vg);

// Set up Blendish
bndSetFont(loadFont("res/DejaVuSans.ttf"));
defaultFont = Font::load("res/DejaVuSans.ttf");
bndSetFont(defaultFont->handle);
// bndSetIconImage(loadImage("res/icons.png"));

gScene = new Scene();
}

void guiDestroy() {
delete gScene;

defaultFont.reset();
nvgDeleteGL2(vg);
glfwDestroyWindow(window);
glfwTerminate();
@@ -264,61 +256,83 @@ const char *guiOpenDialog(const char *filters, const char *filename) {
}


////////////////////
// resources
////////////////////

Font::Font(const std::string &filename) {
handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
if (handle >= 0) {
fprintf(stderr, "Loaded font %s\n", filename.c_str());
}
else {
fprintf(stderr, "Failed to load font %s\n", filename.c_str());
}
}

Font::~Font() {
// There is no NanoVG deleteFont() function, so do nothing
}

std::map<std::string, int> images;
std::map<std::string, int> fonts;
std::shared_ptr<Font> Font::load(const std::string &filename) {
static std::map<std::string, std::weak_ptr<Font>> cache;
auto sp = cache[filename].lock();
if (!sp)
cache[filename] = sp = std::make_shared<Font>(filename);
return sp;
}

int loadImage(std::string filename) {
assert(vg);
int imageId;
auto it = images.find(filename);
if (it == images.end()) {
// Load image
imageId = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY | NVG_IMAGE_NEAREST);
if (imageId == 0) {
printf("Failed to load image %s\n", filename.c_str());
}
else {
printf("Loaded image %s\n", filename.c_str());
}
images[filename] = imageId;
////////////////////
// Image
////////////////////

Image::Image(const std::string &filename) {
handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
if (handle > 0) {
fprintf(stderr, "Loaded image %s\n", filename.c_str());
}
else {
imageId = it->second;
fprintf(stderr, "Failed to load image %s\n", filename.c_str());
}
return imageId;
}

int loadFont(std::string filename) {
assert(vg);
int fontId;
auto it = fonts.find(filename);
if (it == fonts.end()) {
fontId = nvgCreateFont(vg, filename.c_str(), filename.c_str());
if (fontId < 0) {
printf("Failed to load font %s\n", filename.c_str());
}
else {
printf("Loaded font %s\n", filename.c_str());
}
fonts[filename] = fontId;
Image::~Image() {
// TODO What if handle is invalid?
nvgDeleteImage(vg, handle);
}

std::shared_ptr<Image> Image::load(const std::string &filename) {
static std::map<std::string, std::weak_ptr<Image>> cache;
auto sp = cache[filename].lock();
if (!sp)
cache[filename] = sp = std::make_shared<Image>(filename);
return sp;
}

////////////////////
// SVG
////////////////////

SVG::SVG(const std::string &filename) {
image = nsvgParseFromFile(filename.c_str(), "px", 96.0);
if (image) {
fprintf(stderr, "Loaded SVG %s\n", filename.c_str());
}
else {
fontId = it->second;
fprintf(stderr, "Failed to load SVG %s\n", filename.c_str());
}
return fontId;
}

SVG::~SVG() {
nsvgDelete(image);
}

void drawImage(NVGcontext *vg, Vec pos, int imageId) {
int width, height;
nvgImageSize(vg, imageId, &width, &height);
NVGpaint paint = nvgImagePattern(vg, pos.x, pos.y, width, height, 0, imageId, 1.0);
nvgFillPaint(vg, paint);
nvgRect(vg, pos.x, pos.y, width, height);
nvgFill(vg);
std::shared_ptr<SVG> SVG::load(const std::string &filename) {
static std::map<std::string, std::weak_ptr<SVG>> cache;
auto sp = cache[filename].lock();
if (!sp)
cache[filename] = sp = std::make_shared<SVG>(filename);
return sp;
}




+ 6
- 14
src/main.cpp View File

@@ -7,16 +7,6 @@
#include "rack.hpp"


namespace rack {

std::string gApplicationName = "VCV Rack";
std::string gApplicationVersion = "v0.1.0 alpha";

Rack *gRack;

} // namespace rack


using namespace rack;

int main() {
@@ -34,18 +24,20 @@ int main() {
}
#endif

gRack = new Rack();
engineInit();
guiInit();
sceneInit();
pluginInit();
gRackWidget->loadPatch("autosave.json");

gRack->start();
engineStart();
guiRun();
gRack->stop();
engineStop();

gRackWidget->savePatch("autosave.json");
sceneDestroy();
guiDestroy();
delete gRack;
engineDestroy();
pluginDestroy();
return 0;
}


+ 4
- 3
src/plugin.cpp View File

@@ -7,8 +7,7 @@
#include <glob.h>
#endif

#include "rack.hpp"
#include "core/core.hpp"
#include "plugin.hpp"


namespace rack {
@@ -60,8 +59,10 @@ int loadPlugin(const char *path) {

void pluginInit() {
// Load core
Plugin *corePlugin = coreInit();
// This function is defined in core.cpp
Plugin *corePlugin = init();
gPlugins.push_back(corePlugin);

// Search for plugin libraries
#if defined(WINDOWS)
WIN32_FIND_DATA ffd;


+ 22
- 0
src/scene.cpp View File

@@ -0,0 +1,22 @@
#include "scene.hpp"


namespace rack {

std::string gApplicationName = "VCV Rack";
std::string gApplicationVersion = "v0.1.0 alpha";

RackWidget *gRackWidget = NULL;


void sceneInit() {
gScene = new RackScene();
}

void sceneDestroy() {
delete gScene;
gScene = NULL;
}


} // namespace rack

+ 13
- 0
src/widgets.cpp View File

@@ -0,0 +1,13 @@
#include "widgets.hpp"

namespace rack {

Vec gMousePos;
Widget *gHoveredWidget = NULL;
Widget *gDraggedWidget = NULL;
Widget *gSelectedWidget = NULL;
int gGuiFrame;

Scene *gScene = NULL;

} // namespace rack

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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "scene.hpp"


namespace rack {


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

@@ -1,4 +1,5 @@
#include "rack.hpp"
#include "scene.hpp"
#include "gui.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


+ 1
- 1
src/widgets/Light.cpp View File

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "scene.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,5 +1,4 @@
#include "rack.hpp"

#include "widgets.hpp"

namespace rack {



+ 4
- 5
src/widgets/ModulePanel.cpp View File

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "scene.hpp"


namespace rack {
@@ -13,11 +13,10 @@ void ModulePanel::draw(NVGcontext *vg) {
nvgFill(vg);

// Background image
if (!imageFilename.empty()) {
int imageId = loadImage(imageFilename);
if (backgroundImage) {
int width, height;
nvgImageSize(vg, imageId, &width, &height);
paint = nvgImagePattern(vg, box.pos.x, box.pos.y, width, height, 0.0, imageId, 1.0);
nvgImageSize(vg, backgroundImage->handle, &width, &height);
paint = nvgImagePattern(vg, box.pos.x, box.pos.y, width, height, 0.0, backgroundImage->handle, 1.0);
nvgFillPaint(vg, paint);
nvgFill(vg);
}


+ 6
- 4
src/widgets/ModuleWidget.cpp View File

@@ -1,4 +1,6 @@
#include "rack.hpp"
#include "scene.hpp"
#include "engine.hpp"
#include "plugin.hpp"


namespace rack {
@@ -6,7 +8,7 @@ namespace rack {
ModuleWidget::ModuleWidget(Module *module) {
this->module = module;
if (module) {
gRack->addModule(module);
engineAddModule(module);
}
}

@@ -14,7 +16,7 @@ ModuleWidget::~ModuleWidget() {
// Make sure WireWidget destructors are called *before* removing `module` from the rack.
disconnectPorts();
if (module) {
gRack->removeModule(module);
engineRemoveModule(module);
delete module;
}
}
@@ -100,7 +102,7 @@ void ModuleWidget::draw(NVGcontext *vg) {
bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y);

// CPU usage text
if (gScene->toolbar->cpuUsageButton->value != 0.0) {
if (dynamic_cast<RackScene*>(gScene)->toolbar->cpuUsageButton->value != 0.0) {
float cpuTime = module ? module->cpuTime : 0.0;
std::string text = stringf("%.1f%%", cpuTime * 100.0);



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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "scene.hpp"


namespace rack {


+ 3
- 2
src/widgets/ParamWidget.cpp View File

@@ -1,4 +1,5 @@
#include "rack.hpp"
#include "scene.hpp"
#include "engine.hpp"


namespace rack {
@@ -23,7 +24,7 @@ void ParamWidget::onChange() {
return;

// moduleWidget->module->params[paramId] = value;
gRack->setParamSmooth(module, paramId, value);
engineSetParamSmooth(module, paramId, value);
}




+ 1
- 1
src/widgets/Port.cpp View File

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "scene.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


+ 28
- 0
src/widgets/RackScene.cpp View File

@@ -0,0 +1,28 @@
#include "scene.hpp"


namespace rack {

RackScene::RackScene() {
scrollWidget = new ScrollWidget();
{
assert(!gRackWidget);
gRackWidget = new RackWidget();
scrollWidget->container->addChild(gRackWidget);
}
addChild(scrollWidget);

toolbar = new Toolbar();
addChild(toolbar);
scrollWidget->box.pos.y = toolbar->box.size.y;
}

void RackScene::step() {
toolbar->box.size.x = box.size.x;
scrollWidget->box.size = box.size.minus(scrollWidget->box.pos);

Scene::step();
}


} // namespace rack

+ 13
- 8
src/widgets/RackWidget.cpp View File

@@ -1,5 +1,9 @@
#include "rack.hpp"
#include <map>
#include <algorithm>
#include "scene.hpp"
#include "engine.hpp"
#include "plugin.hpp"
#include "gui.hpp"


namespace rack {
@@ -10,6 +14,8 @@ RackWidget::RackWidget() {

wireContainer = new TransparentWidget();
addChild(wireContainer);

railsImage = Image::load("res/rails.png");
}

RackWidget::~RackWidget() {
@@ -65,11 +71,11 @@ json_t *RackWidget::toJson() {
json_object_set_new(root, "version", versionJ);

// wireOpacity
json_t *wireOpacityJ = json_real(gScene->toolbar->wireOpacitySlider->value);
json_t *wireOpacityJ = json_real(dynamic_cast<RackScene*>(gScene)->toolbar->wireOpacitySlider->value);
json_object_set_new(root, "wireOpacity", wireOpacityJ);

// wireTension
json_t *wireTensionJ = json_real(gScene->toolbar->wireTensionSlider->value);
json_t *wireTensionJ = json_real(dynamic_cast<RackScene*>(gScene)->toolbar->wireTensionSlider->value);
json_object_set_new(root, "wireTension", wireTensionJ);

// modules
@@ -140,12 +146,12 @@ void RackWidget::fromJson(json_t *root) {
// wireOpacity
json_t *wireOpacityJ = json_object_get(root, "wireOpacity");
if (wireOpacityJ)
gScene->toolbar->wireOpacitySlider->value = json_number_value(wireOpacityJ);
dynamic_cast<RackScene*>(gScene)->toolbar->wireOpacitySlider->value = json_number_value(wireOpacityJ);

// wireTension
json_t *wireTensionJ = json_object_get(root, "wireTension");
if (wireTensionJ)
gScene->toolbar->wireTensionSlider->value = json_number_value(wireTensionJ);
dynamic_cast<RackScene*>(gScene)->toolbar->wireTensionSlider->value = json_number_value(wireTensionJ);

// modules
std::map<int, ModuleWidget*> moduleWidgets;
@@ -296,10 +302,9 @@ void RackWidget::draw(NVGcontext *vg) {
nvgFill(vg);
}
{
int imageId = loadImage("res/rails.png");
int imageWidth, imageHeight;
nvgImageSize(vg, imageId, &imageWidth, &imageHeight);
paint = nvgImagePattern(vg, box.pos.x, box.pos.y, imageWidth, imageHeight, 0.0, imageId, 1.0);
nvgImageSize(vg, railsImage->handle, &imageWidth, &imageHeight);
paint = nvgImagePattern(vg, box.pos.x, box.pos.y, imageWidth, imageHeight, 0.0, railsImage->handle, 1.0);
nvgFillPaint(vg, paint);
nvgFill(vg);
}


+ 1
- 1
src/widgets/RadioButton.cpp View File

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,22 +1,8 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {

Scene::Scene() {
scrollWidget = new ScrollWidget();
{
assert(!gRackWidget);
gRackWidget = new RackWidget();
scrollWidget->container->addChild(gRackWidget);
}
addChild(scrollWidget);

toolbar = new Toolbar();
addChild(toolbar);
scrollWidget->box.pos.y = toolbar->box.size.y;
}

void Scene::setOverlay(Widget *w) {
if (overlay) {
removeChild(overlay);
@@ -31,8 +17,6 @@ void Scene::setOverlay(Widget *w) {
}

void Scene::step() {
toolbar->box.size.x = box.size.x;
scrollWidget->box.size = box.size.minus(scrollWidget->box.pos);
if (overlay) {
overlay->box.size = box.size;
}


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "scene.hpp"


namespace rack {
@@ -7,7 +7,7 @@ Screw::Screw() {
box.size = Vec(15, 15);
spriteOffset = Vec(-7, -7);
spriteSize = Vec(29, 29);
spriteFilename = "res/screw.png";
spriteImage = Image::load("res/screw.png");

index = randomu32() % 5;
}


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

@@ -1,4 +1,5 @@
#include "rack.hpp"
#include "widgets.hpp"
#include "gui.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,5 @@
#include "rack.hpp"
#include "widgets.hpp"
#include "gui.hpp"


namespace rack {


+ 3
- 9
src/widgets/SpriteWidget.cpp View File

@@ -1,26 +1,20 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {

void SpriteWidget::draw(NVGcontext *vg) {
int imageId = loadImage(spriteFilename);
if (imageId < 0) {
// printf("Could not load image %s for SpriteWidget\n", spriteFilename.c_str());
return;
}

Vec pos = box.pos.plus(spriteOffset);

int width, height;
nvgImageSize(vg, imageId, &width, &height);
nvgImageSize(vg, spriteImage->handle, &width, &height);
int stride = width / spriteSize.x;
if (stride == 0) {
printf("Size of SpriteWidget is %d, %d but spriteSize is %f, %f\n", width, height, spriteSize.x, spriteSize.y);
return;
}
Vec offset = Vec((index % stride) * spriteSize.x, (index / stride) * spriteSize.y);
NVGpaint paint = nvgImagePattern(vg, pos.x - offset.x, pos.y - offset.y, width, height, 0.0, imageId, 1.0);
NVGpaint paint = nvgImagePattern(vg, pos.x - offset.x, pos.y - offset.y, width, height, 0.0, spriteImage->handle, 1.0);
nvgFillPaint(vg, paint);
nvgBeginPath(vg);
nvgRect(vg, pos.x, pos.y, spriteSize.x, spriteSize.y);


+ 5
- 3
src/widgets/Toolbar.cpp View File

@@ -1,4 +1,6 @@
#include "rack.hpp"
#include "scene.hpp"
#include "gui.hpp"
#include "engine.hpp"


namespace rack {
@@ -62,7 +64,7 @@ struct FileChoice : ChoiceButton {
struct SampleRateItem : MenuItem {
float sampleRate;
void onAction() {
gRack->sampleRate = sampleRate;
gSampleRate = sampleRate;
}
};

@@ -85,7 +87,7 @@ struct SampleRateChoice : ChoiceButton {
gScene->setOverlay(overlay);
}
void step() {
text = stringf("%.0f Hz", gRack->sampleRate);
text = stringf("%.0f Hz", gSampleRate);
}
};



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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"


namespace rack {


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

@@ -1,4 +1,4 @@
#include "rack.hpp"
#include "widgets.hpp"
#include <algorithm>




+ 6
- 5
src/widgets/WireWidget.cpp View File

@@ -1,4 +1,5 @@
#include "rack.hpp"
#include "scene.hpp"
#include "engine.hpp"


namespace rack {
@@ -112,7 +113,7 @@ WireWidget::~WireWidget() {

void WireWidget::updateWire() {
if (wire) {
gRack->removeWire(wire);
engineRemoveWire(wire);
delete wire;
wire = NULL;
}
@@ -122,14 +123,15 @@ void WireWidget::updateWire() {
wire->outputId = outputPort->outputId;
wire->inputModule = inputPort->module;
wire->inputId = inputPort->inputId;
gRack->addWire(wire);
engineAddWire(wire);
}
}

void WireWidget::draw(NVGcontext *vg) {
Vec outputPos, inputPos;
Vec absolutePos = getAbsolutePos();
float opacity = gScene->toolbar->wireOpacitySlider->value / 100.0;
float opacity = dynamic_cast<RackScene*>(gScene)->toolbar->wireOpacitySlider->value / 100.0;
float tension = dynamic_cast<RackScene*>(gScene)->toolbar->wireTensionSlider->value;

// Compute location of pos1 and pos2
if (outputPort) {
@@ -150,7 +152,6 @@ void WireWidget::draw(NVGcontext *vg) {
outputPos = outputPos.minus(absolutePos);
inputPos = inputPos.minus(absolutePos);

float tension = gScene->toolbar->wireTensionSlider->value;
drawWire(vg, outputPos, inputPos, tension, color, opacity);
}



Loading…
Cancel
Save