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