Browse Source

Added libsamplerate dependency, some general purpose DSP code

tags/v0.3.0
Andrew Belt 8 years ago
parent
commit
a69ce98903
17 changed files with 305 additions and 28 deletions
  1. +29
    -0
      LICENSE-dist.txt
  2. +9
    -7
      Makefile
  3. +1
    -0
      README.md
  4. +6
    -2
      include/Rack.hpp
  5. +172
    -0
      include/dsp.hpp
  6. +27
    -6
      include/util.hpp
  7. +5
    -4
      include/widgets.hpp
  8. +4
    -0
      src/gui.cpp
  9. +1
    -0
      src/plugin.cpp
  10. +10
    -0
      src/rack.cpp
  11. +19
    -1
      src/util.cpp
  12. +8
    -0
      src/widgets/Button.cpp
  13. +1
    -1
      src/widgets/ModulePanel.cpp
  14. +9
    -0
      src/widgets/ModuleWidget.cpp
  15. +2
    -3
      src/widgets/Port.cpp
  16. +1
    -2
      src/widgets/RackWidget.cpp
  17. +1
    -2
      src/widgets/Screw.cpp

+ 29
- 0
LICENSE-dist.txt View File

@@ -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 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.

+ 9
- 7
Makefile View File

@@ -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 += -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) $(shell pkg-config --libs gtk+-2.0)
TARGET = Rack TARGET = Rack
endif endif
@@ -27,7 +27,7 @@ CXX = clang++
SOURCES += lib/noc/noc_file_dialog.m SOURCES += lib/noc/noc_file_dialog.m
CFLAGS += -DNOC_FILE_DIALOG_OSX CFLAGS += -DNOC_FILE_DIALOG_OSX
CXXFLAGS += -DAPPLE -stdlib=libc++ -I$(HOME)/local/include 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 TARGET = Rack


Rack.app: $(TARGET) Rack.app: $(TARGET)
@@ -40,12 +40,14 @@ CC = x86_64-w64-mingw32-gcc
CXX = x86_64-w64-mingw32-g++ CXX = x86_64-w64-mingw32-g++
SOURCES += lib/noc/noc_file_dialog.c SOURCES += lib/noc/noc_file_dialog.c
CFLAGS += -DNOC_FILE_DIALOG_WIN32 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 -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 \ -L$(HOME)/pkg/portaudio-r1891-build/lib/x64/ReleaseMinDependency -lportaudio_x64 \
-Wl,--export-all-symbols,--out-implib,libRack.a -mwindows -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows
TARGET = Rack.exe TARGET = Rack.exe


+ 1
- 0
README.md View File

