Browse Source

Added sample rate conversion to AudioInterface, added minBLEP to dsp.hpp

tags/v0.3.0
Andrew Belt 7 years ago
parent
commit
1696a2790a
15 changed files with 773 additions and 301 deletions
  1. +2
    -1
      Makefile
  2. +2
    -0
      README.md
  3. +64
    -34
      include/dsp.hpp
  4. +245
    -0
      include/math.hpp
  5. +4
    -2
      include/rack.hpp
  6. +3
    -167
      include/util.hpp
  7. +7
    -4
      src/Rack.cpp
  8. +135
    -73
      src/core/AudioInterface.cpp
  9. +251
    -0
      src/dsp/minBLEP.cpp
  10. +1
    -1
      src/gui.cpp
  11. +12
    -4
      src/util.cpp
  12. +13
    -4
      src/widgets/ModuleWidget.cpp
  13. +26
    -0
      src/widgets/RackWidget.cpp
  14. +5
    -10
      src/widgets/Toolbar.cpp
  15. +3
    -1
      src/widgets/WireWidget.cpp

+ 2
- 1
Makefile View File

@@ -15,7 +15,8 @@ CXX = g++
SOURCES += ext/noc/noc_file_dialog.c SOURCES += ext/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 -lsamplerate \
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


+ 2
- 0
README.md View File

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

# Rack # Rack


Open source Eurorack-style modular DAW Open source Eurorack-style modular DAW


+ 64
- 34
include/dsp.hpp View File

@@ -3,6 +3,11 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <samplerate.h> #include <samplerate.h>
#include "util.hpp"


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




