@@ -1,4 +1,12 @@ | |||||
### 0.6.2 (2018-07-13) | |||||
- Added module presets | |||||
- Added [command line arguments](https://vcvrack.com/manual/Installing.html#command-line-usage) for setting Rack directories | |||||
- Improved UI/engine communication stability | |||||
- [VCV Bridge](https://vcvrack.com/manual/Bridge.html) 0.6.2 | |||||
- Added VST FX plugin | |||||
### 0.6.1 (2018-06-17) | ### 0.6.1 (2018-06-17) | ||||
- Added gamepad MIDI driver | - Added gamepad MIDI driver | ||||
@@ -1,5 +1,5 @@ | |||||
RACK_DIR ?= . | RACK_DIR ?= . | ||||
VERSION = 0.6.1 | |||||
VERSION = 0.6.2 | |||||
FLAGS += \ | FLAGS += \ | ||||
-Iinclude \ | -Iinclude \ | ||||
@@ -121,7 +121,7 @@ endif | |||||
ifdef ARCH_WIN | ifdef ARCH_WIN | ||||
mkdir -p dist/Rack | mkdir -p dist/Rack | ||||
mkdir -p dist/Rack/Bridge | mkdir -p dist/Rack/Bridge | ||||
cp Bridge/VST/dist/VCV-Bridge-{32,64,-fx-32,-fx-64}.dll dist/Rack/Bridge/ | |||||
cp Bridge/VST/dist/VCV-Bridge-{32,64,fx-32,fx-64}.dll dist/Rack/Bridge/ | |||||
cp -R LICENSE* res dist/Rack/ | cp -R LICENSE* res dist/Rack/ | ||||
cp $(TARGET) dist/Rack/ | cp $(TARGET) dist/Rack/ | ||||
$(STRIP) -s dist/Rack/$(TARGET) | $(STRIP) -s dist/Rack/$(TARGET) | ||||
@@ -97,8 +97,8 @@ All **source code** in this repository is licensed under [BSD-3-Clause](LICENSE. | |||||
**Component Library graphics** in `res/ComponentLibrary` are licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) by [Grayscale](http://grayscale.info/). Commercial plugins must request a commercial license to use Component Library graphics by emailing contact@vcvrack.com. | **Component Library graphics** in `res/ComponentLibrary` are licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) by [Grayscale](http://grayscale.info/). Commercial plugins must request a commercial license to use Component Library graphics by emailing contact@vcvrack.com. | ||||
**Core** panel graphics in `res/Core` are copyright © 2017 Grayscale. You may not create derivative works of Core panels. | |||||
**Core panel graphics** in `res/Core` are copyright © 2017 Grayscale. You may not create derivative works of Core panels. | |||||
The **VCV logo and icon** are copyright © 2017 Andrew Belt and may not be used in derivative works. | The **VCV logo and icon** are copyright © 2017 Andrew Belt and may not be used in derivative works. | ||||
The **"VCV" name** is trademarked and may not be used for unofficial products. However, it is acceptable to use the phrase "for VCV Rack" for promotion of your plugin. For all other purposes, email contact@vcvrack.com. | |||||
The **"VCV" name** is trademarked and may not be used for unofficial products. However, it is acceptable to use the phrase "for VCV Rack" for promotion of your Rack plugin. For all other purposes, email contact@vcvrack.com. |
@@ -16,4 +16,8 @@ std::string assetLocal(std::string filename); | |||||
std::string assetPlugin(Plugin *plugin, std::string filename); | std::string assetPlugin(Plugin *plugin, std::string filename); | ||||
extern std::string assetGlobalDir; | |||||
extern std::string assetLocalDir; | |||||
} // namespace rack | } // namespace rack |
@@ -1,39 +1,2 @@ | |||||
#pragma once | #pragma once | ||||
#include "string.h" | |||||
#include "dsp/ringbuffer.hpp" | |||||
#include "dsp/fir.hpp" | |||||
namespace rack { | |||||
template<int OVERSAMPLE, int QUALITY> | |||||
struct Decimator { | |||||
DoubleRingBuffer<float, OVERSAMPLE*QUALITY> inBuffer; | |||||
float kernel[OVERSAMPLE*QUALITY]; | |||||
Decimator(float cutoff = 0.9) { | |||||
boxcarFIR(kernel, OVERSAMPLE*QUALITY, cutoff * 0.5 / OVERSAMPLE); | |||||
blackmanHarrisWindow(kernel, OVERSAMPLE*QUALITY); | |||||
// The sum of the kernel should be 1 | |||||
float sum = 0.0; | |||||
for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { | |||||
sum += kernel[i]; | |||||
} | |||||
for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { | |||||
kernel[i] /= sum; | |||||
} | |||||
// Zero input buffer | |||||
memset(inBuffer.data, 0, sizeof(inBuffer.data)); | |||||
} | |||||
/** `in` must be OVERSAMPLE floats long */ | |||||
float process(float *in) { | |||||
memcpy(inBuffer.endData(), in, OVERSAMPLE*sizeof(float)); | |||||
inBuffer.endIncr(OVERSAMPLE); | |||||
float out = convolve(inBuffer.endData() + OVERSAMPLE*QUALITY, kernel, OVERSAMPLE*QUALITY); | |||||
// Ignore the ring buffer's start position | |||||
return out; | |||||
} | |||||
}; | |||||
} // namespace rack | |||||
#include "resampler.hpp" |
@@ -6,7 +6,7 @@ | |||||
namespace rack { | namespace rack { | ||||
/** Turns high when value reaches 1, turns low when value reaches 0 */ | |||||
/** Turns HIGH when value reaches 1.f, turns LOW when value reaches 0.f. */ | |||||
struct SchmittTrigger { | struct SchmittTrigger { | ||||
// UNKNOWN is used to represent a stable state when the previous state is not yet set | // UNKNOWN is used to represent a stable state when the previous state is not yet set | ||||
enum State { | enum State { | ||||
@@ -14,7 +14,14 @@ struct SchmittTrigger { | |||||
LOW, | LOW, | ||||
HIGH | HIGH | ||||
}; | }; | ||||
State state = UNKNOWN; | |||||
State state; | |||||
SchmittTrigger() { | |||||
reset(); | |||||
} | |||||
void reset() { | |||||
state = UNKNOWN; | |||||
} | |||||
/** Updates the state of the Schmitt Trigger given a value. | /** Updates the state of the Schmitt Trigger given a value. | ||||
Returns true if triggered, i.e. the value increases from 0 to 1. | Returns true if triggered, i.e. the value increases from 0 to 1. | ||||
If different trigger thresholds are needed, use | If different trigger thresholds are needed, use | ||||
@@ -48,25 +55,33 @@ struct SchmittTrigger { | |||||
bool isHigh() { | bool isHigh() { | ||||
return state == HIGH; | return state == HIGH; | ||||
} | } | ||||
void reset() { | |||||
state = UNKNOWN; | |||||
} | |||||
}; | }; | ||||
/** When triggered, holds a high value for a specified time before going low again */ | /** When triggered, holds a high value for a specified time before going low again */ | ||||
struct PulseGenerator { | struct PulseGenerator { | ||||
float time = 0.f; | |||||
float pulseTime = 0.f; | |||||
float time; | |||||
float triggerDuration; | |||||
PulseGenerator() { | |||||
reset(); | |||||
} | |||||
/** Immediately resets the state to LOW */ | |||||
void reset() { | |||||
time = 0.f; | |||||
triggerDuration = 0.f; | |||||
} | |||||
/** Advances the state by `deltaTime`. Returns whether the pulse is in the HIGH state. */ | |||||
bool process(float deltaTime) { | bool process(float deltaTime) { | ||||
time += deltaTime; | time += deltaTime; | ||||
return time < pulseTime; | |||||
return time < triggerDuration; | |||||
} | } | ||||
void trigger(float pulseTime) { | |||||
// Keep the previous pulseTime if the existing pulse would be held longer than the currently requested one. | |||||
if (time + pulseTime >= this->pulseTime) { | |||||
/** Begins a trigger with the given `triggerDuration`. */ | |||||
void trigger(float triggerDuration) { | |||||
// Keep the previous triggerDuration if the existing pulse would be held longer than the currently requested one. | |||||
if (time + triggerDuration >= this->triggerDuration) { | |||||
time = 0.f; | time = 0.f; | ||||
this->pulseTime = pulseTime; | |||||
this->triggerDuration = triggerDuration; | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
@@ -5,34 +5,36 @@ | |||||
namespace rack { | namespace rack { | ||||
/** Perform a direct convolution | |||||
x[-len + 1] to x[0] must be defined | |||||
*/ | |||||
inline float convolve(const float *x, const float *kernel, int len) { | |||||
float y = 0.0; | |||||
/** Performs a direct sum convolution */ | |||||
inline float convolveNaive(const float *in, const float *kernel, int len) { | |||||
float y = 0.f; | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
y += x[-i] * kernel[i]; | |||||
y += in[len - 1 - i] * kernel[i]; | |||||
} | } | ||||
return y; | return y; | ||||
} | } | ||||
inline void blackmanHarrisWindow(float *x, int n) { | |||||
const float a0 = 0.35875; | |||||
const float a1 = 0.48829; | |||||
const float a2 = 0.14128; | |||||
const float a3 = 0.01168; | |||||
for (int i = 0; i < n; i++) { | |||||
x[i] *= a0 | |||||
- a1 * cosf(2 * M_PI * i / (n - 1)) | |||||
+ a2 * cosf(4 * M_PI * i / (n - 1)) | |||||
- a3 * cosf(6 * M_PI * i / (n - 1)); | |||||
/** Computes the impulse response of a boxcar lowpass filter */ | |||||
inline void boxcarLowpassIR(float *out, int len, float cutoff = 0.5f) { | |||||
for (int i = 0; i < len; i++) { | |||||
float t = i - (len - 1) / 2.f; | |||||
out[i] = 2 * cutoff * sinc(2 * cutoff * t); | |||||
} | } | ||||
} | } | ||||
inline void boxcarFIR(float *x, int n, float cutoff) { | |||||
for (int i = 0; i < n; i++) { | |||||
float t = (float)i / (n - 1) * 2.0 - 1.0; | |||||
x[i] = sinc(t * n * cutoff); | |||||
inline void blackmanHarrisWindow(float *x, int len) { | |||||
// Constants from https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window | |||||
const float a0 = 0.35875f; | |||||
const float a1 = 0.48829f; | |||||
const float a2 = 0.14128f; | |||||
const float a3 = 0.01168f; | |||||
float factor = 2*M_PI / (len - 1); | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] *= | |||||
+ a0 | |||||
- a1 * cosf(1*factor * i) | |||||
+ a2 * cosf(2*factor * i) | |||||
- a3 * cosf(3*factor * i); | |||||
} | } | ||||
} | } | ||||
@@ -6,6 +6,7 @@ | |||||
namespace rack { | namespace rack { | ||||
/** The normalized sinc function. https://en.wikipedia.org/wiki/Sinc_function */ | |||||
inline float sinc(float x) { | inline float sinc(float x) { | ||||
if (x == 0.f) | if (x == 0.f) | ||||
return 1.f; | return 1.f; | ||||
@@ -2,47 +2,87 @@ | |||||
namespace rack { | namespace rack { | ||||
namespace ode { | |||||
typedef void (*stepCallback)(float x, const float y[], float dydt[]); | |||||
/** The callback function `f` in each of these stepping functions must have the signature | |||||
/** Solve an ODE system using the 1st order Euler method */ | |||||
inline void stepEuler(stepCallback f, float x, float dx, float y[], int len) { | |||||
void f(float t, const float x[], float dxdt[]) | |||||
A capturing lambda is ideal for this. | |||||
For example, the following solves the system x''(t) = -x(t) using a fixed timestep of 0.01 and initial conditions x(0) = 1, x'(0) = 0. | |||||
float x[2] = {1.f, 0.f}; | |||||
float dt = 0.01f; | |||||
for (float t = 0.f; t < 1.f; t += dt) { | |||||
rack::ode::stepRK4(t, dt, x, 2, [&](float t, const float x[], float dxdt[]) { | |||||
dxdt[0] = x[1]; | |||||
dxdt[1] = -x[0]; | |||||
}); | |||||
printf("%f\n", x[0]); | |||||
} | |||||
*/ | |||||
/** Solves an ODE system using the 1st order Euler method */ | |||||
template<typename F> | |||||
void stepEuler(float t, float dt, float x[], int len, F f) { | |||||
float k[len]; | float k[len]; | ||||
f(x, y, k); | |||||
f(t, x, k); | |||||
for (int i = 0; i < len; i++) { | |||||
x[i] += dt * k[i]; | |||||
} | |||||
} | |||||
/** Solves an ODE system using the 2nd order Runge-Kutta method */ | |||||
template<typename F> | |||||
void stepRK2(float t, float dt, float x[], int len, F f) { | |||||
float k1[len]; | |||||
float k2[len]; | |||||
float yi[len]; | |||||
f(t, x, k1); | |||||
for (int i = 0; i < len; i++) { | |||||
yi[i] = x[i] + k1[i] * dt / 2.f; | |||||
} | |||||
f(t + dt / 2.f, yi, k2); | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
y[i] += dx * k[i]; | |||||
x[i] += dt * k2[i]; | |||||
} | } | ||||
} | } | ||||
/** Solve an ODE system using the 4th order Runge-Kutta method */ | |||||
inline void stepRK4(stepCallback f, float x, float dx, float y[], int len) { | |||||
/** Solves an ODE system using the 4th order Runge-Kutta method */ | |||||
template<typename F> | |||||
void stepRK4(float t, float dt, float x[], int len, F f) { | |||||
float k1[len]; | float k1[len]; | ||||
float k2[len]; | float k2[len]; | ||||
float k3[len]; | float k3[len]; | ||||
float k4[len]; | float k4[len]; | ||||
float yi[len]; | float yi[len]; | ||||
f(x, y, k1); | |||||
f(t, x, k1); | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
yi[i] = y[i] + k1[i] * dx / 2.0; | |||||
yi[i] = x[i] + k1[i] * dt / 2.f; | |||||
} | } | ||||
f(x + dx / 2.0, yi, k2); | |||||
f(t + dt / 2.f, yi, k2); | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
yi[i] = y[i] + k2[i] * dx / 2.0; | |||||
yi[i] = x[i] + k2[i] * dt / 2.f; | |||||
} | } | ||||
f(x + dx / 2.0, yi, k3); | |||||
f(t + dt / 2.f, yi, k3); | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
yi[i] = y[i] + k3[i] * dx; | |||||
yi[i] = x[i] + k3[i] * dt; | |||||
} | } | ||||
f(x + dx, yi, k4); | |||||
f(t + dt, yi, k4); | |||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
y[i] += dx * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]) / 6.0; | |||||
x[i] += dt * (k1[i] + 2.f * k2[i] + 2.f * k3[i] + k4[i]) / 6.f; | |||||
} | } | ||||
} | } | ||||
} // namespace ode | |||||
} // namespace rack | } // namespace rack |
@@ -0,0 +1,174 @@ | |||||
#pragma once | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#include <speex/speex_resampler.h> | |||||
#include "frame.hpp" | |||||
#include "ringbuffer.hpp" | |||||
#include "fir.hpp" | |||||
namespace rack { | |||||
template<int CHANNELS> | |||||
struct SampleRateConverter { | |||||
SpeexResamplerState *st = NULL; | |||||
int channels = CHANNELS; | |||||
int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; | |||||
int inRate = 44100; | |||||
int outRate = 44100; | |||||
SampleRateConverter() { | |||||
refreshState(); | |||||
} | |||||
~SampleRateConverter() { | |||||
if (st) { | |||||
speex_resampler_destroy(st); | |||||
} | |||||
} | |||||
/** Sets the number of channels to actually process. This can be at most CHANNELS. */ | |||||
void setChannels(int channels) { | |||||
assert(channels <= CHANNELS); | |||||
if (channels == this->channels) | |||||
return; | |||||
this->channels = channels; | |||||
refreshState(); | |||||
} | |||||
/** From 0 (worst, fastest) to 10 (best, slowest) */ | |||||
void setQuality(int quality) { | |||||
if (quality == this->quality) | |||||
return; | |||||
this->quality = quality; | |||||
refreshState(); | |||||
} | |||||
void setRates(int inRate, int outRate) { | |||||
if (inRate == this->inRate && outRate == this->outRate) | |||||
return; | |||||
this->inRate = inRate; | |||||
this->outRate = outRate; | |||||
refreshState(); | |||||
} | |||||
void refreshState() { | |||||
if (st) { | |||||
speex_resampler_destroy(st); | |||||
st = NULL; | |||||
} | |||||
if (channels > 0 && inRate != outRate) { | |||||
int err; | |||||
st = speex_resampler_init(channels, inRate, outRate, quality, &err); | |||||
assert(st); | |||||
assert(err == RESAMPLER_ERR_SUCCESS); | |||||
speex_resampler_set_input_stride(st, CHANNELS); | |||||
speex_resampler_set_output_stride(st, CHANNELS); | |||||
} | |||||
} | |||||
/** `in` and `out` are interlaced with the number of channels */ | |||||
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | |||||
assert(in); | |||||
assert(inFrames); | |||||
assert(out); | |||||
assert(outFrames); | |||||
if (st) { | |||||
// Resample each channel at a time | |||||
spx_uint32_t inLen; | |||||
spx_uint32_t outLen; | |||||
for (int i = 0; i < channels; i++) { | |||||
inLen = *inFrames; | |||||
outLen = *outFrames; | |||||
int err = speex_resampler_process_float(st, i, ((const float*) in) + i, &inLen, ((float*) out) + i, &outLen); | |||||
assert(err == RESAMPLER_ERR_SUCCESS); | |||||
} | |||||
*inFrames = inLen; | |||||
*outFrames = outLen; | |||||
} | |||||
else { | |||||
// Simply copy the buffer without conversion | |||||
int frames = min(*inFrames, *outFrames); | |||||
memcpy(out, in, frames * sizeof(Frame<CHANNELS>)); | |||||
*inFrames = frames; | |||||
*outFrames = frames; | |||||
} | |||||
} | |||||
}; | |||||
template<int OVERSAMPLE, int QUALITY> | |||||
struct Decimator { | |||||
float inBuffer[OVERSAMPLE*QUALITY]; | |||||
float kernel[OVERSAMPLE*QUALITY]; | |||||
int inIndex; | |||||
Decimator(float cutoff = 0.9f) { | |||||
boxcarLowpassIR(kernel, OVERSAMPLE*QUALITY, cutoff * 0.5f / OVERSAMPLE); | |||||
blackmanHarrisWindow(kernel, OVERSAMPLE*QUALITY); | |||||
reset(); | |||||
} | |||||
void reset() { | |||||
inIndex = 0; | |||||
memset(inBuffer, 0, sizeof(inBuffer)); | |||||
} | |||||
/** `in` must be length OVERSAMPLE */ | |||||
float process(float *in) { | |||||
// Copy input to buffer | |||||
memcpy(&inBuffer[inIndex], in, OVERSAMPLE*sizeof(float)); | |||||
// Advance index | |||||
inIndex += OVERSAMPLE; | |||||
inIndex %= OVERSAMPLE*QUALITY; | |||||
// Perform naive convolution | |||||
float out = 0.f; | |||||
for (int i = 0; i < OVERSAMPLE*QUALITY; i++) { | |||||
int index = inIndex - 1 - i; | |||||
index = (index + OVERSAMPLE*QUALITY) % (OVERSAMPLE*QUALITY); | |||||
out += kernel[i] * inBuffer[index]; | |||||
} | |||||
return out; | |||||
} | |||||
}; | |||||
template<int OVERSAMPLE, int QUALITY> | |||||
struct Upsampler { | |||||
float inBuffer[QUALITY]; | |||||
float kernel[OVERSAMPLE*QUALITY]; | |||||
int inIndex; | |||||
Upsampler(float cutoff = 0.9f) { | |||||
boxcarLowpassIR(kernel, OVERSAMPLE*QUALITY, cutoff * 0.5f / OVERSAMPLE); | |||||
blackmanHarrisWindow(kernel, OVERSAMPLE*QUALITY); | |||||
reset(); | |||||
} | |||||
void reset() { | |||||
inIndex = 0; | |||||
memset(inBuffer, 0, sizeof(inBuffer)); | |||||
} | |||||
/** `out` must be length OVERSAMPLE */ | |||||
void process(float in, float *out) { | |||||
// Zero-stuff input buffer | |||||
inBuffer[inIndex] = OVERSAMPLE * in; | |||||
// Advance index | |||||
inIndex++; | |||||
inIndex %= QUALITY; | |||||
// Naively convolve each sample | |||||
// TODO replace with polyphase filter hierarchy | |||||
for (int i = 0; i < OVERSAMPLE; i++) { | |||||
float y = 0.f; | |||||
for (int j = 0; j < QUALITY; j++) { | |||||
int index = inIndex - 1 - j; | |||||
index = (index + QUALITY) % QUALITY; | |||||
int kernelIndex = OVERSAMPLE * j + i; | |||||
y += kernel[kernelIndex] * inBuffer[index]; | |||||
} | |||||
out[i] = y; | |||||
} | |||||
} | |||||
}; | |||||
} // namespace rack |
@@ -1,99 +1,2 @@ | |||||
#pragma once | #pragma once | ||||
#include <assert.h> | |||||
#include <string.h> | |||||
#include <speex/speex_resampler.h> | |||||
#include "frame.hpp" | |||||
namespace rack { | |||||
template<int CHANNELS> | |||||
struct SampleRateConverter { | |||||
SpeexResamplerState *st = NULL; | |||||
int channels = CHANNELS; | |||||
int quality = SPEEX_RESAMPLER_QUALITY_DEFAULT; | |||||
int inRate = 44100; | |||||
int outRate = 44100; | |||||
SampleRateConverter() { | |||||
refreshState(); | |||||
} | |||||
~SampleRateConverter() { | |||||
if (st) { | |||||
speex_resampler_destroy(st); | |||||
} | |||||
} | |||||
/** Sets the number of channels to actually process. This can be at most CHANNELS. */ | |||||
void setChannels(int channels) { | |||||
assert(channels <= CHANNELS); | |||||
if (channels == this->channels) | |||||
return; | |||||
this->channels = channels; | |||||
refreshState(); | |||||
} | |||||
/** From 0 (worst, fastest) to 10 (best, slowest) */ | |||||
void setQuality(int quality) { | |||||
if (quality == this->quality) | |||||
return; | |||||
this->quality = quality; | |||||
refreshState(); | |||||
} | |||||
void setRates(int inRate, int outRate) { | |||||
if (inRate == this->inRate && outRate == this->outRate) | |||||
return; | |||||
this->inRate = inRate; | |||||
this->outRate = outRate; | |||||
refreshState(); | |||||
} | |||||
void refreshState() { | |||||
if (st) { | |||||
speex_resampler_destroy(st); | |||||
st = NULL; | |||||
} | |||||
if (channels > 0 && inRate != outRate) { | |||||
int err; | |||||
st = speex_resampler_init(channels, inRate, outRate, quality, &err); | |||||
assert(st); | |||||
assert(err == RESAMPLER_ERR_SUCCESS); | |||||
speex_resampler_set_input_stride(st, CHANNELS); | |||||
speex_resampler_set_output_stride(st, CHANNELS); | |||||
} | |||||
} | |||||
/** `in` and `out` are interlaced with the number of channels */ | |||||
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { | |||||
assert(in); | |||||
assert(inFrames); | |||||
assert(out); | |||||
assert(outFrames); | |||||
if (st) { | |||||
// Resample each channel at a time | |||||
spx_uint32_t inLen; | |||||
spx_uint32_t outLen; | |||||
for (int i = 0; i < channels; i++) { | |||||
inLen = *inFrames; | |||||
outLen = *outFrames; | |||||
int err = speex_resampler_process_float(st, i, ((const float*) in) + i, &inLen, ((float*) out) + i, &outLen); | |||||
assert(err == RESAMPLER_ERR_SUCCESS); | |||||
} | |||||
*inFrames = inLen; | |||||
*outFrames = outLen; | |||||
} | |||||
else { | |||||
// Simply copy the buffer without conversion | |||||
int frames = min(*inFrames, *outFrames); | |||||
memcpy(out, in, frames * sizeof(Frame<CHANNELS>)); | |||||
*inFrames = frames; | |||||
*outFrames = frames; | |||||
} | |||||
} | |||||
}; | |||||
} // namespace rack | |||||
#include "resampler.hpp" |
@@ -0,0 +1,130 @@ | |||||
#include "plugin.hpp" | |||||
#include "engine.hpp" | |||||
#include "app.hpp" | |||||
namespace rack { | |||||
template <class TModule, class TModuleWidget, typename... Tags> | |||||
Model *createModel(std::string author, std::string slug, std::string name, Tags... tags) { | |||||
struct TModel : Model { | |||||
Module *createModule() override { | |||||
TModule *module = new TModule(); | |||||
return module; | |||||
} | |||||
ModuleWidget *createModuleWidget() override { | |||||
TModule *module = new TModule(); | |||||
TModuleWidget *moduleWidget = new TModuleWidget(module); | |||||
moduleWidget->model = this; | |||||
return moduleWidget; | |||||
} | |||||
ModuleWidget *createModuleWidgetNull() override { | |||||
TModuleWidget *moduleWidget = new TModuleWidget(NULL); | |||||
moduleWidget->model = this; | |||||
return moduleWidget; | |||||
} | |||||
}; | |||||
Model *model = new TModel(); | |||||
model->author = author; | |||||
model->slug = slug; | |||||
model->name = name; | |||||
model->tags = {tags...}; | |||||
return model; | |||||
} | |||||
template <class TWidget> | |||||
TWidget *createWidget(Vec pos) { | |||||
TWidget *w = new TWidget(); | |||||
w->box.pos = pos; | |||||
return w; | |||||
} | |||||
/** Deprecated. Use createWidget<TScrew>() instead */ | |||||
template <class TScrew> | |||||
DEPRECATED TScrew *createScrew(Vec pos) { | |||||
return createWidget<TScrew>(pos); | |||||
} | |||||
template <class TParamWidget> | |||||
TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||||
TParamWidget *param = new TParamWidget(); | |||||
param->box.pos = pos; | |||||
param->module = module; | |||||
param->paramId = paramId; | |||||
param->setLimits(minValue, maxValue); | |||||
param->setDefaultValue(defaultValue); | |||||
return param; | |||||
} | |||||
template <class TParamWidget> | |||||
TParamWidget *createParamCentered(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||||
TParamWidget *param = new TParamWidget(); | |||||
param->box.pos = pos.minus(param->box.size.div(2)); | |||||
param->module = module; | |||||
param->paramId = paramId; | |||||
param->setLimits(minValue, maxValue); | |||||
param->setDefaultValue(defaultValue); | |||||
return param; | |||||
} | |||||
template <class TPort> | |||||
TPort *createInput(Vec pos, Module *module, int inputId) { | |||||
TPort *port = new TPort(); | |||||
port->box.pos = pos; | |||||
port->module = module; | |||||
port->type = Port::INPUT; | |||||
port->portId = inputId; | |||||
return port; | |||||
} | |||||
template <class TPort> | |||||
TPort *createInputCentered(Vec pos, Module *module, int inputId) { | |||||
TPort *port = new TPort(); | |||||
port->box.pos = pos.minus(port->box.size.div(2)); | |||||
port->module = module; | |||||
port->type = Port::INPUT; | |||||
port->portId = inputId; | |||||
return port; | |||||
} | |||||
template <class TPort> | |||||
TPort *createOutput(Vec pos, Module *module, int outputId) { | |||||
TPort *port = new TPort(); | |||||
port->box.pos = pos; | |||||
port->module = module; | |||||
port->type = Port::OUTPUT; | |||||
port->portId = outputId; | |||||
return port; | |||||
} | |||||
template <class TPort> | |||||
TPort *createOutputCentered(Vec pos, Module *module, int outputId) { | |||||
TPort *port = new TPort(); | |||||
port->box.pos = pos.minus(port->box.size.div(2)); | |||||
port->module = module; | |||||
port->type = Port::OUTPUT; | |||||
port->portId = outputId; | |||||
return port; | |||||
} | |||||
template <class TModuleLightWidget> | |||||
TModuleLightWidget *createLight(Vec pos, Module *module, int firstLightId) { | |||||
TModuleLightWidget *light = new TModuleLightWidget(); | |||||
light->box.pos = pos; | |||||
light->module = module; | |||||
light->firstLightId = firstLightId; | |||||
return light; | |||||
} | |||||
template <class TModuleLightWidget> | |||||
TModuleLightWidget *createLightCentered(Vec pos, Module *module, int firstLightId) { | |||||
TModuleLightWidget *light = new TModuleLightWidget(); | |||||
light->box.pos = pos.minus(light->box.size.div(2)); | |||||
light->module = module; | |||||
light->firstLightId = firstLightId; | |||||
return light; | |||||
} | |||||
} // namespace rack |
@@ -9,84 +9,4 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "ui.hpp" | #include "ui.hpp" | ||||
#include "componentlibrary.hpp" | #include "componentlibrary.hpp" | ||||
namespace rack { | |||||
//////////////////// | |||||
// helpers | |||||
//////////////////// | |||||
/** Deprecated, use Model::create<TModule, TModuleWidget>(...) instead */ | |||||
template <class TModuleWidget, typename... Tags> | |||||
DEPRECATED Model *createModel(std::string author, std::string slug, std::string name, Tags... tags) { | |||||
struct TModel : Model { | |||||
ModuleWidget *createModuleWidget() override { | |||||
ModuleWidget *moduleWidget = new TModuleWidget(); | |||||
moduleWidget->model = this; | |||||
return moduleWidget; | |||||
} | |||||
}; | |||||
Model *model = new TModel(); | |||||
model->author = author; | |||||
model->slug = slug; | |||||
model->name = name; | |||||
model->tags = {tags...}; | |||||
return model; | |||||
} | |||||
/** Deprecated, use Widget::create<TScrew>() instead */ | |||||
template <class TScrew> | |||||
DEPRECATED TScrew *createScrew(Vec pos) { | |||||
TScrew *screw = new TScrew(); | |||||
screw->box.pos = pos; | |||||
return screw; | |||||
} | |||||
/** Deprecated, use ParamWidget::create<TParamWidget>() instead */ | |||||
template <class TParamWidget> | |||||
DEPRECATED TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||||
TParamWidget *param = new TParamWidget(); | |||||
param->box.pos = pos; | |||||
param->module = module; | |||||
param->paramId = paramId; | |||||
param->setLimits(minValue, maxValue); | |||||
param->setDefaultValue(defaultValue); | |||||
return param; | |||||
} | |||||
/** Deprecated, use Port::create<TPort>(..., Port::INPUT, ...) instead */ | |||||
template <class TPort> | |||||
DEPRECATED TPort *createInput(Vec pos, Module *module, int inputId) { | |||||
TPort *port = new TPort(); | |||||
port->box.pos = pos; | |||||
port->module = module; | |||||
port->type = Port::INPUT; | |||||
port->portId = inputId; | |||||
return port; | |||||
} | |||||
/** Deprecated, use Port::create<TPort>(..., Port::OUTPUT, ...) instead */ | |||||
template <class TPort> | |||||
DEPRECATED TPort *createOutput(Vec pos, Module *module, int outputId) { | |||||
TPort *port = new TPort(); | |||||
port->box.pos = pos; | |||||
port->module = module; | |||||
port->type = Port::OUTPUT; | |||||
port->portId = outputId; | |||||
return port; | |||||
} | |||||
/** Deprecated, use ModuleLightWidget::create<TModuleLightWidget>() instead */ | |||||
template<class TModuleLightWidget> | |||||
DEPRECATED TModuleLightWidget *createLight(Vec pos, Module *module, int firstLightId) { | |||||
TModuleLightWidget *light = new TModuleLightWidget(); | |||||
light->box.pos = pos; | |||||
light->module = module; | |||||
light->firstLightId = firstLightId; | |||||
return light; | |||||
} | |||||
} // namespace rack | |||||
#include "helpers.hpp" |
@@ -116,14 +116,14 @@ Example: | |||||
fclose(file); | fclose(file); | ||||
}); | }); | ||||
*/ | */ | ||||
template <typename F> | |||||
template<typename F> | |||||
struct DeferWrapper { | struct DeferWrapper { | ||||
F f; | F f; | ||||
DeferWrapper(F f) : f(f) {} | DeferWrapper(F f) : f(f) {} | ||||
~DeferWrapper() { f(); } | ~DeferWrapper() { f(); } | ||||
}; | }; | ||||
template <typename F> | |||||
template<typename F> | |||||
DeferWrapper<F> deferWrapper(F f) { | DeferWrapper<F> deferWrapper(F f) { | ||||
return DeferWrapper<F>(f); | return DeferWrapper<F>(f); | ||||
} | } | ||||
@@ -297,6 +297,12 @@ struct Rect { | |||||
r.size = size.plus(delta.mult(2.f)); | r.size = size.plus(delta.mult(2.f)); | ||||
return r; | return r; | ||||
} | } | ||||
Rect shrink(Vec delta) { | |||||
Rect r; | |||||
r.pos = pos.plus(delta); | |||||
r.size = size.minus(delta.mult(2.f)); | |||||
return r; | |||||
} | |||||
}; | }; | ||||
@@ -33,13 +33,13 @@ RequestExecutionLevel admin | |||||
Var VST_64_DIR | Var VST_64_DIR | ||||
!define MUI_DIRECTORYPAGE_VARIABLE $VST_64_DIR | !define MUI_DIRECTORYPAGE_VARIABLE $VST_64_DIR | ||||
!define MUI_DIRECTORYPAGE_TEXT_TOP "Bridge VST 64-bit plugin install directory" | |||||
!define MUI_DIRECTORYPAGE_TEXT_TOP "Bridge 64-bit VST plugin install directory" | |||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE VST_64_DIR_PRE | !define MUI_PAGE_CUSTOMFUNCTION_PRE VST_64_DIR_PRE | ||||
!insertmacro MUI_PAGE_DIRECTORY | !insertmacro MUI_PAGE_DIRECTORY | ||||
Var VST_32_DIR | Var VST_32_DIR | ||||
!define MUI_DIRECTORYPAGE_VARIABLE $VST_32_DIR | !define MUI_DIRECTORYPAGE_VARIABLE $VST_32_DIR | ||||
!define MUI_DIRECTORYPAGE_TEXT_TOP "Bridge VST 32-bit plugin install directory" | |||||
!define MUI_DIRECTORYPAGE_TEXT_TOP "Bridge 32-bit VST plugin install directory" | |||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE VST_32_DIR_PRE | !define MUI_PAGE_CUSTOMFUNCTION_PRE VST_32_DIR_PRE | ||||
!insertmacro MUI_PAGE_DIRECTORY | !insertmacro MUI_PAGE_DIRECTORY | ||||
@@ -79,17 +79,19 @@ Section "VCV Rack" VCVRACK | |||||
SectionEnd | SectionEnd | ||||
Section "Bridge VST 64-bit plugin" VST_64 | |||||
Section "Bridge 64-bit VST plugin" VST_64 | |||||
StrCpy $OUTDIR $VST_64_DIR | StrCpy $OUTDIR $VST_64_DIR | ||||
CreateDirectory $OUTDIR | CreateDirectory $OUTDIR | ||||
File "dist\Rack\Bridge\VCV-Bridge-64.dll" | File "dist\Rack\Bridge\VCV-Bridge-64.dll" | ||||
File "dist\Rack\Bridge\VCV-Bridge-fx-64.dll" | |||||
SectionEnd | SectionEnd | ||||
Section "Bridge VST 32-bit plugin" VST_32 | |||||
Section "Bridge 32-bit VST plugin" VST_32 | |||||
StrCpy $OUTDIR $VST_32_DIR | StrCpy $OUTDIR $VST_32_DIR | ||||
CreateDirectory $OUTDIR | CreateDirectory $OUTDIR | ||||
File "dist\Rack\Bridge\VCV-Bridge-32.dll" | File "dist\Rack\Bridge\VCV-Bridge-32.dll" | ||||
File "dist\Rack\Bridge\VCV-Bridge-fx-32.dll" | |||||
SectionEnd | SectionEnd | ||||
@@ -9,12 +9,12 @@ | |||||
xmlns="http://www.w3.org/2000/svg" | xmlns="http://www.w3.org/2000/svg" | ||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
width="15.999999" | |||||
height="15.997643" | |||||
viewBox="0 0 4.2333331 4.2327099" | |||||
width="5.4186664mm" | |||||
height="5.4178686mm" | |||||
viewBox="0 0 5.4186663 5.4178686" | |||||
version="1.1" | version="1.1" | ||||
id="svg12484" | id="svg12484" | ||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||||
inkscape:version="0.92.2 2405546, 2018-03-11" | |||||
sodipodi:docname="TL1105_0.svg"> | sodipodi:docname="TL1105_0.svg"> | ||||
<defs | <defs | ||||
id="defs12478" /> | id="defs12478" /> | ||||
@@ -25,9 +25,9 @@ | |||||
borderopacity="1.0" | borderopacity="1.0" | ||||
inkscape:pageopacity="0.0" | inkscape:pageopacity="0.0" | ||||
inkscape:pageshadow="2" | inkscape:pageshadow="2" | ||||
inkscape:zoom="44.8" | |||||
inkscape:cx="1.1769543" | |||||
inkscape:cy="7.1864792" | |||||
inkscape:zoom="11.2" | |||||
inkscape:cx="6.3388991" | |||||
inkscape:cy="5.196676" | |||||
inkscape:document-units="mm" | inkscape:document-units="mm" | ||||
inkscape:current-layer="g12430" | inkscape:current-layer="g12430" | ||||
showgrid="false" | showgrid="false" | ||||
@@ -35,12 +35,12 @@ | |||||
fit-margin-left="0" | fit-margin-left="0" | ||||
fit-margin-right="0" | fit-margin-right="0" | ||||
fit-margin-bottom="0" | fit-margin-bottom="0" | ||||
inkscape:window-width="2560" | |||||
inkscape:window-height="1422" | |||||
inkscape:window-width="1600" | |||||
inkscape:window-height="882" | |||||
inkscape:window-x="0" | inkscape:window-x="0" | ||||
inkscape:window-y="18" | inkscape:window-y="18" | ||||
inkscape:window-maximized="0" | inkscape:window-maximized="0" | ||||
units="px" /> | |||||
units="mm" /> | |||||
<metadata | <metadata | ||||
id="metadata12481"> | id="metadata12481"> | ||||
<rdf:RDF> | <rdf:RDF> | ||||
@@ -57,13 +57,13 @@ | |||||
inkscape:label="Layer 1" | inkscape:label="Layer 1" | ||||
inkscape:groupmode="layer" | inkscape:groupmode="layer" | ||||
id="layer1" | id="layer1" | ||||
transform="translate(-87.434409,-107.81432)"> | |||||
transform="translate(-86.841742,-107.22174)"> | |||||
<g | <g | ||||
transform="matrix(0.15583204,0,0,-0.15583204,44.616575,295.36332)" | transform="matrix(0.15583204,0,0,-0.15583204,44.616575,295.36332)" | ||||
id="g12430" | id="g12430" | ||||
style="stroke-width:2.26383328"> | style="stroke-width:2.26383328"> | ||||
<g | <g | ||||
transform="translate(301.93513,1189.951)" | |||||
transform="matrix(1.28,0,0,1.28,305.73837,1189.9507)" | |||||
id="g5959-5" | id="g5959-5" | ||||
style="stroke-width:2.26383328"> | style="stroke-width:2.26383328"> | ||||
<path | <path | ||||
@@ -73,19 +73,19 @@ | |||||
d="m 0,0 c 0,-7.5 -6.083,-13.58 -13.584,-13.58 -7.5,0 -13.582,6.08 -13.582,13.58 0,7.503 6.082,13.582 13.582,13.582 C -6.083,13.582 0,7.503 0,0" /> | d="m 0,0 c 0,-7.5 -6.083,-13.58 -13.584,-13.58 -7.5,0 -13.582,6.08 -13.582,13.58 0,7.503 6.082,13.582 13.582,13.582 C -6.083,13.582 0,7.503 0,0" /> | ||||
</g> | </g> | ||||
<circle | <circle | ||||
style="opacity:1;vector-effect:none;fill:#676967;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.26383328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |||||
style="opacity:1;vector-effect:none;fill:#676967;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.89770651;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |||||
id="circle12436" | id="circle12436" | ||||
cx="288.35214" | cx="288.35214" | ||||
cy="-1190.6586" | |||||
r="11.277138" | |||||
cy="-1190.8564" | |||||
r="14.434736" | |||||
transform="scale(1,-1)" /> | transform="scale(1,-1)" /> | ||||
<circle | <circle | ||||
transform="scale(1,-1)" | transform="scale(1,-1)" | ||||
r="11.277138" | |||||
r="14.434736" | |||||
cy="-1189.952" | cy="-1189.952" | ||||
cx="288.35214" | cx="288.35214" | ||||
id="path12411" | id="path12411" | ||||
style="opacity:1;vector-effect:none;fill:#2b2d2b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.26383328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |||||
style="opacity:1;vector-effect:none;fill:#2b2d2b;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.89770651;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |||||
</g> | </g> | ||||
</g> | </g> | ||||
</svg> | </svg> |
@@ -9,12 +9,12 @@ | |||||
xmlns="http://www.w3.org/2000/svg" | xmlns="http://www.w3.org/2000/svg" | ||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
width="15.999999" | |||||
height="15.997643" | |||||
viewBox="0 0 4.2333331 4.2327099" | |||||
width="5.4186664mm" | |||||
height="5.4178686mm" | |||||
viewBox="0 0 5.4186663 5.4178686" | |||||
version="1.1" | version="1.1" | ||||
id="svg12484" | id="svg12484" | ||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||||
inkscape:version="0.92.2 2405546, 2018-03-11" | |||||
sodipodi:docname="TL1105_1.svg"> | sodipodi:docname="TL1105_1.svg"> | ||||
<defs | <defs | ||||
id="defs12478" /> | id="defs12478" /> | ||||
@@ -25,9 +25,9 @@ | |||||
borderopacity="1.0" | borderopacity="1.0" | ||||
inkscape:pageopacity="0.0" | inkscape:pageopacity="0.0" | ||||
inkscape:pageshadow="2" | inkscape:pageshadow="2" | ||||
inkscape:zoom="44.8" | |||||
inkscape:cx="5.4698259" | |||||
inkscape:cy="8.0360204" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="5.770688" | |||||
inkscape:cy="6.8891413" | |||||
inkscape:document-units="mm" | inkscape:document-units="mm" | ||||
inkscape:current-layer="g12476" | inkscape:current-layer="g12476" | ||||
showgrid="false" | showgrid="false" | ||||
@@ -35,12 +35,12 @@ | |||||
fit-margin-left="0" | fit-margin-left="0" | ||||
fit-margin-right="0" | fit-margin-right="0" | ||||
fit-margin-bottom="0" | fit-margin-bottom="0" | ||||
inkscape:window-width="2560" | |||||
inkscape:window-height="1422" | |||||
inkscape:window-width="1600" | |||||
inkscape:window-height="882" | |||||
inkscape:window-x="0" | inkscape:window-x="0" | ||||
inkscape:window-y="18" | inkscape:window-y="18" | ||||
inkscape:window-maximized="0" | inkscape:window-maximized="0" | ||||
units="px" /> | |||||
units="mm" /> | |||||
<metadata | <metadata | ||||
id="metadata12481"> | id="metadata12481"> | ||||
<rdf:RDF> | <rdf:RDF> | ||||
@@ -57,14 +57,14 @@ | |||||
inkscape:label="Layer 1" | inkscape:label="Layer 1" | ||||
inkscape:groupmode="layer" | inkscape:groupmode="layer" | ||||
id="layer1" | id="layer1" | ||||
transform="translate(-155.97001,-114.87325)"> | |||||
transform="translate(-155.37735,-114.28067)"> | |||||
<g | <g | ||||
id="g12476" | id="g12476" | ||||
transform="matrix(0.15583204,0,0,-0.15583204,113.15218,302.42225)" | transform="matrix(0.15583204,0,0,-0.15583204,113.15218,302.42225)" | ||||
style="stroke-width:2.26383328"> | style="stroke-width:2.26383328"> | ||||
<g | <g | ||||
id="g12468" | id="g12468" | ||||
transform="translate(301.93513,1189.951)" | |||||
transform="matrix(1.28,0,0,1.28,305.73837,1189.9507)" | |||||
style="stroke-width:2.26383328"> | style="stroke-width:2.26383328"> | ||||
<path | <path | ||||
d="m 0,0 c 0,-7.5 -6.083,-13.58 -13.584,-13.58 -7.5,0 -13.582,6.08 -13.582,13.58 0,7.503 6.082,13.582 13.582,13.582 C -6.083,13.582 0,7.503 0,0" | d="m 0,0 c 0,-7.5 -6.083,-13.58 -13.584,-13.58 -7.5,0 -13.582,6.08 -13.582,13.58 0,7.503 6.082,13.582 13.582,13.582 C -6.083,13.582 0,7.503 0,0" | ||||
@@ -74,17 +74,17 @@ | |||||
</g> | </g> | ||||
<circle | <circle | ||||
transform="scale(1,-1)" | transform="scale(1,-1)" | ||||
r="11.277138" | |||||
cy="-1189.2383" | |||||
r="14.434736" | |||||
cy="-1189.0385" | |||||
cx="288.35214" | cx="288.35214" | ||||
id="circle12472" | id="circle12472" | ||||
style="opacity:1;vector-effect:none;fill:#676967;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.26383328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |||||
style="opacity:1;vector-effect:none;fill:#676967;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.89770651;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |||||
<circle | <circle | ||||
style="opacity:1;vector-effect:none;fill:#141514;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.26383328;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |||||
style="opacity:1;vector-effect:none;fill:#141514;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.89770651;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |||||
id="circle12474" | id="circle12474" | ||||
cx="288.35214" | cx="288.35214" | ||||
cy="-1189.952" | cy="-1189.952" | ||||
r="11.277138" | |||||
r="14.434736" | |||||
transform="scale(1,-1)" /> | transform="scale(1,-1)" /> | ||||
</g> | </g> | ||||
</g> | </g> | ||||
@@ -6,7 +6,7 @@ | |||||
#include <condition_variable> | #include <condition_variable> | ||||
#include "Core.hpp" | #include "Core.hpp" | ||||
#include "audio.hpp" | #include "audio.hpp" | ||||
#include "dsp/samplerate.hpp" | |||||
#include "dsp/resampler.hpp" | |||||
#include "dsp/ringbuffer.hpp" | #include "dsp/ringbuffer.hpp" | ||||
@@ -295,7 +295,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
nvgScissor(vg, 0, 0, box.size.x, box.size.y); | nvgScissor(vg, 0, 0, box.size.x, box.size.y); | ||||
Widget::draw(vg); | Widget::draw(vg); | ||||
// CPU meter | |||||
// Power meter | |||||
if (module && gPowerMeter) { | if (module && gPowerMeter) { | ||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, | nvgRect(vg, | ||||
@@ -23,73 +23,86 @@ | |||||
namespace rack { | namespace rack { | ||||
static std::string globalDir; | |||||
static std::string localDir; | |||||
std::string assetGlobalDir; | |||||
std::string assetLocalDir; | |||||
void assetInit(bool devMode) { | void assetInit(bool devMode) { | ||||
if (devMode) { | |||||
// Use current working directory if running in development mode | |||||
globalDir = "."; | |||||
localDir = "."; | |||||
return; | |||||
} | |||||
if (assetGlobalDir.empty()) { | |||||
if (devMode) { | |||||
assetGlobalDir = "."; | |||||
} | |||||
else { | |||||
#if ARCH_MAC | #if ARCH_MAC | ||||
CFBundleRef bundle = CFBundleGetMainBundle(); | |||||
assert(bundle); | |||||
CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(bundle); | |||||
char resourcesBuf[PATH_MAX]; | |||||
Boolean success = CFURLGetFileSystemRepresentation(resourcesUrl, TRUE, (UInt8*) resourcesBuf, sizeof(resourcesBuf)); | |||||
assert(success); | |||||
CFRelease(resourcesUrl); | |||||
globalDir = resourcesBuf; | |||||
// Get home directory | |||||
struct passwd *pw = getpwuid(getuid()); | |||||
assert(pw); | |||||
localDir = pw->pw_dir; | |||||
localDir += "/Documents/Rack"; | |||||
CFBundleRef bundle = CFBundleGetMainBundle(); | |||||
assert(bundle); | |||||
CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(bundle); | |||||
char resourcesBuf[PATH_MAX]; | |||||
Boolean success = CFURLGetFileSystemRepresentation(resourcesUrl, TRUE, (UInt8*) resourcesBuf, sizeof(resourcesBuf)); | |||||
assert(success); | |||||
CFRelease(resourcesUrl); | |||||
assetGlobalDir = resourcesBuf; | |||||
#endif | #endif | ||||
#if ARCH_WIN | #if ARCH_WIN | ||||
char moduleBuf[MAX_PATH]; | |||||
DWORD length = GetModuleFileName(NULL, moduleBuf, sizeof(moduleBuf)); | |||||
assert(length > 0); | |||||
PathRemoveFileSpec(moduleBuf); | |||||
globalDir = moduleBuf; | |||||
// Get "My Documents" folder | |||||
char documentsBuf[MAX_PATH]; | |||||
HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documentsBuf); | |||||
assert(result == S_OK); | |||||
localDir = documentsBuf; | |||||
localDir += "/Rack"; | |||||
char moduleBuf[MAX_PATH]; | |||||
DWORD length = GetModuleFileName(NULL, moduleBuf, sizeof(moduleBuf)); | |||||
assert(length > 0); | |||||
PathRemoveFileSpec(moduleBuf); | |||||
assetGlobalDir = moduleBuf; | |||||
#endif | #endif | ||||
#if ARCH_LIN | #if ARCH_LIN | ||||
// TODO For now, users should launch Rack from their terminal in the global directory | |||||
globalDir = "."; | |||||
// Get home directory | |||||
const char *homeBuf = getenv("HOME"); | |||||
if (!homeBuf) { | |||||
struct passwd *pw = getpwuid(getuid()); | |||||
assert(pw); | |||||
homeBuf = pw->pw_dir; | |||||
// TODO For now, users should launch Rack from their terminal in the global directory | |||||
assetGlobalDir = "."; | |||||
#endif | |||||
} | |||||
} | } | ||||
localDir = homeBuf; | |||||
localDir += "/.Rack"; | |||||
if (assetLocalDir.empty()) { | |||||
if (devMode) { | |||||
assetLocalDir = "."; | |||||
} | |||||
else { | |||||
#if ARCH_WIN | |||||
// Get "My Documents" folder | |||||
char documentsBuf[MAX_PATH]; | |||||
HRESULT result = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS, NULL, SHGFP_TYPE_CURRENT, documentsBuf); | |||||
assert(result == S_OK); | |||||
assetLocalDir = documentsBuf; | |||||
assetLocalDir += "/Rack"; | |||||
#endif | #endif | ||||
systemCreateDirectory(localDir); | |||||
#if ARCH_MAC | |||||
// Get home directory | |||||
struct passwd *pw = getpwuid(getuid()); | |||||
assert(pw); | |||||
assetLocalDir = pw->pw_dir; | |||||
assetLocalDir += "/Documents/Rack"; | |||||
#endif | |||||
#if ARCH_LIN | |||||
// Get home directory | |||||
const char *homeBuf = getenv("HOME"); | |||||
if (!homeBuf) { | |||||
struct passwd *pw = getpwuid(getuid()); | |||||
assert(pw); | |||||
homeBuf = pw->pw_dir; | |||||
} | |||||
assetLocalDir = homeBuf; | |||||
assetLocalDir += "/.Rack"; | |||||
#endif | |||||
} | |||||
} | |||||
systemCreateDirectory(assetGlobalDir); | |||||
systemCreateDirectory(assetLocalDir); | |||||
} | } | ||||
std::string assetGlobal(std::string filename) { | std::string assetGlobal(std::string filename) { | ||||
return globalDir + "/" + filename; | |||||
return assetGlobalDir + "/" + filename; | |||||
} | } | ||||
std::string assetLocal(std::string filename) { | std::string assetLocal(std::string filename) { | ||||
return localDir + "/" + filename; | |||||
return assetLocalDir + "/" + filename; | |||||
} | } | ||||
@@ -26,11 +26,17 @@ int main(int argc, char* argv[]) { | |||||
// Parse command line arguments | // Parse command line arguments | ||||
int c; | int c; | ||||
opterr = 0; | opterr = 0; | ||||
while ((c = getopt(argc, argv, "d")) != -1) { | |||||
while ((c = getopt(argc, argv, "dg:l:")) != -1) { | |||||
switch (c) { | switch (c) { | ||||
case 'd': { | case 'd': { | ||||
devMode = true; | devMode = true; | ||||
} break; | } break; | ||||
case 'g': { | |||||
assetGlobalDir = optarg; | |||||
} break; | |||||
case 'l': { | |||||
assetLocalDir = optarg; | |||||
} break; | |||||
default: break; | default: break; | ||||
} | } | ||||
} | } | ||||