@@ -17,5 +17,6 @@ | |||||
*.map | *.map | ||||
*.o | *.o | ||||
__ATTIC | __ATTIC | ||||
__INCOMING | |||||
@@ -71,7 +71,9 @@ The binary distribution contains the following dynamically loaded add-on modules | |||||
- Template_shared.MyModule | - Template_shared.MyModule | ||||
The following (462) add-on modules are statically linked with the VST plugin: | |||||
The following (478) add-on modules are statically linked with the VST plugin: | |||||
- 21kHz.D_Inf | |||||
- 21kHz.PalmLoop | |||||
- Alikins.IdleSwitch | - Alikins.IdleSwitch | ||||
- Alikins.MomentaryOnButtons | - Alikins.MomentaryOnButtons | ||||
- Alikins.BigMuteButton | - Alikins.BigMuteButton | ||||
@@ -321,6 +323,18 @@ The following (462) add-on modules are statically linked with the VST plugin: | |||||
- HetrickCV.Waveshape | - HetrickCV.Waveshape | ||||
- huaba.EQ3 | - huaba.EQ3 | ||||
- huaba.ABBus | - huaba.ABBus | ||||
- ImpromptuModular.Tact | |||||
- ImpromptuModular.TwelveKey | |||||
- ImpromptuModular.Clocked | |||||
- ImpromptuModular.MidiFile | |||||
- ImpromptuModular.PhraseSeq16 | |||||
- ImpromptuModular.PhraseSeq32 | |||||
- ImpromptuModular.GateSeq64 | |||||
- ImpromptuModular.WriteSeq32 | |||||
- ImpromptuModular.WriteSeq64 | |||||
- ImpromptuModular.BigButtonSeq | |||||
- ImpromptuModular.SemiModularSynth | |||||
- ImpromptuModular.BlankPanel | |||||
- JW_Modules.Cat | - JW_Modules.Cat | ||||
- JW_Modules.BouncyBalls | - JW_Modules.BouncyBalls | ||||
- JW_Modules.FullScope | - JW_Modules.FullScope | ||||
@@ -345,6 +359,8 @@ The following (462) add-on modules are statically linked with the VST plugin: | |||||
- LindenbergResearch.ReShaper | - LindenbergResearch.ReShaper | ||||
- LindenbergResearch.BlankPanel | - LindenbergResearch.BlankPanel | ||||
- LindenbergResearch.BlankPanelM1 | - LindenbergResearch.BlankPanelM1 | ||||
- LindenbergResearch.VCO | |||||
- LindenbergResearch.Westcoast (preview) | |||||
- LOGinstruments.constant | - LOGinstruments.constant | ||||
- LOGinstruments.constant2 | - LOGinstruments.constant2 | ||||
- LOGinstruments.Speck | - LOGinstruments.Speck | ||||
@@ -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,15 +6,21 @@ | |||||
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 | |||||
enum State { | enum State { | ||||
UNKNOWN, | UNKNOWN, | ||||
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 +54,50 @@ struct SchmittTrigger { | |||||
bool isHigh() { | bool isHigh() { | ||||
return state == HIGH; | return state == HIGH; | ||||
} | } | ||||
}; | |||||
struct BooleanTrigger { | |||||
bool lastState; | |||||
BooleanTrigger() { | |||||
reset(); | |||||
} | |||||
void reset() { | void reset() { | ||||
state = UNKNOWN; | |||||
lastState = true; | |||||
} | |||||
bool process(bool state) { | |||||
bool triggered = (state && !lastState); | |||||
lastState = state; | |||||
return triggered; | |||||
} | } | ||||
}; | }; | ||||
/** 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); | |||||
} | } | ||||
} | } | ||||
@@ -53,18 +55,21 @@ struct RealTimeConvolver { | |||||
RealTimeConvolver(size_t blockSize) { | RealTimeConvolver(size_t blockSize) { | ||||
this->blockSize = blockSize; | this->blockSize = blockSize; | ||||
pffft = pffft_new_setup(blockSize*2, PFFFT_REAL); | pffft = pffft_new_setup(blockSize*2, PFFFT_REAL); | ||||
outputTail = new float[blockSize](); | |||||
tmpBlock = new float[blockSize*2](); | |||||
outputTail = new float[blockSize]; | |||||
memset(outputTail, 0, blockSize * sizeof(float)); | |||||
tmpBlock = new float[blockSize*2]; | |||||
memset(tmpBlock, 0, blockSize*2 * sizeof(float)); | |||||
} | } | ||||
~RealTimeConvolver() { | ~RealTimeConvolver() { | ||||
clear(); | |||||
setKernel(NULL, 0); | |||||
delete[] outputTail; | delete[] outputTail; | ||||
delete[] tmpBlock; | delete[] tmpBlock; | ||||
pffft_destroy_setup(pffft); | pffft_destroy_setup(pffft); | ||||
} | } | ||||
void clear() { | |||||
void setKernel(const float *kernel, size_t length) { | |||||
// Clear existing kernel | |||||
if (kernelFfts) { | if (kernelFfts) { | ||||
pffft_aligned_free(kernelFfts); | pffft_aligned_free(kernelFfts); | ||||
kernelFfts = NULL; | kernelFfts = NULL; | ||||
@@ -75,29 +80,24 @@ struct RealTimeConvolver { | |||||
} | } | ||||
kernelBlocks = 0; | kernelBlocks = 0; | ||||
inputPos = 0; | inputPos = 0; | ||||
} | |||||
void setKernel(const float *kernel, size_t length) { | |||||
clear(); | |||||
assert(kernel); | |||||
assert(length > 0); | |||||
// Round up to the nearest factor of `blockSize` | |||||
kernelBlocks = (length - 1) / blockSize + 1; | |||||
// Allocate blocks | |||||
kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks); | |||||
inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks); | |||||
memset(inputFfts, 0, sizeof(float) * blockSize*2 * kernelBlocks); | |||||
for (size_t i = 0; i < kernelBlocks; i++) { | |||||
// Pad each block with zeros | |||||
memset(tmpBlock, 0, sizeof(float) * blockSize*2); | |||||
size_t len = min((int) blockSize, (int) (length - i*blockSize)); | |||||
memcpy(tmpBlock, &kernel[i*blockSize], sizeof(float)*len); | |||||
// Compute fft | |||||
pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize*2 * i], NULL, PFFFT_FORWARD); | |||||
if (kernel && length > 0) { | |||||
// Round up to the nearest factor of `blockSize` | |||||
kernelBlocks = (length - 1) / blockSize + 1; | |||||
// Allocate blocks | |||||
kernelFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks); | |||||
inputFfts = (float*) pffft_aligned_malloc(sizeof(float) * blockSize*2 * kernelBlocks); | |||||
memset(inputFfts, 0, sizeof(float) * blockSize*2 * kernelBlocks); | |||||
for (size_t i = 0; i < kernelBlocks; i++) { | |||||
// Pad each block with zeros | |||||
memset(tmpBlock, 0, sizeof(float) * blockSize*2); | |||||
size_t len = min((int) blockSize, (int) (length - i*blockSize)); | |||||
memcpy(tmpBlock, &kernel[i*blockSize], sizeof(float)*len); | |||||
// Compute fft | |||||
pffft_transform(pffft, tmpBlock, &kernelFfts[blockSize*2 * i], NULL, PFFFT_FORWARD); | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -123,7 +123,7 @@ struct RealTimeConvolver { | |||||
// Note: This is the CPU bottleneck loop | // Note: This is the CPU bottleneck loop | ||||
for (size_t i = 0; i < kernelBlocks; i++) { | for (size_t i = 0; i < kernelBlocks; i++) { | ||||
size_t pos = (inputPos - i + kernelBlocks) % kernelBlocks; | size_t pos = (inputPos - i + kernelBlocks) % kernelBlocks; | ||||
pffft_zconvolve_accumulate(pffft, &kernelFfts[blockSize*2 * i], &inputFfts[blockSize*2 * pos], tmpBlock, 1.0); | |||||
pffft_zconvolve_accumulate(pffft, &kernelFfts[blockSize*2 * i], &inputFfts[blockSize*2 * pos], tmpBlock, 1.f); | |||||
} | } | ||||
// Compute output | // Compute output | ||||
pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD); | pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD); | ||||
@@ -132,9 +132,10 @@ struct RealTimeConvolver { | |||||
tmpBlock[i] += outputTail[i]; | tmpBlock[i] += outputTail[i]; | ||||
} | } | ||||
// Copy output block to output | // Copy output block to output | ||||
float scale = 1.f / (blockSize*2); | |||||
for (size_t i = 0; i < blockSize; i++) { | for (size_t i = 0; i < blockSize; i++) { | ||||
// Scale based on FFT | // Scale based on FFT | ||||
output[i] = tmpBlock[i] / blockSize; | |||||
output[i] = tmpBlock[i] * scale; | |||||
} | } | ||||
// Set tail | // Set tail | ||||
for (size_t i = 0; i < blockSize; i++) { | for (size_t i = 0; i < blockSize; i++) { | ||||
@@ -2,47 +2,94 @@ | |||||
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) { | |||||
float k[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]); | |||||
} | |||||
*/ | |||||
// free'd when fxn returns | |||||
#ifdef _MSC_VER | |||||
#define Dstack_farr(t, n, l) t *n = (t*)alloca(sizeof(t) * l) | |||||
#else | |||||
#define Dstack_farr(t, n, l) t n[l] | |||||
#endif | |||||
/** 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) { | |||||
Dstack_farr(float, k, len); | |||||
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) { | |||||
Dstack_farr(float, k1, len); | |||||
Dstack_farr(float, k2, len); | |||||
Dstack_farr(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); | |||||
f(x, y, k); | |||||
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) { | |||||
float k1[len]; | |||||
float k2[len]; | |||||
float k3[len]; | |||||
float k4[len]; | |||||
float yi[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) { | |||||
Dstack_farr(float, k1, len); | |||||
Dstack_farr(float, k2, len); | |||||
Dstack_farr(float, k3, len); | |||||
Dstack_farr(float, k4, len); | |||||
Dstack_farr(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,176 @@ | |||||
#pragma once | |||||
#ifndef RACK_SKIP_RESAMPLER | |||||
#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 | |||||
#endif // RACK_SKIP_RESAMPLER |
@@ -1,4 +1,5 @@ | |||||
#pragma once | #pragma once | ||||
#ifndef RACK_SKIP_RINGBUFFER | |||||
#include <string.h> | #include <string.h> | ||||
#include "util/common.hpp" | #include "util/common.hpp" | ||||
@@ -198,3 +199,4 @@ struct AppleRingBuffer { | |||||
}; | }; | ||||
} // namespace rack | } // namespace rack | ||||
#endif // RACK_SKIP_RINGBUFFER |
@@ -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" |
@@ -7,6 +7,7 @@ namespace rack { | |||||
extern bool gSkipAutosaveOnLaunch; | extern bool gSkipAutosaveOnLaunch; | ||||
extern bool b_touchkeyboard_enable; | |||||
void settingsSave(std::string filename); | void settingsSave(std::string filename); | ||||
void settingsLoad(std::string filename, bool bWindowSizeOnly); | void settingsLoad(std::string filename, bool bWindowSizeOnly); | ||||
@@ -17,7 +17,7 @@ else | |||||
EXTRALIBS= -LIBPATH:dep/lib/msvc/x86 | EXTRALIBS= -LIBPATH:dep/lib/msvc/x86 | ||||
endif | endif | ||||
EXTRALIBS+= jansson.lib glew.lib gdi32.lib opengl32.lib gdi32.lib user32.lib kernel32.lib Comdlg32.lib Shell32.lib ws2_32.lib winmm.lib | |||||
EXTRALIBS+= jansson.lib glew.lib gdi32.lib opengl32.lib gdi32.lib user32.lib kernel32.lib Comdlg32.lib Shell32.lib ws2_32.lib winmm.lib ole32.lib | |||||
#glfw.lib | #glfw.lib | ||||
PLAF_OBJ= | PLAF_OBJ= | ||||
@@ -0,0 +1,21 @@ | |||||
MIT License | |||||
Copyright (c) 2018 Jake F. Huryn | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
of this software and associated documentation files (the "Software"), to deal | |||||
in the Software without restriction, including without limitation the rights | |||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
copies of the Software, and to permit persons to whom the Software is | |||||
furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all | |||||
copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
SOFTWARE. |
@@ -0,0 +1,29 @@ | |||||
# If RACK_DIR is not defined when calling the Makefile, default to two directories above | |||||
RACK_DIR ?= ../.. | |||||
# Must follow the format in the Naming section of | |||||
# https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||||
SLUG = 21kHz | |||||
# Must follow the format in the Versioning section of | |||||
# https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||||
VERSION = 0.6.1 | |||||
# FLAGS will be passed to both the C and C++ compiler | |||||
FLAGS += | |||||
CFLAGS += | |||||
CXXFLAGS += | |||||
# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. | |||||
# Static libraries are fine. | |||||
LDFLAGS += | |||||
# Add .cpp and .c files to the build | |||||
SOURCES += $(wildcard src/*.cpp) | |||||
# Add files to the ZIP package when running `make dist` | |||||
# The compiled plugin is automatically added. | |||||
DISTRIBUTABLES += $(wildcard LICENSE*) res | |||||
# Include the VCV Rack plugin Makefile framework | |||||
include $(RACK_DIR)/plugin.mk |
@@ -0,0 +1,35 @@ | |||||
# 21kHz 0.6.1 (Coming soon) | |||||
A couple of modules I made for [VCV Rack](https://vcvrack.com/). More to come. The following is a list of and documentation for each module in the plugin. Also, I've linked to audio demos next to the section title of some modules. | |||||
<img src="docs/pl.png" alt="drawing" height="420px"/> | |||||
## Palm Loop ([Audio Demo](https://clyp.it/d5zatc4a)) | |||||
Palm Loop is a basic and CPU-friendly VCO, implementing through-zero FM and polyBLEP and polyBLAMP antialiasing. | |||||
The OCTAVE, COARSE, and FINE knobs change the oscillator frequency, which is C4 by default. The OCTAVE knob changes the frequency in octave increments (C0 to C8), the COARSE knob in half-step increments (-7 to +7), and the FINE knob within a continuous +/-1 half-step range. | |||||
The V/OCT input is the master pitch input. The EXP input is for exponential frequency modulation, and the LIN input is for through-zero linear frequency modulation, both having a dedicated attenuverter. The RESET input restarts each waveform output at the beginning of its cycle upon recieving a trigger. The reset is not antialiased. | |||||
There are five outputs. The top two are saw and sine, and the bottom three are square, triangle, and sine. The bottom three waveforms are pitched an octave lower. | |||||
**Tips** | |||||
- Since there's not much in the way of waveshaping, Palm Loop shines when doing FM, perhaps paired with a second. | |||||
- The LIN input is for the classic glassy FM harmonics; use the EXP input for harsh inharmonic timbres. | |||||
- If you have one modulating another, RESET both on the same trigger to keep the timbre consistent across pitch changes. | |||||
- Mix or scan the outputs for varied waveshapes. | |||||
<img src="docs/d.png" alt="drawing" height="420px"/> | |||||
## *D*<sub>∞</sub> | |||||
A basic module for modifying V/OCT signals by transposition and inversion. | |||||
The OCTAVE knob transposes the signal in octave increments (-4 to +4), and the COARSE knob transposes it in half step increments (-7 to +7). The 1/2 # button raises the transposed signal by a quarter step, so quartertone transpositions can be achieved. When the INV button is on, the incoming signal is inverted about 0V before being transposed. | |||||
The rest of the controls determine when the transposition and inversion are done. By default, if there is no input at the TRIG port, the transposition is always active and the inversion active if the INV button is on. With the GATE button off, a trigger at the TRIG input will toggle the transposition between on and off. With the GATE button on, the transposition will only activate with a signal of >= 5.0V at the TRIG input (generally meant for 0-10V unipolar inputs). Finally, activating the button below INV means that the input will only be inverted when transposition is active (and the INV button is on). Otherwise, the signal will always be inverted if the INV button is on. | |||||
**Tips** | |||||
- Swap between differently transposed sequences with a sequential switch for harmonic movement. | |||||
- Turn on INV and the button below it, and transpose so that the inverted signal is in the same key as the incoming signal. An input at TRIG will create some nice melodic variation, especially if it is offset from the main rhythm. |
@@ -0,0 +1,4 @@ | |||||
ALL_OBJ= \ | |||||
./src/21kHz.o \ | |||||
./src/D_Inf.o \ | |||||
./src/PalmLoop.o |
@@ -0,0 +1,7 @@ | |||||
SLUG=21kHz | |||||
include ../../../build_plugin_pre.mk | |||||
include make.objects | |||||
include ../../../build_plugin_post.mk |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 14 14" width="14" height="14"><defs><clipPath id="_clipPath_4U0jHmP0nxSrFEuH7FfgX9ZPTxB8v2K4"><rect width="14" height="14"/></clipPath></defs><g clip-path="url(#_clipPath_4U0jHmP0nxSrFEuH7FfgX9ZPTxB8v2K4)"><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(7,0,0,7,7,7)" fill="rgb(32,32,32)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(5,0,0,5,7,7)" fill="rgb(64,64,64)"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 14 14" width="14" height="14"><defs><clipPath id="_clipPath_NBU9ziaK6CiUFL7xaxWQRnT3KgV4BzGW"><rect width="14" height="14"/></clipPath></defs><g clip-path="url(#_clipPath_NBU9ziaK6CiUFL7xaxWQRnT3KgV4BzGW)"><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(7,0,0,7,7,7)" fill="rgb(32,32,32)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(5,0,0,5,7,7)" fill="rgb(240,224,116)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(4,0,0,4,7,7)" fill="rgb(240,227,142)"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 48 48" width="48" height="48"><defs><clipPath id="_clipPath_h51s0fQQyWtOKKf1doBQ6gLNILIiENU1"><rect width="48" height="48"/></clipPath></defs><g clip-path="url(#_clipPath_h51s0fQQyWtOKKf1doBQ6gLNILIiENU1)"><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(24,0,0,24,24,24)" fill="rgb(32,32,32)"/><rect x="21.75" y="0" width="4.5" height="7.5" transform="matrix(1,0,0,1,0,0)" fill="rgb(255,255,255)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(17.5,0,0,17.5,24,24)" fill="rgb(255,255,255)"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 32 32" width="32" height="32"><defs><clipPath id="_clipPath_ZtncNXkNbHBvBGLUR9vUV6ptK9YC4FhK"><rect width="32" height="32"/></clipPath></defs><g clip-path="url(#_clipPath_ZtncNXkNbHBvBGLUR9vUV6ptK9YC4FhK)"><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(16,0,0,16,16,16)" fill="rgb(24,24,24)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(11.65,0,0,11.65,16,16)" fill="rgb(240,240,240)"/><rect x="14.5" y="0" width="3" height="16" transform="matrix(1,0,0,1,0,0)" fill="rgb(240,240,240)"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 26 26" width="26" height="26"><defs><clipPath id="_clipPath_VqL0CwvMQZc9BRZIFihm9Daznnswgxit"><rect width="26" height="26"/></clipPath></defs><g clip-path="url(#_clipPath_VqL0CwvMQZc9BRZIFihm9Daznnswgxit)"><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(13,0,0,13,13,13)" fill="rgb(140,140,140)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(12.25,0,0,12.25,13,13)" fill="rgb(224,224,224)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(9.5,0,0,9.5,13,13)" fill="rgb(132,132,132)"/><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(8.75,0,0,8.75,13,13)" fill="rgb(232,232,232)"/><g style="isolation:isolate"><circle vector-effect="non-scaling-stroke" cx="0" cy="0" r="1" transform="matrix(5.5,0,0,5.5,13,13)" fill="rgb(24,24,24)"/></g></g></svg> |
@@ -0,0 +1 @@ | |||||
<?xml version="1.0" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 15 14.999" width="15" height="14.999"><defs><clipPath id="_clipPath_PYdx1Q1piRbRC9b75wBFftaRjjE68dMw"><rect width="15" height="14.999"/></clipPath></defs><g clip-path="url(#_clipPath_PYdx1Q1piRbRC9b75wBFftaRjjE68dMw)"><g><path d=" M 7.532 12.999 C 4.499 12.999 2.032 10.533 2.032 7.5 C 2.032 4.467 4.499 1.999 7.532 1.999 C 10.565 1.999 13.032 4.467 13.032 7.5 C 13.032 10.533 10.565 12.999 7.532 12.999 Z " fill="rgb(196,196,196)" vector-effect="non-scaling-stroke" stroke-width="0.7" stroke="rgb(140,140,140)" stroke-linejoin="miter" stroke-linecap="butt" stroke-miterlimit="4"/><path d=" M 8 11.387 L 7 11.387 L 7 3.612 L 8 3.612 L 8 11.387 Z " fill="rgb(132,132,132)"/><path d=" M 11.419 8 L 3.644 8 L 3.644 7 L 11.419 7 L 11.419 8 Z " fill="rgb(132,132,132)"/></g></g></svg> |
@@ -0,0 +1,14 @@ | |||||
#include "21kHz.hpp" | |||||
RACK_PLUGIN_MODEL_DECLARE(21kHz, PalmLoop); | |||||
RACK_PLUGIN_MODEL_DECLARE(21kHz, D_Inf); | |||||
RACK_PLUGIN_INIT(21kHz) { | |||||
RACK_PLUGIN_INIT_ID(); | |||||
RACK_PLUGIN_INIT_WEBSITE("https://github.com/21kHz/21kHz-rack-plugins"); | |||||
RACK_PLUGIN_INIT_MANUAL("https://github.com/21kHz/21kHz-rack-plugins"); | |||||
RACK_PLUGIN_MODEL_ADD(21kHz, PalmLoop); | |||||
RACK_PLUGIN_MODEL_ADD(21kHz, D_Inf); | |||||
} |
@@ -0,0 +1,64 @@ | |||||
#include "rack.hpp" | |||||
using namespace rack; | |||||
// Forward-declare the Plugin, defined in Template.cpp | |||||
#define plugin "21kHz" | |||||
//////////////////////////////// | |||||
// Knobs | |||||
struct kHzKnob : RoundKnob { | |||||
kHzKnob() { | |||||
setSVG(SVG::load(assetPlugin(plugin, "res/Components/kHzKnob.svg"))); | |||||
shadow->box.pos = Vec(0.0, 3.5); | |||||
} | |||||
}; | |||||
struct kHzKnobSmall : RoundKnob { | |||||
kHzKnobSmall() { | |||||
setSVG(SVG::load(assetPlugin(plugin, "res/Components/kHzKnobSmall.svg"))); | |||||
shadow->box.pos = Vec(0.0, 2.5); | |||||
} | |||||
}; | |||||
struct kHzKnobSnap : kHzKnob { | |||||
kHzKnobSnap() { | |||||
snap = true; | |||||
} | |||||
}; | |||||
struct kHzKnobSmallSnap : kHzKnobSmall { | |||||
kHzKnobSmallSnap() { | |||||
snap = true; | |||||
} | |||||
}; | |||||
// Buttons | |||||
struct kHzButton : SVGSwitch, ToggleSwitch { | |||||
kHzButton() { | |||||
addFrame(SVG::load(assetPlugin(plugin, "res/Components/kHzButton_0.svg"))); | |||||
addFrame(SVG::load(assetPlugin(plugin, "res/Components/kHzButton_1.svg"))); | |||||
} | |||||
}; | |||||
// Ports | |||||
struct kHzPort : SVGPort { | |||||
kHzPort() { | |||||
setSVG(SVG::load(assetPlugin(plugin, "res/Components/kHzPort.svg"))); | |||||
shadow->box.pos = Vec(0.0, 2.0); | |||||
} | |||||
}; | |||||
// Misc | |||||
struct kHzScrew : SVGScrew { | |||||
kHzScrew() { | |||||
sw->setSVG(SVG::load(assetPlugin(plugin, "res/Components/kHzScrew.svg"))); | |||||
} | |||||
}; |
@@ -0,0 +1,115 @@ | |||||
#include "21kHz.hpp" | |||||
#include "dsp/digital.hpp" | |||||
namespace rack_plugin_21kHz { | |||||
struct D_Inf : Module { | |||||
enum ParamIds { | |||||
OCTAVE_PARAM, | |||||
COARSE_PARAM, | |||||
HALF_SHARP_PARAM, | |||||
INVERT_PARAM, | |||||
GATE_PARAM, | |||||
INVERT_TRIG_PARAM, | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
TRIG_INPUT, | |||||
A_INPUT, | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
A_OUTPUT, | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
NUM_LIGHTS | |||||
}; | |||||
bool transpose = true; | |||||
SchmittTrigger transposeTrigger; | |||||
D_Inf() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||||
void step() override; | |||||
// For more advanced Module features, read Rack's engine.hpp header file | |||||
// - toJson, fromJson: serialization of internal data | |||||
// - onSampleRateChange: event triggered by a change of sample rate | |||||
// - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu | |||||
}; | |||||
void D_Inf::step() { | |||||
if (!inputs[TRIG_INPUT].active) { | |||||
transpose = true; | |||||
} | |||||
else { | |||||
if (params[GATE_PARAM].value == 0) { | |||||
if (transposeTrigger.process(inputs[TRIG_INPUT].value)) { | |||||
transpose = !transpose; | |||||
} | |||||
} | |||||
else { | |||||
if (inputs[TRIG_INPUT].value >= 5.0f) { | |||||
transpose = true; | |||||
} | |||||
else { | |||||
transpose = false; | |||||
} | |||||
} | |||||
} | |||||
float output = inputs[A_INPUT].value; | |||||
if (transpose) { | |||||
if (params[INVERT_PARAM].value == 1) { | |||||
output *= -1.0f; | |||||
} | |||||
output += params[OCTAVE_PARAM].value + 0.083333 * params[COARSE_PARAM].value + 0.041667 * params[HALF_SHARP_PARAM].value; | |||||
} | |||||
else { | |||||
if (params[INVERT_TRIG_PARAM].value == 0) { | |||||
if (params[INVERT_PARAM].value == 1) { | |||||
output *= -1.0f; | |||||
} | |||||
} | |||||
} | |||||
outputs[A_OUTPUT].value = output; | |||||
} | |||||
struct D_InfWidget : ModuleWidget { | |||||
D_InfWidget(D_Inf *module) : ModuleWidget(module) { | |||||
setPanel(SVG::load(assetPlugin(plugin, "res/Panels/D_Inf.svg"))); | |||||
addChild(Widget::create<kHzScrew>(Vec(RACK_GRID_WIDTH, 0))); | |||||
addChild(Widget::create<kHzScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addParam(ParamWidget::create<kHzKnobSmallSnap>(Vec(14, 40), module, D_Inf::OCTAVE_PARAM, -4, 4, 0)); | |||||
addParam(ParamWidget::create<kHzKnobSmallSnap>(Vec(14, 96), module, D_Inf::COARSE_PARAM, -7, 7, 0)); | |||||
addParam(ParamWidget::create<kHzButton>(Vec(10, 150), module, D_Inf::HALF_SHARP_PARAM, 0, 1, 0)); | |||||
addParam(ParamWidget::create<kHzButton>(Vec(36, 150), module, D_Inf::INVERT_PARAM, 0, 1, 0)); | |||||
addParam(ParamWidget::create<kHzButton>(Vec(10, 182), module, D_Inf::GATE_PARAM, 0, 1, 0)); | |||||
addParam(ParamWidget::create<kHzButton>(Vec(36, 182), module, D_Inf::INVERT_TRIG_PARAM, 0, 1, 0)); | |||||
addInput(Port::create<kHzPort>(Vec(17, 234), Port::INPUT, module, D_Inf::TRIG_INPUT)); | |||||
addInput(Port::create<kHzPort>(Vec(17, 276), Port::INPUT, module, D_Inf::A_INPUT)); | |||||
addOutput(Port::create<kHzPort>(Vec(17, 318), Port::OUTPUT, module, D_Inf::A_OUTPUT)); | |||||
} | |||||
}; | |||||
} // namespace rack_plugin_21kHz | |||||
using namespace rack_plugin_21kHz; | |||||
RACK_PLUGIN_MODEL_INIT(21kHz, D_Inf) { | |||||
Model *modelD_Inf = Model::create<D_Inf, D_InfWidget>("21kHz", "kHzD_Inf", "D∞ — pitch tools — 4hp", TUNER_TAG, UTILITY_TAG); | |||||
return modelD_Inf; | |||||
} | |||||
// history | |||||
// 0.6.1 | |||||
// create |
@@ -0,0 +1,253 @@ | |||||
#include "21kHz.hpp" | |||||
#include "dsp/digital.hpp" | |||||
#include "dsp/math.hpp" | |||||
#include <array> | |||||
using std::array; | |||||
namespace rack_plugin_21kHz { | |||||
struct PalmLoop : Module { | |||||
enum ParamIds { | |||||
OCT_PARAM, | |||||
COARSE_PARAM, | |||||
FINE_PARAM, | |||||
EXP_FM_PARAM, | |||||
LIN_FM_PARAM, | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
RESET_INPUT, | |||||
V_OCT_INPUT, | |||||
EXP_FM_INPUT, | |||||
LIN_FM_INPUT, | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
SAW_OUTPUT, | |||||
SQR_OUTPUT, | |||||
TRI_OUTPUT, | |||||
SIN_OUTPUT, | |||||
SUB_OUTPUT, | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
NUM_LIGHTS | |||||
}; | |||||
float phase = 0.0f; | |||||
float oldPhase = 0.0f; | |||||
float square = 1.0f; | |||||
int discont = 0; | |||||
int oldDiscont = 0; | |||||
array<float, 4> sawBuffer; | |||||
array<float, 4> sqrBuffer; | |||||
array<float, 4> triBuffer; | |||||
float log2sampleFreq = 15.4284f; | |||||
SchmittTrigger resetTrigger; | |||||
PalmLoop() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} | |||||
void step() override; | |||||
void onSampleRateChange() override; | |||||
// For more advanced Module features, read Rack's engine.hpp header file | |||||
// - toJson, fromJson: serialization of internal data | |||||
// - onSampleRateChange: event triggered by a change of sample rate | |||||
// - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu | |||||
}; | |||||
void PalmLoop::onSampleRateChange() { | |||||
log2sampleFreq = log2f(1 / engineGetSampleTime()) - 0.00009f; | |||||
} | |||||
// quick explanation: the whole thing is driven by a naive sawtooth, which writes to a four-sample buffer for each | |||||
// (non-sine) waveform. the waves are calculated such that their discontinuities (or in the case of triangle, derivative | |||||
// discontinuities) only occur each time the phasor exceeds a [0, 1) range. when we calculate the outputs, we look to see | |||||
// if a discontinuity occured in the previous sample. if one did, we calculate the polyblep or polyblamp and add it to | |||||
// each sample in the buffer. the output is the oldest buffer sample, which gets overwritten in the following step. | |||||
void PalmLoop::step() { | |||||
if (resetTrigger.process(inputs[RESET_INPUT].value)) { | |||||
phase = 0.0f; | |||||
} | |||||
for (int i = 0; i <= 2; ++i) { | |||||
sawBuffer[i] = sawBuffer[i + 1]; | |||||
sqrBuffer[i] = sqrBuffer[i + 1]; | |||||
triBuffer[i] = triBuffer[i + 1]; | |||||
} | |||||
float freq = params[OCT_PARAM].value + 0.031360 + 0.083333 * params[COARSE_PARAM].value + params[FINE_PARAM].value + inputs[V_OCT_INPUT].value; | |||||
if (inputs[EXP_FM_INPUT].active) { | |||||
freq += params[EXP_FM_PARAM].value * inputs[EXP_FM_INPUT].value; | |||||
if (freq >= log2sampleFreq) { | |||||
freq = log2sampleFreq; | |||||
} | |||||
freq = powf(2.0f, freq); | |||||
} | |||||
else { | |||||
if (freq >= log2sampleFreq) { | |||||
freq = log2sampleFreq; | |||||
} | |||||
freq = powf(2.0f, freq); | |||||
} | |||||
float incr = 0.0f; | |||||
if (inputs[LIN_FM_INPUT].active) { | |||||
freq += params[LIN_FM_PARAM].value * params[LIN_FM_PARAM].value * inputs[LIN_FM_INPUT].value; | |||||
incr = engineGetSampleTime() * freq; | |||||
if (incr > 1.0f) { | |||||
incr = 1.0f; | |||||
} | |||||
else if (incr < -1.0f) { | |||||
incr = -1.0f; | |||||
} | |||||
} | |||||
else { | |||||
incr = engineGetSampleTime() * freq; | |||||
} | |||||
phase += incr; | |||||
if (phase >= 0.0f && phase < 1.0f) { | |||||
discont = 0; | |||||
} | |||||
else if (phase >= 1.0f) { | |||||
discont = 1; | |||||
--phase; | |||||
square *= -1.0f; | |||||
} | |||||
else { | |||||
discont = -1; | |||||
++phase; | |||||
square *= -1.0f; | |||||
} | |||||
sawBuffer[3] = phase; | |||||
sqrBuffer[3] = square; | |||||
if (square >= 0.0f) { | |||||
triBuffer[3] = phase; | |||||
} | |||||
else { | |||||
triBuffer[3] = 1.0f - phase; | |||||
} | |||||
if (outputs[SAW_OUTPUT].active) { | |||||
if (oldDiscont == 1) { | |||||
polyblep4(sawBuffer, 1.0f - oldPhase / incr, 1.0f); | |||||
} | |||||
else if (oldDiscont == -1) { | |||||
polyblep4(sawBuffer, 1.0f - (oldPhase - 1.0f) / incr, -1.0f); | |||||
} | |||||
outputs[SAW_OUTPUT].value = clampf(10.0f * (sawBuffer[0] - 0.5f), -5.0f, 5.0f); | |||||
} | |||||
if (outputs[SQR_OUTPUT].active) { | |||||
// for some reason i don't understand, if discontinuities happen in two | |||||
// adjacent samples, the first one must be inverted. otherwise the polyblep | |||||
// is bad and causes aliasing. don't ask me how i managed to figure this out. | |||||
if (discont == 0) { | |||||
if (oldDiscont == 1) { | |||||
polyblep4(sqrBuffer, 1.0f - oldPhase / incr, -2.0f * square); | |||||
} | |||||
else if (oldDiscont == -1) { | |||||
polyblep4(sqrBuffer, 1.0f - (oldPhase - 1.0f) / incr, -2.0f * square); | |||||
} | |||||
} | |||||
else { | |||||
if (oldDiscont == 1) { | |||||
polyblep4(sqrBuffer, 1.0f - oldPhase / incr, 2.0f * square); | |||||
} | |||||
else if (oldDiscont == -1) { | |||||
polyblep4(sqrBuffer, 1.0f - (oldPhase - 1.0f) / incr, 2.0f * square); | |||||
} | |||||
} | |||||
outputs[SQR_OUTPUT].value = clampf(4.9999f * sqrBuffer[0], -5.0f, 5.0f); | |||||
} | |||||
if (outputs[TRI_OUTPUT].active) { | |||||
if (discont == 0) { | |||||
if (oldDiscont == 1) { | |||||
polyblamp4(triBuffer, 1.0f - oldPhase / incr, 2.0f * square * incr); | |||||
} | |||||
else if (oldDiscont == -1) { | |||||
polyblamp4(triBuffer, 1.0f - (oldPhase - 1.0f) / incr, 2.0f * square * incr); | |||||
} | |||||
} | |||||
else { | |||||
if (oldDiscont == 1) { | |||||
polyblamp4(triBuffer, 1.0f - oldPhase / incr, -2.0f * square * incr); | |||||
} | |||||
else if (oldDiscont == -1) { | |||||
polyblamp4(triBuffer, 1.0f - (oldPhase - 1.0f) / incr, -2.0f * square * incr); | |||||
} | |||||
} | |||||
outputs[TRI_OUTPUT].value = clampf(10.0f * (triBuffer[0] - 0.5f), -5.0f, 5.0f); | |||||
} | |||||
if (outputs[SIN_OUTPUT].active) { | |||||
outputs[SIN_OUTPUT].value = 5.0f * sin_01(phase); | |||||
} | |||||
if (outputs[SUB_OUTPUT].active) { | |||||
if (square >= 0.0f) { | |||||
outputs[SUB_OUTPUT].value = 5.0f * sin_01(0.5f * phase); | |||||
} | |||||
else { | |||||
outputs[SUB_OUTPUT].value = 5.0f * sin_01(0.5f * (1.0f - phase)); | |||||
} | |||||
} | |||||
oldPhase = phase; | |||||
oldDiscont = discont; | |||||
} | |||||
struct PalmLoopWidget : ModuleWidget { | |||||
PalmLoopWidget(PalmLoop *module) : ModuleWidget(module) { | |||||
setPanel(SVG::load(assetPlugin(plugin, "res/Panels/PalmLoop.svg"))); | |||||
addChild(Widget::create<kHzScrew>(Vec(RACK_GRID_WIDTH, 0))); | |||||
addChild(Widget::create<kHzScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||||
addChild(Widget::create<kHzScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addChild(Widget::create<kHzScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||||
addParam(ParamWidget::create<kHzKnobSnap>(Vec(36, 40), module, PalmLoop::OCT_PARAM, 4, 12, 8)); | |||||
addParam(ParamWidget::create<kHzKnobSmallSnap>(Vec(16, 112), module, PalmLoop::COARSE_PARAM, -7, 7, 0)); | |||||
addParam(ParamWidget::create<kHzKnobSmall>(Vec(72, 112), module, PalmLoop::FINE_PARAM, -0.083333, 0.083333, 0.0)); | |||||
addParam(ParamWidget::create<kHzKnobSmall>(Vec(16, 168), module, PalmLoop::EXP_FM_PARAM, -1.0, 1.0, 0.0)); | |||||
addParam(ParamWidget::create<kHzKnobSmall>(Vec(72, 168), module, PalmLoop::LIN_FM_PARAM, -40.0, 40.0, 0.0)); | |||||
addInput(Port::create<kHzPort>(Vec(10, 234), Port::INPUT, module, PalmLoop::EXP_FM_INPUT)); | |||||
addInput(Port::create<kHzPort>(Vec(47, 234), Port::INPUT, module, PalmLoop::V_OCT_INPUT)); | |||||
addInput(Port::create<kHzPort>(Vec(84, 234), Port::INPUT, module, PalmLoop::LIN_FM_INPUT)); | |||||
addInput(Port::create<kHzPort>(Vec(10, 276), Port::INPUT, module, PalmLoop::RESET_INPUT)); | |||||
addOutput(Port::create<kHzPort>(Vec(47, 276), Port::OUTPUT, module, PalmLoop::SAW_OUTPUT)); | |||||
addOutput(Port::create<kHzPort>(Vec(84, 276), Port::OUTPUT, module, PalmLoop::SIN_OUTPUT)); | |||||
addOutput(Port::create<kHzPort>(Vec(10, 318), Port::OUTPUT, module, PalmLoop::SQR_OUTPUT)); | |||||
addOutput(Port::create<kHzPort>(Vec(47, 318), Port::OUTPUT, module, PalmLoop::TRI_OUTPUT)); | |||||
addOutput(Port::create<kHzPort>(Vec(84, 318), Port::OUTPUT, module, PalmLoop::SUB_OUTPUT)); | |||||
} | |||||
}; | |||||
} // namespace rack_plugin_21kHz | |||||
using namespace rack_plugin_21kHz; | |||||
RACK_PLUGIN_MODEL_INIT(21kHz, PalmLoop) { | |||||
Model *modelPalmLoop = Model::create<PalmLoop, PalmLoopWidget>("21kHz", "kHzPalmLoop", "Palm Loop — basic VCO — 8hp", OSCILLATOR_TAG); | |||||
return modelPalmLoop; | |||||
} | |||||
// history | |||||
// 0.6.0 | |||||
// create | |||||
// 0.6.1 | |||||
// minor optimizations | |||||
// coarse goes -7 to +7 | |||||
// waveform labels & rearrangement on panel |
@@ -0,0 +1,72 @@ | |||||
#include <array> | |||||
using std::array; | |||||
// four point, fourth-order b-spline polyblep, from: | |||||
// Välimäki, Pekonen, Nam. "Perceptually informed synthesis of bandlimited | |||||
// classical waveforms using integrated polynomial interpolation" | |||||
inline void polyblep4(array<float, 4> &buffer, float d, float u) { | |||||
if (d > 1.0f) { | |||||
d = 1.0f; | |||||
} | |||||
else if (d < 0.0f) { | |||||
d = 0.0f; | |||||
} | |||||
float d2 = d * d; | |||||
float d3 = d2 * d; | |||||
float d4 = d3 * d; | |||||
float dd3 = 0.16667 * (d + d3); | |||||
float cd2 = 0.041667 + 0.25 * d2; | |||||
float d4_1 = 0.041667 * d4; | |||||
buffer[3] += u * (d4_1); | |||||
buffer[2] += u * (cd2 + dd3 - 0.125 * d4); | |||||
buffer[1] += u * (-0.5 + 0.66667 * d - 0.33333 * d3 + 0.125 * d4); | |||||
buffer[0] += u * (-cd2 + dd3 - d4_1); | |||||
} | |||||
// four point, fourth-order b-spline polyblamp, from: | |||||
// Esqueda, Välimäki, Bilbao. "Rounding Corners with BLAMP". | |||||
inline void polyblamp4(array<float, 4> &buffer, float d, float u) { | |||||
if (d > 1.0f) { | |||||
d = 1.0f; | |||||
} | |||||
else if (d < 0.0f) { | |||||
d = 0.0f; | |||||
} | |||||
float d2 = d * d; | |||||
float d3 = d2 * d; | |||||
float d4 = d3 * d; | |||||
float d5 = d4 * d; | |||||
float d5_1 = 0.0083333 * d5; | |||||
float d5_2 = 0.025 * d5; | |||||
buffer[3] += u * (d5_1); | |||||
buffer[2] += u * (0.0083333 + 0.083333 * (d2 + d3) + 0.041667 * (d + d4) - d5_2); | |||||
buffer[1] += u * (0.23333 - 0.5 * d + 0.33333 * d2 - 0.083333 * d4 + d5_2); | |||||
buffer[0] += u * (0.0083333 + 0.041667 * (d4 - d) + 0.083333 * (d2 - d3) - d5_1); | |||||
} | |||||
// fast sine calculation. modified from the Reaktor 6 core library. | |||||
// takes a [0, 1] range and folds it to a triangle on a [0, 0.5] range. | |||||
inline float sin_01(float t) { | |||||
if (t > 1.0f) { | |||||
t = 1.0f; | |||||
} | |||||
else if (t > 0.5) { | |||||
t = 1.0f - t; | |||||
} | |||||
else if (t < 0.0f) { | |||||
t = 0.0f; | |||||
} | |||||
t = 2.0f * t - 0.5f; | |||||
float t2 = t * t; | |||||
t = (((-0.540347 * t2 + 2.53566) * t2 - 5.16651) * t2 + 3.14159) * t; | |||||
return t; | |||||
} |
@@ -29,7 +29,8 @@ void springReverbInit() { | |||||
} | } | ||||
} | } | ||||
namespace rack_plugin_Befaco { | |||||
// clashes with rack/include/dsp/fir.h | |||||
struct RealTimeConvolver { | struct RealTimeConvolver { | ||||
// `kernelBlocks` number of contiguous FFT blocks of size `blockSize` | // `kernelBlocks` number of contiguous FFT blocks of size `blockSize` | ||||
// indexed by [i * blockSize*2 + j] | // indexed by [i * blockSize*2 + j] | ||||
@@ -137,6 +138,7 @@ struct RealTimeConvolver { | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
} // namespace rack_plugin_Befaco | |||||
#define BLOCKSIZE 1024 | #define BLOCKSIZE 1024 | ||||
@@ -168,7 +170,7 @@ struct SpringReverb : Module { | |||||
NUM_LIGHTS = VU1_LIGHT + 7 | NUM_LIGHTS = VU1_LIGHT + 7 | ||||
}; | }; | ||||
RealTimeConvolver *convolver = NULL; | |||||
rack_plugin_Befaco::RealTimeConvolver *convolver = NULL; | |||||
SampleRateConverter<1> inputSrc; | SampleRateConverter<1> inputSrc; | ||||
SampleRateConverter<1> outputSrc; | SampleRateConverter<1> outputSrc; | ||||
DoubleRingBuffer<Frame<1>, 16*BLOCKSIZE> inputBuffer; | DoubleRingBuffer<Frame<1>, 16*BLOCKSIZE> inputBuffer; | ||||
@@ -185,7 +187,7 @@ struct SpringReverb : Module { | |||||
SpringReverb::SpringReverb() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | SpringReverb::SpringReverb() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | ||||
convolver = new RealTimeConvolver(BLOCKSIZE); | |||||
convolver = new rack_plugin_Befaco::RealTimeConvolver(BLOCKSIZE); | |||||
convolver->setKernel(springReverbIR, springReverbIRLen); | convolver->setKernel(springReverbIR, springReverbIRLen); | ||||
} | } | ||||
@@ -1,3 +1,5 @@ | |||||
#define RACK_SKIP_RINGBUFFER | |||||
// #define RACK_SKIP_RESAMPLER | |||||
#include "rack.hpp" | #include "rack.hpp" | ||||
using namespace rack; | using namespace rack; | ||||
@@ -1,7 +1,7 @@ | |||||
#include "FrozenWasteland.hpp" | #include "FrozenWasteland.hpp" | ||||
#include "dsp/samplerate.hpp" | #include "dsp/samplerate.hpp" | ||||
#include "dsp/digital.hpp" | #include "dsp/digital.hpp" | ||||
#include "ringbuffer.hpp" | |||||
#include "dsp-delay/ringbuffer.hpp" | |||||
#include <iostream> | #include <iostream> | ||||
#define HISTORY_SIZE (1<<22) | #define HISTORY_SIZE (1<<22) | ||||
@@ -102,10 +102,9 @@ struct HairPick : Module { | |||||
bool combActive[NUM_TAPS]; | bool combActive[NUM_TAPS]; | ||||
float combLevel[NUM_TAPS]; | float combLevel[NUM_TAPS]; | ||||
MultiTapDoubleRingBuffer<float, HISTORY_SIZE,NUM_TAPS> historyBuffer[CHANNELS]; | |||||
DoubleRingBuffer<float, 16> outBuffer[NUM_TAPS][CHANNELS]; | |||||
SampleRateConverter<1> src; | |||||
rack_plugin_FrozenWasteland::MultiTapDoubleRingBuffer<float, HISTORY_SIZE,NUM_TAPS> historyBuffer[CHANNELS]; | |||||
rack_plugin_FrozenWasteland::DoubleRingBuffer<float, 16> outBuffer[NUM_TAPS][CHANNELS]; | |||||
SampleRateConverter<1> src; | |||||
float lastFeedback[CHANNELS] = {0.0f,0.0f}; | float lastFeedback[CHANNELS] = {0.0f,0.0f}; | ||||
float lerp(float v0, float v1, float t) { | float lerp(float v0, float v1, float t) { | ||||
@@ -3,8 +3,7 @@ | |||||
#include <string.h> | #include <string.h> | ||||
#include "util/common.hpp" | #include "util/common.hpp" | ||||
namespace rack { | |||||
namespace rack_plugin_FrozenWasteland { | |||||
/** A simple cyclic buffer. | /** A simple cyclic buffer. | ||||
S must be a power of 2. | S must be a power of 2. | ||||
@@ -353,4 +352,4 @@ struct AppleRingBuffer { | |||||
} | } | ||||
}; | }; | ||||
} // namespace rack | |||||
} // namespace rack_plugin_FrozenWasteland |
@@ -0,0 +1,4 @@ | |||||
/build | |||||
/dist | |||||
plugin.* | |||||
/src/midifile/midifile |
@@ -0,0 +1,73 @@ | |||||
#### IMPROMPTU MODULAR #### | |||||
Copyright (c) 2018 Marc Boulé. All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
#### GRAYSCALE #### | |||||
Component Library graphics by Grayscale (http://grayscale.info/) | |||||
Licensed under CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/) | |||||
#### FUNDAMENTAL #### | |||||
Copyright (c) 2016 Andrew Belt | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
#### AUDIBLE INSTRUMENTS and VCV RACK #### | |||||
Copyright 2016 Andrew Belt | |||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
#### VALLEY RACK FREE #### | |||||
Copyright 2018 Dale Johnson | |||||
1. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||||
2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||||
3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||||
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||||
### MIDIFILE #### | |||||
Copyright (c) 1999-2018, Craig Stuart Sapp | |||||
All rights reserved. | |||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |||||
2. Redistributions in binary form must reproduce the above copyright notice, and the following disclaimer in the documentation and/or other materials provided with the distribution. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@@ -0,0 +1,27 @@ | |||||
# Must follow the format in the Naming section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||||
SLUG = ImpromptuModular | |||||
# Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html | |||||
VERSION = 0.6.9 | |||||
# FLAGS will be passed to both the C and C++ compiler | |||||
FLAGS += | |||||
CFLAGS += | |||||
CXXFLAGS += | |||||
# Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. | |||||
# Static libraries are fine. | |||||
LDFLAGS += | |||||
# Add .cpp and .c files to the build | |||||
SOURCES += $(wildcard src/*.cpp src/midifile/*.cpp) | |||||
# Add files to the ZIP package when running `make dist` | |||||
# The compiled plugin is automatically added. | |||||
DISTRIBUTABLES += $(wildcard LICENSE*) res | |||||
# If RACK_DIR is not defined when calling the Makefile, default to two levels above | |||||
RACK_DIR ?= ../.. | |||||
# Include the VCV Rack plugin Makefile framework | |||||
include $(RACK_DIR)/plugin.mk |
@@ -0,0 +1,317 @@ | |||||
 | |||||
Modules for [VCV Rack](https://vcvrack.com), available in the [plugin manager](https://vcvrack.com/plugins.html). | |||||
Version 0.6.9 | |||||
[//]: # (!!!!!UPDATE VERSION NUMBER IN MAKEFILE ALSO!!!!! 120% Zoom for jpgs) | |||||
## License | |||||
Based on code from the Fundamental and Audible Instruments plugins by Andrew Belt and graphics from the Component Library by Wes Milholen. See ./LICENSE.txt for all licenses (and ./res/fonts/ for font licenses). | |||||
## Acknowledgements | |||||
Impromptu Modular is not a single-person endeavor: | |||||
* Thanks to **Nigel Sixsmith** for the many fruitful discussions and numerous design improvements that were suggested for the modules, for the concept proposal and development of GateSeq64, for detailed testing/bug-reports, and also for the in-depth presentation of PhraseSeq16 and TwelveKey in Talking Rackheads [epsiode 8](https://www.youtube.com/watch?v=KOpo2oUPTjg), as well as PhraseSeq32 and GateSeq64 in [episode 10](https://www.youtube.com/watch?v=bjqWwTKqERQ) and Clocked in [episode 12](https://www.youtube.com/watch?v=ymfOh1yCzU4). | |||||
* Thanks to **Xavier Belmont** for suggesting improvements to the modules, for testing/bug-reports, for the concept design of the SMS16 module and the blank panel, and for graciously providing the dark panels of all modules. | |||||
* Thanks to **Steve Baker** for many fruitful discussions regarding the BPM Detection method in Clocked, testing and improvements that were suggested for that module. | |||||
* Thanks to **Omri Cohen** for testing and suggesting improvements to the modules, and for the [PhraseSeq16/32 tutorial](https://www.youtube.com/watch?v=N8_rMNzsS7w). | |||||
* Thanks also to **Latif Fital**, **Alfredo Santamaria**, **Nay Seven**, **Alberto Zamora**, **Clément Foulc** for suggesting improvements to the modules, bug reports and testing. | |||||
# Modules <a id="modules"></a> | |||||
Each module is available in light (Classic) or dark (Dark-valor) panels, selectable by right-clicking the module in Rack. | |||||
* [Tact](#tact): A touch-like controller module with dual CV outputs and variable rate of change. | |||||
* [TwelveKey](#twelve-key): Chainable one-octave keyboard controller. | |||||
* [Clocked](#clocked): Chainable clock module with swing, clock delay and pulse width control. | |||||
* [PhraseSeq16](#phrase-seq-16): 16-phrase sequencer with 16 steps per sequence, with onboard keyboard and CV input for easy sequence programming. | |||||
* [PhraseSeq32](#phrase-seq-32): 32-phrase sequencer with 32 steps per sequence, with onboard keyboard and CV input for easy sequence programming (can be configured as 1x32 or 2x16). | |||||
* [GateSeq64](#gate-seq-64): 16-phrase gate sequencer with 64 steps per sequence and per-step gate probability control, perfect for adding controlled randomness to your drum patterns (can be configured as 1x64, 2x32 or 4x16). | |||||
* [BigButtonSeq](#big-button-seq): 6-channel 64-step trigger sequencer based on the infamous BigButton by Look Mum No Computer. | |||||
* [Semi-Modular Synth 16](#sms-16): Internally pre-patched all in one synthesizer for quickly getting sounds and learning the basics of modular synthesis. | |||||
* [WriteSeq32/64](#write-seq): Multi-channel 32/64-step sequencers with CV inputs for easy sequence programming. | |||||
Details about each module are given below. Feedback and bug reports (and [donations!](https://www.paypal.me/marcboule)) are always appreciated! | |||||
## Known issues <a id="known-issues"></a> | |||||
For sequencers and clock modules, it is advisable to have a core audio module added to your patch and assigned to a sound card in order for the timing and response delays in the user interface to be of the proper duration. This is a [known artifact](https://github.com/VCVRack/Rack/issues/919) in VCV Rack. | |||||
## General Concepts <a id="general-concepts"></a> | |||||
Many Impromptu Modular sequencers feature a CV input for entering notes into the sequencers in a quick and natural manner when using, for example: | |||||
* a physical midi keyboard connected via the Core MIDI-1 module in VCV Rack; | |||||
* a software midi keyboard (such as [VMPK](http://vmpk.sourceforge.net/)) via the Core MIDI-1 module (a software midi loopback app may be required); | |||||
* a keyboard within VCV Rack such as the Autodafe keyboard or [TwelveKey](#twelve-key). | |||||
Such sequencers have two main inputs that allow the capturing of (pitch) CVs, as follows: The edge sensitive **WRITE** control voltage is used to trigger the writing of the voltage on the **CV IN** jack into the CV of the current step. Any voltage between -10V and 10V is supported, and when a sequencer displays notes via a built-in keyboard or a display showing note letters, non-quantized CVs are mapped to the closest note but are correctly stored in the sequencer. | |||||
When **AUTOSTEP** is activated, the sequencer automatically advances one step right on each write. For example, to automatically capture the notes played on a keyboard, send the midi keyboard's CV into the sequencer's CV IN, and send the keyboard's gate signal into the sequencer's Write input. With Autostep activated, each key-press will be automatically entered in sequence. An alternative way of automatically stepping the sequencer each time a note is entered is to send the gate signal of the keyboard to both the write and ">" inputs. | |||||
When Run is activated, the sequencer automatically starts playing in the current step position, provided **RESET on RUN** is not checked in the right-click menu; sequencers will start at the first step when this option is checked. All edge sensitive inputs have a threshold of 1V. In all sequencers, the duration of the gates corresponds to the pulse width (high time) of the clock signal. | |||||
Many modules feature an **Expansion panel** to provide additional CV inputs for the module (available in the right-click menu of the module). An extra 4 HP is added on the right side of the module, thus it is advisable to first make room in your Rack for this. | |||||
## Tact <a id="tact"></a> | |||||
 | |||||
A touch-like controller module with dual CV outputs and variable rate of change. With a fast rate of change, the controller offers an alternative to knobs for modulating parameters, and with a slow rate of change it can be used to automate in-out fades, for example, freeing the performer to work elsewhere in the patch. | |||||
**RATE**: Transition time of CV, from 0 (instant transition) to 4 seconds per volt. Transition time is the inverse of slew rate. This knob can be turned in real time to affect the rate of change of a transition already under way. | |||||
**LINK**: Both controls are linked and will be synchronized to the same value. Useful when controlling stereo sounds. Only the left side controls have an effect in this mode; however, both touch pads can be used to change the single CV (which is sent to both output jacks). | |||||
**STORE**: memorize the current CV to later be recalled when a trigger is sent to the Recall CV input. | |||||
**RECALL and ARROW Inputs**: CV inputs for setting the CVs to their stored/top/bottom position. These are edge sensitive inputs with a 1V threshold. | |||||
**SLIDE**: determines whether the recall operation is instantaneous or transitions according to the current rate knob's setting. | |||||
**ATTV**: Typical attenuverter to set the maximum CV range output by the module. At full right, a 0 to 10V CV is produced, and at full left, a 0 to -10V CV is produced. | |||||
**EXP**: Produces an exponential slide (top position) instead of a linear slide (bottom position). | |||||
**EOC**: EOC is an end-of-cycle trigger that is emitted when a slide is completed (to signal its end); this can be used for more automation, for example, by triggering or chaining other operations when a fade in/out completes. The EOC triggers upon the end of any slide event, whether the end position is at the top/bottom or not. | |||||
A 0V CV is initially stored in the CV memory and the slide switches are in the off position, thereby allowing the Recall to act as a **Reset** by default. | |||||
([Back to module list](#modules)) | |||||
## TwelveKey <a id="twelve-key"></a> | |||||
 | |||||
A chainable keyboard controller for your virtual Rack. When multiple TwelveKey modules are connected in series from left to right, only the octave of the left-most module needs to be set, all other down-chain modules' octaves are set automatically. The aggregate output is that of the right-most module. To set up a chain of TwelveKey modules, simply connect the three outputs on the right side of a module to the three inputs of the next module beside it (typically to the right). | |||||
For a brief tutorial on setting up the controller, please see [this segment](https://www.youtube.com/watch?v=KOpo2oUPTjg&t=874s) or [this segment](https://www.youtube.com/watch?v=hbxlK07PQAI&t=4614s) of Nigel Sixsmith's Talking Rackheads series. | |||||
* **CV**: The CV output from the keyboard or its CV input, depending on which key was last pressed, i.e. an up-chain key (from a module to the left) or a key of the given keyboard module. | |||||
* **GATE**: Gate output signal from the keyboard or its gate input. | |||||
* **OCTAVE -/+**: Buttons to set the base octave of the module. These buttons have no effect when a cable is connected to the OCT input. | |||||
* **OCT**: CV input to set the base octave of the module. The voltage range is 0V (octave 0) to 9V (octave 9). Non-integer voltages or voltages outside this range are floored/clamped. | |||||
* **OCT+1**: CV output for setting the voltage of the next down-chain TwelveKey module. This corresponds to the base octave of the current module incremented by 1V. | |||||
([Back to module list](#modules)) | |||||
## Clocked <a id="clocked"></a> | |||||
 | |||||
A chainable master clock module with swing, clock delay and pulse width controls, with master BPM from 30 to 300 and all mult/div ratios up to 16, including 1.5 and 2.5, and with additional ratios spanning prime numbers and powers of two up to 64. The clock can produce waveforms with adjustable pulse widths for use with envelope generators or sequencers that use the clock pulse to produce their gate signals. The clock can also be synchronized to an external clock source. | |||||
For a tutorial on Clocked regarding chaining, clock multiplications and divisions, swing and clock delay features, please see Nigel Sixsmith's [Talking Rackheads episode 12](https://www.youtube.com/watch?v=ymfOh1yCzU4). | |||||
**SWING**: The clock swing is loosely based on the [Roger Linn method](https://www.attackmagazine.com/technique/passing-notes/daw-drum-machine-swing/). For a given clock, all even clocks pulses are offset forward/backward according to the setting of the Swing knob; at 0 (top) everything is aligned as normal. At -100, all even clocks would coincide with odd clocks preceding them, and at +100 they would line up with subsequent clock pulses). The knob thus goes from -99 to +99 such that no beats are missed. In its extreme positions, the timing is tighter than 99 percent of a clock period (the 99 value is only a rough indication). | |||||
**PW**: Pulse width is dependent on the swing knob, but can be used to control the general duration of the clock pulse. In the worst-case knob settings, the pulse width is guaranteed to be a minimum of 1ms, with a minimum 1ms pause between pulses. | |||||
**DELAY**: Clock delay can be used to offset a sub-clock relative to the master clock, and is expressed in fractions of the clock period of the given sub-clock. Using the right click menu, the delay value can also be displayed in notes where one quarter note corresponds to a clock period. | |||||
In place of a detailed explanation of these three main controls, it is recommended to connect the outputs to a scope or a logic analyzer, such as the Fundamental Scope (pictured above) or the SubmarineFree LA-108, to observe the effects of the different controls. | |||||
The PW and Swing **CV inputs** (some are available in the expansion panel) are 0-10V signals, and when using these inputs, the corresponding knobs should be in their default position. When this is the case, no-swing and normal-pulse-width correspond to 5V on the CV inputs. | |||||
### External synchronization <a id="clocked-sync"></a> | |||||
By default, the clock's BPM input is level sensitive and follows [Rack standards for BPM CVs](https://vcvrack.com/manual/VoltageStandards.html#pitch-and-frequencies). For synchronizing Clocked an external clock signal, an optional setting is available in the right-click menu called "Use **BPM Detection** (as opposed to **BPM CV**)". When using this option in a chain of Clocked modules, all modules must have the option checked. The green LED to the right of the main BPM display will light up when this mode is enabled and a cable is connected to the BPM input. | |||||
When using BPM detection, Clocked syncs itself to the incoming clock pulse, and will stay synchronized, as opposed to just calculating the BPM from the external source. This means that it will not drift (or that it will drift in time with the incoming pulses if they drift), and it should stay perfectly synchronized over time; it also allows for latency compensation. Here are a few points to keep in mind when using BPM Detection. | |||||
1. When using the BPM detection mode, Clocked can not be manually turned on, it will autostart on the first pulse it receives. | |||||
1. Clocked will automatically stop when the pulses stop, but in order to detect this, it take a small amount of time. To stop the clock quickly, you can simply send a pulse to the RUN CV input, and if the clock is running, it will turn off. | |||||
1. The external clock must be capable of sending clocks at a minumum of 4 pulses per quarter note (PPQN) and should not have any swing. | |||||
1. For low clock BPMs, synchronization may take some time if the external clock changes markedly from the last BPM it was synchronized to. Making gradual tempo changes is always recommended, and increasing the PPQN setting may also help. An other method is to first prime Clocked with is correct BPM to let it learn the new BPM, so that all further runs at that BPM will sync perfectly. | |||||
([Back to module list](#modules)) | |||||
## PhraseSeq16 <a id="phrase-seq-16"></a> | |||||
 | |||||
A 16 phrase sequencer module, where each phrase is an index into a set of 16 sequences of 16 steps (maximum). CVs can be entered via a CV input when using an external keyboard controller or via the built-in keyboard on the module itself. If you need a 256-step sequence in a single module, this is the sequencer for you! With two separate gates per step, gate 2 is perfect for using as an accent if desired. When notes are entered with the *right mouse button* instead of the left button, the sequencer automatically moves to the next step. | |||||
The following block diagram shows how sequences and phrases relate to each other to create a song. In the diagram, a 12-bar blues pattern is created by setting the song length to 12, the step lengths to 8 (not visible in the figure), and then creating 4 sequences. The 12 phrases are indexes into the 4 sequences that were created. (Not sure anyone plays blues in a modular synth, but it shows the idea at least!) | |||||
 | |||||
Familiarity with the Fundamental SEQ-3 sequencer is recommended, as some operating principles are similar in both sequencers. For an in depth review of the sequencer's capabilities, please see Nigel Sixsmith's [Talking Rackheads episode 8](https://www.youtube.com/watch?v=KOpo2oUPTjg) or Omri Cohen's [PhraseSeq tutorial](https://www.youtube.com/watch?v=N8_rMNzsS7w). | |||||
* **SEQ/SONG**: This is the main switch that controls the two major modes of the sequencer. Seq mode allows the currently selected sequence to be played/edited. In this mode, all controls are available (run mode, transpose, rotate, copy-paste, gates, slide, octave, notes) and the content of a sequence can be modified even when the sequencer is running. Song mode allows the creation of a series of sequence numbers (called phrases). In this mode, only the run mode and length of the song and the sequence index numbers themselves can be modified (whether the sequence is running or not); the other aforementioned controls are unavailable and the actual contents of the sequences cannot be modified. | |||||
* **LENGTH**: When in Seq mode, this button allows the arrow buttons to select the length of sequences (number of steps), the default is 16. The sequences can have different lengths. When in Song mode, this button allows the arrow buttons to select the number of phrases in the song (the default is 4). | |||||
* **<, >**: These arrow buttons step the sequencer one step left or right. They have no effect when Attach is activated. | |||||
* **SEQ#**: In Seq mode, this number determines which sequence is being edited/played. In Song mode, this number determines the sequence index for the currently selected phrase; the selected phrase is shown in the 16 LEDs at the top of the module). When one of the Mode, Transpose, Rotate buttons is pressed, the display instead shows the current run mode (see Mode below), the amount of semi-tones to transpose, and the number of steps to rotate respectively. The SEQ# control voltage can be used to select the active sequence (Seq mode only), whereby a 0 to 10V input is proportionally mapped to the 1 to 16 sequence numbers. This can be used to externally control the playing order of the sequences. | |||||
* **ATTACH**: Allows the edit head to follow the run head (Attach on). The position of the edit head is shown with a red LED, and the position of the run head is shown with a green LED. When in Seq mode, the actual content of the step corresponding to the edit head position (i.e. note, oct, gates, slide) can be modified in real time whether the sequencer is running or not. The edit head automatically follows the run head when Attach is on, or can manually positioned by using the < and > buttons when Attach is off. | |||||
* **MODE**: This controls the run mode of the sequences and the song (one setting for each sequence and one for the song). The modes are: FWD (forward), REV (reverse), PPG (ping-pong, also called forward-reverse), BRN (Brownian random), RND (random), FW2 (forward, play twice), FW3 (play three times) and FW4 (four times). For example, setting the run mode to FWD for sequences and to RND for the song will play the phrases that are part of a song randomly, and the probability of a given phrase playing is proportional to the number of times it appears in the song. For sequences, the FW2, FW3 and FW4 modes can be used to repeat sequences more easily without consuming additional phrases in the song. These last three modes are not available for the song's run mode however. | |||||
* **TRAN/ROT**: Transpose/Rotate the currently selected sequence up-down/left-right by a given number of semi-tones/steps. The main knob is used to set the transposition/rotation amount. Only available in Seq mode. | |||||
* **COPY-PASTE**: Copy and paste the CVs, gates, slide and tied attributes of a part or all of a sequence into another sequence. When ALL is selected, run mode and length are also copied. Only available in Seq mode. | |||||
* **OCT and keyboard**: When in Seq mode, the octave LED buttons and the keyboard can be used to set the notes of a sequence. The octave and keyboard LEDs are used for display purposes only in Song mode with attach on. | |||||
* **GATE 1, 2 buttons and probability knob**: The gate buttons control whether the gate of a current step is active or not. The probability knob controls the chance that when gate 1 is active it is actually sent to its output jack. In the leftmost position, no gates are output, and in the rightmost position, gates are output exactly as stored in a sequence. This knob's probability setting is not memorized for each step and applies to the sequencer as a whole. | |||||
* **SLIDE**: Portamento between CVs of successive steps. Slide can be activated for a given step using the slide button. The slide duration can be set using the slide knob. The slide duration can range from 0 to T seconds, where T is the duration of a clock period (the default is 10% of T). This knob's setting is not memorized for each step and applies to the sequencer as a whole. | |||||
* **TIED STEP**: When CVs are intended to be held across subsequent steps, this button can be used to tie the CV of the previous step to the current step; when tied, the gates of the current step are automatically turned off. If the CV of the head note changes, all consecutive tied notes are updated automatically. | |||||
([Back to module list](#modules)) | |||||
## PhraseSeq32 <a id="phrase-seq-32"></a> | |||||
 | |||||
A 32 phrase sequencer module, where each phrase is an index into a set of 32 sequences of 32 steps (maximum). This sequencer is very similar to [PhraseSeq16](#phrase-seq-16), but with an added configuration switch allowing the sequencer to output dual 16 step sequences (**2x16**) instead of single 32 step sequences (**1x32**). | |||||
Step/phrase selection is done by directly clicking the 32 steps at the top, instead of cursor buttons as used in PhraseSeq16. When running in the 2x16 configuration and in Seq mode, with **ATTACH** activated, simply click any step in a given row to attach the edit head to that row. | |||||
Sequence lengths can be set clicking the **LEN/MODE** button once, and then either turning the main knob below the main display or clicking the desired length directly in the steps (the second method is the recommended way since the display will automatically return to its default state one second after the click of the step). The run modes can be set by clicking the LEN/MODE button twice starting from its initial state. The **TRAN/ROT** button can be used to transpose or rotate a sequence in Seq mode. When the 2x16 configuration is selected, only the row corresponding to the edit head's position is transposed or rotated. | |||||
When the 1x32 configuration is selected, only the top channel outputs are used (labeled A), and when the 2x16 configuration is selected, the top row is sent to the top outputs (CV and gates A), whereas the bottom row of steps is sent to the bottom outputs (CV and gates B). Other than these characteristics, the rest of PhraseSeq32's functionality is identical to that of PhraseSeq16. | |||||
([Back to module list](#modules)) | |||||
## GateSeq64 <a id="gate-seq-64"></a> | |||||
 | |||||
A 64 step gate sequencer with the ability to define **probabilities** for each step. A configuration switch allows the sequencer to output quad 16 step sequences, dual 32 step sequences or single 64 step sequences. To see the sequencer in action and for a tutorial on how it works, please see [this segment](https://www.youtube.com/watch?v=bjqWwTKqERQ&t=6111s) of Nigel Sixsmith's Talking Rackheads episode 10. | |||||
When activating a given step by clicking it once, it will turn green showing that the step is on. Clicking the step again turns it yellow, and the main display shows the probability associated with this step. While the probability remains shown, the probability can be adjusted with the main knob, in 0.02 increments, between 0 and 1. When a yellow step is selected, clicking it again will turn it off. | |||||
This sequencer also features the song mode found in [PhraseSeq16](#phrase-seq-16); 16 phrases can be defined, where a phrase is an index into a set of 16 sequences. In GateSeq64, the song steps are shown using the fourth row of steps, overlapped with the actual sequence progression in lighter shades in the lights. | |||||
The **SEQ** CV input and run **MODES** are identical to those found in PhraseSeq16, and selecting sequence lengths is done in the same manner as described in [PhraseSeq32](#phrase-seq-32). Copy-pasting **ALL** also copies the run mode and length of a given sequence, along with gate states and probabilities, whereas only gates and probabilities are copied when **ROW** is selected. | |||||
When running in the 4x16 configuration, each of the four rows is sent to the four **GATE** output jacks (jacks 1 to 4, with jack 1 being the top-most jack). In the 2x32 configuration, jacks 1 and 3 are used, and in the 1x64 configuration, only jack 1 is used (top-most jack). The pulse width of the gates emitted corresponds to the pulse width of the clock. | |||||
Although no **write** capabilities appear in the main part of the module, automatically storing patterns into the sequencer can be performed using the CV inputs in the **expansion panel**. A write cursor is implicitly stepped forward on each write, and can be repositioned at the first step by pressing the reset button, or at an arbitrary step by simply clicking that given step. | |||||
([Back to module list](#modules)) | |||||
## BigButtonSeq <a id="big-button-seq"></a> | |||||
 | |||||
A 6-channel 64-step trigger sequencer based on the infamous [BigButton](https://www.youtube.com/watch?v=6ArDGcUqiWM) by [Look Mum No Computer](https://www.lookmumnocomputer.com/projects/#/big-button/). The sequencer is mainly for live uses. Although this is not a direct port of the original module, the intent was to keep it as faithful as possible, while adding a few minor extras such as CV inputs. For two-handed control of the knobs and buttons, connect the sequencer to a midi control surface using Rack's Core MIDI-CC module. To see more examples of what the sequencer can do, please see the following videos: | |||||
* [BigButton VCV Rack Module test](https://www.youtube.com/watch?v=uN2l2t5SCyE) by Alberto Zamora; | |||||
* [Small impromptu VCV jam](https://www.youtube.com/watch?v=wm5JEH5spbc) by Matt Tyas; | |||||
* [lookmom](https://www.youtube.com/watch?v=Jcdok8jJ5hQ) and [bbs](https://www.youtube.com/watch?v=j5ejGH5XgFg) by Clément Foulc. | |||||
Here are a few more details on some of the uses of the buttons. The sequencer uses has two types of pushbuttons, namely trigger buttons and state buttons. Trigger buttons react to the change in a button's state as it's being pressed, while state buttons react to the position of the push-button, i.e. pressed or not pressed. | |||||
**CHAN**: channel select button (can be changed in real time). All state buttons will have an effect immediately when the channel is changed. | |||||
**BIG BUTTON**: trigger-type button to set the trigger at the current step in the current channel (when pressing on a step that has a trigger, nothing is done). | |||||
**CLK**: clock input. The sequencer is always running. To stop it, the clock has to be stopped. | |||||
**RND**: a 0 to 1 probability knob, used to randomly change the state of a step. The probability is applied to the trigger of the next step being reached at every clock pulse, in the current channel. A 0 value means no change, 1 means the state of the trigger will be toggled (useful for inverting a pattern). The RND CV input's voltage is divided by 10 and added to the value of the knob to yield the actual probablility. For example, with the knob at full right, the CV input has no effet, and with the knob at full left, an value of 10V on the CV input gives a probability of 1. | |||||
**CLEAR**: state-type button that turns of all triggers of the current channel. | |||||
**BANK**: trigger-type button that toggles between two possible banks (i.e. sequences) for the current channel. | |||||
**DEL**: state-type button that clears the trigger at the current step. The button can be held to clear multiple steps. | |||||
**FILL**: plays continuous triggers for the given channel as long as the button is kept pressed. By default the fills are not written to memory and are only for playback; however, an option to allow the writing of fill steps to memory is available in the right-click menu. | |||||
([Back to module list](#modules)) | |||||
## Semi-Modular Synth 16<a id="sms-16"></a> | |||||
 | |||||
An all-in-one pre-patched semi-modular synthesizer. Based on the [Fundamental](https://vcvrack.com/Fundamental.html) modules by VCV and the [PhraseSeq16](#phrase-seq-16) sequencer (above). Please see those links for the respective manuals, while keeping in mind that not all features of the Fundamental modules were implemented in Semi-Modular Synth 16 (SMS16). A typical signal flow is internally patched by default, as shown by the interconnecting lines on the faceplate of the module. The majority of the internal connections can be overridden by any cables patched into those related jacks. | |||||
This module can be used for quickly exploring ideas for sequences, and is also useful for those new to modular synthesis before learning how to fill the screen with cables! Also note that the final output is the low-pass output of the VCF module (Voltage Controlled Filter). The VCF is purposely placed after the VCA (Voltage Controlled Amplifier) in the signal flow, such that the VCF can be lightly saturated, producing a thicker sound, especially when the Drive knob is turned up. | |||||
Extra controls were also added to the LFO (Low Frequency Oscillator), namely **GAIN** and **OFFSET**, to be able to easily modulate any other on-board parameter. With maximum gain, the LFO produces a 10V peak-to-peak signal (i.e. 5V amplitude). The offset knob is automatically scaled such that with maximum (minimum) offset, the signal's maximum (minimum) voltage is +10V (-10V). That is, with the gain set to 0, the offset value spans -10V to +10V, and with the gain set to maximum, the offset value spans -5V to +5V. Also note that the clock LFO is automatically reset on every reset event in the sequencer. | |||||
([Back to module list](#modules)) | |||||
## WriteSeq32/64 <a id="write-seq"></a> | |||||
 | |||||
WriteSeq32 is a three-channel 32-step writable sequencer module. Although the display shows note names (ex. C4#, D5, etc.), any voltage within the -10V to 10V range can be stored/played in the sequencer, whether it is used as a pitch CV or not, and whether it is quantized or not. Gate states and window selection can be done by pressing the 8 and 4 LED buttons respectively located below and above the main display. Familiarity with the Fundamental SEQ-3 sequencer is recommended, as some operating principles are similar in both sequencers. | |||||
* **WINDOW**: LED buttons to display/select the active 8-step window within the 32 step sequence (hence four windows). No effect on channels 1 to 3 when the sequencer is running. | |||||
* **QUANTIZE**: Quantizes the CV IN input to a regular 12 semi-tone equal temperament scale. Since this quantizes the CV IN, some channels can have quantized CVs while others do not. | |||||
* **Main display**: Shows the note names for the 8 steps corresponding to the active window. When a stored pitch CV has not been quantized, the display shows the closest such note name. For example, 0.03 Volts is shown as C4, whereas 0.05 Volts is shown as C4 sharp or D4 flat. Octaves above 9 or below 0 are shown with a top bar and an underscore respectively. | |||||
* **GATES**: LED buttons to show/modify the gates for the 8 steps in the current window. Gates can be toggled whether the sequencer is running or not. | |||||
* **CHAN**: Selects the channel that is to be displayed/edited in the top part of the module. Even though this is a three-channel sequencer, a fourth channel is available for staging a sequence while the sequencer is running (or not). | |||||
* **COPY-PASTE**: Copy and paste the CVs and gates of a channel into another channel. All 32 steps are copied irrespective of the Steps knob setting. | |||||
* **PASTE SYNC**: Determines whether to paste in real time (RT), on the next clock (CLK), or at the start of the next sequence (SEQ). Pending pastes are shown by a red LED beside CLK/SEQ, and if the selected channel changes, the paste operation will be performed in the channel that was selected when the paste button was pressed. Paste operations into the staging area (channel 4) are always done in real time, irrespective of the state of the paste sync switch. To cancel a pending paste, press the Copy button again. | |||||
* **<, >**: These buttons step the sequencer one step left or right. No effect on channels 1 to 3 when the sequencer is running. A rising edge on the </> control voltage inputs triggered at 1V will also step the sequencer left/right by one step. | |||||
* **RUN 1-3**: Start/stop the sequencer. When running, the sequencer responds to rising edges of the clock input and will step all channels except the staging area (channel 4). A rising edge on the RUN input will also toggle the run mode. | |||||
* **GATE IN**: Allows the gate of the current step/channel to also be written during a Write (see [General Concepts](#general-concepts) above). If no wire is connected, the input is ignored and the currently stored gate is unaffected. No effect on channels 1 to 3 when the sequencer is running. | |||||
* **STEPS**: Sets the number of steps for all the sequences (sequence length). | |||||
* **MONITOR**: This switch determines which CV will be routed to the currently selected channel's CV output when the sequencer is not running. When the switch is in the right-most position, the CV stored in the sequencer at that step is output; in the left-most position, the CV applied to the CV IN jack is output. | |||||
WriteSeq64 is a four-channel 64-step writable sequencer module. This sequencer is more versatile than WriteSeq32 since each channel has its own step position and maximum number of steps (length). Sequences of different lengths can be created, with different starting points. A fifth channel is available to be used as a staging area. | |||||
WriteSeq64 has dual clock inputs, where each controls a pair of channels. When no wire is connected to **CLOCK 3,4**, the **CLOCK 1,2** signal is used internally as the clock for channels 3 and 4. | |||||
Ideas: The first part of the famous [Piano Phase](https://en.wikipedia.org/wiki/Piano_Phase) piece by Steve Reich can be easily programmed into the sequencer by entering the twelve notes into channel 1 with a keyboard, setting STEPS to 12, copy-pasting channel 1 into channel 3, and then driving each clock input with two LFOs that have ever so slightly different frequencies. Exercise left to the reader! | |||||
([Back to module list](#modules)) |
@@ -0,0 +1,23 @@ | |||||
ALL_OBJ= \ | |||||
src/BigButtonSeq.o \ | |||||
src/BlankPanel.o \ | |||||
src/Clocked.o \ | |||||
src/FundamentalUtil.o \ | |||||
src/GateSeq64.o \ | |||||
src/ImpromptuModular.o \ | |||||
src/IMWidgets.o \ | |||||
src/midifile/Binasc.o \ | |||||
src/midifile/MidiEvent.o \ | |||||
src/midifile/MidiEventList.o \ | |||||
src/midifile/MidiFile.o \ | |||||
src/midifile/MidiMessage.o \ | |||||
src/MidiFileModule.o \ | |||||
src/PhraseSeq16.o \ | |||||
src/PhraseSeq32.o \ | |||||
src/SemiModularSynth.o \ | |||||
src/Tact.o \ | |||||
src/TwelveKey.o \ | |||||
src/WriteSeq32.o \ | |||||
src/WriteSeq64.o | |||||
# src/EngTest1.o |
@@ -0,0 +1,7 @@ | |||||
SLUG=ImpromptuModular | |||||
include ../../../build_plugin_pre.mk | |||||
include make.objects | |||||
include ../../../build_plugin_post.mk |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="20.641" height="14" viewBox="0 0 5.461 3.704"><g stroke-width="1.282"><path d="M5.079.002c.214 0 .39.176.39.39v2.924c0 .215-.176.39-.39.39H.398a.392.392 0 0 1-.39-.39V.393c0-.215.176-.39.39-.39z"/><path d="M5.117.583a.234.234 0 0 0-.234-.234H.555A.235.235 0 0 0 .32.583v2.543c0 .128.105.234.234.234h4.328a.235.235 0 0 0 .234-.234z" fill="#282828"/><path d="M2.661.349V3.36H.321V.35z" fill="#444"/><path d="M1.62.349V3.36h-.258V.35zM.578.349V3.36H.321V.35zM1.099.349V3.36H.84V.35zM2.14.349V3.36h-.257V.35zM2.661.349V3.36h-.257V.35z" fill="#999"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="20.641" height="14" viewBox="0 0 5.461 3.704"><g stroke-width="1.282"><path d="M5.07.007c.214 0 .39.176.39.39v2.924c0 .214-.176.39-.39.39H.387a.392.392 0 0 1-.39-.39V.397c0-.214.176-.39.39-.39z"/><path d="M5.107.587a.235.235 0 0 0-.234-.234H.545a.235.235 0 0 0-.234.234V3.13c0 .129.105.234.234.234h4.328a.235.235 0 0 0 .234-.234z" fill="#282828"/><path d="M5.107.353v3.011h-2.34V.354z" fill="#444"/><path d="M4.066.353v3.011h-.258V.354zM3.024.353v3.011h-.257V.354zM3.545.353v3.011h-.258V.354zM4.587.353v3.011h-.258V.354zM5.107.353v3.011H4.85V.354z" fill="#999"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 9.525 9.525"><g transform="translate(-230.096 -123.233)"><path d="M239.594 127.492c-.21-.339-.561-.671-.757-1.145-.196-.472-.183-.955-.273-1.34a4.779 4.779 0 0 0-.717-.717c-.386-.09-.868-.078-1.34-.273-.474-.197-.807-.548-1.145-.757a4.781 4.781 0 0 0-1.007 0c-.338.21-.671.56-1.145.757-.472.195-.954.183-1.34.273a4.777 4.777 0 0 0-.717.716c-.09.386-.078.869-.274 1.341-.195.474-.547.806-.756 1.145a4.781 4.781 0 0 0 0 1.007c.209.338.56.67.756 1.144.196.472.184.955.274 1.34.213.265.453.505.717.718.386.09.868.077 1.34.273.474.196.807.548 1.145.757a4.857 4.857 0 0 0 1.007 0c.338-.21.67-.56 1.144-.757.473-.196.955-.183 1.341-.273.264-.213.504-.453.717-.717.09-.386.077-.869.273-1.34.196-.474.548-.807.757-1.145a4.883 4.883 0 0 0 0-1.007" fill-rule="evenodd" stroke="#4f4f4f" stroke-width=".153" stroke-linecap="round" stroke-linejoin="round"/><circle cx="234.858" cy="127.995" r="2.576" fill="#717171"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36" height="36" viewBox="0 0 9.525 9.525"><defs><linearGradient id="a"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="18" y1="3" x2="28" y2="7" gradientUnits="userSpaceOnUse"/><linearGradient gradientTransform="matrix(-.26458 0 0 .26458 239.62 123.233)" xlink:href="#a" id="c" x1="18" y1="3" x2="28" y2="7" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" x1="18" y1="3" x2="28" y2="7"/><linearGradient xlink:href="#a" id="e" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.26458 0 0 .26458 239.62 123.233)" x1="18" y1="3" x2="28" y2="7"/></defs><path transform="scale(.26458) rotate(45 18 18.002)" d="M18 2.186v1.482a14.332 14.332 0 0 1 10.414 4.496l1.068-1.01A15.814 15.814 0 0 0 18 2.186z" fill="url(#b)"/><path d="M234.858 123.811v.392a3.792 3.792 0 0 0-2.755 1.19l-.283-.267a4.184 4.184 0 0 1 3.038-1.315z" fill="url(#c)" transform="rotate(45 268.566 -211.37)"/><path transform="matrix(.12624 .12624 .12624 -.12624 .206 4.775)" d="M18 2.186v1.482a14.332 14.332 0 0 1 10.414 4.496l1.068-1.01A15.814 15.814 0 0 0 18 2.186z" fill="url(#d)"/><path d="M234.858 123.811v.392a3.792 3.792 0 0 0-2.755 1.19l-.283-.267a4.184 4.184 0 0 1 3.038-1.315z" fill="url(#e)" transform="scale(.67475 -.67475) rotate(-45 -42.097 335.46)"/><path d="M4.762 4.753L6.864 3.27a2.576 2.576 0 0 0-.656-.648z" fill="#b1b1b1"/><path d="M4.762 4.762l.41 2.533a2.576 2.576 0 0 0 2.124-2.087zM4.762 4.762l-.41-2.532A2.576 2.576 0 0 0 2.23 4.317z" fill="#3c3c3c"/><path d="M4.762 4.762L2.661 6.246a2.576 2.576 0 0 0 .656.648z" fill="#b1b1b1"/></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 9.525 9.525"><g transform="translate(-230.096 -123.233)"><path d="M239.594 127.492c-.21-.339-.561-.671-.757-1.145-.196-.472-.183-.955-.273-1.34a4.779 4.779 0 0 0-.717-.717c-.386-.09-.868-.078-1.34-.273-.474-.197-.807-.548-1.145-.757a4.781 4.781 0 0 0-1.007 0c-.338.21-.671.56-1.145.757-.472.195-.954.183-1.34.273a4.777 4.777 0 0 0-.717.716c-.09.386-.078.869-.274 1.341-.195.474-.547.806-.756 1.145a4.781 4.781 0 0 0 0 1.007c.209.338.56.67.756 1.144.196.472.184.955.274 1.34.213.265.453.505.717.718.386.09.868.077 1.34.273.474.196.807.548 1.145.757a4.857 4.857 0 0 0 1.007 0c.338-.21.67-.56 1.144-.757.473-.196.955-.183 1.341-.273.264-.213.504-.453.717-.717.09-.386.077-.869.273-1.34.196-.474.548-.807.757-1.145a4.883 4.883 0 0 0 0-1.007" fill-rule="evenodd" stroke="#4f4f4f" stroke-width=".153" stroke-linecap="round" stroke-linejoin="round"/><circle cx="234.858" cy="127.995" r="2.576" fill="#717171"/><rect rx=".275" width=".613" height="2.38" x="234.547" y="123.004" ry=".158" fill="#e7e7e7" stroke="#2b2b2b" stroke-width=".106" stroke-linejoin="round"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36" height="36" viewBox="0 0 9.525 9.525"><defs><linearGradient id="a"><stop offset="0" stop-color="#fff"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="18" y1="3" x2="28" y2="7" gradientUnits="userSpaceOnUse"/><linearGradient gradientTransform="matrix(-.26458 0 0 .26458 239.62 123.233)" xlink:href="#a" id="c" x1="18" y1="3" x2="28" y2="7" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#a" id="d" gradientUnits="userSpaceOnUse" x1="18" y1="3" x2="28" y2="7"/><linearGradient xlink:href="#a" id="e" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.26458 0 0 .26458 239.62 123.233)" x1="18" y1="3" x2="28" y2="7"/></defs><path transform="scale(.26458) rotate(45 18 18.002)" d="M18 2.186v1.482a14.332 14.332 0 0 1 10.414 4.496l1.068-1.01A15.814 15.814 0 0 0 18 2.186z" fill="url(#b)"/><path d="M234.858 123.811v.392a3.792 3.792 0 0 0-2.755 1.19l-.283-.267a4.184 4.184 0 0 1 3.038-1.315z" fill="url(#c)" transform="rotate(45 268.566 -211.37)"/><path transform="matrix(.12624 .12624 .12624 -.12624 .206 4.775)" d="M18 2.186v1.482a14.332 14.332 0 0 1 10.414 4.496l1.068-1.01A15.814 15.814 0 0 0 18 2.186z" fill="url(#d)"/><path d="M234.858 123.811v.392a3.792 3.792 0 0 0-2.755 1.19l-.283-.267a4.184 4.184 0 0 1 3.038-1.315z" fill="url(#e)" transform="scale(.67475 -.67475) rotate(-45 -42.097 335.46)"/><path d="M4.762 4.753L6.864 3.27a2.576 2.576 0 0 0-.656-.648z" fill="#b1b1b1"/><path d="M4.762 4.762l.41 2.533a2.576 2.576 0 0 0 2.124-2.087zM4.762 4.762l-.41-2.532A2.576 2.576 0 0 0 2.23 4.317z" fill="#3c3c3c"/><path d="M4.762 4.762L2.661 6.246a2.576 2.576 0 0 0 .656.648z" fill="#b1b1b1"/></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 6.879 6.879"><g transform="matrix(.2727 0 0 -.2727 -74.931 327.683)" stroke-width="1.294"><path d="M299.995 1188.983c0-6.965-5.649-12.612-12.614-12.612-6.964 0-12.612 5.647-12.612 12.612 0 6.968 5.648 12.614 12.612 12.614 6.965 0 12.614-5.646 12.614-12.614" fill="#202320"/><ellipse cx="287.382" cy="-1189.64" transform="scale(1 -1)" rx="10.472" ry="10.473" fill="#838583"/><ellipse transform="scale(1 -1)" cy="-1188.984" cx="287.382" rx="10.472" ry="10.473" fill="#515451"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 6.879 6.879"><g transform="matrix(.2727 0 0 -.2727 -74.931 327.683)" stroke-width="1.294"><path d="M299.995 1188.983c0-6.965-5.649-12.612-12.614-12.612-6.964 0-12.612 5.647-12.612 12.612 0 6.968 5.648 12.614 12.612 12.614 6.965 0 12.614-5.646 12.614-12.614" fill="#212221" stroke-width="1.393"/><ellipse transform="scale(1 -1)" cy="-1188.321" cx="287.382" rx="10.472" ry="10.473" fill="#676967"/><ellipse cx="287.382" cy="-1188.984" transform="scale(1 -1)" rx="10.472" ry="10.473" fill="#141514"/></g></svg> |
@@ -0,0 +1,145 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:xlink="http://www.w3.org/1999/xlink" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="8.3556204mm" | |||||
height="8.3556299mm" | |||||
viewBox="0 0 8.3556203 8.3556298" | |||||
version="1.1" | |||||
id="svg15246" | |||||
sodipodi:docname="PJ301M.svg" | |||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"> | |||||
<defs | |||||
id="defs15240"> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
id="linearGradient3935"> | |||||
<stop | |||||
style="stop-color:#000000;stop-opacity:1" | |||||
offset="0" | |||||
id="stop3931" /> | |||||
<stop | |||||
style="stop-color:#ffffff;stop-opacity:1" | |||||
offset="1" | |||||
id="stop3933" /> | |||||
</linearGradient> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient3935" | |||||
id="linearGradient3937" | |||||
x1="92.785469" | |||||
y1="120.45113" | |||||
x2="92.788963" | |||||
y2="126.29511" | |||||
gradientUnits="userSpaceOnUse" /> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient3935" | |||||
id="linearGradient3945" | |||||
x1="92.788963" | |||||
y1="127.42922" | |||||
x2="92.840797" | |||||
y2="119.30055" | |||||
gradientUnits="userSpaceOnUse" /> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient3935" | |||||
id="linearGradient3955" | |||||
x1="90.26416" | |||||
y1="122.50951" | |||||
x2="93.716141" | |||||
y2="122.49179" | |||||
gradientUnits="userSpaceOnUse" | |||||
gradientTransform="matrix(0.80172343,0,0,1,19.228295,0.82829811)" /> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient3935" | |||||
id="linearGradient3955-0" | |||||
x1="89.807892" | |||||
y1="122.50273" | |||||
x2="97.483292" | |||||
y2="122.50082" | |||||
gradientUnits="userSpaceOnUse" | |||||
gradientTransform="matrix(0.35695381,0,0,1.8485282,-126.29288,-349.80317)" /> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="-3.2355082" | |||||
inkscape:cy="13.809697" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
inkscape:window-width="1920" | |||||
inkscape:window-height="1017" | |||||
inkscape:window-x="-8" | |||||
inkscape:window-y="-8" | |||||
inkscape:window-maximized="1" | |||||
units="px" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:snap-global="false" /> | |||||
<metadata | |||||
id="metadata15243"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-88.611154,-119.19859)"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7255" | |||||
d="m 92.788964,127.42922 c -2.235179,0 -4.05281,-1.81762 -4.05281,-4.05282 0,-2.23516 1.817631,-4.05281 4.05281,-4.05281 2.235176,0 4.05281,1.81765 4.05281,4.05281 0,2.2352 -1.817634,4.05282 -4.05281,4.05282" | |||||
style="fill:url(#linearGradient3945);fill-opacity:1;fill-rule:nonzero;stroke:#636663;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7261" | |||||
d="m 92.788964,126.29511 c -1.609548,0 -2.918685,-1.30916 -2.918685,-2.91871 0,-1.60954 1.309137,-2.91867 2.918685,-2.91867 1.609549,0 2.918682,1.30913 2.918682,2.91867 0,1.60955 -1.309133,2.91871 -2.918682,2.91871" | |||||
style="fill:url(#linearGradient3937);fill-opacity:1;fill-rule:nonzero;stroke:#636663;stroke-width:0.25;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path7265" | |||||
d="m 94.588681,123.3764 c 0,0.99357 -0.806153,1.79974 -1.799717,1.79974 -0.993567,0 -1.79972,-0.80617 -1.79972,-1.79974 0,-0.99356 0.806153,-1.79969 1.79972,-1.79969 0.993564,0 1.799717,0.80613 1.799717,1.79969" | |||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||||
<rect | |||||
style="fill:url(#linearGradient3955);fill-opacity:1;stroke-width:0.23690529" | |||||
id="rect3947" | |||||
width="1.9820552" | |||||
height="1.0022607" | |||||
x="91.140427" | |||||
y="122.83668" /> | |||||
<rect | |||||
style="fill:url(#linearGradient3955-0);fill-opacity:1;stroke-width:0.21492234" | |||||
id="rect3947-1" | |||||
width="0.88247657" | |||||
height="1.8527071" | |||||
x="-94.275215" | |||||
y="-124.26724" | |||||
transform="scale(-1)" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,88 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="7.9995098mm" | |||||
height="7.9995222mm" | |||||
viewBox="0 0 7.9995093 7.9995222" | |||||
version="1.1" | |||||
id="svg111794" | |||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" | |||||
sodipodi:docname="RoundSmallBlackKnob.svg"> | |||||
<defs | |||||
id="defs111788" /> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="3.4976945" | |||||
inkscape:cy="14.902022" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer1" | |||||
showgrid="false" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:window-width="1920" | |||||
inkscape:window-height="1018" | |||||
inkscape:window-x="-8" | |||||
inkscape:window-y="-8" | |||||
inkscape:window-maximized="1" | |||||
inkscape:snap-object-midpoints="true" | |||||
inkscape:snap-global="false" | |||||
inkscape:snap-page="true" /> | |||||
<metadata | |||||
id="metadata111791"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:label="Layer 1" | |||||
inkscape:groupmode="layer" | |||||
id="layer1" | |||||
transform="translate(-168.09312,-67.39659)" | |||||
style="display:inline"> | |||||
<path | |||||
transform="translate(168.09312,67.39659)" | |||||
inkscape:connector-curvature="0" | |||||
id="path6449" | |||||
style="display:inline;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#4f4f4f;stroke-width:0.12869287;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
d="M 7.992184,3.5866373 C 7.8163712,3.301999 7.520386,3.0220373 7.3557011,2.623678 7.1910162,2.2266565 7.2019258,1.8205168 7.1258118,1.4960426 6.9466597,1.2741609 6.7450408,1.0727608 6.5229405,0.89338976 6.1982392,0.81751986 5.7925369,0.82820226 5.3950778,0.66350059 4.9967188,0.49836999 4.7167571,0.20304928 4.4325646,0.02701763 4.2934753,0.01229785 4.1521569,0.00388655 4.0092827,0.00388655 c -0.1433202,0 -0.2846383,0.008495 -0.4235089,0.0231311 C 3.3011437,0.20304928 3.0211738,0.49836999 2.622823,0.66350059 2.2258012,0.82818546 1.8200991,0.81750306 1.4951791,0.89338976 1.2733058,1.0727608 1.0714515,1.2741609 0.89252632,1.4960426 0.81640407,1.8205168 0.82708642,2.2266565 0.66219131,2.623678 0.49750649,3.0220373 0.20173999,3.301999 0.02615413,3.5866373 0.01143436,3.7257266 0.00319128,3.8670448 0.00319128,4.0099192 c 0,0.1431014 0.0084113,0.2844111 0.02296285,0.4239462 0.17558586,0.2841925 0.47135236,0.5641542 0.63603718,0.9625132 0.16490352,0.3970218 0.15422117,0.8031699 0.23033501,1.127417 0.17892518,0.2221003 0.38077948,0.4237276 0.60265278,0.6028797 0.32492,0.076122 0.7306221,0.065188 1.1276439,0.2296621 0.3983508,0.1653579 0.6783207,0.4608973 0.9629508,0.6367101 0.1388706,0.014468 0.2801887,0.022963 0.4235089,0.022963 0.1428742,0 0.2841926,-0.00849 0.4232819,-0.022963 C 4.7167571,7.8172347 4.9967188,7.5216953 5.3950778,7.3563374 5.7925369,7.1918797 6.1982392,7.2027808 6.5229405,7.1266753 6.7450408,6.9475232 6.9466597,6.7458959 7.1258118,6.5237956 7.2019341,6.1995485 7.1909994,5.7934004 7.3557011,5.3963786 7.520386,4.9980196 7.8163712,4.7180579 7.992184,4.4338654 8.006652,4.2943303 8.014895,4.1530206 8.014895,4.0099192 c 0,-0.1428744 -0.00841,-0.2841926 -0.022711,-0.4232819" /> | |||||
<rect | |||||
transform="translate(168.09312,67.39659)" | |||||
rx="0.23169038" | |||||
style="display:inline;opacity:1;fill:#e7e7e7;fill-opacity:1;stroke:#2b2b2b;stroke-width:0.08887223;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |||||
id="rect1546-9" | |||||
width="0.51598799" | |||||
height="2.0019751" | |||||
x="3.7469757" | |||||
y="-0.18859674" | |||||
ry="0.13252623" /> | |||||
<circle | |||||
transform="translate(168.09312,67.39659)" | |||||
style="display:inline;opacity:1;fill:#717171;fill-opacity:1;stroke:none;stroke-width:0.05895557;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
id="path6739" | |||||
cx="4.009007" | |||||
cy="4.0099874" | |||||
r="2.1665909" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1,172 @@ | |||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||||
<svg | |||||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||||
xmlns:cc="http://creativecommons.org/ns#" | |||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||||
xmlns:svg="http://www.w3.org/2000/svg" | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
xmlns:xlink="http://www.w3.org/1999/xlink" | |||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||||
width="7.9995098mm" | |||||
height="7.9995222mm" | |||||
viewBox="0 0 7.9995093 7.9995222" | |||||
version="1.1" | |||||
id="svg111794" | |||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" | |||||
sodipodi:docname="RoundSmallBlackKnobEffects.svg"> | |||||
<defs | |||||
id="defs111788"> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient4525" | |||||
id="linearGradient4597" | |||||
gradientUnits="userSpaceOnUse" | |||||
x1="18" | |||||
y1="3" | |||||
x2="28" | |||||
y2="7" /> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
id="linearGradient4525"> | |||||
<stop | |||||
style="stop-color:#ffffff;stop-opacity:1" | |||||
offset="0" | |||||
id="stop4521" /> | |||||
<stop | |||||
style="stop-color:#ffffff;stop-opacity:0" | |||||
offset="1" | |||||
id="stop4523" /> | |||||
</linearGradient> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient4525" | |||||
id="linearGradient4599" | |||||
gradientUnits="userSpaceOnUse" | |||||
gradientTransform="matrix(-0.26458333,0,0,0.26458333,239.62085,123.23283)" | |||||
x1="18" | |||||
y1="3" | |||||
x2="28" | |||||
y2="7" /> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient4525" | |||||
id="linearGradient6334" | |||||
gradientUnits="userSpaceOnUse" | |||||
x1="18" | |||||
y1="3" | |||||
x2="28" | |||||
y2="7" /> | |||||
<linearGradient | |||||
inkscape:collect="always" | |||||
xlink:href="#linearGradient4525" | |||||
id="linearGradient6336" | |||||
gradientUnits="userSpaceOnUse" | |||||
gradientTransform="matrix(-0.26458333,0,0,0.26458333,239.62085,123.23283)" | |||||
x1="18" | |||||
y1="3" | |||||
x2="28" | |||||
y2="7" /> | |||||
</defs> | |||||
<sodipodi:namedview | |||||
id="base" | |||||
pagecolor="#ffffff" | |||||
bordercolor="#666666" | |||||
borderopacity="1.0" | |||||
inkscape:pageopacity="0.0" | |||||
inkscape:pageshadow="2" | |||||
inkscape:zoom="15.839192" | |||||
inkscape:cx="3.4976945" | |||||
inkscape:cy="14.902022" | |||||
inkscape:document-units="mm" | |||||
inkscape:current-layer="layer2" | |||||
showgrid="false" | |||||
fit-margin-top="0" | |||||
fit-margin-left="0" | |||||
fit-margin-right="0" | |||||
fit-margin-bottom="0" | |||||
inkscape:window-width="1920" | |||||
inkscape:window-height="1018" | |||||
inkscape:window-x="-8" | |||||
inkscape:window-y="-8" | |||||
inkscape:window-maximized="1" | |||||
inkscape:snap-object-midpoints="true" | |||||
inkscape:snap-global="false" | |||||
inkscape:snap-page="true" /> | |||||
<metadata | |||||
id="metadata111791"> | |||||
<rdf:RDF> | |||||
<cc:Work | |||||
rdf:about=""> | |||||
<dc:format>image/svg+xml</dc:format> | |||||
<dc:type | |||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||||
<dc:title /> | |||||
</cc:Work> | |||||
</rdf:RDF> | |||||
</metadata> | |||||
<g | |||||
inkscape:groupmode="layer" | |||||
id="layer2" | |||||
inkscape:label="effect" | |||||
style="display:inline"> | |||||
<g | |||||
style="display:inline" | |||||
transform="matrix(0.59476864,0.59476864,-0.59476864,0.59476864,-59.549442,-211.80376)" | |||||
id="g4555" | |||||
inkscape:transform-center-y="-1.7078435" | |||||
inkscape:transform-center-x="-1.7078442"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6739-1" | |||||
transform="matrix(0.26458333,0,0,0.26458333,230.09585,123.23283)" | |||||
d="m 18,2.1855469 v 1.4824219 a 14.33154,14.33154 0 0 1 10.414062,4.4960937 l 1.06836,-1.0097656 A 15.813972,15.813972 0 0 0 18,2.1855469 Z" | |||||
style="opacity:1;fill:url(#linearGradient6334);fill-opacity:1;stroke:none;stroke-width:0.43031731;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6739-1-5" | |||||
d="m 234.85835,123.81109 v 0.39222 a 3.7918866,3.7918866 0 0 0 -2.75539,1.1896 l -0.28267,-0.26717 a 4.1841134,4.1841134 0 0 1 3.03806,-1.31465 z" | |||||
style="opacity:1;fill:url(#linearGradient6336);fill-opacity:1;stroke:none;stroke-width:0.11385479;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
</g> | |||||
<g | |||||
style="display:inline" | |||||
transform="matrix(0.40131764,0.40131764,0.40131764,-0.40131764,-141.62056,-38.865482)" | |||||
id="g4555-4" | |||||
inkscape:transform-center-x="1.162122" | |||||
inkscape:transform-center-y="1.1621231"> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6739-1-9" | |||||
transform="matrix(0.26458333,0,0,0.26458333,230.09585,123.23283)" | |||||
d="m 18,2.1855469 v 1.4824219 a 14.33154,14.33154 0 0 1 10.414062,4.4960937 l 1.06836,-1.0097656 A 15.813972,15.813972 0 0 0 18,2.1855469 Z" | |||||
style="opacity:1;fill:url(#linearGradient4597);fill-opacity:1;stroke:none;stroke-width:0.43031731;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
<path | |||||
inkscape:connector-curvature="0" | |||||
id="path6739-1-5-4" | |||||
d="m 234.85835,123.81109 v 0.39222 a 3.7918866,3.7918866 0 0 0 -2.75539,1.1896 l -0.28267,-0.26717 a 4.1841134,4.1841134 0 0 1 3.03806,-1.31465 z" | |||||
style="opacity:1;fill:url(#linearGradient4599);fill-opacity:1;stroke:none;stroke-width:0.11385479;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> | |||||
</g> | |||||
<path | |||||
style="display:inline;opacity:1;fill:#b1b1b1;fill-opacity:1;stroke:none;stroke-width:0.05895557;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
d="M 4.0090051,4.0022733 5.7767909,2.7547849 A 2.1665909,2.1665909 0 0 0 5.2252031,2.2097076 Z" | |||||
id="path6739-0-1" | |||||
inkscape:connector-curvature="0" /> | |||||
<path | |||||
style="display:inline;opacity:1;fill:#3c3c3c;fill-opacity:1;stroke:none;stroke-width:0.05895557;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
d="m 4.0090038,4.0100952 0.34469,2.1302977 A 2.1665909,2.1665909 0 0 0 6.1397362,4.3847773 Z" | |||||
id="path6739-7" | |||||
inkscape:connector-curvature="0" /> | |||||
<path | |||||
style="display:inline;opacity:1;fill:#3c3c3c;fill-opacity:1;stroke:none;stroke-width:0.05895557;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
d="M 4.0090051,4.0099864 3.6643184,1.8796903 A 2.1665909,2.1665909 0 0 0 1.8782717,3.635305 Z" | |||||
id="path6739-7-1" | |||||
inkscape:connector-curvature="0" /> | |||||
<path | |||||
style="display:inline;opacity:1;fill:#b1b1b1;fill-opacity:1;stroke:none;stroke-width:0.05895557;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" | |||||
d="M 4.0090038,4.0100952 2.2412176,5.2575864 a 2.1665909,2.1665909 0 0 0 0.551591,0.5450712 z" | |||||
id="path6739-0" | |||||
inkscape:connector-curvature="0" /> | |||||
</g> | |||||
</svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15" height="14.999" viewBox="0 0 3.969 3.968"><defs><linearGradient id="a"><stop offset="0" stop-color="#272727"/><stop offset="1" stop-color="#eaeaea" stop-opacity=".928"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="32.122" y1="70.528" x2="32.122" y2="67.613" gradientUnits="userSpaceOnUse"/></defs><path d="M32.123 70.66a1.589 1.589 0 0 1-1.588-1.587 1.59 1.59 0 0 1 1.588-1.588 1.59 1.59 0 0 1 1.587 1.588c0 .875-.712 1.587-1.587 1.587" fill="url(#b)" stroke="#acacac" stroke-width=".265" transform="translate(-30.138 -67.088)"/><path d="M2.18 1.79h-.39V.956h.39z" fill="#262626"/><path d="M1.79 2.18H.956v-.39h.834zM3.013 2.18h-.834v-.39h.834z" fill="#4d4d4d"/><path d="M2.18 3.013h-.39v-.834h.39z" fill="#ccc"/><path fill="#4d4d4d" d="M1.79 1.79h.389v.389h-.39z"/></svg> |
@@ -0,0 +1,93 @@ | |||||
Copyright (c) 2009, Paul Flo Williams (paul@frixxon.co.uk). | |||||
This Font Software is licensed under the SIL Open Font License, Version 1.1. | |||||
This license is copied below, and is also available with a FAQ at: | |||||
http://scripts.sil.org/OFL | |||||
----------------------------------------------------------- | |||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |||||
----------------------------------------------------------- | |||||
PREAMBLE | |||||
The goals of the Open Font License (OFL) are to stimulate worldwide | |||||
development of collaborative font projects, to support the font creation | |||||
efforts of academic and linguistic communities, and to provide a free and | |||||
open framework in which fonts may be shared and improved in partnership | |||||
with others. | |||||
The OFL allows the licensed fonts to be used, studied, modified and | |||||
redistributed freely as long as they are not sold by themselves. The | |||||
fonts, including any derivative works, can be bundled, embedded, | |||||
redistributed and/or sold with any software provided that any reserved | |||||
names are not used by derivative works. The fonts and derivatives, | |||||
however, cannot be released under any other type of license. The | |||||
requirement for fonts to remain under this license does not apply | |||||
to any document created using the fonts or their derivatives. | |||||
DEFINITIONS | |||||
"Font Software" refers to the set of files released by the Copyright | |||||
Holder(s) under this license and clearly marked as such. This may | |||||
include source files, build scripts and documentation. | |||||
"Reserved Font Name" refers to any names specified as such after the | |||||
copyright statement(s). | |||||
"Original Version" refers to the collection of Font Software components as | |||||
distributed by the Copyright Holder(s). | |||||
"Modified Version" refers to any derivative made by adding to, deleting, | |||||
or substituting -- in part or in whole -- any of the components of the | |||||
Original Version, by changing formats or by porting the Font Software to a | |||||
new environment. | |||||
"Author" refers to any designer, engineer, programmer, technical | |||||
writer or other person who contributed to the Font Software. | |||||
PERMISSION & CONDITIONS | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of the Font Software, to use, study, copy, merge, embed, modify, | |||||
redistribute, and sell modified and unmodified copies of the Font | |||||
Software, subject to the following conditions: | |||||
1) Neither the Font Software nor any of its individual components, | |||||
in Original or Modified Versions, may be sold by itself. | |||||
2) Original or Modified Versions of the Font Software may be bundled, | |||||
redistributed and/or sold with any software, provided that each copy | |||||
contains the above copyright notice and this license. These can be | |||||
included either as stand-alone text files, human-readable headers or | |||||
in the appropriate machine-readable metadata fields within text or | |||||
binary files as long as those fields can be easily viewed by the user. | |||||
3) No Modified Version of the Font Software may use the Reserved Font | |||||
Name(s) unless explicit written permission is granted by the corresponding | |||||
Copyright Holder. This restriction only applies to the primary font name as | |||||
presented to the users. | |||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |||||
Software shall not be used to promote, endorse or advertise any | |||||
Modified Version, except to acknowledge the contribution(s) of the | |||||
Copyright Holder(s) and the Author(s) or with their explicit written | |||||
permission. | |||||
5) The Font Software, modified or unmodified, in part or in whole, | |||||
must be distributed entirely under this license, and must not be | |||||
distributed under any other license. The requirement for fonts to | |||||
remain under this license does not apply to any document created | |||||
using the Font Software. | |||||
TERMINATION | |||||
This license becomes null and void if any of the above conditions are | |||||
not met. | |||||
DISCLAIMER | |||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |||||
OTHER DEALINGS IN THE FONT SOFTWARE. |
@@ -0,0 +1,96 @@ | |||||
Copyright (c) 2008, Haley Fiege (haley@kingdomofawesome.com), | |||||
Copyright (c) 2012, Brenda Gallo (gbrenda1987@gmail.com) | |||||
Copyright (c) 2013, Pablo Impallari (www.impallari.com|impallari@gmail.com), | |||||
with no Reserved Font Name. | |||||
This Font Software is licensed under the SIL Open Font License, Version 1.1. | |||||
This license is copied below, and is also available with a FAQ at: | |||||
http://scripts.sil.org/OFL | |||||
----------------------------------------------------------- | |||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |||||
----------------------------------------------------------- | |||||
PREAMBLE | |||||
The goals of the Open Font License (OFL) are to stimulate worldwide | |||||
development of collaborative font projects, to support the font creation | |||||
efforts of academic and linguistic communities, and to provide a free and | |||||
open framework in which fonts may be shared and improved in partnership | |||||
with others. | |||||
The OFL allows the licensed fonts to be used, studied, modified and | |||||
redistributed freely as long as they are not sold by themselves. The | |||||
fonts, including any derivative works, can be bundled, embedded, | |||||
redistributed and/or sold with any software provided that any reserved | |||||
names are not used by derivative works. The fonts and derivatives, | |||||
however, cannot be released under any other type of license. The | |||||
requirement for fonts to remain under this license does not apply | |||||
to any document created using the fonts or their derivatives. | |||||
DEFINITIONS | |||||
"Font Software" refers to the set of files released by the Copyright | |||||
Holder(s) under this license and clearly marked as such. This may | |||||
include source files, build scripts and documentation. | |||||
"Reserved Font Name" refers to any names specified as such after the | |||||
copyright statement(s). | |||||
"Original Version" refers to the collection of Font Software components as | |||||
distributed by the Copyright Holder(s). | |||||
"Modified Version" refers to any derivative made by adding to, deleting, | |||||
or substituting -- in part or in whole -- any of the components of the | |||||
Original Version, by changing formats or by porting the Font Software to a | |||||
new environment. | |||||
"Author" refers to any designer, engineer, programmer, technical | |||||
writer or other person who contributed to the Font Software. | |||||
PERMISSION & CONDITIONS | |||||
Permission is hereby granted, free of charge, to any person obtaining | |||||
a copy of the Font Software, to use, study, copy, merge, embed, modify, | |||||
redistribute, and sell modified and unmodified copies of the Font | |||||
Software, subject to the following conditions: | |||||
1) Neither the Font Software nor any of its individual components, | |||||
in Original or Modified Versions, may be sold by itself. | |||||
2) Original or Modified Versions of the Font Software may be bundled, | |||||
redistributed and/or sold with any software, provided that each copy | |||||
contains the above copyright notice and this license. These can be | |||||
included either as stand-alone text files, human-readable headers or | |||||
in the appropriate machine-readable metadata fields within text or | |||||
binary files as long as those fields can be easily viewed by the user. | |||||
3) No Modified Version of the Font Software may use the Reserved Font | |||||
Name(s) unless explicit written permission is granted by the corresponding | |||||
Copyright Holder. This restriction only applies to the primary font name as | |||||
presented to the users. | |||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |||||
Software shall not be used to promote, endorse or advertise any | |||||
Modified Version, except to acknowledge the contribution(s) of the | |||||
Copyright Holder(s) and the Author(s) or with their explicit written | |||||
permission. | |||||
5) The Font Software, modified or unmodified, in part or in whole, | |||||
must be distributed entirely under this license, and must not be | |||||
distributed under any other license. The requirement for fonts to | |||||
remain under this license does not apply to any document created | |||||
using the Font Software. | |||||
TERMINATION | |||||
This license becomes null and void if any of the above conditions are | |||||
not met. | |||||
DISCLAIMER | |||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |||||
OTHER DEALINGS IN THE FONT SOFTWARE. |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36.002" viewBox="0 0 9.525 9.525"><path d="M9.423 4.267c-.207-.333-.552-.66-.745-1.126-.193-.465-.18-.94-.269-1.32a4.703 4.703 0 0 0-.706-.705c-.38-.088-.854-.076-1.318-.268C5.918.654 5.59.308 5.258.103a4.705 4.705 0 0 0-.99 0c-.334.206-.661.55-1.128.745-.464.192-.939.18-1.318.268a4.701 4.701 0 0 0-.706.705c-.089.38-.077.855-.27 1.32-.191.466-.538.793-.744 1.126a4.705 4.705 0 0 0 0 .991c.206.333.552.66.744 1.126.193.465.181.94.27 1.319.21.26.446.497.706.706.38.089.854.076 1.318.269.467.193.794.54 1.127.745a4.78 4.78 0 0 0 .991 0c.333-.207.66-.551 1.126-.745.465-.193.94-.18 1.32-.269.26-.21.495-.445.705-.705.089-.38.076-.855.269-1.319.193-.466.539-.794.745-1.127a4.805 4.805 0 0 0 0-.99" fill-rule="evenodd" stroke="#787878" stroke-width=".153" stroke-linecap="round" stroke-linejoin="round"/><circle r="3.732" cy="4.762" cx="4.728" fill="#464646" stroke="#595959" stroke-width=".103"/></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 9.525 9.525"><path d="M9.423 4.267c-.207-.334-.552-.66-.745-1.127-.193-.464-.18-.94-.269-1.318a4.703 4.703 0 0 0-.705-.706c-.38-.089-.854-.077-1.319-.269C5.919.653 5.591.308 5.258.102a4.705 4.705 0 0 0-.99 0C3.934.31 3.606.653 3.14.847c-.465.192-.94.18-1.32.269a4.701 4.701 0 0 0-.705.705c-.088.38-.076.855-.27 1.32-.191.466-.538.792-.743 1.126a4.705 4.705 0 0 0 0 .991c.205.333.55.66.744 1.126.193.464.18.94.27 1.319.209.26.445.497.705.706.38.089.854.076 1.319.269.466.193.794.54 1.126.745a4.78 4.78 0 0 0 .991 0c.333-.207.66-.551 1.126-.745.466-.193.94-.18 1.32-.269a4.7 4.7 0 0 0 .705-.705c.089-.38.076-.856.269-1.32.193-.466.54-.793.745-1.126a4.805 4.805 0 0 0 0-.99" fill-rule="evenodd" stroke="#787878" stroke-width=".153" stroke-linecap="round" stroke-linejoin="round"/><ellipse cy="4.762" cx="4.728" rx="3.732" ry="3.732" fill="#464646" stroke="#595959" stroke-width=".103"/><rect ry=".263" y=".068" x="4.468" height="3.91" width=".577" rx=".263" fill="#e7e7e7" stroke="#2b2b2b" stroke-width=".133" stroke-linejoin="round"/></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 6.879 6.879"><g transform="matrix(.2727 0 0 -.2727 -74.931 327.683)" stroke-width="1.294"><path d="M299.995 1188.983c0-6.965-5.649-12.612-12.614-12.612-6.964 0-12.612 5.647-12.612 12.612 0 6.968 5.648 12.614 12.612 12.614 6.965 0 12.614-5.646 12.614-12.614" fill="#212221"/><ellipse cx="287.382" cy="-1189.64" transform="scale(1 -1)" rx="10.472" ry="10.473" fill="#676967"/><ellipse transform="scale(1 -1)" cy="-1188.984" cx="287.382" rx="10.472" ry="10.473" fill="#2b2d2b"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 6.879 6.879"><g transform="matrix(.2727 0 0 -.2727 -74.931 327.683)" stroke-width="1.294"><path d="M299.995 1188.983c0-6.965-5.649-12.612-12.614-12.612-6.964 0-12.612 5.647-12.612 12.612 0 6.968 5.648 12.614 12.612 12.614 6.965 0 12.614-5.646 12.614-12.614" fill="#212221"/><ellipse transform="scale(1 -1)" cy="-1188.321" cx="287.382" rx="10.472" ry="10.473" fill="#676967"/><ellipse cx="287.382" cy="-1188.984" transform="scale(1 -1)" rx="10.472" ry="10.473" fill="#141514"/></g></svg> |
@@ -0,0 +1 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 6.35 6.35"><path d="M6.268 2.845c-.137-.221-.367-.438-.495-.748-.128-.308-.12-.624-.178-.875a3.123 3.123 0 0 0-.469-.469C4.874.694 4.56.703 4.251.575 3.94.446 3.723.217 3.503.08a3.124 3.124 0 0 0-.658 0c-.221.137-.439.366-.749.495-.308.127-.623.12-.875.178a3.121 3.121 0 0 0-.469.468c-.058.252-.05.568-.179.876-.127.31-.357.527-.494.748a3.124 3.124 0 0 0 0 .658c.137.221.366.438.494.748.128.308.12.624.18.875.139.173.295.33.468.47.252.058.567.05.875.178.31.128.528.358.749.494a3.174 3.174 0 0 0 .658 0c.22-.137.437-.365.747-.494.31-.128.624-.12.876-.179.173-.139.33-.296.469-.468.059-.252.05-.568.178-.876.128-.31.358-.527.495-.748a3.19 3.19 0 0 0 0-.658" fill-rule="evenodd" stroke="#787878" stroke-width=".127" stroke-linecap="round" stroke-linejoin="round"/><circle r="2.478" cy="3.174" cx="3.15" fill="#464646" stroke="#595959" stroke-width=".086"/><rect ry=".263" y=".068" x="2.97" height="2.596" width=".368" rx=".263" fill="#e7e7e7" stroke="#2b2b2b" stroke-width=".133" stroke-linejoin="round"/></svg> |
@@ -0,0 +1,681 @@ | |||||
//*********************************************************************************************** | |||||
//Six channel 32-step sequencer module for VCV Rack by Marc Boulé | |||||
// | |||||
//Based on code from the Fundamental and AudibleInstruments plugins by Andrew Belt | |||||
//and graphics from the Component Library by Wes Milholen | |||||
//See ./LICENSE.txt for all licenses | |||||
//See ./res/fonts/ for font licenses | |||||
// | |||||
//Based on the BigButton sequencer by Look-Mum-No-Computer | |||||
//https://www.youtube.com/watch?v=6ArDGcUqiWM | |||||
//https://www.lookmumnocomputer.com/projects/#/big-button/ | |||||
// | |||||
//*********************************************************************************************** | |||||
#include "ImpromptuModular.hpp" | |||||
#include "dsp/digital.hpp" | |||||
namespace rack_plugin_ImpromptuModular { | |||||
struct BigButtonSeq : Module { | |||||
enum ParamIds { | |||||
CHAN_PARAM, | |||||
LEN_PARAM, | |||||
RND_PARAM, | |||||
RESET_PARAM, | |||||
CLEAR_PARAM, | |||||
BANK_PARAM, | |||||
DEL_PARAM, | |||||
FILL_PARAM, | |||||
BIG_PARAM, | |||||
NUM_PARAMS | |||||
}; | |||||
enum InputIds { | |||||
CLK_INPUT, | |||||
CHAN_INPUT, | |||||
BIG_INPUT, | |||||
LEN_INPUT, | |||||
RND_INPUT, | |||||
RESET_INPUT, | |||||
CLEAR_INPUT, | |||||
BANK_INPUT, | |||||
DEL_INPUT, | |||||
FILL_INPUT, | |||||
NUM_INPUTS | |||||
}; | |||||
enum OutputIds { | |||||
ENUMS(CHAN_OUTPUTS, 6), | |||||
NUM_OUTPUTS | |||||
}; | |||||
enum LightIds { | |||||
ENUMS(CHAN_LIGHTS, 6 * 2),// Room for GreenRed | |||||
BIG_LIGHT, | |||||
BIGC_LIGHT, | |||||
ENUMS(METRONOME_LIGHT, 2),// Room for GreenRed | |||||
NUM_LIGHTS | |||||
}; | |||||
// Constants | |||||
static constexpr float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) | |||||
// Need to save, with reset | |||||
int indexStep; | |||||
int bank[6]; | |||||
uint64_t gates[6][2];// chan , bank | |||||
// Need to save, no reset | |||||
int panelTheme; | |||||
int metronomeDiv; | |||||
bool writeFillsToMemory; | |||||
// No need to save, with reset | |||||
// none | |||||
// No need to save, no reset | |||||
bool scheduledReset; | |||||
int len; | |||||
long clockIgnoreOnReset; | |||||
double lastPeriod;//2.0 when not seen yet (init or stopped clock and went greater than 2s, which is max period supported for time-snap) | |||||
double clockTime;//clock time counter (time since last clock) | |||||
int pendingOp;// 0 means nothing pending, +1 means pending big button push, -1 means pending del | |||||
float bigLight; | |||||
float metronomeLightStart; | |||||
float metronomeLightDiv; | |||||
SchmittTrigger clockTrigger; | |||||
SchmittTrigger resetTrigger; | |||||
SchmittTrigger bankTrigger; | |||||
SchmittTrigger bigTrigger; | |||||
PulseGenerator outPulse; | |||||
PulseGenerator outLightPulse; | |||||
PulseGenerator bigPulse; | |||||
PulseGenerator bigLightPulse; | |||||
inline void toggleGate(int chan) {gates[chan][bank[chan]] ^= (((uint64_t)1) << (uint64_t)indexStep);} | |||||
inline void setGate(int chan) {gates[chan][bank[chan]] |= (((uint64_t)1) << (uint64_t)indexStep);} | |||||
inline void clearGate(int chan) {gates[chan][bank[chan]] &= ~(((uint64_t)1) << (uint64_t)indexStep);} | |||||
inline bool getGate(int chan) {return !((gates[chan][bank[chan]] & (((uint64_t)1) << (uint64_t)indexStep)) == 0);} | |||||
BigButtonSeq() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||||
// Need to save, no reset | |||||
panelTheme = 0; | |||||
metronomeDiv = 4; | |||||
writeFillsToMemory = false; | |||||
// No need to save, no reset | |||||
scheduledReset = false; | |||||
len = 0; | |||||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||||
lastPeriod = 2.0; | |||||
clockTime = 0.0; | |||||
pendingOp = 0; | |||||
bigLight = 0.0f; | |||||
metronomeLightStart = 0.0f; | |||||
metronomeLightDiv = 0.0f; | |||||
clockTrigger.reset(); | |||||
resetTrigger.reset(); | |||||
bankTrigger.reset(); | |||||
bigTrigger.reset(); | |||||
outPulse.reset(); | |||||
outLightPulse.reset(); | |||||
bigPulse.reset(); | |||||
bigLightPulse.reset(); | |||||
onReset(); | |||||
} | |||||
// widgets are not yet created when module is created | |||||
// even if widgets not created yet, can use params[] and should handle 0.0f value since step may call | |||||
// this before widget creation anyways | |||||
// called from the main thread if by constructor, called by engine thread if right-click initialization | |||||
// when called by constructor, module is created before the first step() is called | |||||
void onReset() override { | |||||
// Need to save, with reset | |||||
indexStep = 0; | |||||
for (int c = 0; c < 6; c++) { | |||||
bank[c] = 0; | |||||
gates[c][0] = 0; | |||||
gates[c][1] = 0; | |||||
} | |||||
// No need to save, with reset | |||||
// none | |||||
scheduledReset = true; | |||||
} | |||||
// widgets randomized before onRandomize() is called | |||||
// called by engine thread if right-click randomize | |||||
void onRandomize() override { | |||||
// Need to save, with reset | |||||
indexStep = randomu32() % 32; | |||||
for (int c = 0; c < 6; c++) { | |||||
bank[c] = randomu32() % 2; | |||||
gates[c][0] = randomu64(); | |||||
gates[c][1] = randomu64(); | |||||
} | |||||
// No need to save, with reset | |||||
// none | |||||
scheduledReset = true; | |||||
} | |||||
// called by main thread | |||||
json_t *toJson() override { | |||||
json_t *rootJ = json_object(); | |||||
// Need to save (reset or not) | |||||
// indexStep | |||||
json_object_set_new(rootJ, "indexStep", json_integer(indexStep)); | |||||
// bank | |||||
json_t *bankJ = json_array(); | |||||
for (int c = 0; c < 6; c++) | |||||
json_array_insert_new(bankJ, c, json_integer(bank[c])); | |||||
json_object_set_new(rootJ, "bank", bankJ); | |||||
// gates | |||||
json_t *gatesJ = json_array(); | |||||
for (int c = 0; c < 6; c++) | |||||
for (int b = 0; b < 8; b++) {// bank to store is like to uint64_t to store, so go to 8 | |||||
// first to get stored is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1 | |||||
unsigned int intValue = (unsigned int) ( (uint64_t)0xFFFF & (gates[c][b/4] >> (uint64_t)(16 * (b % 4))) ); | |||||
json_array_insert_new(gatesJ, b + (c << 3) , json_integer(intValue)); | |||||
} | |||||
json_object_set_new(rootJ, "gates", gatesJ); | |||||
// panelTheme | |||||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||||
// metronomeDiv | |||||
json_object_set_new(rootJ, "metronomeDiv", json_integer(metronomeDiv)); | |||||
// writeFillsToMemory | |||||
json_object_set_new(rootJ, "writeFillsToMemory", json_boolean(writeFillsToMemory)); | |||||
return rootJ; | |||||
} | |||||
// widgets have their fromJson() called before this fromJson() is called | |||||
// called by main thread | |||||
void fromJson(json_t *rootJ) override { | |||||
// Need to save (reset or not) | |||||
// indexStep | |||||
json_t *indexStepJ = json_object_get(rootJ, "indexStep"); | |||||
if (indexStepJ) | |||||
indexStep = json_integer_value(indexStepJ); | |||||
// bank | |||||
json_t *bankJ = json_object_get(rootJ, "bank"); | |||||
if (bankJ) | |||||
for (int c = 0; c < 6; c++) | |||||
{ | |||||
json_t *bankArrayJ = json_array_get(bankJ, c); | |||||
if (bankArrayJ) | |||||
bank[c] = json_integer_value(bankArrayJ); | |||||
} | |||||
// gates | |||||
json_t *gatesJ = json_object_get(rootJ, "gates"); | |||||
uint64_t bank8ints[8] = {0,0,0,0,0,0,0,0}; | |||||
if (gatesJ) { | |||||
for (int c = 0; c < 6; c++) { | |||||
for (int b = 0; b < 8; b++) {// bank to store is like to uint64_t to store, so go to 8 | |||||
// first to get read is 16 lsbits of bank 0, then next 16 bits,... to 16 msbits of bank 1 | |||||
json_t *gateJ = json_array_get(gatesJ, b + (c << 3)); | |||||
if (gateJ) | |||||
bank8ints[b] = (uint64_t) json_integer_value(gateJ); | |||||
} | |||||
gates[c][0] = bank8ints[0] | (bank8ints[1] << (uint64_t)16) | (bank8ints[2] << (uint64_t)32) | (bank8ints[3] << (uint64_t)48); | |||||
gates[c][1] = bank8ints[4] | (bank8ints[5] << (uint64_t)16) | (bank8ints[6] << (uint64_t)32) | (bank8ints[7] << (uint64_t)48); | |||||
} | |||||
} | |||||
// panelTheme | |||||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||||
if (panelThemeJ) | |||||
panelTheme = json_integer_value(panelThemeJ); | |||||
// metronomeDiv | |||||
json_t *metronomeDivJ = json_object_get(rootJ, "metronomeDiv"); | |||||
if (metronomeDivJ) | |||||
metronomeDiv = json_integer_value(metronomeDivJ); | |||||
// writeFillsToMemory | |||||
json_t *writeFillsToMemoryJ = json_object_get(rootJ, "writeFillsToMemory"); | |||||
if (writeFillsToMemoryJ) | |||||
writeFillsToMemory = json_is_true(writeFillsToMemoryJ); | |||||
// No need to save, with reset | |||||
// none | |||||
scheduledReset = true; | |||||
} | |||||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||||
void step() override { | |||||
double sampleTime = 1.0 / engineGetSampleRate(); | |||||
static const float lightTime = 0.1f; | |||||
// Scheduled reset (just the parts that do not have a place below in rest of function) | |||||
if (scheduledReset) { | |||||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||||
lastPeriod = 2.0; | |||||
clockTime = 0.0; | |||||
pendingOp = 0; | |||||
bigLight = 0.0f; | |||||
metronomeLightStart = 0.0f; | |||||
metronomeLightDiv = 0.0f; | |||||
clockTrigger.reset(); | |||||
resetTrigger.reset(); | |||||
bankTrigger.reset(); | |||||
bigTrigger.reset(); | |||||
outPulse.reset(); | |||||
outLightPulse.reset(); | |||||
bigPulse.reset(); | |||||
bigLightPulse.reset(); | |||||
} | |||||
//********** Buttons, knobs, switches and inputs ********** | |||||
// Length | |||||
len = (int) clamp(roundf( params[LEN_PARAM].value + ( inputs[LEN_INPUT].active ? (inputs[LEN_INPUT].value / 10.0f * (64.0f - 1.0f)) : 0.0f ) ), 0.0f, (64.0f - 1.0f)) + 1; | |||||
// Chan | |||||
float chanInputValue = inputs[CHAN_INPUT].value / 10.0f * (6.0f - 1.0f); | |||||
int chan = (int) clamp(roundf(params[CHAN_PARAM].value + chanInputValue), 0.0f, (6.0f - 1.0f)); | |||||
// Big button | |||||
if (bigTrigger.process(params[BIG_PARAM].value + inputs[BIG_INPUT].value)) { | |||||
bigLight = 1.0f; | |||||
if (clockTime > (lastPeriod / 2.0) && clockTime <= (lastPeriod * 1.01))// allow for 1% clock jitter | |||||
pendingOp = 1; | |||||
else { | |||||
if (!getGate(chan)) { | |||||
setGate(chan);// bank and indexStep are global | |||||
bigPulse.trigger(0.001f); | |||||
bigLightPulse.trigger(lightTime); | |||||
} | |||||
} | |||||
} | |||||
// Bank button | |||||
if (bankTrigger.process(params[BANK_PARAM].value + inputs[BANK_INPUT].value)) | |||||
bank[chan] = 1 - bank[chan]; | |||||
// Clear button | |||||
if (params[CLEAR_PARAM].value + inputs[CLEAR_INPUT].value > 0.5f) | |||||
gates[chan][bank[chan]] = 0; | |||||
// Del button | |||||
if (params[DEL_PARAM].value + inputs[DEL_INPUT].value > 0.5f) { | |||||
if (clockTime > (lastPeriod / 2.0) && clockTime <= (lastPeriod * 1.01))// allow for 1% clock jitter | |||||
pendingOp = -1;// overrides the pending write if it exists | |||||
else | |||||
clearGate(chan);// bank and indexStep are global | |||||
} | |||||
// Fill button | |||||
bool fillPressed = (params[FILL_PARAM].value + inputs[FILL_INPUT].value) > 0.5f; | |||||
if (fillPressed && writeFillsToMemory) | |||||
setGate(chan);// bank and indexStep are global | |||||
// Pending timeout (write/del current step) | |||||
if (pendingOp != 0 && clockTime > (lastPeriod * 1.01) ) | |||||
performPending(chan, lightTime); | |||||
//********** Clock and reset ********** | |||||
// Clock | |||||
if (clockTrigger.process(inputs[CLK_INPUT].value)) { | |||||
if (clockIgnoreOnReset == 0l) { | |||||
outPulse.trigger(0.001f); | |||||
outLightPulse.trigger(lightTime); | |||||
indexStep = moveIndex(indexStep, indexStep + 1, len); | |||||
if (pendingOp != 0) | |||||
performPending(chan, lightTime);// Proper pending write/del to next step which is now reached | |||||
if (indexStep == 0) | |||||
metronomeLightStart = 1.0f; | |||||
metronomeLightDiv = ((indexStep % metronomeDiv) == 0 && indexStep != 0) ? 1.0f : 0.0f; | |||||
// Random (toggle gate according to probability knob) | |||||
float rnd01 = params[RND_PARAM].value / 100.0f + inputs[RND_INPUT].value / 10.0f; | |||||
if (rnd01 > 0.0f) { | |||||
if (randomUniform() < rnd01)// randomUniform is [0.0, 1.0), see include/util/common.hpp | |||||
toggleGate(chan); | |||||
} | |||||
lastPeriod = clockTime > 2.0 ? 2.0 : clockTime; | |||||
clockTime = 0.0; | |||||
} | |||||
} | |||||
// Reset | |||||
if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) { | |||||
indexStep = 0; | |||||
outPulse.trigger(0.001f); | |||||
outLightPulse.trigger(0.02f); | |||||
metronomeLightStart = 1.0f; | |||||
metronomeLightDiv = 0.0f; | |||||
clockTrigger.reset(); | |||||
clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); | |||||
} | |||||
//********** Outputs and lights ********** | |||||
bool bigPulseState = bigPulse.process((float)sampleTime); | |||||
bool bigLightPulseState = bigLightPulse.process((float)sampleTime); | |||||
bool outPulseState = outPulse.process((float)sampleTime); | |||||
bool outLightPulseState = outLightPulse.process((float)sampleTime); | |||||
// Gate and light outputs | |||||
for (int i = 0; i < 6; i++) { | |||||
bool gate = getGate(i); | |||||
bool outSignal = (((gate || (i == chan && fillPressed)) && outPulseState) || (gate && bigPulseState && i == chan)); | |||||
bool outLight = (((gate || (i == chan && fillPressed)) && outLightPulseState) || (gate && bigLightPulseState && i == chan)); | |||||
outputs[CHAN_OUTPUTS + i].value = outSignal ? 10.0f : 0.0f; | |||||
lights[(CHAN_LIGHTS + i) * 2 + 1].setBrightnessSmooth(outLight ? 1.0f : 0.0f); | |||||
lights[(CHAN_LIGHTS + i) * 2 + 0].setBrightnessSmooth(i == chan ? (1.0f - lights[(CHAN_LIGHTS + i) * 2 + 1].value) / 2.0f : 0.0f); | |||||
} | |||||
// Big button lights | |||||
lights[BIG_LIGHT].value = bank[chan] == 1 ? 1.0f : 0.0f; | |||||
lights[BIGC_LIGHT].value = bigLight; | |||||
// Metronome light | |||||
lights[METRONOME_LIGHT + 1].value = metronomeLightStart; | |||||
lights[METRONOME_LIGHT + 0].value = metronomeLightDiv; | |||||
if (clockIgnoreOnReset > 0l) | |||||
clockIgnoreOnReset--; | |||||
bigLight -= (bigLight / lightLambda) * (float)sampleTime; | |||||
metronomeLightStart -= (metronomeLightStart / lightLambda) * (float)sampleTime; | |||||
metronomeLightDiv -= (metronomeLightDiv / lightLambda) * (float)sampleTime; | |||||
clockTime += sampleTime; | |||||
scheduledReset = false; | |||||
}// step() | |||||
inline void performPending(int chan, float lightTime) { | |||||
if (pendingOp == 1) { | |||||
if (!getGate(chan)) { | |||||
setGate(chan);// bank and indexStep are global | |||||
bigPulse.trigger(0.001f); | |||||
bigLightPulse.trigger(lightTime); | |||||
} | |||||
} | |||||
else { | |||||
clearGate(chan);// bank and indexStep are global | |||||
} | |||||
pendingOp = 0; | |||||
} | |||||
}; | |||||
struct BigButtonSeqWidget : ModuleWidget { | |||||
struct StepsDisplayWidget : TransparentWidget { | |||||
int *len; | |||||
std::shared_ptr<Font> font; | |||||
StepsDisplayWidget() { | |||||
font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); | |||||
} | |||||
void draw(NVGcontext *vg) override { | |||||
NVGcolor textColor = prepareDisplay(vg, &box); | |||||
nvgFontFaceId(vg, font->handle); | |||||
//nvgTextLetterSpacing(vg, 2.5); | |||||
Vec textPos = Vec(6, 24); | |||||
nvgFillColor(vg, nvgTransRGBA(textColor, 16)); | |||||
nvgText(vg, textPos.x, textPos.y, "~~", NULL); | |||||
nvgFillColor(vg, textColor); | |||||
char displayStr[3]; | |||||
snprintf(displayStr, 3, "%2u", (unsigned) *len ); | |||||
nvgText(vg, textPos.x, textPos.y, displayStr, NULL); | |||||
} | |||||
}; | |||||
struct PanelThemeItem : MenuItem { | |||||
BigButtonSeq *module; | |||||
int theme; | |||||
void onAction(EventAction &e) override { | |||||
module->panelTheme = theme; | |||||
} | |||||
void step() override { | |||||
rightText = (module->panelTheme == theme) ? "✔" : ""; | |||||
} | |||||
}; | |||||
struct MetronomeItem : MenuItem { | |||||
BigButtonSeq *module; | |||||
int div; | |||||
void onAction(EventAction &e) override { | |||||
module->metronomeDiv = div; | |||||
} | |||||
void step() override { | |||||
rightText = (module->metronomeDiv == div) ? "✔" : ""; | |||||
} | |||||
}; | |||||
struct FillModeItem : MenuItem { | |||||
BigButtonSeq *module; | |||||
void onAction(EventAction &e) override { | |||||
module->writeFillsToMemory = !module->writeFillsToMemory; | |||||
} | |||||
}; | |||||
Menu *createContextMenu() override { | |||||
Menu *menu = ModuleWidget::createContextMenu(); | |||||
MenuLabel *spacerLabel = new MenuLabel(); | |||||
menu->addChild(spacerLabel); | |||||
BigButtonSeq *module = dynamic_cast<BigButtonSeq*>(this->module); | |||||
assert(module); | |||||
MenuLabel *themeLabel = new MenuLabel(); | |||||
themeLabel->text = "Panel Theme"; | |||||
menu->addChild(themeLabel); | |||||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||||
lightItem->text = lightPanelID;// ImpromptuModular.hpp | |||||
lightItem->module = module; | |||||
lightItem->theme = 0; | |||||
menu->addChild(lightItem); | |||||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||||
std::string hotRodLabel = " Hot-rod"; | |||||
hotRodLabel.insert(0, darkPanelID);// ImpromptuModular.hpp | |||||
darkItem->text = hotRodLabel; | |||||
darkItem->module = module; | |||||
darkItem->theme = 1; | |||||
menu->addChild(darkItem); | |||||
menu->addChild(new MenuLabel());// empty line | |||||
MenuLabel *settingsLabel = new MenuLabel(); | |||||
settingsLabel->text = "Settings"; | |||||
menu->addChild(settingsLabel); | |||||
FillModeItem *fillItem = MenuItem::create<FillModeItem>("Write Fills to Memory", CHECKMARK(module->writeFillsToMemory)); | |||||
fillItem->module = module; | |||||
menu->addChild(fillItem); | |||||
menu->addChild(new MenuLabel());// empty line | |||||
MenuLabel *metronomeLabel = new MenuLabel(); | |||||
metronomeLabel->text = "Metronome light"; | |||||
menu->addChild(metronomeLabel); | |||||
MetronomeItem *met1Item = MenuItem::create<MetronomeItem>("Every clock", CHECKMARK(module->metronomeDiv == 1)); | |||||
met1Item->module = module; | |||||
met1Item->div = 1; | |||||
menu->addChild(met1Item); | |||||
MetronomeItem *met2Item = MenuItem::create<MetronomeItem>("/2", CHECKMARK(module->metronomeDiv == 2)); | |||||
met2Item->module = module; | |||||
met2Item->div = 2; | |||||
menu->addChild(met2Item); | |||||
MetronomeItem *met4Item = MenuItem::create<MetronomeItem>("/4", CHECKMARK(module->metronomeDiv == 4)); | |||||
met4Item->module = module; | |||||
met4Item->div = 4; | |||||
menu->addChild(met4Item); | |||||
MetronomeItem *met8Item = MenuItem::create<MetronomeItem>("/8", CHECKMARK(module->metronomeDiv == 8)); | |||||
met8Item->module = module; | |||||
met8Item->div = 8; | |||||
menu->addChild(met8Item); | |||||
MetronomeItem *met1000Item = MenuItem::create<MetronomeItem>("Full length", CHECKMARK(module->metronomeDiv == 1000)); | |||||
met1000Item->module = module; | |||||
met1000Item->div = 1000; | |||||
menu->addChild(met1000Item); | |||||
return menu; | |||||
} | |||||
BigButtonSeqWidget(BigButtonSeq *module) : ModuleWidget(module) { | |||||
// Main panel from Inkscape | |||||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BigButtonSeq.svg"))); | |||||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/BigButtonSeq_dark.svg"))); | |||||
box.size = panel->box.size; | |||||
panel->mode = &module->panelTheme; | |||||
addChild(panel); | |||||
// Screws | |||||
addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme)); | |||||
addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme)); | |||||
addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme)); | |||||
addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme)); | |||||
// Column rulers (horizontal positions) | |||||
static const int rowRuler0 = 49;// outputs and leds | |||||
static const int colRulerCenter = 115;// not real center, but pos so that a jack would be centered | |||||
static const int offsetChanOutX = 20; | |||||
static const int colRulerT0 = colRulerCenter - offsetChanOutX * 5; | |||||
static const int colRulerT1 = colRulerCenter - offsetChanOutX * 3; | |||||
static const int colRulerT2 = colRulerCenter - offsetChanOutX * 1; | |||||
static const int colRulerT3 = colRulerCenter + offsetChanOutX * 1; | |||||
static const int colRulerT4 = colRulerCenter + offsetChanOutX * 3; | |||||
static const int colRulerT5 = colRulerCenter + offsetChanOutX * 5; | |||||
static const int ledOffsetY = 28; | |||||
// Outputs | |||||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT0, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 0, &module->panelTheme)); | |||||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT1, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 1, &module->panelTheme)); | |||||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT2, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 2, &module->panelTheme)); | |||||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT3, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 3, &module->panelTheme)); | |||||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT4, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 4, &module->panelTheme)); | |||||
addOutput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 5, &module->panelTheme)); | |||||
// LEDs | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerT0 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 0)); | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerT1 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 2)); | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerT2 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 4)); | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerT3 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 6)); | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerT4 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 8)); | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerT5 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 10)); | |||||
static const int rowRuler1 = rowRuler0 + 72;// clk, chan and big CV | |||||
static const int knobCVjackOffsetX = 52; | |||||
// Clock input | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerT0, rowRuler1), Port::INPUT, module, BigButtonSeq::CLK_INPUT, &module->panelTheme)); | |||||
// Chan knob and jack | |||||
addParam(createDynamicParam<IMSixPosBigKnob>(Vec(colRulerCenter + offsetIMBigKnob, rowRuler1 + offsetIMBigKnob), module, BigButtonSeq::CHAN_PARAM, 0.0f, 6.0f - 1.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - knobCVjackOffsetX, rowRuler1), Port::INPUT, module, BigButtonSeq::CHAN_INPUT, &module->panelTheme)); | |||||
// Length display | |||||
StepsDisplayWidget *displaySteps = new StepsDisplayWidget(); | |||||
displaySteps->box.pos = Vec(colRulerT5 - 17, rowRuler1 + vOffsetDisplay - 1); | |||||
displaySteps->box.size = Vec(40, 30);// 2 characters | |||||
displaySteps->len = &module->len; | |||||
addChild(displaySteps); | |||||
static const int rowRuler2 = rowRuler1 + 50;// len and rnd | |||||
static const int lenAndRndKnobOffsetX = 90; | |||||
// Len knob and jack | |||||
addParam(createDynamicParam<IMBigSnapKnob>(Vec(colRulerCenter - lenAndRndKnobOffsetX + offsetIMBigKnob, rowRuler2 + offsetIMBigKnob), module, BigButtonSeq::LEN_PARAM, 0.0f, 64.0f - 1.0f, 32.0f - 1.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - lenAndRndKnobOffsetX + knobCVjackOffsetX, rowRuler2), Port::INPUT, module, BigButtonSeq::LEN_INPUT, &module->panelTheme)); | |||||
// Rnd knob and jack | |||||
addParam(createDynamicParam<IMBigSnapKnob>(Vec(colRulerCenter + lenAndRndKnobOffsetX + offsetIMBigKnob, rowRuler2 + offsetIMBigKnob), module, BigButtonSeq::RND_PARAM, 0.0f, 100.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter + lenAndRndKnobOffsetX - knobCVjackOffsetX, rowRuler2), Port::INPUT, module, BigButtonSeq::RND_INPUT, &module->panelTheme)); | |||||
static const int rowRuler3 = rowRuler2 + 35;// bank | |||||
static const int rowRuler4 = rowRuler3 + 22;// clear and del | |||||
static const int rowRuler5 = rowRuler4 + 52;// reset and fill | |||||
static const int clearAndDelButtonOffsetX = (colRulerCenter - colRulerT0) / 2 + 8; | |||||
static const int knobCVjackOffsetY = 40; | |||||
// Bank button and jack | |||||
addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerCenter + offsetCKD6b, rowRuler3 + offsetCKD6b), module, BigButtonSeq::BANK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter, rowRuler3 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::BANK_INPUT, &module->panelTheme)); | |||||
// Clear button and jack | |||||
addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerCenter - clearAndDelButtonOffsetX + offsetCKD6b, rowRuler4 + offsetCKD6b), module, BigButtonSeq::CLEAR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::CLEAR_INPUT, &module->panelTheme)); | |||||
// Del button and jack | |||||
addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerCenter + clearAndDelButtonOffsetX + offsetCKD6b, rowRuler4 + offsetCKD6b), module, BigButtonSeq::DEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::DEL_INPUT, &module->panelTheme)); | |||||
// Reset button and jack | |||||
addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerT0 + offsetCKD6b, rowRuler5 + offsetCKD6b), module, BigButtonSeq::RESET_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerT0, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::RESET_INPUT, &module->panelTheme)); | |||||
// Fill button and jack | |||||
addParam(createDynamicParam<IMBigPushButton>(Vec(colRulerT5 + offsetCKD6b, rowRuler5 + offsetCKD6b), module, BigButtonSeq::FILL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerT5, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::FILL_INPUT, &module->panelTheme)); | |||||
// And now time for... BIG BUTTON! | |||||
addChild(ModuleLightWidget::create<GiantLight<RedLight>>(Vec(colRulerCenter + offsetLEDbezelBig - offsetLEDbezelLight*2.0f, rowRuler5 + 26 + offsetLEDbezelBig - offsetLEDbezelLight*2.0f), module, BigButtonSeq::BIG_LIGHT)); | |||||
addParam(ParamWidget::create<LEDBezelBig>(Vec(colRulerCenter + offsetLEDbezelBig, rowRuler5 + 26 + offsetLEDbezelBig), module, BigButtonSeq::BIG_PARAM, 0.0f, 1.0f, 0.0f)); | |||||
addChild(ModuleLightWidget::create<GiantLight2<RedLight>>(Vec(colRulerCenter + offsetLEDbezelBig - offsetLEDbezelLight*2.0f + 9, rowRuler5 + 26 + offsetLEDbezelBig - offsetLEDbezelLight*2.0f + 9), module, BigButtonSeq::BIGC_LIGHT)); | |||||
// Big input | |||||
addInput(createDynamicPort<IMPort>(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::BIG_INPUT, &module->panelTheme)); | |||||
// Metronome light | |||||
addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(Vec(colRulerCenter + clearAndDelButtonOffsetX + offsetMediumLight - 1, rowRuler5 + knobCVjackOffsetY + offsetMediumLight - 1), module, BigButtonSeq::METRONOME_LIGHT + 0)); | |||||
} | |||||
}; | |||||
} // namespace rack_plugin_ImpromptuModular | |||||
using namespace rack_plugin_ImpromptuModular; | |||||
RACK_PLUGIN_MODEL_INIT(ImpromptuModular, BigButtonSeq) { | |||||
Model *modelBigButtonSeq = Model::create<BigButtonSeq, BigButtonSeqWidget>("Impromptu Modular", "Big-Button-Seq", "SEQ - Big-Button-Seq", SEQUENCER_TAG); | |||||
return modelBigButtonSeq; | |||||
} | |||||
/*CHANGE LOG | |||||
0.6.10: detect BPM and snap BigButton and Del to nearest beat (with timeout if beat slows down too much or stops). TODO: update manual | |||||
0.6.8: | |||||
created | |||||
*/ |
@@ -0,0 +1,114 @@ | |||||
//*********************************************************************************************** | |||||
//Blank Panel for VCV Rack by Marc Boulé | |||||
//*********************************************************************************************** | |||||
#include "ImpromptuModular.hpp" | |||||
namespace rack_plugin_ImpromptuModular { | |||||
struct BlankPanel : Module { | |||||
int panelTheme = 0; | |||||
BlankPanel() : Module(0, 0, 0, 0) { | |||||
onReset(); | |||||
} | |||||
void onReset() override { | |||||
} | |||||
void onRandomize() override { | |||||
} | |||||
json_t *toJson() override { | |||||
json_t *rootJ = json_object(); | |||||
// panelTheme | |||||
json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); | |||||
return rootJ; | |||||
} | |||||
void fromJson(json_t *rootJ) override { | |||||
// panelTheme | |||||
json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); | |||||
if (panelThemeJ) | |||||
panelTheme = json_integer_value(panelThemeJ); | |||||
} | |||||
// Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() | |||||
void step() override { | |||||
} | |||||
}; | |||||
struct BlankPanelWidget : ModuleWidget { | |||||
struct PanelThemeItem : MenuItem { | |||||
BlankPanel *module; | |||||
int theme; | |||||
void onAction(EventAction &e) override { | |||||
module->panelTheme = theme; | |||||
} | |||||
void step() override { | |||||
rightText = (module->panelTheme == theme) ? "✔" : ""; | |||||
} | |||||
}; | |||||
Menu *createContextMenu() override { | |||||
Menu *menu = ModuleWidget::createContextMenu(); | |||||
MenuLabel *spacerLabel = new MenuLabel(); | |||||
menu->addChild(spacerLabel); | |||||
BlankPanel *module = dynamic_cast<BlankPanel*>(this->module); | |||||
assert(module); | |||||
MenuLabel *themeLabel = new MenuLabel(); | |||||
themeLabel->text = "Panel Theme"; | |||||
menu->addChild(themeLabel); | |||||
PanelThemeItem *lightItem = new PanelThemeItem(); | |||||
lightItem->text = lightPanelID;// ImpromptuModular.hpp | |||||
lightItem->module = module; | |||||
lightItem->theme = 0; | |||||
menu->addChild(lightItem); | |||||
PanelThemeItem *darkItem = new PanelThemeItem(); | |||||
darkItem->text = darkPanelID;// ImpromptuModular.hpp | |||||
darkItem->module = module; | |||||
darkItem->theme = 1; | |||||
menu->addChild(darkItem); | |||||
return menu; | |||||
} | |||||
BlankPanelWidget(BlankPanel *module) : ModuleWidget(module) { | |||||
// Main panel from Inkscape | |||||
DynamicSVGPanel *panel = new DynamicSVGPanel(); | |||||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/BlankPanel.svg"))); | |||||
panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/BlankPanel_dark.svg"))); | |||||
box.size = panel->box.size; | |||||
panel->mode = &module->panelTheme; | |||||
addChild(panel); | |||||
// Screws | |||||
addChild(createDynamicScrew<IMScrew>(Vec(15, 0), &module->panelTheme)); | |||||
addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 0), &module->panelTheme)); | |||||
addChild(createDynamicScrew<IMScrew>(Vec(15, 365), &module->panelTheme)); | |||||
addChild(createDynamicScrew<IMScrew>(Vec(box.size.x-30, 365), &module->panelTheme)); | |||||
} | |||||
}; | |||||
} // namespace rack_plugin_ImpromptuModular | |||||
using namespace rack_plugin_ImpromptuModular; | |||||
RACK_PLUGIN_MODEL_INIT(ImpromptuModular, BlankPanel) { | |||||
Model *modelBlankPanel = Model::create<BlankPanel, BlankPanelWidget>("Impromptu Modular", "Blank-Panel", "MISC - Blank-Panel", BLANK_TAG); | |||||
return modelBlankPanel; | |||||
} |