namespace rack { namespace rack {
@@ -28,6 +33,9 @@ struct RingBuffer {
T shift() { T shift() {
return data[mask(start++)]; return data[mask(start++)];
} }
void clear() {
start = end;
}
bool empty() const { bool empty() const {
return start >= end; return start >= end;
} }
@@ -61,6 +69,9 @@ struct DoubleRingBuffer {
T shift() { T shift() {
return data[mask(start++)]; return data[mask(start++)];
} }
void clear() {
start = end;
}
bool empty() const { bool empty() const {
return start >= end; return start >= end;
} }
@@ -149,13 +160,14 @@ struct AppleRingBuffer {
}; };




template<int CHANNELS>
struct SampleRateConverter { struct SampleRateConverter {
SRC_STATE *state; SRC_STATE *state;
SRC_DATA data; SRC_DATA data;


SampleRateConverter() { SampleRateConverter() {
int error; int error;
state = src_new(SRC_SINC_FASTEST, 1, &error);
state = src_new(SRC_SINC_FASTEST, CHANNELS, &error);
assert(!error); assert(!error);


data.src_ratio = 1.0; data.src_ratio = 1.0;
@@ -165,53 +177,71 @@ struct SampleRateConverter {
src_delete(state); src_delete(state);
} }
void setRatio(float r) { void setRatio(float r) {
src_set_ratio(state, r);
data.src_ratio = r; data.src_ratio = r;
} }
void process(float *in, int inLength, float *out, int outLength) {
/** `in` and `out` are interlaced with the number of channels */
void process(float *in, int *inFrames, float *out, int *outFrames) {
data.data_in = in; data.data_in = in;
data.input_frames = inLength;
data.input_frames = *inFrames;
data.data_out = out; data.data_out = out;
data.output_frames = outLength;
data.output_frames = *outFrames;
src_process(state, &data); src_process(state, &data);
*inFrames = data.input_frames_used;
*outFrames = data.output_frames_gen;
}
void reset() {
src_reset(state);
} }
}; };




template <size_t OVERSAMPLE>
struct Decimator {
SRC_STATE *state;
SRC_DATA data;
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;
int oversample;

/** Places a discontinuity with magnitude dx at -1 < p <= 0 relative to the current frame */
void jump(float p, float dx) {
if (p <= -1 || 0 < p)
return;
for (int j = 0; j < 2*ZERO_CROSSINGS; j++) {
float minblepIndex = ((float)j - p) * oversample;
int index = (pos + j) % (2*ZERO_CROSSINGS);
buf[index] += dx * (-1.0 + interpf(minblep, minblepIndex));
}
}
float shift() {
float v = buf[pos];
buf[pos] = 0.0;
pos = (pos + 1) % (2*ZERO_CROSSINGS);
return v;
}
};


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;
struct RCFilter {
float c = 0.0;
float xstate[1] = {};
float ystate[1] = {};

// `r` is the ratio between the cutoff frequency and sample rate, i.e. r = f_c / f_s
void setCutoff(float r) {
c = 2.0 / r;
} }
~Decimator() {
src_delete(state);
void process(float x) {
float y = (x + xstate[0] - ystate[0] * (1 - c)) / (1 + c);
xstate[0] = x;
ystate[0] = y;
} }
/** 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;
}
float lowpass() {
return ystate[0];
} }
void reset() {
src_reset(state);
float highpass() {
return xstate[0] - ystate[0];
} }
}; };




+ 245
- 0
include/math.hpp View File

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

#include <math.h>


namespace rack {

/** 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) {
if (x > max)
x = max;
if (x < min)
x = min;
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);
}

inline float crossf(float a, float b, float frac) {
return (1.0 - frac) * a + frac * b;
}

inline int mini(int a, int b) {
return a < b ? a : b;
}

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

inline float getf(const float *p, float v = 0.0) {
return p ? *p : v;
}

inline void setf(float *p, float v) {
if (p)
*p = 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(const float *p, float x) {
int xi = x;
float xf = x - xi;
return crossf(p[xi], p[xi+1], xf);
}

////////////////////
// 2D float vector
////////////////////

struct Vec {
float x, y;

Vec() : x(0.0), y(0.0) {}
Vec(float x, float y) : x(x), y(y) {}

Vec neg() {
return Vec(-x, -y);
}
Vec plus(Vec b) {
return Vec(x + b.x, y + b.y);
}
Vec minus(Vec b) {
return Vec(x - b.x, y - b.y);
}
Vec mult(float s) {
return Vec(x * s, y * s);
}
Vec div(float s) {
return Vec(x / s, y / s);
}
float dot(Vec b) {
return x * b.x + y * b.y;
}
float norm() {
return hypotf(x, y);
}
Vec min(Vec b) {
return Vec(fminf(x, b.x), fminf(y, b.y));
}
Vec max(Vec b) {
return Vec(fmaxf(x, b.x), fmaxf(y, b.y));
}
Vec round() {
return Vec(roundf(x), roundf(y));
}
};


struct Rect {
Vec pos;
Vec size;

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);
}
Vec getCenter() {
return pos.plus(size.mult(0.5));
}
Vec getTopRight() {
return pos.plus(Vec(size.x, 0.0));
}
Vec getBottomLeft() {
return pos.plus(Vec(0.0, size.y));
}
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;
}
};

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

+ 4
- 2
include/rack.hpp View File

@@ -95,8 +95,10 @@ struct Wire {
int outputId; int outputId;
Module *inputModule = NULL; Module *inputModule = NULL;
int inputId; int inputId;
// The voltage which is pointed to by module inputs/outputs
float value = 0.0;
/** The voltage connected to input ports */
float inputValue = 0.0;
/** The voltage connected to output ports */
float outputValue = 0.0;
}; };


struct Rack { struct Rack {


+ 3
- 167
include/util.hpp View File

@@ -1,96 +1,12 @@
#pragma once #pragma once


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




namespace rack { 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) {
if (x > max)
x = max;
if (x < min)
x = min;
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);
}

inline float crossf(float a, float b, float frac) {
return (1.0 - frac) * a + frac * b;
}

inline int mini(int a, int b) {
return a < b ? a : b;
}

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

inline float getf(const float *p, float v = 0.0) {
return p ? *p : v;
}

inline void setf(float *p, float v) {
if (p)
*p = 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(const float *p, float x) {
int xi = x;
float xf = x - xi;
return crossf(p[xi], p[xi+1], xf);
}


//////////////////// ////////////////////
// RNG // RNG
@@ -102,88 +18,6 @@ float randomf();
/** Returns a normal random number with mean 0 and std dev 1 */ /** Returns a normal random number with mean 0 and std dev 1 */
float randomNormal(); float randomNormal();


////////////////////
// 2D float vector
////////////////////

struct Vec {
float x, y;

Vec() : x(0.0), y(0.0) {}
Vec(float x, float y) : x(x), y(y) {}

Vec neg() {
return Vec(-x, -y);
}
Vec plus(Vec b) {
return Vec(x + b.x, y + b.y);
}
Vec minus(Vec b) {
return Vec(x - b.x, y - b.y);
}
Vec mult(float s) {
return Vec(x * s, y * s);
}
Vec div(float s) {
return Vec(x / s, y / s);
}
float dot(Vec b) {
return x * b.x + y * b.y;
}
float norm() {
return hypotf(x, y);
}
Vec min(Vec b) {
return Vec(fminf(x, b.x), fminf(y, b.y));
}
Vec max(Vec b) {
return Vec(fmaxf(x, b.x), fmaxf(y, b.y));
}
Vec round() {
return Vec(roundf(x), roundf(y));
}
};


struct Rect {
Vec pos;
Vec size;

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);
}
Vec getCenter() {
return pos.plus(size.mult(0.5));
}
Vec getTopRight() {
return pos.plus(Vec(size.x, 0.0));
}
Vec getBottomLeft() {
return pos.plus(Vec(0.0, size.y));
}
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;
}
};

//////////////////// ////////////////////
// Helper functions // Helper functions
//////////////////// ////////////////////
@@ -191,5 +25,7 @@ struct Rect {
/** Converts a printf format string and optional arguments into a std::string */ /** Converts a printf format string and optional arguments into a std::string */
std::string stringf(const char *format, ...); std::string stringf(const char *format, ...);


/** Truncates and adds "..." to a string, not exceeding `len` characters */
std::string ellipsize(std::string s, size_t len);


} // namespace rack } // namespace rack

+ 7
- 4
src/Rack.cpp View File

@@ -132,6 +132,11 @@ void Rack::step() {
const float lambda = 1.0; const float lambda = 1.0;
module->cpuTime += (elapsed - module->cpuTime) * lambda / sampleRate; module->cpuTime += (elapsed - module->cpuTime) * lambda / sampleRate;
} }
// Step cables by moving their output values to inputs
for (Wire *wire : impl->wires) {
wire->inputValue = wire->outputValue;
wire->outputValue = 0.0;
}
} }


