Browse Source

Changed how events work, added ModulePanel, added cable tension

tags/v0.3.0
Andrew Belt 7 years ago
parent
commit
06326a899a
31 changed files with 495 additions and 333 deletions
  1. +2
    -2
      Makefile
  2. +22
    -0
      README.md
  3. +0
    -9
      README.txt
  4. +6
    -0
      include/Rack.hpp
  5. +34
    -8
      include/util.hpp
  6. +109
    -56
      include/widgets.hpp
  7. +1
    -1
      src/core/AudioInterface.cpp
  8. +13
    -8
      src/core/MidiInterface.cpp
  9. +57
    -44
      src/gui.cpp
  10. +10
    -1
      src/main.cpp
  11. +15
    -14
      src/plugin.cpp
  12. +2
    -70
      src/util.cpp
  13. +2
    -1
      src/widgets/InputPort.cpp
  14. +6
    -9
      src/widgets/MenuItem.cpp
  15. +11
    -5
      src/widgets/MenuOverlay.cpp
  16. +29
    -0
      src/widgets/ModulePanel.cpp
  17. +4
    -4
      src/widgets/ModuleWidget.cpp
  18. +1
    -1
      src/widgets/OutputPort.cpp
  19. +4
    -2
      src/widgets/ParamWidget.cpp
  20. +7
    -15
      src/widgets/Port.cpp
  21. +21
    -0
      src/widgets/QuantityWidget.cpp
  22. +20
    -42
      src/widgets/RackWidget.cpp
  23. +19
    -2
      src/widgets/Scene.cpp
  24. +3
    -1
      src/widgets/Screw.cpp
  25. +16
    -10
      src/widgets/ScrollWidget.cpp
  26. +1
    -3
      src/widgets/Slider.cpp
  27. +1
    -1
      src/widgets/SpriteWidget.cpp
  28. +28
    -12
      src/widgets/Toolbar.cpp
  29. +2
    -0
      src/widgets/Tooltip.cpp
  30. +44
    -8
      src/widgets/Widget.cpp
  31. +5
    -4
      src/widgets/WireWidget.cpp

+ 2
- 2
Makefile View File

@@ -1,6 +1,6 @@
ARCH ?= linux ARCH ?= linux
CFLAGS = -MMD -g -Wall -O2 CFLAGS = -MMD -g -Wall -O2
CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math \
CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math -fno-exceptions \
-I./lib -I./include -I./lib -I./include
LDFLAGS = LDFLAGS =


@@ -15,7 +15,7 @@ CXX = g++
SOURCES += lib/noc/noc_file_dialog.c SOURCES += lib/noc/noc_file_dialog.c
CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0)
CXXFLAGS += -DLINUX CXXFLAGS += -DLINUX
LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -lprofiler -ljansson -lportaudio -lportmidi \
LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi \
$(shell pkg-config --libs gtk+-2.0) $(shell pkg-config --libs gtk+-2.0)
TARGET = Rack TARGET = Rack
endif endif


+ 22
- 0
README.md View File

@@ -0,0 +1,22 @@
*Note: This software is in semi-public alpha. If you have stumbled upon this project, feel free to try it out and report bugs to the GitHub issue tracker. However, with more users it becomes difficult to make breaking changes, so please don't spread this to your friends and the Internet just yet, until the official announcement has been made.*

░█▀▄░█▀█░█▀▀░█░█
░█▀▄░█▀█░█░░░█▀▄
░▀░▀░▀░▀░▀▀▀░▀░▀

Eurorack-style modular DAW

## Building

Install dependencies

