Browse Source

Now requires SSE, added RadioButton, added hardcoded minBLEP, added CPU usage button

tags/v0.3.0
Andrew Belt 7 years ago
parent
commit
18628c86ab
16 changed files with 403 additions and 554 deletions
  1. +1
    -1
      Makefile
  2. +1
    -1
      README.md
  3. +73
    -6
      include/dsp.hpp
  4. +24
    -79
      include/math.hpp
  5. +1
    -1
      include/rack.hpp
  6. +224
    -0
      include/rackwidgets.hpp
  7. +0
    -1
      include/util.hpp
  8. +13
    -210
      include/widgets.hpp
  9. +5
    -0
      src/Rack.cpp
  10. +6
    -245
      src/dsp/minBLEP.cpp
  11. +2
    -2
      src/main.cpp
  12. +1
    -1
      src/widgets/Knob.cpp
  13. +3
    -0
      src/widgets/ModulePanel.cpp
  14. +9
    -5
      src/widgets/ModuleWidget.cpp
  15. +28
    -0
      src/widgets/RadioButton.cpp
  16. +12
    -2
      src/widgets/Toolbar.cpp

+ 1
- 1
Makefile View File

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



+ 1
- 1
README.md View File

@@ -1,6 +1,6 @@
*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.*

*Feel free to post **bugs**, **enhancements**, and **questions** on the [Issue Tracker](https://github.com/AndrewBelt/Rack/issues). To vote for a feature, give a thumbs-up on the first post.*
*Feel free to post bugs, enhancements, and questions on the [Issue Tracker](https://github.com/AndrewBelt/Rack/issues). To vote for a feature, give a thumbs-up on the first post.*

# Rack



+ 73
- 6
include/dsp.hpp View File

@@ -3,14 +3,78 @@
#include <assert.h>
#include <string.h>
#include <samplerate.h>
#include "util.hpp"
#include <complex.h>
#include "math.hpp"


// in minBLEP.cpp
float *generateMinBLEP(int zeroCrossings, int overSampling);
namespace rack {


namespace rack {
/** Construct a C-style complex float
With -O3 this is as fast as the 1.0 + 1.0*I syntax but it stops the compiler from complaining
*/
inline float _Complex complexf(float r, float i) {
union {
float x[2];
float _Complex c;
} v;
v.x[0] = r;
v.x[1] = i;
return v.c;
}

/** Simple FFT implementation
If you need something fast, use pffft, KissFFT, etc instead.
The size N must be a power of 2
*/
struct SimpleFFT {
int N;
/** Twiddle factors e^(2pi k/N), interleaved complex numbers */
float _Complex *tw;
SimpleFFT(int N, bool inverse) : N(N) {
tw = new float _Complex[N];
for (int i = 0; i < N; i++) {
float phase = 2*M_PI * (float)i / N;
if (inverse)
phase *= -1.0;
tw[i] = cexpf(phase * complexf(0.0, 1.0));
}
}
~SimpleFFT() {
delete[] tw;
}
/** Reference naive implementation
x and y are arrays of interleaved complex numbers
y must be size N/s
s is the stride factor for the x array which divides the size N
*/
void dft(const float _Complex *x, float _Complex *y, int s=1) {
for (int k = 0; k < N/s; k++) {
float _Complex yk = 0.0;
for (int n = 0; n < N; n += s) {
int m = (n*k) % N;
yk += x[n] * tw[m];
}
y[k] = yk;
}
}
void fft(const float _Complex *x, float _Complex *y, int s=1) {
if (N/s <= 2) {
// Naive DFT is faster than further FFT recursions at this point
dft(x, y, s);
return;
}
float _Complex e[N/(2*s)]; // Even inputs
float _Complex o[N/(2*s)]; // Odd inputs
fft(x, e, 2*s);
fft(x + s, o, 2*s);
for (int k = 0; k < N/(2*s); k++) {
int m = (k*s) % N;
y[k] = e[k] + tw[m] * o[k];
y[k + N/(2*s)] = e[k] - tw[m] * o[k];
}
}
};


/** A simple cyclic buffer.
@@ -196,12 +260,15 @@ struct SampleRateConverter {
};


// Pre-made minBLEP samples in minBLEP.cpp
extern const float minblep_16_32[];


template<int ZERO_CROSSINGS>
struct MinBLEP {
float buf[2*ZERO_CROSSINGS] = {};
int pos = 0;
/** You must set this to the array generated by generateMinBLEP() */
float *minblep = NULL;
const float *minblep;
int oversample;

/** Places a discontinuity with magnitude dx at -1 < p <= 0 relative to the current frame */


+ 24
- 79
include/math.hpp View File

@@ -60,11 +60,11 @@ inline float quintic(float x) {
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) {
int mod = a % base;
return mod < 0 ? mod + base : mod;
inline float sincf(float x) {
if (x == 0.0)
return 1.0;
x *= M_PI;
return sinf(x) / x;
}

inline float getf(const float *p, float v = 0.0) {
@@ -85,6 +85,25 @@ inline float interpf(const float *p, float x) {
return crossf(p[xi], p[xi+1], xf);
}

// 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) {
int mod = a % base;
return mod < 0 ? mod + base : mod;
}

inline int log2i(int n) {
int i = 0;
while (n >>= 1) {
i++;
}
return i;
}

inline bool ispow2(int n) {
return n > 0 && (n & (n - 1)) == 0;
}

////////////////////
// 2D float vector
////////////////////
@@ -167,79 +186,5 @@ struct Rect {
}
};

////////////////////
// Simple FFT implementation
////////////////////

// Derived from the Italian Wikipedia article for FFT
// https://it.wikipedia.org/wiki/Trasformata_di_Fourier_veloce
// If you need speed, use KissFFT, pffft, etc instead.

inline int log2i(int n) {
int i = 0;
while (n >>= 1) {
i++;
}
return i;
}

inline bool isPowerOf2(int n) {
return n > 0 && (n & (n-1)) == 0;
}

/*
inline int reverse(int N, int n) //calculating revers number
{
int j, p = 0;
for(j = 1; j <= log2i(N); j++) {
if(n & (1 << (log2i(N) - j)))
p |= 1 << (j - 1);
}
return p;
}

inline void ordina(complex<double>* f1, int N) //using the reverse order in the array
{
complex<double> f2[MAX];
for(int i = 0; i < N; i++)
f2[i] = f1[reverse(N, i)];
for(int j = 0; j < N; j++)
f1[j] = f2[j];
}

inline void transform(complex<double>* f, int N)
{
ordina(f, N); //first: reverse order
complex<double> *W;
W = (complex<double> *)malloc(N / 2 * sizeof(complex<double>));
W[1] = polar(1., -2. * M_PI / N);
W[0] = 1;
for(int i = 2; i < N / 2; i++)
W[i] = pow(W[1], i);
int n = 1;
int a = N / 2;
for(int j = 0; j < log2i(N); j++) {
for(int i = 0; i < N; i++) {
if(!(i & n)) {
complex<double> temp = f[i];
complex<double> Temp = W[(i * a) % (n * a)] * f[i + n];
f[i] = temp + Temp;
f[i + n] = temp - Temp;
}
}
n *= 2;
a = a / 2;
}
}

inline void FFT(complex<double>* f, int N, double d)
{
transform(f, N);
for(int i = 0; i < N; i++)
f[i] *= d; //multiplying by step
}
*/



} // namespace rack

+ 1
- 1
include/rack.hpp View File

@@ -7,7 +7,7 @@
#include <set>
#include <thread>
#include <mutex>
#include "widgets.hpp"
#include "rackwidgets.hpp"


namespace rack {


+ 224
- 0
include/rackwidgets.hpp View File

@@ -0,0 +1,224 @@
#pragma once

#include "widgets.hpp"
#include <jansson.h>


namespace rack {


struct Module;
struct Wire;

struct RackWidget;
struct ParamWidget;
struct InputPort;
struct OutputPort;


////////////////////
// module
////////////////////

// A 1U module should be 15x380. Thus the width of a module should be a factor of 15.
struct Model;
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;
// int moduleId;

std::vector<InputPort*> inputs;
std::vector<OutputPort*> outputs;
std::vector<ParamWidget*> params;

ModuleWidget(Module *module);
~ModuleWidget();
// Convenience functions for adding special widgets (calls addChild())
void addInput(InputPort *input);
void addOutput(OutputPort *output);
void addParam(ParamWidget *param);

json_t *toJson();
void fromJson(json_t *root);
void disconnectPorts();
void resetParams();
void cloneParams(ModuleWidget *source);

void draw(NVGcontext *vg);

bool requested = false;
Vec requestedPos;
Vec dragPos;
void onDragStart();
void onDragMove(Vec mouseRel);
void onDragEnd();
void onMouseDown(int button);
};

struct WireWidget : OpaqueWidget {
OutputPort *outputPort = NULL;
InputPort *inputPort = NULL;
Wire *wire = NULL;
NVGcolor color;

WireWidget();
~WireWidget();
void updateWire();
void draw(NVGcontext *vg);
void drawOutputPlug(NVGcontext *vg);
void drawInputPlug(NVGcontext *vg);
};

struct RackWidget : OpaqueWidget {
// Only put ModuleWidgets in here
Widget *moduleContainer;
// Only put WireWidgets in here
Widget *wireContainer;
WireWidget *activeWire = NULL;

RackWidget();
~RackWidget();
void clear();
void savePatch(std::string filename);
void loadPatch(std::string filename);
json_t *toJson();
void fromJson(json_t *root);

void repositionModule(ModuleWidget *module);
void step();
void draw(NVGcontext *vg);

void onMouseDown(int button);
};

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

////////////////////
// params
////////////////////

struct Light : TransparentWidget, SpriteWidget {
NVGcolor color;
void draw(NVGcontext *vg);
};

// If you don't add these to your ModuleWidget, it will fall out of the RackWidget
struct Screw : TransparentWidget, SpriteWidget {
Screw();
};

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

json_t *toJson();
void fromJson(json_t *root);
void onMouseDown(int button);
void onChange();
};

struct Knob : ParamWidget, SpriteWidget {
int minIndex, maxIndex, spriteCount;
void step();
void onDragStart();
void onDragMove(Vec mouseRel);
void onDragEnd();
};

struct Switch : ParamWidget, SpriteWidget {
};

struct ToggleSwitch : virtual Switch {
void onDragStart() {
index = 1;
}
void onDragEnd() {
index = 0;
}
void onDragDrop(Widget *origin) {
if (origin != this)
return;

// Cycle through modes
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3.
float v = value + 1.0;
setValue(v > maxValue ? minValue : v);
}
};

struct MomentarySwitch : virtual Switch {
void onDragStart() {
setValue(maxValue);
index = 1;
}
void onDragEnd() {
setValue(minValue);
index = 0;
}
};

////////////////////
// ports
////////////////////

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

Port();
~Port();
void disconnect();

int type;
void drawGlow(NVGcontext *vg);
void onMouseDown(int button);
void onDragEnd();
};

struct InputPort : Port {
int inputId;

void draw(NVGcontext *vg);
void onDragStart();
void onDragDrop(Widget *origin);
};

struct OutputPort : Port {
int outputId;

void draw(NVGcontext *vg);
void onDragStart();
void onDragDrop(Widget *origin);
};

////////////////////
// scene
////////////////////

struct Toolbar : OpaqueWidget {
Slider *wireOpacitySlider;
Slider *wireTensionSlider;
RadioButton *cpuUsageButton;

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

struct Scene : OpaqueWidget {
Toolbar *toolbar;
ScrollWidget *scrollWidget;
Widget *overlay = NULL;

Scene();
void setOverlay(Widget *w);
void step();
};


} // namespace rack

+ 0
- 1
include/util.hpp View File

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

#include <stdint.h>
#include <string>
#include "math.hpp"


namespace rack {


+ 13
- 210
include/widgets.hpp View File

@@ -6,23 +6,15 @@
#include <list>
#include <map>

#include <jansson.h>

#include "../ext/nanovg/src/nanovg.h"
#include "../ext/oui/blendish.h"

#include "math.hpp"
#include "util.hpp"


namespace rack {

struct Module;
struct Wire;

struct RackWidget;
struct ParamWidget;
struct InputPort;
struct OutputPort;

////////////////////
// base class and traits
@@ -227,6 +219,18 @@ struct ChoiceButton : Button {
void draw(NVGcontext *vg);
};

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

RadioButton() {
box.size.y = BND_WIDGET_HEIGHT;
}
void draw(NVGcontext *vg);
void onMouseEnter();
void onMouseLeave();
void onDragDrop(Widget *origin);
};

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

@@ -272,206 +276,5 @@ struct Tooltip : Widget {
void draw(NVGcontext *vg);
};

////////////////////
// module
////////////////////

// A 1U module should be 15x380. Thus the width of a module should be a factor of 15.
struct Model;
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;
// int moduleId;

std::vector<InputPort*> inputs;
std::vector<OutputPort*> outputs;
std::vector<ParamWidget*> params;

ModuleWidget(Module *module);
~ModuleWidget();
// Convenience functions for adding special widgets (calls addChild())
void addInput(InputPort *input);
void addOutput(OutputPort *output);
void addParam(ParamWidget *param);

json_t *toJson();
void fromJson(json_t *root);
void disconnectPorts();
void resetParams();
void cloneParams(ModuleWidget *source);

void draw(NVGcontext *vg);

bool requested = false;
Vec requestedPos;
Vec dragPos;
void onDragStart();
void onDragMove(Vec mouseRel);
void onDragEnd();
void onMouseDown(int button);
};

struct WireWidget : OpaqueWidget {
OutputPort *outputPort = NULL;
InputPort *inputPort = NULL;
Wire *wire = NULL;
NVGcolor color;

WireWidget();
~WireWidget();
void updateWire();
void draw(NVGcontext *vg);
void drawOutputPlug(NVGcontext *vg);
void drawInputPlug(NVGcontext *vg);
};

struct RackWidget : OpaqueWidget {
// Only put ModuleWidgets in here
Widget *moduleContainer;
// Only put WireWidgets in here
Widget *wireContainer;
WireWidget *activeWire = NULL;

RackWidget();
~RackWidget();
void clear();
void savePatch(std::string filename);
void loadPatch(std::string filename);
json_t *toJson();
void fromJson(json_t *root);

void repositionModule(ModuleWidget *module);
void step();
void draw(NVGcontext *vg);

void onMouseDown(int button);
};

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

////////////////////
// params
////////////////////

struct Light : TransparentWidget, SpriteWidget {
NVGcolor color;
void draw(NVGcontext *vg);
};

// If you don't add these to your ModuleWidget, it will fall out of the RackWidget
struct Screw : TransparentWidget, SpriteWidget {
Screw();
};

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

json_t *toJson();
void fromJson(json_t *root);
void onMouseDown(int button);
void onChange();
};

struct Knob : ParamWidget, SpriteWidget {
int minIndex, maxIndex, spriteCount;
void step();
void onDragStart();
void onDragMove(Vec mouseRel);
void onDragEnd();
};

struct Switch : ParamWidget, SpriteWidget {
};

struct ToggleSwitch : virtual Switch {
void onDragStart() {
index = 1;
}
void onDragEnd() {
index = 0;
}
void onDragDrop(Widget *origin) {
if (origin != this)
return;

// Cycle through modes
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3.
float v = value + 1.0;
setValue(v > maxValue ? minValue : v);
}
};

struct MomentarySwitch : virtual Switch {
void onDragStart() {
setValue(maxValue);
index = 1;
}
void onDragEnd() {
setValue(minValue);
index = 0;
}
};

////////////////////
// ports
////////////////////

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

Port();
~Port();
void disconnect();

int type;
void drawGlow(NVGcontext *vg);
void onMouseDown(int button);
void onDragEnd();
};