void Rack::addModule(Module *module) { void Rack::addModule(Module *module) {
@@ -166,8 +171,6 @@ void Rack::addWire(Wire *wire) {
assert(wire); assert(wire);
VIPLock vipLock(impl->vipMutex); VIPLock vipLock(impl->vipMutex);
std::lock_guard<std::mutex> lock(impl->mutex); std::lock_guard<std::mutex> lock(impl->mutex);
// It would probably be good to reset the wire voltage
wire->value = 0.0;
// Check that the wire is not already added // Check that the wire is not already added
assert(impl->wires.find(wire) == impl->wires.end()); assert(impl->wires.find(wire) == impl->wires.end());
assert(wire->outputModule); assert(wire->outputModule);
@@ -180,8 +183,8 @@ void Rack::addWire(Wire *wire) {
} }
// Connect the wire to inputModule // Connect the wire to inputModule
impl->wires.insert(wire); impl->wires.insert(wire);
wire->inputModule->inputs[wire->inputId] = &wire->value;
wire->outputModule->outputs[wire->outputId] = &wire->value;
wire->inputModule->inputs[wire->inputId] = &wire->inputValue;
wire->outputModule->outputs[wire->outputId] = &wire->outputValue;
} }


void Rack::removeWire(Wire *wire) { void Rack::removeWire(Wire *wire) {


+ 135
- 73
src/core/AudioInterface.cpp View File

@@ -2,6 +2,7 @@
#include <mutex> #include <mutex>
#include <portaudio.h> #include <portaudio.h>
#include "core.hpp" #include "core.hpp"
#include "dsp.hpp"


using namespace rack; using namespace rack;


@@ -9,7 +10,7 @@ using namespace rack;
void audioInit() { void audioInit() {
PaError err = Pa_Initialize(); PaError err = Pa_Initialize();
if (err) { if (err) {
printf("Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err));
fprintf(stderr, "Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err));
return; return;
} }
} }
@@ -22,31 +23,35 @@ struct AudioInterface : Module {
enum InputIds { enum InputIds {
AUDIO1_INPUT, AUDIO1_INPUT,
AUDIO2_INPUT, AUDIO2_INPUT,
AUDIO3_INPUT,
AUDIO4_INPUT,
NUM_INPUTS NUM_INPUTS
}; };
enum OutputIds { enum OutputIds {
AUDIO1_OUTPUT, AUDIO1_OUTPUT,
AUDIO2_OUTPUT, AUDIO2_OUTPUT,
AUDIO3_OUTPUT,
AUDIO4_OUTPUT,
NUM_OUTPUTS NUM_OUTPUTS
}; };


PaStream *stream = NULL; PaStream *stream = NULL;
int numOutputs;
int numInputs;
int bufferFrame;
float outputBuffer[1<<14] = {};
float inputBuffer[1<<14] = {};
// Used because the GUI thread and Rack thread can both interact with this class
std::mutex mutex;

// Set these and call openDevice()
// Stream properties
int deviceId = -1; int deviceId = -1;
float sampleRate = 44100.0; float sampleRate = 44100.0;
int blockSize = 256; int blockSize = 256;
int numOutputs = 0;
int numInputs = 0;

// Used because the GUI thread and Rack thread can both interact with this class
std::mutex mutex;

SampleRateConverter<2> inSrc;
SampleRateConverter<2> outSrc;

struct Frame {
float samples[2];
};
// in device's sample rate
RingBuffer<Frame, (1<<15)> inBuffer;
// in rack's sample rate
RingBuffer<Frame, (1<<15)> outBuffer;


AudioInterface(); AudioInterface();
~AudioInterface(); ~AudioInterface();
@@ -54,8 +59,19 @@ struct AudioInterface : Module {


int getDeviceCount(); int getDeviceCount();
std::string getDeviceName(int deviceId); std::string getDeviceName(int deviceId);
void openDevice();

void openDevice(int deviceId, float sampleRate, int blockSize);
void closeDevice(); void closeDevice();

void setDeviceId(int deviceId) {
openDevice(deviceId, sampleRate, blockSize);
}
void setSampleRate(float sampleRate) {
openDevice(deviceId, sampleRate, blockSize);
}
void setBlockSize(int blockSize) {
openDevice(deviceId, sampleRate, blockSize);
}
}; };




@@ -63,7 +79,6 @@ AudioInterface::AudioInterface() {
params.resize(NUM_PARAMS); params.resize(NUM_PARAMS);
inputs.resize(NUM_INPUTS); inputs.resize(NUM_INPUTS);
outputs.resize(NUM_OUTPUTS); outputs.resize(NUM_OUTPUTS);
closeDevice();
} }


AudioInterface::~AudioInterface() { AudioInterface::~AudioInterface() {
@@ -72,51 +87,97 @@ AudioInterface::~AudioInterface() {


void AudioInterface::step() { void AudioInterface::step() {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);

if (!stream) {
setf(inputs[AUDIO1_OUTPUT], 0.0);
setf(inputs[AUDIO2_OUTPUT], 0.0);
setf(inputs[AUDIO3_OUTPUT], 0.0);
setf(inputs[AUDIO4_OUTPUT], 0.0);
if (!stream)
return; return;
}


// Input ports -> Output buffer
for (int i = 0; i < numOutputs; i++) {
outputBuffer[numOutputs * bufferFrame + i] = getf(inputs[i]) / 5.0;
}
// Input buffer -> Output ports
for (int i = 0; i < numInputs; i++) {
setf(outputs[i], inputBuffer[numOutputs * bufferFrame + i] * 5.0);
// Get input and pass it through the sample rate converter
if (numOutputs > 0) {
Frame f;
f.samples[0] = getf(inputs[AUDIO1_INPUT]);
f.samples[1] = getf(inputs[AUDIO2_INPUT]);

inSrc.setRatio(sampleRate / gRack->sampleRate);
int inLength = 1;
int outLength = 16;
float buf[2*outLength];
inSrc.process(f.samples, &inLength, buf, &outLength);
for (int i = 0; i < outLength; i++) {
if (inBuffer.full())
break;
Frame f;
f.samples[0] = buf[2*i + 0];
f.samples[1] = buf[2*i + 1];
inBuffer.push(f);
}
} }


if (++bufferFrame >= blockSize) {
bufferFrame = 0;
// Read/write stream if we have enough input
// TODO If numOutputs == 0, call this when outBuffer.empty()
bool streamReady = (numOutputs > 0) ? ((int)inBuffer.size() >= blockSize) : (outBuffer.empty());
if (streamReady) {
// printf("%d %d\n", inBuffer.size(), outBuffer.size());
PaError err; PaError err;


// Input
// Read output from input stream
// (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.) // (for some reason, if you write the output stream before you read the input stream, PortAudio can segfault on Windows.)
if (numInputs > 0) { if (numInputs > 0) {
err = Pa_ReadStream(stream, inputBuffer, blockSize);
float *buf = new float[numInputs * blockSize];
err = Pa_ReadStream(stream, buf, blockSize);
if (err) { if (err) {
// Ignore buffer underflows // Ignore buffer underflows
if (err != paInputOverflowed) { if (err != paInputOverflowed) {
printf("Audio input buffer underflow\n");
fprintf(stderr, "Audio input buffer underflow\n");
} }
} }

// Pass output through sample rate converter
outSrc.setRatio(gRack->sampleRate / sampleRate);
int inLength = blockSize;
int outLength = 8*blockSize;
float *outBuf = new float[2*outLength];
outSrc.process(buf, &inLength, outBuf, &outLength);
// Add to output ring buffer
for (int i = 0; i < outLength; i++) {
if (outBuffer.full())
break;
Frame f;
f.samples[0] = outBuf[2*i + 0];
f.samples[1] = outBuf[2*i + 1];
outBuffer.push(f);
}
delete[] outBuf;
delete[] buf;
} }


// Output

// Write input to output stream
if (numOutputs > 0) { if (numOutputs > 0) {
err = Pa_WriteStream(stream, outputBuffer, blockSize);
float *buf = new float[numOutputs * blockSize]();
for (int i = 0; i < blockSize; i++) {
assert(!inBuffer.empty());
Frame f = inBuffer.shift();
for (int channel = 0; channel < numOutputs; channel++) {
buf[i * numOutputs + channel] = f.samples[channel];
}
}

err = Pa_WriteStream(stream, buf, blockSize);
if (err) { if (err) {
// Ignore buffer underflows // Ignore buffer underflows
if (err != paOutputUnderflowed) { if (err != paOutputUnderflowed) {
printf("Audio output buffer underflow\n");
fprintf(stderr, "Audio output buffer underflow\n");
} }
} }
delete[] buf;
} }
} }

// Set output
if (!outBuffer.empty()) {
Frame f = outBuffer.shift();
setf(outputs[AUDIO1_OUTPUT], f.samples[0]);
setf(outputs[AUDIO2_OUTPUT], f.samples[1]);
}
} }


int AudioInterface::getDeviceCount() { int AudioInterface::getDeviceCount() {
@@ -131,52 +192,61 @@ std::string AudioInterface::getDeviceName(int deviceId) {
return stringf("%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels); return stringf("%s: %s (%d in, %d out)", apiInfo->name, info->name, info->maxInputChannels, info->maxOutputChannels);
} }


void AudioInterface::openDevice() {
void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
closeDevice(); closeDevice();
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);


this->sampleRate = sampleRate;
this->blockSize = blockSize;

// Open new device // Open new device
if (deviceId >= 0) { if (deviceId >= 0) {
PaError err; PaError err;
const PaDeviceInfo *info = Pa_GetDeviceInfo(deviceId);
if (!info) {
printf("Failed to query audio device\n");
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(deviceId);
if (!deviceInfo) {
fprintf(stderr, "Failed to query audio device\n");
return; return;
} }


numOutputs = mini(info->maxOutputChannels, 4);
numInputs = mini(info->maxInputChannels, 4);
numOutputs = mini(deviceInfo->maxOutputChannels, 2);
numInputs = mini(deviceInfo->maxInputChannels, 2);


PaStreamParameters outputParameters; PaStreamParameters outputParameters;
outputParameters.device = deviceId; outputParameters.device = deviceId;
outputParameters.channelCount = numOutputs; outputParameters.channelCount = numOutputs;
outputParameters.sampleFormat = paFloat32; outputParameters.sampleFormat = paFloat32;
outputParameters.suggestedLatency = info->defaultLowOutputLatency;
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL; outputParameters.hostApiSpecificStreamInfo = NULL;


PaStreamParameters inputParameters; PaStreamParameters inputParameters;
inputParameters.device = deviceId; inputParameters.device = deviceId;
inputParameters.channelCount = numInputs; inputParameters.channelCount = numInputs;
inputParameters.sampleFormat = paFloat32; inputParameters.sampleFormat = paFloat32;
inputParameters.suggestedLatency = info->defaultLowInputLatency;
inputParameters.suggestedLatency = deviceInfo->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL; inputParameters.hostApiSpecificStreamInfo = NULL;


// Don't use stream parameters if 0 input or output channels // Don't use stream parameters if 0 input or output channels
err = Pa_OpenStream(&stream, err = Pa_OpenStream(&stream,
numInputs == 0 ? NULL : &outputParameters,
numOutputs == 0 ? NULL : &inputParameters,
numInputs == 0 ? NULL : &inputParameters,
numOutputs == 0 ? NULL : &outputParameters,
sampleRate, blockSize, paNoFlag, NULL, NULL); sampleRate, blockSize, paNoFlag, NULL, NULL);
if (err) { if (err) {
printf("Failed to open audio stream: %s\n", Pa_GetErrorText(err));
fprintf(stderr, "Failed to open audio stream: %s\n", Pa_GetErrorText(err));
return; return;
} }


err = Pa_StartStream(stream); err = Pa_StartStream(stream);
if (err) { if (err) {
printf("Failed to start audio stream: %s\n", Pa_GetErrorText(err));
fprintf(stderr, "Failed to start audio stream: %s\n", Pa_GetErrorText(err));
return; return;
} }

// Correct sample rate
const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream);
this->sampleRate = streamInfo->sampleRate;
} }

this->deviceId = deviceId;
} }


void AudioInterface::closeDevice() { void AudioInterface::closeDevice() {
@@ -187,13 +257,19 @@ void AudioInterface::closeDevice() {
err = Pa_CloseStream(stream); err = Pa_CloseStream(stream);
if (err) { if (err) {
// This shouldn't happen: // This shouldn't happen:
printf("Failed to close audio stream: %s\n", Pa_GetErrorText(err));
fprintf(stderr, "Failed to close audio stream: %s\n", Pa_GetErrorText(err));
} }
stream = NULL;
} }

// Reset stream settings
stream = NULL;
deviceId = -1;
numOutputs = 0; numOutputs = 0;
numInputs = 0; numInputs = 0;
bufferFrame = 0;

// Clear buffers
inBuffer.clear();
outBuffer.clear();
} }




@@ -201,8 +277,7 @@ struct AudioItem : MenuItem {
AudioInterface *audioInterface; AudioInterface *audioInterface;
int deviceId; int deviceId;
void onAction() { void onAction() {
audioInterface->deviceId = deviceId;
audioInterface->openDevice();
audioInterface->setDeviceId(deviceId);
} }
}; };


@@ -233,10 +308,7 @@ struct AudioChoice : ChoiceButton {
} }
void step() { void step() {
std::string name = audioInterface->getDeviceName(audioInterface->deviceId); std::string name = audioInterface->getDeviceName(audioInterface->deviceId);
if (name.empty())
text = "(no device)";
else
text = name;
text = name.empty() ? "(no device)" : ellipsize(name, 14);
} }
}; };