- [GLEW](http://www.glfw.org/)
- [GLFW](http://glew.sourceforge.net/)
- [jansson](http://www.digip.org/jansson/)
- [portaudio](http://www.portaudio.com/)
- [portmidi](http://portmedia.sourceforge.net/portmidi/)
- GTK+-2.0 if Linux (for file open/save dialog)

Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple`

If the build breaks because you think I've missed a step, feel free to post an issue.

+ 0
- 9
README.txt View File

@@ -1,9 +0,0 @@
░█▀▄░█▀█░█▀▀░█░█
░█▀▄░█▀█░█░░░█▀▄
░▀░▀░▀░▀░▀▀▀░▀░▀

Virtual modular synthesizer engine

## Building

`make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple`

+ 6
- 0
include/Rack.hpp View File

@@ -9,6 +9,9 @@


namespace rack { namespace rack {


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

extern Scene *gScene; extern Scene *gScene;
extern RackWidget *gRackWidget; extern RackWidget *gRackWidget;


@@ -53,12 +56,15 @@ void pluginDestroy();
extern Vec gMousePos; extern Vec gMousePos;
extern Widget *gHoveredWidget; extern Widget *gHoveredWidget;
extern Widget *gDraggedWidget; extern Widget *gDraggedWidget;
extern Widget *gSelectedWidget;


void guiInit(); void guiInit();
void guiDestroy(); void guiDestroy();
void guiRun(); void guiRun();
void guiCursorLock(); void guiCursorLock();
void guiCursorUnlock(); 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 loadFont(std::string filename);
int loadImage(std::string filename); int loadImage(std::string filename);


+ 34
- 8
include/util.hpp View File

@@ -2,6 +2,7 @@


#include <stdint.h> #include <stdint.h>
#include <math.h> #include <math.h>
#include <random>




namespace rack { namespace rack {
@@ -10,8 +11,15 @@ namespace rack {
// Math // Math
//////////////////// ////////////////////


/** Limits a value between a minimum and maximum
If min > max for some reason, returns min;
*/
inline float clampf(float x, float min, float max) { inline float clampf(float x, float min, float max) {
return fmaxf(min, fminf(max, x));
if (x > max)
x = max;
if (x < min)
x = min;
return x;
} }


inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) {
@@ -30,6 +38,17 @@ inline int maxi(int a, int b) {
return a > b ? a : b; return a > b ? a : b;
} }


inline float quadraticBipolar(float x) {
float x2 = x*x;
return x >= 0.0 ? x2 : -x2;
}

inline float quarticBipolar(float x) {
float x2 = x*x;
float x4 = x2*x2;
return x >= 0.0 ? x4 : -x4;
}

// Euclidean modulus, always returns 0 <= mod < base for positive base // Euclidean modulus, always returns 0 <= mod < base for positive base
// Assumes this architecture's division is non-Euclidean // Assumes this architecture's division is non-Euclidean
inline int eucMod(int a, int base) { inline int eucMod(int a, int base) {
@@ -46,7 +65,9 @@ inline void setf(float *p, float v) {
*p = v; *p = v;
} }


// Linearly interpolate an array `p` with index `x`
/** Linearly interpolate an array `p` with index `x`
Assumes that the array at `p` is of length at least ceil(x)+1.
*/
inline float interpf(float *p, float x) { inline float interpf(float *p, float x) {
int i = x; int i = x;
x -= i; x -= i;
@@ -57,12 +78,7 @@ inline float interpf(float *p, float x) {
// RNG // RNG
//////////////////// ////////////////////


uint64_t randomi64(void);

// Return a uniform random number on [0.0, 1.0)
inline float randomf(void) {
return (float)randomi64() / UINT64_MAX;
}
extern std::mt19937 rng;


//////////////////// ////////////////////
// 2D float vector // 2D float vector
@@ -114,10 +130,12 @@ struct Rect {
Rect() {} Rect() {}
Rect(Vec pos, Vec size) : pos(pos), size(size) {} Rect(Vec pos, Vec size) : pos(pos), size(size) {}


/** Returns whether this Rect contains another Rect, inclusive on the top/left, non-inclusive on the bottom/right */
bool contains(Vec v) { bool contains(Vec v) {
return pos.x <= v.x && v.x < pos.x + size.x return pos.x <= v.x && v.x < pos.x + size.x
&& pos.y <= v.y && v.y < pos.y + size.y; && pos.y <= v.y && v.y < pos.y + size.y;
} }
/** Returns whether this Rect overlaps with another Rect */
bool intersects(Rect r) { bool intersects(Rect r) {
return (pos.x + size.x > r.pos.x && r.pos.x + r.size.x > pos.x) return (pos.x + size.x > r.pos.x && r.pos.x + r.size.x > pos.x)
&& (pos.y + size.y > r.pos.y && r.pos.y + r.size.y > pos.y); && (pos.y + size.y > r.pos.y && r.pos.y + r.size.y > pos.y);
@@ -134,6 +152,14 @@ struct Rect {
Vec getBottomRight() { Vec getBottomRight() {
return pos.plus(size); return pos.plus(size);
} }
/** Clamps the position to fix inside a bounding box */
Rect clamp(Rect bound) {
Rect r;
r.size = size;
r.pos.x = clampf(pos.x, bound.pos.x, bound.pos.x + bound.size.x - size.x);
r.pos.y = clampf(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y);
return r;
}
}; };






+ 109
- 56
include/widgets.hpp View File

@@ -7,8 +7,6 @@
#include <list> #include <list>
#include <map> #include <map>


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


#include "../lib/nanovg/src/nanovg.h" #include "../lib/nanovg/src/nanovg.h"
@@ -31,10 +29,10 @@ struct OutputPort;
// base class and traits // base class and traits
//////////////////// ////////////////////


// A node in the 2D scene graph
/** A node in the 2D scene graph */
struct Widget { struct Widget {
// Stores position and size
Rect box = Rect(Vec(0, 0), Vec(INFINITY, INFINITY));
/** Stores position and size */
Rect box = Rect(Vec(), Vec(INFINITY, INFINITY));
Widget *parent = NULL; Widget *parent = NULL;
std::list<Widget*> children; std::list<Widget*> children;


@@ -42,55 +40,92 @@ struct Widget {


Vec getAbsolutePos(); Vec getAbsolutePos();
Rect getChildrenBoundingBox(); Rect getChildrenBoundingBox();
template <class T>
T *getAncestorOfType() {
if (!parent) return NULL;
T *p = dynamic_cast<T*>(parent);
if (p) return p;
return parent->getAncestorOfType<T>();
}


// Gives ownership of widget to this widget instance
/** Adds widget to list of children.
Gives ownership of widget to this widget instance.
*/
void addChild(Widget *widget); void addChild(Widget *widget);
// Does not delete widget but transfers ownership to caller // Does not delete widget but transfers ownership to caller
// Silenty fails if widget is not a child // Silenty fails if widget is not a child
void removeChild(Widget *widget); void removeChild(Widget *widget);
void clearChildren(); void clearChildren();


// Advances the module by one frame
/** Advances the module by one frame */
virtual void step(); virtual void step();
// Draws to NanoVG context
/** Draws to NanoVG context */
virtual void draw(NVGcontext *vg); virtual void draw(NVGcontext *vg);


// Override this to return NULL if the widget is to be "invisible" to mouse events.
// Override this to return `this` if the widget is to override events of its children.
virtual Widget *pick(Vec pos);

// Events // Events


virtual void onMouseDown(int button) {}
virtual void onMouseUp(int button) {}
virtual void onMouseMove(Vec mouseRel) {}
/** Called when a mouse button is pressed over this widget
0 for left, 1 for right, 2 for middle.
Return `this` to accept the event.
Return NULL to reject the event and pass it to the widget behind this one.
*/
virtual Widget *onMouseDown(Vec pos, int button);
virtual Widget *onMouseUp(Vec pos, int button);
virtual Widget *onMouseMove(Vec pos, Vec mouseRel);
/** Called when this widget begins responding to `onMouseMove` events */
virtual void onMouseEnter() {} virtual void onMouseEnter() {}
/** Called when another widget begins responding to `onMouseMove` events */
virtual void onMouseLeave() {} virtual void onMouseLeave() {}
virtual void onScroll(Vec scrollRel) {}
virtual Widget *onScroll(Vec pos, Vec scrollRel);

/** Called when a widget responds to `onMouseDown` for a left button press */
virtual void onDragStart() {} virtual void onDragStart() {}
virtual void onDragDrop(Widget *origin) {}
virtual void onDragHover(Widget *origin) {}
/** Called when a widget responds to `onMouseMove` and is being dragged */
virtual void onDragMove(Vec mouseRel) {} virtual void onDragMove(Vec mouseRel) {}
/** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */
virtual void onDragDrop(Widget *origin) {}
/** Called when the left button is released and this widget is being dragged */
virtual void onDragEnd() {} virtual void onDragEnd() {}
virtual void onResize() {}
virtual void onAction() {} virtual void onAction() {}
virtual void onChange() {} virtual void onChange() {}
}; };


// Widget that does not respond to events
/** Widget that does not respond to events */
struct TransparentWidget : virtual Widget { struct TransparentWidget : virtual Widget {
Widget *pick(Vec pos) { return NULL; }
};

// Widget that does not respond to events, but allows its children to
struct TranslucentWidget : virtual Widget {
Widget *pick(Vec pos) {
Widget *picked = Widget::pick(pos);
if (picked == this) {
return NULL;
}
return picked;
Widget *onMouseDown(Vec pos, int button) {return NULL;}
Widget *onMouseUp(Vec pos, int button) {return NULL;}
Widget *onMouseMove(Vec pos, Vec mouseRel) {return NULL;}
Widget *onScroll(Vec pos, Vec scrollRel) {return NULL;}
};

/** Widget that itself responds to mouse events */
struct OpaqueWidget : virtual Widget {
Widget *onMouseDown(Vec pos, int button) {
Widget *w = Widget::onMouseDown(pos, button);
if (w) return w;
onMouseDown(button);
return this;
} }
Widget *onMouseUp(Vec pos, int button) {
Widget *w = Widget::onMouseUp(pos, button);
if (w) return w;
onMouseUp(button);
return this;
}
Widget *onMouseMove(Vec pos, Vec mouseRel) {
Widget *w = Widget::onMouseMove(pos, mouseRel);
if (w) return w;
onMouseMove(mouseRel);
return this;
}

/** "High level" events called by the above lower level events.
Use these if you don't care about the clicked position.
*/
virtual void onMouseDown(int button) {}
virtual void onMouseUp(int button) {}
virtual void onMouseMove(Vec mouseRel) {}
}; };


struct SpriteWidget : virtual Widget { struct SpriteWidget : virtual Widget {
@@ -107,38 +142,49 @@ struct QuantityWidget : virtual Widget {
float maxValue = 1.0; float maxValue = 1.0;
float defaultValue = 0.0; float defaultValue = 0.0;
std::string label; std::string label;
// Include a space character if you want a space after the number, e.g. " Hz"
/** Include a space character if you want a space after the number, e.g. " Hz" */
std::string unit; std::string unit;
/** The digit place to round for displaying values.
A precision of -2 will display as "1.00" for example.
*/
int precision = -2;


QuantityWidget();
void setValue(float value); void setValue(float value);
void setLimits(float minValue, float maxValue); void setLimits(float minValue, float maxValue);
void setDefaultValue(float defaultValue); void setDefaultValue(float defaultValue);
/** Generates the display value */
std::string getText();
}; };


//////////////////// ////////////////////
// gui elements // gui elements
//////////////////// ////////////////////


struct Label : TransparentWidget {
struct Label : Widget {
std::string text; std::string text;
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
}; };


// Deletes itself from parent when clicked // Deletes itself from parent when clicked
struct MenuOverlay : Widget {
void onMouseDown(int button);
struct MenuOverlay : OpaqueWidget {
void step();
Widget *onScroll(Vec pos, Vec scrollRel) {
return this;
}
void onDragDrop(Widget *origin);
}; };


struct Menu : Widget {
struct Menu : OpaqueWidget {
Menu() { Menu() {
box.size = Vec(0, 0); box.size = Vec(0, 0);
} }
// Transfers ownership, like addChild()
// Resizes menu and calls addChild()
void pushChild(Widget *child); void pushChild(Widget *child);
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
}; };


struct MenuEntry : Widget {
struct MenuEntry : OpaqueWidget {
std::string text; std::string text;
MenuEntry() { MenuEntry() {
box.size = Vec(0, BND_WIDGET_HEIGHT); box.size = Vec(0, BND_WIDGET_HEIGHT);
@@ -155,12 +201,12 @@ struct MenuItem : MenuEntry {


void draw(NVGcontext *vg); void draw(NVGcontext *vg);


void onMouseUp(int button);
void onMouseEnter(); void onMouseEnter();
void onMouseLeave() ; void onMouseLeave() ;
void onDragDrop(Widget *origin);
}; };


struct Button : Widget {
struct Button : OpaqueWidget {
std::string text; std::string text;
BNDwidgetState state = BND_DEFAULT; BNDwidgetState state = BND_DEFAULT;


@@ -175,7 +221,7 @@ struct ChoiceButton : Button {
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
}; };


struct Slider : QuantityWidget {
struct Slider : OpaqueWidget, QuantityWidget {
BNDwidgetState state = BND_DEFAULT; BNDwidgetState state = BND_DEFAULT;


Slider(); Slider();
@@ -185,7 +231,7 @@ struct Slider : QuantityWidget {
void onDragEnd(); void onDragEnd();
}; };


struct ScrollBar : Widget {
struct ScrollBar : OpaqueWidget {
enum { VERTICAL, HORIZONTAL } orientation; enum { VERTICAL, HORIZONTAL } orientation;
float containerOffset = 0.0; float containerOffset = 0.0;
float containerSize = 0.0; float containerSize = 0.0;
@@ -200,18 +246,18 @@ struct ScrollBar : Widget {
}; };


// Handles a container with scrollbars // Handles a container with scrollbars
struct ScrollWidget : Widget {
struct ScrollWidget : OpaqueWidget {
Widget *container; Widget *container;
ScrollBar *hScrollBar; ScrollBar *hScrollBar;
ScrollBar *vScrollBar; ScrollBar *vScrollBar;


ScrollWidget(); ScrollWidget();
void step();
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
void onResize();
void onScroll(Vec scrollRel);
Widget *onScroll(Vec pos, Vec scrollRel);
}; };


struct Tooltip : TransparentWidget {
struct Tooltip : Widget {
void step(); void step();
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
}; };
@@ -222,7 +268,7 @@ struct Tooltip : TransparentWidget {


// A 1U module should be 15x380. Thus the width of a module should be a factor of 15. // A 1U module should be 15x380. Thus the width of a module should be a factor of 15.
struct Model; struct Model;
struct ModuleWidget : Widget {
struct ModuleWidget : OpaqueWidget {
Model *model = NULL; Model *model = NULL;
// Eventually this should be replaced with a `moduleId` which will be used for inter-process communication between the gui world and the audio world. // Eventually this should be replaced with a `moduleId` which will be used for inter-process communication between the gui world and the audio world.
Module *module = NULL; Module *module = NULL;
@@ -256,7 +302,7 @@ struct ModuleWidget : Widget {
void onMouseDown(int button); void onMouseDown(int button);
}; };


struct WireWidget : Widget {
struct WireWidget : OpaqueWidget {
OutputPort *outputPort = NULL; OutputPort *outputPort = NULL;
InputPort *inputPort = NULL; InputPort *inputPort = NULL;
Wire *wire = NULL; Wire *wire = NULL;
@@ -270,12 +316,11 @@ struct WireWidget : Widget {
void drawInputPlug(NVGcontext *vg); void drawInputPlug(NVGcontext *vg);
}; };


struct RackWidget : Widget {
struct RackWidget : OpaqueWidget {
// Only put ModuleWidgets in here // Only put ModuleWidgets in here
Widget *moduleContainer; Widget *moduleContainer;
// Only put WireWidgets in here // Only put WireWidgets in here
Widget *wireContainer; Widget *wireContainer;
// An unowned reference to the currently dragged wire
WireWidget *activeWire = NULL; WireWidget *activeWire = NULL;


RackWidget(); RackWidget();
@@ -294,6 +339,12 @@ struct RackWidget : Widget {
void onMouseDown(int button); void onMouseDown(int button);
}; };


struct ModulePanel : TransparentWidget {
std::string imageFilename;
NVGcolor backgroundColor;
void draw(NVGcontext *vg);
};

//////////////////// ////////////////////
// params // params
//////////////////// ////////////////////
@@ -308,7 +359,7 @@ struct Screw : TransparentWidget, SpriteWidget {
Screw(); Screw();
}; };


struct ParamWidget : QuantityWidget {
struct ParamWidget : OpaqueWidget, QuantityWidget {
Module *module = NULL; Module *module = NULL;
int paramId; int paramId;


@@ -362,7 +413,7 @@ struct MomentarySwitch : virtual Switch {
// ports // ports
//////////////////// ////////////////////


struct Port : Widget {
struct Port : OpaqueWidget, SpriteWidget {
Module *module = NULL; Module *module = NULL;
WireWidget *connectedWire = NULL; WireWidget *connectedWire = NULL;


@@ -371,7 +422,6 @@ struct Port : Widget {
void disconnect(); void disconnect();


int type; int type;
void draw(NVGcontext *vg);
void drawGlow(NVGcontext *vg); void drawGlow(NVGcontext *vg);
void onMouseDown(int button); void onMouseDown(int button);
void onDragEnd(); void onDragEnd();
@@ -397,17 +447,20 @@ struct OutputPort : Port {
// scene // scene
//////////////////// ////////////////////


struct Toolbar : Widget {
struct Toolbar : OpaqueWidget {
Slider *wireOpacitySlider; Slider *wireOpacitySlider;
Slider *wireTensionSlider;
Toolbar(); Toolbar();
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
}; };


struct Scene : Widget {
struct Scene : OpaqueWidget {
Toolbar *toolbar; Toolbar *toolbar;
ScrollWidget *scrollWidget; ScrollWidget *scrollWidget;
Widget *overlay = NULL;
Scene(); Scene();
void onResize();
void setOverlay(Widget *w);
void step();
}; };






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

@@ -169,7 +169,7 @@ struct AudioChoice : ChoiceButton {
menu->pushChild(audioItem); menu->pushChild(audioItem);
} }
overlay->addChild(menu); overlay->addChild(menu);
gScene->addChild(overlay);
gScene->setOverlay(overlay);
} }
}; };




+ 13
- 8
src/core/MidiInterface.cpp View File

@@ -26,9 +26,10 @@ struct MidiInterface : Module {
PortMidiStream *stream = NULL; PortMidiStream *stream = NULL;
std::list<int> notes; std::list<int> notes;
bool pedal = false; bool pedal = false;
bool gate = false;
int note = 64; // C4 int note = 64; // C4
int pitchWheel = 64; int pitchWheel = 64;
bool retrigger = true;
bool retriggered = false;


MidiInterface(); MidiInterface();
~MidiInterface(); ~MidiInterface();
@@ -74,6 +75,11 @@ void MidiInterface::step() {
} }


if (outputs[GATE_OUTPUT]) { if (outputs[GATE_OUTPUT]) {
bool gate = pedal || !notes.empty();
if (retrigger && retriggered) {
gate = false;
retriggered = false;
}
*outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; *outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0;
} }
if (outputs[PITCH_OUTPUT]) { if (outputs[PITCH_OUTPUT]) {
@@ -113,12 +119,14 @@ void MidiInterface::openPort(int portId) {
} }


void MidiInterface::pressNote(int note) { void MidiInterface::pressNote(int note) {
// Remove existing similar note
auto it = std::find(notes.begin(), notes.end(), note); auto it = std::find(notes.begin(), notes.end(), note);
if (it != notes.end()) if (it != notes.end())
notes.erase(it); notes.erase(it);
// Push note
notes.push_back(note); notes.push_back(note);
this->gate = true;
this->note = note; this->note = note;
retriggered = true;
} }


void MidiInterface::releaseNote(int note) { void MidiInterface::releaseNote(int note) {
@@ -135,10 +143,7 @@ void MidiInterface::releaseNote(int note) {
auto it2 = notes.end(); auto it2 = notes.end();
it2--; it2--;
this->note = *it2; this->note = *it2;
}
else {
// No notes are held, turn the gate off
this->gate = false;
retriggered = true;
} }
} }


@@ -158,7 +163,7 @@ void MidiInterface::processMidi(long msg) {
releaseNote(data1); releaseNote(data1);
} break; } break;
case 0x9: // note on case 0x9: // note on
if (data2) {
if (data2 > 0) {
pressNote(data1); pressNote(data1);
} }
else { else {
@@ -210,7 +215,7 @@ struct MidiChoice : ChoiceButton {
menu->pushChild(midiItem); menu->pushChild(midiItem);
} }
overlay->addChild(menu); overlay->addChild(menu);
gScene->addChild(overlay);
gScene->setOverlay(overlay);
} }
}; };




+ 57
- 44
src/gui.cpp View File

@@ -1,6 +1,9 @@
#include <unistd.h> #include <unistd.h>
#include "Rack.hpp" #include "Rack.hpp"


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

// #define NANOVG_GLEW // #define NANOVG_GLEW
#define NANOVG_IMPLEMENTATION #define NANOVG_IMPLEMENTATION
#include "../lib/nanovg/src/nanovg.h" #include "../lib/nanovg/src/nanovg.h"
@@ -9,6 +12,10 @@
#define BLENDISH_IMPLEMENTATION #define BLENDISH_IMPLEMENTATION
#include "../lib/oui/blendish.h" #include "../lib/oui/blendish.h"


extern "C" {
#include "../lib/noc/noc_file_dialog.h"
}



namespace rack { namespace rack {


@@ -18,44 +25,45 @@ RackWidget *gRackWidget = NULL;
Vec gMousePos; Vec gMousePos;
Widget *gHoveredWidget = NULL; Widget *gHoveredWidget = NULL;
Widget *gDraggedWidget = NULL; Widget *gDraggedWidget = NULL;
Widget *gSelectedWidget = NULL;


static GLFWwindow *window;
static GLFWwindow *window = NULL;
static NVGcontext *vg = NULL; static NVGcontext *vg = NULL;




void windowSizeCallback(GLFWwindow* window, int width, int height) { void windowSizeCallback(GLFWwindow* window, int width, int height) {
gScene->box.size = Vec(width, height); gScene->box.size = Vec(width, height);
gScene->onResize();
} }


void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
if (gHoveredWidget) {
// onMouseDown and onMouseUp
if (action == GLFW_PRESS) {
gHoveredWidget->onMouseDown(button);
}
else if (action == GLFW_RELEASE) {
gHoveredWidget->onMouseUp(button);
}
}
if (action == GLFW_PRESS) {
// onMouseDown
Widget *w = gScene->onMouseDown(gMousePos, button);
gSelectedWidget = w;


// onDragStart, onDragEnd, and onDragDrop
if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (action == GLFW_PRESS) {
if (gHoveredWidget) {
gDraggedWidget = gHoveredWidget;
if (button == GLFW_MOUSE_BUTTON_LEFT) {
gDraggedWidget = w;
if (gDraggedWidget) {
// onDragStart
gDraggedWidget->onDragStart(); gDraggedWidget->onDragStart();
} }
} }
else if (action == GLFW_RELEASE) {
}
else if (action == GLFW_RELEASE) {
// onMouseUp
Widget *w = gScene->onMouseUp(gMousePos, button);

if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (gDraggedWidget) { if (gDraggedWidget) {
Widget *dropped = gScene->pick(gMousePos);
if (dropped) {
dropped->onDragDrop(gDraggedWidget);
}
// onDragDrop
w->onDragDrop(gDraggedWidget);
}
// gDraggedWidget might have been set to null in the last event, recheck here
if (gDraggedWidget) {
// onDragEnd
gDraggedWidget->onDragEnd(); gDraggedWidget->onDragEnd();
gDraggedWidget = NULL;
} }
gDraggedWidget = NULL;
} }
} }
} }
@@ -70,40 +78,33 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
} }


// onScroll // onScroll
int middleButton = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE);
if (middleButton == GLFW_PRESS) {
gScene->scrollWidget->onScroll(mouseRel.neg());
}

Widget *hovered = gScene->pick(mousePos);
// int middleButton = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE);
// if (middleButton == GLFW_PRESS) {
// gScene->scrollWidget->onScroll(mouseRel.neg());
// }


if (gDraggedWidget) { if (gDraggedWidget) {
// onDragMove // onDragMove
// Drag slower if Ctrl is held // Drag slower if Ctrl is held
bool fine = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL ) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
float factor = fine ? 0.1 : 1.0;
bool fine = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
float factor = fine ? 1.0/8.0 : 1.0;
gDraggedWidget->onDragMove(mouseRel.mult(factor)); gDraggedWidget->onDragMove(mouseRel.mult(factor));
// onDragHover
if (hovered) {
hovered->onDragHover(gDraggedWidget);
}
} }
else { else {
// onMouseEnter and onMouseLeave
// onMouseMove
Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel);

if (hovered != gHoveredWidget) { if (hovered != gHoveredWidget) {
if (gHoveredWidget) { if (gHoveredWidget) {
// onMouseLeave
gHoveredWidget->onMouseLeave(); gHoveredWidget->onMouseLeave();
} }
if (hovered) { if (hovered) {
// onMouseEnter
hovered->onMouseEnter(); hovered->onMouseEnter();
} }
} }
gHoveredWidget = hovered; gHoveredWidget = hovered;

// onMouseMove
if (hovered) {
hovered->onMouseMove(mouseRel);
}
} }
} }


@@ -119,7 +120,7 @@ void cursorEnterCallback(GLFWwindow* window, int entered) {
void scrollCallback(GLFWwindow *window, double x, double y) { void scrollCallback(GLFWwindow *window, double x, double y) {
Vec scrollRel = Vec(x, y); Vec scrollRel = Vec(x, y);
// onScroll // onScroll
gScene->scrollWidget->onScroll(scrollRel.mult(-95));
gScene->onScroll(gMousePos, scrollRel.mult(-95));
} }


void charCallback(GLFWwindow *window, unsigned int value) { void charCallback(GLFWwindow *window, unsigned int value) {
@@ -151,7 +152,7 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods


void renderGui() { void renderGui() {
int width, height; int width, height;
// The docs say to use the framebuffer size to get pixel-perfect matching for high-DPI displays, but I actually don't want this. A "screen" pixel
// The docs say to use the framebuffer size to get pixel-perfect matching for high-DPI displays, but I actually don't want this. On 2x displays, one gui pixel should be 2x2 monitor pixels.
// glfwGetFramebufferSize(window, &width, &height); // glfwGetFramebufferSize(window, &width, &height);
glfwGetWindowSize(window, &width, &height); glfwGetWindowSize(window, &width, &height);


@@ -176,10 +177,12 @@ 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, "Rack", NULL, NULL);
window = glfwCreateWindow(1020, 700, gApplicationName.c_str(), NULL, NULL);
assert(window); assert(window);
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);


glfwSwapInterval(1);

glfwSetWindowSizeCallback(window, windowSizeCallback); glfwSetWindowSizeCallback(window, windowSizeCallback);
glfwSetMouseButtonCallback(window, mouseButtonCallback); glfwSetMouseButtonCallback(window, mouseButtonCallback);
// glfwSetCursorPosCallback(window, cursorPosCallback); // glfwSetCursorPosCallback(window, cursorPosCallback);
@@ -203,7 +206,7 @@ void guiInit() {


// Set up Blendish // Set up Blendish
bndSetFont(loadFont("res/DejaVuSans.ttf")); bndSetFont(loadFont("res/DejaVuSans.ttf"));
// bndSetIconImage(loadImage("res/blender_icons16.png"));
// bndSetIconImage(loadImage("res/icons.png"));


gScene = new Scene(); gScene = new Scene();
} }
@@ -249,6 +252,16 @@ void guiCursorUnlock() {
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
} }


const char *guiSaveDialog(const char *filters, const char *filename) {
return noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, filename);
}

const char *guiOpenDialog(const char *filters, const char *filename) {
return noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, filename);
}







std::map<std::string, int> images; std::map<std::string, int> images;


+ 10
- 1
src/main.cpp View File

@@ -7,6 +7,14 @@
#include "Rack.hpp" #include "Rack.hpp"




namespace rack {

std::string gApplicationName = "Virtuoso Rack";
std::string gApplicationVersion = "v0.0.0";

} // namespace rack


using namespace rack; using namespace rack;


int main() { int main() {
@@ -20,7 +28,7 @@ int main() {
assert(success); assert(success);
CFRelease(bundleURL); CFRelease(bundleURL);


// chdir(dirname(path));
chdir(dirname(path));
} }
#endif #endif


@@ -40,3 +48,4 @@ int main() {
pluginDestroy(); pluginDestroy();
return 0; return 0;
} }


+ 15
- 14
src/plugin.cpp View File

@@ -15,6 +15,7 @@ namespace rack {


std::list<Plugin*> gPlugins; std::list<Plugin*> gPlugins;


static
int loadPlugin(const char *path) { int loadPlugin(const char *path) {
// Load dynamic/shared library // Load dynamic/shared library
#if defined(WINDOWS) #if defined(WINDOWS)
@@ -24,8 +25,8 @@ int loadPlugin(const char *path) {
return -1; return -1;
} }
#elif defined(LINUX) || defined(APPLE) #elif defined(LINUX) || defined(APPLE)
char ppath[512];
snprintf(ppath, 512, "./%s", path);
char ppath[1024];
snprintf(ppath, sizeof(ppath), "./%s", path);
void *handle = dlopen(ppath, RTLD_NOW | RTLD_GLOBAL); void *handle = dlopen(ppath, RTLD_NOW | RTLD_GLOBAL);
if (!handle) { if (!handle) {
fprintf(stderr, "Failed to load library %s: %s\n", path, dlerror()); fprintf(stderr, "Failed to load library %s: %s\n", path, dlerror());
@@ -61,24 +62,24 @@ void pluginInit() {
// Load core // Load core
Plugin *corePlugin = coreInit(); Plugin *corePlugin = coreInit();
gPlugins.push_back(corePlugin); gPlugins.push_back(corePlugin);

// Search for plugin libraries // Search for plugin libraries
#if defined(WINDOWS) #if defined(WINDOWS)
// TODO
// WIN32_FIND_DATA ffd;
// HANDLE hFind = FindFirstFile("plugins/*/plugin.dll", &ffd);
// if (hFind != INVALID_HANDLE_VALUE) {
// do {
// loadPlugin(ffd.cFileName);
// } while (FindNextFile(hFind, &ffd));
// }
// FindClose(hFind);
loadPlugin("plugins/Simple/plugin.dll");
loadPlugin("plugins/AudibleInstruments/plugin.dll");
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile("plugins/*", &ffd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
char pluginFilename[MAX_PATH];
snprintf(pluginFilename, sizeof(pluginFilename), "plugins/%s/plugin.dll", ffd.cFileName);
loadPlugin(pluginFilename);
} while (FindNextFile(hFind, &ffd));
}
FindClose(hFind);


#elif defined(LINUX) || defined(APPLE) #elif defined(LINUX) || defined(APPLE)
#if defined(LINUX) #if defined(LINUX)
const char *globPath = "plugins/*/plugin.so"; const char *globPath = "plugins/*/plugin.so";
#elif defined(WINDOWS)
const char *globPath = "plugins/*/plugin.dll";
#elif defined(APPLE) #elif defined(APPLE)
const char *globPath = "plugins/*/plugin.dylib"; const char *globPath = "plugins/*/plugin.dylib";
#endif #endif


+ 2
- 70
src/util.cpp View File

@@ -1,77 +1,9 @@
#include "util.hpp" #include "util.hpp"
#include <stdint.h>




namespace rack { namespace rack {



/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org)

To the extent possible under law, the author has dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty.

See <http://creativecommons.org/publicdomain/zero/1.0/>. */

/* This is the successor to xorshift128+. It is the fastest full-period
generator passing BigCrush without systematic failures, but due to the
relatively short period it is acceptable only for applications with a
mild amount of parallelism; otherwise, use a xorshift1024* generator.

Beside passing BigCrush, this generator passes the PractRand test suite
up to (and included) 16TB, with the exception of binary rank tests,
which fail due to the lowest bit being an LFSR; all other bits pass all
tests. We suggest to use a sign test to extract a random Boolean value.

Note that the generator uses a simulated rotate operation, which most C
compilers will turn into a single instruction. In Java, you can use
Long.rotateLeft(). In languages that do not make low-level rotation
instructions accessible xorshift128+ could be faster.

The state must be seeded so that it is not everywhere zero. If you have
a 64-bit seed, we suggest to seed a splitmix64 generator and use its
output to fill s. */

static uint64_t s[2] = {15879747621982183911ULL, 4486682751732329651ULL};

static inline uint64_t rotl(const uint64_t x, int k) {
return (x << k) | (x >> (64 - k));
}

uint64_t randomi64(void) {
const uint64_t s0 = s[0];
uint64_t s1 = s[1];
const uint64_t result = s0 + s1;

s1 ^= s0;
s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b
s[1] = rotl(s1, 36); // c

return result;
}


/* This is the jump function for the generator. It is equivalent
to 2^64 calls to next(); it can be used to generate 2^64
non-overlapping subsequences for parallel computations. */

void randomjump(void) {
static const uint64_t JUMP[] = { 0xbeac0467eba5facb, 0xd86b048b86aa9922 };

uint64_t s0 = 0;
uint64_t s1 = 0;
for(unsigned int i = 0; i < sizeof JUMP / sizeof *JUMP; i++)
for(int b = 0; b < 64; b++) {
if (JUMP[i] & 1ULL << b) {
s0 ^= s[0];
s1 ^= s[1];
}
randomi64();
}

s[0] = s0;
s[1] = s1;
}

static std::random_device rd;
std::mt19937 rng(rd());


} // namespace rack } // namespace rack

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

@@ -4,7 +4,7 @@
namespace rack { namespace rack {


void InputPort::draw(NVGcontext *vg) { void InputPort::draw(NVGcontext *vg) {
Port::draw(vg);
SpriteWidget::draw(vg);
if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) {
Port::drawGlow(vg); Port::drawGlow(vg);
} }
@@ -27,6 +27,7 @@ void InputPort::onDragStart() {
} }


void InputPort::onDragDrop(Widget *origin) { void InputPort::onDragDrop(Widget *origin) {
printf("%p\n", origin);
if (connectedWire) return; if (connectedWire) return;
if (gRackWidget->activeWire) { if (gRackWidget->activeWire) {
if (gRackWidget->activeWire->inputPort) return; if (gRackWidget->activeWire->inputPort) return;


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

@@ -15,16 +15,13 @@ void MenuItem::onMouseLeave() {
state = BND_DEFAULT; state = BND_DEFAULT;
} }


void MenuItem::onMouseUp(int button) {
void MenuItem::onDragDrop(Widget *origin) {
if (origin != this)
return;

onAction(); onAction();
// Remove overlay from scene
// HACK
Widget *overlay = parent->parent;
assert(overlay);
if (overlay->parent) {
overlay->parent->removeChild(overlay);
}
delete overlay;
// deletes `this`
gScene->setOverlay(NULL);
} }






+ 11
- 5
src/widgets/MenuOverlay.cpp View File

@@ -3,12 +3,18 @@


namespace rack { namespace rack {


void MenuOverlay::onMouseDown(int button) {
if (parent) {
parent->removeChild(this);
void MenuOverlay::step() {
// Try to fit all children into the overlay's box
for (Widget *child : children) {
child->box = child->box.clamp(box);
}
}

void MenuOverlay::onDragDrop(Widget *origin) {
if (origin == this) {
// deletes `this`
gScene->setOverlay(NULL);
} }
// Commit sudoku
delete this;
} }






+ 29
- 0
src/widgets/ModulePanel.cpp View File

@@ -0,0 +1,29 @@
#include "Rack.hpp"


namespace rack {

void ModulePanel::draw(NVGcontext *vg) {
nvgBeginPath(vg);
nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y);
NVGpaint paint;
// Background gradient
Vec c = box.getTopRight();
float length = box.size.norm();
NVGcolor color1 = nvgLerpRGBA(backgroundColor, nvgRGBf(1.0, 1.0, 1.0), 0.5);
NVGcolor color2 = backgroundColor;
paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, color1, color2);
nvgFillPaint(vg, paint);
nvgFill(vg);
// Background image
if (!imageFilename.empty()) {
int imageId = loadImage(imageFilename);
int width, height;
nvgImageSize(vg, imageId, &width, &height);
paint = nvgImagePattern(vg, box.pos.x, box.pos.y, width, height, 0.0, imageId, 1.0);
nvgFillPaint(vg, paint);
nvgFill(vg);
}
}

} // namespace rack

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

@@ -145,7 +145,7 @@ struct DeleteModuleMenuItem : MenuItem {
}; };


void ModuleWidget::onMouseDown(int button) { void ModuleWidget::onMouseDown(int button) {
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
if (button == 1) {
MenuOverlay *overlay = new MenuOverlay(); MenuOverlay *overlay = new MenuOverlay();
Menu *menu = new Menu(); Menu *menu = new Menu();
menu->box.pos = gMousePos; menu->box.pos = gMousePos;
@@ -155,12 +155,12 @@ void ModuleWidget::onMouseDown(int button) {
menu->pushChild(menuLabel); menu->pushChild(menuLabel);


ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); ResetParamsMenuItem *resetItem = new ResetParamsMenuItem();
resetItem->text = "Initialize parameters";
resetItem->text = "Reset parameters";
resetItem->moduleWidget = this; resetItem->moduleWidget = this;
menu->pushChild(resetItem); menu->pushChild(resetItem);


DisconnectPortsMenuItem *disconnectItem = new DisconnectPortsMenuItem(); DisconnectPortsMenuItem *disconnectItem = new DisconnectPortsMenuItem();
disconnectItem->text = "Disconnect wires";
disconnectItem->text = "Disconnect cables";
disconnectItem->moduleWidget = this; disconnectItem->moduleWidget = this;
menu->pushChild(disconnectItem); menu->pushChild(disconnectItem);


@@ -175,7 +175,7 @@ void ModuleWidget::onMouseDown(int button) {
menu->pushChild(deleteItem); menu->pushChild(deleteItem);
} }
overlay->addChild(menu); overlay->addChild(menu);
gScene->addChild(overlay);
gScene->setOverlay(overlay);
} }
} }




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

@@ -4,7 +4,7 @@
namespace rack { namespace rack {


void OutputPort::draw(NVGcontext *vg) { void OutputPort::draw(NVGcontext *vg) {
Port::draw(vg);
SpriteWidget::draw(vg);
if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) {
Port::drawGlow(vg); Port::drawGlow(vg);
} }


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

@@ -13,13 +13,15 @@ void ParamWidget::fromJson(json_t *root) {
} }


void ParamWidget::onMouseDown(int button) { void ParamWidget::onMouseDown(int button) {
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
if (button == 1) {
setValue(defaultValue); setValue(defaultValue);
} }
} }


void ParamWidget::onChange() { void ParamWidget::onChange() {
assert(module);
if (!module)
return;

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


+ 7
- 15
src/widgets/Port.cpp View File

@@ -5,7 +5,12 @@ namespace rack {


Port::Port() { Port::Port() {
box.size = Vec(20, 20); box.size = Vec(20, 20);
type = randomi64() % 5;
spriteOffset = Vec(-18, -18);
spriteSize = Vec(56, 56);
spriteFilename = "res/port.png";
std::uniform_int_distribution<> dist(0, 4);
index = dist(rng);
} }


Port::~Port() { Port::~Port() {
@@ -20,19 +25,6 @@ void Port::disconnect() {
} }
} }


void Port::draw(NVGcontext *vg) {
Vec pos = box.pos.plus(Vec(-18, -18));
int width, height;
int imageId = loadImage("res/port.png");
nvgImageSize(vg, imageId, &width, &height);
float offsetY = type * width;
NVGpaint paint = nvgImagePattern(vg, pos.x, pos.y - offsetY, width, height, 0.0, imageId, 1.0);
nvgFillPaint(vg, paint);
nvgBeginPath(vg);
nvgRect(vg, pos.x, pos.y, width, width);
nvgFill(vg);
}

void Port::drawGlow(NVGcontext *vg) { void Port::drawGlow(NVGcontext *vg) {
Vec c = box.getCenter(); Vec c = box.getCenter();
NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5); NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5);
@@ -45,7 +37,7 @@ void Port::drawGlow(NVGcontext *vg) {
} }


void Port::onMouseDown(int button) { void Port::onMouseDown(int button) {
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
if (button == 1) {
disconnect(); disconnect();
} }
} }


+ 21
- 0
src/widgets/QuantityWidget.cpp View File

@@ -3,6 +3,10 @@


namespace rack { namespace rack {


QuantityWidget::QuantityWidget() {
onChange();
}

void QuantityWidget::setValue(float value) { void QuantityWidget::setValue(float value) {
this->value = clampf(value, minValue, maxValue); this->value = clampf(value, minValue, maxValue);
onChange(); onChange();
@@ -18,5 +22,22 @@ void QuantityWidget::setDefaultValue(float defaultValue) {
setValue(defaultValue); setValue(defaultValue);
} }


std::string QuantityWidget::getText() {
std::string text = label;
text += ": ";
char valueStr[128];
if (precision >= 0) {
float factor = powf(10.0, precision);
float v = roundf(value / factor) * factor;
snprintf(valueStr, sizeof(valueStr), "%.0f", v);
}
else {
snprintf(valueStr, sizeof(valueStr), "%.*f", -precision, value);
}
text += valueStr;
text += unit;
return text;
}



} // namespace rack } // namespace rack

+ 20
- 42
src/widgets/RackWidget.cpp View File

@@ -5,7 +5,7 @@
namespace rack { namespace rack {


RackWidget::RackWidget() { RackWidget::RackWidget() {
moduleContainer = new TranslucentWidget();
moduleContainer = new Widget();
addChild(moduleContainer); addChild(moduleContainer);


wireContainer = new TransparentWidget(); wireContainer = new TransparentWidget();
@@ -56,26 +56,10 @@ void RackWidget::loadPatch(std::string filename) {
fclose(file); fclose(file);
} }


// Recursive function to get the ModuleWidget eventually containing a widget
static ModuleWidget *getAncestorModuleWidget(Widget *w) {
if (!w) return NULL;
ModuleWidget *m = dynamic_cast<ModuleWidget*>(w);
if (m) return m;
return getAncestorModuleWidget(w->parent);
}

json_t *RackWidget::toJson() { json_t *RackWidget::toJson() {
// root // root
json_t *root = json_object(); json_t *root = json_object();


// rack
json_t *rack = json_object();
{
json_t *size = json_pack("[f, f]", (double) box.size.x, (double) box.size.y);
json_object_set_new(rack, "size", size);
}
json_object_set_new(root, "rack", rack);

// modules // modules
json_t *modulesJ = json_array(); json_t *modulesJ = json_array();
std::map<ModuleWidget*, int> moduleIds; std::map<ModuleWidget*, int> moduleIds;
@@ -102,9 +86,9 @@ json_t *RackWidget::toJson() {
// wire // wire
json_t *wire = json_object(); json_t *wire = json_object();
{ {
ModuleWidget *outputModuleWidget = getAncestorModuleWidget(wireWidget->outputPort);
ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType<ModuleWidget>();
assert(outputModuleWidget); assert(outputModuleWidget);
ModuleWidget *inputModuleWidget = getAncestorModuleWidget(wireWidget->inputPort);
ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType<ModuleWidget>();
assert(inputModuleWidget); assert(inputModuleWidget);
int outputModuleId = moduleIds[outputModuleWidget]; int outputModuleId = moduleIds[outputModuleWidget];
int inputModuleId = moduleIds[inputModuleWidget]; int inputModuleId = moduleIds[inputModuleWidget];
@@ -121,26 +105,16 @@ json_t *RackWidget::toJson() {
} }


void RackWidget::fromJson(json_t *root) { void RackWidget::fromJson(json_t *root) {
// TODO There's virtually no validation in here. Bad input will result in a crash.
// rack
json_t *rack = json_object_get(root, "rack");
assert(rack);
{
// size
json_t *size = json_object_get(rack, "size");
double width, height;
json_unpack(size, "[F, F]", &width, &height);
box.size = Vec(width, height);
}

// modules // modules
std::map<int, ModuleWidget*> moduleWidgets; std::map<int, ModuleWidget*> moduleWidgets;
json_t *modulesJ = json_object_get(root, "modules"); json_t *modulesJ = json_object_get(root, "modules");
if (!modulesJ) return;
size_t moduleId; size_t moduleId;
json_t *moduleJ; json_t *moduleJ;
json_array_foreach(modulesJ, moduleId, moduleJ) { json_array_foreach(modulesJ, moduleId, moduleJ) {
// Get plugin // Get plugin
json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); json_t *pluginSlugJ = json_object_get(moduleJ, "plugin");
if (!pluginSlugJ) continue;
const char *pluginSlug = json_string_value(pluginSlugJ); const char *pluginSlug = json_string_value(pluginSlugJ);
Plugin *plugin = NULL; Plugin *plugin = NULL;
for (Plugin *p : gPlugins) { for (Plugin *p : gPlugins) {
@@ -149,7 +123,7 @@ void RackWidget::fromJson(json_t *root) {
break; break;
} }
} }
assert(plugin);
if (!plugin) continue;


// Get model // Get model
json_t *modelSlug = json_object_get(moduleJ, "model"); json_t *modelSlug = json_object_get(moduleJ, "model");
@@ -160,10 +134,11 @@ void RackWidget::fromJson(json_t *root) {
break; break;
} }
} }
assert(model);
if (!model) continue;


// Create ModuleWidget // Create ModuleWidget
ModuleWidget *moduleWidget = model->createModuleWidget(); ModuleWidget *moduleWidget = model->createModuleWidget();
assert(moduleWidget);
moduleWidget->fromJson(moduleJ); moduleWidget->fromJson(moduleJ);
moduleContainer->addChild(moduleWidget); moduleContainer->addChild(moduleWidget);
moduleWidgets[moduleId] = moduleWidget; moduleWidgets[moduleId] = moduleWidget;
@@ -171,23 +146,25 @@ void RackWidget::fromJson(json_t *root) {


// wires // wires
json_t *wiresJ = json_object_get(root, "wires"); json_t *wiresJ = json_object_get(root, "wires");
if (!wiresJ) return;
size_t wireId; size_t wireId;
json_t *wireJ; json_t *wireJ;
json_array_foreach(wiresJ, wireId, wireJ) { json_array_foreach(wiresJ, wireId, wireJ) {
int outputModuleId, outputId; int outputModuleId, outputId;
int inputModuleId, inputId; int inputModuleId, inputId;
json_unpack(wireJ, "{s:i, s:i, s:i, s:i}",
int err = json_unpack(wireJ, "{s:i, s:i, s:i, s:i}",
"outputModuleId", &outputModuleId, "outputId", &outputId, "outputModuleId", &outputModuleId, "outputId", &outputId,
"inputModuleId", &inputModuleId, "inputId", &inputId); "inputModuleId", &inputModuleId, "inputId", &inputId);
if (err) continue;
// Get ports // Get ports
ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId];
assert(outputModuleWidget);
if (!outputModuleWidget) continue;
OutputPort *outputPort = outputModuleWidget->outputs[outputId]; OutputPort *outputPort = outputModuleWidget->outputs[outputId];
assert(outputPort);
if (!outputPort) continue;
ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId];
assert(inputModuleWidget);
if (!inputModuleWidget) continue;
InputPort *inputPort = inputModuleWidget->inputs[inputId]; InputPort *inputPort = inputModuleWidget->inputs[inputId];
assert(inputPort);
if (!inputPort) continue;
// Create WireWidget // Create WireWidget
WireWidget *wireWidget = new WireWidget(); WireWidget *wireWidget = new WireWidget();
wireWidget->outputPort = outputPort; wireWidget->outputPort = outputPort;
@@ -253,8 +230,9 @@ void RackWidget::step() {
} }
} }


// Autosave every 2 minutes
if (++frame >= 60*60*2) {
// Autosave every 15 seconds
// (This is alpha software, expect crashes!)
if (++frame >= 60*15) {
frame = 0; frame = 0;
savePatch("autosave.json"); savePatch("autosave.json");
} }
@@ -299,7 +277,7 @@ struct AddModuleMenuItem : MenuItem {
}; };


void RackWidget::onMouseDown(int button) { void RackWidget::onMouseDown(int button) {
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
if (button == 1) {
// Get relative position of the click // Get relative position of the click
Vec modulePos = gMousePos.minus(getAbsolutePos()); Vec modulePos = gMousePos.minus(getAbsolutePos());


@@ -320,7 +298,7 @@ void RackWidget::onMouseDown(int button) {
} }
} }
overlay->addChild(menu); overlay->addChild(menu);
gScene->addChild(overlay);
gScene->setOverlay(overlay);
} }
} }




+ 19
- 2
src/widgets/Scene.cpp View File

@@ -17,10 +17,27 @@ Scene::Scene() {
scrollWidget->box.pos.y = toolbar->box.size.y; scrollWidget->box.pos.y = toolbar->box.size.y;
} }


void Scene::onResize() {
void Scene::setOverlay(Widget *w) {
if (overlay) {
removeChild(overlay);
delete overlay;
overlay = NULL;
}
if (w) {
addChild(w);
overlay = w;
overlay->box.pos = Vec();
}
}

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

Widget::step();
} }






+ 3
- 1
src/widgets/Screw.cpp View File

@@ -8,7 +8,9 @@ Screw::Screw() {
spriteOffset = Vec(-7, -7); spriteOffset = Vec(-7, -7);
spriteSize = Vec(29, 29); spriteSize = Vec(29, 29);
spriteFilename = "res/screw.png"; spriteFilename = "res/screw.png";
index = randomi64() % 5;

std::uniform_int_distribution<> dist(0, 4);
index = dist(rng);
} }






+ 16
- 10
src/widgets/ScrollWidget.cpp View File

@@ -16,6 +16,18 @@ ScrollWidget::ScrollWidget() {
addChild(vScrollBar); addChild(vScrollBar);
} }


void ScrollWidget::step() {
Vec b = Vec(box.size.x - vScrollBar->box.size.x, box.size.y - hScrollBar->box.size.y);

hScrollBar->box.pos.y = b.y;
hScrollBar->box.size.x = b.x;

vScrollBar->box.pos.x = b.x;
vScrollBar->box.size.y = b.y;

Widget::step();
}

void ScrollWidget::draw(NVGcontext *vg) { void ScrollWidget::draw(NVGcontext *vg) {
// Update the scrollbar sizes // Update the scrollbar sizes
Vec c = container->getChildrenBoundingBox().getBottomRight(); Vec c = container->getChildrenBoundingBox().getBottomRight();
@@ -28,19 +40,13 @@ void ScrollWidget::draw(NVGcontext *vg) {
Widget::draw(vg); Widget::draw(vg);
} }


void ScrollWidget::onResize() {
Vec b = Vec(box.size.x - vScrollBar->box.size.x, box.size.y - hScrollBar->box.size.y);

hScrollBar->box.pos.y = b.y;
hScrollBar->box.size.x = b.x;

vScrollBar->box.pos.x = b.x;
vScrollBar->box.size.y = b.y;
}
Widget *ScrollWidget::onScroll(Vec pos, Vec scrollRel) {
Widget *w = Widget::onScroll(pos, scrollRel);
if (w) return w;


void ScrollWidget::onScroll(Vec scrollRel) {
hScrollBar->move(scrollRel.x); hScrollBar->move(scrollRel.x);
vScrollBar->move(scrollRel.y); vScrollBar->move(scrollRel.y);
return this;
} }






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

@@ -11,9 +11,7 @@ Slider::Slider() {


void Slider::draw(NVGcontext *vg) { void Slider::draw(NVGcontext *vg) {
float progress = mapf(value, minValue, maxValue, 0.0, 1.0); float progress = mapf(value, minValue, maxValue, 0.0, 1.0);
char quantity[100];
snprintf(quantity, 100, "%.3g%s", value, unit.c_str());
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, label.c_str(), quantity);
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, getText().c_str(), NULL);
} }


void Slider::onDragStart() { void Slider::onDragStart() {


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

@@ -16,7 +16,7 @@ void SpriteWidget::draw(NVGcontext *vg) {
nvgImageSize(vg, imageId, &width, &height); nvgImageSize(vg, imageId, &width, &height);
int stride = width / spriteSize.x; int stride = width / spriteSize.x;
if (stride == 0) { if (stride == 0) {
printf("Width of SpriteWidget is %d but spriteSize is %f\n", width, spriteSize.x);
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);


+ 28
- 12
src/widgets/Toolbar.cpp View File

@@ -1,9 +1,5 @@
#include "Rack.hpp" #include "Rack.hpp"


extern "C" {
#include "../lib/noc/noc_file_dialog.h"
}



namespace rack { namespace rack {


@@ -18,7 +14,7 @@ struct NewItem : MenuItem {


struct SaveItem : MenuItem { struct SaveItem : MenuItem {
void onAction() { void onAction() {
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, "Untitled.json");
const char *path = guiSaveDialog(filters, "Untitled.json");
if (path) { if (path) {
gRackWidget->savePatch(path); gRackWidget->savePatch(path);
} }
@@ -27,7 +23,7 @@ struct SaveItem : MenuItem {


struct OpenItem : MenuItem { struct OpenItem : MenuItem {
void onAction() { void onAction() {
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL);
const char *path = guiOpenDialog(filters, NULL);
if (path) { if (path) {
gRackWidget->loadPatch(path); gRackWidget->loadPatch(path);
} }
@@ -58,7 +54,7 @@ struct FileChoice : ChoiceButton {
menu->pushChild(saveAsItem); menu->pushChild(saveAsItem);
} }
overlay->addChild(menu); overlay->addChild(menu);
gScene->addChild(overlay);
gScene->setOverlay(overlay);
} }
}; };


@@ -77,6 +73,12 @@ struct SampleRateChoice : ChoiceButton {
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y));
menu->box.size.x = box.size.x; menu->box.size.x = box.size.x;


{
MenuLabel *item = new MenuLabel();
item->text = "(sample rate switching not yet implemented)";
menu->pushChild(item);
}

float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000};
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
SampleRateItem *item = new SampleRateItem(); SampleRateItem *item = new SampleRateItem();
@@ -88,20 +90,20 @@ struct SampleRateChoice : ChoiceButton {
} }


overlay->addChild(menu); overlay->addChild(menu);
gScene->addChild(overlay);
gScene->setOverlay(overlay);
} }
}; };




Toolbar::Toolbar() { Toolbar::Toolbar() {
float margin = 5; float margin = 5;
box.size = Vec(1020, BND_WIDGET_HEIGHT + 2*margin);
box.size.y = BND_WIDGET_HEIGHT + 2*margin;


float xPos = margin; float xPos = margin;
{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(xPos, margin); label->box.pos = Vec(xPos, margin);
label->text = "Rack v0.0.0 alpha";
label->text = gApplicationName + " " + gApplicationVersion;
addChild(label); addChild(label);
xPos += 150; xPos += 150;
} }
@@ -132,13 +134,27 @@ Toolbar::Toolbar() {
wireOpacitySlider = new Slider(); wireOpacitySlider = new Slider();
wireOpacitySlider->box.pos = Vec(xPos, margin); wireOpacitySlider->box.pos = Vec(xPos, margin);
wireOpacitySlider->box.size.x = 150; wireOpacitySlider->box.size.x = 150;
wireOpacitySlider->label = "Wire opacity";
wireOpacitySlider->label = "Cable opacity";
wireOpacitySlider->precision = 0;
wireOpacitySlider->unit = "%"; wireOpacitySlider->unit = "%";
wireOpacitySlider->setLimits(0.0, 100.0); wireOpacitySlider->setLimits(0.0, 100.0);
wireOpacitySlider->setDefaultValue(100.0);
wireOpacitySlider->setDefaultValue(50.0);
addChild(wireOpacitySlider); addChild(wireOpacitySlider);
xPos += wireOpacitySlider->box.size.x; xPos += wireOpacitySlider->box.size.x;
} }

xPos += margin;
{
wireTensionSlider = new Slider();
wireTensionSlider->box.pos = Vec(xPos, margin);
wireTensionSlider->box.size.x = 150;
wireTensionSlider->label = "Cable tension";
// wireTensionSlider->unit = "";
wireTensionSlider->setLimits(0.0, 1.0);
wireTensionSlider->setDefaultValue(0.5);
addChild(wireTensionSlider);
xPos += wireTensionSlider->box.size.x;
}
} }


void Toolbar::draw(NVGcontext *vg) { void Toolbar::draw(NVGcontext *vg) {


+ 2
- 0
src/widgets/Tooltip.cpp View File

@@ -9,6 +9,8 @@ void Tooltip::step() {


// Wrap size to contents // Wrap size to contents
// box.size = getChildrenBoundingBox().getBottomRight(); // box.size = getChildrenBoundingBox().getBottomRight();

Widget::step();
} }






+ 44
- 8
src/widgets/Widget.cpp View File

@@ -12,6 +12,8 @@ Widget::~Widget() {
gHoveredWidget = NULL; gHoveredWidget = NULL;
if (gDraggedWidget == this) if (gDraggedWidget == this)
gDraggedWidget = NULL; gDraggedWidget = NULL;
if (gSelectedWidget == this)
gSelectedWidget = NULL;
clearChildren(); clearChildren();
} }


@@ -77,18 +79,52 @@ void Widget::draw(NVGcontext *vg) {
nvgRestore(vg); nvgRestore(vg);
} }


Widget *Widget::pick(Vec pos) {
if (!box.contains(pos))
return NULL;
pos = pos.minus(box.pos);
Widget *Widget::onMouseDown(Vec pos, int button) {
for (auto it = children.rbegin(); it != children.rend(); it++) { for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it; Widget *child = *it;
Widget *picked = child->pick(pos);
if (picked)
return picked;
if (child->box.contains(pos)) {
Widget *w = child->onMouseDown(pos.minus(child->box.pos), button);
if (w)
return w;
}
} }
return this;
return NULL;
} }


Widget *Widget::onMouseUp(Vec pos, int button) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (child->box.contains(pos)) {
Widget *w = child->onMouseUp(pos.minus(child->box.pos), button);
if (w)
return w;
}
}
return NULL;
}

Widget *Widget::onMouseMove(Vec pos, Vec mouseRel) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (child->box.contains(pos)) {
Widget *w = child->onMouseMove(pos.minus(child->box.pos), mouseRel);
if (w)
return w;
}
}
return NULL;
}

Widget *Widget::onScroll(Vec pos, Vec scrollRel) {
for (auto it = children.rbegin(); it != children.rend(); it++) {
Widget *child = *it;
if (child->box.contains(pos)) {
Widget *w = child->onScroll(pos.minus(child->box.pos), scrollRel);
if (w)
return w;
}
}
return NULL;
}


} // namespace rack } // namespace rack

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

@@ -3,10 +3,10 @@


namespace rack { namespace rack {


void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color) {
void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, float tension, NVGcolor color) {
float dist = pos1.minus(pos2).norm(); float dist = pos1.minus(pos2).norm();
Vec slump = Vec(0, 100.0 + 0.5*dist);
Vec pos3 = pos1.plus(pos2).div(2).plus(slump);
float slump = (1.0 - tension) * (150.0 + 1.0*dist);
Vec pos3 = pos1.plus(pos2).div(2).plus(Vec(0, slump));


NVGcolor colorOutline = nvgRGBf(0, 0, 0); NVGcolor colorOutline = nvgRGBf(0, 0, 0);


@@ -108,7 +108,8 @@ void WireWidget::draw(NVGcontext *vg) {
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0;
if (wireOpacity > 0.0) { if (wireOpacity > 0.0) {
nvgGlobalAlpha(vg, wireOpacity); nvgGlobalAlpha(vg, wireOpacity);
drawWire(vg, outputPos, inputPos, color);
float tension = gScene->toolbar->wireTensionSlider->value;
drawWire(vg, outputPos, inputPos, tension, color);
} }
nvgRestore(vg); nvgRestore(vg);
} }


Loading…
Cancel
Save