@@ -15,7 +15,8 @@ CXX = g++ | |||
SOURCES += ext/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 -lsamplerate \ | |||
LDFLAGS += -rdynamic \ | |||
-lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi -lsamplerate \ | |||
$(shell pkg-config --libs gtk+-2.0) | |||
TARGET = Rack | |||
endif | |||
@@ -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.* | |||
*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 | |||
Open source Eurorack-style modular DAW | |||
@@ -3,6 +3,11 @@ | |||
#include <assert.h> | |||
#include <string.h> | |||
#include <samplerate.h> | |||
#include "util.hpp" | |||
// in minBLEP.cpp | |||
float *generateMinBLEP(int zeroCrossings, int overSampling); | |||
namespace rack { | |||
@@ -28,6 +33,9 @@ struct RingBuffer { | |||
T shift() { | |||
return data[mask(start++)]; | |||
} | |||
void clear() { | |||
start = end; | |||
} | |||
bool empty() const { | |||
return start >= end; | |||
} | |||
@@ -61,6 +69,9 @@ struct DoubleRingBuffer { | |||
T shift() { | |||
return data[mask(start++)]; | |||
} | |||
void clear() { | |||
start = end; | |||
} | |||
bool empty() const { | |||
return start >= end; | |||
} | |||
@@ -149,13 +160,14 @@ struct AppleRingBuffer { | |||
}; | |||
template<int CHANNELS> | |||
struct SampleRateConverter { | |||
SRC_STATE *state; | |||
SRC_DATA data; | |||
SampleRateConverter() { | |||
int error; | |||
state = src_new(SRC_SINC_FASTEST, 1, &error); | |||
state = src_new(SRC_SINC_FASTEST, CHANNELS, &error); | |||
assert(!error); | |||
data.src_ratio = 1.0; | |||
@@ -165,53 +177,71 @@ struct SampleRateConverter { | |||
src_delete(state); | |||
} | |||
void setRatio(float r) { | |||
src_set_ratio(state, 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.input_frames = inLength; | |||
data.input_frames = *inFrames; | |||
data.data_out = out; | |||
data.output_frames = outLength; | |||
data.output_frames = *outFrames; | |||
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]; | |||
} | |||
}; | |||
@@ -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 |
@@ -95,8 +95,10 @@ struct Wire { | |||
int outputId; | |||
Module *inputModule = NULL; | |||
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 { | |||
@@ -1,96 +1,12 @@ | |||
#pragma once | |||
#include <stdint.h> | |||
#include <math.h> | |||
#include <random> | |||
#include <string> | |||
#include "math.hpp" | |||
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 | |||
@@ -102,88 +18,6 @@ float randomf(); | |||
/** Returns a normal random number with mean 0 and std dev 1 */ | |||
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 | |||
//////////////////// | |||
@@ -191,5 +25,7 @@ struct Rect { | |||
/** Converts a printf format string and optional arguments into a std::string */ | |||
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 |
@@ -132,6 +132,11 @@ void Rack::step() { | |||
const float lambda = 1.0; | |||
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) { | |||
@@ -166,8 +171,6 @@ void Rack::addWire(Wire *wire) { | |||
assert(wire); | |||
VIPLock vipLock(impl->vipMutex); | |||
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 | |||
assert(impl->wires.find(wire) == impl->wires.end()); | |||
assert(wire->outputModule); | |||
@@ -180,8 +183,8 @@ void Rack::addWire(Wire *wire) { | |||
} | |||
// Connect the wire to inputModule | |||
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) { | |||
@@ -2,6 +2,7 @@ | |||
#include <mutex> | |||
#include <portaudio.h> | |||
#include "core.hpp" | |||
#include "dsp.hpp" | |||
using namespace rack; | |||
@@ -9,7 +10,7 @@ using namespace rack; | |||
void audioInit() { | |||
PaError err = Pa_Initialize(); | |||
if (err) { | |||
printf("Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err)); | |||
fprintf(stderr, "Failed to initialize PortAudio: %s\n", Pa_GetErrorText(err)); | |||
return; | |||
} | |||
} | |||
@@ -22,31 +23,35 @@ struct AudioInterface : Module { | |||
enum InputIds { | |||
AUDIO1_INPUT, | |||
AUDIO2_INPUT, | |||
AUDIO3_INPUT, | |||
AUDIO4_INPUT, | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
AUDIO1_OUTPUT, | |||
AUDIO2_OUTPUT, | |||
AUDIO3_OUTPUT, | |||
AUDIO4_OUTPUT, | |||
NUM_OUTPUTS | |||
}; | |||
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; | |||
float sampleRate = 44100.0; | |||
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(); | |||
@@ -54,8 +59,19 @@ struct AudioInterface : Module { | |||
int getDeviceCount(); | |||
std::string getDeviceName(int deviceId); | |||
void openDevice(); | |||
void openDevice(int deviceId, float sampleRate, int blockSize); | |||
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); | |||
inputs.resize(NUM_INPUTS); | |||
outputs.resize(NUM_OUTPUTS); | |||
closeDevice(); | |||
} | |||
AudioInterface::~AudioInterface() { | |||
@@ -72,51 +87,97 @@ AudioInterface::~AudioInterface() { | |||
void AudioInterface::step() { | |||
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; | |||
} | |||
// 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; | |||
// 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.) | |||
if (numInputs > 0) { | |||
err = Pa_ReadStream(stream, inputBuffer, blockSize); | |||
float *buf = new float[numInputs * blockSize]; | |||
err = Pa_ReadStream(stream, buf, blockSize); | |||
if (err) { | |||
// Ignore buffer underflows | |||
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) { | |||
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) { | |||
// Ignore buffer underflows | |||
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() { | |||
@@ -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); | |||
} | |||
void AudioInterface::openDevice() { | |||
void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) { | |||
closeDevice(); | |||
std::lock_guard<std::mutex> lock(mutex); | |||
this->sampleRate = sampleRate; | |||
this->blockSize = blockSize; | |||
// Open new device | |||
if (deviceId >= 0) { | |||
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; | |||
} | |||
numOutputs = mini(info->maxOutputChannels, 4); | |||
numInputs = mini(info->maxInputChannels, 4); | |||
numOutputs = mini(deviceInfo->maxOutputChannels, 2); | |||
numInputs = mini(deviceInfo->maxInputChannels, 2); | |||
PaStreamParameters outputParameters; | |||
outputParameters.device = deviceId; | |||
outputParameters.channelCount = numOutputs; | |||
outputParameters.sampleFormat = paFloat32; | |||
outputParameters.suggestedLatency = info->defaultLowOutputLatency; | |||
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency; | |||
outputParameters.hostApiSpecificStreamInfo = NULL; | |||
PaStreamParameters inputParameters; | |||
inputParameters.device = deviceId; | |||
inputParameters.channelCount = numInputs; | |||
inputParameters.sampleFormat = paFloat32; | |||
inputParameters.suggestedLatency = info->defaultLowInputLatency; | |||
inputParameters.suggestedLatency = deviceInfo->defaultLowInputLatency; | |||
inputParameters.hostApiSpecificStreamInfo = NULL; | |||
// Don't use stream parameters if 0 input or output channels | |||
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); | |||
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; | |||
} | |||
err = Pa_StartStream(stream); | |||
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; | |||
} | |||
// Correct sample rate | |||
const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); | |||
this->sampleRate = streamInfo->sampleRate; | |||
} | |||
this->deviceId = deviceId; | |||
} | |||
void AudioInterface::closeDevice() { | |||
@@ -187,13 +257,19 @@ void AudioInterface::closeDevice() { | |||
err = Pa_CloseStream(stream); | |||
if (err) { | |||
// 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; | |||
numInputs = 0; | |||
bufferFrame = 0; | |||
// Clear buffers | |||
inBuffer.clear(); | |||
outBuffer.clear(); | |||
} | |||
@@ -201,8 +277,7 @@ struct AudioItem : MenuItem { | |||
AudioInterface *audioInterface; | |||
int deviceId; | |||
void onAction() { | |||
audioInterface->deviceId = deviceId; | |||
audioInterface->openDevice(); | |||
audioInterface->setDeviceId(deviceId); | |||
} | |||
}; | |||
@@ -233,10 +308,7 @@ struct AudioChoice : ChoiceButton { | |||
} | |||
void step() { | |||
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; | |||
float sampleRate; | |||
void onAction() { | |||
audioInterface->sampleRate = sampleRate; | |||
audioInterface->openDevice(); | |||
audioInterface->setSampleRate(sampleRate); | |||
} | |||
}; | |||
@@ -262,7 +333,7 @@ struct SampleRateChoice : ChoiceButton { | |||
SampleRateItem *item = new SampleRateItem(); | |||
item->audioInterface = audioInterface; | |||
item->sampleRate = sampleRates[i]; | |||
item->text = stringf("%.0f", sampleRates[i]); | |||
item->text = stringf("%.0f Hz", sampleRates[i]); | |||
menu->pushChild(item); | |||
} | |||
@@ -270,7 +341,7 @@ struct SampleRateChoice : ChoiceButton { | |||
gScene->setOverlay(overlay); | |||
} | |||
void step() { | |||
this->text = stringf("%.0f", audioInterface->sampleRate); | |||
this->text = stringf("%.0f Hz", audioInterface->sampleRate); | |||
} | |||
}; | |||
@@ -279,8 +350,7 @@ struct BlockSizeItem : MenuItem { | |||
AudioInterface *audioInterface; | |||
int blockSize; | |||
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)); | |||
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->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(75, yPos), module, AudioInterface::AUDIO2_OUTPUT)); | |||
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) { | |||
@@ -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; | |||
} |
@@ -276,7 +276,7 @@ int loadImage(std::string filename) { | |||
auto it = images.find(filename); | |||
if (it == images.end()) { | |||
// 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) { | |||
printf("Failed to load image %s\n", filename.c_str()); | |||
} | |||
@@ -1,6 +1,7 @@ | |||
#include "util.hpp" | |||
#include <stdio.h> | |||
#include <stdarg.h> | |||
#include <random> | |||
namespace rack { | |||
@@ -25,6 +26,7 @@ float randomNormal(){ | |||
return normalDist(rng); | |||
} | |||
std::string stringf(const char *format, ...) { | |||
va_list ap; | |||
va_start(ap, format); | |||
@@ -32,13 +34,19 @@ std::string stringf(const char *format, ...) { | |||
if (size < 0) | |||
return ""; | |||
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); | |||
std::string s = buf; | |||
delete[] buf; | |||
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 |
@@ -100,10 +100,19 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||
bndBevel(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||
// 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); | |||
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); | |||
} | |||
} | |||
@@ -190,4 +199,4 @@ void ModuleWidget::onMouseDown(int button) { | |||
} | |||
} // namespace rack | |||
} // namespace rack |
@@ -64,6 +64,14 @@ json_t *RackWidget::toJson() { | |||
json_t *versionJ = json_string(gApplicationVersion.c_str()); | |||
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 | |||
json_t *modulesJ = json_array(); | |||
std::map<ModuleWidget*, int> moduleIds; | |||
@@ -121,6 +129,24 @@ json_t *RackWidget::toJson() { | |||
} | |||
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 | |||
std::map<int, ModuleWidget*> moduleWidgets; | |||
json_t *modulesJ = json_object_get(root, "modules"); | |||
@@ -62,7 +62,7 @@ struct FileChoice : ChoiceButton { | |||
struct SampleRateItem : MenuItem { | |||
float sampleRate; | |||
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.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}; | |||
for (int i = 0; i < 6; i++) { | |||
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]; | |||
menu->pushChild(item); | |||
} | |||
@@ -92,6 +84,9 @@ struct SampleRateChoice : ChoiceButton { | |||
overlay->addChild(menu); | |||
gScene->setOverlay(overlay); | |||
} | |||
void step() { | |||
text = stringf("%.0f Hz", gRack->sampleRate); | |||
} | |||
}; | |||
@@ -87,18 +87,21 @@ void WireWidget::updateWire() { | |||
void WireWidget::draw(NVGcontext *vg) { | |||
Vec outputPos, inputPos; | |||
Vec absolutePos = getAbsolutePos(); | |||
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | |||
if (outputPort) { | |||
outputPos = Rect(outputPort->getAbsolutePos(), outputPort->box.size).getCenter(); | |||
} | |||
else { | |||
outputPos = gMousePos; | |||
wireOpacity = 1.0; | |||
} | |||
if (inputPort) { | |||
inputPos = Rect(inputPort->getAbsolutePos(), inputPort->box.size).getCenter(); | |||
} | |||
else { | |||
inputPos = gMousePos; | |||
wireOpacity = 1.0; | |||
} | |||
outputPos = outputPos.minus(absolutePos); | |||
@@ -107,7 +110,6 @@ void WireWidget::draw(NVGcontext *vg) { | |||
bndNodePort(vg, outputPos.x, outputPos.y, BND_DEFAULT, color); | |||
bndNodePort(vg, inputPos.x, inputPos.y, BND_DEFAULT, color); | |||
nvgSave(vg); | |||
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | |||
if (wireOpacity > 0.0) { | |||
nvgGlobalAlpha(vg, wireOpacity); | |||
float tension = gScene->toolbar->wireTensionSlider->value; | |||