@@ -245,8 +317,7 @@ struct SampleRateItem : MenuItem {
AudioInterface *audioInterface; AudioInterface *audioInterface;
float sampleRate; float sampleRate;
void onAction() { void onAction() {
audioInterface->sampleRate = sampleRate;
audioInterface->openDevice();
audioInterface->setSampleRate(sampleRate);
} }
}; };


@@ -262,7 +333,7 @@ struct SampleRateChoice : ChoiceButton {
SampleRateItem *item = new SampleRateItem(); SampleRateItem *item = new SampleRateItem();
item->audioInterface = audioInterface; item->audioInterface = audioInterface;
item->sampleRate = sampleRates[i]; item->sampleRate = sampleRates[i];
item->text = stringf("%.0f", sampleRates[i]);
item->text = stringf("%.0f Hz", sampleRates[i]);
menu->pushChild(item); menu->pushChild(item);
} }


@@ -270,7 +341,7 @@ struct SampleRateChoice : ChoiceButton {
gScene->setOverlay(overlay); gScene->setOverlay(overlay);
} }
void step() { void step() {
this->text = stringf("%.0f", audioInterface->sampleRate);
this->text = stringf("%.0f Hz", audioInterface->sampleRate);
} }
}; };


@@ -279,8 +350,7 @@ struct BlockSizeItem : MenuItem {
AudioInterface *audioInterface; AudioInterface *audioInterface;
int blockSize; int blockSize;
void onAction() { void onAction() {
audioInterface->blockSize = blockSize;
audioInterface->openDevice();
audioInterface->setBlockSize(blockSize);
} }
}; };


