@@ -206,3 +206,32 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | |||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
# libsamplerate | |||
Copyright (c) 2012-2016, Erik de Castro Lopo <erikd@mega-nerd.com> | |||
All rights reserved. | |||
Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are | |||
met: | |||
1. Redistributions of source code must retain the above copyright | |||
notice, this list of conditions and the following disclaimer. | |||
2. Redistributions in binary form must reproduce the above copyright | |||
notice, this list of conditions and the following disclaimer in the | |||
documentation and/or other materials provided with the distribution. | |||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS | |||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A | |||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | |||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -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 += -rdynamic -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi \ | |||
LDFLAGS += -rdynamic -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi -lsamplerate \ | |||
$(shell pkg-config --libs gtk+-2.0) | |||
TARGET = Rack | |||
endif | |||
@@ -27,7 +27,7 @@ CXX = clang++ | |||
SOURCES += lib/noc/noc_file_dialog.m | |||
CFLAGS += -DNOC_FILE_DIALOG_OSX | |||
CXXFLAGS += -DAPPLE -stdlib=libc++ -I$(HOME)/local/include | |||
LDFLAGS += -stdlib=libc++ -L$(HOME)/local/lib -lpthread -lglew -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -ldl -ljansson -lportaudio -lportmidi | |||
LDFLAGS += -stdlib=libc++ -L$(HOME)/local/lib -lpthread -lglew -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo -ldl -ljansson -lportaudio -lportmidi -lsamplerate | |||
TARGET = Rack | |||
Rack.app: $(TARGET) | |||
@@ -40,12 +40,14 @@ CC = x86_64-w64-mingw32-gcc | |||
CXX = x86_64-w64-mingw32-g++ | |||
SOURCES += lib/noc/noc_file_dialog.c | |||
CFLAGS += -DNOC_FILE_DIALOG_WIN32 | |||
CXXFLAGS += -DWINDOWS -D_USE_MATH_DEFINES \ | |||
CXXFLAGS += -DWINDOWS -D_USE_MATH_DEFINES -DGLEW_STATIC \ | |||
-I$(HOME)/pkg/portaudio-r1891-build/include | |||
LDFLAGS += -lpthread \ | |||
-lglfw3 -lgdi32 -lopengl32 -lglew32 \ | |||
-lcomdlg32 -lole32 \ | |||
-ljansson -lportmidi \ | |||
LDFLAGS += \ | |||
-Wl,-Bstatic,--whole-archive \ | |||
-lglfw3 -lgdi32 -lglew32 -ljansson -lsamplerate \ | |||
-Wl,-Bdynamic,--no-whole-archive \ | |||
-lpthread -lopengl32 -lcomdlg32 -lole32 \ | |||
-lportmidi \ | |||
-L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \ | |||
-Wl,--export-all-symbols,--out-implib,libRack.a -mwindows | |||
TARGET = Rack.exe | |||
@@ -13,6 +13,7 @@ Install dependencies | |||
- [jansson](http://www.digip.org/jansson/) | |||
- [portaudio](http://www.portaudio.com/) | |||
- [portmidi](http://portmedia.sourceforge.net/portmidi/) | |||
- [libsamplerate](http://www.mega-nerd.com/SRC/) | |||
- GTK+-2.0 if Linux (for file open/save dialog) | |||
Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` | |||
@@ -57,6 +57,7 @@ extern Vec gMousePos; | |||
extern Widget *gHoveredWidget; | |||
extern Widget *gDraggedWidget; | |||
extern Widget *gSelectedWidget; | |||
extern int gGuiFrame; | |||
void guiInit(); | |||
void guiDestroy(); | |||
@@ -82,10 +83,13 @@ struct Wire; | |||
struct Module { | |||
std::vector<float> params; | |||
// Pointers to voltage values at each port | |||
// If value is NULL, the input/output is disconnected | |||
/** Pointers to voltage values at each port | |||
If value is NULL, the input/output is disconnected | |||
*/ | |||
std::vector<float*> inputs; | |||
std::vector<float*> outputs; | |||
/** For CPU usage */ | |||
float cpuTime = 0.0; | |||
virtual ~Module() {} | |||
@@ -0,0 +1,172 @@ | |||
#pragma once | |||
#include <assert.h> | |||
#include <string.h> | |||
#include <samplerate.h> | |||
namespace rack { | |||
/** S must be a power of 2 */ | |||
template <typename T, size_t S> | |||
struct RingBuffer { | |||
T data[S] = {}; | |||
size_t start = 0; | |||
size_t end = 0; | |||
size_t mask(size_t i) { | |||
return i & (S - 1); | |||
} | |||
void push(T t) { | |||
size_t i = mask(end++); | |||
data[i] = t; | |||
} | |||
T shift() { | |||
return data[mask(start++)]; | |||
} | |||
bool empty() { | |||
return start >= end; | |||
} | |||
bool full() { | |||
return end - start >= S; | |||
} | |||
size_t size() { | |||
return end - start; | |||
} | |||
}; | |||
/** S must be a power of 2 */ | |||
template <typename T, size_t S> | |||
struct DoubleRingBuffer { | |||
T data[S*2] = {}; | |||
size_t start = 0; | |||
size_t end = 0; | |||
size_t mask(size_t i) { | |||
return i & (S - 1); | |||
} | |||
void push(T t) { | |||
size_t i = mask(end++); | |||
data[i] = t; | |||
data[i + S] = t; | |||
} | |||
T shift() { | |||
return data[mask(start++)]; | |||
} | |||
bool empty() { | |||
return start >= end; | |||
} | |||
bool full() { | |||
return end - start >= S; | |||
} | |||
size_t size() { | |||
return end - start; | |||
} | |||
}; | |||
template <typename T, size_t S, size_t N> | |||
struct AppleRingBuffer { | |||
T data[N] = {}; | |||
size_t start = 0; | |||
size_t end = 0; | |||
void push(T t) { | |||
data[end++] = t; | |||
if (end >= N) { | |||
// move end block to beginning | |||
memmove(data, &data[N - S], sizeof(T) * S); | |||
start -= N - S; | |||
end = S; | |||
} | |||
} | |||
T shift() { | |||
return data[start++]; | |||
} | |||
bool empty() { | |||
return start >= end; | |||
} | |||
bool full() { | |||
return end - start >= S; | |||
} | |||
size_t size() { | |||
return end - start; | |||
} | |||
}; | |||
template <size_t S> | |||
struct SampleRateConverter { | |||
SRC_STATE *state; | |||
SRC_DATA data; | |||
SampleRateConverter() { | |||
int error; | |||
state = src_new(SRC_SINC_FASTEST, 1, &error); | |||
assert(!error); | |||
data.src_ratio = 1.0; | |||
data.end_of_input = false; | |||
} | |||
~SampleRateConverter() { | |||
src_delete(state); | |||
} | |||
void setRatio(float r) { | |||
data.src_ratio = r; | |||
} | |||
void push(const float *in, int length) { | |||
float out[S]; | |||
data.data_in = in; | |||
data.input_frames = length; | |||
data.data_out = out; | |||
data.output_frames = S; | |||
src_process(state, &data); | |||
} | |||
void push(float in) { | |||
push(&in, 1); | |||
} | |||
}; | |||
template <size_t OVERSAMPLE> | |||
struct Decimator { | |||
SRC_STATE *state; | |||
SRC_DATA data; | |||
Decimator() { | |||
int error; | |||
state = src_new(SRC_SINC_FASTEST, 1, &error); | |||
assert(!error); | |||
data.data_in = NULL; | |||
data.data_out = NULL; | |||
data.input_frames = OVERSAMPLE; | |||
data.output_frames = 1; | |||
data.end_of_input = false; | |||
data.src_ratio = 1.0 / OVERSAMPLE; | |||
} | |||
~Decimator() { | |||
src_delete(state); | |||
} | |||
/** input must be length OVERSAMPLE */ | |||
float process(float *input) { | |||
float output[1]; | |||
data.data_in = input; | |||
data.data_out = output; | |||
src_process(state, &data); | |||
if (data.output_frames_gen > 0) { | |||
return output[0]; | |||
} | |||
else { | |||
return 0.0; | |||
} | |||
} | |||
void reset() { | |||
src_reset(state); | |||
} | |||
}; | |||
} // namespace rack |
@@ -12,7 +12,7 @@ namespace rack { | |||
//////////////////// | |||
/** Limits a value between a minimum and maximum | |||
If min > max for some reason, returns min; | |||
If min > max for some reason, returns min | |||
*/ | |||
inline float clampf(float x, float min, float max) { | |||
if (x > max) | |||
@@ -22,6 +22,13 @@ inline float clampf(float x, float min, float max) { | |||
return x; | |||
} | |||
/** If the magnitude of x if less than eps, return 0 */ | |||
inline float chopf(float x, float eps) { | |||
if (x < eps && x > -eps) | |||
return 0.0; | |||
return x; | |||
} | |||
inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { | |||
return yMin + (x - xMin) / (xMax - xMin) * (yMax - yMin); | |||
} | |||
@@ -43,12 +50,22 @@ inline float quadraticBipolar(float x) { | |||
return x >= 0.0 ? x2 : -x2; | |||
} | |||
inline float cubic(float x) { | |||
// optimal with --fast-math | |||
return x*x*x; | |||
} | |||
inline float quarticBipolar(float x) { | |||
float x2 = x*x; | |||
float x4 = x2*x2; | |||
return x >= 0.0 ? x4 : -x4; | |||
} | |||
inline float quintic(float x) { | |||
// optimal with --fast-math | |||
return x*x*x*x*x; | |||
} | |||
// 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) { | |||
@@ -68,17 +85,21 @@ inline void setf(float *p, float v) { | |||
/** 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; | |||
return crossf(p[i], p[i+1], x); | |||
inline float interpf(const float *p, float x) { | |||
int xi = x; | |||
float xf = x - xi; | |||
return crossf(p[xi], p[xi+1], xf); | |||
} | |||
//////////////////// | |||
// RNG | |||
//////////////////// | |||
extern std::mt19937 rng; | |||
uint32_t randomu32(); | |||
/** Returns a uniform random float in the interval [0.0, 1.0) */ | |||
float randomf(); | |||
/** Returns a normal random number with mean 0 and std dev 1 */ | |||
float randomNormal(); | |||
//////////////////// | |||
// 2D float vector | |||
@@ -79,12 +79,12 @@ struct Widget { | |||
/** Called when a widget responds to `onMouseDown` for a left button press */ | |||
virtual void onDragStart() {} | |||
/** Called when the left button is released and this widget is being dragged */ | |||
virtual void onDragEnd() {} | |||
/** 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 onAction() {} | |||
virtual void onChange() {} | |||
@@ -217,7 +217,9 @@ struct Button : OpaqueWidget { | |||
} | |||
void draw(NVGcontext *vg); | |||
void onMouseEnter(); | |||
void onMouseLeave() ; | |||
void onMouseLeave(); | |||
void onDragStart(); | |||
void onDragEnd(); | |||
void onDragDrop(Widget *origin); | |||
}; | |||
@@ -339,7 +341,6 @@ struct RackWidget : OpaqueWidget { | |||
json_t *toJson(); | |||
void fromJson(json_t *root); | |||
int frame = 0; | |||
void repositionModule(ModuleWidget *module); | |||
void step(); | |||
void draw(NVGcontext *vg); | |||
@@ -27,6 +27,8 @@ Widget *gHoveredWidget = NULL; | |||
Widget *gDraggedWidget = NULL; | |||
Widget *gSelectedWidget = NULL; | |||
int gGuiFrame; | |||
static GLFWwindow *window = NULL; | |||
static NVGcontext *vg = NULL; | |||
@@ -225,8 +227,10 @@ void guiRun() { | |||
glfwGetWindowSize(window, &width, &height); | |||
windowSizeCallback(window, width, height); | |||
} | |||
gGuiFrame = 0; | |||
double lastTime = 0.0; | |||
while(!glfwWindowShouldClose(window)) { | |||
gGuiFrame++; | |||
glfwPollEvents(); | |||
{ | |||
double xpos, ypos; | |||
@@ -94,6 +94,7 @@ void pluginInit() { | |||
void pluginDestroy() { | |||
for (Plugin *plugin : gPlugins) { | |||
// TODO unload plugin with `dlclose` or `FreeLibrary` | |||
delete plugin; | |||
} | |||
gPlugins.clear(); | |||
@@ -73,8 +73,18 @@ void rackStep() { | |||
} | |||
} | |||
// Step all modules | |||
std::chrono::time_point<std::chrono::high_resolution_clock> start, end; | |||
for (Module *module : modules) { | |||
// Start clock for CPU usage | |||
start = std::chrono::high_resolution_clock::now(); | |||
// Step module by one frame | |||
module->step(); | |||
// Stop clock and smooth step time value | |||
end = std::chrono::high_resolution_clock::now(); | |||
std::chrono::duration<float> diff = end - start; | |||
float elapsed = diff.count() * SAMPLE_RATE; | |||
const float lambda = 1.0; | |||
module->cpuTime += (elapsed - module->cpuTime) * lambda / SAMPLE_RATE; | |||
} | |||
} | |||
@@ -3,7 +3,25 @@ | |||
namespace rack { | |||
// TODO | |||
// Convert this to xoroshiro128+ and custom normal dist implementation | |||
static std::random_device rd; | |||
std::mt19937 rng(rd()); | |||
static std::mt19937 rng(rd()); | |||
static std::uniform_real_distribution<float> uniformDist; | |||
static std::normal_distribution<float> normalDist; | |||
uint32_t randomu32() { | |||
return rng(); | |||
} | |||
float randomf() { | |||
return uniformDist(rng); | |||
} | |||
float randomNormal(){ | |||
return normalDist(rng); | |||
} | |||
} // namespace rack |
@@ -15,6 +15,14 @@ void Button::onMouseLeave() { | |||
state = BND_DEFAULT; | |||
} | |||
void Button::onDragStart() { | |||
state = BND_ACTIVE; | |||
} | |||
void Button::onDragEnd() { | |||
state = BND_HOVER; | |||
} | |||
void Button::onDragDrop(Widget *origin) { | |||
if (origin == this) { | |||
onAction(); | |||
@@ -8,7 +8,7 @@ void ModulePanel::draw(NVGcontext *vg) { | |||
nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||
NVGpaint paint; | |||
// Background gradient | |||
Vec c = box.getTopRight(); | |||
Vec c = box.pos; | |||
float length = box.size.norm(); | |||
paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor); | |||
nvgFillPaint(vg, paint); | |||
@@ -98,6 +98,15 @@ void ModuleWidget::cloneParams(ModuleWidget *source) { | |||
void ModuleWidget::draw(NVGcontext *vg) { | |||
Widget::draw(vg); | |||
bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||
// CPU usage text | |||
if (module) { | |||
char text[32]; | |||
snprintf(text, sizeof(text), "%.2f%%", module->cpuTime * 10); | |||
nvgSave(vg); | |||
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text, NULL); | |||
nvgRestore(vg); | |||
} | |||
} | |||
void ModuleWidget::onDragStart() { | |||
@@ -8,9 +8,8 @@ Port::Port() { | |||
spriteOffset = Vec(-18, -18); | |||
spriteSize = Vec(56, 56); | |||
spriteFilename = "res/port.png"; | |||
std::uniform_int_distribution<> dist(0, 4); | |||
index = dist(rng); | |||
index = randomu32() % 5; | |||
} | |||
Port::~Port() { | |||
@@ -248,8 +248,7 @@ void RackWidget::step() { | |||
// Autosave every 15 seconds | |||
// (This is alpha software, expect crashes!) | |||
if (++frame >= 60*15) { | |||
frame = 0; | |||
if (gGuiFrame % (60*15) == 0) { | |||
savePatch("autosave.json"); | |||
} | |||
@@ -9,8 +9,7 @@ Screw::Screw() { | |||
spriteSize = Vec(29, 29); | |||
spriteFilename = "res/screw.png"; | |||
std::uniform_int_distribution<> dist(0, 4); | |||
index = dist(rng); | |||
index = randomu32() % 5; | |||
} | |||