@@ -13,6 +13,7 @@ Install dependencies
- [jansson](http://www.digip.org/jansson/) - [jansson](http://www.digip.org/jansson/)
- [portaudio](http://www.portaudio.com/) - [portaudio](http://www.portaudio.com/)
- [portmidi](http://portmedia.sourceforge.net/portmidi/) - [portmidi](http://portmedia.sourceforge.net/portmidi/)
- [libsamplerate](http://www.mega-nerd.com/SRC/)
- GTK+-2.0 if Linux (for file open/save dialog) - GTK+-2.0 if Linux (for file open/save dialog)


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


+ 6
- 2
include/Rack.hpp View File

@@ -57,6 +57,7 @@ extern Vec gMousePos;
extern Widget *gHoveredWidget; extern Widget *gHoveredWidget;
extern Widget *gDraggedWidget; extern Widget *gDraggedWidget;
extern Widget *gSelectedWidget; extern Widget *gSelectedWidget;
extern int gGuiFrame;


void guiInit(); void guiInit();
void guiDestroy(); void guiDestroy();
@@ -82,10 +83,13 @@ struct Wire;


struct Module { struct Module {
std::vector<float> params; 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*> inputs;
std::vector<float*> outputs; std::vector<float*> outputs;
/** For CPU usage */
float cpuTime = 0.0;


virtual ~Module() {} virtual ~Module() {}




+ 172
- 0
include/dsp.hpp View File

@@ -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

+ 27
- 6
include/util.hpp View File

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


/** Limits a value between a minimum and maximum /** 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) { inline float clampf(float x, float min, float max) {
if (x > max) if (x > max)
@@ -22,6 +22,13 @@ inline float clampf(float x, float min, float max) {
return x; 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) { inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) {
return yMin + (x - xMin) / (xMax - xMin) * (yMax - yMin); return yMin + (x - xMin) / (xMax - xMin) * (yMax - yMin);
} }
@@ -43,12 +50,22 @@ inline float quadraticBipolar(float x) {
return x >= 0.0 ? x2 : -x2; return x >= 0.0 ? x2 : -x2;
} }


inline float cubic(float x) {
// optimal with --fast-math
return x*x*x;
}

inline float quarticBipolar(float x) { inline float quarticBipolar(float x) {
float x2 = x*x; float x2 = x*x;
float x4 = x2*x2; float x4 = x2*x2;
return x >= 0.0 ? x4 : -x4; 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 // 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) {
@@ -68,17 +85,21 @@ inline void setf(float *p, float 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. 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 // 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 // 2D float vector


+ 5
- 4
include/widgets.hpp View File

@@ -79,12 +79,12 @@ struct Widget {


/** Called when a widget responds to `onMouseDown` for a left button press */ /** Called when a widget responds to `onMouseDown` for a left button press */
virtual void onDragStart() {} 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 */ /** 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 */ /** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */
virtual void onDragDrop(Widget *origin) {} 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 onAction() {}
virtual void onChange() {} virtual void onChange() {}
@@ -217,7 +217,9 @@ struct Button : OpaqueWidget {
} }
void draw(NVGcontext *vg); void draw(NVGcontext *vg);
void onMouseEnter(); void onMouseEnter();
void onMouseLeave() ;
void onMouseLeave();
void onDragStart();
void onDragEnd();
void onDragDrop(Widget *origin); void onDragDrop(Widget *origin);
}; };


@@ -339,7 +341,6 @@ struct RackWidget : OpaqueWidget {
json_t *toJson(); json_t *toJson();
void fromJson(json_t *root); void fromJson(json_t *root);


int frame = 0;
void repositionModule(ModuleWidget *module); void repositionModule(ModuleWidget *module);
void step(); void step();
void draw(NVGcontext *vg); void draw(NVGcontext *vg);


+ 4
- 0
src/gui.cpp View File

@@ -27,6 +27,8 @@ Widget *gHoveredWidget = NULL;
Widget *gDraggedWidget = NULL; Widget *gDraggedWidget = NULL;
Widget *gSelectedWidget = NULL; Widget *gSelectedWidget = NULL;


int gGuiFrame;

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


@@ -225,8 +227,10 @@ void guiRun() {
glfwGetWindowSize(window, &width, &height); glfwGetWindowSize(window, &width, &height);
windowSizeCallback(window, width, height); windowSizeCallback(window, width, height);
} }
gGuiFrame = 0;
double lastTime = 0.0; double lastTime = 0.0;
while(!glfwWindowShouldClose(window)) { while(!glfwWindowShouldClose(window)) {
gGuiFrame++;
glfwPollEvents(); glfwPollEvents();
{ {
double xpos, ypos; double xpos, ypos;


+ 1
- 0
src/plugin.cpp View File

@@ -94,6 +94,7 @@ void pluginInit() {


void pluginDestroy() { void pluginDestroy() {
for (Plugin *plugin : gPlugins) { for (Plugin *plugin : gPlugins) {
// TODO unload plugin with `dlclose` or `FreeLibrary`
delete plugin; delete plugin;
} }
gPlugins.clear(); gPlugins.clear();


+ 10
- 0
src/rack.cpp View File

@@ -73,8 +73,18 @@ void rackStep() {
} }
} }
// Step all modules // Step all modules
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
for (Module *module : modules) { for (Module *module : modules) {
// Start clock for CPU usage
start = std::chrono::high_resolution_clock::now();
// Step module by one frame
module->step(); 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;
} }
} }




+ 19
- 1
src/util.cpp View File

@@ -3,7 +3,25 @@


namespace rack { namespace rack {


// TODO
// Convert this to xoroshiro128+ and custom normal dist implementation

static std::random_device rd; 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 } // namespace rack

+ 8
- 0
src/widgets/Button.cpp View File

@@ -15,6 +15,14 @@ void Button::onMouseLeave() {
state = BND_DEFAULT; state = BND_DEFAULT;
} }


void Button::onDragStart() {
state = BND_ACTIVE;
}

void Button::onDragEnd() {
state = BND_HOVER;
}

void Button::onDragDrop(Widget *origin) { void Button::onDragDrop(Widget *origin) {
if (origin == this) { if (origin == this) {
onAction(); onAction();


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

@@ -8,7 +8,7 @@ void ModulePanel::draw(NVGcontext *vg) {
nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y);
NVGpaint paint; NVGpaint paint;
// Background gradient // Background gradient
Vec c = box.getTopRight();
Vec c = box.pos;
float length = box.size.norm(); float length = box.size.norm();
paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor); paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor);
nvgFillPaint(vg, paint); nvgFillPaint(vg, paint);


+ 9
- 0
src/widgets/ModuleWidget.cpp View File

@@ -98,6 +98,15 @@ void ModuleWidget::cloneParams(ModuleWidget *source) {
void ModuleWidget::draw(NVGcontext *vg) { void ModuleWidget::draw(NVGcontext *vg) {
Widget::draw(vg); Widget::draw(vg);
bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y);

// CPU usage text
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() { void ModuleWidget::onDragStart() {


+ 2
- 3
src/widgets/Port.cpp View File

@@ -8,9 +8,8 @@ Port::Port() {
spriteOffset = Vec(-18, -18); spriteOffset = Vec(-18, -18);
spriteSize = Vec(56, 56); spriteSize = Vec(56, 56);
spriteFilename = "res/port.png"; spriteFilename = "res/port.png";
std::uniform_int_distribution<> dist(0, 4);
index = dist(rng);

index = randomu32() % 5;
} }


Port::~Port() { Port::~Port() {


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

@@ -248,8 +248,7 @@ void RackWidget::step() {


// Autosave every 15 seconds // Autosave every 15 seconds
// (This is alpha software, expect crashes!) // (This is alpha software, expect crashes!)
if (++frame >= 60*15) {
frame = 0;
if (gGuiFrame % (60*15) == 0) {
savePatch("autosave.json"); savePatch("autosave.json");
} }




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

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


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






Loading…
Cancel
Save