@@ -381,10 +451,6 @@ AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()
addInput(createInput(Vec(75, yPos), module, AudioInterface::AUDIO2_INPUT)); addInput(createInput(Vec(75, yPos), module, AudioInterface::AUDIO2_INPUT));
yPos += 35 + margin; yPos += 35 + margin;


addInput(createInput(Vec(25, yPos), module, AudioInterface::AUDIO3_INPUT));
addInput(createInput(Vec(75, yPos), module, AudioInterface::AUDIO4_INPUT));
yPos += 35 + margin;

{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(margin, yPos); label->box.pos = Vec(margin, yPos);
@@ -397,10 +463,6 @@ AudioInterfaceWidget::AudioInterfaceWidget() : ModuleWidget(new AudioInterface()
addOutput(createOutput(Vec(25, yPos), module, AudioInterface::AUDIO1_OUTPUT)); addOutput(createOutput(Vec(25, yPos), module, AudioInterface::AUDIO1_OUTPUT));
addOutput(createOutput(Vec(75, yPos), module, AudioInterface::AUDIO2_OUTPUT)); addOutput(createOutput(Vec(75, yPos), module, AudioInterface::AUDIO2_OUTPUT));
yPos += 35 + margin; yPos += 35 + margin;

addOutput(createOutput(Vec(25, yPos), module, AudioInterface::AUDIO3_OUTPUT));
addOutput(createOutput(Vec(75, yPos), module, AudioInterface::AUDIO4_OUTPUT));
yPos += 35 + margin;
} }


