@@ -1,6 +1,6 @@ | |||
ARCH ?= linux | |||
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 | |||
LDFLAGS = | |||
@@ -15,7 +15,7 @@ CXX = g++ | |||
SOURCES += lib/noc/noc_file_dialog.c | |||
CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | |||
CXXFLAGS += -DLINUX | |||
LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -lprofiler -ljansson -lportaudio -lportmidi \ | |||
LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi \ | |||
$(shell pkg-config --libs gtk+-2.0) | |||
TARGET = Rack | |||
endif | |||
@@ -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. |
@@ -1,9 +0,0 @@ | |||
░█▀▄░█▀█░█▀▀░█░█ | |||
░█▀▄░█▀█░█░░░█▀▄ | |||
░▀░▀░▀░▀░▀▀▀░▀░▀ | |||
Virtual modular synthesizer engine | |||
## Building | |||
`make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` |
@@ -9,6 +9,9 @@ | |||
namespace rack { | |||
extern std::string gApplicationName; | |||
extern std::string gApplicationVersion; | |||
extern Scene *gScene; | |||
extern RackWidget *gRackWidget; | |||
@@ -53,12 +56,15 @@ void pluginDestroy(); | |||
extern Vec gMousePos; | |||
extern Widget *gHoveredWidget; | |||
extern Widget *gDraggedWidget; | |||
extern Widget *gSelectedWidget; | |||
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); | |||
@@ -2,6 +2,7 @@ | |||
#include <stdint.h> | |||
#include <math.h> | |||
#include <random> | |||
namespace rack { | |||
@@ -10,8 +11,15 @@ namespace rack { | |||
// 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) { | |||
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) { | |||
@@ -30,6 +38,17 @@ inline int maxi(int a, int 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 | |||
// Assumes this architecture's division is non-Euclidean | |||
inline int eucMod(int a, int base) { | |||
@@ -46,7 +65,9 @@ inline void setf(float *p, float 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) { | |||
int i = x; | |||
x -= i; | |||
@@ -57,12 +78,7 @@ inline float interpf(float *p, float x) { | |||
// RNG | |||
//////////////////// | |||
uint64_t randomi64(void); | |||
// Return a uniform random number on [0.0, 1.0) | |||
inline float randomf(void) { | |||
return (float)randomi64() / UINT64_MAX; | |||
} | |||
extern std::mt19937 rng; | |||
//////////////////// | |||
// 2D float vector | |||
@@ -114,10 +130,12 @@ struct Rect { | |||
Rect() {} | |||
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) { | |||
return pos.x <= v.x && v.x < pos.x + size.x | |||
&& pos.y <= v.y && v.y < pos.y + size.y; | |||
} | |||
/** Returns whether this Rect overlaps with another Rect */ | |||
bool intersects(Rect r) { | |||
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); | |||
@@ -134,6 +152,14 @@ struct Rect { | |||
Vec getBottomRight() { | |||
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; | |||
} | |||
}; | |||
@@ -7,8 +7,6 @@ | |||
#include <list> | |||
#include <map> | |||
#include <GL/glew.h> | |||
#include <GLFW/glfw3.h> | |||
#include <jansson.h> | |||
#include "../lib/nanovg/src/nanovg.h" | |||
@@ -31,10 +29,10 @@ struct OutputPort; | |||
// base class and traits | |||
//////////////////// | |||
// A node in the 2D scene graph | |||
/** A node in the 2D scene graph */ | |||
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; | |||
std::list<Widget*> children; | |||
@@ -42,55 +40,92 @@ struct Widget { | |||
Vec getAbsolutePos(); | |||
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); | |||
// Does not delete widget but transfers ownership to caller | |||
// Silenty fails if widget is not a child | |||
void removeChild(Widget *widget); | |||
void clearChildren(); | |||
// Advances the module by one frame | |||
/** Advances the module by one frame */ | |||
virtual void step(); | |||
// Draws to NanoVG context | |||
/** Draws to NanoVG context */ | |||
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 | |||
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() {} | |||
/** Called when another widget begins responding to `onMouseMove` events */ | |||
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 onDragDrop(Widget *origin) {} | |||
virtual void onDragHover(Widget *origin) {} | |||
/** Called when a widget responds to `onMouseMove` and is being dragged */ | |||
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 onResize() {} | |||
virtual void onAction() {} | |||
virtual void onChange() {} | |||
}; | |||
// Widget that does not respond to events | |||
/** Widget that does not respond to events */ | |||
struct TransparentWidget : virtual Widget { | |||
Widget *pick(Vec pos) { return NULL; } | |||
}; | |||
// Widget that does not respond to events, but allows its children to | |||
struct TranslucentWidget : virtual Widget { | |||
Widget *pick(Vec pos) { | |||
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 { | |||
@@ -107,38 +142,49 @@ struct QuantityWidget : virtual Widget { | |||
float maxValue = 1.0; | |||
float defaultValue = 0.0; | |||
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; | |||
/** 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 setLimits(float minValue, float maxValue); | |||
void setDefaultValue(float defaultValue); | |||
/** Generates the display value */ | |||
std::string getText(); | |||
}; | |||
//////////////////// | |||
// gui elements | |||
//////////////////// | |||
struct Label : TransparentWidget { | |||
struct Label : Widget { | |||
std::string text; | |||
void draw(NVGcontext *vg); | |||
}; | |||
// 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() { | |||
box.size = Vec(0, 0); | |||
} | |||
// Transfers ownership, like addChild() | |||
// Resizes menu and calls addChild() | |||
void pushChild(Widget *child); | |||
void draw(NVGcontext *vg); | |||
}; | |||
struct MenuEntry : Widget { | |||
struct MenuEntry : OpaqueWidget { | |||
std::string text; | |||
MenuEntry() { | |||
box.size = Vec(0, BND_WIDGET_HEIGHT); | |||
@@ -155,12 +201,12 @@ struct MenuItem : MenuEntry { | |||
void draw(NVGcontext *vg); | |||
void onMouseUp(int button); | |||
void onMouseEnter(); | |||
void onMouseLeave() ; | |||
void onDragDrop(Widget *origin); | |||
}; | |||
struct Button : Widget { | |||
struct Button : OpaqueWidget { | |||
std::string text; | |||
BNDwidgetState state = BND_DEFAULT; | |||
@@ -175,7 +221,7 @@ struct ChoiceButton : Button { | |||
void draw(NVGcontext *vg); | |||
}; | |||
struct Slider : QuantityWidget { | |||
struct Slider : OpaqueWidget, QuantityWidget { | |||
BNDwidgetState state = BND_DEFAULT; | |||
Slider(); | |||
@@ -185,7 +231,7 @@ struct Slider : QuantityWidget { | |||
void onDragEnd(); | |||
}; | |||
struct ScrollBar : Widget { | |||
struct ScrollBar : OpaqueWidget { | |||
enum { VERTICAL, HORIZONTAL } orientation; | |||
float containerOffset = 0.0; | |||
float containerSize = 0.0; | |||
@@ -200,18 +246,18 @@ struct ScrollBar : Widget { | |||
}; | |||
// Handles a container with scrollbars | |||
struct ScrollWidget : Widget { | |||
struct ScrollWidget : OpaqueWidget { | |||
Widget *container; | |||
ScrollBar *hScrollBar; | |||
ScrollBar *vScrollBar; | |||
ScrollWidget(); | |||
void step(); | |||
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 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. | |||
struct Model; | |||
struct ModuleWidget : Widget { | |||
struct ModuleWidget : OpaqueWidget { | |||
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. | |||
Module *module = NULL; | |||
@@ -256,7 +302,7 @@ struct ModuleWidget : Widget { | |||
void onMouseDown(int button); | |||
}; | |||
struct WireWidget : Widget { | |||
struct WireWidget : OpaqueWidget { | |||
OutputPort *outputPort = NULL; | |||
InputPort *inputPort = NULL; | |||
Wire *wire = NULL; | |||
@@ -270,12 +316,11 @@ struct WireWidget : Widget { | |||
void drawInputPlug(NVGcontext *vg); | |||
}; | |||
struct RackWidget : Widget { | |||
struct RackWidget : OpaqueWidget { | |||
// Only put ModuleWidgets in here | |||
Widget *moduleContainer; | |||
// Only put WireWidgets in here | |||
Widget *wireContainer; | |||
// An unowned reference to the currently dragged wire | |||
WireWidget *activeWire = NULL; | |||
RackWidget(); | |||
@@ -294,6 +339,12 @@ struct RackWidget : Widget { | |||
void onMouseDown(int button); | |||
}; | |||
struct ModulePanel : TransparentWidget { | |||
std::string imageFilename; | |||
NVGcolor backgroundColor; | |||
void draw(NVGcontext *vg); | |||
}; | |||
//////////////////// | |||
// params | |||
//////////////////// | |||
@@ -308,7 +359,7 @@ struct Screw : TransparentWidget, SpriteWidget { | |||
Screw(); | |||
}; | |||
struct ParamWidget : QuantityWidget { | |||
struct ParamWidget : OpaqueWidget, QuantityWidget { | |||
Module *module = NULL; | |||
int paramId; | |||
@@ -362,7 +413,7 @@ struct MomentarySwitch : virtual Switch { | |||
// ports | |||
//////////////////// | |||
struct Port : Widget { | |||
struct Port : OpaqueWidget, SpriteWidget { | |||
Module *module = NULL; | |||
WireWidget *connectedWire = NULL; | |||
@@ -371,7 +422,6 @@ struct Port : Widget { | |||
void disconnect(); | |||
int type; | |||
void draw(NVGcontext *vg); | |||
void drawGlow(NVGcontext *vg); | |||
void onMouseDown(int button); | |||
void onDragEnd(); | |||
@@ -397,17 +447,20 @@ struct OutputPort : Port { | |||
// scene | |||
//////////////////// | |||
struct Toolbar : Widget { | |||
struct Toolbar : OpaqueWidget { | |||
Slider *wireOpacitySlider; | |||
Slider *wireTensionSlider; | |||
Toolbar(); | |||
void draw(NVGcontext *vg); | |||
}; | |||
struct Scene : Widget { | |||
struct Scene : OpaqueWidget { | |||
Toolbar *toolbar; | |||
ScrollWidget *scrollWidget; | |||
Widget *overlay = NULL; | |||
Scene(); | |||
void onResize(); | |||
void setOverlay(Widget *w); | |||
void step(); | |||
}; | |||
@@ -169,7 +169,7 @@ struct AudioChoice : ChoiceButton { | |||
menu->pushChild(audioItem); | |||
} | |||
overlay->addChild(menu); | |||
gScene->addChild(overlay); | |||
gScene->setOverlay(overlay); | |||
} | |||
}; | |||
@@ -26,9 +26,10 @@ struct MidiInterface : Module { | |||
PortMidiStream *stream = NULL; | |||
std::list<int> notes; | |||
bool pedal = false; | |||
bool gate = false; | |||
int note = 64; // C4 | |||
int pitchWheel = 64; | |||
bool retrigger = true; | |||
bool retriggered = false; | |||
MidiInterface(); | |||
~MidiInterface(); | |||
@@ -74,6 +75,11 @@ void MidiInterface::step() { | |||
} | |||
if (outputs[GATE_OUTPUT]) { | |||
bool gate = pedal || !notes.empty(); | |||
if (retrigger && retriggered) { | |||
gate = false; | |||
retriggered = false; | |||
} | |||
*outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; | |||
} | |||
if (outputs[PITCH_OUTPUT]) { | |||
@@ -113,12 +119,14 @@ void MidiInterface::openPort(int portId) { | |||
} | |||
void MidiInterface::pressNote(int note) { | |||
// Remove existing similar note | |||
auto it = std::find(notes.begin(), notes.end(), note); | |||
if (it != notes.end()) | |||
notes.erase(it); | |||
// Push note | |||
notes.push_back(note); | |||
this->gate = true; | |||
this->note = note; | |||
retriggered = true; | |||
} | |||
void MidiInterface::releaseNote(int note) { | |||
@@ -135,10 +143,7 @@ void MidiInterface::releaseNote(int note) { | |||
auto it2 = notes.end(); | |||
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); | |||
} break; | |||
case 0x9: // note on | |||
if (data2) { | |||
if (data2 > 0) { | |||
pressNote(data1); | |||
} | |||
else { | |||
@@ -210,7 +215,7 @@ struct MidiChoice : ChoiceButton { | |||
menu->pushChild(midiItem); | |||
} | |||
overlay->addChild(menu); | |||
gScene->addChild(overlay); | |||
gScene->setOverlay(overlay); | |||
} | |||
}; | |||
@@ -1,6 +1,9 @@ | |||
#include <unistd.h> | |||
#include "Rack.hpp" | |||
#include <GL/glew.h> | |||
#include <GLFW/glfw3.h> | |||
// #define NANOVG_GLEW | |||
#define NANOVG_IMPLEMENTATION | |||
#include "../lib/nanovg/src/nanovg.h" | |||
@@ -9,6 +12,10 @@ | |||
#define BLENDISH_IMPLEMENTATION | |||
#include "../lib/oui/blendish.h" | |||
extern "C" { | |||
#include "../lib/noc/noc_file_dialog.h" | |||
} | |||
namespace rack { | |||
@@ -18,44 +25,45 @@ RackWidget *gRackWidget = NULL; | |||
Vec gMousePos; | |||
Widget *gHoveredWidget = NULL; | |||
Widget *gDraggedWidget = NULL; | |||
Widget *gSelectedWidget = NULL; | |||
static GLFWwindow *window; | |||
static GLFWwindow *window = NULL; | |||
static NVGcontext *vg = NULL; | |||
void windowSizeCallback(GLFWwindow* window, int width, int height) { | |||
gScene->box.size = Vec(width, height); | |||
gScene->onResize(); | |||
} | |||
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(); | |||
} | |||
} | |||
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) { | |||
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 = NULL; | |||
} | |||
gDraggedWidget = NULL; | |||
} | |||
} | |||
} | |||
@@ -70,40 +78,33 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { | |||
} | |||
// 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) { | |||
// onDragMove | |||
// 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)); | |||
// onDragHover | |||
if (hovered) { | |||
hovered->onDragHover(gDraggedWidget); | |||
} | |||
} | |||
else { | |||
// onMouseEnter and onMouseLeave | |||
// onMouseMove | |||
Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel); | |||
if (hovered != gHoveredWidget) { | |||
if (gHoveredWidget) { | |||
// onMouseLeave | |||
gHoveredWidget->onMouseLeave(); | |||
} | |||
if (hovered) { | |||
// onMouseEnter | |||
hovered->onMouseEnter(); | |||
} | |||
} | |||
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) { | |||
Vec scrollRel = Vec(x, y); | |||
// onScroll | |||
gScene->scrollWidget->onScroll(scrollRel.mult(-95)); | |||
gScene->onScroll(gMousePos, scrollRel.mult(-95)); | |||
} | |||
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() { | |||
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); | |||
glfwGetWindowSize(window, &width, &height); | |||
@@ -176,10 +177,12 @@ void guiInit() { | |||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | |||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | |||
window = glfwCreateWindow(1020, 700, "Rack", NULL, NULL); | |||
window = glfwCreateWindow(1020, 700, gApplicationName.c_str(), NULL, NULL); | |||
assert(window); | |||
glfwMakeContextCurrent(window); | |||
glfwSwapInterval(1); | |||
glfwSetWindowSizeCallback(window, windowSizeCallback); | |||
glfwSetMouseButtonCallback(window, mouseButtonCallback); | |||
// glfwSetCursorPosCallback(window, cursorPosCallback); | |||
@@ -203,7 +206,7 @@ void guiInit() { | |||
// Set up Blendish | |||
bndSetFont(loadFont("res/DejaVuSans.ttf")); | |||
// bndSetIconImage(loadImage("res/blender_icons16.png")); | |||
// bndSetIconImage(loadImage("res/icons.png")); | |||
gScene = new Scene(); | |||
} | |||
@@ -249,6 +252,16 @@ void guiCursorUnlock() { | |||
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; | |||
@@ -7,6 +7,14 @@ | |||
#include "Rack.hpp" | |||
namespace rack { | |||
std::string gApplicationName = "Virtuoso Rack"; | |||
std::string gApplicationVersion = "v0.0.0"; | |||
} // namespace rack | |||
using namespace rack; | |||
int main() { | |||
@@ -20,7 +28,7 @@ int main() { | |||
assert(success); | |||
CFRelease(bundleURL); | |||
// chdir(dirname(path)); | |||
chdir(dirname(path)); | |||
} | |||
#endif | |||
@@ -40,3 +48,4 @@ int main() { | |||
pluginDestroy(); | |||
return 0; | |||
} | |||
@@ -15,6 +15,7 @@ namespace rack { | |||
std::list<Plugin*> gPlugins; | |||
static | |||
int loadPlugin(const char *path) { | |||
// Load dynamic/shared library | |||
#if defined(WINDOWS) | |||
@@ -24,8 +25,8 @@ int loadPlugin(const char *path) { | |||
return -1; | |||
} | |||
#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); | |||
if (!handle) { | |||
fprintf(stderr, "Failed to load library %s: %s\n", path, dlerror()); | |||
@@ -61,24 +62,24 @@ void pluginInit() { | |||
// Load core | |||
Plugin *corePlugin = coreInit(); | |||
gPlugins.push_back(corePlugin); | |||
// Search for plugin libraries | |||
#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) | |||
#if defined(LINUX) | |||
const char *globPath = "plugins/*/plugin.so"; | |||
#elif defined(WINDOWS) | |||
const char *globPath = "plugins/*/plugin.dll"; | |||
#elif defined(APPLE) | |||
const char *globPath = "plugins/*/plugin.dylib"; | |||
#endif | |||
@@ -1,77 +1,9 @@ | |||
#include "util.hpp" | |||
#include <stdint.h> | |||
namespace rack { | |||
/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org) | |||
To the extent possible under law, the author has dedicated all copyright | |||
and related and neighboring rights to this software to the public domain | |||
worldwide. This software is distributed without any warranty. | |||
See <http://creativecommons.org/publicdomain/zero/1.0/>. */ | |||
/* This is the successor to xorshift128+. It is the fastest full-period | |||
generator passing BigCrush without systematic failures, but due to the | |||
relatively short period it is acceptable only for applications with a | |||
mild amount of parallelism; otherwise, use a xorshift1024* generator. | |||
Beside passing BigCrush, this generator passes the PractRand test suite | |||
up to (and included) 16TB, with the exception of binary rank tests, | |||
which fail due to the lowest bit being an LFSR; all other bits pass all | |||
tests. We suggest to use a sign test to extract a random Boolean value. | |||
Note that the generator uses a simulated rotate operation, which most C | |||
compilers will turn into a single instruction. In Java, you can use | |||
Long.rotateLeft(). In languages that do not make low-level rotation | |||
instructions accessible xorshift128+ could be faster. | |||
The state must be seeded so that it is not everywhere zero. If you have | |||
a 64-bit seed, we suggest to seed a splitmix64 generator and use its | |||
output to fill s. */ | |||
static uint64_t s[2] = {15879747621982183911ULL, 4486682751732329651ULL}; | |||
static inline uint64_t rotl(const uint64_t x, int k) { | |||
return (x << k) | (x >> (64 - k)); | |||
} | |||
uint64_t randomi64(void) { | |||
const uint64_t s0 = s[0]; | |||
uint64_t s1 = s[1]; | |||
const uint64_t result = s0 + s1; | |||
s1 ^= s0; | |||
s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b | |||
s[1] = rotl(s1, 36); // c | |||
return result; | |||
} | |||
/* This is the jump function for the generator. It is equivalent | |||
to 2^64 calls to next(); it can be used to generate 2^64 | |||
non-overlapping subsequences for parallel computations. */ | |||
void randomjump(void) { | |||
static const uint64_t JUMP[] = { 0xbeac0467eba5facb, 0xd86b048b86aa9922 }; | |||
uint64_t s0 = 0; | |||
uint64_t s1 = 0; | |||
for(unsigned int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) | |||
for(int b = 0; b < 64; b++) { | |||
if (JUMP[i] & 1ULL << b) { | |||
s0 ^= s[0]; | |||
s1 ^= s[1]; | |||
} | |||
randomi64(); | |||
} | |||
s[0] = s0; | |||
s[1] = s1; | |||
} | |||
static std::random_device rd; | |||
std::mt19937 rng(rd()); | |||
} // namespace rack |
@@ -4,7 +4,7 @@ | |||
namespace rack { | |||
void InputPort::draw(NVGcontext *vg) { | |||
Port::draw(vg); | |||
SpriteWidget::draw(vg); | |||
if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { | |||
Port::drawGlow(vg); | |||
} | |||
@@ -27,6 +27,7 @@ void InputPort::onDragStart() { | |||
} | |||
void InputPort::onDragDrop(Widget *origin) { | |||
printf("%p\n", origin); | |||
if (connectedWire) return; | |||
if (gRackWidget->activeWire) { | |||
if (gRackWidget->activeWire->inputPort) return; | |||
@@ -15,16 +15,13 @@ void MenuItem::onMouseLeave() { | |||
state = BND_DEFAULT; | |||
} | |||
void MenuItem::onMouseUp(int button) { | |||
void MenuItem::onDragDrop(Widget *origin) { | |||
if (origin != this) | |||
return; | |||
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); | |||
} | |||
@@ -3,12 +3,18 @@ | |||
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; | |||
} | |||
@@ -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 |
@@ -145,7 +145,7 @@ struct DeleteModuleMenuItem : MenuItem { | |||
}; | |||
void ModuleWidget::onMouseDown(int button) { | |||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
if (button == 1) { | |||
MenuOverlay *overlay = new MenuOverlay(); | |||
Menu *menu = new Menu(); | |||
menu->box.pos = gMousePos; | |||
@@ -155,12 +155,12 @@ void ModuleWidget::onMouseDown(int button) { | |||
menu->pushChild(menuLabel); | |||
ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); | |||
resetItem->text = "Initialize parameters"; | |||
resetItem->text = "Reset parameters"; | |||
resetItem->moduleWidget = this; | |||
menu->pushChild(resetItem); | |||
DisconnectPortsMenuItem *disconnectItem = new DisconnectPortsMenuItem(); | |||
disconnectItem->text = "Disconnect wires"; | |||
disconnectItem->text = "Disconnect cables"; | |||
disconnectItem->moduleWidget = this; | |||
menu->pushChild(disconnectItem); | |||
@@ -175,7 +175,7 @@ void ModuleWidget::onMouseDown(int button) { | |||
menu->pushChild(deleteItem); | |||
} | |||
overlay->addChild(menu); | |||
gScene->addChild(overlay); | |||
gScene->setOverlay(overlay); | |||
} | |||
} | |||
@@ -4,7 +4,7 @@ | |||
namespace rack { | |||
void OutputPort::draw(NVGcontext *vg) { | |||
Port::draw(vg); | |||
SpriteWidget::draw(vg); | |||
if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { | |||
Port::drawGlow(vg); | |||
} | |||
@@ -13,13 +13,15 @@ void ParamWidget::fromJson(json_t *root) { | |||
} | |||
void ParamWidget::onMouseDown(int button) { | |||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
if (button == 1) { | |||
setValue(defaultValue); | |||
} | |||
} | |||
void ParamWidget::onChange() { | |||
assert(module); | |||
if (!module) | |||
return; | |||
// moduleWidget->module->params[paramId] = value; | |||
rackSetParamSmooth(module, paramId, value); | |||
} | |||
@@ -5,7 +5,12 @@ namespace rack { | |||
Port::Port() { | |||
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() { | |||
@@ -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) { | |||
Vec c = box.getCenter(); | |||
NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5); | |||
@@ -45,7 +37,7 @@ void Port::drawGlow(NVGcontext *vg) { | |||
} | |||
void Port::onMouseDown(int button) { | |||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
if (button == 1) { | |||
disconnect(); | |||
} | |||
} | |||
@@ -3,6 +3,10 @@ | |||
namespace rack { | |||
QuantityWidget::QuantityWidget() { | |||
onChange(); | |||
} | |||
void QuantityWidget::setValue(float value) { | |||
this->value = clampf(value, minValue, maxValue); | |||
onChange(); | |||
@@ -18,5 +22,22 @@ void QuantityWidget::setDefaultValue(float 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 |
@@ -5,7 +5,7 @@ | |||
namespace rack { | |||
RackWidget::RackWidget() { | |||
moduleContainer = new TranslucentWidget(); | |||
moduleContainer = new Widget(); | |||
addChild(moduleContainer); | |||
wireContainer = new TransparentWidget(); | |||
@@ -56,26 +56,10 @@ void RackWidget::loadPatch(std::string filename) { | |||
fclose(file); | |||
} | |||
// Recursive function to get the ModuleWidget eventually containing a widget | |||
static ModuleWidget *getAncestorModuleWidget(Widget *w) { | |||
if (!w) return NULL; | |||
ModuleWidget *m = dynamic_cast<ModuleWidget*>(w); | |||
if (m) return m; | |||
return getAncestorModuleWidget(w->parent); | |||
} | |||
json_t *RackWidget::toJson() { | |||
// root | |||
json_t *root = json_object(); | |||
// 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 | |||
json_t *modulesJ = json_array(); | |||
std::map<ModuleWidget*, int> moduleIds; | |||
@@ -102,9 +86,9 @@ json_t *RackWidget::toJson() { | |||
// wire | |||
json_t *wire = json_object(); | |||
{ | |||
ModuleWidget *outputModuleWidget = getAncestorModuleWidget(wireWidget->outputPort); | |||
ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType<ModuleWidget>(); | |||
assert(outputModuleWidget); | |||
ModuleWidget *inputModuleWidget = getAncestorModuleWidget(wireWidget->inputPort); | |||
ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType<ModuleWidget>(); | |||
assert(inputModuleWidget); | |||
int outputModuleId = moduleIds[outputModuleWidget]; | |||
int inputModuleId = moduleIds[inputModuleWidget]; | |||
@@ -121,26 +105,16 @@ json_t *RackWidget::toJson() { | |||
} | |||
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 | |||
std::map<int, ModuleWidget*> moduleWidgets; | |||
json_t *modulesJ = json_object_get(root, "modules"); | |||
if (!modulesJ) return; | |||
size_t moduleId; | |||
json_t *moduleJ; | |||
json_array_foreach(modulesJ, moduleId, moduleJ) { | |||
// Get plugin | |||
json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
if (!pluginSlugJ) continue; | |||
const char *pluginSlug = json_string_value(pluginSlugJ); | |||
Plugin *plugin = NULL; | |||
for (Plugin *p : gPlugins) { | |||
@@ -149,7 +123,7 @@ void RackWidget::fromJson(json_t *root) { | |||
break; | |||
} | |||
} | |||
assert(plugin); | |||
if (!plugin) continue; | |||
// Get model | |||
json_t *modelSlug = json_object_get(moduleJ, "model"); | |||
@@ -160,10 +134,11 @@ void RackWidget::fromJson(json_t *root) { | |||
break; | |||
} | |||
} | |||
assert(model); | |||
if (!model) continue; | |||
// Create ModuleWidget | |||
ModuleWidget *moduleWidget = model->createModuleWidget(); | |||
assert(moduleWidget); | |||
moduleWidget->fromJson(moduleJ); | |||
moduleContainer->addChild(moduleWidget); | |||
moduleWidgets[moduleId] = moduleWidget; | |||
@@ -171,23 +146,25 @@ void RackWidget::fromJson(json_t *root) { | |||
// wires | |||
json_t *wiresJ = json_object_get(root, "wires"); | |||
if (!wiresJ) return; | |||
size_t wireId; | |||
json_t *wireJ; | |||
json_array_foreach(wiresJ, wireId, wireJ) { | |||
int outputModuleId, outputId; | |||
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, | |||
"inputModuleId", &inputModuleId, "inputId", &inputId); | |||
if (err) continue; | |||
// Get ports | |||
ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; | |||
assert(outputModuleWidget); | |||
if (!outputModuleWidget) continue; | |||
OutputPort *outputPort = outputModuleWidget->outputs[outputId]; | |||
assert(outputPort); | |||
if (!outputPort) continue; | |||
ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; | |||
assert(inputModuleWidget); | |||
if (!inputModuleWidget) continue; | |||
InputPort *inputPort = inputModuleWidget->inputs[inputId]; | |||
assert(inputPort); | |||
if (!inputPort) continue; | |||
// Create WireWidget | |||
WireWidget *wireWidget = new WireWidget(); | |||
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; | |||
savePatch("autosave.json"); | |||
} | |||
@@ -299,7 +277,7 @@ struct AddModuleMenuItem : MenuItem { | |||
}; | |||
void RackWidget::onMouseDown(int button) { | |||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
if (button == 1) { | |||
// Get relative position of the click | |||
Vec modulePos = gMousePos.minus(getAbsolutePos()); | |||
@@ -320,7 +298,7 @@ void RackWidget::onMouseDown(int button) { | |||
} | |||
} | |||
overlay->addChild(menu); | |||
gScene->addChild(overlay); | |||
gScene->setOverlay(overlay); | |||
} | |||
} | |||
@@ -17,10 +17,27 @@ Scene::Scene() { | |||
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; | |||
scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); | |||
scrollWidget->onResize(); | |||
if (overlay) { | |||
overlay->box.size = box.size; | |||
} | |||
Widget::step(); | |||
} | |||
@@ -8,7 +8,9 @@ Screw::Screw() { | |||
spriteOffset = Vec(-7, -7); | |||
spriteSize = Vec(29, 29); | |||
spriteFilename = "res/screw.png"; | |||
index = randomi64() % 5; | |||
std::uniform_int_distribution<> dist(0, 4); | |||
index = dist(rng); | |||
} | |||
@@ -16,6 +16,18 @@ ScrollWidget::ScrollWidget() { | |||
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) { | |||
// Update the scrollbar sizes | |||
Vec c = container->getChildrenBoundingBox().getBottomRight(); | |||
@@ -28,19 +40,13 @@ void ScrollWidget::draw(NVGcontext *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); | |||
vScrollBar->move(scrollRel.y); | |||
return this; | |||
} | |||
@@ -11,9 +11,7 @@ Slider::Slider() { | |||
void Slider::draw(NVGcontext *vg) { | |||
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() { | |||
@@ -16,7 +16,7 @@ void SpriteWidget::draw(NVGcontext *vg) { | |||
nvgImageSize(vg, imageId, &width, &height); | |||
int stride = width / spriteSize.x; | |||
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; | |||
} | |||
Vec offset = Vec((index % stride) * spriteSize.x, (index / stride) * spriteSize.y); | |||
@@ -1,9 +1,5 @@ | |||
#include "Rack.hpp" | |||
extern "C" { | |||
#include "../lib/noc/noc_file_dialog.h" | |||
} | |||
namespace rack { | |||
@@ -18,7 +14,7 @@ struct NewItem : MenuItem { | |||
struct SaveItem : MenuItem { | |||
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) { | |||
gRackWidget->savePatch(path); | |||
} | |||
@@ -27,7 +23,7 @@ struct SaveItem : MenuItem { | |||
struct OpenItem : MenuItem { | |||
void onAction() { | |||
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL); | |||
const char *path = guiOpenDialog(filters, NULL); | |||
if (path) { | |||
gRackWidget->loadPatch(path); | |||
} | |||
@@ -58,7 +54,7 @@ struct FileChoice : ChoiceButton { | |||
menu->pushChild(saveAsItem); | |||
} | |||
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.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}; | |||
for (int i = 0; i < 6; i++) { | |||
SampleRateItem *item = new SampleRateItem(); | |||
@@ -88,20 +90,20 @@ struct SampleRateChoice : ChoiceButton { | |||
} | |||
overlay->addChild(menu); | |||
gScene->addChild(overlay); | |||
gScene->setOverlay(overlay); | |||
} | |||
}; | |||
Toolbar::Toolbar() { | |||
float margin = 5; | |||
box.size = Vec(1020, BND_WIDGET_HEIGHT + 2*margin); | |||
box.size.y = BND_WIDGET_HEIGHT + 2*margin; | |||
float xPos = margin; | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(xPos, margin); | |||
label->text = "Rack v0.0.0 alpha"; | |||
label->text = gApplicationName + " " + gApplicationVersion; | |||
addChild(label); | |||
xPos += 150; | |||
} | |||
@@ -132,13 +134,27 @@ Toolbar::Toolbar() { | |||
wireOpacitySlider = new Slider(); | |||
wireOpacitySlider->box.pos = Vec(xPos, margin); | |||
wireOpacitySlider->box.size.x = 150; | |||
wireOpacitySlider->label = "Wire opacity"; | |||
wireOpacitySlider->label = "Cable opacity"; | |||
wireOpacitySlider->precision = 0; | |||
wireOpacitySlider->unit = "%"; | |||
wireOpacitySlider->setLimits(0.0, 100.0); | |||
wireOpacitySlider->setDefaultValue(100.0); | |||
wireOpacitySlider->setDefaultValue(50.0); | |||
addChild(wireOpacitySlider); | |||
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) { | |||
@@ -9,6 +9,8 @@ void Tooltip::step() { | |||
// Wrap size to contents | |||
// box.size = getChildrenBoundingBox().getBottomRight(); | |||
Widget::step(); | |||
} | |||
@@ -12,6 +12,8 @@ Widget::~Widget() { | |||
gHoveredWidget = NULL; | |||
if (gDraggedWidget == this) | |||
gDraggedWidget = NULL; | |||
if (gSelectedWidget == this) | |||
gSelectedWidget = NULL; | |||
clearChildren(); | |||
} | |||
@@ -77,18 +79,52 @@ void Widget::draw(NVGcontext *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++) { | |||
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 |
@@ -3,10 +3,10 @@ | |||
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(); | |||
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); | |||
@@ -108,7 +108,8 @@ void WireWidget::draw(NVGcontext *vg) { | |||
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | |||
if (wireOpacity > 0.0) { | |||
nvgGlobalAlpha(vg, wireOpacity); | |||
drawWire(vg, outputPos, inputPos, color); | |||
float tension = gScene->toolbar->wireTensionSlider->value; | |||
drawWire(vg, outputPos, inputPos, tension, color); | |||
} | |||
nvgRestore(vg); | |||
} | |||