Browse Source

Added libsamplerate dependency, some general purpose DSP code

tags/v0.3.0
Andrew Belt 7 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
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.

+ 9
- 7
Makefile View File

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


+ 1
- 0
README.md View File

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


+ 6
- 2
include/Rack.hpp View File

@@ -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() {}



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


+ 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 */
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);


+ 4
- 0
src/gui.cpp View File

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


+ 1
- 0
src/plugin.cpp View File

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

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


+ 10
- 0
src/rack.cpp View File

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



+ 19
- 1
src/util.cpp View File

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

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

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


+ 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);
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);


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

@@ -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() {


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

@@ -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() {


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

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



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

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




Loading…
Cancel
Save