void AudioInterfaceWidget::draw(NVGcontext *vg) { void AudioInterfaceWidget::draw(NVGcontext *vg) {


+ 251
- 0
src/dsp/minBLEP.cpp View File

@@ -0,0 +1,251 @@
// MinBLEP Generation Code
// By Daniel Werner
// This Code Is Public Domain

#include <math.h>

#define PI 3.14159265358979f

// SINC Function
static float SINC(float x)
{
float pix;

if(x == 0.0f)
return 1.0f;
else
{
pix = PI * x;
return sinf(pix) / pix;
}
}

// Generate Blackman Window
static void BlackmanWindow(int n, float *w)
{
int m = n - 1;
int i;
float f1, f2, fm;

fm = (float)m;
for(i = 0; i <= m; i++)
{
f1 = (2.0f * PI * (float)i) / fm;
f2 = 2.0f * f1;
w[i] = 0.42f - (0.5f * cosf(f1)) + (0.08f * cosf(f2));
}
}

// Discrete Fourier Transform
static void DFT(int n, float *realTime, float *imagTime, float *realFreq, float *imagFreq)
{
int k, i;
float sr, si, p;

for(k = 0; k < n; k++)
{
realFreq[k] = 0.0f;
imagFreq[k] = 0.0f;
}

for(k = 0; k < n; k++)
for(i = 0; i < n; i++)
{
p = (2.0f * PI * (float)(k * i)) / n;
sr = cosf(p);
si = -sinf(p);
realFreq[k] += (realTime[i] * sr) - (imagTime[i] * si);
imagFreq[k] += (realTime[i] * si) + (imagTime[i] * sr);
}
}

// Inverse Discrete Fourier Transform
static void InverseDFT(int n, float *realTime, float *imagTime, float *realFreq, float *imagFreq)
{
int k, i;
float sr, si, p;

for(k = 0; k < n; k++)
{
realTime[k] = 0.0f;
imagTime[k] = 0.0f;
}

for(k = 0; k < n; k++)
{
for(i = 0; i < n; i++)
{
p = (2.0f * PI * (float)(k * i)) / n;
sr = cosf(p);
si = -sinf(p);
realTime[k] += (realFreq[i] * sr) + (imagFreq[i] * si);
imagTime[k] += (realFreq[i] * si) - (imagFreq[i] * sr);
}
realTime[k] /= n;
imagTime[k] /= n;
}
}

// Complex Absolute Value
static float cabs(float x, float y)
{
return sqrtf((x * x) + (y * y));
}

// Complex Exponential
static void cexp(float x, float y, float *zx, float *zy)
{
float expx;

expx = expf(x);
*zx = expx * cosf(y);
*zy = expx * sinf(y);
}

// Compute Real Cepstrum Of Signal
static void RealCepstrum(int n, float *signal, float *realCepstrum)
{
float *realTime, *imagTime, *realFreq, *imagFreq;
int i;

realTime = new float[n];
imagTime = new float[n];
realFreq = new float[n];
imagFreq = new float[n];

// Compose Complex FFT Input

for(i = 0; i < n; i++)
{
realTime[i] = signal[i];
imagTime[i] = 0.0f;
}

// Perform DFT

DFT(n, realTime, imagTime, realFreq, imagFreq);

// Calculate Log Of Absolute Value

for(i = 0; i < n; i++)
{
realFreq[i] = logf(cabs(realFreq[i], imagFreq[i]));
imagFreq[i] = 0.0f;
}

// Perform Inverse FFT

InverseDFT(n, realTime, imagTime, realFreq, imagFreq);

// Output Real Part Of FFT
for(i = 0; i < n; i++)
realCepstrum[i] = realTime[i];

delete realTime;
delete imagTime;
delete realFreq;
delete imagFreq;
}

// Compute Minimum Phase Reconstruction Of Signal
static void MinimumPhase(int n, float *realCepstrum, float *minimumPhase)
{
int i, nd2;
float *realTime, *imagTime, *realFreq, *imagFreq;

nd2 = n / 2;
realTime = new float[n];
imagTime = new float[n];
realFreq = new float[n];
imagFreq = new float[n];

if((n % 2) == 1)
{
realTime[0] = realCepstrum[0];
for(i = 1; i < nd2; i++)
realTime[i] = 2.0f * realCepstrum[i];
for(i = nd2; i < n; i++)
realTime[i] = 0.0f;
}
else
{
realTime[0] = realCepstrum[0];
for(i = 1; i < nd2; i++)
realTime[i] = 2.0f * realCepstrum[i];
realTime[nd2] = realCepstrum[nd2];
for(i = nd2 + 1; i < n; i++)
realTime[i] = 0.0f;
}

for(i = 0; i < n; i++)
imagTime[i] = 0.0f;

DFT(n, realTime, imagTime, realFreq, imagFreq);

for(i = 0; i < n; i++)
cexp(realFreq[i], imagFreq[i], &realFreq[i], &imagFreq[i]);

InverseDFT(n, realTime, imagTime, realFreq, imagFreq);

for(i = 0; i < n; i++)
minimumPhase[i] = realTime[i];

delete realTime;
delete imagTime;
delete realFreq;
delete imagFreq;
}

// Generate MinBLEP And Return It In An Array Of Floating Point Values
float *generateMinBLEP(int zeroCrossings, int overSampling)
{
int i, n;
float r, a, b;
float *buffer1, *buffer2, *minBLEP;

n = (zeroCrossings * 2 * overSampling) + 1;

buffer1 = new float[n];
buffer2 = new float[n];

// Generate Sinc

a = (float)-zeroCrossings;
b = (float)zeroCrossings;
for(i = 0; i < n; i++)
{
r = ((float)i) / ((float)(n - 1));
buffer1[i] = SINC(a + (r * (b - a)));
}

// Window Sinc

BlackmanWindow(n, buffer2);
for(i = 0; i < n; i++)
buffer1[i] *= buffer2[i];

// Minimum Phase Reconstruction

RealCepstrum(n, buffer1, buffer2);
MinimumPhase(n, buffer2, buffer1);

// Integrate Into MinBLEP

minBLEP = new float[n];
a = 0.0f;
for(i = 0; i < n; i++)
{
a += buffer1[i];
minBLEP[i] = a;
}

// Normalize
a = minBLEP[n - 1];
a = 1.0f / a;
for(i = 0; i < n; i++)
minBLEP[i] *= a;

delete buffer1;
delete buffer2;
return minBLEP;
}

+ 1
- 1
src/gui.cpp View File

@@ -276,7 +276,7 @@ int loadImage(std::string filename) {
auto it = images.find(filename); auto it = images.find(filename);
if (it == images.end()) { if (it == images.end()) {
// Load image // Load image
imageId = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
imageId = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY | NVG_IMAGE_NEAREST);
if (imageId == 0) { if (imageId == 0) {
printf("Failed to load image %s\n", filename.c_str()); printf("Failed to load image %s\n", filename.c_str());
} }


+ 12
- 4
src/util.cpp View File

@@ -1,6 +1,7 @@
#include "util.hpp" #include "util.hpp"
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <random>




namespace rack { namespace rack {
@@ -25,6 +26,7 @@ float randomNormal(){
return normalDist(rng); return normalDist(rng);
} }



std::string stringf(const char *format, ...) { std::string stringf(const char *format, ...) {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
@@ -32,13 +34,19 @@ std::string stringf(const char *format, ...) {
if (size < 0) if (size < 0)
return ""; return "";
size++; size++;
char *buf = new char[size];
vsnprintf(buf, size, format, ap);
std::string s;
s.resize(size);
vsnprintf(&s[0], size, format, ap);
va_end(ap); va_end(ap);
std::string s = buf;
delete[] buf;
return s; return s;
} }


std::string ellipsize(std::string s, size_t len) {
if (s.size() <= len)
return s;
else
return s.substr(0, len - 3) + "...";
}



} // namespace rack } // namespace rack

+ 13
- 4
src/widgets/ModuleWidget.cpp View File

@@ -100,10 +100,19 @@ void ModuleWidget::draw(NVGcontext *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 // CPU usage text
if (module) {
std::string text = stringf("%.1f%%", module->cpuTime * 100.0);
if (true) {
float cpuTime = module ? module->cpuTime : 0.0;
std::string text = stringf("%.1f%%", cpuTime * 100.0);
nvgSave(vg); nvgSave(vg);
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL, BND_DEFAULT, module->cpuTime, text.c_str(), NULL);

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));
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());
nvgRestore(vg); nvgRestore(vg);
} }
} }
@@ -190,4 +199,4 @@ void ModuleWidget::onMouseDown(int button) {
} }




} // namespace rack
} // namespace rack

