@@ -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 |
@@ -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 |
@@ -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(); |
@@ -1,130 +1,14 @@ | |||||
#pragma once | #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 { | 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 | inline | ||||
@@ -191,26 +75,5 @@ Screw *createScrew(Vec pos) { | |||||
return screw; | return screw; | ||||
} | } | ||||
//////////////////// | |||||
// Globals | |||||
//////////////////// | |||||
extern std::string gApplicationName; | |||||
extern std::string gApplicationVersion; | |||||
extern Scene *gScene; | |||||
extern RackWidget *gRackWidget; | |||||
extern Rack *gRack; | |||||
} // namespace rack | } // namespace rack | ||||
//////////////////// | |||||
// Implemented by plugin | |||||
//////////////////// | |||||
// Called once to initialize and return Plugin. | |||||
// Plugin is destructed when Rack closes | |||||
extern "C" | |||||
rack::Plugin *init(); |
@@ -1,7 +1,7 @@ | |||||
#pragma once | #pragma once | ||||
#include "widgets.hpp" | |||||
#include <vector> | |||||
#include <jansson.h> | #include <jansson.h> | ||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -14,7 +14,7 @@ struct RackWidget; | |||||
struct ParamWidget; | struct ParamWidget; | ||||
struct InputPort; | struct InputPort; | ||||
struct OutputPort; | struct OutputPort; | ||||
struct Scene; | |||||
//////////////////// | //////////////////// | ||||
// module | // module | ||||
@@ -76,6 +76,7 @@ struct RackWidget : OpaqueWidget { | |||||
// Only put WireWidgets in here | // Only put WireWidgets in here | ||||
Widget *wireContainer; | Widget *wireContainer; | ||||
WireWidget *activeWire = NULL; | WireWidget *activeWire = NULL; | ||||
std::shared_ptr<Image> railsImage; | |||||
RackWidget(); | RackWidget(); | ||||
~RackWidget(); | ~RackWidget(); | ||||
@@ -94,7 +95,7 @@ struct RackWidget : OpaqueWidget { | |||||
struct ModulePanel : TransparentWidget { | struct ModulePanel : TransparentWidget { | ||||
NVGcolor backgroundColor; | NVGcolor backgroundColor; | ||||
std::string imageFilename; | |||||
std::shared_ptr<Image> backgroundImage; | |||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
@@ -205,18 +206,17 @@ struct Toolbar : OpaqueWidget { | |||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
struct Scene : OpaqueWidget { | |||||
struct RackScene : Scene { | |||||
Toolbar *toolbar; | Toolbar *toolbar; | ||||
ScrollWidget *scrollWidget; | ScrollWidget *scrollWidget; | ||||
Widget *overlay = NULL; | |||||
Scene(); | |||||
void setOverlay(Widget *w); | |||||
RackScene(); | |||||
void step(); | void step(); | ||||
}; | }; | ||||
//////////////////// | //////////////////// | ||||
// component library | |||||
// Component Library | |||||
//////////////////// | //////////////////// | ||||
struct PJ301M : SpriteWidget { | struct PJ301M : SpriteWidget { | ||||
@@ -224,7 +224,7 @@ struct PJ301M : SpriteWidget { | |||||
box.size = Vec(24, 24); | box.size = Vec(24, 24); | ||||
spriteOffset = Vec(-10, -10); | spriteOffset = Vec(-10, -10); | ||||
spriteSize = Vec(48, 48); | spriteSize = Vec(48, 48); | ||||
spriteFilename = "res/ComponentLibrary/PJ301M.png"; | |||||
spriteImage = Image::load("res/ComponentLibrary/PJ301M.png"); | |||||
} | } | ||||
}; | }; | ||||
struct InputPortPJ301M : InputPort, PJ301M {}; | struct InputPortPJ301M : InputPort, PJ301M {}; | ||||
@@ -235,7 +235,7 @@ struct PJ3410 : SpriteWidget { | |||||
box.size = Vec(31, 31); | box.size = Vec(31, 31); | ||||
spriteOffset = Vec(-9, -9); | spriteOffset = Vec(-9, -9); | ||||
spriteSize = Vec(54, 54); | spriteSize = Vec(54, 54); | ||||
spriteFilename = "res/ComponentLibrary/PJ3410.png"; | |||||
spriteImage = Image::load("res/ComponentLibrary/PJ3410.png"); | |||||
} | } | ||||
}; | }; | ||||
struct InputPortPJ3410 : InputPort, PJ3410 {}; | struct InputPortPJ3410 : InputPort, PJ3410 {}; | ||||
@@ -246,10 +246,24 @@ struct CL1362 : SpriteWidget { | |||||
box.size = Vec(33, 29); | box.size = Vec(33, 29); | ||||
spriteOffset = Vec(-10, -10); | spriteOffset = Vec(-10, -10); | ||||
spriteSize = Vec(57, 54); | spriteSize = Vec(57, 54); | ||||
spriteFilename = "res/ComponentLibrary/CL1362.png"; | |||||
spriteImage = Image::load("res/ComponentLibrary/CL1362.png"); | |||||
} | } | ||||
}; | }; | ||||
struct InputPortCL1362 : InputPort, CL1362 {}; | struct InputPortCL1362 : InputPort, CL1362 {}; | ||||
struct OutputPortCL1362 : OutputPort, 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 | } // namespace rack |
@@ -1,6 +1,12 @@ | |||||
#pragma once | #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 <stdint.h> | ||||
#include <assert.h> | |||||
#include <string> | #include <string> | ||||
@@ -1,13 +1,10 @@ | |||||
#pragma once | #pragma once | ||||
#include <assert.h> | |||||
#include <stdio.h> | |||||
#include <math.h> | |||||
#include <list> | #include <list> | ||||
#include <map> | |||||
#include <memory> | |||||
#include "../ext/nanovg/src/nanovg.h" | #include "../ext/nanovg/src/nanovg.h" | ||||
#include "../ext/oui/blendish.h" | #include "../ext/oui/blendish.h" | ||||
#include "../ext/nanosvg/src/nanosvg.h" | |||||
#include "math.hpp" | #include "math.hpp" | ||||
#include "util.hpp" | #include "util.hpp" | ||||
@@ -16,6 +13,35 @@ | |||||
namespace rack { | 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 | // base class and traits | ||||
//////////////////// | //////////////////// | ||||
@@ -122,7 +148,7 @@ struct OpaqueWidget : virtual Widget { | |||||
struct SpriteWidget : virtual Widget { | struct SpriteWidget : virtual Widget { | ||||
Vec spriteOffset; | Vec spriteOffset; | ||||
Vec spriteSize; | Vec spriteSize; | ||||
std::string spriteFilename; | |||||
std::shared_ptr<Image> spriteImage; | |||||
int index = 0; | int index = 0; | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
@@ -276,5 +302,24 @@ struct Tooltip : Widget { | |||||
void draw(NVGcontext *vg); | 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 | } // namespace rack |
@@ -99,7 +99,7 @@ void AudioInterface::step() { | |||||
// Once full, sample rate convert the input | // Once full, sample rate convert the input | ||||
if (inputBuffer.full()) { | if (inputBuffer.full()) { | ||||
inputSrc.setRatio(sampleRate / gRack->sampleRate); | |||||
inputSrc.setRatio(sampleRate / gSampleRate); | |||||
int inLen = inputBuffer.size(); | int inLen = inputBuffer.size(); | ||||
int outLen = inputSrcBuffer.capacity(); | int outLen = inputSrcBuffer.capacity(); | ||||
inputSrc.process((const float*) inputBuffer.startData(), &inLen, (float*) inputSrcBuffer.endData(), &outLen); | inputSrc.process((const float*) inputBuffer.startData(), &inLen, (float*) inputSrcBuffer.endData(), &outLen); | ||||
@@ -127,7 +127,7 @@ void AudioInterface::step() { | |||||
} | } | ||||
// Pass output through sample rate converter | // Pass output through sample rate converter | ||||
outputSrc.setRatio(gRack->sampleRate / sampleRate); | |||||
outputSrc.setRatio(gSampleRate / sampleRate); | |||||
int inLen = blockSize; | int inLen = blockSize; | ||||
int outLen = outputBuffer.capacity(); | int outLen = outputBuffer.capacity(); | ||||
outputSrc.process((float*) buf, &inLen, (float*) outputBuffer.endData(), &outLen); | outputSrc.process((float*) buf, &inLen, (float*) outputBuffer.endData(), &outLen); | ||||
@@ -3,12 +3,20 @@ | |||||
using namespace rack; | 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(); | audioInit(); | ||||
midiInit(); | midiInit(); | ||||
Plugin *plugin = createPlugin("Core", "Core"); | |||||
createModel<AudioInterfaceWidget>(plugin, "AudioInterface", "Audio Interface"); | |||||
createModel<MidiInterfaceWidget>(plugin, "MidiInterface", "MIDI Interface"); | |||||
return plugin; | |||||
return new CorePlugin(); | |||||
} | } |
@@ -3,8 +3,6 @@ | |||||
using namespace rack; | using namespace rack; | ||||
Plugin *coreInit(); | |||||
void audioInit(); | void audioInit(); | ||||
void midiInit(); | void midiInit(); | ||||
@@ -2,15 +2,20 @@ | |||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <assert.h> | #include <assert.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <set> | |||||
#include <chrono> | #include <chrono> | ||||
#include <thread> | |||||
#include <mutex> | |||||
#include <condition_variable> | #include <condition_variable> | ||||
#include <xmmintrin.h> | #include <xmmintrin.h> | ||||
#include "rack.hpp" | |||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
float gSampleRate; | |||||
/** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | /** 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. | 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. | // 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 | // 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 lambda = 60.0; // decay rate is 1 graphics frame | ||||
const float snap = 0.0001; | const float snap = 0.0001; | ||||
float delta = impl->smoothValue - value; | |||||
float delta = smoothValue - value; | |||||
if (fabsf(delta) < snap) { | if (fabsf(delta) < snap) { | ||||
impl->smoothModule->params[impl->smoothParamId] = impl->smoothValue; | |||||
impl->smoothModule = NULL; | |||||
smoothModule->params[smoothParamId] = smoothValue; | |||||
smoothModule = NULL; | |||||
} | } | ||||
else { | else { | ||||
value += delta * lambda / sampleRate; | |||||
impl->smoothModule->params[impl->smoothParamId] = value; | |||||
value += delta * lambda / gSampleRate; | |||||
smoothModule->params[smoothParamId] = value; | |||||
} | } | ||||
} | } | ||||
// Step all modules | // Step all modules | ||||
std::chrono::time_point<std::chrono::high_resolution_clock> start, end; | 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 clock for CPU usage | ||||
start = std::chrono::high_resolution_clock::now(); | start = std::chrono::high_resolution_clock::now(); | ||||
// Step module by one frame | // Step module by one frame | ||||
@@ -133,91 +99,126 @@ void Rack::step() { | |||||
// Stop clock and smooth step time value | // Stop clock and smooth step time value | ||||
end = std::chrono::high_resolution_clock::now(); | end = std::chrono::high_resolution_clock::now(); | ||||
std::chrono::duration<float> diff = end - start; | std::chrono::duration<float> diff = end - start; | ||||
float elapsed = diff.count() * sampleRate; | |||||
float elapsed = diff.count() * gSampleRate; | |||||
const float lambda = 1.0; | 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 | // Step cables by moving their output values to inputs | ||||
for (Wire *wire : impl->wires) { | |||||
for (Wire *wire : wires) { | |||||
wire->inputValue = wire->outputValue; | wire->inputValue = wire->outputValue; | ||||
wire->outputValue = 0.0; | 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); | 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 | // 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); | 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 | // 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 | // Check that all wires are disconnected | ||||
for (Wire *wire : impl->wires) { | |||||
for (Wire *wire : wires) { | |||||
assert(wire->outputModule != module); | assert(wire->outputModule != module); | ||||
assert(wire->inputModule != 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); | 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 | // 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->outputModule); | ||||
assert(wire->inputModule); | assert(wire->inputModule); | ||||
// Check that the inputs/outputs are not already used by another cable | // 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 != wire); | ||||
assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId)); | assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId)); | ||||
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | ||||
} | } | ||||
// Connect the wire to inputModule | // Connect the wire to inputModule | ||||
impl->wires.insert(wire); | |||||
wires.insert(wire); | |||||
wire->inputModule->inputs[wire->inputId] = &wire->inputValue; | wire->inputModule->inputs[wire->inputId] = &wire->inputValue; | ||||
wire->outputModule->outputs[wire->outputId] = &wire->outputValue; | wire->outputModule->outputs[wire->outputId] = &wire->outputValue; | ||||
} | } | ||||
void Rack::removeWire(Wire *wire) { | |||||
void engineRemoveWire(Wire *wire) { | |||||
assert(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 | // Disconnect wire from inputModule | ||||
wire->inputModule->inputs[wire->inputId] = NULL; | wire->inputModule->inputs[wire->inputId] = NULL; | ||||
wire->outputModule->outputs[wire->outputId] = NULL; | wire->outputModule->outputs[wire->outputId] = NULL; | ||||
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 | // 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 | // 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; | |||||
} | } | ||||
@@ -1,16 +1,19 @@ | |||||
#include <unistd.h> | |||||
#include "rack.hpp" | |||||
#include <map> | |||||
#include <GL/glew.h> | #include <GL/glew.h> | ||||
#include <GLFW/glfw3.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 | #define NANOVG_GL2_IMPLEMENTATION | ||||
#include "../ext/nanovg/src/nanovg_gl.h" | #include "../ext/nanovg/src/nanovg_gl.h" | ||||
#define BLENDISH_IMPLEMENTATION | #define BLENDISH_IMPLEMENTATION | ||||
#include "../ext/oui/blendish.h" | #include "../ext/oui/blendish.h" | ||||
#define NANOSVG_IMPLEMENTATION | |||||
#include "../ext/nanosvg/src/nanosvg.h" | |||||
extern "C" { | extern "C" { | ||||
#include "../ext/noc/noc_file_dialog.h" | #include "../ext/noc/noc_file_dialog.h" | ||||
@@ -19,18 +22,9 @@ extern "C" { | |||||
namespace rack { | 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 GLFWwindow *window = NULL; | ||||
static NVGcontext *vg = NULL; | static NVGcontext *vg = NULL; | ||||
static std::shared_ptr<Font> defaultFont; | |||||
void windowSizeCallback(GLFWwindow* window, int width, int height) { | void windowSizeCallback(GLFWwindow* window, int width, int height) { | ||||
@@ -178,7 +172,7 @@ void guiInit() { | |||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | ||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | 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); | assert(window); | ||||
glfwMakeContextCurrent(window); | glfwMakeContextCurrent(window); | ||||
@@ -206,15 +200,13 @@ void guiInit() { | |||||
assert(vg); | assert(vg); | ||||
// Set up Blendish | // Set up Blendish | ||||
bndSetFont(loadFont("res/DejaVuSans.ttf")); | |||||
defaultFont = Font::load("res/DejaVuSans.ttf"); | |||||
bndSetFont(defaultFont->handle); | |||||
// bndSetIconImage(loadImage("res/icons.png")); | // bndSetIconImage(loadImage("res/icons.png")); | ||||
gScene = new Scene(); | |||||
} | } | ||||
void guiDestroy() { | void guiDestroy() { | ||||
delete gScene; | |||||
defaultFont.reset(); | |||||
nvgDeleteGL2(vg); | nvgDeleteGL2(vg); | ||||
glfwDestroyWindow(window); | glfwDestroyWindow(window); | ||||
glfwTerminate(); | 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 { | 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 { | 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; | |||||
} | } | ||||
@@ -7,16 +7,6 @@ | |||||
#include "rack.hpp" | #include "rack.hpp" | ||||
namespace rack { | |||||
std::string gApplicationName = "VCV Rack"; | |||||
std::string gApplicationVersion = "v0.1.0 alpha"; | |||||
Rack *gRack; | |||||
} // namespace rack | |||||
using namespace rack; | using namespace rack; | ||||
int main() { | int main() { | ||||
@@ -34,18 +24,20 @@ int main() { | |||||
} | } | ||||
#endif | #endif | ||||
gRack = new Rack(); | |||||
engineInit(); | |||||
guiInit(); | guiInit(); | ||||
sceneInit(); | |||||
pluginInit(); | pluginInit(); | ||||
gRackWidget->loadPatch("autosave.json"); | gRackWidget->loadPatch("autosave.json"); | ||||
gRack->start(); | |||||
engineStart(); | |||||
guiRun(); | guiRun(); | ||||
gRack->stop(); | |||||
engineStop(); | |||||
gRackWidget->savePatch("autosave.json"); | gRackWidget->savePatch("autosave.json"); | ||||
sceneDestroy(); | |||||
guiDestroy(); | guiDestroy(); | ||||
delete gRack; | |||||
engineDestroy(); | |||||
pluginDestroy(); | pluginDestroy(); | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -7,8 +7,7 @@ | |||||
#include <glob.h> | #include <glob.h> | ||||
#endif | #endif | ||||
#include "rack.hpp" | |||||
#include "core/core.hpp" | |||||
#include "plugin.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -60,8 +59,10 @@ int loadPlugin(const char *path) { | |||||
void pluginInit() { | void pluginInit() { | ||||
// Load core | // Load core | ||||
Plugin *corePlugin = coreInit(); | |||||
// This function is defined in core.cpp | |||||
Plugin *corePlugin = init(); | |||||
gPlugins.push_back(corePlugin); | gPlugins.push_back(corePlugin); | ||||
// Search for plugin libraries | // Search for plugin libraries | ||||
#if defined(WINDOWS) | #if defined(WINDOWS) | ||||
WIN32_FIND_DATA ffd; | WIN32_FIND_DATA ffd; | ||||
@@ -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 |
@@ -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,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,5 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -13,11 +13,10 @@ void ModulePanel::draw(NVGcontext *vg) { | |||||
nvgFill(vg); | nvgFill(vg); | ||||
// Background image | // Background image | ||||
if (!imageFilename.empty()) { | |||||
int imageId = loadImage(imageFilename); | |||||
if (backgroundImage) { | |||||
int width, height; | 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); | nvgFillPaint(vg, paint); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
@@ -1,4 +1,6 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
#include "engine.hpp" | |||||
#include "plugin.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -6,7 +8,7 @@ namespace rack { | |||||
ModuleWidget::ModuleWidget(Module *module) { | ModuleWidget::ModuleWidget(Module *module) { | ||||
this->module = module; | this->module = module; | ||||
if (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. | // Make sure WireWidget destructors are called *before* removing `module` from the rack. | ||||
disconnectPorts(); | disconnectPorts(); | ||||
if (module) { | if (module) { | ||||
gRack->removeModule(module); | |||||
engineRemoveModule(module); | |||||
delete 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); | bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | ||||
// CPU usage text | // 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; | float cpuTime = module ? module->cpuTime : 0.0; | ||||
std::string text = stringf("%.1f%%", cpuTime * 100.0); | std::string text = stringf("%.1f%%", cpuTime * 100.0); | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -23,7 +24,7 @@ void ParamWidget::onChange() { | |||||
return; | return; | ||||
// moduleWidget->module->params[paramId] = value; | // moduleWidget->module->params[paramId] = value; | ||||
gRack->setParamSmooth(module, paramId, value); | |||||
engineSetParamSmooth(module, paramId, value); | |||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -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 |
@@ -1,5 +1,9 @@ | |||||
#include "rack.hpp" | |||||
#include <map> | |||||
#include <algorithm> | #include <algorithm> | ||||
#include "scene.hpp" | |||||
#include "engine.hpp" | |||||
#include "plugin.hpp" | |||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -10,6 +14,8 @@ RackWidget::RackWidget() { | |||||
wireContainer = new TransparentWidget(); | wireContainer = new TransparentWidget(); | ||||
addChild(wireContainer); | addChild(wireContainer); | ||||
railsImage = Image::load("res/rails.png"); | |||||
} | } | ||||
RackWidget::~RackWidget() { | RackWidget::~RackWidget() { | ||||
@@ -65,11 +71,11 @@ json_t *RackWidget::toJson() { | |||||
json_object_set_new(root, "version", versionJ); | json_object_set_new(root, "version", versionJ); | ||||
// wireOpacity | // 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); | json_object_set_new(root, "wireOpacity", wireOpacityJ); | ||||
// wireTension | // 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); | json_object_set_new(root, "wireTension", wireTensionJ); | ||||
// modules | // modules | ||||
@@ -140,12 +146,12 @@ void RackWidget::fromJson(json_t *root) { | |||||
// wireOpacity | // wireOpacity | ||||
json_t *wireOpacityJ = json_object_get(root, "wireOpacity"); | json_t *wireOpacityJ = json_object_get(root, "wireOpacity"); | ||||
if (wireOpacityJ) | if (wireOpacityJ) | ||||
gScene->toolbar->wireOpacitySlider->value = json_number_value(wireOpacityJ); | |||||
dynamic_cast<RackScene*>(gScene)->toolbar->wireOpacitySlider->value = json_number_value(wireOpacityJ); | |||||
// wireTension | // wireTension | ||||
json_t *wireTensionJ = json_object_get(root, "wireTension"); | json_t *wireTensionJ = json_object_get(root, "wireTension"); | ||||
if (wireTensionJ) | if (wireTensionJ) | ||||
gScene->toolbar->wireTensionSlider->value = json_number_value(wireTensionJ); | |||||
dynamic_cast<RackScene*>(gScene)->toolbar->wireTensionSlider->value = json_number_value(wireTensionJ); | |||||
// modules | // modules | ||||
std::map<int, ModuleWidget*> moduleWidgets; | std::map<int, ModuleWidget*> moduleWidgets; | ||||
@@ -296,10 +302,9 @@ void RackWidget::draw(NVGcontext *vg) { | |||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
{ | { | ||||
int imageId = loadImage("res/rails.png"); | |||||
int imageWidth, imageHeight; | 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); | nvgFillPaint(vg, paint); | ||||
nvgFill(vg); | nvgFill(vg); | ||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,22 +1,8 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | 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) { | void Scene::setOverlay(Widget *w) { | ||||
if (overlay) { | if (overlay) { | ||||
removeChild(overlay); | removeChild(overlay); | ||||
@@ -31,8 +17,6 @@ void Scene::setOverlay(Widget *w) { | |||||
} | } | ||||
void Scene::step() { | void Scene::step() { | ||||
toolbar->box.size.x = box.size.x; | |||||
scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); | |||||
if (overlay) { | if (overlay) { | ||||
overlay->box.size = box.size; | overlay->box.size = box.size; | ||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -7,7 +7,7 @@ Screw::Screw() { | |||||
box.size = Vec(15, 15); | box.size = Vec(15, 15); | ||||
spriteOffset = Vec(-7, -7); | spriteOffset = Vec(-7, -7); | ||||
spriteSize = Vec(29, 29); | spriteSize = Vec(29, 29); | ||||
spriteFilename = "res/screw.png"; | |||||
spriteImage = Image::load("res/screw.png"); | |||||
index = randomu32() % 5; | index = randomu32() % 5; | ||||
} | } | ||||
@@ -1,4 +1,5 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
#include "gui.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,26 +1,20 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
void SpriteWidget::draw(NVGcontext *vg) { | 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); | Vec pos = box.pos.plus(spriteOffset); | ||||
int width, height; | int width, height; | ||||
nvgImageSize(vg, imageId, &width, &height); | |||||
nvgImageSize(vg, spriteImage->handle, &width, &height); | |||||
int stride = width / spriteSize.x; | int stride = width / spriteSize.x; | ||||
if (stride == 0) { | if (stride == 0) { | ||||
printf("Size of SpriteWidget is %d, %d but spriteSize is %f, %f\n", width, height, spriteSize.x, spriteSize.y); | printf("Size of SpriteWidget is %d, %d but spriteSize is %f, %f\n", width, height, spriteSize.x, spriteSize.y); | ||||
return; | return; | ||||
} | } | ||||
Vec offset = Vec((index % stride) * spriteSize.x, (index / stride) * spriteSize.y); | 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); | nvgFillPaint(vg, paint); | ||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, pos.x, pos.y, spriteSize.x, spriteSize.y); | nvgRect(vg, pos.x, pos.y, spriteSize.x, spriteSize.y); | ||||
@@ -1,4 +1,6 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
#include "gui.hpp" | |||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -62,7 +64,7 @@ struct FileChoice : ChoiceButton { | |||||
struct SampleRateItem : MenuItem { | struct SampleRateItem : MenuItem { | ||||
float sampleRate; | float sampleRate; | ||||
void onAction() { | void onAction() { | ||||
gRack->sampleRate = sampleRate; | |||||
gSampleRate = sampleRate; | |||||
} | } | ||||
}; | }; | ||||
@@ -85,7 +87,7 @@ struct SampleRateChoice : ChoiceButton { | |||||
gScene->setOverlay(overlay); | gScene->setOverlay(overlay); | ||||
} | } | ||||
void step() { | void step() { | ||||
text = stringf("%.0f Hz", gRack->sampleRate); | |||||
text = stringf("%.0f Hz", gSampleRate); | |||||
} | } | ||||
}; | }; | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,4 @@ | |||||
#include "rack.hpp" | |||||
#include "widgets.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
@@ -1,4 +1,5 @@ | |||||
#include "rack.hpp" | |||||
#include "scene.hpp" | |||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -112,7 +113,7 @@ WireWidget::~WireWidget() { | |||||
void WireWidget::updateWire() { | void WireWidget::updateWire() { | ||||
if (wire) { | if (wire) { | ||||
gRack->removeWire(wire); | |||||
engineRemoveWire(wire); | |||||
delete wire; | delete wire; | ||||
wire = NULL; | wire = NULL; | ||||
} | } | ||||
@@ -122,14 +123,15 @@ void WireWidget::updateWire() { | |||||
wire->outputId = outputPort->outputId; | wire->outputId = outputPort->outputId; | ||||
wire->inputModule = inputPort->module; | wire->inputModule = inputPort->module; | ||||
wire->inputId = inputPort->inputId; | wire->inputId = inputPort->inputId; | ||||
gRack->addWire(wire); | |||||
engineAddWire(wire); | |||||
} | } | ||||
} | } | ||||
void WireWidget::draw(NVGcontext *vg) { | void WireWidget::draw(NVGcontext *vg) { | ||||
Vec outputPos, inputPos; | Vec outputPos, inputPos; | ||||
Vec absolutePos = getAbsolutePos(); | 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 | // Compute location of pos1 and pos2 | ||||
if (outputPort) { | if (outputPort) { | ||||
@@ -150,7 +152,6 @@ void WireWidget::draw(NVGcontext *vg) { | |||||
outputPos = outputPos.minus(absolutePos); | outputPos = outputPos.minus(absolutePos); | ||||
inputPos = inputPos.minus(absolutePos); | inputPos = inputPos.minus(absolutePos); | ||||
float tension = gScene->toolbar->wireTensionSlider->value; | |||||
drawWire(vg, outputPos, inputPos, tension, color, opacity); | drawWire(vg, outputPos, inputPos, tension, color, opacity); | ||||
} | } | ||||