struct InputPort : Port {
int inputId;

void draw(NVGcontext *vg);
void onDragStart();
void onDragDrop(Widget *origin);
};

struct OutputPort : Port {
int outputId;

void draw(NVGcontext *vg);
void onDragStart();
void onDragDrop(Widget *origin);
};

////////////////////
// scene
////////////////////

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

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


} // namespace rack

+ 5
- 0
src/Rack.cpp View File

@@ -4,6 +4,7 @@
#include <math.h>
#include <chrono>
#include <condition_variable>
#include <xmmintrin.h>

#include "rack.hpp"

@@ -82,6 +83,10 @@ void Rack::stop() {
}

void Rack::run() {
// Set CPU to denormals-are-zero mode
// http://carlh.net/plugins/denormals.php
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

// Every time the rack waits and locks a mutex, it steps this many frames
const int stepSize = 32;



+ 6
- 245
src/dsp/minBLEP.cpp
File diff suppressed because it is too large
View File


+ 2
- 2
src/main.cpp View File

@@ -9,8 +9,8 @@

namespace rack {

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

Rack *gRack;



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

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


void Knob::step() {
index = eucMod((int) roundf(mapf(value, minValue, maxValue, minIndex, maxIndex)), spriteCount);
index = eucmod((int) roundf(mapf(value, minValue, maxValue, minIndex, maxIndex)), spriteCount);
}

void Knob::onDragStart() {


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

@@ -7,12 +7,15 @@ 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.pos;
float length = box.size.norm();
paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, highlightColor, backgroundColor);
nvgFillPaint(vg, paint);
// nvgFillColor(vg, backgroundColor);
nvgFill(vg);

// Background image
if (!imageFilename.empty()) {
int imageId = loadImage(imageFilename);


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

@@ -100,19 +100,23 @@ void ModuleWidget::draw(NVGcontext *vg) {
bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y);

// CPU usage text
if (true) {
if (gScene->toolbar->cpuUsageButton->value != 0.0) {
float cpuTime = module ? module->cpuTime : 0.0;
std::string text = stringf("%.1f%%", cpuTime * 100.0);

nvgSave(vg);
nvgBeginPath(vg);
nvgRect(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT);
nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0));
nvgFill(vg);

nvgBeginPath(vg);
cpuTime = clampf(cpuTime, 0.0, 1.0);
const float barWidth = 15.0;
nvgRect(vg, box.pos.x, box.pos.y + (1.0 - cpuTime) * box.size.y, barWidth, cpuTime * box.size.y);
nvgFillColor(vg, nvgHSLA(0.33 * cubic(1.0 - cpuTime), 1.0, 0.5, 128));
nvgRect(vg, box.pos.x, box.pos.y, box.size.x * cpuTime, BND_WIDGET_HEIGHT);
nvgFillColor(vg, nvgHSL(0.33 * cubic(1.0 - cpuTime), 1.0, 0.4));
nvgFill(vg);

bndLabel(vg, box.pos.x, box.pos.y + box.size.y - BND_WIDGET_HEIGHT, box.size.x, BND_WIDGET_HEIGHT, -1, text.c_str());
bndMenuItem(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_DEFAULT, -1, text.c_str());
nvgRestore(vg);
}
}


+ 28
- 0
src/widgets/RadioButton.cpp View File

@@ -0,0 +1,28 @@
#include "rack.hpp"


namespace rack {

void RadioButton::draw(NVGcontext *vg) {
bndRadioButton(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, value == 0.0 ? state : BND_ACTIVE, -1, label.c_str());
}

void RadioButton::onMouseEnter() {
state = BND_HOVER;
}

void RadioButton::onMouseLeave() {
state = BND_DEFAULT;
}

void RadioButton::onDragDrop(Widget *origin) {
if (origin == this) {
if (value == 0.0)
value = 1.0;
else
value = 0.0;
}
}


} // namespace rack

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

@@ -98,9 +98,9 @@ Toolbar::Toolbar() {
{
Label *label = new Label();
label->box.pos = Vec(xPos, margin);
label->text = gApplicationName + " " + gApplicationVersion;
label->text = gApplicationVersion;
addChild(label);
xPos += 175;
xPos += 100;
}

xPos += margin;
@@ -150,6 +150,16 @@ Toolbar::Toolbar() {
addChild(wireTensionSlider);
xPos += wireTensionSlider->box.size.x;
}

xPos += margin;
{
cpuUsageButton = new RadioButton();
cpuUsageButton->box.pos = Vec(xPos, margin);
cpuUsageButton->box.size.x = 100;
cpuUsageButton->label = "CPU usage";
addChild(cpuUsageButton);
xPos += cpuUsageButton->box.size.x;
}
}

void Toolbar::draw(NVGcontext *vg) {


Loading…
Cancel
Save