+ 26
- 0
src/widgets/RackWidget.cpp View File

@@ -64,6 +64,14 @@ json_t *RackWidget::toJson() {
json_t *versionJ = json_string(gApplicationVersion.c_str()); json_t *versionJ = json_string(gApplicationVersion.c_str());
json_object_set_new(root, "version", versionJ); json_object_set_new(root, "version", versionJ);


// wireOpacity
json_t *wireOpacityJ = json_real(gScene->toolbar->wireOpacitySlider->value);
json_object_set_new(root, "wireOpacity", wireOpacityJ);

// wireTension
json_t *wireTensionJ = json_real(gScene->toolbar->wireTensionSlider->value);
json_object_set_new(root, "wireTension", wireTensionJ);

// modules // modules
json_t *modulesJ = json_array(); json_t *modulesJ = json_array();
std::map<ModuleWidget*, int> moduleIds; std::map<ModuleWidget*, int> moduleIds;
@@ -121,6 +129,24 @@ json_t *RackWidget::toJson() {
} }


void RackWidget::fromJson(json_t *root) { void RackWidget::fromJson(json_t *root) {
// version
json_t *versionJ = json_object_get(root, "version");
if (versionJ) {
const char *version = json_string_value(versionJ);
if (gApplicationVersion != version)
printf("JSON version mismatch, attempting to convert JSON version %s to %s\n", version, gApplicationVersion.c_str());
}

// wireOpacity
json_t *wireOpacityJ = json_object_get(root, "wireOpacity");
if (wireOpacityJ)
gScene->toolbar->wireOpacitySlider->value = json_number_value(wireOpacityJ);

// wireTension
json_t *wireTensionJ = json_object_get(root, "wireTension");
if (wireTensionJ)
gScene->toolbar->wireTensionSlider->value = json_number_value(wireTensionJ);

// modules // modules
std::map<int, ModuleWidget*> moduleWidgets; std::map<int, ModuleWidget*> moduleWidgets;
json_t *modulesJ = json_object_get(root, "modules"); json_t *modulesJ = json_object_get(root, "modules");


+ 5
- 10
src/widgets/Toolbar.cpp View File

@@ -62,7 +62,7 @@ struct FileChoice : ChoiceButton {
struct SampleRateItem : MenuItem { struct SampleRateItem : MenuItem {
float sampleRate; float sampleRate;
void onAction() { void onAction() {
printf("\"\"\"\"\"\"\"\"switching\"\"\"\"\"\"\"\" sample rate to %f\n", sampleRate);
gRack->sampleRate = sampleRate;
} }
}; };


@@ -73,18 +73,10 @@ struct SampleRateChoice : ChoiceButton {
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y));
menu->box.size.x = box.size.x; 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}; float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000};
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
SampleRateItem *item = new SampleRateItem(); SampleRateItem *item = new SampleRateItem();
char text[100];
snprintf(text, 100, "%.0f Hz", sampleRates[i]);
item->text = std::string(text);
item->text = stringf("%.0f Hz", sampleRates[i]);
item->sampleRate = sampleRates[i]; item->sampleRate = sampleRates[i];
menu->pushChild(item); menu->pushChild(item);
} }
@@ -92,6 +84,9 @@ struct SampleRateChoice : ChoiceButton {
overlay->addChild(menu); overlay->addChild(menu);
gScene->setOverlay(overlay); gScene->setOverlay(overlay);
} }
void step() {
text = stringf("%.0f Hz", gRack->sampleRate);
}
}; };






+ 3
- 1
src/widgets/WireWidget.cpp View File

@@ -87,18 +87,21 @@ void WireWidget::updateWire() {
void WireWidget::draw(NVGcontext *vg) { void WireWidget::draw(NVGcontext *vg) {
Vec outputPos, inputPos; Vec outputPos, inputPos;
Vec absolutePos = getAbsolutePos(); Vec absolutePos = getAbsolutePos();
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0;


if (outputPort) { if (outputPort) {
outputPos = Rect(outputPort->getAbsolutePos(), outputPort->box.size).getCenter(); outputPos = Rect(outputPort->getAbsolutePos(), outputPort->box.size).getCenter();
} }
else { else {
outputPos = gMousePos; outputPos = gMousePos;
wireOpacity = 1.0;
} }
if (inputPort) { if (inputPort) {
inputPos = Rect(inputPort->getAbsolutePos(), inputPort->box.size).getCenter(); inputPos = Rect(inputPort->getAbsolutePos(), inputPort->box.size).getCenter();
} }
else { else {
inputPos = gMousePos; inputPos = gMousePos;
wireOpacity = 1.0;
} }


outputPos = outputPos.minus(absolutePos); outputPos = outputPos.minus(absolutePos);
@@ -107,7 +110,6 @@ void WireWidget::draw(NVGcontext *vg) {
bndNodePort(vg, outputPos.x, outputPos.y, BND_DEFAULT, color); bndNodePort(vg, outputPos.x, outputPos.y, BND_DEFAULT, color);
bndNodePort(vg, inputPos.x, inputPos.y, BND_DEFAULT, color); bndNodePort(vg, inputPos.x, inputPos.y, BND_DEFAULT, color);
nvgSave(vg); nvgSave(vg);
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0;
if (wireOpacity > 0.0) { if (wireOpacity > 0.0) {
nvgGlobalAlpha(vg, wireOpacity); nvgGlobalAlpha(vg, wireOpacity);
float tension = gScene->toolbar->wireTensionSlider->value; float tension = gScene->toolbar->wireTensionSlider->value;


Loading…
Cancel
Save