diff --git a/.gitignore b/.gitignore index 9cfeca83..34c4aa73 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ *.map *.o __ATTIC +__INCOMING diff --git a/README.md b/README.md index a2d9d737..0704b3fb 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,9 @@ The binary distribution contains the following dynamically loaded add-on modules - 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.MomentaryOnButtons - Alikins.BigMuteButton @@ -321,6 +323,18 @@ The following (462) add-on modules are statically linked with the VST plugin: - HetrickCV.Waveshape - huaba.EQ3 - 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.BouncyBalls - JW_Modules.FullScope @@ -345,6 +359,8 @@ The following (462) add-on modules are statically linked with the VST plugin: - LindenbergResearch.ReShaper - LindenbergResearch.BlankPanel - LindenbergResearch.BlankPanelM1 + - LindenbergResearch.VCO + - LindenbergResearch.Westcoast (preview) - LOGinstruments.constant - LOGinstruments.constant2 - LOGinstruments.Speck diff --git a/include/dsp/decimator.hpp b/include/dsp/decimator.hpp index 0f383fc8..4d5b98b7 100644 --- a/include/dsp/decimator.hpp +++ b/include/dsp/decimator.hpp @@ -1,39 +1,2 @@ #pragma once - -#include "string.h" -#include "dsp/ringbuffer.hpp" -#include "dsp/fir.hpp" - - -namespace rack { - -template -struct Decimator { - DoubleRingBuffer 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" diff --git a/include/dsp/digital.hpp b/include/dsp/digital.hpp index 4a90a5a5..4f24380a 100644 --- a/include/dsp/digital.hpp +++ b/include/dsp/digital.hpp @@ -6,15 +6,21 @@ 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 { - // UNKNOWN is used to represent a stable state when the previous state is not yet set enum State { UNKNOWN, LOW, HIGH }; - State state = UNKNOWN; + State state; + + SchmittTrigger() { + reset(); + } + void reset() { + state = UNKNOWN; + } /** Updates the state of the Schmitt Trigger given a value. Returns true if triggered, i.e. the value increases from 0 to 1. If different trigger thresholds are needed, use @@ -48,25 +54,50 @@ struct SchmittTrigger { bool isHigh() { return state == HIGH; } +}; + + +struct BooleanTrigger { + bool lastState; + + BooleanTrigger() { + 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 */ 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) { 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; - this->pulseTime = pulseTime; + this->triggerDuration = triggerDuration; } } }; diff --git a/include/dsp/fir.hpp b/include/dsp/fir.hpp index 4756e6ec..65d1f07e 100644 --- a/include/dsp/fir.hpp +++ b/include/dsp/fir.hpp @@ -5,34 +5,36 @@ 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++) { - y += x[-i] * kernel[i]; + y += in[len - 1 - i] * kernel[i]; } 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) { this->blockSize = blockSize; 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() { - clear(); + setKernel(NULL, 0); delete[] outputTail; delete[] tmpBlock; pffft_destroy_setup(pffft); } - void clear() { + void setKernel(const float *kernel, size_t length) { + // Clear existing kernel if (kernelFfts) { pffft_aligned_free(kernelFfts); kernelFfts = NULL; @@ -75,29 +80,24 @@ struct RealTimeConvolver { } kernelBlocks = 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 for (size_t i = 0; i < kernelBlocks; i++) { 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 pffft_transform(pffft, tmpBlock, tmpBlock, NULL, PFFFT_BACKWARD); @@ -132,9 +132,10 @@ struct RealTimeConvolver { tmpBlock[i] += outputTail[i]; } // Copy output block to output + float scale = 1.f / (blockSize*2); for (size_t i = 0; i < blockSize; i++) { // Scale based on FFT - output[i] = tmpBlock[i] / blockSize; + output[i] = tmpBlock[i] * scale; } // Set tail for (size_t i = 0; i < blockSize; i++) { diff --git a/include/dsp/ode.hpp b/include/dsp/ode.hpp index c94dfe19..cdd72a41 100644 --- a/include/dsp/ode.hpp +++ b/include/dsp/ode.hpp @@ -2,47 +2,94 @@ 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 +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 +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++) { - 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 +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++) { - 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++) { - 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++) { - 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++) { - 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 diff --git a/include/dsp/resampler.hpp b/include/dsp/resampler.hpp new file mode 100644 index 00000000..0eaad174 --- /dev/null +++ b/include/dsp/resampler.hpp @@ -0,0 +1,176 @@ +#pragma once +#ifndef RACK_SKIP_RESAMPLER + +#include +#include +#include +#include "frame.hpp" +#include "ringbuffer.hpp" +#include "fir.hpp" + + +namespace rack { + +template +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 *in, int *inFrames, Frame *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)); + *inFrames = frames; + *outFrames = frames; + } + } +}; + + +template +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 +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 diff --git a/include/dsp/ringbuffer.hpp b/include/dsp/ringbuffer.hpp index 73506e91..f2f38f8c 100644 --- a/include/dsp/ringbuffer.hpp +++ b/include/dsp/ringbuffer.hpp @@ -1,4 +1,5 @@ #pragma once +#ifndef RACK_SKIP_RINGBUFFER #include #include "util/common.hpp" @@ -198,3 +199,4 @@ struct AppleRingBuffer { }; } // namespace rack +#endif // RACK_SKIP_RINGBUFFER diff --git a/include/dsp/samplerate.hpp b/include/dsp/samplerate.hpp index ab4acd2e..4d5b98b7 100644 --- a/include/dsp/samplerate.hpp +++ b/include/dsp/samplerate.hpp @@ -1,99 +1,2 @@ #pragma once - -#include -#include -#include -#include "frame.hpp" - - -namespace rack { - -template -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 *in, int *inFrames, Frame *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)); - *inFrames = frames; - *outFrames = frames; - } - } -}; - -} // namespace rack +#include "resampler.hpp" diff --git a/include/settings.hpp b/include/settings.hpp index 15822700..fca46e7e 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -7,6 +7,7 @@ namespace rack { extern bool gSkipAutosaveOnLaunch; +extern bool b_touchkeyboard_enable; void settingsSave(std::string filename); void settingsLoad(std::string filename, bool bWindowSizeOnly); diff --git a/makefile_shared_lib.msvc b/makefile_shared_lib.msvc index ebfaaccb..70ad6ae7 100644 --- a/makefile_shared_lib.msvc +++ b/makefile_shared_lib.msvc @@ -17,7 +17,7 @@ else EXTRALIBS= -LIBPATH:dep/lib/msvc/x86 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 PLAF_OBJ= diff --git a/plugins/community/repos/21kHz/LICENSE.txt b/plugins/community/repos/21kHz/LICENSE.txt new file mode 100644 index 00000000..0534bf2f --- /dev/null +++ b/plugins/community/repos/21kHz/LICENSE.txt @@ -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. diff --git a/plugins/community/repos/21kHz/Makefile b/plugins/community/repos/21kHz/Makefile new file mode 100644 index 00000000..70aa5fe9 --- /dev/null +++ b/plugins/community/repos/21kHz/Makefile @@ -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 diff --git a/plugins/community/repos/21kHz/README.md b/plugins/community/repos/21kHz/README.md new file mode 100644 index 00000000..faef6f69 --- /dev/null +++ b/plugins/community/repos/21kHz/README.md @@ -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. + +drawing + +## 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. + +drawing + +## *D*∞ + +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. diff --git a/plugins/community/repos/21kHz/docs/d.png b/plugins/community/repos/21kHz/docs/d.png new file mode 100644 index 00000000..8fbbd954 Binary files /dev/null and b/plugins/community/repos/21kHz/docs/d.png differ diff --git a/plugins/community/repos/21kHz/docs/pl.png b/plugins/community/repos/21kHz/docs/pl.png new file mode 100644 index 00000000..33555803 Binary files /dev/null and b/plugins/community/repos/21kHz/docs/pl.png differ diff --git a/plugins/community/repos/21kHz/make.objects b/plugins/community/repos/21kHz/make.objects new file mode 100644 index 00000000..0b9248c6 --- /dev/null +++ b/plugins/community/repos/21kHz/make.objects @@ -0,0 +1,4 @@ +ALL_OBJ= \ + ./src/21kHz.o \ + ./src/D_Inf.o \ + ./src/PalmLoop.o diff --git a/plugins/community/repos/21kHz/makefile.msvc b/plugins/community/repos/21kHz/makefile.msvc new file mode 100644 index 00000000..06896518 --- /dev/null +++ b/plugins/community/repos/21kHz/makefile.msvc @@ -0,0 +1,7 @@ +SLUG=21kHz + +include ../../../build_plugin_pre.mk + +include make.objects + +include ../../../build_plugin_post.mk diff --git a/plugins/community/repos/21kHz/res/Components/kHzButton_0.svg b/plugins/community/repos/21kHz/res/Components/kHzButton_0.svg new file mode 100644 index 00000000..edcfad31 --- /dev/null +++ b/plugins/community/repos/21kHz/res/Components/kHzButton_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Components/kHzButton_1.svg b/plugins/community/repos/21kHz/res/Components/kHzButton_1.svg new file mode 100644 index 00000000..42ced401 --- /dev/null +++ b/plugins/community/repos/21kHz/res/Components/kHzButton_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Components/kHzKnob.svg b/plugins/community/repos/21kHz/res/Components/kHzKnob.svg new file mode 100644 index 00000000..8933dad4 --- /dev/null +++ b/plugins/community/repos/21kHz/res/Components/kHzKnob.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Components/kHzKnobSmall.svg b/plugins/community/repos/21kHz/res/Components/kHzKnobSmall.svg new file mode 100644 index 00000000..fa0818a5 --- /dev/null +++ b/plugins/community/repos/21kHz/res/Components/kHzKnobSmall.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Components/kHzPort.svg b/plugins/community/repos/21kHz/res/Components/kHzPort.svg new file mode 100644 index 00000000..448664e3 --- /dev/null +++ b/plugins/community/repos/21kHz/res/Components/kHzPort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Components/kHzScrew.svg b/plugins/community/repos/21kHz/res/Components/kHzScrew.svg new file mode 100644 index 00000000..61cff8ef --- /dev/null +++ b/plugins/community/repos/21kHz/res/Components/kHzScrew.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Panels/D_Inf.svg b/plugins/community/repos/21kHz/res/Panels/D_Inf.svg new file mode 100644 index 00000000..ac9bbd1b --- /dev/null +++ b/plugins/community/repos/21kHz/res/Panels/D_Inf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/res/Panels/PalmLoop.svg b/plugins/community/repos/21kHz/res/Panels/PalmLoop.svg new file mode 100644 index 00000000..a15e5e13 --- /dev/null +++ b/plugins/community/repos/21kHz/res/Panels/PalmLoop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/21kHz/src/21kHz.cpp b/plugins/community/repos/21kHz/src/21kHz.cpp new file mode 100644 index 00000000..eb5a7255 --- /dev/null +++ b/plugins/community/repos/21kHz/src/21kHz.cpp @@ -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); +} diff --git a/plugins/community/repos/21kHz/src/21kHz.hpp b/plugins/community/repos/21kHz/src/21kHz.hpp new file mode 100644 index 00000000..af2b47a1 --- /dev/null +++ b/plugins/community/repos/21kHz/src/21kHz.hpp @@ -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"))); + } +}; diff --git a/plugins/community/repos/21kHz/src/D_Inf.cpp b/plugins/community/repos/21kHz/src/D_Inf.cpp new file mode 100644 index 00000000..9fa1cda7 --- /dev/null +++ b/plugins/community/repos/21kHz/src/D_Inf.cpp @@ -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(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(ParamWidget::create(Vec(14, 40), module, D_Inf::OCTAVE_PARAM, -4, 4, 0)); + addParam(ParamWidget::create(Vec(14, 96), module, D_Inf::COARSE_PARAM, -7, 7, 0)); + + addParam(ParamWidget::create(Vec(10, 150), module, D_Inf::HALF_SHARP_PARAM, 0, 1, 0)); + addParam(ParamWidget::create(Vec(36, 150), module, D_Inf::INVERT_PARAM, 0, 1, 0)); + + addParam(ParamWidget::create(Vec(10, 182), module, D_Inf::GATE_PARAM, 0, 1, 0)); + addParam(ParamWidget::create(Vec(36, 182), module, D_Inf::INVERT_TRIG_PARAM, 0, 1, 0)); + + addInput(Port::create(Vec(17, 234), Port::INPUT, module, D_Inf::TRIG_INPUT)); + addInput(Port::create(Vec(17, 276), Port::INPUT, module, D_Inf::A_INPUT)); + addOutput(Port::create(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("21kHz", "kHzD_Inf", "D∞ — pitch tools — 4hp", TUNER_TAG, UTILITY_TAG); + return modelD_Inf; +} + +// history +// 0.6.1 +// create diff --git a/plugins/community/repos/21kHz/src/PalmLoop.cpp b/plugins/community/repos/21kHz/src/PalmLoop.cpp new file mode 100644 index 00000000..b4aaa551 --- /dev/null +++ b/plugins/community/repos/21kHz/src/PalmLoop.cpp @@ -0,0 +1,253 @@ +#include "21kHz.hpp" +#include "dsp/digital.hpp" +#include "dsp/math.hpp" +#include + + +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 sawBuffer; + array sqrBuffer; + array 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(Vec(RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(Widget::create(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(Widget::create(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(ParamWidget::create(Vec(36, 40), module, PalmLoop::OCT_PARAM, 4, 12, 8)); + + addParam(ParamWidget::create(Vec(16, 112), module, PalmLoop::COARSE_PARAM, -7, 7, 0)); + addParam(ParamWidget::create(Vec(72, 112), module, PalmLoop::FINE_PARAM, -0.083333, 0.083333, 0.0)); + + addParam(ParamWidget::create(Vec(16, 168), module, PalmLoop::EXP_FM_PARAM, -1.0, 1.0, 0.0)); + addParam(ParamWidget::create(Vec(72, 168), module, PalmLoop::LIN_FM_PARAM, -40.0, 40.0, 0.0)); + + addInput(Port::create(Vec(10, 234), Port::INPUT, module, PalmLoop::EXP_FM_INPUT)); + addInput(Port::create(Vec(47, 234), Port::INPUT, module, PalmLoop::V_OCT_INPUT)); + addInput(Port::create(Vec(84, 234), Port::INPUT, module, PalmLoop::LIN_FM_INPUT)); + + addInput(Port::create(Vec(10, 276), Port::INPUT, module, PalmLoop::RESET_INPUT)); + addOutput(Port::create(Vec(47, 276), Port::OUTPUT, module, PalmLoop::SAW_OUTPUT)); + addOutput(Port::create(Vec(84, 276), Port::OUTPUT, module, PalmLoop::SIN_OUTPUT)); + + addOutput(Port::create(Vec(10, 318), Port::OUTPUT, module, PalmLoop::SQR_OUTPUT)); + addOutput(Port::create(Vec(47, 318), Port::OUTPUT, module, PalmLoop::TRI_OUTPUT)); + addOutput(Port::create(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("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 diff --git a/plugins/community/repos/21kHz/src/dsp/math.hpp b/plugins/community/repos/21kHz/src/dsp/math.hpp new file mode 100644 index 00000000..af4a1808 --- /dev/null +++ b/plugins/community/repos/21kHz/src/dsp/math.hpp @@ -0,0 +1,72 @@ +#include + + +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 &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 &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; +} diff --git a/plugins/community/repos/Befaco/src/SpringReverb.cpp b/plugins/community/repos/Befaco/src/SpringReverb.cpp index d1be1051..1c50ab38 100644 --- a/plugins/community/repos/Befaco/src/SpringReverb.cpp +++ b/plugins/community/repos/Befaco/src/SpringReverb.cpp @@ -29,7 +29,8 @@ void springReverbInit() { } } - +namespace rack_plugin_Befaco { +// clashes with rack/include/dsp/fir.h struct RealTimeConvolver { // `kernelBlocks` number of contiguous FFT blocks of size `blockSize` // indexed by [i * blockSize*2 + j] @@ -137,6 +138,7 @@ struct RealTimeConvolver { } } }; +} // namespace rack_plugin_Befaco #define BLOCKSIZE 1024 @@ -168,7 +170,7 @@ struct SpringReverb : Module { NUM_LIGHTS = VU1_LIGHT + 7 }; - RealTimeConvolver *convolver = NULL; + rack_plugin_Befaco::RealTimeConvolver *convolver = NULL; SampleRateConverter<1> inputSrc; SampleRateConverter<1> outputSrc; DoubleRingBuffer, 16*BLOCKSIZE> inputBuffer; @@ -185,7 +187,7 @@ struct SpringReverb : Module { 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); } diff --git a/plugins/community/repos/FrozenWasteland/src/FrozenWasteland.hpp b/plugins/community/repos/FrozenWasteland/src/FrozenWasteland.hpp index 2f7f95b9..baef5fa3 100644 --- a/plugins/community/repos/FrozenWasteland/src/FrozenWasteland.hpp +++ b/plugins/community/repos/FrozenWasteland/src/FrozenWasteland.hpp @@ -1,3 +1,5 @@ +#define RACK_SKIP_RINGBUFFER +// #define RACK_SKIP_RESAMPLER #include "rack.hpp" using namespace rack; diff --git a/plugins/community/repos/FrozenWasteland/src/HairPick.cpp b/plugins/community/repos/FrozenWasteland/src/HairPick.cpp index 6d93f968..2bfa55ef 100644 --- a/plugins/community/repos/FrozenWasteland/src/HairPick.cpp +++ b/plugins/community/repos/FrozenWasteland/src/HairPick.cpp @@ -1,7 +1,7 @@ #include "FrozenWasteland.hpp" #include "dsp/samplerate.hpp" #include "dsp/digital.hpp" -#include "ringbuffer.hpp" +#include "dsp-delay/ringbuffer.hpp" #include #define HISTORY_SIZE (1<<22) @@ -102,10 +102,9 @@ struct HairPick : Module { bool combActive[NUM_TAPS]; float combLevel[NUM_TAPS]; - - MultiTapDoubleRingBuffer historyBuffer[CHANNELS]; - DoubleRingBuffer outBuffer[NUM_TAPS][CHANNELS]; - SampleRateConverter<1> src; + rack_plugin_FrozenWasteland::MultiTapDoubleRingBuffer historyBuffer[CHANNELS]; + rack_plugin_FrozenWasteland::DoubleRingBuffer outBuffer[NUM_TAPS][CHANNELS]; + SampleRateConverter<1> src; float lastFeedback[CHANNELS] = {0.0f,0.0f}; float lerp(float v0, float v1, float t) { diff --git a/plugins/community/repos/FrozenWasteland/src/dsp-delay/ringbuffer.hpp b/plugins/community/repos/FrozenWasteland/src/dsp-delay/ringbuffer.hpp index 8b3e3cd3..2e15664c 100644 --- a/plugins/community/repos/FrozenWasteland/src/dsp-delay/ringbuffer.hpp +++ b/plugins/community/repos/FrozenWasteland/src/dsp-delay/ringbuffer.hpp @@ -3,8 +3,7 @@ #include #include "util/common.hpp" - -namespace rack { +namespace rack_plugin_FrozenWasteland { /** A simple cyclic buffer. S must be a power of 2. @@ -353,4 +352,4 @@ struct AppleRingBuffer { } }; -} // namespace rack +} // namespace rack_plugin_FrozenWasteland diff --git a/plugins/community/repos/ImpromptuModular/.gitignore b/plugins/community/repos/ImpromptuModular/.gitignore new file mode 100644 index 00000000..1c67d5b2 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/.gitignore @@ -0,0 +1,4 @@ +/build +/dist +plugin.* +/src/midifile/midifile \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/LICENSE.txt b/plugins/community/repos/ImpromptuModular/LICENSE.txt new file mode 100644 index 00000000..951a369a --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/LICENSE.txt @@ -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. diff --git a/plugins/community/repos/ImpromptuModular/Makefile b/plugins/community/repos/ImpromptuModular/Makefile new file mode 100644 index 00000000..e86a9e5d --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/Makefile @@ -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 diff --git a/plugins/community/repos/ImpromptuModular/README.md b/plugins/community/repos/ImpromptuModular/README.md new file mode 100644 index 00000000..11f210cc --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/README.md @@ -0,0 +1,317 @@ +![IM](res/img/Blank.jpg) + +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 + +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 +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 + +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 + +![IM](res/img/Tact.jpg) + +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 + +![IM](res/img/TwelveKey.jpg) + +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 + +![IM](res/img/Clocked.jpg) + +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 + +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 + +![IM](res/img/PhraseSeq16.jpg) + +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!) + +![IM](res/img/PhraseSeq16BlockDiag.jpg) + +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 + +![IM](res/img/PhraseSeq32.jpg) + +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 + +![IM](res/img/GateSeq64.jpg) + +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 + +![IM](res/img/BigButtonSeq.jpg) + +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 + +![IM](res/img/SemiModularSynth.jpg) + +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 + +![IM](res/img/WriteSeqs.jpg) + +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)) diff --git a/plugins/community/repos/ImpromptuModular/make.objects b/plugins/community/repos/ImpromptuModular/make.objects new file mode 100644 index 00000000..346948e9 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/make.objects @@ -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 diff --git a/plugins/community/repos/ImpromptuModular/makefile.msvc b/plugins/community/repos/ImpromptuModular/makefile.msvc new file mode 100644 index 00000000..80c42c69 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/makefile.msvc @@ -0,0 +1,7 @@ +SLUG=ImpromptuModular + +include ../../../build_plugin_pre.mk + +include make.objects + +include ../../../build_plugin_post.mk diff --git a/plugins/community/repos/ImpromptuModular/res/PhraseSeq16BlockDiag.svg b/plugins/community/repos/ImpromptuModular/res/PhraseSeq16BlockDiag.svg new file mode 100644 index 00000000..596d7845 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/PhraseSeq16BlockDiag.svg @@ -0,0 +1 @@ +SEQSONGPHRASES (= SEQUENCE INDEXES)EACH SEQUENCE CAN HAVE UP TO 16 STEPS (NOT SHOWN)16 SEPARATE SEQUENCES CAN BE CREATEDA SONG CAN HAVE UP TO 16 PHRASES \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/comp/CKSSH_0.svg b/plugins/community/repos/ImpromptuModular/res/comp/CKSSH_0.svg new file mode 100644 index 00000000..f538b3b9 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/comp/CKSSH_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/comp/CKSSH_1.svg b/plugins/community/repos/ImpromptuModular/res/comp/CKSSH_1.svg new file mode 100644 index 00000000..7c3ec633 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/comp/CKSSH_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/BigButtonSeq_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/BigButtonSeq_dark.svg new file mode 100644 index 00000000..d7a1a62f --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/BigButtonSeq_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/BlankPanel_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/BlankPanel_dark.svg new file mode 100644 index 00000000..267e22d8 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/BlankPanel_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg new file mode 100644 index 00000000..53c10ee3 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/Clocked_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg new file mode 100644 index 00000000..a80aca89 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/GateSeq64_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg new file mode 100644 index 00000000..85477ddf --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq16_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg new file mode 100644 index 00000000..3c342d3c --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/PhraseSeq32_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/SemiModular_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/SemiModular_dark.svg new file mode 100644 index 00000000..94b841fd --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/SemiModular_dark.svg @@ -0,0 +1 @@ ++- \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/Tact_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/Tact_dark.svg new file mode 100644 index 00000000..dc01681a --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/Tact_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/TwelveKey_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/TwelveKey_dark.svg new file mode 100644 index 00000000..a529d7a5 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/TwelveKey_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/WriteSeq32_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/WriteSeq32_dark.svg new file mode 100644 index 00000000..cb5e6482 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/WriteSeq32_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/WriteSeq64_dark.svg b/plugins/community/repos/ImpromptuModular/res/dark/WriteSeq64_dark.svg new file mode 100644 index 00000000..50aa13e4 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/WriteSeq64_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLarge.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLarge.svg new file mode 100644 index 00000000..cdffd8a5 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLarge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeEffects.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeEffects.svg new file mode 100644 index 00000000..e17c38c1 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeEffects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMark.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMark.svg new file mode 100644 index 00000000..b32c394d --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMarkEffects.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMarkEffects.svg new file mode 100644 index 00000000..e17c38c1 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMarkEffects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/CKD6b_0.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/CKD6b_0.svg new file mode 100644 index 00000000..050c15b8 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/CKD6b_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/CKD6b_1.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/CKD6b_1.svg new file mode 100644 index 00000000..bf7875fb --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/CKD6b_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/PJ301M.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/PJ301M.svg new file mode 100644 index 00000000..a0410488 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/PJ301M.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/RoundSmallBlackKnob.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/RoundSmallBlackKnob.svg new file mode 100644 index 00000000..0a2f6286 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/RoundSmallBlackKnob.svg @@ -0,0 +1,88 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/RoundSmallBlackKnobEffects.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/RoundSmallBlackKnobEffects.svg new file mode 100644 index 00000000..988c29ba --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/RoundSmallBlackKnobEffects.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/ImpromptuModular/res/dark/comp/ScrewSilver.svg b/plugins/community/repos/ImpromptuModular/res/dark/comp/ScrewSilver.svg new file mode 100644 index 00000000..8bfcafc8 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/dark/comp/ScrewSilver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/fonts/OFL_Segment14.txt b/plugins/community/repos/ImpromptuModular/res/fonts/OFL_Segment14.txt new file mode 100644 index 00000000..25a557fb --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/fonts/OFL_Segment14.txt @@ -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. diff --git a/plugins/community/repos/ImpromptuModular/res/fonts/OFL_Sniglet.txt b/plugins/community/repos/ImpromptuModular/res/fonts/OFL_Sniglet.txt new file mode 100644 index 00000000..b0a6d8c8 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/fonts/OFL_Sniglet.txt @@ -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. diff --git a/plugins/community/repos/ImpromptuModular/res/fonts/Segment14.ttf b/plugins/community/repos/ImpromptuModular/res/fonts/Segment14.ttf new file mode 100644 index 00000000..6baaa987 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/fonts/Segment14.ttf differ diff --git a/plugins/community/repos/ImpromptuModular/res/fonts/Sniglet-Regular.ttf b/plugins/community/repos/ImpromptuModular/res/fonts/Sniglet-Regular.ttf new file mode 100644 index 00000000..a1c45c96 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/fonts/Sniglet-Regular.ttf differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/BigButtonSeq.jpg b/plugins/community/repos/ImpromptuModular/res/img/BigButtonSeq.jpg new file mode 100644 index 00000000..e9f30e93 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/BigButtonSeq.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/Blank.jpg b/plugins/community/repos/ImpromptuModular/res/img/Blank.jpg new file mode 100644 index 00000000..d3a41e02 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/Blank.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg b/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg new file mode 100644 index 00000000..4f591160 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/Clocked.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg b/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg new file mode 100644 index 00000000..6201b7be Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/GateSeq64.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg new file mode 100644 index 00000000..b74cee6f Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16BlockDiag.jpg b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16BlockDiag.jpg new file mode 100644 index 00000000..65aeeab9 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq16BlockDiag.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg new file mode 100644 index 00000000..23f56fb0 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/PhraseSeq32.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/SemiModularSynth.jpg b/plugins/community/repos/ImpromptuModular/res/img/SemiModularSynth.jpg new file mode 100644 index 00000000..81621534 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/SemiModularSynth.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/Tact.jpg b/plugins/community/repos/ImpromptuModular/res/img/Tact.jpg new file mode 100644 index 00000000..10ca0fa7 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/Tact.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/TwelveKey.jpg b/plugins/community/repos/ImpromptuModular/res/img/TwelveKey.jpg new file mode 100644 index 00000000..bd169926 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/TwelveKey.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/img/WriteSeqs.jpg b/plugins/community/repos/ImpromptuModular/res/img/WriteSeqs.jpg new file mode 100644 index 00000000..ff47d794 Binary files /dev/null and b/plugins/community/repos/ImpromptuModular/res/img/WriteSeqs.jpg differ diff --git a/plugins/community/repos/ImpromptuModular/res/light/BigButtonSeq.svg b/plugins/community/repos/ImpromptuModular/res/light/BigButtonSeq.svg new file mode 100644 index 00000000..379b7b28 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/BigButtonSeq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/BlankPanel.svg b/plugins/community/repos/ImpromptuModular/res/light/BlankPanel.svg new file mode 100644 index 00000000..17841474 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/BlankPanel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg b/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg new file mode 100644 index 00000000..a0979cff --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/Clocked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/EngTest1.svg b/plugins/community/repos/ImpromptuModular/res/light/EngTest1.svg new file mode 100644 index 00000000..51528c47 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/EngTest1.svg @@ -0,0 +1,931 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg b/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg new file mode 100644 index 00000000..7f233fc4 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/GateSeq64.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/MidiFile.svg b/plugins/community/repos/ImpromptuModular/res/light/MidiFile.svg new file mode 100644 index 00000000..679c2097 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/MidiFile.svg @@ -0,0 +1,375 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MIDI-FILE + diff --git a/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg new file mode 100644 index 00000000..ed0f38d4 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq16.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg new file mode 100644 index 00000000..3bd8d3d8 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/PhraseSeq32.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/SemiModular.svg b/plugins/community/repos/ImpromptuModular/res/light/SemiModular.svg new file mode 100644 index 00000000..934edbc2 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/SemiModular.svg @@ -0,0 +1 @@ ++- \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/Tact.svg b/plugins/community/repos/ImpromptuModular/res/light/Tact.svg new file mode 100644 index 00000000..5f15c349 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/Tact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/TwelveKey.svg b/plugins/community/repos/ImpromptuModular/res/light/TwelveKey.svg new file mode 100644 index 00000000..00e147f5 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/TwelveKey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/WriteSeq32.svg b/plugins/community/repos/ImpromptuModular/res/light/WriteSeq32.svg new file mode 100644 index 00000000..30ec4cf0 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/WriteSeq32.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/WriteSeq64.svg b/plugins/community/repos/ImpromptuModular/res/light/WriteSeq64.svg new file mode 100644 index 00000000..e083208f --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/WriteSeq64.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/comp/BlackKnobLarge.svg b/plugins/community/repos/ImpromptuModular/res/light/comp/BlackKnobLarge.svg new file mode 100644 index 00000000..d8fad831 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/comp/BlackKnobLarge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/comp/BlackKnobLargeWithMark.svg b/plugins/community/repos/ImpromptuModular/res/light/comp/BlackKnobLargeWithMark.svg new file mode 100644 index 00000000..0060c370 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/comp/BlackKnobLargeWithMark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/comp/CKD6b_0.svg b/plugins/community/repos/ImpromptuModular/res/light/comp/CKD6b_0.svg new file mode 100644 index 00000000..51f61708 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/comp/CKD6b_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/comp/CKD6b_1.svg b/plugins/community/repos/ImpromptuModular/res/light/comp/CKD6b_1.svg new file mode 100644 index 00000000..958ad14a --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/comp/CKD6b_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/res/light/comp/RoundSmallBlackKnob.svg b/plugins/community/repos/ImpromptuModular/res/light/comp/RoundSmallBlackKnob.svg new file mode 100644 index 00000000..94178d97 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/res/light/comp/RoundSmallBlackKnob.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp b/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp new file mode 100644 index 00000000..e2b6dc65 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/BigButtonSeq.cpp @@ -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; + + 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(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("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("Every clock", CHECKMARK(module->metronomeDiv == 1)); + met1Item->module = module; + met1Item->div = 1; + menu->addChild(met1Item); + + MetronomeItem *met2Item = MenuItem::create("/2", CHECKMARK(module->metronomeDiv == 2)); + met2Item->module = module; + met2Item->div = 2; + menu->addChild(met2Item); + + MetronomeItem *met4Item = MenuItem::create("/4", CHECKMARK(module->metronomeDiv == 4)); + met4Item->module = module; + met4Item->div = 4; + menu->addChild(met4Item); + + MetronomeItem *met8Item = MenuItem::create("/8", CHECKMARK(module->metronomeDiv == 8)); + met8Item->module = module; + met8Item->div = 8; + menu->addChild(met8Item); + + MetronomeItem *met1000Item = MenuItem::create("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(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(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(Vec(colRulerT0, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT1, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 1, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT2, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 2, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT3, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 3, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT4, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 4, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT5, rowRuler0), Port::OUTPUT, module, BigButtonSeq::CHAN_OUTPUTS + 5, &module->panelTheme)); + // LEDs + addChild(ModuleLightWidget::create>(Vec(colRulerT0 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 0)); + addChild(ModuleLightWidget::create>(Vec(colRulerT1 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 2)); + addChild(ModuleLightWidget::create>(Vec(colRulerT2 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 4)); + addChild(ModuleLightWidget::create>(Vec(colRulerT3 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 6)); + addChild(ModuleLightWidget::create>(Vec(colRulerT4 + offsetMediumLight - 1, rowRuler0 + ledOffsetY + offsetMediumLight), module, BigButtonSeq::CHAN_LIGHTS + 8)); + addChild(ModuleLightWidget::create>(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(Vec(colRulerT0, rowRuler1), Port::INPUT, module, BigButtonSeq::CLK_INPUT, &module->panelTheme)); + // Chan knob and jack + addParam(createDynamicParam(Vec(colRulerCenter + offsetIMBigKnob, rowRuler1 + offsetIMBigKnob), module, BigButtonSeq::CHAN_PARAM, 0.0f, 6.0f - 1.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(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(Vec(colRulerCenter - lenAndRndKnobOffsetX + offsetIMBigKnob, rowRuler2 + offsetIMBigKnob), module, BigButtonSeq::LEN_PARAM, 0.0f, 64.0f - 1.0f, 32.0f - 1.0f, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerCenter - lenAndRndKnobOffsetX + knobCVjackOffsetX, rowRuler2), Port::INPUT, module, BigButtonSeq::LEN_INPUT, &module->panelTheme)); + // Rnd knob and jack + addParam(createDynamicParam(Vec(colRulerCenter + lenAndRndKnobOffsetX + offsetIMBigKnob, rowRuler2 + offsetIMBigKnob), module, BigButtonSeq::RND_PARAM, 0.0f, 100.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(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(Vec(colRulerCenter + offsetCKD6b, rowRuler3 + offsetCKD6b), module, BigButtonSeq::BANK_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerCenter, rowRuler3 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::BANK_INPUT, &module->panelTheme)); + // Clear button and jack + addParam(createDynamicParam(Vec(colRulerCenter - clearAndDelButtonOffsetX + offsetCKD6b, rowRuler4 + offsetCKD6b), module, BigButtonSeq::CLEAR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::CLEAR_INPUT, &module->panelTheme)); + // Del button and jack + addParam(createDynamicParam(Vec(colRulerCenter + clearAndDelButtonOffsetX + offsetCKD6b, rowRuler4 + offsetCKD6b), module, BigButtonSeq::DEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerCenter + clearAndDelButtonOffsetX, rowRuler4 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::DEL_INPUT, &module->panelTheme)); + // Reset button and jack + addParam(createDynamicParam(Vec(colRulerT0 + offsetCKD6b, rowRuler5 + offsetCKD6b), module, BigButtonSeq::RESET_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerT0, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::RESET_INPUT, &module->panelTheme)); + // Fill button and jack + addParam(createDynamicParam(Vec(colRulerT5 + offsetCKD6b, rowRuler5 + offsetCKD6b), module, BigButtonSeq::FILL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerT5, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::FILL_INPUT, &module->panelTheme)); + + // And now time for... BIG BUTTON! + addChild(ModuleLightWidget::create>(Vec(colRulerCenter + offsetLEDbezelBig - offsetLEDbezelLight*2.0f, rowRuler5 + 26 + offsetLEDbezelBig - offsetLEDbezelLight*2.0f), module, BigButtonSeq::BIG_LIGHT)); + addParam(ParamWidget::create(Vec(colRulerCenter + offsetLEDbezelBig, rowRuler5 + 26 + offsetLEDbezelBig), module, BigButtonSeq::BIG_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerCenter + offsetLEDbezelBig - offsetLEDbezelLight*2.0f + 9, rowRuler5 + 26 + offsetLEDbezelBig - offsetLEDbezelLight*2.0f + 9), module, BigButtonSeq::BIGC_LIGHT)); + // Big input + addInput(createDynamicPort(Vec(colRulerCenter - clearAndDelButtonOffsetX, rowRuler5 + knobCVjackOffsetY), Port::INPUT, module, BigButtonSeq::BIG_INPUT, &module->panelTheme)); + // Metronome light + addChild(ModuleLightWidget::create>(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("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 + +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/BlankPanel.cpp b/plugins/community/repos/ImpromptuModular/src/BlankPanel.cpp new file mode 100644 index 00000000..d6145702 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/BlankPanel.cpp @@ -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(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(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(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("Impromptu Modular", "Blank-Panel", "MISC - Blank-Panel", BLANK_TAG); + return modelBlankPanel; +} diff --git a/plugins/community/repos/ImpromptuModular/src/Clocked.cpp b/plugins/community/repos/ImpromptuModular/src/Clocked.cpp new file mode 100644 index 00000000..ee427e36 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/Clocked.cpp @@ -0,0 +1,1080 @@ +//*********************************************************************************************** +//Chain-able clock module for VCV Rack by Marc Boulé +// +//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 +//See ./res/fonts/ for font licenses +// +//Module concept and design by Marc Boulé, Nigel Sixsmith and Xavier Belmont +// +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +class Clock { + // The -1.0 step is used as a reset state every double-period so that + // lengths can be re-computed; it will stay at -1.0 when a clock is inactive. + // a clock frame is defined as "length * iterations + syncWait", and + // for master, syncWait does not apply and iterations = 1 + + + double step;// -1.0 when stopped, [0 to 2*period[ for clock steps (*2 is because of swing, so we do groups of 2 periods) + double length;// double period + double sampleTime; + int iterations;// run this many double periods before going into sync if sub-clock + Clock* syncSrc = nullptr; // only subclocks will have this set to master clock + static constexpr double guard = 0.0005;// in seconds, region for sync to occur right before end of length of last iteration; sub clocks must be low during this period + + public: + + int isHigh(float swing, float pulseWidth) { + // last 0.5ms (guard time) must be low so that sync mechanism will work properly (i.e. no missed pulses) + // this will automatically be the case, since code below disallows any pulses or inter-pulse times less than 1ms + int high = 0; + if (step >= 0.0) { + float swParam = swing;// swing is [-1 : 1] + + // all following values are in seconds + float onems = 0.001f; + float period = (float)length / 2.0f; + float swing = (period - 2.0f * onems) * swParam; + float p2min = onems; + float p2max = period - onems - fabs(swing); + if (p2max < p2min) { + p2max = p2min; + } + + //double p1 = 0.0;// implicit, no need + double p2 = (double)((p2max - p2min) * pulseWidth + p2min);// pulseWidth is [0 : 1] + double p3 = (double)(period + swing); + double p4 = ((double)(period + swing)) + p2; + + if (step < p2) + high = 1; + else if ((step >= p3) && (step < p4)) + high = 2; + } + return high; + } + + void setSync(Clock* clkGiven) { + syncSrc = clkGiven; + } + + inline void reset() { + step = -1.0; + } + inline bool isReset() { + return step == -1.0; + } + inline double getStep() { + return step; + } + + inline void setup(double lengthGiven, int iterationsGiven, double sampleTimeGiven) { + length = lengthGiven; + iterations = iterationsGiven; + sampleTime = sampleTimeGiven; + } + + inline void start() { + step = 0.0; + } + + void stepClock() {// here the clock was output on step "step", this function is called at end of module::step() + if (step >= 0.0) {// if active clock + step += sampleTime; + if ( (syncSrc != nullptr) && (iterations == 1) && (step > (length - guard)) ) {// if in sync region + if (syncSrc->isReset()) { + reset(); + }// else nothing needs to be done, just wait and step stays the same + } + else { + if (step >= length) {// reached end iteration + iterations--; + step -= length; + if (iterations <= 0) + reset();// frame done + } + } + } + } + + void applyNewLength(double lengthStretchFactor) { + if (step != -1.0) + step *= lengthStretchFactor; + length *= lengthStretchFactor; + } +}; + + +//***************************************************************************** + + +class ClockDelay { + long stepCounter; + int lastWriteValue; + bool readState; + long stepRise1; + long stepFall1; + long stepRise2; + long stepFall2; + + public: + + void reset() { + stepCounter = 0l; + lastWriteValue = 0; + readState = false; + stepRise1 = 0l; + stepFall1 = 0l; + stepRise2 = 0l; + stepFall2 = 0l; + } + + void write(int value) { + if (value == 1) {// first pulse is high + if (lastWriteValue == 0) // if got rise 1 + stepRise1 = stepCounter; + } + else if (value == 2) {// second pulse is high + if (lastWriteValue == 0) // if got rise 2 + stepRise2 = stepCounter; + } + else {// value = 0 (pulse is low) + if (lastWriteValue == 1) // if got fall 1 + stepFall1 = stepCounter; + else if (lastWriteValue == 2) // if got fall 2 + stepFall2 = stepCounter; + } + + lastWriteValue = value; + } + + bool read(long delaySamples) { + long delayedStepCounter = stepCounter - delaySamples; + if (delayedStepCounter == stepRise1 || delayedStepCounter == stepRise2) + readState = true; + else if (delayedStepCounter == stepFall1 || delayedStepCounter == stepFall2) + readState = false; + stepCounter++; + if (stepCounter > 1e8) {// keep within long's bounds (could go higher or could allow negative) + stepCounter -= 1e8;// 192000 samp/s * 2s * 64 * (3/4) = 18.4 Msamp + stepRise1 -= 1e8; + stepFall1 -= 1e8; + stepRise2 -= 1e8; + stepFall2 -= 1e8; + } + return readState; + } +}; + + +//***************************************************************************** + + +struct Clocked : Module { + enum ParamIds { + ENUMS(RATIO_PARAMS, 4),// master is index 0 + ENUMS(SWING_PARAMS, 4),// master is index 0 + ENUMS(PW_PARAMS, 4),// master is index 0 + RESET_PARAM, + RUN_PARAM, + ENUMS(DELAY_PARAMS, 4),// index 0 is unused + NUM_PARAMS + }; + enum InputIds { + ENUMS(PW_INPUTS, 4),// master is index 0 + RESET_INPUT, + RUN_INPUT, + BPM_INPUT, + ENUMS(SWING_INPUTS, 4),// master is index 0 + NUM_INPUTS + }; + enum OutputIds { + ENUMS(CLK_OUTPUTS, 4),// master is index 0 + RESET_OUTPUT, + RUN_OUTPUT, + BPM_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + RESET_LIGHT, + RUN_LIGHT, + ENUMS(CLK_LIGHTS, 4),// master is index 0 + NUM_LIGHTS + }; + + + // Constants + const float delayValues[8] = {0.0f, 0.0625f, 0.125f, 0.25f, 1.0f/3.0f, 0.5f , 2.0f/3.0f, 0.75f}; + const float ratioValues[34] = {1, 1.5, 2, 2.5, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 23, 24, 29, 31, 32, 37, 41, 43, 47, 48, 53, 59, 61, 64}; + static const int bpmMax = 300; + static const int bpmMin = 30; + static constexpr float masterLengthMax = 120.0f / bpmMin;// a length is a double period + static constexpr float masterLengthMin = 120.0f / bpmMax;// a length is a double period + static constexpr float delayInfoTime = 3.0f;// seconds + static constexpr float swingInfoTime = 2.0f;// seconds + + // Need to save, with reset + bool running; + + // Need to save, no reset + int panelTheme; + int expansion; + bool displayDelayNoteMode; + bool bpmDetectionMode; + bool emitResetOnStopRun; + int ppqn; + + // No need to save, with reset + // none + + // No need to save, no reset + bool scheduledReset; + float swingVal[4]; + long swingInfo[4];// downward step counter when swing to be displayed, 0 when normal display + int delayKnobIndexes[4]; + long delayInfo[4];// downward step counter when delay to be displayed, 0 when normal display + int ratiosDoubled[4]; + int newRatiosDoubled[4]; + Clock clk[4]; + ClockDelay delay[4]; + float masterLength;// a length is a double period + float resetLight; + SchmittTrigger resetTrigger; + SchmittTrigger runTrigger; + PulseGenerator resetPulse; + PulseGenerator runPulse; + SchmittTrigger bpmDetectTrigger; + int extPulseNumber;// 0 to ppqn - 1 + double extIntervalTime; + double timeoutTime; + long cantRunWarning;// 0 when no warning, positive downward step counter timer when warning + + + // called from the main thread (step() can not be called until all modules created) + Clocked() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + // Need to save, no reset + panelTheme = 0; + expansion = 0; + displayDelayNoteMode = true; + bpmDetectionMode = false; + emitResetOnStopRun = false; + ppqn = 4; + // No need to save, no reset + scheduledReset = false; + for (int i = 0; i < 4; i++) { + clk[i].setSync(i == 0 ? nullptr : &clk[0]); + swingVal[i] = 0.0f; + swingInfo[i] = 0l; + delayKnobIndexes[i] = 0; + delayInfo[i] = 0l; + ratiosDoubled[i] = 0; + newRatiosDoubled[i] = 0; + clk[i].reset(); + delay[i].reset(); + } + masterLength = 1.0f;// 120 BPM + resetLight = 0.0f; + resetTrigger.reset(); + runTrigger.reset(); + resetPulse.reset(); + runPulse.reset(); + bpmDetectTrigger.reset(); + extPulseNumber = -1; + extIntervalTime = 0.0; + timeoutTime = 2.0 / ppqn + 0.1; + cantRunWarning = 0ul; + + 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 + running = false; + // 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 + running = false; + // 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) + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // expansion + json_object_set_new(rootJ, "expansion", json_integer(expansion)); + + // displayDelayNoteMode + json_object_set_new(rootJ, "displayDelayNoteMode", json_boolean(displayDelayNoteMode)); + + // bpmDetectionMode + json_object_set_new(rootJ, "bpmDetectionMode", json_boolean(bpmDetectionMode)); + + // emitResetOnStopRun + json_object_set_new(rootJ, "emitResetOnStopRun", json_boolean(emitResetOnStopRun)); + + // ppqn + json_object_set_new(rootJ, "ppqn", json_integer(ppqn)); + + 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) + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // expansion + json_t *expansionJ = json_object_get(rootJ, "expansion"); + if (expansionJ) + expansion = json_integer_value(expansionJ); + + // displayDelayNoteMode + json_t *displayDelayNoteModeJ = json_object_get(rootJ, "displayDelayNoteMode"); + if (displayDelayNoteModeJ) + displayDelayNoteMode = json_is_true(displayDelayNoteModeJ); + + // bpmDetectionMode + json_t *bpmDetectionModeJ = json_object_get(rootJ, "bpmDetectionMode"); + if (bpmDetectionModeJ) + bpmDetectionMode = json_is_true(bpmDetectionModeJ); + + // emitResetOnStopRun + json_t *emitResetOnStopRunJ = json_object_get(rootJ, "emitResetOnStopRun"); + if (emitResetOnStopRunJ) + emitResetOnStopRun = json_is_true(emitResetOnStopRunJ); + + // ppqn + json_t *ppqnJ = json_object_get(rootJ, "ppqn"); + if (ppqnJ) + ppqn = clamp(json_integer_value(ppqnJ), 4, 24); + + // No need to save, with reset + // none + + scheduledReset = true; + } + + + int getRatioDoubled(int ratioKnobIndex) { + // ratioKnobIndex is 0 for master BPM's ratio (1 is implicitly returned), and 1 to 3 for other ratio knobs + // returns a positive ratio for mult, negative ratio for div (0 never returned) + int ret = 1; + if (ratioKnobIndex > 0) { + bool isDivision = false; + int i = (int) round( params[RATIO_PARAMS + ratioKnobIndex].value );// [ -(numRatios-1) ; (numRatios-1) ] + if (i < 0) { + i *= -1; + isDivision = true; + } + if (i >= 34) { + i = 34 - 1; + } + ret = (int) (ratioValues[i] * 2.0f + 0.5f); + if (isDivision) + ret = -1l * ret; + } + return ret; + } + + + void resetClocked() { + for (int i = 0; i < 4; i++) { + clk[i].reset(); + delay[i].reset(); + } + extPulseNumber = -1; + extIntervalTime = 0.0; + timeoutTime = 2.0 / ppqn + 0.1; + } + + // called by engine thread + void onSampleRateChange() override { + resetClocked(); + } + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + // called by engine thread + void step() override { + double sampleRate = (double)engineGetSampleRate(); + double sampleTime = 1.0 / sampleRate;// do this here since engineGetSampleRate() returns float + long cantRunWarningInit = (long) (0.7 * engineGetSampleRate()); + + // Scheduled reset (just the parts that do not have a place below in rest of function) + if (scheduledReset) { + resetClocked(); + resetLight = 0.0f; + resetTrigger.reset(); + runTrigger.reset(); + resetPulse.reset(); + runPulse.reset(); + bpmDetectTrigger.reset(); + cantRunWarning = 0l; + } + + // Run button + if (runTrigger.process(params[RUN_PARAM].value + inputs[RUN_INPUT].value)) { + if (!(bpmDetectionMode && inputs[BPM_INPUT].active) || running) {// toggle when not BPM detect, turn off only when BPM detect (allows turn off faster than timeout if don't want any trailing beats after stoppage). If allow manually start in bpmDetectionMode the clock will not know which pulse is the 1st of a ppqn set, so only allow stop + running = !running; + runPulse.trigger(0.001f); + resetClocked();// reset on any change of run state (will not re-launch if not running, thus clock railed low) + if (!running && emitResetOnStopRun) { + //resetLight = 1.0f; + resetPulse.trigger(0.001f); + } + } + else + cantRunWarning = cantRunWarningInit; + } + + // Reset (has to be near top because it sets steps to 0, and 0 not a real step (clock section will move to 1 before reaching outputs) + if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + resetLight = 1.0f; + resetPulse.trigger(0.001f); + resetClocked(); + } + else + resetLight -= (resetLight / lightLambda) * (float)sampleTime; + + // BPM input and knob + float newMasterLength = masterLength; + if (inputs[BPM_INPUT].active) { + float bpmInValue = inputs[BPM_INPUT].value; + bool trigBpmInValue = bpmDetectTrigger.process(bpmInValue); + + // BPM Detection method + if (bpmDetectionMode) { + if (scheduledReset) + newMasterLength = 1.0f;// 120 BPM + // rising edge detect + if (trigBpmInValue) { + if (!running) { + // this must be the only way to start runnning when in bpmDetectionMode or else + // when manually starting, the clock will not know which pulse is the 1st of a ppqn set + //runPulse.trigger(0.001f); don't need this since slaves will detect the same thing + running = true; + runPulse.trigger(0.001f); + resetClocked(); + } + if (running) { + extPulseNumber++; + if (extPulseNumber >= ppqn * 2)// *2 because working with double_periods + extPulseNumber = 0; + if (extPulseNumber == 0)// if first pulse, start interval timer + extIntervalTime = 0.0; + else { + // all other ppqn pulses except the first one. now we have an interval upon which to plan a strecth + double timeLeft = extIntervalTime * (double)(ppqn * 2 - extPulseNumber) / ((double)extPulseNumber); + newMasterLength = clk[0].getStep() + timeLeft; + timeoutTime = extIntervalTime * ((double)(1 + extPulseNumber) / ((double)extPulseNumber)) + 0.1; + } + } + } + if (running) { + extIntervalTime += sampleTime; + if (extIntervalTime > timeoutTime) { + running = false; + runPulse.trigger(0.001f); + resetClocked(); + if (emitResetOnStopRun) { + //resetLight = 1.0f; + resetPulse.trigger(0.001f); + } + } + } + } + // BPM CV method + else { + newMasterLength = 1.0f / powf(2.0f, bpmInValue);// bpm = 120*2^V, 2T = 120/bpm = 120/(120*2^V) = 1/2^V + // no need to round since this clocked's master's BPM knob is a snap knob thus already rounded, and with passthru approach, no cumul error + } + } + else { + newMasterLength = 120.0f / params[RATIO_PARAMS + 0].value;// already integer BPM since using snap knob + } + newMasterLength = clamp(newMasterLength, masterLengthMin, masterLengthMax); + if (scheduledReset) + masterLength = newMasterLength; + if (newMasterLength != masterLength) { + double lengthStretchFactor = ((double)newMasterLength) / ((double)masterLength); + for (int i = 0; i < 4; i++) { + clk[i].applyNewLength(lengthStretchFactor); + } + masterLength = newMasterLength; + } + + // Ratio knobs changed (setup a sync) + bool syncRatios[4] = {false, false, false, false};// 0 index unused + for (int i = 1; i < 4; i++) { + newRatiosDoubled[i] = getRatioDoubled(i); + if (scheduledReset) + ratiosDoubled[i] = newRatiosDoubled[i]; + if (newRatiosDoubled[i] != ratiosDoubled[i]) { + syncRatios[i] = true;// 0 index not used, but loop must start at i = 0 + } + } + + // Swing and delay changed (for swing and delay info), ignore CV inputs for the info process + for (int i = 0; i < 4; i++) { + float newSwingVal = params[SWING_PARAMS + i].value; + if (scheduledReset) { + swingInfo[i] = 0l; + swingVal[i] = newSwingVal; + } + if (newSwingVal != swingVal[i]) { + swingVal[i] = newSwingVal; + swingInfo[i] = (long) (swingInfoTime * (float)sampleRate);// trigger swing info on channel i + delayInfo[i] = 0l;// cancel delayed being displayed (if so) + } + if (i > 0) { + int newDelayKnobIndex = clamp((int) round( params[DELAY_PARAMS + i].value ), 0, 8 - 1); + if (scheduledReset) { + delayInfo[i] = 0l; + delayKnobIndexes[i] = newDelayKnobIndex; + } + if (newDelayKnobIndex != delayKnobIndexes[i]) { + delayKnobIndexes[i] = newDelayKnobIndex; + delayInfo[i] = (long) (delayInfoTime * (float)sampleRate);// trigger delay info on channel i + swingInfo[i] = 0l;// cancel swing being displayed (if so) + } + } + } + + + + //********** Clocks and Delays ********** + + // Clocks + if (running) { + // See if clocks finished their prescribed number of iteratios of double periods (and syncWait for sub) or + // were forced reset and if so, recalc and restart them + + // Master clock + if (clk[0].isReset()) { + // See if ratio knobs changed (or unitinialized) + for (int i = 1; i < 4; i++) { + if (syncRatios[i]) {// always false for master + clk[i].reset();// force reset (thus refresh) of that sub-clock + ratiosDoubled[i] = newRatiosDoubled[i]; + syncRatios[i] = false; + } + } + clk[0].setup(masterLength, 1, sampleTime);// must call setup before start. length = double_period + clk[0].start(); + } + + // Sub clocks + for (int i = 1; i < 4; i++) { + if (clk[i].isReset()) { + double length; + int iterations; + int ratioDoubled = ratiosDoubled[i]; + if (ratioDoubled < 0) { // if div + ratioDoubled *= -1; + length = masterLength * ((double)ratioDoubled) / 2.0; + iterations = 1l + (ratioDoubled % 2); + clk[i].setup(length, iterations, sampleTime); + } + else {// mult + length = (2.0f * masterLength) / ((double)ratioDoubled); + iterations = ratioDoubled / (2l - (ratioDoubled % 2l)); + clk[i].setup(length, iterations, sampleTime); + } + clk[i].start(); + } + } + + + // Write clk outputs into delay buffer + for (int i = 0; i < 4; i++) { + float pulseWidth = params[PW_PARAMS + i].value; + if (i < 3 && inputs[PW_INPUTS + i].active) { + pulseWidth += (inputs[PW_INPUTS + i].value / 10.0f) - 0.5f; + pulseWidth = clamp(pulseWidth, 0.0f, 1.0f); + } + float swingAmount = swingVal[i]; + if (i < 3 && inputs[SWING_INPUTS + i].active) { + swingAmount += (inputs[SWING_INPUTS + i].value / 5.0f) - 1.0f; + swingAmount = clamp(swingAmount, -1.0f, 1.0f); + } + delay[i].write(clk[i].isHigh(swingAmount, pulseWidth)); + } + + + + //********** Outputs and lights ********** + + // Clock outputs + for (int i = 0; i < 4; i++) { + long delaySamples = 0l; + if (i > 0) { + float delayFraction = delayValues[delayKnobIndexes[i]]; + float ratioValue = ((float)ratiosDoubled[i]) / 2.0f; + if (ratioValue < 0) + ratioValue = 1.0f / (-1.0f * ratioValue); + delaySamples = (long)(masterLength * delayFraction * sampleRate / (ratioValue * 2.0)); + } + outputs[CLK_OUTPUTS + i].value = delay[i].read(delaySamples) ? 10.0f : 0.0f; + } + } + else { + for (int i = 0; i < 4; i++) + outputs[CLK_OUTPUTS + i].value = 0.0f; + } + + // Chaining outputs + outputs[RESET_OUTPUT].value = (resetPulse.process((float)sampleTime) ? 10.0f : 0.0f); + outputs[RUN_OUTPUT].value = (runPulse.process((float)sampleTime) ? 10.0f : 0.0f); + outputs[BPM_OUTPUT].value = inputs[BPM_INPUT].active ? inputs[BPM_INPUT].value : log2f(1.0f / masterLength); + + // Reset light + lights[RESET_LIGHT].value = resetLight; + + // Run light + lights[RUN_LIGHT].value = running; + + // BPM light + if (cantRunWarning > 0l) { + bool warningFlashState = calcWarningFlash(cantRunWarning, cantRunWarningInit); + lights[CLK_LIGHTS + 0].value = (warningFlashState) ? 1.0f : 0.0f; + } + else + lights[CLK_LIGHTS + 0].value = (bpmDetectionMode && inputs[BPM_INPUT].active) ? 1.0f : 0.0f; + + // ratios synched lights + for (int i = 1; i < 4; i++) { + lights[CLK_LIGHTS + i].value = (syncRatios[i] && running) ? 1.0f: 0.0f; + } + + // Incr/decr all counters related to step() + for (int i = 0; i < 4; i++) { + clk[i].stepClock(); + if (swingInfo[i] > 0) + swingInfo[i]--; + if (delayInfo[i] > 0) + delayInfo[i]--; + } + if (cantRunWarning > 0l) + cantRunWarning--; + + scheduledReset = false; + } +}; + + +struct ClockedWidget : ModuleWidget { + Clocked *module; + DynamicSVGPanel *panel; + int oldExpansion; + int expWidth = 60; + IMPort* expPorts[5]; + + + struct RatioDisplayWidget : TransparentWidget { + Clocked *module; + int knobIndex; + std::shared_ptr font; + char displayStr[4]; + const std::string delayLabelsClock[8] = {" 0", "/16", "1/8", "1/4", "1/3", "1/2", "2/3", "3/4"}; + const std::string delayLabelsNote[8] = {" 0", "/64", "/32", "/16", "/8t", "1/8", "/4t", "/8d"}; + + + RatioDisplayWidget() { + 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); + if (module->swingInfo[knobIndex] > 0) + { + float swValue = module->swingVal[knobIndex]; + int swInt = (int)round(swValue * 99.0f); + snprintf(displayStr, 4, " %2u", (unsigned) abs(swInt)); + if (swInt < 0) + displayStr[0] = '-'; + if (swInt > 0) + displayStr[0] = '+'; + } + else if (module->delayInfo[knobIndex] > 0) + { + int delayKnobIndex = module->delayKnobIndexes[knobIndex]; + if (module->displayDelayNoteMode) + snprintf(displayStr, 4, "%s", (delayLabelsNote[delayKnobIndex]).c_str()); + else + snprintf(displayStr, 4, "%s", (delayLabelsClock[delayKnobIndex]).c_str()); + } + else { + if (knobIndex > 0) {// ratio to display + bool isDivision = false; + int ratioDoubled = module->newRatiosDoubled[knobIndex]; + if (ratioDoubled < 0) { + ratioDoubled = -1 * ratioDoubled; + isDivision = true; + } + if ( (ratioDoubled % 2) == 1 ) + snprintf(displayStr, 4, "%c,5", 0x30 + (char)(ratioDoubled / 2)); + else { + snprintf(displayStr, 4, "X%2u", (unsigned)(ratioDoubled / 2)); + if (isDivision) + displayStr[0] = '/'; + } + } + else {// BPM to display + snprintf(displayStr, 4, "%3u", (unsigned) round(120.0f / module->masterLength)); + } + } + displayStr[3] = 0;// more safety + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + struct PanelThemeItem : MenuItem { + Clocked *module; + int theme; + void onAction(EventAction &e) override { + module->panelTheme = theme; + } + void step() override { + rightText = (module->panelTheme == theme) ? "✔" : ""; + } + }; + struct ExpansionItem : MenuItem { + Clocked *module; + void onAction(EventAction &e) override { + module->expansion = module->expansion == 1 ? 0 : 1; + } + }; + struct DelayDisplayNoteItem : MenuItem { + Clocked *module; + void onAction(EventAction &e) override { + module->displayDelayNoteMode = !module->displayDelayNoteMode; + } + }; + struct BpmDetectionItem : MenuItem { + Clocked *module; + void onAction(EventAction &e) override { + module->bpmDetectionMode = !module->bpmDetectionMode; + if (module->bpmDetectionMode && module->running) { + module->running = false; + module->resetClocked(); + } + } + }; + struct EmitResetItem : MenuItem { + Clocked *module; + void onAction(EventAction &e) override { + module->emitResetOnStopRun = !module->emitResetOnStopRun; + } + }; + struct BpmPpqnItem : MenuItem { + Clocked *module; + int oldPpqn = -1; + void onAction(EventAction &e) override { + if (module->ppqn == 4) { + module->ppqn = 8; + } + else if (module->ppqn == 8) { + module->ppqn = 24; + } + else { + module->ppqn = 4; + } + } + void step() override { + if (oldPpqn != module->ppqn) { + oldPpqn = module->ppqn; + + if (oldPpqn == 4) { + text = "- BPM detection PPQN: <4>, 8, 24"; + } + else if (module->ppqn == 8) { + text = "- BPM detection PPQN: 4, <8>, 24"; + } + else if (module->ppqn == 24) { + text = "- BPM detection PPQN: 4, 8, <24>"; + } + else { + text = "- BPM detection PPQN: *error*"; + } + } + MenuItem::step(); + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + Clocked *module = dynamic_cast(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); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + DelayDisplayNoteItem *ddnItem = MenuItem::create("Display Delay Values in Notes", CHECKMARK(module->displayDelayNoteMode)); + ddnItem->module = module; + menu->addChild(ddnItem); + + EmitResetItem *erItem = MenuItem::create("Emit Reset when Run is Turned Off", CHECKMARK(module->emitResetOnStopRun)); + erItem->module = module; + menu->addChild(erItem); + + BpmDetectionItem *detectItem = MenuItem::create("Use BPM Detection (as opposed to BPM CV)", CHECKMARK(module->bpmDetectionMode)); + detectItem->module = module; + menu->addChild(detectItem); + + BpmPpqnItem *detect4Item = MenuItem::create("PPQN", CHECKMARK(false)); + detect4Item->module = module; + menu->addChild(detect4Item); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *expansionLabel = new MenuLabel(); + expansionLabel->text = "Expansion module"; + menu->addChild(expansionLabel); + + ExpansionItem *expItem = MenuItem::create(expansionMenuLabel, CHECKMARK(module->expansion != 0)); + expItem->module = module; + menu->addChild(expItem); + + return menu; + } + + void step() override { + if(module->expansion != oldExpansion) { + if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks + for (int i = 0; i < 5; i++) + rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); + } + oldExpansion = module->expansion; + } + box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth; + Widget::step(); + } + + ClockedWidget(Clocked *module) : ModuleWidget(module) { + this->module = module; + oldExpansion = -1; + + // Main panel from Inkscape + panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->expWidth = &expWidth; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/Clocked.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/Clocked_dark.svg"))); + box.size = panel->box.size; + box.size.x = box.size.x - (1 - module->expansion) * expWidth; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme)); + + + static const int rowRuler0 = 50;//reset,run inputs, master knob and bpm display + static const int rowRuler1 = rowRuler0 + 55;// reset,run switches + // + static const int rowRuler2 = rowRuler1 + 55;// clock 1 + static const int rowSpacingClks = 50; + static const int rowRuler5 = rowRuler2 + rowSpacingClks * 2 + 55;// reset,run outputs, pw inputs + + + static const int colRulerL = 18;// reset input and button, ratio knobs + // First two rows and last row + static const int colRulerSpacingT = 47; + static const int colRulerT1 = colRulerL + colRulerSpacingT;// run input and button + static const int colRulerT2 = colRulerT1 + colRulerSpacingT;// in and pwMaster inputs + static const int colRulerT3 = colRulerT2 + colRulerSpacingT + 5;// swingMaster knob + static const int colRulerT4 = colRulerT3 + colRulerSpacingT;// pwMaster knob + static const int colRulerT5 = colRulerT4 + colRulerSpacingT;// clkMaster output + // Three clock rows + static const int colRulerM0 = colRulerL + 5;// ratio knobs + static const int colRulerM1 = colRulerL + 60;// ratio displays + static const int colRulerM2 = colRulerT3;// swingX knobs + static const int colRulerM3 = colRulerT4;// pwX knobs + static const int colRulerM4 = colRulerT5;// clkX outputs + + RatioDisplayWidget *displayRatios[4]; + + // Row 0 + // Reset input + addInput(createDynamicPort(Vec(colRulerL, rowRuler0), Port::INPUT, module, Clocked::RESET_INPUT, &module->panelTheme)); + // Run input + addInput(createDynamicPort(Vec(colRulerT1, rowRuler0), Port::INPUT, module, Clocked::RUN_INPUT, &module->panelTheme)); + // In input + addInput(createDynamicPort(Vec(colRulerT2, rowRuler0), Port::INPUT, module, Clocked::BPM_INPUT, &module->panelTheme)); + // Master BPM knob + addParam(createDynamicParam(Vec(colRulerT3 + 1 + offsetIMBigKnob, rowRuler0 + offsetIMBigKnob), module, Clocked::RATIO_PARAMS + 0, (float)(module->bpmMin), (float)(module->bpmMax), 120.0f, &module->panelTheme));// must be a snap knob, code in step() assumes that a rounded value is read from the knob (chaining considerations vs BPM detect) + // BPM display + displayRatios[0] = new RatioDisplayWidget(); + displayRatios[0]->box.pos = Vec(colRulerT4 + 11, rowRuler0 + vOffsetDisplay); + displayRatios[0]->box.size = Vec(55, 30);// 3 characters + displayRatios[0]->module = module; + displayRatios[0]->knobIndex = 0; + addChild(displayRatios[0]); + // BPM external pulses lock light + addChild(ModuleLightWidget::create>(Vec(colRulerT4 + 11 + 62, rowRuler0 + 10), module, Clocked::CLK_LIGHTS + 0)); + + // Row 1 + // Reset LED bezel and light + addParam(ParamWidget::create(Vec(colRulerL + offsetLEDbezel, rowRuler1 + offsetLEDbezel), module, Clocked::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerL + offsetLEDbezel + offsetLEDbezelLight, rowRuler1 + offsetLEDbezel + offsetLEDbezelLight), module, Clocked::RESET_LIGHT)); + // Run LED bezel and light + addParam(ParamWidget::create(Vec(colRulerT1 + offsetLEDbezel, rowRuler1 + offsetLEDbezel), module, Clocked::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerT1 + offsetLEDbezel + offsetLEDbezelLight, rowRuler1 + offsetLEDbezel + offsetLEDbezelLight), module, Clocked::RUN_LIGHT)); + // PW master input + addInput(createDynamicPort(Vec(colRulerT2, rowRuler1), Port::INPUT, module, Clocked::PW_INPUTS + 0, &module->panelTheme)); + // Swing master knob + addParam(createDynamicParam(Vec(colRulerT3 + offsetIMSmallKnob, rowRuler1 + offsetIMSmallKnob), module, Clocked::SWING_PARAMS + 0, -1.0f, 1.0f, 0.0f, &module->panelTheme)); + // PW master knob + addParam(createDynamicParam(Vec(colRulerT4 + offsetIMSmallKnob, rowRuler1 + offsetIMSmallKnob), module, Clocked::PW_PARAMS + 0, 0.0f, 1.0f, 0.5f, &module->panelTheme)); + // Clock master out + addOutput(createDynamicPort(Vec(colRulerT5, rowRuler1), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 0, &module->panelTheme)); + + + // Row 2-4 (sub clocks) + for (int i = 0; i < 3; i++) { + // Ratio1 knob + addParam(createDynamicParam(Vec(colRulerM0 + offsetIMBigKnob, rowRuler2 + i * rowSpacingClks + offsetIMBigKnob), module, Clocked::RATIO_PARAMS + 1 + i, (34.0f - 1.0f)*-1.0f, 34.0f - 1.0f, 0.0f, &module->panelTheme)); + // Ratio display + displayRatios[i + 1] = new RatioDisplayWidget(); + displayRatios[i + 1]->box.pos = Vec(colRulerM1, rowRuler2 + i * rowSpacingClks + vOffsetDisplay); + displayRatios[i + 1]->box.size = Vec(55, 30);// 3 characters + displayRatios[i + 1]->module = module; + displayRatios[i + 1]->knobIndex = i + 1; + addChild(displayRatios[i + 1]); + // Sync light + addChild(ModuleLightWidget::create>(Vec(colRulerM1 + 62, rowRuler2 + i * rowSpacingClks + 10), module, Clocked::CLK_LIGHTS + i + 1)); + // Swing knobs + addParam(createDynamicParam(Vec(colRulerM2 + offsetIMSmallKnob, rowRuler2 + i * rowSpacingClks + offsetIMSmallKnob), module, Clocked::SWING_PARAMS + 1 + i, -1.0f, 1.0f, 0.0f, &module->panelTheme)); + // PW knobs + addParam(createDynamicParam(Vec(colRulerM3 + offsetIMSmallKnob, rowRuler2 + i * rowSpacingClks + offsetIMSmallKnob), module, Clocked::PW_PARAMS + 1 + i, 0.0f, 1.0f, 0.5f, &module->panelTheme)); + // Delay knobs + addParam(createDynamicParam(Vec(colRulerM4 + offsetIMSmallKnob, rowRuler2 + i * rowSpacingClks + offsetIMSmallKnob), module, Clocked::DELAY_PARAMS + 1 + i , 0.0f, 8.0f - 1.0f, 0.0f, &module->panelTheme)); + } + + // Last row + // Reset out + addOutput(createDynamicPort(Vec(colRulerL, rowRuler5), Port::OUTPUT, module, Clocked::RESET_OUTPUT, &module->panelTheme)); + // Run out + addOutput(createDynamicPort(Vec(colRulerT1, rowRuler5), Port::OUTPUT, module, Clocked::RUN_OUTPUT, &module->panelTheme)); + // Out out + addOutput(createDynamicPort(Vec(colRulerT2, rowRuler5), Port::OUTPUT, module, Clocked::BPM_OUTPUT, &module->panelTheme)); + // Sub-clock outputs + addOutput(createDynamicPort(Vec(colRulerT3, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 1, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT4, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 2, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerT5, rowRuler5), Port::OUTPUT, module, Clocked::CLK_OUTPUTS + 3, &module->panelTheme)); + + // Expansion module + static const int rowRulerExpTop = 65; + static const int rowSpacingExp = 60; + static const int colRulerExp = 497 - 30 -150;// Clocked is (2+10)HP less than PS32 + addInput(expPorts[0] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, Clocked::PW_INPUTS + 1, &module->panelTheme)); + addInput(expPorts[1] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, Clocked::PW_INPUTS + 2, &module->panelTheme)); + addInput(expPorts[2] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, Clocked::SWING_INPUTS + 0, &module->panelTheme)); + addInput(expPorts[3] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, Clocked::SWING_INPUTS + 1, &module->panelTheme)); + addInput(expPorts[4] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, Clocked::SWING_INPUTS + 2, &module->panelTheme)); + + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, Clocked) { + Model *modelClocked = Model::create("Impromptu Modular", "Clocked", "CLK - Clocked", CLOCK_TAG); + return modelClocked; +} + +/*CHANGE LOG + +0.6.9: +new approach to BPM Detection (all slaves must enable Use BPM Detect if master does, and same ppqn) +choice of 4, 8, 24 PPQN when using BPM detection +add sub-clock ratio of 24 (existing patches making use of greater than 23 mult or div will need to adjust) +add right click option for emit reset when run turned off + +0.6.8: +replace bit-ring-buffer delay engine with event-based delay engine +add BPM pulse frequency vs CV level option in right click settings +updated BPM CV levels (in, out) to new Rack standard for clock CVs + +0.6.7: +created + +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/EngTest1.cpp b/plugins/community/repos/ImpromptuModular/src/EngTest1.cpp new file mode 100644 index 00000000..60416cd8 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/EngTest1.cpp @@ -0,0 +1,202 @@ +//*********************************************************************************************** +//Engineering Test 1 +// +//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 +// +//Module concept by Marc Boulé +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + + +struct EngTest1 : Module { + enum ParamIds { + ENUMS(NOTETYPE_PARAMS, 5), + ENUMS(STEP_PARAMS, 24), + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(NOTETYPE_LIGHTS, 5), + ENUMS(STEP_LIGHTS, 24 * 2),// room for GreenRed + NUM_LIGHTS + }; + + + // Need to save + int panelTheme; + int noteType;// 0 is full note, 1 is half note, 2 is quarter note, etc... + uint8_t notes[24]; + + // No need to save + + + SchmittTrigger noteTypeTriggers[5]; + SchmittTrigger stepTriggers[24]; + + + EngTest1() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + panelTheme = 0; + for (int i = 0; i < 5; i++) + noteTypeTriggers[i].reset(); + for (int i = 0; i < 24; i++) + stepTriggers[i].reset(); + + onReset(); + } + + + // widgets are not yet created when module is created (and when onReset() is called by constructor) + // onReset() is also called when right-click initialization of module + void onReset() override { + noteType = 2; + for (int i = 0; i < 24; i++) { + notes[i] = (i / 6) * 6; + } + } + + + // widgets randomized before onRandomize() is called + void onRandomize() override { + + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + + return rootJ; + } + + + // widgets loaded before this fromJson() is called + void fromJson(json_t *rootJ) override { + + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + + // Step buttons + for (int i = 0; i < 24; i++) { + if (stepTriggers[i].process(params[STEP_PARAMS + i].value)) { + int count = 3 * (1 << (3 - noteType)); + int j; + for (j = 0; j < count; j++) { + if ((i + j) >= 24) + break; + notes[i + j] = i; + } + if ((i + j) < 24) { + int oldVal = notes[i + j]; + int newVal = i + j; + for (; j < 24; j++) {// j < 24 is safety + if ((i + j) >= 24) + break; + if (notes[i + j] == oldVal) + notes[i + j] = newVal; + } + } + } + } + + // Note type buttons + for (int i = 0; i < 5; i++) { + if (noteTypeTriggers[i].process(params[NOTETYPE_PARAMS + i].value)) { + noteType = i; + } + } + + + // Step lights + bool isGreen = true; + for (int i = 0; i < 24; i++) { + if (i > 0 && notes[i] != notes[i-1]) + isGreen = !isGreen; + setGreenRed(STEP_LIGHTS + i * 2, isGreen ? 1.0f : 0.0f, !isGreen ? 1.0f : 0.0f); + } + + // Note type lights + for (int i = 0; i < 5; i++) + lights[NOTETYPE_LIGHTS + i].value = (i == noteType ? 1.0f : 0.0f); + + }// step() + + + void setGreenRed(int id, float green, float red) { + lights[id + 0].value = green; + lights[id + 1].value = red; + } + +};// EngTest1 : module + +struct EngTest1Widget : ModuleWidget { + + EngTest1Widget(EngTest1 *module) : ModuleWidget(module) { + // Main panel from Inkscape + DynamicSVGPanel* panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/EngTest1.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/EngTest1_dark.svg"))); + box.size = panel->box.size; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 365), &module->panelTheme)); + + + // ****** Top portion (2 switches and LED button array ****** + + static const int rowRuler0 = 65; + static const int colRulerSteps = 15; + static const int spacingSteps = 20; + //static const int spacingSteps4 = 4; + + + // Step LED buttons + int posX = colRulerSteps; + for (int x = 0; x < 24; x++) { + addParam(ParamWidget::create(Vec(posX, rowRuler0 + 8 - 4.4f), module, EngTest1::STEP_PARAMS + x, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(posX + 4.4f, rowRuler0 + 8), module, EngTest1::STEP_LIGHTS + x * 2)); + posX += spacingSteps; + //if ((x + 1) % 4 == 0) + //posX += spacingSteps4; + } + + + // Note type buttons + static const int rowRuler1 = 160; + static const int posLEDvsButton = 25; + static const int spacingButtons = 40; + static const int columnRulerMB1 = colRulerSteps + 10; + for (int x = 0; x < 5; x++) { + addChild(ModuleLightWidget::create>(Vec(columnRulerMB1 + x * spacingButtons + offsetMediumLight, rowRuler1 + offsetMediumLight - posLEDvsButton), module, EngTest1::NOTETYPE_LIGHTS + x)); + addParam(createDynamicParam(Vec(columnRulerMB1 + x * spacingButtons + offsetCKD6b, rowRuler1 + offsetCKD6b), module, EngTest1::NOTETYPE_PARAMS + x, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + } + + } +}; + + +Model *modelEngTest1 = Model::create("Impromptu Modular", "Eng-Test-1", "??? - Eng-Test-1", BLANK_TAG); + +/*CHANGE LOG + +0.6.10: +created + +*/ \ No newline at end of file diff --git a/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.cpp b/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.cpp new file mode 100644 index 00000000..56f64b8e --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.cpp @@ -0,0 +1,165 @@ +//*********************************************************************************************** +//Impromptu Modular: Modules for VCV Rack by Marc Boulé +// +//This is code from the Fundamental plugin by Andrew Belt +//See ./LICENSE.txt for all licenses +//*********************************************************************************************** + + +#include "FundamentalUtil.hpp" + +namespace rack_plugin_ImpromptuModular { + +// From Fundamental VCF.cpp + +inline float clip(float x) { + return tanhf(x); +}; + +void LadderFilter::process(float input, float dt) { + ode::stepRK4(0.f, dt, state, 4, [&](float t, const float x[], float dxdt[]) { + float inputc = clip(input - resonance * x[3]); + float yc0 = clip(x[0]); + float yc1 = clip(x[1]); + float yc2 = clip(x[2]); + float yc3 = clip(x[3]); + + dxdt[0] = omega0 * (inputc - yc0); + dxdt[1] = omega0 * (yc0 - yc1); + dxdt[2] = omega0 * (yc1 - yc2); + dxdt[3] = omega0 * (yc2 - yc3); + }); + + lowpass = state[3]; + // TODO This is incorrect when `resonance > 0`. Is the math wrong? + highpass = clip((input - resonance*state[3]) - 4 * state[0] + 6*state[1] - 4*state[2] + state[3]); +}; + + + +// From Fundamental VCO.cpp + +void VoltageControlledOscillator::setPitch(float pitchKnob, float pitchCv) { + // Compute frequency + pitch = pitchKnob; + if (analog) { + // Apply pitch slew + const float pitchSlewAmount = 3.0f; + pitch += pitchSlew * pitchSlewAmount; + } + else { + // Quantize coarse knob if digital mode + pitch = roundf(pitch); + } + pitch += pitchCv; + // Note C4 + freq = 261.626f * powf(2.0f, pitch / 12.0f); +}; + +void VoltageControlledOscillator::setPulseWidth(float pulseWidth) { + const float pwMin = 0.01f; + pw = clamp(pulseWidth, pwMin, 1.0f - pwMin); +}; + +void VoltageControlledOscillator::process(float deltaTime, float syncValue) { + if (analog) { + // Adjust pitch slew + if (++pitchSlewIndex > 32) { + const float pitchSlewTau = 100.0f; // Time constant for leaky integrator in seconds + pitchSlew += (randomNormal() - pitchSlew / pitchSlewTau) * engineGetSampleTime(); + pitchSlewIndex = 0; + } + } + + // Advance phase + float deltaPhase = clamp(freq * deltaTime, 1e-6, 0.5f); + + // Detect sync + int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE) + float syncCrossing = 0.0f; // Offset that sync occurs [0.0f, 1.0f) + if (syncEnabled) { + syncValue -= 0.01f; + if (syncValue > 0.0f && lastSyncValue <= 0.0f) { + float deltaSync = syncValue - lastSyncValue; + syncCrossing = 1.0f - syncValue / deltaSync; + syncCrossing *= OVERSAMPLE; + syncIndex = (int)syncCrossing; + syncCrossing -= syncIndex; + } + lastSyncValue = syncValue; + } + + if (syncDirection) + deltaPhase *= -1.0f; + + sqrFilter.setCutoff(40.0f * deltaTime); + + for (int i = 0; i < OVERSAMPLE; i++) { + if (syncIndex == i) { + if (soft) { + syncDirection = !syncDirection; + deltaPhase *= -1.0f; + } + else { + // phase = syncCrossing * deltaPhase / OVERSAMPLE; + phase = 0.0f; + } + } + + if (analog) { + // Quadratic approximation of sine, slightly richer harmonics + if (phase < 0.5f) + sinBuffer[i] = 1.f - 16.f * powf(phase - 0.25f, 2); + else + sinBuffer[i] = -1.f + 16.f * powf(phase - 0.75f, 2); + sinBuffer[i] *= 1.08f; + } + else { + sinBuffer[i] = sinf(2.f*M_PI * phase); + } + if (analog) { + triBuffer[i] = 1.25f * interpolateLinear(triTable, phase * 2047.f); + } + else { + if (phase < 0.25f) + triBuffer[i] = 4.f * phase; + else if (phase < 0.75f) + triBuffer[i] = 2.f - 4.f * phase; + else + triBuffer[i] = -4.f + 4.f * phase; + } + if (analog) { + sawBuffer[i] = 1.66f * interpolateLinear(sawTable, phase * 2047.f); + } + else { + if (phase < 0.5f) + sawBuffer[i] = 2.f * phase; + else + sawBuffer[i] = -2.f + 2.f * phase; + } + sqrBuffer[i] = (phase < pw) ? 1.f : -1.f; + if (analog) { + // Simply filter here + sqrFilter.process(sqrBuffer[i]); + sqrBuffer[i] = 0.71f * sqrFilter.highpass(); + } + + // Advance phase + phase += deltaPhase / OVERSAMPLE; + phase = eucmod(phase, 1.0f); + } +}; + + + +// From Fundamental VCO.cpp + +float sawTable[2048] = { + 0.00221683, 0.00288535, 0.00382874, 0.00493397, 0.006088, 0.00717778, 0.0080962, 0.00887097, 0.00957292, 0.0102536, 0.0109644, 0.0117569, 0.0126765, 0.0137091, 0.014811, 0.0159388, 0.0170492, 0.0180987, 0.0190781, 0.0200243, 0.0209441, 0.0218439, 0.0227299, 0.023608, 0.0244664, 0.0253042, 0.0261303, 0.0269533, 0.0277823, 0.0286234, 0.0294678, 0.0303142, 0.0311635, 0.0320168, 0.0328749, 0.0337369, 0.0346001, 0.0354672, 0.0363411, 0.0372246, 0.0381206, 0.0390346, 0.039964, 0.0409017, 0.0418407, 0.042774, 0.0436961, 0.0446189, 0.0455409, 0.0464563, 0.047359, 0.0482433, 0.0491029, 0.049938, 0.0507563, 0.0515658, 0.0523744, 0.0531901, 0.0540067, 0.0548165, 0.0556269, 0.0564456, 0.0572799, 0.0581378, 0.0590252, 0.059934, 0.0608529, 0.0617707, 0.0626761, 0.0635623, 0.0644415, 0.0653142, 0.0661789, 0.0670339, 0.0678778, 0.0687058, 0.0695165, 0.070317, 0.0711147, 0.0719168, 0.0727304, 0.0735539, 0.0743828, 0.075215, 0.0760485, 0.0768811, 0.07771, 0.0785292, 0.0793449, 0.0801652, 0.080998, 0.0818513, 0.082737, 0.0836581, 0.0845968, 0.0855346, 0.0864532, 0.0873342, 0.0881769, 0.0889945, 0.0897932, 0.0905797, 0.0913603, 0.0921398, 0.0929034, 0.0936544, 0.0944023, 0.0951566, 0.0959267, 0.0967196, 0.0975297, 0.0983516, 0.09918, 0.10001, 0.100836, 0.101666, 0.102504, 0.103342, 0.104172, 0.104985, 0.105773, 0.106533, 0.107272, 0.107999, 0.108722, 0.109449, 0.110184, 0.110909, 0.11163, 0.112359, 0.113107, 0.113883, 0.114701, 0.115559, 0.116438, 0.117321, 0.118187, 0.119019, 0.11982, 0.120602, 0.121371, 0.122131, 0.122889, 0.123648, 0.124405, 0.125157, 0.125905, 0.126649, 0.127387, 0.128116, 0.128828, 0.129533, 0.130244, 0.130971, 0.131727, 0.132522, 0.133348, 0.134189, 0.13503, 0.135856, 0.136654, 0.137426, 0.138182, 0.13893, 0.13968, 0.140439, 0.141215, 0.142006, 0.142805, 0.143602, 0.144389, 0.145159, 0.145905, 0.146633, 0.147351, 0.148066, 0.148785, 0.149516, 0.150256, 0.151, 0.151747, 0.152495, 0.153242, 0.15399, 0.154742, 0.155496, 0.156248, 0.156992, 0.157725, 0.158443, 0.15915, 0.159849, 0.160542, 0.16123, 0.161916, 0.162592, 0.163258, 0.163921, 0.164589, 0.165267, 0.165962, 0.166664, 0.167374, 0.168094, 0.168827, 0.169575, 0.170347, 0.171149, 0.171967, 0.172788, 0.173595, 0.174376, 0.175127, 0.17586, 0.17658, 0.177293, 0.178003, 0.178717, 0.179439, 0.180163, 0.180879, 0.181579, 0.182254, 0.182887, 0.183461, 0.184006, 0.184553, 0.185136, 0.185786, 0.186519, 0.187312, 0.188146, 0.189001, 0.189859, 0.190701, 0.191556, 0.192426, 0.193296, 0.194149, 0.194968, 0.195739, 0.19647, 0.197172, 0.197852, 0.198518, 0.199177, 0.199827, 0.200455, 0.20107, 0.201683, 0.202303, 0.202941, 0.203594, 0.204255, 0.204924, 0.205599, 0.206279, 0.206967, 0.207672, 0.208389, 0.209106, 0.209812, 0.210497, 0.211154, 0.211791, 0.212413, 0.213022, 0.213622, 0.214216, 0.214789, 0.215338, 0.21588, 0.216432, 0.21701, 0.217632, 0.218309, 0.219022, 0.219748, 0.220466, 0.221152, 0.221789, 0.222391, 0.22297, 0.223536, 0.224097, 0.224665, 0.225227, 0.225769, 0.22631, 0.226866, 0.227457, 0.2281, 0.228809, 0.229566, 0.230349, 0.231134, 0.231897, 0.232619, 0.233316, 0.233998, 0.234672, 0.235345, 0.236023, 0.236715, 0.237415, 0.238118, 0.238815, 0.239499, 0.240162, 0.240802, 0.241424, 0.242035, 0.242638, 0.24324, 0.243846, 0.244452, 0.245055, 0.245654, 0.24625, 0.246841, 0.247425, 0.248001, 0.248572, 0.249141, 0.249711, 0.250285, 0.250867, 0.251454, 0.252043, 0.252627, 0.253202, 0.253762, 0.254314, 0.254859, 0.255395, 0.255918, 0.256428, 0.256916, 0.257372, 0.257809, 0.258245, 0.258695, 0.259177, 0.259689, 0.260217, 0.260762, 0.26133, 0.261924, 0.262549, 0.263221, 0.263934, 0.264667, 0.2654, 0.266114, 0.266792, 0.267451, 0.268097, 0.268732, 0.269357, 0.269973, 0.270576, 0.271158, 0.271729, 0.2723, 0.272879, 0.273479, 0.274107, 0.274757, 0.275414, 0.276063, 0.27669, 0.277282, 0.277837, 0.278368, 0.278887, 0.279406, 0.279937, 0.280489, 0.281057, 0.281633, 0.282207, 0.282772, 0.283319, 0.283845, 0.284355, 0.284855, 0.285351, 0.285848, 0.286353, 0.286861, 0.287368, 0.287877, 0.288389, 0.288904, 0.289424, 0.289941, 0.290461, 0.290987, 0.291524, 0.292076, 0.292648, 0.293238, 0.293841, 0.294449, 0.295058, 0.295663, 0.296276, 0.296899, 0.297519, 0.298127, 0.29871, 0.299257, 0.299765, 0.300247, 0.300716, 0.301185, 0.301669, 0.302178, 0.302707, 0.303244, 0.303774, 0.304286, 0.304765, 0.305192, 0.305574, 0.305941, 0.306323, 0.306748, 0.307245, 0.307827, 0.308466, 0.309123, 0.309764, 0.310351, 0.310859, 0.311312, 0.311727, 0.312122, 0.312512, 0.312915, 0.313329, 0.313739, 0.314148, 0.314558, 0.314972, 0.315393, 0.315812, 0.316231, 0.316656, 0.317095, 0.317554, 0.318043, 0.318572, 0.319125, 0.319683, 0.320227, 0.320739, 0.321207, 0.321641, 0.322057, 0.322467, 0.322886, 0.323327, 0.32378, 0.324239, 0.324707, 0.325188, 0.325686, 0.326205, 0.326755, 0.327327, 0.327907, 0.328484, 0.329045, 0.329585, 0.330116, 0.33064, 0.331156, 0.331664, 0.332165, 0.332653, 0.33313, 0.3336, 0.334068, 0.334539, 0.33502, 0.33552, 0.33603, 0.336536, 0.337024, 0.337481, 0.337893, 0.338261, 0.3386, 0.338927, 0.339258, 0.33961, 0.339982, 0.340358, 0.340742, 0.341136, 0.341541, 0.34196, 0.342407, 0.342874, 0.343348, 0.343815, 0.344261, 0.344675, 0.345061, 0.345429, 0.345787, 0.346144, 0.346507, 0.346879, 0.347253, 0.347628, 0.348002, 0.348377, 0.34875, 0.349118, 0.349481, 0.349845, 0.350216, 0.350597, 0.350995, 0.351411, 0.351838, 0.352273, 0.352708, 0.353139, 0.353563, 0.353984, 0.354404, 0.354824, 0.355243, 0.355661, 0.356077, 0.356489, 0.356901, 0.357316, 0.357737, 0.358168, 0.358608, 0.359055, 0.359506, 0.35996, 0.360414, 0.360869, 0.361328, 0.361789, 0.36225, 0.362706, 0.363154, 0.363596, 0.364034, 0.364466, 0.364893, 0.365313, 0.365724, 0.366133, 0.366538, 0.366935, 0.367317, 0.367681, 0.36802, 0.368324, 0.368606, 0.36888, 0.369159, 0.369458, 0.369786, 0.370135, 0.370494, 0.370854, 0.371206, 0.371541, 0.371852, 0.372145, 0.372432, 0.372726, 0.373037, 0.373377, 0.373753, 0.374151, 0.374555, 0.374948, 0.375316, 0.375645, 0.375944, 0.376225, 0.376497, 0.376772, 0.377061, 0.377367, 0.377681, 0.377998, 0.378314, 0.378622, 0.378916, 0.379197, 0.379469, 0.379735, 0.380002, 0.380274, 0.380552, 0.380831, 0.38111, 0.381395, 0.381687, 0.381992, 0.382311, 0.382642, 0.382983, 0.383329, 0.383676, 0.384021, 0.384367, 0.384716, 0.385066, 0.385417, 0.385767, 0.386116, 0.38647, 0.386824, 0.387177, 0.387522, 0.387858, 0.388187, 0.388517, 0.388841, 0.389149, 0.389433, 0.389684, 0.389883, 0.390037, 0.390171, 0.39031, 0.390476, 0.390693, 0.390945, 0.391221, 0.391516, 0.391825, 0.392141, 0.392465, 0.392808, 0.393164, 0.393524, 0.393881, 0.394226, 0.39456, 0.394891, 0.395217, 0.395538, 0.395851, 0.396157, 0.396451, 0.396736, 0.397016, 0.397296, 0.39758, 0.397874, 0.39818, 0.39849, 0.398797, 0.399095, 0.399375, 0.399634, 0.399877, 0.400107, 0.400331, 0.400554, 0.40078, 0.401001, 0.401216, 0.401431, 0.401652, 0.401885, 0.402136, 0.402411, 0.402699, 0.402992, 0.403279, 0.403551, 0.403804, 0.404051, 0.404289, 0.404515, 0.404728, 0.404924, 0.405086, 0.405215, 0.405334, 0.405465, 0.405629, 0.405848, 0.406118, 0.406424, 0.406751, 0.407085, 0.407412, 0.407728, 0.408054, 0.408384, 0.408707, 0.409011, 0.409286, 0.40952, 0.409718, 0.409896, 0.410074, 0.410268, 0.410495, 0.410756, 0.411038, 0.411329, 0.411618, 0.411891, 0.412139, 0.41237, 0.412589, 0.412802, 0.413013, 0.413229, 0.413447, 0.413663, 0.413878, 0.414097, 0.414321, 0.414556, 0.4148, 0.415051, 0.415309, 0.41557, 0.415834, 0.416101, 0.416385, 0.416677, 0.416965, 0.417238, 0.417483, 0.417691, 0.417871, 0.418029, 0.418173, 0.418311, 0.418451, 0.418583, 0.418702, 0.418816, 0.418934, 0.419064, 0.419214, 0.419382, 0.419563, 0.419752, 0.419942, 0.42013, 0.420312, 0.420491, 0.420669, 0.420849, 0.42103, 0.421215, 0.421399, 0.421577, 0.421758, 0.421949, 0.42216, 0.422397, 0.422673, 0.422978, 0.423295, 0.423607, 0.4239, 0.424162, 0.424418, 0.424664, 0.424892, 0.425091, 0.425255, 0.425365, 0.425417, 0.425437, 0.425453, 0.425491, 0.425579, 0.425726, 0.425913, 0.426116, 0.426313, 0.426481, 0.426598, 0.42666, 0.426688, 0.426709, 0.426745, 0.42682, 0.426953, 0.427125, 0.427325, 0.427539, 0.427754, 0.427957, 0.428158, 0.428365, 0.428571, 0.42877, 0.428954, 0.429116, 0.429254, 0.429373, 0.429483, 0.429592, 0.429707, 0.429833, 0.429956, 0.43008, 0.430211, 0.430356, 0.43052, 0.430711, 0.430923, 0.431151, 0.431385, 0.431618, 0.431843, 0.432075, 0.432315, 0.43255, 0.432772, 0.432969, 0.43313, 0.43326, 0.433365, 0.433455, 0.433539, 0.433624, 0.4337, 0.433748, 0.43379, 0.433848, 0.433947, 0.434111, 0.434373, 0.434713, 0.435083, 0.435437, 0.435726, 0.435907, 0.435996, 0.436022, 0.436011, 0.435989, 0.435982, 0.435999, 0.436012, 0.436022, 0.436035, 0.436054, 0.436083, 0.436125, 0.436177, 0.436234, 0.436294, 0.436352, 0.436403, 0.436434, 0.436453, 0.436478, 0.436526, 0.436614, 0.43676, 0.436966, 0.437211, 0.43747, 0.43772, 0.437936, 0.438113, 0.438274, 0.43842, 0.438554, 0.438679, 0.438796, 0.438898, 0.438986, 0.439067, 0.439145, 0.439229, 0.439323, 0.439421, 0.439522, 0.439625, 0.43973, 0.439836, 0.439943, 0.440053, 0.440164, 0.440277, 0.440389, 0.440502, 0.440621, 0.440748, 0.440873, 0.440989, 0.441088, 0.441163, 0.441213, 0.441245, 0.441266, 0.441282, 0.441302, 0.441323, 0.441334, 0.441341, 0.441354, 0.441383, 0.441436, 0.44152, 0.441628, 0.441752, 0.441883, 0.442013, 0.442135, 0.44226, 0.44239, 0.442518, 0.442638, 0.442745, 0.44283, 0.442892, 0.442939, 0.442982, 0.443033, 0.4431, 0.443186, 0.443279, 0.443381, 0.443494, 0.443621, 0.443764, 0.443936, 0.444132, 0.444338, 0.444539, 0.44472, 0.444871, 0.445005, 0.445125, 0.445231, 0.445322, 0.445397, 0.445451, 0.445479, 0.445491, 0.445496, 0.445505, 0.445526, 0.445556, 0.445586, 0.445621, 0.445663, 0.445715, 0.445779, 0.445858, 0.445947, 0.446044, 0.446145, 0.446248, 0.44635, 0.446452, 0.446557, 0.446667, 0.446784, 0.446909, 0.447046, 0.447194, 0.447348, 0.447505, 0.447661, 0.447814, 0.447987, 0.448173, 0.448351, 0.4485, 0.448599, 0.448623, 0.448552, 0.448426, 0.448289, 0.448186, 0.448161, 0.448239, 0.448392, 0.44859, 0.448805, 0.449007, 0.449168, 0.449302, 0.449424, 0.449536, 0.449637, 0.449728, 0.44981, 0.449878, 0.449934, 0.449982, 0.450025, 0.450064, 0.450101, 0.45013, 0.450154, 0.450176, 0.450199, 0.450225, 0.450252, 0.450279, 0.450307, 0.450338, 0.450373, 0.450413, 0.450461, 0.450514, 0.45057, 0.450625, 0.450677, 0.450725, 0.450775, 0.450825, 0.45087, 0.450906, 0.450929, 0.450938, 0.450934, 0.450921, 0.450901, 0.450877, 0.45085, 0.450811, 0.450761, 0.45071, 0.450664, 0.450633, 0.450621, 0.45062, 0.450628, 0.450649, 0.450683, 0.450732, 0.450802, 0.450892, 0.450997, 0.45111, 0.451225, 0.451334, 0.451451, 0.451579, 0.451705, 0.451819, 0.451909, 0.451964, 0.451985, 0.451981, 0.451963, 0.451939, 0.451919, 0.451906, 0.451887, 0.451865, 0.451845, 0.451832, 0.45183, 0.451841, 0.451861, 0.451888, 0.451919, 0.451949, 0.451977, 0.452007, 0.452039, 0.452071, 0.452102, 0.452129, 0.452153, 0.452177, 0.452199, 0.452217, 0.452231, 0.452237, 0.452233, 0.452219, 0.452199, 0.452179, 0.452163, 0.452155, 0.452151, 0.452149, 0.452152, 0.452159, 0.452173, 0.452194, 0.452227, 0.452267, 0.45231, 0.452352, 0.452388, 0.452422, 0.45246, 0.452497, 0.452525, 0.452538, 0.452531, 0.452507, 0.452471, 0.45242, 0.452351, 0.452262, 0.452146, 0.451988, 0.451802, 0.451605, 0.451418, 0.45126, 0.451143, 0.451054, 0.450984, 0.450924, 0.450866, 0.450801, 0.450725, 0.450645, 0.450566, 0.450496, 0.45044, 0.450406, 0.450404, 0.450422, 0.450449, 0.450469, 0.450469, 0.450438, 0.450381, 0.450308, 0.450229, 0.450154, 0.450093, 0.450049, 0.450013, 0.449983, 0.449957, 0.449933, 0.449908, 0.449892, 0.449885, 0.449877, 0.449858, 0.449822, 0.44976, 0.449688, 0.449602, 0.449498, 0.449371, 0.449217, 0.449034, 0.448826, 0.448593, 0.448337, 0.448059, 0.447758, 0.447436, 0.44709, 0.446721, 0.446331, 0.445918, 0.445483, 0.445027, 0.444548, 0.444047, 0.443524, 0.44298, 0.442413, 0.441818, 0.441202, 0.440567, 0.439919, 0.439262, 0.438614, 0.437974, 0.437319, 0.436626, 0.43587, 0.435049, 0.434402, 0.433835, 0.433123, 0.432039, 0.430358, 0.428368, 0.427503, 0.426601, 0.424216, 0.418905, 0.409225, 0.396402, 0.382576, 0.365547, 0.34307, 0.312906, 0.272788, 0.221013, 0.159633, 0.091601, 0.0198945, -0.0525338, -0.124172, -0.201123, -0.28145, -0.361514, -0.437677, -0.506323, -0.565947, -0.61981, -0.668566, -0.712743, -0.752877, -0.789483, -0.821637, -0.849041, -0.872562, -0.893078, -0.911459, -0.928371, -0.94253, -0.953999, -0.963373, -0.971244, -0.978205, -0.984408, -0.988963, -0.992177, -0.994451, -0.996186, -0.997786, -0.999106, -0.999791, -1.0, -0.999892, -0.999627, -0.99935, -0.998924, -0.998293, -0.997489, -0.996544, -0.99549, -0.994324, -0.992938, -0.9914, -0.989797, -0.988217, -0.98675, -0.985459, -0.984295, -0.983177, -0.982026, -0.98076, -0.9793, -0.977576, -0.975661, -0.973667, -0.971707, -0.969891, -0.968331, -0.967043, -0.965911, -0.964815, -0.963632, -0.962241, -0.960532, -0.958548, -0.956417, -0.954273, -0.952245, -0.950466, -0.948995, -0.947739, -0.946587, -0.94543, -0.944156, -0.942662, -0.940966, -0.939147, -0.937271, -0.935407, -0.933622, -0.931964, -0.930389, -0.928864, -0.927356, -0.925834, -0.924266, -0.922637, -0.920969, -0.919284, -0.917603, -0.915947, -0.914338, -0.912774, -0.91124, -0.909724, -0.908215, -0.9067, -0.905174, -0.903657, -0.902144, -0.900623, -0.899085, -0.897519, -0.895907, -0.894246, -0.892562, -0.890886, -0.889245, -0.887668, -0.886183, -0.884763, -0.883367, -0.881952, -0.880476, -0.878893, -0.877185, -0.875402, -0.873605, -0.871856, -0.870213, -0.868734, -0.867403, -0.866151, -0.864909, -0.863607, -0.862175, -0.86057, -0.85884, -0.857056, -0.855289, -0.853607, -0.852081, -0.850725, -0.849477, -0.848268, -0.847031, -0.845696, -0.844208, -0.842597, -0.840914, -0.839205, -0.83752, -0.835907, -0.834385, -0.83292, -0.831484, -0.830055, -0.828605, -0.827111, -0.825575, -0.824013, -0.82244, -0.820868, -0.81931, -0.817777, -0.816267, -0.814769, -0.813273, -0.811771, -0.810251, -0.808705, -0.807134, -0.805552, -0.803974, -0.802414, -0.800886, -0.799393, -0.797924, -0.796471, -0.795025, -0.793575, -0.792118, -0.790676, -0.78924, -0.787798, -0.786332, -0.784828, -0.78326, -0.781618, -0.77994, -0.778267, -0.776639, -0.775098, -0.773676, -0.772344, -0.771056, -0.769763, -0.768417, -0.766973, -0.765427, -0.763815, -0.762179, -0.760556, -0.758986, -0.757505, -0.756108, -0.754755, -0.753405, -0.752018, -0.750554, -0.748967, -0.747275, -0.745539, -0.743822, -0.742186, -0.740694, -0.739378, -0.738183, -0.737041, -0.735882, -0.734639, -0.73325, -0.731736, -0.730147, -0.728531, -0.726936, -0.725409, -0.723977, -0.722606, -0.721272, -0.719951, -0.718621, -0.717259, -0.715868, -0.714464, -0.713052, -0.711638, -0.710226, -0.708824, -0.707431, -0.706042, -0.704652, -0.703255, -0.701847, -0.700422, -0.698983, -0.697535, -0.696081, -0.694629, -0.693182, -0.691736, -0.690286, -0.688838, -0.687396, -0.685967, -0.684557, -0.683176, -0.681815, -0.680459, -0.679094, -0.677703, -0.676271, -0.674794, -0.673291, -0.671787, -0.670306, -0.668872, -0.66751, -0.666213, -0.664942, -0.663664, -0.66234, -0.660935, -0.659418, -0.657823, -0.656203, -0.654607, -0.653087, -0.651693, -0.650422, -0.649228, -0.648061, -0.64687, -0.645608, -0.64424, -0.642798, -0.641314, -0.639821, -0.638352, -0.636938, -0.635586, -0.634273, -0.63298, -0.631688, -0.630378, -0.629033, -0.627661, -0.626273, -0.624876, -0.623477, -0.622085, -0.620705, -0.619334, -0.617966, -0.616596, -0.615218, -0.613827, -0.612416, -0.61099, -0.609555, -0.608122, -0.606697, -0.60529, -0.603898, -0.602516, -0.60114, -0.599766, -0.598392, -0.597014, -0.595638, -0.594262, -0.592886, -0.59151, -0.590132, -0.588747, -0.587351, -0.585953, -0.584565, -0.583196, -0.581857, -0.580561, -0.579296, -0.578045, -0.576789, -0.575509, -0.574187, -0.572823, -0.571433, -0.570038, -0.568655, -0.567304, -0.566, -0.564732, -0.563486, -0.562244, -0.560992, -0.559712, -0.558394, -0.557048, -0.555694, -0.55435, -0.553035, -0.551767, -0.550554, -0.549377, -0.548213, -0.547039, -0.545833, -0.544579, -0.543296, -0.541991, -0.540673, -0.539347, -0.538023, -0.536698, -0.535368, -0.534033, -0.532691, -0.531345, -0.529992, -0.528625, -0.527247, -0.525867, -0.524493, -0.523133, -0.521796, -0.520479, -0.519176, -0.517878, -0.51658, -0.515273, -0.513957, -0.512638, -0.511316, -0.509991, -0.508665, -0.507338, -0.506007, -0.504673, -0.503337, -0.502003, -0.500672, -0.499347, -0.498028, -0.496714, -0.495401, -0.494085, -0.492762, -0.491429, -0.490084, -0.488734, -0.487386, -0.486047, -0.484726, -0.483416, -0.482114, -0.480822, -0.479543, -0.478281, -0.477039, -0.475822, -0.474626, -0.473441, -0.47226, -0.471073, -0.469878, -0.468688, -0.467499, -0.466305, -0.465101, -0.463881, -0.462642, -0.461385, -0.460117, -0.458846, -0.457581, -0.456327, -0.455092, -0.453867, -0.452643, -0.451408, -0.450154, -0.448868, -0.447541, -0.44619, -0.444838, -0.443506, -0.442214, -0.44098, -0.439791, -0.438628, -0.437473, -0.436306, -0.43511, -0.433885, -0.432645, -0.431396, -0.430143, -0.428894, -0.427652, -0.426416, -0.425183, -0.423949, -0.422711, -0.421466, -0.420212, -0.418951, -0.417684, -0.416414, -0.415141, -0.413868, -0.412589, -0.411302, -0.410015, -0.408732, -0.407459, -0.406204, -0.404969, -0.403749, -0.402535, -0.401316, -0.400084, -0.398829, -0.397551, -0.396262, -0.394973, -0.393697, -0.392445, -0.391224, -0.390027, -0.388843, -0.387665, -0.386484, -0.385291, -0.384089, -0.382883, -0.381676, -0.380469, -0.379266, -0.378068, -0.37687, -0.375674, -0.374482, -0.373293, -0.372111, -0.370936, -0.369768, -0.368605, -0.367445, -0.366287, -0.365128, -0.36397, -0.362812, -0.361657, -0.360503, -0.359351, -0.358203, -0.357061, -0.355922, -0.354784, -0.353643, -0.352495, -0.351336, -0.350168, -0.348994, -0.34782, -0.346649, -0.345486, -0.344334, -0.343189, -0.342048, -0.340906, -0.339762, -0.338611, -0.337458, -0.336305, -0.335148, -0.333984, -0.33281, -0.331624, -0.330426, -0.329218, -0.328005, -0.326791, -0.32558, -0.324371, -0.323158, -0.321945, -0.320736, -0.319535, -0.318345, -0.317165, -0.315991, -0.314825, -0.313666, -0.312516, -0.311376, -0.310249, -0.309132, -0.30802, -0.30691, -0.305796, -0.304683, -0.30358, -0.302479, -0.301367, -0.300236, -0.299074, -0.297859, -0.296599, -0.295325, -0.294068, -0.292855, -0.291717, -0.290659, -0.289654, -0.28867, -0.287676, -0.286641, -0.28555, -0.284431, -0.28329, -0.282128, -0.280946, -0.279746, -0.278513, -0.277246, -0.27596, -0.274674, -0.273405, -0.272167, -0.270948, -0.26974, -0.268546, -0.26737, -0.266215, -0.265084, -0.263983, -0.262904, -0.261837, -0.260774, -0.259706, -0.258637, -0.257578, -0.256522, -0.255458, -0.254379, -0.253276, -0.252151, -0.251008, -0.249851, -0.248684, -0.247511, -0.246333, -0.245133, -0.243922, -0.242709, -0.241508, -0.240329, -0.239178, -0.238042, -0.236921, -0.235815, -0.234725, -0.233651, -0.232605, -0.231587, -0.230582, -0.229575, -0.228551, -0.227496, -0.226409, -0.225303, -0.22419, -0.223083, -0.221996, -0.22094, -0.219911, -0.218897, -0.217885, -0.216863, -0.215818, -0.214744, -0.213651, -0.212546, -0.211438, -0.210337, -0.209249, -0.208177, -0.207112, -0.20605, -0.204984, -0.203909, -0.202819, -0.201716, -0.200605, -0.19949, -0.198377, -0.197271, -0.196173, -0.195078, -0.193985, -0.192896, -0.191809, -0.190725, -0.189653, -0.188591, -0.187529, -0.186458, -0.185368, -0.18425, -0.183097, -0.181922, -0.180741, -0.179569, -0.17842, -0.1773, -0.17619, -0.175094, -0.174015, -0.172958, -0.171927, -0.170938, -0.169986, -0.169054, -0.168122, -0.167172, -0.166186, -0.165179, -0.164157, -0.163123, -0.162077, -0.161021, -0.159952, -0.158857, -0.157749, -0.15664, -0.155542, -0.15447, -0.153432, -0.152424, -0.151428, -0.150429, -0.149413, -0.148364, -0.147283, -0.146183, -0.145069, -0.14395, -0.142834, -0.141724, -0.140608, -0.139489, -0.138373, -0.137265, -0.136171, -0.135093, -0.134025, -0.132968, -0.131919, -0.130878, -0.129844, -0.128821, -0.127809, -0.126804, -0.125801, -0.124797, -0.123787, -0.122773, -0.121758, -0.120744, -0.119733, -0.118728, -0.117733, -0.11675, -0.115771, -0.114792, -0.113806, -0.112807, -0.111794, -0.110771, -0.109741, -0.108707, -0.107672, -0.106636, -0.105591, -0.104541, -0.103491, -0.102448, -0.10142, -0.100412, -0.0994198, -0.0984399, -0.0974673, -0.0964976, -0.0955265, -0.0945581, -0.0935982, -0.0926402, -0.0916777, -0.0907045, -0.0897139, -0.0887001, -0.0876694, -0.0866321, -0.0855982, -0.0845778, -0.0835811, -0.0826095, -0.0816517, -0.0806952, -0.0797277, -0.0787369, -0.0777107, -0.0766526, -0.075579, -0.0745061, -0.0734503, -0.0724281, -0.0714473, -0.0704955, -0.0695569, -0.0686153, -0.067655, -0.0666595, -0.0656228, -0.0645626, -0.0634995, -0.0624539, -0.0614458, -0.0604904, -0.0595764, -0.0586898, -0.0578174, -0.0569453, -0.0560601, -0.0551719, -0.0542917, -0.0534099, -0.0525163, -0.0516014, -0.0506555, -0.0496757, -0.0486708, -0.0476511, -0.0466261, -0.0456061, -0.044596, -0.0435836, -0.0425703, -0.0415587, -0.040552, -0.0395533, -0.0385637, -0.0375815, -0.0366045, -0.0356314, -0.0346604, -0.0336902, -0.0327247, -0.0317635, -0.0308037, -0.0298422, -0.0288762, -0.0279032, -0.026925, -0.025943, -0.0249582, -0.0239714, -0.0229839, -0.0219896, -0.0209822, -0.0199722, -0.0189704, -0.0179881, -0.0170357, -0.0161152, -0.0152188, -0.0143404, -0.0134736, -0.0126123, -0.0117534, -0.0109179, -0.0100977, -0.00927668, -0.00843894, -0.0075681, -0.00664104, -0.0056472, -0.00462252, -0.00360524, -0.00263304, -0.00174417 +}; + +float triTable[2048] = { + 0.00205034, 0.00360933, 0.00578754, 0.00835358, 0.0110761, 0.0137236, 0.0160981, 0.0183687, 0.020617, 0.0228593, 0.0251122, 0.027392, 0.0297206, 0.0321003, 0.034499, 0.0368839, 0.0392222, 0.0414812, 0.0436497, 0.0457532, 0.0478204, 0.0498797, 0.0519597, 0.0540801, 0.0562156, 0.0583599, 0.0605111, 0.0626673, 0.0648265, 0.0669885, 0.069155, 0.0713261, 0.0735021, 0.0756831, 0.0778692, 0.0800601, 0.0822559, 0.0844568, 0.086663, 0.0888749, 0.091096, 0.0933308, 0.095572, 0.097812, 0.100043, 0.102257, 0.104463, 0.106663, 0.108853, 0.111028, 0.113181, 0.115309, 0.11741, 0.11949, 0.121555, 0.123613, 0.125667, 0.127712, 0.129738, 0.131757, 0.133783, 0.135828, 0.137904, 0.140002, 0.142116, 0.144248, 0.146397, 0.148564, 0.150756, 0.152981, 0.155227, 0.157478, 0.159719, 0.161935, 0.164142, 0.166348, 0.16854, 0.170706, 0.172831, 0.174903, 0.176915, 0.178884, 0.180829, 0.18277, 0.184725, 0.186695, 0.188658, 0.190622, 0.192593, 0.194577, 0.196582, 0.198611, 0.200659, 0.202715, 0.204773, 0.206824, 0.208863, 0.210895, 0.212924, 0.214952, 0.216985, 0.219024, 0.221064, 0.223103, 0.225147, 0.227201, 0.229273, 0.231368, 0.23349, 0.235633, 0.237784, 0.239934, 0.242072, 0.244201, 0.246336, 0.248466, 0.250583, 0.252677, 0.254738, 0.256759, 0.258749, 0.260722, 0.26269, 0.264667, 0.26666, 0.268659, 0.27066, 0.272662, 0.274664, 0.276664, 0.278666, 0.280672, 0.282676, 0.284675, 0.286663, 0.288636, 0.290596, 0.292546, 0.294487, 0.296422, 0.298354, 0.300276, 0.30218, 0.304077, 0.305982, 0.307905, 0.309861, 0.311873, 0.313923, 0.315983, 0.318021, 0.320007, 0.321913, 0.323747, 0.325536, 0.327304, 0.329078, 0.330883, 0.33272, 0.334568, 0.336425, 0.338291, 0.340166, 0.342047, 0.34393, 0.345818, 0.347717, 0.349633, 0.351571, 0.353541, 0.355551, 0.357581, 0.359612, 0.361626, 0.363604, 0.365556, 0.367494, 0.369414, 0.371311, 0.373181, 0.375019, 0.376812, 0.378576, 0.380325, 0.382078, 0.38385, 0.385646, 0.387452, 0.389264, 0.391078, 0.39289, 0.394698, 0.396507, 0.398318, 0.400125, 0.401927, 0.403717, 0.405494, 0.407256, 0.40901, 0.41076, 0.41251, 0.414267, 0.416024, 0.41778, 0.419538, 0.421301, 0.423072, 0.424857, 0.426659, 0.428472, 0.430287, 0.432097, 0.433893, 0.435678, 0.437459, 0.439233, 0.440997, 0.442746, 0.444477, 0.446184, 0.44787, 0.449548, 0.451226, 0.452915, 0.454622, 0.456343, 0.45807, 0.459797, 0.461518, 0.463227, 0.464921, 0.466606, 0.468285, 0.469962, 0.47164, 0.473324, 0.475015, 0.476707, 0.478398, 0.480083, 0.481759, 0.483424, 0.485082, 0.486734, 0.488379, 0.490016, 0.491647, 0.493268, 0.49488, 0.496487, 0.49809, 0.499692, 0.501294, 0.502892, 0.504487, 0.506082, 0.507679, 0.509283, 0.510886, 0.512485, 0.514089, 0.515705, 0.51734, 0.519007, 0.520729, 0.522484, 0.524239, 0.52596, 0.527614, 0.529175, 0.530657, 0.532094, 0.533515, 0.534954, 0.53644, 0.537952, 0.539475, 0.541016, 0.542579, 0.544171, 0.545804, 0.547497, 0.549225, 0.550958, 0.552666, 0.554319, 0.555907, 0.557451, 0.558968, 0.560474, 0.561985, 0.563516, 0.565063, 0.566615, 0.568164, 0.569701, 0.571217, 0.572711, 0.574192, 0.575659, 0.577106, 0.578532, 0.579932, 0.581296, 0.582629, 0.583943, 0.585252, 0.586569, 0.5879, 0.589228, 0.590557, 0.591892, 0.593238, 0.594602, 0.595985, 0.597385, 0.598797, 0.600218, 0.601643, 0.603069, 0.604501, 0.605938, 0.60738, 0.608827, 0.610277, 0.61173, 0.613187, 0.614649, 0.616114, 0.617584, 0.619058, 0.620545, 0.622044, 0.623545, 0.625037, 0.62651, 0.627958, 0.629403, 0.630838, 0.632249, 0.633624, 0.634949, 0.636201, 0.63738, 0.63852, 0.639657, 0.640828, 0.642065, 0.643361, 0.644693, 0.646047, 0.647405, 0.648753, 0.650083, 0.651415, 0.652746, 0.654072, 0.655389, 0.656694, 0.65799, 0.659279, 0.660559, 0.661826, 0.663078, 0.664312, 0.665526, 0.666724, 0.667911, 0.66909, 0.670267, 0.671435, 0.672582, 0.673724, 0.674874, 0.676046, 0.677253, 0.678497, 0.679767, 0.681053, 0.682348, 0.683643, 0.684935, 0.686242, 0.687554, 0.688863, 0.690158, 0.691429, 0.692672, 0.693896, 0.695105, 0.696306, 0.697506, 0.698708, 0.699909, 0.701106, 0.7023, 0.703489, 0.704673, 0.705857, 0.707048, 0.708236, 0.709408, 0.710553, 0.711661, 0.712721, 0.713743, 0.714746, 0.715747, 0.716765, 0.71781, 0.718864, 0.719926, 0.720996, 0.722076, 0.723167, 0.724272, 0.725391, 0.726519, 0.727651, 0.728784, 0.729915, 0.731064, 0.732222, 0.733373, 0.734501, 0.73559, 0.736625, 0.737607, 0.738559, 0.739503, 0.740458, 0.741448, 0.742462, 0.74349, 0.744529, 0.745577, 0.746634, 0.747701, 0.748797, 0.749906, 0.751012, 0.752098, 0.753145, 0.754144, 0.755107, 0.756048, 0.756979, 0.757913, 0.758861, 0.759812, 0.760762, 0.761714, 0.76267, 0.763634, 0.76461, 0.765599, 0.766594, 0.76759, 0.768579, 0.769555, 0.770513, 0.77146, 0.772402, 0.773347, 0.774301, 0.775274, 0.776266, 0.777269, 0.778272, 0.779266, 0.780239, 0.781189, 0.782124, 0.783047, 0.783961, 0.784869, 0.785775, 0.786678, 0.787576, 0.788467, 0.78935, 0.790223, 0.791082, 0.791926, 0.79276, 0.793589, 0.794419, 0.795256, 0.796098, 0.79694, 0.797783, 0.79863, 0.799482, 0.80034, 0.801205, 0.802075, 0.802947, 0.80382, 0.804691, 0.805566, 0.806452, 0.807338, 0.808211, 0.80906, 0.809874, 0.810645, 0.811386, 0.81211, 0.812829, 0.813558, 0.814304, 0.815056, 0.815811, 0.816566, 0.81732, 0.818071, 0.818818, 0.819561, 0.820303, 0.821046, 0.821791, 0.82254, 0.823293, 0.824047, 0.824803, 0.825561, 0.82632, 0.827082, 0.827848, 0.828615, 0.829381, 0.830142, 0.830896, 0.83163, 0.832351, 0.833072, 0.833807, 0.83457, 0.835373, 0.83622, 0.837092, 0.837972, 0.838841, 0.839683, 0.840509, 0.841335, 0.842146, 0.842929, 0.84367, 0.844352, 0.844957, 0.845509, 0.846038, 0.846575, 0.847151, 0.847775, 0.848416, 0.849075, 0.849755, 0.850456, 0.851183, 0.85195, 0.852752, 0.85357, 0.854387, 0.855184, 0.855947, 0.856677, 0.857391, 0.858105, 0.858834, 0.859596, 0.860406, 0.861255, 0.862121, 0.86298, 0.863811, 0.864592, 0.86533, 0.86604, 0.866731, 0.867413, 0.868096, 0.868783, 0.869464, 0.87014, 0.870811, 0.871481, 0.872149, 0.872817, 0.873483, 0.874146, 0.874806, 0.875459, 0.876105, 0.876739, 0.877367, 0.877993, 0.878622, 0.879259, 0.879906, 0.880561, 0.88122, 0.881879, 0.882535, 0.883182, 0.883819, 0.884449, 0.885079, 0.885715, 0.886363, 0.887038, 0.887757, 0.888489, 0.889203, 0.889868, 0.890451, 0.890953, 0.891393, 0.891786, 0.892143, 0.892477, 0.8928, 0.89311, 0.893397, 0.893649, 0.893858, 0.894011, 0.894122, 0.894207, 0.894245, 0.894213, 0.894089, 0.893852, 0.893506, 0.893064, 0.892538, 0.891937, 0.891272, 0.890547, 0.889748, 0.888879, 0.887944, 0.886946, 0.885891, 0.884773, 0.883588, 0.882343, 0.881043, 0.879694, 0.878299, 0.876848, 0.875342, 0.87379, 0.872197, 0.87057, 0.868908, 0.867204, 0.86546, 0.863682, 0.861874, 0.860039, 0.858177, 0.856286, 0.854363, 0.852406, 0.850414, 0.848385, 0.846323, 0.844227, 0.842095, 0.839925, 0.837715, 0.835457, 0.83315, 0.830807, 0.82844, 0.826061, 0.82368, 0.821277, 0.818856, 0.816424, 0.81399, 0.811562, 0.80915, 0.806747, 0.804345, 0.801937, 0.799514, 0.797067, 0.7946, 0.792119, 0.789623, 0.787112, 0.784586, 0.782038, 0.779443, 0.776826, 0.774214, 0.771636, 0.76912, 0.766686, 0.764314, 0.761978, 0.759652, 0.757309, 0.754927, 0.752535, 0.750137, 0.747724, 0.745288, 0.742821, 0.740305, 0.737727, 0.73512, 0.732517, 0.72995, 0.727452, 0.725032, 0.722667, 0.72033, 0.717997, 0.715644, 0.713254, 0.710851, 0.708438, 0.706014, 0.703575, 0.701121, 0.698641, 0.696136, 0.693616, 0.691095, 0.688585, 0.686098, 0.683641, 0.6812, 0.678761, 0.676308, 0.673827, 0.671306, 0.668755, 0.666184, 0.663599, 0.661012, 0.65843, 0.655845, 0.653253, 0.65066, 0.64807, 0.645488, 0.64292, 0.640369, 0.637827, 0.635287, 0.632742, 0.630183, 0.6276, 0.624998, 0.622388, 0.619784, 0.617199, 0.614645, 0.612122, 0.60962, 0.607132, 0.60465, 0.602168, 0.599682, 0.597203, 0.594727, 0.592252, 0.589774, 0.58729, 0.584793, 0.582286, 0.579778, 0.577277, 0.57479, 0.572324, 0.569874, 0.567436, 0.56501, 0.562592, 0.560181, 0.557782, 0.555401, 0.553029, 0.550655, 0.548272, 0.545869, 0.543444, 0.541004, 0.538559, 0.536116, 0.533683, 0.531266, 0.52886, 0.52646, 0.524064, 0.521671, 0.519279, 0.516893, 0.514518, 0.512143, 0.50976, 0.507362, 0.504938, 0.502484, 0.50001, 0.497529, 0.495055, 0.4926, 0.490175, 0.487775, 0.485386, 0.482995, 0.480589, 0.478154, 0.475672, 0.473159, 0.47064, 0.468141, 0.46569, 0.463311, 0.460999, 0.458729, 0.456477, 0.454215, 0.451918, 0.449584, 0.44723, 0.444865, 0.442494, 0.440126, 0.437766, 0.43542, 0.433079, 0.430733, 0.428371, 0.425982, 0.423555, 0.421087, 0.418597, 0.416105, 0.413627, 0.411182, 0.408763, 0.406361, 0.403972, 0.401595, 0.399227, 0.396868, 0.394526, 0.392195, 0.389871, 0.387547, 0.385218, 0.382886, 0.380556, 0.378225, 0.375891, 0.373548, 0.371195, 0.368828, 0.366452, 0.364071, 0.361691, 0.359317, 0.356948, 0.354579, 0.352212, 0.349851, 0.347498, 0.345159, 0.342839, 0.340534, 0.338236, 0.335938, 0.333632, 0.331313, 0.328992, 0.326667, 0.324335, 0.321992, 0.319635, 0.317258, 0.314862, 0.312455, 0.310044, 0.307636, 0.305238, 0.30284, 0.300442, 0.298047, 0.29566, 0.293284, 0.290916, 0.288546, 0.286183, 0.283838, 0.281522, 0.279246, 0.277034, 0.274878, 0.272745, 0.270602, 0.268418, 0.266164, 0.263851, 0.261499, 0.259129, 0.256758, 0.254405, 0.25208, 0.249766, 0.247457, 0.245146, 0.242826, 0.240491, 0.238132, 0.235759, 0.233382, 0.231014, 0.228664, 0.22634, 0.224037, 0.221748, 0.219468, 0.217193, 0.214919, 0.212651, 0.210391, 0.208136, 0.205878, 0.203614, 0.201338, 0.19905, 0.196756, 0.194458, 0.192161, 0.189868, 0.187578, 0.185288, 0.182999, 0.180713, 0.178434, 0.176164, 0.173903, 0.171648, 0.1694, 0.167155, 0.164912, 0.162674, 0.16045, 0.158231, 0.156008, 0.153768, 0.151502, 0.149211, 0.146903, 0.144578, 0.142234, 0.139871, 0.137483, 0.135038, 0.132556, 0.130074, 0.127626, 0.125247, 0.12297, 0.120783, 0.118646, 0.116519, 0.114362, 0.112133, 0.109827, 0.107474, 0.105099, 0.102728, 0.100388, 0.0980987, 0.0958451, 0.0936141, 0.0913946, 0.0891749, 0.0869439, 0.084699, 0.0824483, 0.0801958, 0.0779458, 0.0757025, 0.073469, 0.0712418, 0.06902, 0.066804, 0.0645945, 0.0623925, 0.0602031, 0.0580333, 0.0558718, 0.0537071, 0.0515269, 0.0493195, 0.0470875, 0.0448382, 0.0425741, 0.0402977, 0.0380118, 0.0357137, 0.0333861, 0.0310419, 0.028698, 0.0263721, 0.0240815, 0.0218298, 0.019603, 0.0173978, 0.015211, 0.013039, 0.0108804, 0.00875302, 0.00665008, 0.00455578, 0.00245417, 0.000329488, -0.00183022, -0.00401578, -0.00621704, -0.00842485, -0.0106295, -0.0128215, -0.0149995, -0.0171705, -0.019339, -0.0215093, -0.0236866, -0.0258755, -0.0280772, -0.0302866, -0.0324974, -0.0347034, -0.0368983, -0.0390758, -0.0412378, -0.0433932, -0.0455518, -0.0477225, -0.0499145, -0.0521294, -0.0543603, -0.056599, -0.0588375, -0.0610684, -0.0632876, -0.0655037, -0.0677167, -0.0699247, -0.0721264, -0.0743201, -0.0765108, -0.0786993, -0.0808802, -0.0830487, -0.0851995, -0.0873284, -0.089439, -0.0915334, -0.0936121, -0.0956753, -0.0977242, -0.0997517, -0.101752, -0.103736, -0.105717, -0.107708, -0.10972, -0.111741, -0.113769, -0.115806, -0.117857, -0.119925, -0.122017, -0.124138, -0.126278, -0.128425, -0.130568, -0.132695, -0.134806, -0.136907, -0.139004, -0.141099, -0.143195, -0.145297, -0.147412, -0.149533, -0.151649, -0.15375, -0.155824, -0.157859, -0.159851, -0.161824, -0.163796, -0.16579, -0.167825, -0.169907, -0.172019, -0.174147, -0.176275, -0.178386, -0.18047, -0.182542, -0.184604, -0.186657, -0.188702, -0.19074, -0.192767, -0.19478, -0.196787, -0.198791, -0.200797, -0.202811, -0.204826, -0.206843, -0.208861, -0.210884, -0.212911, -0.214946, -0.216992, -0.219043, -0.221093, -0.223135, -0.225165, -0.227174, -0.229169, -0.231159, -0.233156, -0.235167, -0.237203, -0.239262, -0.241337, -0.243416, -0.245491, -0.247553, -0.249605, -0.251657, -0.253703, -0.255735, -0.257747, -0.25973, -0.261673, -0.263588, -0.265491, -0.267398, -0.269325, -0.271286, -0.27327, -0.275268, -0.277268, -0.279258, -0.281228, -0.283188, -0.285145, -0.28709, -0.289016, -0.290914, -0.292773, -0.294573, -0.296337, -0.298095, -0.299876, -0.301711, -0.303622, -0.305594, -0.307596, -0.309599, -0.311574, -0.313491, -0.315348, -0.31717, -0.318975, -0.320785, -0.322621, -0.324499, -0.326405, -0.328328, -0.330257, -0.332182, -0.334093, -0.33599, -0.337882, -0.339769, -0.341653, -0.343533, -0.345412, -0.347287, -0.34916, -0.351029, -0.352894, -0.354757, -0.356615, -0.358471, -0.360323, -0.362171, -0.364016, -0.365857, -0.367695, -0.36953, -0.371361, -0.373187, -0.375008, -0.376822, -0.378625, -0.380423, -0.382218, -0.384017, -0.385823, -0.387639, -0.389461, -0.391286, -0.393111, -0.39493, -0.396744, -0.398568, -0.400394, -0.402209, -0.404001, -0.405756, -0.407455, -0.409092, -0.410699, -0.412308, -0.41395, -0.415656, -0.417438, -0.419271, -0.421126, -0.422972, -0.424782, -0.426532, -0.428234, -0.429909, -0.431572, -0.433239, -0.434928, -0.436646, -0.438382, -0.440124, -0.44186, -0.443579, -0.445267, -0.446914, -0.448535, -0.450152, -0.451785, -0.453454, -0.455174, -0.456935, -0.458721, -0.460517, -0.462308, -0.464079, -0.465839, -0.467598, -0.46935, -0.471092, -0.472818, -0.474526, -0.476213, -0.477884, -0.479547, -0.481207, -0.482871, -0.484546, -0.486228, -0.487908, -0.489577, -0.491226, -0.492845, -0.49444, -0.496014, -0.497571, -0.499113, -0.500644, -0.502158, -0.503643, -0.505112, -0.506581, -0.508065, -0.509579, -0.511122, -0.512685, -0.514262, -0.515847, -0.517436, -0.519026, -0.520626, -0.522233, -0.523842, -0.525448, -0.527046, -0.528634, -0.530216, -0.531793, -0.533369, -0.534945, -0.536525, -0.538114, -0.539707, -0.541298, -0.542879, -0.544443, -0.545986, -0.54751, -0.549021, -0.550522, -0.552019, -0.553515, -0.555017, -0.556521, -0.558019, -0.559502, -0.56096, -0.562383, -0.563768, -0.565125, -0.566468, -0.567813, -0.569172, -0.57054, -0.571898, -0.573261, -0.574643, -0.576059, -0.577525, -0.579052, -0.580624, -0.582221, -0.583824, -0.585413, -0.586978, -0.588546, -0.590111, -0.591667, -0.593204, -0.594717, -0.596205, -0.597674, -0.599125, -0.600559, -0.601977, -0.60338, -0.604764, -0.606132, -0.607485, -0.608824, -0.610153, -0.611468, -0.612764, -0.614047, -0.615324, -0.616602, -0.617887, -0.619173, -0.620458, -0.621743, -0.623032, -0.624325, -0.625623, -0.626915, -0.628209, -0.629514, -0.630837, -0.632189, -0.633581, -0.635008, -0.636455, -0.637902, -0.639332, -0.640731, -0.642122, -0.643505, -0.644871, -0.646211, -0.647516, -0.648774, -0.649981, -0.651157, -0.652322, -0.653495, -0.654696, -0.655923, -0.657163, -0.658409, -0.659653, -0.660887, -0.662105, -0.66331, -0.664506, -0.6657, -0.666896, -0.668101, -0.669321, -0.670557, -0.671796, -0.673025, -0.674233, -0.675407, -0.676535, -0.677633, -0.678721, -0.679822, -0.680957, -0.682138, -0.683351, -0.684586, -0.685833, -0.687084, -0.688329, -0.689583, -0.69085, -0.692118, -0.69337, -0.694595, -0.69578, -0.696928, -0.698049, -0.699154, -0.700253, -0.701358, -0.702475, -0.703598, -0.704718, -0.705824, -0.70691, -0.707963, -0.708967, -0.709937, -0.710897, -0.711871, -0.712884, -0.713953, -0.715065, -0.716205, -0.717357, -0.718507, -0.719638, -0.720763, -0.721891, -0.723013, -0.724122, -0.725208, -0.726265, -0.72729, -0.728292, -0.72928, -0.730265, -0.731255, -0.732248, -0.73323, -0.734211, -0.7352, -0.736205, -0.737238, -0.738311, -0.739412, -0.740521, -0.741619, -0.742686, -0.743708, -0.744696, -0.745661, -0.746613, -0.747561, -0.748516, -0.749476, -0.750434, -0.75139, -0.752341, -0.753286, -0.754223, -0.75515, -0.756071, -0.756987, -0.757902, -0.758819, -0.759739, -0.760661, -0.761581, -0.762501, -0.763418, -0.764331, -0.765241, -0.76615, -0.767056, -0.767958, -0.768856, -0.769747, -0.770623, -0.771492, -0.772365, -0.773251, -0.77416, -0.775104, -0.776077, -0.777062, -0.778043, -0.779001, -0.779922, -0.780812, -0.781681, -0.782536, -0.783385, -0.784234, -0.785089, -0.785946, -0.7868, -0.787644, -0.788474, -0.789283, -0.790058, -0.790807, -0.791546, -0.792295, -0.793069, -0.793885, -0.79474, -0.795619, -0.796505, -0.797383, -0.798236, -0.799069, -0.799894, -0.800708, -0.801505, -0.802282, -0.803032, -0.80375, -0.804442, -0.80512, -0.805793, -0.806474, -0.807167, -0.807859, -0.808552, -0.809247, -0.809944, -0.810644, -0.811352, -0.812064, -0.812778, -0.813491, -0.814198, -0.814899, -0.815595, -0.816289, -0.816978, -0.817663, -0.818343, -0.819017, -0.819684, -0.820347, -0.821007, -0.821667, -0.822327, -0.822983, -0.823637, -0.824291, -0.824947, -0.82561, -0.826279, -0.826953, -0.827631, -0.828313, -0.829, -0.82969, -0.830403, -0.831135, -0.831867, -0.832579, -0.833252, -0.833867, -0.834421, -0.834934, -0.835429, -0.835926, -0.836447, -0.836999, -0.837564, -0.838138, -0.838714, -0.839288, -0.839852, -0.840397, -0.840932, -0.841471, -0.842025, -0.842609, -0.843234, -0.843893, -0.844576, -0.845274, -0.845979, -0.846681, -0.847395, -0.848127, -0.848861, -0.849583, -0.850275, -0.850926, -0.851554, -0.85216, -0.852739, -0.853287, -0.853801, -0.85427, -0.854693, -0.855083, -0.855456, -0.855826, -0.856207, -0.85659, -0.856966, -0.857339, -0.857712, -0.858088, -0.858469, -0.858855, -0.859243, -0.85963, -0.860011, -0.860383, -0.86074, -0.861083, -0.861421, -0.861764, -0.862121, -0.862499, -0.862889, -0.863289, -0.863701, -0.86413, -0.864576, -0.865053, -0.865568, -0.866101, -0.866632, -0.867139, -0.867603, -0.86803, -0.868434, -0.868816, -0.86918, -0.869529, -0.869881, -0.870272, -0.870662, -0.871001, -0.871238, -0.871324, -0.87118, -0.870828, -0.870378, -0.869937, -0.869614, -0.869502, -0.869488, -0.869554, -0.869727, -0.870037, -0.870513, -0.871247, -0.872318, -0.873557, -0.874789, -0.875836, -0.876525, -0.876945, -0.877194, -0.877256, -0.877115, -0.876754, -0.87615, -0.875272, -0.874164, -0.872877, -0.871464, -0.869974, -0.868396, -0.866678, -0.864844, -0.862921, -0.860934, -0.858903, -0.856797, -0.854614, -0.852368, -0.850075, -0.84775, -0.845392, -0.842974, -0.840512, -0.838024, -0.83553, -0.833047, -0.830567, -0.828078, -0.825584, -0.823086, -0.820586, -0.818085, -0.815569, -0.813046, -0.810528, -0.808027, -0.805556, -0.803131, -0.800752, -0.798393, -0.796028, -0.79363, -0.791176, -0.788669, -0.786125, -0.783558, -0.780983, -0.778414, -0.775859, -0.773306, -0.770751, -0.768194, -0.765633, -0.763064, -0.76048, -0.757883, -0.755282, -0.752691, -0.750121, -0.747581, -0.745068, -0.742576, -0.740095, -0.737621, -0.735144, -0.732669, -0.730205, -0.727744, -0.725278, -0.722802, -0.720306, -0.717784, -0.715244, -0.712698, -0.710156, -0.707631, -0.70513, -0.702646, -0.700175, -0.697711, -0.695248, -0.692784, -0.69032, -0.687859, -0.6854, -0.682941, -0.680482, -0.67802, -0.675555, -0.673088, -0.670622, -0.668159, -0.665703, -0.663256, -0.660822, -0.658392, -0.655958, -0.653513, -0.651048, -0.64856, -0.646055, -0.643541, -0.641026, -0.63852, -0.636029, -0.633553, -0.631083, -0.628611, -0.626129, -0.62363, -0.62111, -0.618576, -0.616031, -0.613479, -0.610924, -0.608366, -0.605796, -0.603217, -0.600638, -0.598067, -0.59551, -0.592974, -0.590451, -0.58794, -0.585435, -0.582935, -0.580436, -0.577928, -0.575415, -0.572911, -0.570425, -0.567972, -0.565563, -0.563209, -0.560889, -0.55858, -0.55626, -0.553906, -0.551515, -0.549106, -0.546682, -0.544246, -0.541801, -0.53935, -0.536897, -0.534438, -0.531967, -0.529476, -0.526961, -0.524413, -0.521828, -0.519219, -0.516602, -0.513988, -0.511392, -0.508796, -0.506192, -0.503597, -0.501024, -0.498489, -0.496007, -0.493576, -0.491182, -0.488812, -0.486449, -0.484079, -0.481706, -0.479345, -0.476989, -0.474628, -0.472254, -0.46986, -0.467445, -0.465015, -0.462576, -0.460135, -0.457698, -0.455269, -0.452846, -0.450424, -0.448, -0.445567, -0.443123, -0.440673, -0.438221, -0.435759, -0.433282, -0.430783, -0.428251, -0.425661, -0.423039, -0.420415, -0.417822, -0.415292, -0.412848, -0.410474, -0.40814, -0.405816, -0.40347, -0.401075, -0.39863, -0.396155, -0.393671, -0.391196, -0.388748, -0.386341, -0.38396, -0.381597, -0.379251, -0.376914, -0.374584, -0.372274, -0.369988, -0.367709, -0.365422, -0.36311, -0.360759, -0.358377, -0.355973, -0.353552, -0.35112, -0.348683, -0.346232, -0.343757, -0.341272, -0.338791, -0.33633, -0.333904, -0.331509, -0.329135, -0.326778, -0.324435, -0.322102, -0.319783, -0.317494, -0.315222, -0.312948, -0.310658, -0.308332, -0.305972, -0.303591, -0.301191, -0.298771, -0.296331, -0.293869, -0.291362, -0.288824, -0.286279, -0.28375, -0.281261, -0.278825, -0.276424, -0.274049, -0.27169, -0.269337, -0.266981, -0.264622, -0.262268, -0.259921, -0.257585, -0.255261, -0.252955, -0.250671, -0.248402, -0.246138, -0.243872, -0.241594, -0.239308, -0.237026, -0.234738, -0.232437, -0.230116, -0.227767, -0.225386, -0.222981, -0.220562, -0.218139, -0.215721, -0.213308, -0.210888, -0.208467, -0.206051, -0.203648, -0.201264, -0.198897, -0.196543, -0.194201, -0.191873, -0.18956, -0.187264, -0.184994, -0.182742, -0.180497, -0.178248, -0.175985, -0.173709, -0.17143, -0.169145, -0.16685, -0.164542, -0.162215, -0.159862, -0.15749, -0.15511, -0.152733, -0.150369, -0.148029, -0.145705, -0.143391, -0.141079, -0.138761, -0.136432, -0.134085, -0.131728, -0.129367, -0.127009, -0.124661, -0.122327, -0.119997, -0.117672, -0.115358, -0.113058, -0.110777, -0.108528, -0.106311, -0.10411, -0.101907, -0.0996853, -0.097427, -0.0951316, -0.0928122, -0.0904801, -0.0881476, -0.0858269, -0.0835248, -0.0812303, -0.0789414, -0.0766578, -0.074378, -0.0721014, -0.0698264, -0.0675536, -0.0652851, -0.0630235, -0.0607712, -0.0585312, -0.0563106, -0.0541026, -0.0518976, -0.0496861, -0.0474588, -0.0452096, -0.0429452, -0.040671, -0.0383926, -0.0361152, -0.0338438, -0.0315732, -0.0293009, -0.0270301, -0.024764, -0.0225054, -0.0202499, -0.0179787, -0.0157089, -0.0134595, -0.011251, -0.00910325 +}; + +} // namespace rack_plugin_ImpromptuModular diff --git a/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp b/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp new file mode 100644 index 00000000..18bb866d --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/FundamentalUtil.hpp @@ -0,0 +1,164 @@ +//*********************************************************************************************** +//Impromptu Modular: Modules for VCV Rack by Marc Boulé +// +//This is code from the Fundamental plugin by Andrew Belt +//See ./LICENSE.txt for all licenses and see below for the filter code license +//*********************************************************************************************** + +#include "rack.hpp" +#include "dsp/functions.hpp" +#include "dsp/resampler.hpp" +#include "dsp/ode.hpp" +#include "dsp/filter.hpp" +#include "dsp/digital.hpp" + + +using namespace rack; + +namespace rack_plugin_ImpromptuModular { + +extern float sawTable[2048];// see end of file +extern float triTable[2048];// see end of file + + +// From Fundamental VCF +struct LadderFilter { + float omega0; + float resonance = 1.0f; + float state[4]; + float input; + float lowpass; + float highpass; + + LadderFilter() { + reset(); + setCutoff(0.f); + } + void reset() { + for (int i = 0; i < 4; i++) { + state[i] = 0.f; + } + } + void setCutoff(float cutoff) { + omega0 = 2.f*M_PI * cutoff; + } + void process(float input, float dt); +}; + + +// From Fundamental VCO.cpp +//template +static const int OVERSAMPLE = 16; +static const int QUALITY = 16; +struct VoltageControlledOscillator { + bool analog = false; + bool soft = false; + float lastSyncValue = 0.0f; + float phase = 0.0f; + float freq; + float pw = 0.5f; + float pitch; + bool syncEnabled = false; + bool syncDirection = false; + + Decimator sinDecimator; + Decimator triDecimator; + Decimator sawDecimator; + Decimator sqrDecimator; + RCFilter sqrFilter; + + // For analog detuning effect + float pitchSlew = 0.0f; + int pitchSlewIndex = 0; + + float sinBuffer[OVERSAMPLE] = {}; + float triBuffer[OVERSAMPLE] = {}; + float sawBuffer[OVERSAMPLE] = {}; + float sqrBuffer[OVERSAMPLE] = {}; + + void setPitch(float pitchKnob, float pitchCv); + void setPulseWidth(float pulseWidth); + void process(float deltaTime, float syncValue); + + float sin() { + return sinDecimator.process(sinBuffer); + } + float tri() { + return triDecimator.process(triBuffer); + } + float saw() { + return sawDecimator.process(sawBuffer); + } + float sqr() { + return sqrDecimator.process(sqrBuffer); + } + float light() { + return sinf(2*M_PI * phase); + } +}; + + + +// From Fundamental LFO.cpp +struct LowFrequencyOscillator { + float phase = 0.0f; + float pw = 0.5f; + float freq = 1.0f; + bool offset = false; + bool invert = false; + SchmittTrigger resetTrigger; + + LowFrequencyOscillator() {} + void setPitch(float pitch) { + pitch = fminf(pitch, 8.0f); + freq = powf(2.0f, pitch); + } + void setPulseWidth(float pw_) { + const float pwMin = 0.01f; + pw = clamp(pw_, pwMin, 1.0f - pwMin); + } + void setReset(float reset) { + if (resetTrigger.process(reset / 0.01f)) { + phase = 0.0f; + } + } + void step(float dt) { + float deltaPhase = fminf(freq * dt, 0.5f); + phase += deltaPhase; + if (phase >= 1.0f) + phase -= 1.0f; + } + float sin() { + if (offset) + return 1.0f - cosf(2*M_PI * phase) * (invert ? -1.0f : 1.0f); + else + return sinf(2*M_PI * phase) * (invert ? -1.0f : 1.0f); + } + float tri(float x) { + return 4.0f * fabsf(x - roundf(x)); + } + float tri() { + if (offset) + return tri(invert ? phase - 0.5f : phase); + else + return -1.0f + tri(invert ? phase - 0.25f : phase - 0.75f); + } + float saw(float x) { + return 2.0f * (x - roundf(x)); + } + float saw() { + if (offset) + return invert ? 2.0f * (1.0f - phase) : 2.0f * phase; + else + return saw(phase) * (invert ? -1.0f : 1.0f); + } + float sqr() { + float sqr = (phase < pw) ^ invert ? 1.0f : -1.0f; + return offset ? sqr + 1.0f : sqr; + } + float light() { + return sinf(2*M_PI * phase); + } +}; + +} // namespace rack_plugin_ImpromptuModular diff --git a/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp b/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp new file mode 100644 index 00000000..5ce5dafe --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/GateSeq64.cpp @@ -0,0 +1,1139 @@ +//*********************************************************************************************** +//Gate 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 +// +//Module concept by Nigel Sixsmith and Marc Boulé +// +//Acknowledgements: please see README.md +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct GateSeq64 : Module { + enum ParamIds { + ENUMS(STEP_PARAMS, 64), + MODES_PARAM, + RUN_PARAM, + CONFIG_PARAM, + COPY_PARAM, + PASTE_PARAM, + RESET_PARAM, + PROB_KNOB_PARAM,// no longer used + EDIT_PARAM, + SEQUENCE_PARAM, + CPMODE_PARAM, + NUM_PARAMS + }; + enum InputIds { + CLOCK_INPUT, + RESET_INPUT, + RUNCV_INPUT, + SEQCV_INPUT, + // -- 0.6.7 ^^ + WRITE_INPUT, + GATE_INPUT, + PROB_INPUT, + WRITE1_INPUT, + WRITE0_INPUT, + NUM_INPUTS + }; + enum OutputIds { + ENUMS(GATE_OUTPUTS, 4), + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(STEP_LIGHTS, 64 * 2),// room for GreenRed + P_LIGHT, + RUN_LIGHT, + RESET_LIGHT, + NUM_LIGHTS + }; + + enum DisplayStateIds {DISP_GATE, DISP_LENGTH, DISP_MODES, DISP_ROW_SEL}; + enum AttributeBitMasks {ATT_MSK_PROB = 0xFF, ATT_MSK_GATEP = 0x100, ATT_MSK_GATE = 0x200}; + + // Need to save + int panelTheme = 0; + int expansion = 0; + bool running; + int runModeSeq[16]; + int runModeSong; + // + int sequence; + int lengths[16];// values are 1 to 16 + // + int phrase[16];// This is the song (series of phases; a phrase is a patten number) + int phrases;//1 to 16 + // + int attributes[16][64]; + // + bool resetOnRun; + + // No need to save + int displayState; + int stepIndexRun; + int stepIndexWrite; + int phraseIndexEdit; + int phraseIndexRun; + int stepIndexRunHistory;// no need to initialize + int phraseIndexRunHistory;// no need to initialize + int cpBufAttributes[64] = {};// copy-paste one row or all rows + int cpBufLength;// copy-paste only one row + int modeCPbuffer; + long feedbackCP;// downward step counter for CP feedback + long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste + float resetLight = 0.0f; + long feedbackCPinit;// no need to initialize + int cpInfo;// copy = 1, paste = 2 + long clockIgnoreOnReset; + const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) + int displayProb;// -1 when prob can not be modified, 0 to 63 when prob can be changed. + long displayProbInfo;// downward step counter for displayProb feedback + int sequenceKnob = 0; + bool gateRandomEnable[4] = {}; + long revertDisplay; + + static constexpr float CONFIG_PARAM_INIT_VALUE = 0.0f;// so that module constructor is coherent with widget initialization, since module created before widget + int stepConfigLast; + static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget + bool editingSequence; + bool editingSequenceLast; + + + SchmittTrigger modesTrigger; + SchmittTrigger stepTriggers[64]; + SchmittTrigger copyTrigger; + SchmittTrigger pasteTrigger; + SchmittTrigger runningTrigger; + SchmittTrigger clockTrigger; + SchmittTrigger resetTrigger; + SchmittTrigger writeTrigger; + SchmittTrigger write0Trigger; + SchmittTrigger write1Trigger; + + + inline bool getGate(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE) != 0;} + inline bool getGateP(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATEP) != 0;} + inline int getGatePVal(int seq, int step) {return attributes[seq][step] & ATT_MSK_PROB;} + inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} + inline bool calcGateRandomEnable(bool gateP, int gatePVal) {return (randomUniform() < (((float)(gatePVal))/100.0f)) || !gateP;}// randomUniform is [0.0, 1.0), see include/util/common.hpp + + + GateSeq64() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + + // widgets are not yet created when module is created (and when onReset() is called by constructor) + // onReset() is also called when right-click initialization of module + void onReset() override { + int stepConfig = 1;// 4x16 + if (CONFIG_PARAM_INIT_VALUE > 1.5f)// 1x64 + stepConfig = 4; + else if (CONFIG_PARAM_INIT_VALUE > 0.5f)// 2x32 + stepConfig = 2; + stepConfigLast = stepConfig; + running = false; + stepIndexWrite = 0; + runModeSong = MODE_FWD; + phraseIndexEdit = 0; + sequence = 0; + phrases = 4; + for (int i = 0; i < 16; i++) { + for (int s = 0; s < 64; s++) { + attributes[i][s] = 50; + } + runModeSeq[i] = MODE_FWD; + phrase[i] = 0; + lengths[i] = 16 * stepConfig; + } + for (int i = 0; i < 64; i++) + cpBufAttributes[i] = 50; + initRun(stepConfig, true); + cpBufLength = 16; + modeCPbuffer = MODE_FWD; + feedbackCP = 0l; + displayState = DISP_GATE; + displayProb = -1; + displayProbInfo = 0l; + infoCopyPaste = 0l; + revertDisplay = 0l; + editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; + editingSequenceLast = editingSequence; + resetOnRun = false; + } + + + // widgets randomized before onRandomize() is called + void onRandomize() override { + int stepConfig = 1;// 4x16 + if (params[CONFIG_PARAM].value > 1.5f)// 1x64 + stepConfig = 4; + else if (params[CONFIG_PARAM].value > 0.5f)// 2x32 + stepConfig = 2; + running = (randomUniform() > 0.5f); + stepIndexWrite = 0; + runModeSong = randomu32() % 5; + phraseIndexEdit = 0; + sequence = randomu32() % 16; + phrases = 1 + (randomu32() % 16); + for (int i = 0; i < 16; i++) { + for (int s = 0; s < 64; s++) { + attributes[i][s] = (randomu32() % 101) | (randomu32() & (ATT_MSK_GATEP | ATT_MSK_GATE)); + } + runModeSeq[i] = randomu32() % NUM_MODES; + phrase[i] = randomu32() % 16; + lengths[i] = 1 + (randomu32() % (16 * stepConfig)); + } + for (int i = 0; i < 64; i++) + cpBufAttributes[i] = 50; + initRun(stepConfig, true); + cpBufLength = 16; + modeCPbuffer = MODE_FWD; + feedbackCP = 0l; + displayState = DISP_GATE; + displayProb = -1; + displayProbInfo = 0l; + infoCopyPaste = 0l; + revertDisplay = 0l; + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + resetOnRun = false; + } + + + void initRun(int stepConfig, bool hard) {// run button activated or run edge in run input jack + if (hard) { + phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); + if (editingSequence) + stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); + else + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); + } + for (int i = 0; i < 4; i++) + gateRandomEnable[i] = false; + if (editingSequence) { + for (int i = 0; i < 4; i += stepConfig) + gateRandomEnable[i] = calcGateRandomEnable(getGateP(sequence, (i * 16) + stepIndexRun), getGatePVal(sequence, (i * 16) + stepIndexRun)); + } + else { + for (int i = 0; i < 4; i += stepConfig) + gateRandomEnable[i] = calcGateRandomEnable(getGateP(phrase[phraseIndexRun], (i * 16) + stepIndexRun), getGatePVal(phrase[phraseIndexRun], (i * 16) + stepIndexRun)); + } + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // expansion + json_object_set_new(rootJ, "expansion", json_integer(expansion)); + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // runModeSeq + json_t *runModeSeqJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(runModeSeqJ, i, json_integer(runModeSeq[i])); + json_object_set_new(rootJ, "runModeSeq2", runModeSeqJ); + + // runModeSong + json_object_set_new(rootJ, "runModeSong", json_integer(runModeSong)); + + // sequence + json_object_set_new(rootJ, "sequence", json_integer(sequence)); + + // lengths + json_t *lengthsJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(lengthsJ, i, json_integer(lengths[i])); + json_object_set_new(rootJ, "lengths", lengthsJ); + + // phrase + json_t *phraseJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(phraseJ, i, json_integer(phrase[i])); + json_object_set_new(rootJ, "phrase", phraseJ); + + // phrases + json_object_set_new(rootJ, "phrases", json_integer(phrases)); + + // attributes + json_t *attributesJ = json_array(); + for (int i = 0; i < 16; i++) + for (int s = 0; s < 64; s++) { + json_array_insert_new(attributesJ, s + (i * 64), json_integer(attributes[i][s])); + } + json_object_set_new(rootJ, "attributes", attributesJ); + + // resetOnRun + json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); + + return rootJ; + } + + + // widgets loaded before this fromJson() is called + void fromJson(json_t *rootJ) override { + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // expansion + json_t *expansionJ = json_object_get(rootJ, "expansion"); + if (expansionJ) + expansion = json_integer_value(expansionJ); + + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // runModeSeq + json_t *runModeSeqJ = json_object_get(rootJ, "runModeSeq2"); + if (runModeSeqJ) { + for (int i = 0; i < 16; i++) + { + json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i); + if (runModeSeqArrayJ) + runModeSeq[i] = json_integer_value(runModeSeqArrayJ); + } + } + + // runModeSong + json_t *runModeSongJ = json_object_get(rootJ, "runModeSong"); + if (runModeSongJ) + runModeSong = json_integer_value(runModeSongJ); + + // sequence + json_t *sequenceJ = json_object_get(rootJ, "sequence"); + if (sequenceJ) + sequence = json_integer_value(sequenceJ); + + // lengths + json_t *lengthsJ = json_object_get(rootJ, "lengths"); + if (lengthsJ) { + for (int i = 0; i < 16; i++) + { + json_t *lengthsArrayJ = json_array_get(lengthsJ, i); + if (lengthsArrayJ) + lengths[i] = json_integer_value(lengthsArrayJ); + } + } + + // phrase + json_t *phraseJ = json_object_get(rootJ, "phrase"); + if (phraseJ) + for (int i = 0; i < 16; i++) + { + json_t *phraseArrayJ = json_array_get(phraseJ, i); + if (phraseArrayJ) + phrase[i] = json_integer_value(phraseArrayJ); + } + + // phrases + json_t *phrasesJ = json_object_get(rootJ, "phrases"); + if (phrasesJ) + phrases = json_integer_value(phrasesJ); + + // attributes + json_t *attributesJ = json_object_get(rootJ, "attributes"); + if (attributesJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 64; s++) { + json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 64)); + if (attributesArrayJ) + attributes[i][s] = json_integer_value(attributesArrayJ); + } + } + + // resetOnRun + json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); + if (resetOnRunJ) + resetOnRun = json_is_true(resetOnRunJ); + + // Initialize dependants after everything loaded (widgets already loaded when reach here) + int stepConfig = 1;// 4x16 + if (params[CONFIG_PARAM].value > 1.5f)// 1x64 + stepConfig = 4; + else if (params[CONFIG_PARAM].value > 0.5f)// 2x32 + stepConfig = 2; + stepConfigLast = stepConfig; + initRun(stepConfig, true); + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + static const float feedbackCPinitTime = 3.0f;// seconds + static const float copyPasteInfoTime = 0.5f;// seconds + static const float displayProbInfoTime = 3.0f;// seconds + static const float revertDisplayTime = 0.7f;// seconds + float engineSampleRate = engineGetSampleRate(); + feedbackCPinit = (long) (feedbackCPinitTime * engineSampleRate); + long displayProbInfoInit = (long) (displayProbInfoTime * engineSampleRate); + + //********** Buttons, knobs, switches and inputs ********** + + // Config switch + int stepConfig = 1;// 4x16 + if (params[CONFIG_PARAM].value > 1.5f)// 1x64 + stepConfig = 4; + else if (params[CONFIG_PARAM].value > 0.5f)// 2x32 + stepConfig = 2; + // Config: set lengths to their new max when move switch + if (stepConfigLast != stepConfig) { + for (int i = 0; i < 16; i++) + lengths[i] = 16 * stepConfig; + displayProb = -1; + stepConfigLast = stepConfig; + } + + // Edit mode + editingSequence = isEditingSequence();// true = editing sequence, false = editing song + if (editingSequenceLast != editingSequence) { + if (running) + initRun(stepConfig, true); + displayState = DISP_GATE; + displayProb = -1; + editingSequenceLast = editingSequence; + } + + // Seq CV input + if (inputs[SEQCV_INPUT].active) { + sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * 15.0f / 10.0f), 0.0f, 15.0f ); + } + + // Run state button + if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { + running = !running; + if (running) + initRun(stepConfig, resetOnRun); + displayState = DISP_GATE; + displayProb = -1; + } + + // Mode/Length button + if (modesTrigger.process(params[MODES_PARAM].value)) { + if (displayState == DISP_GATE || displayState == DISP_ROW_SEL) + displayState = DISP_LENGTH; + else if (displayState == DISP_LENGTH) + displayState = DISP_MODES; + else + displayState = DISP_GATE; + displayProb = -1; + } + + + // Sequence knob (Main knob) + float seqParamValue = params[SEQUENCE_PARAM].value; + int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); + if (seqParamValue == 0.0f)// true when constructor or fromJson() occured + sequenceKnob = newSequenceKnob; + int deltaKnob = newSequenceKnob - sequenceKnob; + if (deltaKnob != 0) { + if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) + if (displayProb != -1 && editingSequence) { + int pval = getGatePVal(sequence, displayProb); + pval += deltaKnob * 2; + if (pval > 100) + pval = 100; + if (pval < 0) + pval = 0; + attributes[sequence][displayProb] = pval | (attributes[sequence][displayProb] & (ATT_MSK_GATE | ATT_MSK_GATEP)); + displayProbInfo = displayProbInfoInit; + } + else if (displayState == DISP_MODES) { + if (editingSequence) { + runModeSeq[sequence] += deltaKnob; + if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0; + if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1; + } + else { + runModeSong += deltaKnob; + if (runModeSong < 0) runModeSong = 0; + if (runModeSong >= 5) runModeSong = 5 - 1; + } + } + else if (displayState == DISP_LENGTH) { + if (editingSequence) { + lengths[sequence] += deltaKnob; + if (lengths[sequence] > (16 * stepConfig)) + lengths[sequence] = (16 * stepConfig); + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + } + else { + phrases += deltaKnob; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1; + } + } + else if (displayState == DISP_ROW_SEL) { + } + else { + if (editingSequence) { + if (!inputs[SEQCV_INPUT].active) { + sequence += deltaKnob; + if (sequence < 0) sequence = 0; + if (sequence > 15) sequence = 15; + } + } + else { + phrase[phraseIndexEdit] += deltaKnob; + if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0; + if (phrase[phraseIndexEdit] > 15) phrase[phraseIndexEdit] = 15; + } + } + } + sequenceKnob = newSequenceKnob; + } + + // Copy, paste buttons + bool copyTrigged = copyTrigger.process(params[COPY_PARAM].value); + bool pasteTrigged = pasteTrigger.process(params[PASTE_PARAM].value); + if (editingSequence) { + if (copyTrigged || pasteTrigged) { + if (displayState == DISP_GATE) { + if (params[CPMODE_PARAM].value > 0.5f) {// if copy-paste in row mode + cpInfo = 0; + if (copyTrigged) cpInfo = 1; + if (pasteTrigged) cpInfo = 2; + displayState = DISP_ROW_SEL; + feedbackCP = feedbackCPinit; + } + else {// copy-paste in "all" mode + if (copyTrigged) { + for (int i = 0; i < 64; i++) + cpBufAttributes[i] = attributes[sequence][i]; + cpBufLength = lengths[sequence]; + modeCPbuffer = runModeSeq[sequence]; + infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); + } + else {// paste triggered + for (int i = 0; i < 64; i++) + attributes[sequence][i] = cpBufAttributes[i]; + lengths[sequence] = cpBufLength; + if (lengths[sequence] > 16 * stepConfig) + lengths[sequence] = 16 * stepConfig; + runModeSeq[sequence] = modeCPbuffer; + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + } + } + } + else if (displayState == DISP_ROW_SEL) {// abort copy or paste + displayState = DISP_GATE; + } + displayProb = -1; + } + } + + + // Write inputs + bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); + bool write0Trig = write0Trigger.process(inputs[WRITE0_INPUT].value); + bool write1Trig = write1Trigger.process(inputs[WRITE1_INPUT].value); + if (writeTrig || write0Trig || write1Trig) { + if (editingSequence) { + if (writeTrig) {// higher priority than write0 and write1 + if (inputs[PROB_INPUT].active) { + attributes[sequence][stepIndexWrite] = clamp( (int)round(inputs[PROB_INPUT].value * 10.0f), 0, 100); + attributes[sequence][stepIndexWrite] |= ATT_MSK_GATEP; + } + else + attributes[sequence][stepIndexWrite] = 50; + if (inputs[GATE_INPUT].value >= 1.0f) + attributes[sequence][stepIndexWrite] |= ATT_MSK_GATE; + } + else {// write1 or write0 + attributes[sequence][stepIndexWrite] = write1Trig ? ATT_MSK_GATE : 0; + } + // Autostep (after grab all active inputs) + stepIndexWrite += 1; + if (stepIndexWrite >= 64) + stepIndexWrite = 0; + } + } + + // Step LED button presses + int row = -1; + int col = -1; + int stepPressed = -1; + for (int i = 0; i < 64; i++) { + if (stepTriggers[i].process(params[STEP_PARAMS + i].value)) + stepPressed = i; + } + if (stepPressed != -1) { + if (editingSequence) { + if (displayState == DISP_LENGTH) { + col = stepPressed % (16 * stepConfig); + lengths[sequence] = col + 1; + revertDisplay = (long) (revertDisplayTime * engineGetSampleRate()); + } + else if (displayState == DISP_ROW_SEL) { + row = stepPressed / 16;// copy-paste done on blocks of 16 even when in 2x32 or 1x64 config (and length is not copied) + if (cpInfo == 1) {// copy + for (int i = 0; i < 16; i++) { + cpBufAttributes[i] = attributes[sequence][row * 16 + i]; + } + } + else if (cpInfo == 2) {// paste + for (int i = 0; i < 16; i++) + attributes[sequence][row * 16 + i] = cpBufAttributes[i]; + } + displayState = DISP_GATE; + } + else if (displayState == DISP_MODES) { + } + else { + stepIndexWrite = stepPressed; + if (!getGate(sequence, stepPressed)) {// clicked inactive, so turn gate on + attributes[sequence][stepPressed] |= ATT_MSK_GATE; + attributes[sequence][stepPressed] &= ~ATT_MSK_GATEP; + displayProb = -1; + } + else { + if (!getGateP(sequence, stepPressed)) {// clicked active, but not in prob mode + displayProb = stepPressed; + displayProbInfo = displayProbInfoInit; + attributes[sequence][stepPressed] |= ATT_MSK_GATEP; + } + else {// clicked active, and in prob mode + if (displayProb != stepPressed) {// coming from elsewhere, so don't change any states, just show its prob + displayProb = stepPressed; + displayProbInfo = displayProbInfoInit; + } + else {// coming from current step, so turn off + attributes[sequence][stepPressed] &= ~(ATT_MSK_GATEP | ATT_MSK_GATE); + displayProb = -1; + } + } + } + } + } + else {// editing song + row = stepPressed / 16; + if (row == 3) { + col = stepPressed % 16; + if (displayState == DISP_LENGTH) { + phrases = col + 1; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + revertDisplay = (long) (revertDisplayTime * engineGetSampleRate()); + } + else if (displayState == DISP_MODES) { + if (col >= 11 && col <= 15) + runModeSong = col - 11; + } + else { + if (!running) { + phraseIndexEdit = stepPressed - 48; + //if (phraseIndexEdit >= phrases)// Commented for full edit capabilities + //phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + } + } + } + } + + + //********** Clock and reset ********** + + // Clock + if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { + if (running && clockIgnoreOnReset == 0l) { + for (int i = 0; i < 4; i++) + gateRandomEnable[i] = false; + if (editingSequence) { + moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); + for (int i = 0; i < 4; i += stepConfig) + gateRandomEnable[i] = calcGateRandomEnable(getGateP(sequence, (i * 16) + stepIndexRun), getGatePVal(sequence, (i * 16) + stepIndexRun)); + } + else { + if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) { + moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed + } + for (int i = 0; i < 4; i += stepConfig) + gateRandomEnable[i] = calcGateRandomEnable(getGateP(phrase[phraseIndexRun], (i * 16) + stepIndexRun), getGatePVal(phrase[phraseIndexRun], (i * 16) + stepIndexRun)); + } + } + } + + // Reset + if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + //sequence = 0; + stepIndexWrite = 0; + initRun(stepConfig, true);// must be after sequence reset + resetLight = 1.0f; + displayState = DISP_GATE; + clockTrigger.reset(); + } + else + resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); + + + //********** Outputs and lights ********** + + // Gate outputs + if (running) { + int seq = editingSequence ? sequence : phrase[phraseIndexRun]; + bool gateOut[4] = {false, false, false, false}; + for (int i = 0; i < 4; i += stepConfig) + gateOut[i] = gateRandomEnable[i] && clockTrigger.isHigh() && getGate(seq, (i * 16) + stepIndexRun); + for (int i = 0; i < 4; i++) + outputs[GATE_OUTPUTS + i].value = gateOut[i] ? 10.0f : 0.0f; + } + else {// not running (no gates, no need to hear anything) + for (int i = 0; i < 4; i++) + outputs[GATE_OUTPUTS + i].value = 0.0f; + } + + // Step LED button lights + int rowToLight = -1; + if (displayState == DISP_ROW_SEL) + rowToLight = CalcRowToLight(feedbackCP, feedbackCPinit); + for (int i = 0; i < 64; i++) { + row = i / (16 * stepConfig); + if (stepConfig == 2 && row == 1) + row++; + col = i % (16 * stepConfig); + if (editingSequence) { + if (displayState == DISP_LENGTH) { + if (col < (lengths[sequence] - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 0.1f, 0.0f); + else if (col == (lengths[sequence] - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); + } + else if (displayState == DISP_ROW_SEL) { + if ((i / 16) == rowToLight) + setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); + } + else { + float stepHereOffset = ((stepIndexRun == col) && running) ? 0.5f : 0.0f; + if (getGate(sequence, i)) { + if (i == displayProb && getGateP(sequence, i)) + setGreenRed(STEP_LIGHTS + i * 2, 0.4f, 1.0f - stepHereOffset); + else + setGreenRed(STEP_LIGHTS + i * 2, 1.0f - stepHereOffset, getGateP(sequence, i) ? (1.0f - stepHereOffset) : 0.0f); + } + else { + setGreenRed(STEP_LIGHTS + i * 2, stepHereOffset / 5.0f, 0.0f); + } + } + } + else {// editing Song + if (displayState == DISP_LENGTH) { + row = i / 16; + col = i % 16; + if (row == 3 && col < (phrases - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 0.1f, 0.0f); + else if (row == 3 && col == (phrases - 1)) + setGreenRed(STEP_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_LIGHTS + i * 2, 0.0f, 0.0f); + } + else { + float green; + if (running) + green = (i == (phraseIndexRun + 48)) ? 1.0f : 0.0f; + else + green = (i == (phraseIndexEdit + 48)) ? 1.0f : 0.0f; + green += ((running && (col == stepIndexRun) && i != (phraseIndexEdit + 48)) ? 0.1f : 0.0f); + setGreenRed(STEP_LIGHTS + i * 2, clamp(green, 0.0f, 1.0f), 0.0f); + } + } + } + + // Reset light + lights[RESET_LIGHT].value = resetLight; + + // Run lights + lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; + + if (feedbackCP > 0l) + feedbackCP--; + else + feedbackCP = feedbackCPinit;// roll over + + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (displayProbInfo > 0l) + displayProbInfo--; + else + displayProb = -1; + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; + if (revertDisplay > 0l) { + if (revertDisplay == 1) + displayState = DISP_GATE; + revertDisplay--; + } + }// step() + + + void setGreenRed(int id, float green, float red) { + lights[id + 0].value = green; + lights[id + 1].value = red; + } + + int CalcRowToLight(long feedbackCP, long feedbackCPinit) { + int rowToLight = -1; + long onDelta = feedbackCPinit / 14; + long onThreshold;// top based + + onThreshold = feedbackCPinit; + if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) + rowToLight = 0; + else { + onThreshold = feedbackCPinit * 3 / 4; + if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) + rowToLight = 1; + else { + onThreshold = feedbackCPinit * 2 / 4; + if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) + rowToLight = 2; + else { + onThreshold = feedbackCPinit * 1 / 4; + if (feedbackCP < onThreshold && feedbackCP > (onThreshold - onDelta)) + rowToLight = 3; + } + } + } + return rowToLight; + } +};// GateSeq64 : module + +struct GateSeq64Widget : ModuleWidget { + GateSeq64 *module; + DynamicSVGPanel *panel; + int oldExpansion; + int expWidth = 60; + IMPort* expPorts[5]; + + struct SequenceDisplayWidget : TransparentWidget { + GateSeq64 *module; + std::shared_ptr font; + char displayStr[4]; + //std::string modeLabels[5]={"FWD","REV","PPG","BRN","RND"}; + + SequenceDisplayWidget() { + font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); + } + + void runModeToStr(int num) { + if (num >= 0 && num < NUM_MODES) + snprintf(displayStr, 4, "%s", modeLabels[num].c_str()); + } + + 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); + if (module->infoCopyPaste != 0l) { + if (module->infoCopyPaste > 0l) {// if copy display "CPY" + snprintf(displayStr, 4, "CPY"); + } + else {// if paste display "PST" + snprintf(displayStr, 4, "PST"); + } + } + else if (module->displayProb != -1) { + int prob = module->getGatePVal(module->sequence, module->displayProb); + if ( prob>= 100) + snprintf(displayStr, 4, " 1"); + else if (prob >= 1) + snprintf(displayStr, 4, ",%2u", (unsigned) prob); + else + snprintf(displayStr, 4, " 0"); + } + else if (module->displayState == GateSeq64::DISP_LENGTH) { + if (module->editingSequence) + snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); + else + snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases); + } + else if (module->displayState == GateSeq64::DISP_MODES) { + if (module->editingSequence) + runModeToStr(module->runModeSeq[module->sequence]); + else + runModeToStr(module->runModeSong); + } + else if (module->displayState == GateSeq64::DISP_ROW_SEL) { + snprintf(displayStr, 4, "CPY"); + if (module->cpInfo == 2) + snprintf(displayStr, 4, "PST"); + } + else { + int dispVal = 0; + if (module->editingSequence) + dispVal = module->sequence; + else { + if (module->running) + dispVal = module->phrase[module->phraseIndexRun]; + else + dispVal = module->phrase[module->phraseIndexEdit]; + } + snprintf(displayStr, 4, " %2u", (unsigned)(dispVal) + 1 ); + } + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + struct PanelThemeItem : MenuItem { + GateSeq64 *module; + int theme; + void onAction(EventAction &e) override { + module->panelTheme = theme; + } + void step() override { + rightText = (module->panelTheme == theme) ? "✔" : ""; + } + }; + struct ExpansionItem : MenuItem { + GateSeq64 *module; + void onAction(EventAction &e) override { + module->expansion = module->expansion == 1 ? 0 : 1; + } + }; + struct ResetOnRunItem : MenuItem { + GateSeq64 *module; + void onAction(EventAction &e) override { + module->resetOnRun = !module->resetOnRun; + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + GateSeq64 *module = dynamic_cast(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); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); + rorItem->module = module; + menu->addChild(rorItem); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *expansionLabel = new MenuLabel(); + expansionLabel->text = "Expansion module"; + menu->addChild(expansionLabel); + + ExpansionItem *expItem = MenuItem::create(expansionMenuLabel, CHECKMARK(module->expansion != 0)); + expItem->module = module; + menu->addChild(expItem); + + return menu; + } + + void step() override { + if(module->expansion != oldExpansion) { + if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks + for (int i = 0; i < 5; i++) + rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); + } + oldExpansion = module->expansion; + } + box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth; + Widget::step(); + } + + GateSeq64Widget(GateSeq64 *module) : ModuleWidget(module) { + this->module = module; + oldExpansion = -1; + + // Main panel from Inkscape + panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->expWidth = &expWidth; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/GateSeq64.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/GateSeq64_dark.svg"))); + box.size = panel->box.size; + box.size.x = box.size.x - (1 - module->expansion) * expWidth; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme)); + + + // ****** Top portion (2 switches and LED button array ****** + + static const int rowRuler0 = 34; + static const int spacingRows = 36; + static const int colRulerSteps = 15; + static const int spacingSteps = 20; + static const int spacingSteps4 = 4; + + + // Step LED buttons + for (int y = 0; y < 4; y++) { + int posX = colRulerSteps; + for (int x = 0; x < 16; x++) { + addParam(ParamWidget::create(Vec(posX, rowRuler0 + 8 + y * spacingRows - 4.4f), module, GateSeq64::STEP_PARAMS + y * 16 + x, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(posX + 4.4f, rowRuler0 + 8 + y * spacingRows), module, GateSeq64::STEP_LIGHTS + (y * 16 + x) * 2)); + posX += spacingSteps; + if ((x + 1) % 4 == 0) + posX += spacingSteps4; + } + } + + + + // ****** 5x3 Main bottom half Control section ****** + + static const int colRulerC0 = 25; + static const int colRulerSpacing = 72; + static const int colRulerC1 = colRulerC0 + colRulerSpacing; + static const int colRulerC2 = colRulerC1 + colRulerSpacing; + static const int colRulerC3 = colRulerC2 + colRulerSpacing; + static const int rowRulerC0 = 204; + static const int rowRulerSpacing = 56; + static const int rowRulerC1 = rowRulerC0 + rowRulerSpacing; + static const int rowRulerC2 = rowRulerC1 + rowRulerSpacing; + + + // Clock input + addInput(createDynamicPort(Vec(colRulerC0, rowRulerC0), Port::INPUT, module, GateSeq64::CLOCK_INPUT, &module->panelTheme)); + // Reset CV + addInput(createDynamicPort(Vec(colRulerC0, rowRulerC1), Port::INPUT, module, GateSeq64::RESET_INPUT, &module->panelTheme)); + // Seq CV + addInput(createDynamicPort(Vec(colRulerC0, rowRulerC2), Port::INPUT, module, GateSeq64::SEQCV_INPUT, &module->panelTheme)); + + + // Seq/Song selector + addParam(ParamWidget::create(Vec(colRulerC1 + hOffsetCKSS, rowRulerC0 - 2 + vOffsetCKSS), module, GateSeq64::EDIT_PARAM, 0.0f, 1.0f, GateSeq64::EDIT_PARAM_INIT_VALUE)); + // Reset LED bezel and light + addParam(ParamWidget::create(Vec(colRulerC1 - 17 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerC1 - 17 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RESET_LIGHT)); + // Run LED bezel and light + addParam(ParamWidget::create(Vec(colRulerC1 + 17 + offsetLEDbezel, rowRulerC1 + offsetLEDbezel), module, GateSeq64::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(colRulerC1 + 17 + offsetLEDbezel + offsetLEDbezelLight, rowRulerC1 + offsetLEDbezel + offsetLEDbezelLight), module, GateSeq64::RUN_LIGHT)); + // Run CV + addInput(createDynamicPort(Vec(colRulerC1, rowRulerC2), Port::INPUT, module, GateSeq64::RUNCV_INPUT, &module->panelTheme)); + + + // Sequence display + SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget(); + displaySequence->box.pos = Vec(colRulerC2 - 15, rowRulerC0 + 2 + vOffsetDisplay); + displaySequence->box.size = Vec(55, 30);// 3 characters + displaySequence->module = module; + addChild(displaySequence); + // Sequence knob + addParam(createDynamicParam(Vec(colRulerC2 + 1 + offsetIMBigKnob, rowRulerC1 + offsetIMBigKnob), module, GateSeq64::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); + // Config switch (3 position) + addParam(ParamWidget::create(Vec(colRulerC2 + hOffsetCKSS, rowRulerC2 - 2 + vOffsetCKSSThree), module, GateSeq64::CONFIG_PARAM, 0.0f, 2.0f, GateSeq64::CONFIG_PARAM_INIT_VALUE));// 0.0f is top position + + + // Modes button and light + addParam(createDynamicParam(Vec(colRulerC3 + offsetCKD6b, rowRulerC0 + offsetCKD6b), module, GateSeq64::MODES_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Copy/paste buttons + addParam(ParamWidget::create(Vec(colRulerC3 - 10, rowRulerC1 + offsetTL1105), module, GateSeq64::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(colRulerC3 + 20, rowRulerC1 + offsetTL1105), module, GateSeq64::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Copy paste mode + addParam(ParamWidget::create(Vec(colRulerC3 + 2 + hOffsetCKSS, rowRulerC2 - 3 + vOffsetCKSS), module, GateSeq64::CPMODE_PARAM, 0.0f, 1.0f, 1.0f)); + + // Outputs + for (int iSides = 0; iSides < 4; iSides++) + addOutput(createDynamicPort(Vec(311, rowRulerC0 + iSides * 40), Port::OUTPUT, module, GateSeq64::GATE_OUTPUTS + iSides, &module->panelTheme)); + + // Expansion module + static const int rowRulerExpTop = 65; + static const int rowSpacingExp = 60; + static const int colRulerExp = 497 - 30 - 90;// GS64 is (2+6)HP less than PS32 + addInput(expPorts[0] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, GateSeq64::WRITE_INPUT, &module->panelTheme)); + addInput(expPorts[1] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, GateSeq64::GATE_INPUT, &module->panelTheme)); + addInput(expPorts[2] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, GateSeq64::PROB_INPUT, &module->panelTheme)); + addInput(expPorts[3] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, GateSeq64::WRITE0_INPUT, &module->panelTheme)); + addInput(expPorts[4] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, GateSeq64::WRITE1_INPUT, &module->panelTheme)); + + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, GateSeq64) { + Model *modelGateSeq64 = Model::create("Impromptu Modular", "Gate-Seq-64", "SEQ - Gate-Seq-64", SEQUENCER_TAG); + return modelGateSeq64; +} + +/*CHANGE LOG + +0.6.9: +add FW2, FW3 and FW4 run modes for sequences (but not for song) + +0.6.7: +add expansion panel with extra CVs for writing steps into the module +allow full edit capabilities in song mode +no reset on run by default, with switch added in context menu +reset does not revert seq or song number to 1 + +0.6.6: +config and knob bug fixes when loading patch + +0.6.5: +swap MODE/LEN so that length happens first (update manual) + +0.6.4: +initial release of GS64 +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/IMWidgets.cpp b/plugins/community/repos/ImpromptuModular/src/IMWidgets.cpp new file mode 100644 index 00000000..3a26fdc9 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/IMWidgets.cpp @@ -0,0 +1,325 @@ +//*********************************************************************************************** +//Impromptu Modular: Modules for VCV Rack by Marc Boulé +// +//Based on code from Valley Rack Free by Dale Johnson +//See ./LICENSE.txt for all licenses +//*********************************************************************************************** + + +#include "IMWidgets.hpp" + + + +// Dynamic SVGScrew + + +ScrewCircle::ScrewCircle(float _angle) { + static const float highRadius = 1.4f;// radius for 0 degrees (screw looks like a +) + static const float lowRadius = 1.1f;// radius for 45 degrees (screw looks like an x) + angle = _angle; + _angle = fabs(angle - M_PI/4.0f); + radius = ((highRadius - lowRadius)/(M_PI/4.0f)) * _angle + lowRadius; +} +void ScrewCircle::draw(NVGcontext *vg) { + NVGcolor backgroundColor = nvgRGB(0x72, 0x72, 0x72); + NVGcolor borderColor = nvgRGB(0x72, 0x72, 0x72); + nvgBeginPath(vg); + nvgCircle(vg, box.size.x/2.0f, box.size.y/2.0f, radius);// box, radius + nvgFillColor(vg, backgroundColor); + nvgFill(vg); + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, borderColor); + nvgStroke(vg); +} +DynamicSVGScrew::DynamicSVGScrew() { + mode = nullptr; + oldMode = -1; + + + // for random rotated screw used in primary mode (code copied from ImpromptuModular.cpp ScrewSilverRandomRot::ScrewSilverRandomRot()) + // ********** + float angle0_90 = randomUniform()*M_PI/2.0f; + //float angle0_90 = randomUniform() > 0.5f ? M_PI/4.0f : 0.0f;// for testing + + tw = new TransformWidget(); + addChild(tw); + + sw = new SVGWidget(); + tw->addChild(sw); + //sw->setSVG(SVG::load(assetPlugin(plugin, "res/Screw.svg"))); + sw->setSVG(SVG::load(assetGlobal("res/ComponentLibrary/ScrewSilver.svg"))); + + sc = new ScrewCircle(angle0_90); + sc->box.size = sw->box.size; + tw->addChild(sc); + + box.size = sw->box.size; + tw->box.size = sw->box.size; + tw->identity(); + // Rotate SVG + Vec center = sw->box.getCenter(); + tw->translate(center); + tw->rotate(angle0_90); + tw->translate(center.neg()); + + // for fixed svg screw used in alternate mode + // ********** + swAlt = new SVGWidget(); + swAlt->visible = false; + addChild(swAlt); +} + + +void DynamicSVGScrew::addSVGalt(std::shared_ptr svg) { + if(!swAlt->svg) { + swAlt->setSVG(svg); + } +} + +void DynamicSVGScrew::step() { // all code except middle if() from SVGPanel::step() in SVGPanel.cpp + if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { + // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer + oversample = 2.f; + } + if(mode != nullptr && *mode != oldMode) { + if ((*mode) == 0) { + sw->visible = true; + swAlt->visible = false; + } + else { + sw->visible = false; + swAlt->visible = true; + } + oldMode = *mode; + dirty = true; + } + FramebufferWidget::step(); +} + + + +// Dynamic SVGPanel + +void PanelBorderWidget_Impromptu::draw(NVGcontext *vg) { // carbon copy from SVGPanel.cpp + NVGcolor borderColor = nvgRGBAf(0.5, 0.5, 0.5, 0.5); + nvgBeginPath(vg); + nvgRect(vg, 0.5, 0.5, box.size.x - 1.0, box.size.y - 1.0);// full rect of module (including expansion area if a module has one) + nvgStrokeColor(vg, borderColor); + nvgStrokeWidth(vg, 1.0); + nvgStroke(vg); + if (expWidth != nullptr && *expWidth != nullptr) {// add expansion division when pannel uses expansion area + int expW = **expWidth; + nvgBeginPath(vg); + nvgMoveTo(vg, box.size.x - expW, 1); + nvgLineTo(vg, box.size.x - expW, box.size.y - 1.0); + nvgStrokeWidth(vg, 2.0); + nvgStroke(vg); + } +} + +DynamicSVGPanel::DynamicSVGPanel() { + mode = nullptr; + oldMode = -1; + expWidth = nullptr; + visiblePanel = new SVGWidget(); + addChild(visiblePanel); + border = new PanelBorderWidget_Impromptu(); + border->expWidth = &expWidth; + addChild(border); +} + +void DynamicSVGPanel::addPanel(std::shared_ptr svg) { + panels.push_back(svg); + if(!visiblePanel->svg) { + visiblePanel->setSVG(svg); + box.size = visiblePanel->box.size.div(RACK_GRID_SIZE).round().mult(RACK_GRID_SIZE); + border->box.size = box.size; + } +} + +void DynamicSVGPanel::step() { // all code except middle if() from SVGPanel::step() in SVGPanel.cpp + if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { + // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer + oversample = 2.f; + } + if(mode != nullptr && *mode != oldMode) { + visiblePanel->setSVG(panels[*mode]); + oldMode = *mode; + dirty = true; + } + FramebufferWidget::step(); +} + + + +// Dynamic SVGPort + +DynamicSVGPort::DynamicSVGPort() { + mode = nullptr; + oldMode = -1; + //SVGPort constructor automatically called +} + +void DynamicSVGPort::addFrame(std::shared_ptr svg) { + frames.push_back(svg); + if(!background->svg) + SVGPort::setSVG(svg); +} + +void DynamicSVGPort::step() { + if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { + // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer + oversample = 2.f; + } + if(mode != nullptr && *mode != oldMode) { + background->setSVG(frames[*mode]); + oldMode = *mode; + dirty = true; + } + Port::step(); +} + + + +// Dynamic SVGSwitch + +DynamicSVGSwitch::DynamicSVGSwitch() { + mode = nullptr; + oldMode = -1; + //SVGSwitch constructor automatically called +} + +void DynamicSVGSwitch::addFrameAll(std::shared_ptr svg) { + framesAll.push_back(svg); + if (framesAll.size() == 2) { + addFrame(framesAll[0]); + addFrame(framesAll[1]); + } +} + +void DynamicSVGSwitch::step() { + if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { + // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer + oversample = 2.f; + } + if(mode != nullptr && *mode != oldMode) { + if ((*mode) == 0) { + frames[0]=framesAll[0]; + frames[1]=framesAll[1]; + } + else { + frames[0]=framesAll[2]; + frames[1]=framesAll[3]; + } + oldMode = *mode; + onChange(*(new EventChange()));// required because of the way SVGSwitch changes images, we only change the frames above. + //dirty = true;// dirty is not sufficient when changing via frames assignments above (i.e. onChange() is required) + } +} + + + +// Dynamic SVGKnob + +DynamicSVGKnob::DynamicSVGKnob() { + mode = nullptr; + oldMode = -1; + effect = new SVGWidget(); + //SVGKnob constructor automatically called +} + +void DynamicSVGKnob::addFrameAll(std::shared_ptr svg) { + framesAll.push_back(svg); + if (framesAll.size() == 1) { + setSVG(svg); + } +} + +void DynamicSVGKnob::addEffect(std::shared_ptr svg) { + effect->setSVG(svg); + addChild(effect); +} + +void DynamicSVGKnob::step() { + if (isNear(rack::global_ui->window.gPixelRatio, 1.0)) { + // Small details draw poorly at low DPI, so oversample when drawing to the framebuffer + oversample = 2.f; + } + if(mode != nullptr && *mode != oldMode) { + if ((*mode) == 0) { + setSVG(framesAll[0]); + effect->visible = false; + } + else { + setSVG(framesAll[1]); + effect->visible = true; + } + oldMode = *mode; + dirty = true; + } + SVGKnob::step(); +} + + + +// Dynamic IMTactile + +DynamicIMTactile::DynamicIMTactile() { + snap = false; + smooth = false;// must be false or else DynamicIMTactile::changeValue() call from module will crash Rack + wider = nullptr; + paramReadRequest = nullptr; + oldWider = -1.0f; + box.size = Vec(padWidth, padHeight); +} + +void DynamicIMTactile::step() { + if(wider != nullptr && *wider != oldWider) { + if ((*wider) > 0.5f) { + box.size = Vec(padWidthWide, padHeight); + } + else { + box.size = Vec(padWidth, padHeight); + } + oldWider = *wider; + } + if (paramReadRequest != nullptr) { + float readVal = *paramReadRequest; + if (readVal != -10.0f) { + setValue(readVal); + *paramReadRequest = -10.0f; + } + } + FramebufferWidget::step(); +} + +void DynamicIMTactile::onDragStart(EventDragStart &e) { + dragValue = value; + dragY = rack::global_ui->app.gRackWidget->lastMousePos.y; +} + +void DynamicIMTactile::onDragMove(EventDragMove &e) { + float rangeValue = maxValue - minValue;// infinite not supported (not relevant) + float newDragY = rack::global_ui->app.gRackWidget->lastMousePos.y; + float delta = -(newDragY - dragY) * rangeValue / box.size.y; + dragY = newDragY; + dragValue += delta; + float dragValueClamped = clamp2(dragValue, minValue, maxValue); + if (snap) + dragValueClamped = roundf(dragValueClamped); + setValue(dragValueClamped); +} + +void DynamicIMTactile::onMouseDown(EventMouseDown &e) { + float val = rescale(e.pos.y, box.size.y, 0.0f , minValue, maxValue); + if (snap) + val = roundf(val); + setValue(val); + ParamWidget::onMouseDown(e); +} + +//void DynamicIMTactile::changeValue(float newVal) { +// setValue(newVal); +//} + + diff --git a/plugins/community/repos/ImpromptuModular/src/IMWidgets.hpp b/plugins/community/repos/ImpromptuModular/src/IMWidgets.hpp new file mode 100644 index 00000000..f4f7fa44 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/IMWidgets.hpp @@ -0,0 +1,165 @@ +//*********************************************************************************************** +//Impromptu Modular: Modules for VCV Rack by Marc Boulé +// +//Based on code from Valley Rack Free by Dale Johnson +//See ./LICENSE.txt for all licenses +//*********************************************************************************************** + +#ifndef IM_WIDGETS_HPP +#define IM_WIDGETS_HPP + +#include "rack.hpp" +#include "window.hpp" + +using namespace rack; + + + +// Dynamic SVGScrew + +// General Dynamic Screw creation +template +TWidget* createDynamicScrew(Vec pos, int* mode) { + TWidget *dynScrew = Widget::create(pos); + dynScrew->mode = mode; + return dynScrew; +} + +struct ScrewCircle : TransparentWidget { + float angle = 0.0f; + float radius = 2.0f; + ScrewCircle(float _angle); + void draw(NVGcontext *vg) override; +}; +struct DynamicSVGScrew : FramebufferWidget { + int* mode; + int oldMode; + // for random rotated screw used in primary mode + SVGWidget *sw; + TransformWidget *tw; + ScrewCircle *sc; + // for fixed svg screw used in alternate mode + SVGWidget* swAlt; + + DynamicSVGScrew(); + void addSVGalt(std::shared_ptr svg); + void step() override; +}; + + + +// Dynamic SVGPanel + +struct PanelBorderWidget_Impromptu : TransparentWidget { // from SVGPanel.cpp + int** expWidth = nullptr; + void draw(NVGcontext *vg) override; +}; + +struct DynamicSVGPanel : FramebufferWidget { // like SVGPanel (in app.hpp and SVGPanel.cpp) but with dynmically assignable panel + int* mode; + int oldMode; + int* expWidth; + std::vector> panels; + SVGWidget* visiblePanel; + PanelBorderWidget_Impromptu* border; + DynamicSVGPanel(); + void addPanel(std::shared_ptr svg); + void step() override; +}; + + + +// ******** Dynamic Ports ******** + +// General Dynamic Port creation +template +TDynamicPort* createDynamicPort(Vec pos, Port::PortType type, Module *module, int portId, + int* mode) { + TDynamicPort *dynPort = Port::create(pos, type, module, portId); + dynPort->mode = mode; + return dynPort; +} + +// Dynamic SVGPort (see SVGPort in app.hpp and SVGPort.cpp) +struct DynamicSVGPort : SVGPort { + int* mode; + int oldMode; + std::vector> frames; + + DynamicSVGPort(); + void addFrame(std::shared_ptr svg); + void step() override; +}; + + + +// ******** Dynamic Params ******** + +// General Dynamic Param creation +template +TDynamicParam* createDynamicParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue, + int* mode) { + TDynamicParam *dynParam = ParamWidget::create(pos, module, paramId, minValue, maxValue, defaultValue); + dynParam->mode = mode; + return dynParam; +} + +// Dynamic SVGSwitch (see SVGSwitch in app.hpp and SVGSwitch.cpp) +struct DynamicSVGSwitch : SVGSwitch { + int* mode; + int oldMode; + std::vector> framesAll; + + DynamicSVGSwitch(); + void addFrameAll(std::shared_ptr svg); + void step() override; +}; + +// Dynamic SVGKnob (see SVGKnob in app.hpp and SVGKnob.cpp) +struct DynamicSVGKnob : SVGKnob { + int* mode; + int oldMode; + std::vector> framesAll; + SVGWidget* effect; + + DynamicSVGKnob(); + void addFrameAll(std::shared_ptr svg); + void addEffect(std::shared_ptr svg);// do this last + void step() override; +}; + + + +// General Dynamic Param creation version two with float* instead of one int* +template +TDynamicParam* createDynamicParam2(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue, + float* wider, float* paramReadRequest) { + TDynamicParam *dynParam = ParamWidget::create(pos, module, paramId, minValue, maxValue, defaultValue); + dynParam->wider = wider; + dynParam->paramReadRequest = paramReadRequest; + return dynParam; +} + +// Dynamic Tactile pad (see Knob in app.hpp and Knob.cpp, and see SVGSlider in SVGSlider.cpp and app.hpp) +struct DynamicIMTactile : ParamWidget, FramebufferWidget { + float* wider;// > 0.5f = true + float* paramReadRequest; + float oldWider; + float dragY; + float dragValue; + bool snap; + static const int padWidth = 45; + static const int padHeight = 200; + static const int padInterSpace = 18; + static const int padWidthWide = padWidth * 2 + padInterSpace; + + DynamicIMTactile(); + void step() override; + void onDragStart(EventDragStart &e) override; + void onDragMove(EventDragMove &e) override; + void onMouseDown(EventMouseDown &e) override; + //void changeValue(float newVal); +}; + + +#endif diff --git a/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp new file mode 100644 index 00000000..029bafa7 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.cpp @@ -0,0 +1,267 @@ +//*********************************************************************************************** +//Impromptu Modular: Modules 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 +//*********************************************************************************************** + +#include "ImpromptuModular.hpp" + +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, Tact); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, TwelveKey); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, Clocked); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, MidiFile); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, PhraseSeq16); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, PhraseSeq32); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, GateSeq64); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, WriteSeq32); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, WriteSeq64); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, BigButtonSeq); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, SemiModularSynth); +RACK_PLUGIN_MODEL_DECLARE(ImpromptuModular, BlankPanel); + +RACK_PLUGIN_INIT(ImpromptuModular) { + RACK_PLUGIN_INIT_ID(); + + RACK_PLUGIN_INIT_WEBSITE("https://github.com/MarcBoule/ImpromptuModular"); + RACK_PLUGIN_INIT_MANUAL("https://github.com/MarcBoule/ImpromptuModular"); + + //p->addModel(modelEngTest1); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, Tact); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, TwelveKey); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, Clocked); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, MidiFile); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, PhraseSeq16); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, PhraseSeq32); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, GateSeq64); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, WriteSeq32); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, WriteSeq64); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, BigButtonSeq); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, SemiModularSynth); + RACK_PLUGIN_MODEL_ADD(ImpromptuModular, BlankPanel); +} + + +LEDBezelBig::LEDBezelBig() { + float ratio = 2.13f; + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/LEDBezel.svg"))); + sw->box.size = sw->box.size.mult(ratio); + box.size = sw->box.size; + tw = new TransformWidget(); + removeChild(sw); + tw->addChild(sw); + + addChild(tw); + + tw->box.size = sw->box.size; + tw->scale(Vec(ratio, ratio)); +} + + +void InvisibleKeySmall::onMouseDown(EventMouseDown &e) { + if (e.button == 1) {// if right button (see events.hpp) + maxValue = 2.0f; + // Simulate MomentarySwitch::onDragStart() since not called for right clicks: + setValue(maxValue); + EventAction eAction; + onAction(eAction); + } + else + maxValue = 1.0f; + //ParamWidget::onMouseDown(e);// don't want the reset() that is called in ParamWidget::onMouseDown(), so implement rest of that function here: + e.consumed = true; + e.target = this; +} +void InvisibleKeySmall::onMouseUp(EventMouseUp &e) { + if (e.button == 1) {// if right button (see events.hpp) + // Simulate MomentarySwitch::onDragEnd() since not called for right clicks: + setValue(minValue); + } + ParamWidget::onMouseUp(e); +} + + +ScrewSilverRandomRot::ScrewSilverRandomRot() { + float angle0_90 = randomUniform()*M_PI/2.0f; + //float angle0_90 = randomUniform() > 0.5f ? M_PI/4.0f : 0.0f;// for testing + + tw = new TransformWidget(); + addChild(tw); + + sw = new SVGWidget(); + tw->addChild(sw); + //sw->setSVG(SVG::load(assetPlugin(plugin, "res/Screw0.svg"))); + sw->setSVG(SVG::load(assetGlobal("res/ComponentLibrary/ScrewSilver.svg"))); + + sc = new ScrewCircle(angle0_90); + sc->box.size = sw->box.size; + tw->addChild(sc); + + box.size = sw->box.size; + tw->box.size = sw->box.size; + tw->identity(); + // Rotate SVG + Vec center = sw->box.getCenter(); + tw->translate(center); + tw->rotate(angle0_90); + tw->translate(center.neg()); +} + + +ScrewHole::ScrewHole(Vec posGiven) { + box.size = Vec(16, 7); + box.pos = Vec(posGiven.x, posGiven.y + 4);// nudgeX for realism, 0 = no nudge +} +void ScrewHole::draw(NVGcontext *vg) { + NVGcolor backgroundColor = nvgRGB(0x10, 0x10, 0x10); + NVGcolor borderColor = nvgRGB(0x20, 0x20, 0x20); + nvgBeginPath(vg); + nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 2.5f); + nvgFillColor(vg, backgroundColor); + nvgFill(vg); + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, borderColor); + nvgStroke(vg); +} + + +NVGcolor prepareDisplay(NVGcontext *vg, Rect *box) { + NVGcolor backgroundColor = nvgRGB(0x38, 0x38, 0x38); + NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10); + nvgBeginPath(vg); + nvgRoundedRect(vg, 0.0, 0.0, box->size.x, box->size.y, 5.0); + nvgFillColor(vg, backgroundColor); + nvgFill(vg); + nvgStrokeWidth(vg, 1.0); + nvgStrokeColor(vg, borderColor); + nvgStroke(vg); + nvgFontSize(vg, 18); + NVGcolor textColor = nvgRGB(0xaf, 0xd2, 0x2c); + return textColor; +} + + +int moveIndex(int index, int indexNext, int numSteps) { + if (indexNext < 0) + index = numSteps - 1; + else + { + if (indexNext - index >= 0) { // if moving right or same place + if (indexNext >= numSteps) + index = 0; + else + index = indexNext; + } + else { // moving left + if (indexNext >= numSteps) + index = numSteps - 1; + else + index = indexNext; + } + } + return index; +} + + +bool moveIndexRunMode(int* index, int numSteps, int runMode, int* history) { + bool crossBoundary = false; + int numRuns;// for FWx + + switch (runMode) { + + case MODE_REV :// reverse; history base is 1000 (not needed) + (*history) = 1000; + (*index)--; + if ((*index) < 0) { + (*index) = numSteps - 1; + crossBoundary = true; + } + break; + + case MODE_PPG :// forward-reverse; history base is 2000 + if ((*history) != 2000 && (*history) != 2001) // 2000 means going forward, 2001 means going reverse + (*history) = 2000; + if ((*history) == 2000) {// forward phase + (*index)++; + if ((*index) >= numSteps) { + (*index) = numSteps - 1; + (*history) = 2001; + } + } + else {// it is 2001; reverse phase + (*index)--; + if ((*index) < 0) { + (*index) = 0; + (*history) = 2000; + crossBoundary = true; + } + } + break; + + case MODE_BRN :// brownian random; history base is 3000 + if ( (*history) < 3000 || ((*history) > (3000 + numSteps)) ) + (*history) = 3000 + numSteps; + (*index) += (randomu32() % 3) - 1; + if ((*index) >= numSteps) { + (*index) = 0; + } + if ((*index) < 0) { + (*index) = numSteps - 1; + } + (*history)--; + if ((*history) <= 3000) { + (*history) = 3000 + numSteps; + crossBoundary = true; + } + break; + + case MODE_RND :// random; history base is 4000 + if ( (*history) < 4000 || ((*history) > (4000 + numSteps)) ) + (*history) = 4000 + numSteps; + (*index) = (randomu32() % numSteps) ; + (*history)--; + if ((*history) <= 4000) { + (*history) = 4000 + numSteps; + crossBoundary = true; + } + break; + + case MODE_FW2 :// forward twice + case MODE_FW3 :// forward three times + case MODE_FW4 :// forward four times + numRuns = 5002 + runMode - MODE_FW2; + if ( (*history) < 5000 || (*history) >= numRuns ) // 5000 means first pass, 5001 means 2nd pass, etc... + (*history) = 5000; + (*index)++; + if ((*index) >= numSteps) { + (*index) = 0; + (*history)++; + if ((*history) >= numRuns) { + (*history) = 5000; + crossBoundary = true; + } + } + break; + + default :// MODE_FWD forward; history base is 0 (not needed) + (*history) = 0; + (*index)++; + if ((*index) >= numSteps) { + (*index) = 0; + crossBoundary = true; + } + } + + return crossBoundary; +} + +bool calcWarningFlash(long count, long countInit) { + bool warningFlashState = true; + if (count > (countInit * 2l / 4l) && count < (countInit * 3l / 4l)) + warningFlashState = false; + else if (count < (countInit * 1l / 4l)) + warningFlashState = false; + return warningFlashState; + } diff --git a/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp new file mode 100644 index 00000000..80584d05 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/ImpromptuModular.hpp @@ -0,0 +1,257 @@ +//*********************************************************************************************** +//Impromptu Modular: Modules 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 +//*********************************************************************************************** + +#ifndef IMPROMPU_MODULAR_HPP +#define IMPROMPU_MODULAR_HPP + + +#include "rack.hpp" +#include "IMWidgets.hpp" + +using namespace rack; + +#define plugin "ImpromptuModular" + +// General constants +static const float lightLambda = 0.075f; +static const std::string lightPanelID = "Classic"; +static const std::string darkPanelID = "Dark-valor"; +static const std::string expansionMenuLabel = "Extra CVs (requires +4HP to the right!)"; +enum RunModeIds {MODE_FWD, MODE_REV, MODE_PPG, MODE_BRN, MODE_RND, MODE_FW2, MODE_FW3, MODE_FW4, NUM_MODES}; +static const std::string modeLabels[NUM_MODES]={"FWD","REV","PPG","BRN","RND","FW2","FW3","FW4"}; +enum GateModeIds {GATE_24, GATE_34, GATE_44, GATE_14, GATE_TRIG, GATE_DUO, GATE_DU1, GATE_DU2, + GATE_TRIPLET, GATE_TRIP1, GATE_TRIP2, GATE_TRIP3, GATE_TRIP4, GATE_TRIP5, GATE_TRIP6, NUM_GATES}; +static const std::string gateLabels[NUM_GATES]={"2/4","3/4","4/4","1/4","TRG","DUO","DU1","DU2", + "TRP","TR1","TR2","TR3","TR4","TR5","TR6"}; + +// Constants for displaying notes + +static const char noteLettersSharp[12] = {'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'}; +static const char noteLettersFlat [12] = {'C', 'D', 'D', 'E', 'E', 'F', 'G', 'G', 'A', 'A', 'B', 'B'}; +static const char isBlackKey [12] = { 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 }; + + +// Component offset constants + +static const int hOffsetCKSS = 5; +static const int vOffsetCKSS = 2; +static const int vOffsetCKSSThree = -2; +static const int hOffsetCKSSH = 2; +static const int vOffsetCKSSH = 5; +static const int offsetCKD6 = -1;//does both h and v +static const int offsetCKD6b = 0;//does both h and v +static const int vOffsetDisplay = -2; +static const int offsetIMBigKnob = -6;//does both h and v +static const int offsetIMSmallKnob = 0;//does both h and v +static const int offsetMediumLight = 9; +static const float offsetLEDbutton = 3.0f;//does both h and v +static const float offsetLEDbuttonLight = 4.4f;//does both h and v +static const int offsetTL1105 = 4;//does both h and v +static const int offsetLEDbezel = 1;//does both h and v +static const float offsetLEDbezelLight = 2.2f;//does both h and v +static const float offsetLEDbezelBig = -11;//does both h and v +static const int offsetTrimpot = 3;//does both h and v + + + +// Variations on existing knobs, lights, etc + + +// Screws + +struct IMScrew : DynamicSVGScrew { + IMScrew() { + addSVGalt(SVG::load(assetPlugin(plugin, "res/dark/comp/ScrewSilver.svg"))); + } +}; + + +// Ports + +struct IMPort : DynamicSVGPort { + IMPort() { + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/PJ301M.svg"))); + addFrame(SVG::load(assetPlugin(plugin, "res/dark/comp/PJ301M.svg"))); + shadow->blurRadius = 10.0; + shadow->opacity = 0.8; + } +}; + + +// Buttons and switches + +struct CKSSH : SVGSwitch, ToggleSwitch { + CKSSH() { + addFrame(SVG::load(assetPlugin(plugin, "res/comp/CKSSH_0.svg"))); + addFrame(SVG::load(assetPlugin(plugin, "res/comp/CKSSH_1.svg"))); + sw->wrap(); + box.size = sw->box.size; + } +}; + +struct CKSSThreeInv : SVGSwitch, ToggleSwitch { + CKSSThreeInv() { + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_2.svg"))); + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_1.svg"))); + addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_0.svg"))); + } +}; + +struct IMBigPushButton : DynamicSVGSwitch, MomentarySwitch { + IMBigPushButton() { + addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/CKD6b_0.svg"))); + addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/CKD6b_1.svg"))); + addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/CKD6b_0.svg"))); + addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/CKD6b_1.svg"))); + } +}; + +struct LEDBezelBig : SVGSwitch, MomentarySwitch { + TransformWidget *tw; + LEDBezelBig(); +}; + + +// Knobs + +struct IMKnob : DynamicSVGKnob { + IMKnob() { + minAngle = -0.83*M_PI; + maxAngle = 0.83*M_PI; + shadow->blurRadius = 10.0; + shadow->opacity = 0.8; + } +}; + +struct IMBigKnob : IMKnob { + IMBigKnob() { + addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/BlackKnobLargeWithMark.svg"))); + addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/BlackKnobLargeWithMark.svg"))); + addEffect(SVG::load(assetPlugin(plugin, "res/dark/comp/BlackKnobLargeWithMarkEffects.svg"))); + } +}; +struct IMBigSnapKnob : IMBigKnob { + IMBigSnapKnob() { + snap = true; + smooth = false; + } +}; + +struct IMBigKnobInf : IMKnob { + IMBigKnobInf() { + addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/BlackKnobLarge.svg"))); + addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/BlackKnobLarge.svg"))); + addEffect(SVG::load(assetPlugin(plugin, "res/dark/comp/BlackKnobLargeEffects.svg"))); + speed = 0.9f; + //smooth = false; + } +}; + +struct IMSmallKnob : IMKnob { + IMSmallKnob() { + addFrameAll(SVG::load(assetPlugin(plugin, "res/light/comp/RoundSmallBlackKnob.svg"))); + addFrameAll(SVG::load(assetPlugin(plugin, "res/dark/comp/RoundSmallBlackKnob.svg"))); + addEffect(SVG::load(assetPlugin(plugin, "res/dark/comp/RoundSmallBlackKnobEffects.svg"))); + shadow->box.pos = Vec(0.0, box.size.y * 0.15); + } +}; + +struct IMSmallSnapKnob : IMSmallKnob { + IMSmallSnapKnob() { + snap = true; + smooth = false; + } +}; + +struct IMFivePosSmallKnob : IMSmallSnapKnob { + IMFivePosSmallKnob() { + minAngle = -0.5*M_PI; + maxAngle = 0.5*M_PI; + } +}; + +struct IMSixPosBigKnob : IMBigSnapKnob { + IMSixPosBigKnob() { + minAngle = -0.4*M_PI; + maxAngle = 0.4*M_PI; + } +}; + +struct IMTactile : DynamicIMTactile { + IMTactile() { + smooth = false;// must be false or else DynamicIMTactile::changeValue() call from module will crash Rack + } +}; + + + +// Lights + +struct OrangeLight : GrayModuleLightWidget { + OrangeLight() { + addBaseColor(COLOR_ORANGE); + } +}; + +template +struct MuteLight : BASE { + MuteLight() { + this->box.size = mm2px(Vec(6.0f, 6.0f)); + } +}; + + +template +struct GiantLight : BASE { + GiantLight() { + this->box.size = mm2px(Vec(19.0f, 19.0f)); + } +}; +template +struct GiantLight2 : BASE { + GiantLight2() { + this->box.size = mm2px(Vec(12.8f, 12.8f)); + } +}; + +// Other + +struct InvisibleKey : MomentarySwitch { + InvisibleKey() { + box.size = Vec(34, 72); + } +}; + +struct InvisibleKeySmall : MomentarySwitch { + InvisibleKeySmall() { + box.size = Vec(23, 50); + } + void onMouseDown(EventMouseDown &e) override; + void onMouseUp(EventMouseUp &e) override; +}; + +struct ScrewSilverRandomRot : FramebufferWidget {// location: include/app.hpp and src/app/SVGScrew.cpp [some code also from src/app/SVGKnob.cpp] + SVGWidget *sw; + TransformWidget *tw; + ScrewCircle *sc; + ScrewSilverRandomRot(); +}; + +struct ScrewHole : TransparentWidget { + ScrewHole(Vec posGiven); + void draw(NVGcontext *vg) override; +}; + + +NVGcolor prepareDisplay(NVGcontext *vg, Rect *box); +int moveIndex(int index, int indexNext, int numSteps); +bool moveIndexRunMode(int* index, int numSteps, int runMode, int* history); +bool calcWarningFlash(long count, long countInit); + +#endif diff --git a/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp b/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp new file mode 100644 index 00000000..fac8e692 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/MidiFileModule.cpp @@ -0,0 +1,217 @@ +//*********************************************************************************************** +//MidiFile 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 +//Also based on Midifile, a C++ MIDI file parsing library by Craig Stuart Sapp +//See ./LICENSE.txt for all licenses +//See ./res/fonts/ for font licenses +// +//Module concept by Marc Boulé +//*********************************************************************************************** + + +/* temporary notes + +https://github.com/craigsapp/midifile + +Dekstop (callback mechanism and file opening): +https://github.com/dekstop/vcvrackplugins_dekstop/blob/master/src/Recorder.cpp + +VCVRack-Simple (file opening): +https://github.com/IohannRabeson/VCVRack-Simple/commit/2d33e97d2e344d2926548a0b9f11f1c15ee4ca3c + + +*/ + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" +#include "midifile/MidiFile.h" +#include "osdialog.h" +#include + +using namespace std; +using namespace smf; + + +//***************************************************************************** + +namespace rack_plugin_ImpromptuModular { + +struct MidiFileModule : Module { + enum ParamIds { + LOADMIDI_PARAM, + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(LOADMIDI_LIGHT, 2), + NUM_LIGHTS + }; + + + // Need to save, with reset + // none + + // Need to save, no reset + int panelTheme; + string lastPath;// TODO: save also the filename so that it can automatically be reloaded when Rack starts? + + // No need to save, with reset + // none + + // No need to save, no reset + MidiFile midifile; + bool fileLoaded; + + + + MidiFileModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + // Need to save, no reset + panelTheme = 0; + lastPath = ""; + + // No need to save, no reset + fileLoaded = false; + + onReset(); + } + + + // widgets are not yet created when module is created (and when onReset() is called by constructor) + // onReset() is also called when right-click initialization of module + void onReset() override { + + } + + + // widgets randomized before onRandomize() is called + void onRandomize() override { + + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + // TODO // Need to save (reset or not) + return rootJ; + } + + + // widgets loaded before this fromJson() is called + void fromJson(json_t *rootJ) override { + // TODO // Need to save (reset or not) + + + // No need to save, with reset + // none + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + lights[LOADMIDI_LIGHT + 0].value = fileLoaded ? 1.0f : 0.0f; + lights[LOADMIDI_LIGHT + 1].value = !fileLoaded ? 1.0f : 0.0f; + }// step() + + + void loadMidiFile() { + + osdialog_filters *filters = osdialog_filters_parse("Midi File (.mid):mid;Text File (.txt):txt"); + string dir = lastPath.empty() ? assetLocal("") : stringDirectory(lastPath); + char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); + if (path) { + lastPath = path; + //lastFilename = stringFilename(path); + if (midifile.read(path)) { + fileLoaded = true; + midifile.doTimeAnalysis(); + midifile.linkNotePairs(); + + int tracks = midifile.getTrackCount(); + cout << "TPQ: " << midifile.getTicksPerQuarterNote() << endl; + if (tracks > 1) cout << "TRACKS: " << tracks << endl; + for (int track=0; track 1) cout << "\nTrack " << track << endl; + cout << "Tick\tSeconds\tDur\tMessage" << endl; + for (int event=0; event 0.0 && moduleL != nullptr) { + moduleL->loadMidiFile(); + } + IMBigPushButton::onChange(e); + } + }; + + + MidiFileWidget(MidiFileModule *module) : ModuleWidget(module) { + // Main panel from Inkscape + DynamicSVGPanel* panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/MidiFile.svg"))); + //panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/MidiFile_dark.svg"))); + box.size = panel->box.size; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 365), &module->panelTheme)); + + // main load button + LoadMidiPushButton* midiButton = createDynamicParam(Vec(100, 100), module, MidiFileModule::LOADMIDI_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme); + midiButton->moduleL = module; + addParam(midiButton); + + // load light + addChild(ModuleLightWidget::create>(Vec(100, 200), module, MidiFileModule::LOADMIDI_LIGHT + 0)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, MidiFile) { + Model *modelMidiFile = Model::create("Impromptu Modular", "Midi-File", "UTIL - Midi-File", MIDI_TAG); + return modelMidiFile; +} + +/*CHANGE LOG + +0.6.10: +created + +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp b/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp new file mode 100644 index 00000000..0a2214ef --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/PhraseSeq16.cpp @@ -0,0 +1,1694 @@ +//*********************************************************************************************** +//Multi-phrase 16 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 +// +//Module inspired by the SA-100 Stepper Acid sequencer by Transistor Sounds Labs +// +//Acknowledgements: please see README.md +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct PhraseSeq16 : Module { + enum ParamIds { + LEFT_PARAM, + RIGHT_PARAM, + LENGTH_PARAM, + EDIT_PARAM, + SEQUENCE_PARAM, + RUN_PARAM, + COPY_PARAM, + PASTE_PARAM, + RESET_PARAM, + ENUMS(OCTAVE_PARAM, 7), + GATE1_PARAM, + GATE2_PARAM, + SLIDE_BTN_PARAM, + SLIDE_KNOB_PARAM, + ATTACH_PARAM, + ROTATEL_PARAM,// no longer used + ROTATER_PARAM,// no longer used + PASTESYNC_PARAM,// no longer used + AUTOSTEP_PARAM, + ENUMS(KEY_PARAMS, 12), + TRANSPOSEU_PARAM,// no longer used + TRANSPOSED_PARAM,// no longer used + // -- 0.6.2 ^^ + RUNMODE_PARAM, + TRAN_ROT_PARAM, + ROTATE_PARAM,//no longer used + GATE1_KNOB_PARAM, + GATE2_KNOB_PARAM,// no longer used + GATE1_PROB_PARAM, + TIE_PARAM,// Legato + // -- 0.6.3 ^^ + CPMODE_PARAM, + // -- 0.6.4 ^^ + NUM_PARAMS + }; + enum InputIds { + WRITE_INPUT, + CV_INPUT, + RESET_INPUT, + CLOCK_INPUT, + // -- 0.6.2 ^^ + LEFTCV_INPUT, + RIGHTCV_INPUT, + RUNCV_INPUT, + SEQCV_INPUT, + MODECV_INPUT, + // -- 0.6.3 ^^ + // -- 0.6.4 ^^ + GATE1CV_INPUT, + GATE2CV_INPUT, + TIEDCV_INPUT, + SLIDECV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + CV_OUTPUT, + GATE1_OUTPUT, + GATE2_OUTPUT, + // -- 0.6.2 ^^ + // -- 0.6.3 ^^ + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(STEP_PHRASE_LIGHTS, 16 * 2),// room for GreenRed + ENUMS(OCTAVE_LIGHTS, 7),// octaves 1 to 7 + ENUMS(KEY_LIGHTS, 12), + RUN_LIGHT, + RESET_LIGHT, + ENUMS(GATE1_LIGHT, 2),// room for GreenRed + ENUMS(GATE2_LIGHT, 2),// room for GreenRed + SLIDE_LIGHT, + ATTACH_LIGHT, + PENDING_LIGHT,// no longer used + // -- 0.6.2 ^^ + GATE1_PROB_LIGHT, + // -- 0.6.3 ^^ + TIE_LIGHT, + NUM_LIGHTS + }; + + enum DisplayStateIds {DISP_NORMAL, DISP_MODE, DISP_TRANSPOSE, DISP_ROTATE}; + enum AttributeBitMasks {ATT_MSK_GATE1 = 0x01, ATT_MSK_GATE1P = 0x02, ATT_MSK_GATE2 = 0x04, ATT_MSK_SLIDE = 0x08, ATT_MSK_TIED = 0x10};// 5 bits + static const int ATT_MSK_GATE1MODE = 0x01E0;// 4 bits + static const int gate1ModeShift = 5; + static const int ATT_MSK_GATE2MODE = 0x1E00;// 4 bits + static const int gate2ModeShift = 9; + + + // Need to save + int panelTheme = 0; + int expansion = 0; + int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 12, 24 PPS (Pulses per step) + bool running; + int runModeSeq[16]; + int runModeSong; + // + int sequence; + int lengths[16];//1 to 16 + // + int phrase[16];// This is the song (series of phases; a phrase is a patten number) + int phrases;//1 to 16 + // + float cv[16][16];// [-3.0 : 3.917]. First index is patten number, 2nd index is step + int attributes[16][16];// First index is patten number, 2nd index is step (see enum AttributeBitMasks for details) + // + bool resetOnRun; + bool attached; + + // No need to save + float resetLight = 0.0f; + int stepIndexEdit; + int stepIndexRun; + int phraseIndexEdit; + int phraseIndexRun; + unsigned long editingLength;// 0 when not editing length, downward step counter timer when editing length + long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste + unsigned long editingGate;// 0 when no edit gate, downward step counter timer when edit gate + float editingGateCV;// no need to initialize, this is a companion to editingGate (output this only when editingGate > 0) + int editingGateKeyLight;// no need to initialize, this is a companion to editingGate (use this only when editingGate > 0) + int stepIndexRunHistory;// no need to initialize + int phraseIndexRunHistory;// no need to initialize + int displayState; + unsigned long slideStepsRemain;// 0 when no slide under way, downward step counter when sliding + float slideCVdelta;// no need to initialize, this is a companion to slideStepsRemain + float cvCPbuffer[16];// copy paste buffer for CVs + int attributesCPbuffer[16];// copy paste buffer for attributes + int lengthCPbuffer; + int modeCPbuffer; + int countCP;// number of steps to paste (in case CPMODE_PARAM changes between copy and paste) + int transposeOffset;// no need to initialize, this is companion to displayMode = DISP_TRANSPOSE + int rotateOffset;// no need to initialize, this is companion to displayMode = DISP_ROTATE + long clockIgnoreOnReset; + const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) + unsigned long clockPeriod;// counts number of step() calls upward from last clock (reset after clock processed) + long tiedWarning;// 0 when no warning, positive downward step counter timer when warning + int sequenceKnob = 0; + bool gate1RandomEnable; + long gate1HoldDetect;// 0 when not detecting, downward counter when detecting + long editingGateLength;// 0 when no info, positive downward step counter timer when gate1, negative upward when gate2 + long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn + int ppqnCount; + + static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget + bool editingSequence; + bool editingSequenceLast; + + + SchmittTrigger resetTrigger; + SchmittTrigger leftTrigger; + SchmittTrigger rightTrigger; + SchmittTrigger runningTrigger; + SchmittTrigger clockTrigger; + SchmittTrigger octTriggers[7]; + SchmittTrigger octmTrigger; + SchmittTrigger gate1Trigger; + SchmittTrigger gate1ProbTrigger; + SchmittTrigger gate2Trigger; + SchmittTrigger slideTrigger; + SchmittTrigger lengthTrigger; + SchmittTrigger keyTriggers[12]; + SchmittTrigger writeTrigger; + SchmittTrigger attachedTrigger; + SchmittTrigger copyTrigger; + SchmittTrigger pasteTrigger; + SchmittTrigger modeTrigger; + SchmittTrigger rotateTrigger; + SchmittTrigger transposeTrigger; + SchmittTrigger tiedTrigger; + + + inline bool getGate1(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1) != 0;} + inline bool getGate2(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2) != 0;} + inline bool getGate1P(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1P) != 0;} + inline bool getTied(int seq, int step) {return (attributes[seq][step] & ATT_MSK_TIED) != 0;} + inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} + inline bool calcGate1RandomEnable(bool gate1P) {return (randomUniform() < (params[GATE1_KNOB_PARAM].value)) || !gate1P;}// randomUniform is [0.0, 1.0), see include/util/common.hpp + inline int ppsToIndex() {return (pulsesPerStep == 24 ? 3 : (pulsesPerStep == 12 ? 2 : (pulsesPerStep == 4 ? 1 : 0)));}// map 1,4,12,24, to 0,1,2,3 + inline int indexToPps(int index) {return (index == 3 ? 24 : (index == 2 ? 12 : (index == 1 ? 4 : 1)));}// inverse map of above + inline int getGate1Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1MODE) >> gate1ModeShift;} + inline int getGate2Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2MODE) >> gate2ModeShift;} + inline void setGate1Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE1MODE; attributes[seq][step] |= (gateMode << gate1ModeShift);} + inline void setGate2Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE2MODE; attributes[seq][step] |= (gateMode << gate2ModeShift);} + + + PhraseSeq16() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + + void onReset() override { + pulsesPerStep = 1; + running = false; + runModeSong = MODE_FWD; + stepIndexEdit = 0; + phraseIndexEdit = 0; + sequence = 0; + phrases = 4; + for (int i = 0; i < 16; i++) { + for (int s = 0; s < 16; s++) { + cv[i][s] = 0.0f; + attributes[i][s] = ATT_MSK_GATE1; + } + runModeSeq[i] = MODE_FWD; + phrase[i] = 0; + lengths[i] = 16; + cvCPbuffer[i] = 0.0f; + attributesCPbuffer[i] = ATT_MSK_GATE1; + } + initRun(true); + lengthCPbuffer = 16; + modeCPbuffer = MODE_FWD; + countCP = 16; + editingLength = 0ul; + editingGate = 0ul; + infoCopyPaste = 0l; + displayState = DISP_NORMAL; + slideStepsRemain = 0ul; + attached = true; + clockPeriod = 0ul; + tiedWarning = 0ul; + editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; + editingSequenceLast = editingSequence; + resetOnRun = false; + gate1HoldDetect = 0l; + editingGateLength = 0l; + editingPpqn = 0l; + } + + + void onRandomize() override { + running = false; + runModeSong = randomu32() % 5; + stepIndexEdit = 0; + phraseIndexEdit = 0; + sequence = randomu32() % 16; + phrases = 1 + (randomu32() % 16); + for (int i = 0; i < 16; i++) { + for (int s = 0; s < 16; s++) { + cv[i][s] = ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f; + attributes[i][s] = randomu32() % 32;// 32 because 5 attributes + if (getTied(i,s)) { + attributes[i][s] = ATT_MSK_TIED;// clear other attributes if tied + applyTiedStep(i, s, lengths[i]); + } + // TODO Randomize gate lengths (even though randomize forces ppqn to 1, can be useful when set to other than 1 after a random) + } + runModeSeq[i] = randomu32() % NUM_MODES; + phrase[i] = randomu32() % 16; + lengths[i] = 1 + (randomu32() % 16); + cvCPbuffer[i] = 0.0f; + attributesCPbuffer[i] = ATT_MSK_GATE1; + } + initRun(true); + lengthCPbuffer = 16; + modeCPbuffer = MODE_FWD; + countCP = 16; + editingLength = 0ul; + editingGate = 0ul; + infoCopyPaste = 0l; + displayState = DISP_NORMAL; + slideStepsRemain = 0ul; + attached = true; + clockPeriod = 0ul; + tiedWarning = 0ul; + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + resetOnRun = false; + gate1HoldDetect = 0l; + editingGateLength = 0l; + editingPpqn = 0l; + } + + + void initRun(bool hard) {// run button activated or run edge in run input jack or edit mode toggled + if (hard) { + phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); + if (editingSequence) + stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); + else + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); + } + gate1RandomEnable = false; + ppqnCount = 0; + if (editingSequence) + gate1RandomEnable = calcGate1RandomEnable(getGate1P(sequence, stepIndexRun)); + else + gate1RandomEnable = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], stepIndexRun)); + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // expansion + json_object_set_new(rootJ, "expansion", json_integer(expansion)); + + // pulsesPerStep + json_object_set_new(rootJ, "pulsesPerStep", json_integer(pulsesPerStep)); + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // runModeSeq + json_t *runModeSeqJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(runModeSeqJ, i, json_integer(runModeSeq[i])); + json_object_set_new(rootJ, "runModeSeq2", runModeSeqJ);// "2" appended so no break patches + + // runModeSong + json_object_set_new(rootJ, "runModeSong", json_integer(runModeSong)); + + // sequence + json_object_set_new(rootJ, "sequence", json_integer(sequence)); + + // lengths + json_t *lengthsJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(lengthsJ, i, json_integer(lengths[i])); + json_object_set_new(rootJ, "lengths", lengthsJ); + + // phrase + json_t *phraseJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(phraseJ, i, json_integer(phrase[i])); + json_object_set_new(rootJ, "phrase", phraseJ); + + // phrases + json_object_set_new(rootJ, "phrases", json_integer(phrases)); + + // CV + json_t *cvJ = json_array(); + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_array_insert_new(cvJ, s + (i * 16), json_real(cv[i][s])); + } + json_object_set_new(rootJ, "cv", cvJ); + + // attributes + json_t *attributesJ = json_array(); + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_array_insert_new(attributesJ, s + (i * 16), json_integer(attributes[i][s])); + } + json_object_set_new(rootJ, "attributes", attributesJ); + + // attached + json_object_set_new(rootJ, "attached", json_boolean(attached)); + + // resetOnRun + json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); + + return rootJ; + } + + void fromJson(json_t *rootJ) override { + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // expansion + json_t *expansionJ = json_object_get(rootJ, "expansion"); + if (expansionJ) + expansion = json_integer_value(expansionJ); + + // pulsesPerStep + json_t *pulsesPerStepJ = json_object_get(rootJ, "pulsesPerStep"); + if (pulsesPerStepJ) + pulsesPerStep = json_integer_value(pulsesPerStepJ); + + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // runModeSeq + json_t *runModeSeqJ = json_object_get(rootJ, "runModeSeq2");// "2" appended so no break patches + if (runModeSeqJ) { + for (int i = 0; i < 16; i++) + { + json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i); + if (runModeSeqArrayJ) + runModeSeq[i] = json_integer_value(runModeSeqArrayJ); + } + } + else {// legacy + runModeSeqJ = json_object_get(rootJ, "runModeSeq"); + if (runModeSeqJ) + runModeSeq[0] = json_integer_value(runModeSeqJ); + for (int i = 1; i < 16; i++) + runModeSeq[i] = runModeSeq[0]; + } + + // runModeSong + json_t *runModeSongJ = json_object_get(rootJ, "runModeSong"); + if (runModeSongJ) + runModeSong = json_integer_value(runModeSongJ); + + // sequence + json_t *sequenceJ = json_object_get(rootJ, "sequence"); + if (sequenceJ) + sequence = json_integer_value(sequenceJ); + + // lengths + json_t *lengthsJ = json_object_get(rootJ, "lengths"); + if (lengthsJ) { + for (int i = 0; i < 16; i++) + { + json_t *lengthsArrayJ = json_array_get(lengthsJ, i); + if (lengthsArrayJ) + lengths[i] = json_integer_value(lengthsArrayJ); + } + } + else {// legacy + json_t *stepsJ = json_object_get(rootJ, "steps"); + if (stepsJ) { + int steps = json_integer_value(stepsJ); + for (int i = 0; i < 16; i++) + lengths[i] = steps; + } + } + + // phrase + json_t *phraseJ = json_object_get(rootJ, "phrase"); + if (phraseJ) + for (int i = 0; i < 16; i++) + { + json_t *phraseArrayJ = json_array_get(phraseJ, i); + if (phraseArrayJ) + phrase[i] = json_integer_value(phraseArrayJ); + } + + // phrases + json_t *phrasesJ = json_object_get(rootJ, "phrases"); + if (phrasesJ) + phrases = json_integer_value(phrasesJ); + + // CV + json_t *cvJ = json_object_get(rootJ, "cv"); + if (cvJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *cvArrayJ = json_array_get(cvJ, s + (i * 16)); + if (cvArrayJ) + cv[i][s] = json_real_value(cvArrayJ); + } + } + + // attributes + json_t *attributesJ = json_object_get(rootJ, "attributes"); + if (attributesJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 16)); + if (attributesArrayJ) + attributes[i][s] = json_integer_value(attributesArrayJ); + } + } + else {// legacy + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) + attributes[i][s] = 0; + // gate1 + json_t *gate1J = json_object_get(rootJ, "gate1"); + if (gate1J) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *gate1arrayJ = json_array_get(gate1J, s + (i * 16)); + if (gate1arrayJ) + if (!!json_integer_value(gate1arrayJ)) attributes[i][s] |= ATT_MSK_GATE1; + } + } + // gate1Prob + json_t *gate1ProbJ = json_object_get(rootJ, "gate1Prob"); + if (gate1ProbJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *gate1ProbarrayJ = json_array_get(gate1ProbJ, s + (i * 16)); + if (gate1ProbarrayJ) + if (!!json_integer_value(gate1ProbarrayJ)) attributes[i][s] |= ATT_MSK_GATE1P; + } + } + // gate2 + json_t *gate2J = json_object_get(rootJ, "gate2"); + if (gate2J) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *gate2arrayJ = json_array_get(gate2J, s + (i * 16)); + if (gate2arrayJ) + if (!!json_integer_value(gate2arrayJ)) attributes[i][s] |= ATT_MSK_GATE2; + } + } + // slide + json_t *slideJ = json_object_get(rootJ, "slide"); + if (slideJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *slideArrayJ = json_array_get(slideJ, s + (i * 16)); + if (slideArrayJ) + if (!!json_integer_value(slideArrayJ)) attributes[i][s] |= ATT_MSK_SLIDE; + } + } + // tied + json_t *tiedJ = json_object_get(rootJ, "tied"); + if (tiedJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *tiedArrayJ = json_array_get(tiedJ, s + (i * 16)); + if (tiedArrayJ) + if (!!json_integer_value(tiedArrayJ)) attributes[i][s] |= ATT_MSK_TIED; + } + } + } + + // attached + json_t *attachedJ = json_object_get(rootJ, "attached"); + if (attachedJ) + attached = json_is_true(attachedJ); + + // resetOnRun + json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); + if (resetOnRunJ) + resetOnRun = json_is_true(resetOnRunJ); + + // Initialize dependants after everything loaded + initRun(true); + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + } + + + void rotateSeq(int seqNum, bool directionRight, int seqLength) { + float rotCV; + int rotAttributes; + int iStart = 0; + int iEnd = seqLength - 1; + int iRot = iStart; + int iDelta = 1; + if (directionRight) { + iRot = iEnd; + iDelta = -1; + } + rotCV = cv[seqNum][iRot]; + rotAttributes = attributes[seqNum][iRot]; + for ( ; ; iRot += iDelta) { + if (iDelta == 1 && iRot >= iEnd) break; + if (iDelta == -1 && iRot <= iStart) break; + cv[seqNum][iRot] = cv[seqNum][iRot + iDelta]; + attributes[seqNum][iRot] = attributes[seqNum][iRot + iDelta]; + } + cv[seqNum][iRot] = rotCV; + attributes[seqNum][iRot] = rotAttributes; + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + float sampleRate = engineGetSampleRate(); + static const float gateTime = 0.4f;// seconds + static const float copyPasteInfoTime = 0.5f;// seconds + static const float editLengthTime = 2.0f;// seconds + static const float tiedWarningTime = 0.7f;// seconds + static const float gateHoldDetectTime = 2.0f;// seconds + static const float editGateLengthTime = 2.5f;// seconds + long tiedWarningInit = (long) (tiedWarningTime * sampleRate); + + + //********** Buttons, knobs, switches and inputs ********** + + // Notes: + // * a tied step's attributes can not be modified by any of the following: + // write input, oct and keyboard buttons, gate1, gate1Prob, gate2 and slide buttons + // however, paste, transpose, rotate obviously can. + // * Whenever cv[][] is modified or tied[] is activated for a step, call applyTiedStep(sequence,stepIndexEdit,steps) + + // Edit mode + editingSequence = isEditingSequence();// true = editing sequence, false = editing song + if (editingSequenceLast != editingSequence) { + if (running) + initRun(true); + displayState = DISP_NORMAL; + editingSequenceLast = editingSequence; + } + + // Seq CV input + if (inputs[SEQCV_INPUT].active) { + sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * (16.0f - 1.0f) / 10.0f), 0.0f, (16.0f - 1.0f) ); + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + // Mode CV input + if (inputs[MODECV_INPUT].active) { + if (editingSequence) + runModeSeq[sequence] = (int) clamp( round(inputs[MODECV_INPUT].value * ((float)NUM_MODES - 1.0f) / 10.0f), 0.0f, (float)NUM_MODES - 1.0f ); + } + + // Run button + if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { + running = !running; + if (running) + initRun(resetOnRun); + displayState = DISP_NORMAL; + } + + // Attach button + if (attachedTrigger.process(params[ATTACH_PARAM].value)) { + if (running) + attached = !attached; + displayState = DISP_NORMAL; + } + if (running && attached) { + if (editingSequence) + stepIndexEdit = stepIndexRun; + else + phraseIndexEdit = phraseIndexRun; + } + + // Copy button + if (copyTrigger.process(params[COPY_PARAM].value)) { + if (editingSequence) { + infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); + //CPinfo must be set to 0 for copy/paste all, and 0x1ii for copy/paste 4 at pos ii, 0x2ii for copy/paste 8 at 0xii + int sStart = stepIndexEdit; + int sCount = 16; + if (params[CPMODE_PARAM].value > 1.5f)// all + sStart = 0; + else if (params[CPMODE_PARAM].value < 0.5f)// 4 + sCount = 4; + else// 8 + sCount = 8; + countCP = sCount; + for (int i = 0, s = sStart; i < countCP; i++, s++) { + if (s >= 16) s = 0; + cvCPbuffer[i] = cv[sequence][s]; + attributesCPbuffer[i] = attributes[sequence][s]; + if ((--sCount) <= 0) + break; + } + lengthCPbuffer = lengths[sequence]; + modeCPbuffer = runModeSeq[sequence]; + } + displayState = DISP_NORMAL; + } + // Paste button + if (pasteTrigger.process(params[PASTE_PARAM].value)) { + if (editingSequence) { + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + int sStart = ((countCP == 16) ? 0 : stepIndexEdit); + int sCount = countCP; + for (int i = 0, s = sStart; i < countCP; i++, s++) { + if (s >= 16) + break; + cv[sequence][s] = cvCPbuffer[i]; + attributes[sequence][s] = attributesCPbuffer[i]; + if ((--sCount) <= 0) + break; + } + if (params[CPMODE_PARAM].value > 1.5f) {// all + lengths[sequence] = lengthCPbuffer; + runModeSeq[sequence] = modeCPbuffer; + } + } + displayState = DISP_NORMAL; + } + + // Length button + if (lengthTrigger.process(params[LENGTH_PARAM].value)) { + if (editingLength > 0ul) + editingLength = 0ul;// allow user to quickly leave editing mode when re-press + else + editingLength = (unsigned long) (editLengthTime * engineGetSampleRate()); + displayState = DISP_NORMAL; + } + + // Write input (must be before Left and Right in case route gate simultaneously to Right and Write for example) + // (write must be to correct step) + bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); + if (writeTrig) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit) & !inputs[TIEDCV_INPUT].active) + tiedWarning = tiedWarningInit; + else { + cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + // Extra CVs from expansion panel: + if (inputs[TIEDCV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[TIEDCV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_TIED) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_TIED); + if (getTied(sequence, stepIndexEdit)) + attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + else { + if (inputs[GATE1CV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[GATE1CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE1) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE1); + if (inputs[GATE2CV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[GATE2CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE2) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE2); + if (inputs[SLIDECV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[SLIDECV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_SLIDE) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_SLIDE); + } + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + // Autostep (after grab all active inputs) + if (params[AUTOSTEP_PARAM].value > 0.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16);//lengths[sequence]);// Commented for full edit capabilities + } + } + displayState = DISP_NORMAL; + } + // Left and Right CV inputs and buttons + int delta = 0; + if (leftTrigger.process(inputs[LEFTCV_INPUT].value + params[LEFT_PARAM].value)) { + delta = -1; + displayState = DISP_NORMAL; + } + if (rightTrigger.process(inputs[RIGHTCV_INPUT].value + params[RIGHT_PARAM].value)) { + delta = +1; + displayState = DISP_NORMAL; + } + if (delta != 0) { + if (editingLength > 0ul) { + editingLength = (unsigned long) (editLengthTime * engineGetSampleRate());// restart editing length timer + if (editingSequence) { + lengths[sequence] += delta; + if (lengths[sequence] > 16) lengths[sequence] = 16; + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + else { + phrases += delta; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + } + else { + if (!running || !attached) {// don't move heads when attach and running + if (editingSequence) { + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + delta, 16);//lengths[sequence]);// Commented for full edit capabilities + if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step + if (!writeTrig) {// in case autostep when simultaneous writeCV and stepCV (keep what was done in Write Input block above) + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + } + } + } + else + phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 16);//phrases);// Commented for full edit capabilities + } + } + } + + // Mode and Transpose/Rotate buttons + if (modeTrigger.process(params[RUNMODE_PARAM].value)) { + if (displayState != DISP_MODE) + displayState = DISP_MODE; + else + displayState = DISP_NORMAL; + } + if (transposeTrigger.process(params[TRAN_ROT_PARAM].value)) { + if (editingSequence) { + if (displayState == DISP_NORMAL || displayState == DISP_MODE) { + displayState = DISP_TRANSPOSE; + transposeOffset = 0; + } + else if (displayState == DISP_TRANSPOSE) { + displayState = DISP_ROTATE; + rotateOffset = 0; + } + else + displayState = DISP_NORMAL; + } + } + + // Sequence knob + float seqParamValue = params[SEQUENCE_PARAM].value; + int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); + if (seqParamValue == 0.0f)// true when constructor or fromJson() occured + sequenceKnob = newSequenceKnob; + int deltaKnob = newSequenceKnob - sequenceKnob; + if (deltaKnob != 0) { + if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) + if (editingLength > 0ul) { + editingLength = (unsigned long) (editLengthTime * engineGetSampleRate());// restart editing length timer + if (editingSequence) { + lengths[sequence] += deltaKnob; + if (lengths[sequence] > 16) lengths[sequence] = 16 ; + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + } + else { + phrases += deltaKnob; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + } + } + else if (editingPpqn != 0) { + editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); + pulsesPerStep = indexToPps(clamp(ppsToIndex() + deltaKnob, 0, 3)); + } + else if (editingGateLength != 0) { + if (editingGateLength > 0) { + editingGateLength = (long) (editGateLengthTime * engineGetSampleRate()); + setGate1Mode(sequence, stepIndexEdit, calcNewGateMode(getGate1Mode(sequence, stepIndexEdit), deltaKnob)); + } + else { + editingGateLength = (long) (-1 * editGateLengthTime * engineGetSampleRate()); + setGate2Mode(sequence, stepIndexEdit, calcNewGateMode(getGate2Mode(sequence, stepIndexEdit), deltaKnob)); + } + } + else if (displayState == DISP_MODE) { + if (editingSequence) { + if (!inputs[MODECV_INPUT].active) { + runModeSeq[sequence] += deltaKnob; + if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0; + if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1; + } + } + else { + runModeSong += deltaKnob; + if (runModeSong < 0) runModeSong = 0; + if (runModeSong >= 5) runModeSong = 5 - 1; + } + } + else if (displayState == DISP_TRANSPOSE) { + if (editingSequence) { + transposeOffset += deltaKnob; + if (transposeOffset > 99) transposeOffset = 99; + if (transposeOffset < -99) transposeOffset = -99; + // Tranpose by this number of semi-tones: deltaKnob + float transposeOffsetCV = ((float)(deltaKnob))/12.0f; + for (int s = 0; s < 16; s++) { + cv[sequence][s] += transposeOffsetCV; + } + } + } + else if (displayState == DISP_ROTATE) { + if (editingSequence) { + rotateOffset += deltaKnob; + if (rotateOffset > 99) rotateOffset = 99; + if (rotateOffset < -99) rotateOffset = -99; + if (deltaKnob > 0 && deltaKnob < 99) {// Rotate right, 99 is safety + for (int i = deltaKnob; i > 0; i--) + rotateSeq(sequence, true, lengths[sequence]); + } + if (deltaKnob < 0 && deltaKnob > -99) {// Rotate left, 99 is safety + for (int i = deltaKnob; i < 0; i++) + rotateSeq(sequence, false, lengths[sequence]); + } + } + } + else {// DISP_NORMAL + if (editingSequence) { + if (!inputs[SEQCV_INPUT].active) { + sequence += deltaKnob; + if (sequence < 0) sequence = 0; + if (sequence >= 16) sequence = (16 - 1); + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + } + else { + phrase[phraseIndexEdit] += deltaKnob; + if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0; + if (phrase[phraseIndexEdit] >= 16) phrase[phraseIndexEdit] = (16 - 1); + } + } + } + sequenceKnob = newSequenceKnob; + } + + // Octave buttons + int newOct = -1; + for (int i = 0; i < 7; i++) { + if (octTriggers[i].process(params[OCTAVE_PARAM + i].value)) { + newOct = 6 - i; + displayState = DISP_NORMAL; + } + } + if (newOct >= 0 && newOct <= 6) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else { + float newCV = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages + newCV = newCV - floor(newCV) + (float) (newOct - 3); + if (newCV >= -3.0f && newCV < 4.0f) { + cv[sequence][stepIndexEdit] = newCV; + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + } + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + } + } + } + + // Keyboard buttons + for (int i = 0; i < 12; i++) { + if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) { + if (params[KEY_PARAMS + i].value > 1.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16); + else + tiedWarning = tiedWarningInit; + } + else { + cv[sequence][stepIndexEdit] = floor(cv[sequence][stepIndexEdit]) + ((float) i) / 12.0f; + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + if (params[KEY_PARAMS + i].value > 1.5f) { + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16); + editingGateKeyLight = i; + } + } + } + displayState = DISP_NORMAL; + } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied buttons + if (gate1Trigger.process(params[GATE1_PARAM].value)) { + if (editingSequence) { + //if (getTied(sequence,stepIndexEdit)) + //tiedWarning = tiedWarningInit; + //else { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1; + if (!running) { + if (pulsesPerStep != 1) + editingGateLength = getGate1(sequence,stepIndexEdit) ? (long) (editGateLengthTime * engineGetSampleRate()) : 0l; + gate1HoldDetect = (long) (gateHoldDetectTime * engineGetSampleRate()); + } + //} + } + displayState = DISP_NORMAL; + } + if (gate1ProbTrigger.process(params[GATE1_PROB_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1P; + } + displayState = DISP_NORMAL; + } + if (gate2Trigger.process(params[GATE2_PARAM].value)) { + if (editingSequence) { + //if (getTied(sequence,stepIndexEdit)) + //tiedWarning = tiedWarningInit; + //else { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; + if (!running) { + if (pulsesPerStep != 1) + editingGateLength = getGate2(sequence,stepIndexEdit) ? (long) (-1 * editGateLengthTime * engineGetSampleRate()) : 0l; + } + //} + } + displayState = DISP_NORMAL; + } + if (slideTrigger.process(params[SLIDE_BTN_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_SLIDE; + } + displayState = DISP_NORMAL; + } + if (tiedTrigger.process(params[TIE_PARAM].value)) { + if (editingSequence) { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_TIED; + if (getTied(sequence,stepIndexEdit)) { + attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + } + else + attributes[sequence][stepIndexEdit] |= (ATT_MSK_GATE1 | ATT_MSK_GATE2); + } + displayState = DISP_NORMAL; + } + + + //********** Clock and reset ********** + + // Clock + if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { + if (ppqnCount >= (pulsesPerStep - 1)) { + if (running && clockIgnoreOnReset == 0l) { + float slideFromCV = 0.0f; + float slideToCV = 0.0f; + if (editingSequence) { + slideFromCV = cv[sequence][stepIndexRun]; + moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); + slideToCV = cv[sequence][stepIndexRun]; + gate1RandomEnable = calcGate1RandomEnable(getGate1P(sequence,stepIndexRun));// must be calculated on clock edge only + } + else { + slideFromCV = cv[phrase[phraseIndexRun]][stepIndexRun]; + if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) { + moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed + } + slideToCV = cv[phrase[phraseIndexRun]][stepIndexRun]; + gate1RandomEnable = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun],stepIndexRun));// must be calculated on clock edge only + } + + // Slide + if ( (editingSequence && ((attributes[sequence][stepIndexRun] & ATT_MSK_SLIDE) != 0) ) || + (!editingSequence && ((attributes[phrase[phraseIndexRun]][stepIndexRun] & ATT_MSK_SLIDE) != 0) ) ) { + // avtivate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) + slideStepsRemain = (unsigned long) (((float)clockPeriod) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) + //slideStepsRemain = (unsigned long) (engineGetSampleRate() * params[SLIDE_KNOB_PARAM].value );// 0-2s slide + slideCVdelta = (slideToCV - slideFromCV)/(float)slideStepsRemain; + } + } + clockPeriod = 0ul; + ppqnCount = 0; + } + else + ppqnCount++; + } + clockPeriod++; + + // Reset + if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + //stepIndexEdit = 0; + //sequence = 0; + initRun(true);// must be after sequence reset + resetLight = 1.0f; + displayState = DISP_NORMAL; + clockTrigger.reset(); + } + else + resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); + + + //********** Outputs and lights ********** + + // CV and gates outputs + int seq = editingSequence ? (sequence) : (running ? phrase[phraseIndexRun] : phrase[phraseIndexEdit]); + int step = editingSequence ? (running ? stepIndexRun : stepIndexEdit) : (stepIndexRun); + if (running) { + float slideOffset = (slideStepsRemain > 0ul ? (slideCVdelta * (float)slideStepsRemain) : 0.0f); + outputs[CV_OUTPUT].value = cv[seq][step] - slideOffset; + outputs[GATE1_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable && + ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; + outputs[GATE2_OUTPUT].value = (clockTrigger.isHigh() && + ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + } + else {// not running + outputs[CV_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step]; + outputs[GATE1_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + outputs[GATE2_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + } + + // Step/phrase lights + if (infoCopyPaste != 0l) { + for (int i = 0; i < 16; i++) { + if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 16) ) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.5f;// Green when copy interval + else + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.0f; // Green (nothing) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f;// Red (nothing) + } + } + else { + for (int i = 0; i < 16; i++) { + if (editingLength > 0ul) { + // Length (green) + if (editingSequence) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < lengths[sequence]) ? 0.5f : 0.0f); + else + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < phrases) ? 0.5f : 0.0f); + // Nothing (red) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f; + } + else { + // Run cursor (green) + if (editingSequence) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((running && (i == stepIndexRun)) ? 1.0f : 0.0f); + else { + float green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); + green += ((running && (i == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = clamp(green, 0.0f, 1.0f); + } + // Edit cursor (red) + if (editingSequence) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == stepIndexEdit ? 1.0f : 0.0f); + else + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == phraseIndexEdit ? 1.0f : 0.0f); + } + } + } + + // Octave lights + float octCV = 0.0f; + if (editingSequence) + octCV = cv[sequence][stepIndexEdit]; + else + octCV = cv[phrase[phraseIndexEdit]][stepIndexRun]; + int octLightIndex = (int) floor(octCV + 3.0f); + for (int i = 0; i < 7; i++) { + if (!editingSequence && (!attached || !running))// no oct lights when song mode and either (detached [1] or stopped [2]) + // [1] makes no sense, can't mod steps and stepping though seq that may not be playing + // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display + lights[OCTAVE_LIGHTS + i].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[OCTAVE_LIGHTS + i].value = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f; + } + else + lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f); + } + } + + // Keyboard lights + float cvValOffset; + if (editingSequence) + cvValOffset = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages + else + cvValOffset = cv[phrase[phraseIndexEdit]][stepIndexRun] + 10.0f;//to properly handle negative note voltages + int keyLightIndex = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); + for (int i = 0; i < 12; i++) { + if (!editingSequence && (!attached || !running))// no keyboard lights when song mode and either (detached [1] or stopped [2]) + // [1] makes no sense, can't mod steps and stepping though seq that may not be playing + // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display + lights[KEY_LIGHTS + i].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[KEY_LIGHTS + i].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; + } + else { + if (editingGate > 0ul && editingGateKeyLight != -1) + lights[KEY_LIGHTS + i].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * engineGetSampleRate())) : 0.0f); + else + lights[KEY_LIGHTS + i].value = (i == keyLightIndex ? 1.0f : 0.0f); + } + } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied lights + int attributesVal = attributes[sequence][stepIndexEdit]; + if (!editingSequence) + attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; + // + lights[GATE1_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; + lights[GATE1_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE1_LIGHT + 1].value : 0.0f); + lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; + lights[GATE2_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; + lights[GATE2_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE2_LIGHT + 1].value : 0.0f); + lights[SLIDE_LIGHT].value = ((attributesVal & ATT_MSK_SLIDE) != 0) ? 1.0f : 0.0f; + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f; + } + else + lights[TIE_LIGHT].value = ((attributesVal & ATT_MSK_TIED) != 0) ? 1.0f : 0.0f; + + // Attach light + lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f; + + // Reset light + lights[RESET_LIGHT].value = resetLight; + + // Run light + lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; + + if (editingLength > 0ul) + editingLength--; + if (editingGate > 0ul) + editingGate--; + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (slideStepsRemain > 0ul) + slideStepsRemain--; + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; + if (tiedWarning > 0l) + tiedWarning--; + if (gate1HoldDetect > 0l) { + if (params[GATE1_PARAM].value < 0.5f) + gate1HoldDetect = 0l; + else { + if (gate1HoldDetect == 1l) { + attributes[sequence][stepIndexEdit] |= ATT_MSK_GATE1; + if (pulsesPerStep == 1) { + pulsesPerStep = 4;// default + } + editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); + } + gate1HoldDetect--; + } + } + if (editingGateLength != 0l) { + if (editingGateLength > 0l) + editingGateLength --; + if (editingGateLength < 0l) + editingGateLength ++; + } + if (editingPpqn > 0l) + editingPpqn--; + + }// step() + + void applyTiedStep(int seqNum, int indexTied, int seqLength) { + // Start on indexTied and loop until seqLength + // Called because either: + // case A: tied was activated for given step + // case B: the given step's CV was modified + // These cases are mutually exclusive + + // copy previous CV over to current step if tied + if (getTied(seqNum,indexTied) && (indexTied > 0)) + cv[seqNum][indexTied] = cv[seqNum][indexTied - 1]; + + // Affect downstream CVs of subsequent tied note chain (can be 0 length if next note is not tied) + for (int i = indexTied + 1; i < seqLength && getTied(seqNum,i); i++) + cv[seqNum][i] = cv[seqNum][indexTied]; + } + + int calcNewGateMode(int currentGateMode, int deltaKnob) { + return clamp(currentGateMode + deltaKnob, 0, NUM_GATES - 1); + } +}; + + + +struct PhraseSeq16Widget : ModuleWidget { + PhraseSeq16 *module; + DynamicSVGPanel *panel; + int oldExpansion; + int expWidth = 60; + IMPort* expPorts[5]; + + struct SequenceDisplayWidget : TransparentWidget { + PhraseSeq16 *module; + std::shared_ptr font; + char displayStr[4]; + + SequenceDisplayWidget() { + font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); + } + + void runModeToStr(int num) { + if (num >= 0 && num < NUM_MODES) + snprintf(displayStr, 4, "%s", modeLabels[num].c_str()); + } + void gateModeToStr(int num) { + if (num >= 0 && num < NUM_GATES) + snprintf(displayStr, 4, "%s", gateLabels[num].c_str()); + } + + 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); + if (module->infoCopyPaste != 0l) { + if (module->infoCopyPaste > 0l) {// if copy display "CPY" + snprintf(displayStr, 4, "CPY"); + } + else {// if paste display "PST" + snprintf(displayStr, 4, "PST"); + } + } + else { + if (module->displayState == PhraseSeq16::DISP_MODE) { + if (module->editingSequence) + runModeToStr(module->runModeSeq[module->sequence]); + else + runModeToStr(module->runModeSong); + } + else if (module->editingLength > 0ul) { + if (module->editingSequence) + snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); + else + snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases); + } + else if (module->editingPpqn != 0ul) { + snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); + } + else if (module->editingGateLength != 0l) { + if (module->editingGateLength > 0l) + gateModeToStr(module->getGate1Mode(module->sequence, module->stepIndexEdit)); + else + gateModeToStr(module->getGate2Mode(module->sequence, module->stepIndexEdit)); + } + else if (module->displayState == PhraseSeq16::DISP_TRANSPOSE) { + snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); + if (module->transposeOffset < 0) + displayStr[0] = '-'; + } + else if (module->displayState == PhraseSeq16::DISP_ROTATE) { + snprintf(displayStr, 4, ")%2u", (unsigned) abs(module->rotateOffset)); + if (module->rotateOffset < 0) + displayStr[0] = '('; + } + else {// DISP_NORMAL + snprintf(displayStr, 4, " %2u", (unsigned) (module->editingSequence ? + module->sequence : module->phrase[module->phraseIndexEdit]) + 1 ); + } + } + displayStr[3] = 0;// more safety + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + struct PanelThemeItem : MenuItem { + PhraseSeq16 *module; + int theme; + void onAction(EventAction &e) override { + module->panelTheme = theme; + } + void step() override { + rightText = (module->panelTheme == theme) ? "✔" : ""; + } + }; + struct ExpansionItem : MenuItem { + PhraseSeq16 *module; + void onAction(EventAction &e) override { + module->expansion = module->expansion == 1 ? 0 : 1; + } + }; + struct ResetOnRunItem : MenuItem { + PhraseSeq16 *module; + void onAction(EventAction &e) override { + module->resetOnRun = !module->resetOnRun; + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + PhraseSeq16 *module = dynamic_cast(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); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); + rorItem->module = module; + menu->addChild(rorItem); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *expansionLabel = new MenuLabel(); + expansionLabel->text = "Expansion module"; + menu->addChild(expansionLabel); + + ExpansionItem *expItem = MenuItem::create(expansionMenuLabel, CHECKMARK(module->expansion != 0)); + expItem->module = module; + menu->addChild(expItem); + + return menu; + } + + void step() override { + if(module->expansion != oldExpansion) { + if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks + for (int i = 0; i < 5; i++) + rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); + } + oldExpansion = module->expansion; + } + box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth; + Widget::step(); + } + + PhraseSeq16Widget(PhraseSeq16 *module) : ModuleWidget(module) { + this->module = module; + oldExpansion = -1; + + // Main panel from Inkscape + panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->expWidth = &expWidth; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PhraseSeq16.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/PhraseSeq16_dark.svg"))); + box.size = panel->box.size; + box.size.x = box.size.x - (1 - module->expansion) * expWidth; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme)); + + + + // ****** Top row ****** + + static const int rowRulerT0 = 48; + static const int columnRulerT0 = 15;// Length button + static const int columnRulerT1 = columnRulerT0 + 47;// Left/Right buttons + static const int columnRulerT2 = columnRulerT1 + 75;// Step/Phase lights + static const int columnRulerT3 = columnRulerT2 + 263;// Attach (also used to align rest of right side of module) + + // Length button + addParam(createDynamicParam(Vec(columnRulerT0 + offsetCKD6b, rowRulerT0 + offsetCKD6b), module, PhraseSeq16::LENGTH_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Left/Right buttons + addParam(createDynamicParam(Vec(columnRulerT1 + offsetCKD6b, rowRulerT0 + offsetCKD6b), module, PhraseSeq16::LEFT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addParam(createDynamicParam(Vec(columnRulerT1 + 38 + offsetCKD6b, rowRulerT0 + offsetCKD6b), module, PhraseSeq16::RIGHT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Step/Phrase lights + static const int spLightsSpacing = 15; + for (int i = 0; i < 16; i++) { + addChild(ModuleLightWidget::create>(Vec(columnRulerT2 + spLightsSpacing * i + offsetMediumLight, rowRulerT0 + offsetMediumLight), module, PhraseSeq16::STEP_PHRASE_LIGHTS + (i*2))); + } + // Attach button and light + addParam(ParamWidget::create(Vec(columnRulerT3 - 4, rowRulerT0 + 2 + offsetTL1105), module, PhraseSeq16::ATTACH_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerT3 + 12 + offsetMediumLight, rowRulerT0 + offsetMediumLight), module, PhraseSeq16::ATTACH_LIGHT)); + + + + // ****** Octave and keyboard area ****** + + // Octave LED buttons + static const float octLightsIntY = 20.0f; + for (int i = 0; i < 7; i++) { + addParam(ParamWidget::create(Vec(15 + 3, 82 + 24 + i * octLightsIntY- 4.4f), module, PhraseSeq16::OCTAVE_PARAM + i, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(15 + 3 + 4.4f, 82 + 24 + i * octLightsIntY), module, PhraseSeq16::OCTAVE_LIGHTS + i)); + } + // Keys and Key lights + static const int keyNudgeX = 7; + static const int keyNudgeY = 2; + static const int offsetKeyLEDx = 6; + static const int offsetKeyLEDy = 28; + // Black keys and lights + addParam(ParamWidget::create( Vec(65+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(65+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 1)); + addParam(ParamWidget::create( Vec(93+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(93+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 3)); + addParam(ParamWidget::create( Vec(150+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(150+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 6)); + addParam(ParamWidget::create( Vec(178+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(178+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 8)); + addParam(ParamWidget::create( Vec(206+keyNudgeX, 89+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(206+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 10)); + // White keys and lights + addParam(ParamWidget::create( Vec(51+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(51+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 0)); + addParam(ParamWidget::create( Vec(79+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(79+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 2)); + addParam(ParamWidget::create( Vec(107+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(107+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 4)); + addParam(ParamWidget::create( Vec(136+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(136+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 5)); + addParam(ParamWidget::create( Vec(164+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(164+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 7)); + addParam(ParamWidget::create( Vec(192+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(192+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 9)); + addParam(ParamWidget::create( Vec(220+keyNudgeX, 139+keyNudgeY), module, PhraseSeq16::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(220+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq16::KEY_LIGHTS + 11)); + + + + // ****** Right side control area ****** + + static const int rowRulerMK0 = 101;// Edit mode row + static const int rowRulerMK1 = rowRulerMK0 + 56; // Run row + static const int rowRulerMK2 = rowRulerMK1 + 54; // Reset row + static const int columnRulerMK0 = 276;// Edit mode column + static const int columnRulerMK1 = columnRulerMK0 + 59;// Display column + static const int columnRulerMK2 = columnRulerT3;// Run mode column + + // Edit mode switch + addParam(ParamWidget::create(Vec(columnRulerMK0 + hOffsetCKSS, rowRulerMK0 + vOffsetCKSS), module, PhraseSeq16::EDIT_PARAM, 0.0f, 1.0f, PhraseSeq16::EDIT_PARAM_INIT_VALUE)); + // Sequence display + SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget(); + displaySequence->box.pos = Vec(columnRulerMK1-15, rowRulerMK0 + 3 + vOffsetDisplay); + displaySequence->box.size = Vec(55, 30);// 3 characters + displaySequence->module = module; + addChild(displaySequence); + // Run mode button + addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK0 + 0 + offsetCKD6b), module, PhraseSeq16::RUNMODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Run LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerMK0 + offsetLEDbezel, rowRulerMK1 + 7 + offsetLEDbezel), module, PhraseSeq16::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerMK0 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK1 + 7 + offsetLEDbezel + offsetLEDbezelLight), module, PhraseSeq16::RUN_LIGHT)); + // Sequence knob + addParam(createDynamicParam(Vec(columnRulerMK1 + 1 + offsetIMBigKnob, rowRulerMK0 + 55 + offsetIMBigKnob), module, PhraseSeq16::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); + // Transpose/rotate button + addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK1 + 4 + offsetCKD6b), module, PhraseSeq16::TRAN_ROT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Reset LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerMK0 + offsetLEDbezel, rowRulerMK2 + 5 + offsetLEDbezel), module, PhraseSeq16::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerMK0 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK2 + 5 + offsetLEDbezel + offsetLEDbezelLight), module, PhraseSeq16::RESET_LIGHT)); + // Copy/paste buttons + addParam(ParamWidget::create(Vec(columnRulerMK1 - 10, rowRulerMK2 + 5 + offsetTL1105), module, PhraseSeq16::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(columnRulerMK1 + 20, rowRulerMK2 + 5 + offsetTL1105), module, PhraseSeq16::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Copy-paste mode switch (3 position) + addParam(ParamWidget::create(Vec(columnRulerMK2 + hOffsetCKSS + 1, rowRulerMK2 - 3 + vOffsetCKSSThree), module, PhraseSeq16::CPMODE_PARAM, 0.0f, 2.0f, 2.0f)); // 0.0f is top position + + + + // ****** Gate and slide section ****** + + static const int rowRulerMB0 = 214; + static const int columnRulerMBspacing = 70; + static const int columnRulerMB2 = 130;// Gate2 + static const int columnRulerMB1 = columnRulerMB2 - columnRulerMBspacing;// Gate1 + static const int columnRulerMB3 = columnRulerMB2 + columnRulerMBspacing;// Tie + static const int posLEDvsButton = + 25; + + // Gate 1 light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB1 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq16::GATE1_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB1 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq16::GATE1_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Gate 2 light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB2 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq16::GATE2_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB2 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq16::GATE2_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Tie light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB3 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq16::TIE_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB3 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq16::TIE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + + + // ****** Bottom two rows ****** + + static const int outputJackSpacingX = 54; + static const int rowRulerB0 = 323; + static const int rowRulerB1 = 269; + static const int columnRulerB0 = 22; + static const int columnRulerB1 = columnRulerB0 + outputJackSpacingX; + static const int columnRulerB2 = columnRulerB1 + outputJackSpacingX; + static const int columnRulerB3 = columnRulerB2 + outputJackSpacingX; + static const int columnRulerB4 = columnRulerB3 + outputJackSpacingX; + static const int columnRulerB7 = columnRulerMK2 + 1; + static const int columnRulerB6 = columnRulerB7 - outputJackSpacingX; + static const int columnRulerB5 = columnRulerB6 - outputJackSpacingX; + + + // Gate 1 probability light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerB0 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, PhraseSeq16::GATE1_PROB_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerB0 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, PhraseSeq16::GATE1_PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Gate 1 probability knob + addParam(createDynamicParam(Vec(columnRulerB1 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, PhraseSeq16::GATE1_KNOB_PARAM, 0.0f, 1.0f, 1.0f, &module->panelTheme)); + // Slide light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerB2 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, PhraseSeq16::SLIDE_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerB2 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, PhraseSeq16::SLIDE_BTN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Slide knob + addParam(createDynamicParam(Vec(columnRulerB3 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, PhraseSeq16::SLIDE_KNOB_PARAM, 0.0f, 2.0f, 0.2f, &module->panelTheme)); + // Autostep + addParam(ParamWidget::create(Vec(columnRulerB4 + hOffsetCKSS, rowRulerB1 + vOffsetCKSS), module, PhraseSeq16::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); + // CV in + addInput(createDynamicPort(Vec(columnRulerB5, rowRulerB1), Port::INPUT, module, PhraseSeq16::CV_INPUT, &module->panelTheme)); + // Clock + addInput(createDynamicPort(Vec(columnRulerB6, rowRulerB1), Port::INPUT, module, PhraseSeq16::CLOCK_INPUT, &module->panelTheme)); + // Reset + addInput(createDynamicPort(Vec(columnRulerB7, rowRulerB1), Port::INPUT, module, PhraseSeq16::RESET_INPUT, &module->panelTheme)); + + + + // ****** Bottom row (all aligned) ****** + + + // CV control Inputs + addInput(createDynamicPort(Vec(columnRulerB0, rowRulerB0), Port::INPUT, module, PhraseSeq16::LEFTCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB1, rowRulerB0), Port::INPUT, module, PhraseSeq16::RIGHTCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB2, rowRulerB0), Port::INPUT, module, PhraseSeq16::SEQCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB3, rowRulerB0), Port::INPUT, module, PhraseSeq16::RUNCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB4, rowRulerB0), Port::INPUT, module, PhraseSeq16::WRITE_INPUT, &module->panelTheme)); + // Outputs + addOutput(createDynamicPort(Vec(columnRulerB5, rowRulerB0), Port::OUTPUT, module, PhraseSeq16::CV_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerB6, rowRulerB0), Port::OUTPUT, module, PhraseSeq16::GATE1_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerB7, rowRulerB0), Port::OUTPUT, module, PhraseSeq16::GATE2_OUTPUT, &module->panelTheme)); + + + // Expansion module + static const int rowRulerExpTop = 65; + static const int rowSpacingExp = 60; + static const int colRulerExp = 497 - 30;// PS16 is 2HP less than PS32 + addInput(expPorts[0] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, PhraseSeq16::GATE1CV_INPUT, &module->panelTheme)); + addInput(expPorts[1] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, PhraseSeq16::GATE2CV_INPUT, &module->panelTheme)); + addInput(expPorts[2] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, PhraseSeq16::TIEDCV_INPUT, &module->panelTheme)); + addInput(expPorts[3] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, PhraseSeq16::SLIDECV_INPUT, &module->panelTheme)); + addInput(expPorts[4] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, PhraseSeq16::MODECV_INPUT, &module->panelTheme)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, PhraseSeq16) { + Model *modelPhraseSeq16 = Model::create("Impromptu Modular", "Phrase-Seq-16", "SEQ - Phrase-Seq-16", SEQUENCER_TAG); + return modelPhraseSeq16; +} + +/*CHANGE LOG + +0.6.10: +allow main knob to also change length when length editing is active + +0.6.9: +add FW2, FW3 and FW4 run modes for sequences (but not for song) +right click on notes now does same as left click but with autostep + +0.6.8: +show length in display when editing length + +0.6.7: +allow full edit capabilities in Seq and song mode +no reset on run by default, with switch added in context menu +reset does not revert seq or song number to 1 +gate 2 is off by default +fix emitted monitoring gates to depend on gate states instead of always triggering + +0.6.6: +knob bug fixes when loading patch + +0.6.5: +paste 4, 8 doesn't loop over to overwrite first steps +small adjustements to gates and CVs used in monitoring write operations +add GATE1, GATE2, TIED, SLIDE CV inputs +add MODE CV input (affects only selected sequence and in Seq mode) +add expansion panel option + +0.6.4: +turn off keyboard and oct lights when detached in song mode (makes no sense, can't mod steps and shows stepping though seq that may not be playing) +removed mode CV input, added paste 4/8/ALL option (ALL copies length and run mode also) +allow each sequence to have its own length and run mode +merged functionalities of transpose and rotate into one knob +implemented tied notes state bit for each step, and added light to tied steps +implemented 0-T slide as opposed to 0-2s slide, where T is clock period +changed copy-paste indication, now uses display rather than keyboard lights + +0.6.3: +added tie step macro button +added gate probabilities (one prob setting for all steps) +removed paste-sync + +0.6.2: +initial release of PS16 +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp b/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp new file mode 100644 index 00000000..cab40249 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/PhraseSeq32.cpp @@ -0,0 +1,1810 @@ +//*********************************************************************************************** +//Multi-phrase 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 +// +//Module inspired by the SA-100 Stepper Acid sequencer by Transistor Sounds Labs +// +//Acknowledgements: please see README.md +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct PhraseSeq32 : Module { + enum ParamIds { + LEFT_PARAM, + RIGHT_PARAM, + RIGHT8_PARAM,// not used + EDIT_PARAM, + SEQUENCE_PARAM, + RUN_PARAM, + COPY_PARAM, + PASTE_PARAM, + RESET_PARAM, + ENUMS(OCTAVE_PARAM, 7), + GATE1_PARAM, + GATE2_PARAM, + SLIDE_BTN_PARAM, + SLIDE_KNOB_PARAM, + ATTACH_PARAM, + AUTOSTEP_PARAM, + ENUMS(KEY_PARAMS, 12), + RUNMODE_PARAM, + TRAN_ROT_PARAM, + GATE1_KNOB_PARAM, + GATE1_PROB_PARAM, + TIE_PARAM,// Legato + CPMODE_PARAM, + ENUMS(STEP_PHRASE_PARAMS, 32), + CONFIG_PARAM, + // -- 0.6.4 ^^ + NUM_PARAMS + }; + enum InputIds { + WRITE_INPUT, + CV_INPUT, + RESET_INPUT, + CLOCK_INPUT, + LEFTCV_INPUT, + RIGHTCV_INPUT, + RUNCV_INPUT, + SEQCV_INPUT, + // -- 0.6.4 ^^ + MODECV_INPUT, + GATE1CV_INPUT, + GATE2CV_INPUT, + TIEDCV_INPUT, + SLIDECV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + CVA_OUTPUT, + GATE1A_OUTPUT, + GATE2A_OUTPUT, + CVB_OUTPUT, + GATE1B_OUTPUT, + GATE2B_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(STEP_PHRASE_LIGHTS, 32 * 2),// room for GreenRed + ENUMS(OCTAVE_LIGHTS, 7),// octaves 1 to 7 + ENUMS(KEY_LIGHTS, 12), + RUN_LIGHT, + RESET_LIGHT, + ENUMS(GATE1_LIGHT, 2),// room for GreenRed + ENUMS(GATE2_LIGHT, 2),// room for GreenRed + SLIDE_LIGHT, + ATTACH_LIGHT, + GATE1_PROB_LIGHT, + TIE_LIGHT, + NUM_LIGHTS + }; + + enum DisplayStateIds {DISP_NORMAL, DISP_MODE, DISP_LENGTH, DISP_TRANSPOSE, DISP_ROTATE}; + enum AttributeBitMasks {ATT_MSK_GATE1 = 0x01, ATT_MSK_GATE1P = 0x02, ATT_MSK_GATE2 = 0x04, ATT_MSK_SLIDE = 0x08, ATT_MSK_TIED = 0x10};// 5 bits + static const int ATT_MSK_GATE1MODE = 0x01E0;// 4 bits + static const int gate1ModeShift = 5; + static const int ATT_MSK_GATE2MODE = 0x1E00;// 4 bits + static const int gate2ModeShift = 9; + + + // Need to save + int panelTheme = 0; + int expansion = 0; + int pulsesPerStep;// 1 means normal gate mode, alt choices are 4, 12, 24 PPS (Pulses per step) + bool running; + int runModeSeq[32]; + int runModeSong; + // + int sequence; + int lengths[32];//1 to 32 + // + int phrase[32];// This is the song (series of phases; a phrase is a patten number) + int phrases;//1 to 32 + // + float cv[32][32];// [-3.0 : 3.917]. First index is patten number, 2nd index is step + int attributes[32][32];// First index is patten number, 2nd index is step (see enum AttributeBitMasks for details) + // + bool resetOnRun; + bool attached; + + // No need to save + float resetLight = 0.0f; + int stepIndexEdit; + int stepIndexRun; + int phraseIndexEdit; + int phraseIndexRun; + long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste + unsigned long editingGate;// 0 when no edit gate, downward step counter timer when edit gate + float editingGateCV;// no need to initialize, this is a companion to editingGate (output this only when editingGate > 0) + int editingGateKeyLight;// no need to initialize, this is a companion to editingGate (use this only when editingGate > 0) + int editingChannel;// 0 means channel A, 1 means channel B. no need to initialize, this is a companion to editingGate + int stepIndexRunHistory;// no need to initialize + int phraseIndexRunHistory;// no need to initialize + int displayState; + unsigned long slideStepsRemain[2];// 0 when no slide under way, downward step counter when sliding + float slideCVdelta[2];// no need to initialize, this is a companion to slideStepsRemain + float cvCPbuffer[32];// copy paste buffer for CVs + int attributesCPbuffer[32];// copy paste buffer for attributes + int lengthCPbuffer; + int modeCPbuffer; + int countCP;// number of steps to paste (in case CPMODE_PARAM changes between copy and paste) + int transposeOffset;// no need to initialize, this is companion to displayMode = DISP_TRANSPOSE + int rotateOffset;// no need to initialize, this is companion to displayMode = DISP_ROTATE + long clockIgnoreOnReset; + const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) + unsigned long clockPeriod;// counts number of step() calls upward from last clock (reset after clock processed) + long tiedWarning;// 0 when no warning, positive downward step counter timer when warning + int sequenceKnob = 0; + bool gate1RandomEnable[2]; + bool attachedChanB; + long revertDisplay; + long gate1HoldDetect;// 0 when not detecting, downward counter when detecting + long editingGateLength;// 0 when no info, positive downward step counter timer when gate1, negative upward when gate2 + long editingPpqn;// 0 when no info, positive downward step counter timer when editing ppqn + int ppqnCount; + + static constexpr float CONFIG_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget + int stepConfigLast; + static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget + bool editingSequence; + bool editingSequenceLast; + + + SchmittTrigger resetTrigger; + SchmittTrigger leftTrigger; + SchmittTrigger rightTrigger; + SchmittTrigger runningTrigger; + SchmittTrigger clockTrigger; + SchmittTrigger octTriggers[7]; + SchmittTrigger octmTrigger; + SchmittTrigger gate1Trigger; + SchmittTrigger gate1ProbTrigger; + SchmittTrigger gate2Trigger; + SchmittTrigger slideTrigger; + SchmittTrigger keyTriggers[12]; + SchmittTrigger writeTrigger; + SchmittTrigger attachedTrigger; + SchmittTrigger copyTrigger; + SchmittTrigger pasteTrigger; + SchmittTrigger modeTrigger; + SchmittTrigger rotateTrigger; + SchmittTrigger transposeTrigger; + SchmittTrigger tiedTrigger; + SchmittTrigger stepTriggers[32]; + + + inline bool getGate1(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1) != 0;} + inline bool getGate2(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2) != 0;} + inline bool getGate1P(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1P) != 0;} + inline bool getTied(int seq, int step) {return (attributes[seq][step] & ATT_MSK_TIED) != 0;} + inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} + inline bool calcGate1RandomEnable(bool gate1P) {return (randomUniform() < (params[GATE1_KNOB_PARAM].value)) || !gate1P;}// randomUniform is [0.0, 1.0), see include/util/common.hpp + inline int ppsToIndex() {return (pulsesPerStep == 24 ? 3 : (pulsesPerStep == 12 ? 2 : (pulsesPerStep == 4 ? 1 : 0)));}// map 1,4,12,24, to 0,1,2,3 + inline int indexToPps(int index) {return (index == 3 ? 24 : (index == 2 ? 12 : (index == 1 ? 4 : 1)));}// inverse map of above + inline int getGate1Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1MODE) >> gate1ModeShift;} + inline int getGate2Mode(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2MODE) >> gate2ModeShift;} + inline void setGate1Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE1MODE; attributes[seq][step] |= (gateMode << gate1ModeShift);} + inline void setGate2Mode(int seq, int step, int gateMode) {attributes[seq][step] &= ~ATT_MSK_GATE2MODE; attributes[seq][step] |= (gateMode << gate2ModeShift);} + + + PhraseSeq32() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + + // widgets are not yet created when module is created (and when onReset() is called by constructor) + // onReset() is also called when right-click initialization of module + void onReset() override { + int stepConfig = 1;// 2x16 + if (CONFIG_PARAM_INIT_VALUE < 0.5f)// 1x32 + stepConfig = 2; + stepConfigLast = stepConfig; + pulsesPerStep = 1; + running = false; + runModeSong = MODE_FWD; + stepIndexEdit = 0; + phraseIndexEdit = 0; + sequence = 0; + phrases = 4; + for (int i = 0; i < 32; i++) { + for (int s = 0; s < 32; s++) { + cv[i][s] = 0.0f; + attributes[i][s] = ATT_MSK_GATE1; + } + runModeSeq[i] = MODE_FWD; + phrase[i] = 0; + lengths[i] = 16 * stepConfig; + cvCPbuffer[i] = 0.0f; + attributesCPbuffer[i] = ATT_MSK_GATE1; + } + initRun(stepConfig, true); + lengthCPbuffer = 32; + modeCPbuffer = MODE_FWD; + countCP = 32; + editingGate = 0ul; + infoCopyPaste = 0l; + displayState = DISP_NORMAL; + slideStepsRemain[0] = 0ul; + slideStepsRemain[1] = 0ul; + attached = true; + clockPeriod = 0ul; + tiedWarning = 0ul; + attachedChanB = false; + revertDisplay = 0l; + editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; + editingSequenceLast = editingSequence; + resetOnRun = false; + gate1HoldDetect = 0l; + editingGateLength = 0l; + editingPpqn = 0l; + } + + + // widgets randomized before onRandomize() is called + void onRandomize() override { + int stepConfig = 1;// 2x16 + if (params[CONFIG_PARAM].value < 0.5f)// 1x32 + stepConfig = 2; + stepConfigLast = stepConfig; + running = false; + runModeSong = randomu32() % 5; + stepIndexEdit = 0; + phraseIndexEdit = 0; + sequence = randomu32() % 32; + phrases = 1 + (randomu32() % 32); + for (int i = 0; i < 32; i++) { + for (int s = 0; s < 32; s++) { + cv[i][s] = ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f; + attributes[i][s] = randomu32() % 32;// 32 because 5 attributes + if (getTied(i,s)) { + attributes[i][s] = ATT_MSK_TIED;// clear other attributes if tied + applyTiedStep(i, s, lengths[i]); + } + // TODO Randomize gate lengths (even though randomize forces ppqn to 1, can be useful when set to other than 1 after a random) + } + runModeSeq[i] = randomu32() % NUM_MODES; + phrase[i] = randomu32() % 32; + lengths[i] = 1 + (randomu32() % (16 * stepConfig)); + cvCPbuffer[i] = 0.0f; + attributesCPbuffer[i] = ATT_MSK_GATE1; + } + initRun(stepConfig, true); + lengthCPbuffer = 32; + modeCPbuffer = MODE_FWD; + countCP = 32; + editingGate = 0ul; + infoCopyPaste = 0l; + displayState = DISP_NORMAL; + slideStepsRemain[0] = 0ul; + slideStepsRemain[1] = 0ul; + attached = true; + clockPeriod = 0ul; + tiedWarning = 0ul; + attachedChanB = false; + revertDisplay = 0l; + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + resetOnRun = false; + } + + + void initRun(int stepConfig, bool hard) {// run button activated or run edge in run input jack or edit mode toggled + if (hard) { + phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); + if (editingSequence) + stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); + else + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); + } + gate1RandomEnable[0] = false; + gate1RandomEnable[1] = false; + ppqnCount = 0; + if (editingSequence) { + for (int i = 0; i < 2; i += stepConfig) + gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(sequence, (i * 16) + stepIndexRun)); + } + else { + for (int i = 0; i < 2; i += stepConfig) + gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], (i * 16) + stepIndexRun)); + } + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // expansion + json_object_set_new(rootJ, "expansion", json_integer(expansion)); + + // pulsesPerStep + json_object_set_new(rootJ, "pulsesPerStep", json_integer(pulsesPerStep)); + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // runModeSeq + json_t *runModeSeqJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(runModeSeqJ, i, json_integer(runModeSeq[i])); + json_object_set_new(rootJ, "runModeSeq2", runModeSeqJ);// "2" appended so no break patches + + // runModeSong + json_object_set_new(rootJ, "runModeSong", json_integer(runModeSong)); + + // sequence + json_object_set_new(rootJ, "sequence", json_integer(sequence)); + + // lengths + json_t *lengthsJ = json_array(); + for (int i = 0; i < 32; i++) + json_array_insert_new(lengthsJ, i, json_integer(lengths[i])); + json_object_set_new(rootJ, "lengths", lengthsJ); + + // phrase + json_t *phraseJ = json_array(); + for (int i = 0; i < 32; i++) + json_array_insert_new(phraseJ, i, json_integer(phrase[i])); + json_object_set_new(rootJ, "phrase", phraseJ); + + // phrases + json_object_set_new(rootJ, "phrases", json_integer(phrases)); + + // CV + json_t *cvJ = json_array(); + for (int i = 0; i < 32; i++) + for (int s = 0; s < 32; s++) { + json_array_insert_new(cvJ, s + (i * 32), json_real(cv[i][s])); + } + json_object_set_new(rootJ, "cv", cvJ); + + // attributes + json_t *attributesJ = json_array(); + for (int i = 0; i < 32; i++) + for (int s = 0; s < 32; s++) { + json_array_insert_new(attributesJ, s + (i * 32), json_integer(attributes[i][s])); + } + json_object_set_new(rootJ, "attributes", attributesJ); + + // attached + json_object_set_new(rootJ, "attached", json_boolean(attached)); + + // resetOnRun + json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); + + return rootJ; + } + + + // widgets loaded before this fromJson() is called + void fromJson(json_t *rootJ) override { + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // expansion + json_t *expansionJ = json_object_get(rootJ, "expansion"); + if (expansionJ) + expansion = json_integer_value(expansionJ); + + // pulsesPerStep + json_t *pulsesPerStepJ = json_object_get(rootJ, "pulsesPerStep"); + if (pulsesPerStepJ) + pulsesPerStep = json_integer_value(pulsesPerStepJ); + + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // runModeSeq + json_t *runModeSeqJ = json_object_get(rootJ, "runModeSeq2");// "2" appended so no break patches + if (runModeSeqJ) { + for (int i = 0; i < 16; i++) + { + json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i); + if (runModeSeqArrayJ) + runModeSeq[i] = json_integer_value(runModeSeqArrayJ); + } + } + + // runModeSong + json_t *runModeSongJ = json_object_get(rootJ, "runModeSong"); + if (runModeSongJ) + runModeSong = json_integer_value(runModeSongJ); + + // sequence + json_t *sequenceJ = json_object_get(rootJ, "sequence"); + if (sequenceJ) + sequence = json_integer_value(sequenceJ); + + // lengths + json_t *lengthsJ = json_object_get(rootJ, "lengths"); + if (lengthsJ) + for (int i = 0; i < 32; i++) + { + json_t *lengthsArrayJ = json_array_get(lengthsJ, i); + if (lengthsArrayJ) + lengths[i] = json_integer_value(lengthsArrayJ); + } + + // phrase + json_t *phraseJ = json_object_get(rootJ, "phrase"); + if (phraseJ) + for (int i = 0; i < 32; i++) + { + json_t *phraseArrayJ = json_array_get(phraseJ, i); + if (phraseArrayJ) + phrase[i] = json_integer_value(phraseArrayJ); + } + + // phrases + json_t *phrasesJ = json_object_get(rootJ, "phrases"); + if (phrasesJ) + phrases = json_integer_value(phrasesJ); + + // CV + json_t *cvJ = json_object_get(rootJ, "cv"); + if (cvJ) { + for (int i = 0; i < 32; i++) + for (int s = 0; s < 32; s++) { + json_t *cvArrayJ = json_array_get(cvJ, s + (i * 32)); + if (cvArrayJ) + cv[i][s] = json_real_value(cvArrayJ); + } + } + + // attributes + json_t *attributesJ = json_object_get(rootJ, "attributes"); + if (attributesJ) { + for (int i = 0; i < 32; i++) + for (int s = 0; s < 32; s++) { + json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 32)); + if (attributesArrayJ) + attributes[i][s] = json_integer_value(attributesArrayJ); + } + } + + // attached + json_t *attachedJ = json_object_get(rootJ, "attached"); + if (attachedJ) + attached = json_is_true(attachedJ); + + // resetOnRun + json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); + if (resetOnRunJ) + resetOnRun = json_is_true(resetOnRunJ); + + // Initialize dependants after everything loaded (widgets already loaded when reach here) + int stepConfig = 1;// 2x16 + if (params[CONFIG_PARAM].value < 0.5f)// 1x32 + stepConfig = 2; + stepConfigLast = stepConfig; + initRun(stepConfig, true); + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + } + + void rotateSeq(int seqNum, bool directionRight, int seqLength, bool chanB_16) { + // set chanB_16 to false to rotate chan A in 2x16 config (length will be <= 16) or single chan in 1x32 config (length will be <= 32) + // set chanB_16 to true to rotate chan B in 2x16 config (length must be <= 16) + float rotCV; + int rotAttributes; + int iStart = chanB_16 ? 16 : 0; + int iEnd = iStart + seqLength - 1; + int iRot = iStart; + int iDelta = 1; + if (directionRight) { + iRot = iEnd; + iDelta = -1; + } + rotCV = cv[seqNum][iRot]; + rotAttributes = attributes[seqNum][iRot]; + for ( ; ; iRot += iDelta) { + if (iDelta == 1 && iRot >= iEnd) break; + if (iDelta == -1 && iRot <= iStart) break; + cv[seqNum][iRot] = cv[seqNum][iRot + iDelta]; + attributes[seqNum][iRot] = attributes[seqNum][iRot + iDelta]; + } + cv[seqNum][iRot] = rotCV; + attributes[seqNum][iRot] = rotAttributes; + } + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + float sampleRate = engineGetSampleRate(); + static const float gateTime = 0.4f;// seconds + static const float copyPasteInfoTime = 0.5f;// seconds + static const float revertDisplayTime = 0.7f;// seconds + static const float tiedWarningTime = 0.7f;// seconds + static const float gateHoldDetectTime = 2.0f;// seconds + static const float editGateLengthTime = 2.5f;// seconds + long tiedWarningInit = (long) (tiedWarningTime * sampleRate); + + + //********** Buttons, knobs, switches and inputs ********** + + // Notes: + // * a tied step's attributes can not be modified by any of the following: + // write input, oct and keyboard buttons, gate1, gate1Prob, gate2 and slide buttons + // however, paste, transpose, rotate obviously can. + // * Whenever cv[][] is modified or tied[] is activated for a step, call applyTiedStep(sequence,stepIndexEdit,steps) + + + // Config switch + int stepConfig = 1;// 2x16 + if (params[CONFIG_PARAM].value < 0.5f)// 1x32 + stepConfig = 2; + // Config: set lengths to their new max when move switch + if (stepConfigLast != stepConfig) { + for (int i = 0; i < 32; i++) + lengths[i] = 16 * stepConfig; + attachedChanB = false; + stepConfigLast = stepConfig; + } + + // Edit mode + editingSequence = isEditingSequence();// true = editing sequence, false = editing song + if (editingSequenceLast != editingSequence) { + if (running) + initRun(stepConfig, true); + displayState = DISP_NORMAL; + editingSequenceLast = editingSequence; + } + + // Seq CV input + if (inputs[SEQCV_INPUT].active) { + sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * (32.0f - 1.0f) / 10.0f), 0.0f, (32.0f - 1.0f) ); + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + // Mode CV input + if (inputs[MODECV_INPUT].active) { + if (editingSequence) + runModeSeq[sequence] = (int) clamp( round(inputs[MODECV_INPUT].value * ((float)NUM_MODES - 1.0f) / 10.0f), 0.0f, (float)NUM_MODES - 1.0f ); + } + + // Run button + if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { + running = !running; + if (running) + initRun(stepConfig, resetOnRun); + displayState = DISP_NORMAL; + } + + // Attach button + if (attachedTrigger.process(params[ATTACH_PARAM].value)) { + if (running) { + attached = !attached; + if (attached && editingSequence && stepConfig == 1 ) + attachedChanB = stepIndexEdit >= 16; + } + displayState = DISP_NORMAL; + } + if (running && attached) { + if (editingSequence) + stepIndexEdit = stepIndexRun + ((attachedChanB && stepConfig == 1) ? 16 : 0); + else + phraseIndexEdit = phraseIndexRun; + } + + // Copy button + if (copyTrigger.process(params[COPY_PARAM].value)) { + if (editingSequence) { + infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); + //CPinfo must be set to 0 for copy/paste all, and 0x1ii for copy/paste 4 at pos ii, 0x2ii for copy/paste 8 at 0xii + int sStart = stepIndexEdit; + int sCount = 32; + if (params[CPMODE_PARAM].value > 1.5f)// all + sStart = 0; + else if (params[CPMODE_PARAM].value < 0.5f)// 4 + sCount = 4; + else// 8 + sCount = 8; + countCP = sCount; + for (int i = 0, s = sStart; i < countCP; i++, s++) { + if (s >= 32) s = 0; + cvCPbuffer[i] = cv[sequence][s]; + attributesCPbuffer[i] = attributes[sequence][s]; + if ((--sCount) <= 0) + break; + } + lengthCPbuffer = lengths[sequence]; + modeCPbuffer = runModeSeq[sequence]; + } + displayState = DISP_NORMAL; + } + // Paste button + if (pasteTrigger.process(params[PASTE_PARAM].value)) { + if (editingSequence) { + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + int sStart = ((countCP == 32) ? 0 : stepIndexEdit); + int sCount = countCP; + for (int i = 0, s = sStart; i < countCP; i++, s++) { + if (s >= 32) break; + cv[sequence][s] = cvCPbuffer[i]; + attributes[sequence][s] = attributesCPbuffer[i]; + if ((--sCount) <= 0) + break; + } + if (params[CPMODE_PARAM].value > 1.5f) {// all + lengths[sequence] = lengthCPbuffer; + if (lengths[sequence] > 16 * stepConfig) + lengths[sequence] = 16 * stepConfig; + runModeSeq[sequence] = modeCPbuffer; + } + } + displayState = DISP_NORMAL; + } + + // Write input (must be before Left and Right in case route gate simultaneously to Right and Write for example) + // (write must be to correct step) + bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); + if (writeTrig) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit) & !inputs[TIEDCV_INPUT].active) + tiedWarning = tiedWarningInit; + else { + cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; + applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); + // Extra CVs from expansion panel: + if (inputs[TIEDCV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[TIEDCV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_TIED) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_TIED); + if (getTied(sequence, stepIndexEdit)) + attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + else { + if (inputs[GATE1CV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[GATE1CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE1) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE1); + if (inputs[GATE2CV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[GATE2CV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_GATE2) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_GATE2); + if (inputs[SLIDECV_INPUT].active) + attributes[sequence][stepIndexEdit] = (inputs[SLIDECV_INPUT].value > 1.0f) ? (attributes[sequence][stepIndexEdit] | ATT_MSK_SLIDE) : (attributes[sequence][stepIndexEdit] & ~ATT_MSK_SLIDE); + } + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; + // Autostep (after grab all active inputs) + if (params[AUTOSTEP_PARAM].value > 0.5f) { + stepIndexEdit += 1; + //if (stepConfig == 1) { // Commented for full edit capabilities (whole if) + // if (stepIndexEdit >= lengths[sequence] && stepIndexEdit < 16)// if past length in chan A + // stepIndexEdit = 16; + // else if (stepIndexEdit >= 16 + lengths[sequence])// if past length in chan B (including >=32) + // stepIndexEdit = 0; + //} + //else // Commented for full edit capabilities + if (stepIndexEdit >= 32)//lengths[sequence])// Commented for full edit capabilities + stepIndexEdit = 0; + } + } + } + displayState = DISP_NORMAL; + } + // Left and right CV inputs + int delta = 0; + if (leftTrigger.process(inputs[LEFTCV_INPUT].value)) { + delta = -1; + if (displayState != DISP_LENGTH) + displayState = DISP_NORMAL; + } + if (rightTrigger.process(inputs[RIGHTCV_INPUT].value)) { + delta = +1; + if (displayState != DISP_LENGTH) + displayState = DISP_NORMAL; + } + if (delta != 0) { + if (displayState == DISP_LENGTH) { + if (editingSequence) { + lengths[sequence] += delta; + if (lengths[sequence] > (16 * stepConfig)) lengths[sequence] = (16 * stepConfig); + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + lengths[sequence] = ((lengths[sequence] - 1) % (16 * stepConfig)) + 1; + //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities + } + else { + phrases += delta; + if (phrases > 32) phrases = 32; + if (phrases < 1 ) phrases = 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + } + else { + if (!running || !attached) {// don't move heads when attach and running + if (editingSequence) { + stepIndexEdit += delta; + if (stepIndexEdit < 0) + stepIndexEdit = ((stepConfig == 1) ? 16 : 0) + lengths[sequence] - 1; + //if (stepConfig == 1) {// Commented for full edit capabilities (whole if) + // if (stepIndexEdit >= lengths[sequence] && stepIndexEdit < 16)// if past length in chan A + // stepIndexEdit = delta > 0 ? 16 : lengths[sequence] - 1; + // else if (stepIndexEdit >= 16 + lengths[sequence])// if past length in chan B (including >=32) + // stepIndexEdit = delta > 0 ? 0 : 16 + lengths[sequence] - 1; + //} + //else// Commented for full edit capabilities + if (stepIndexEdit >= 32)//lengths[sequence]) // Commented for full edit capabilities + stepIndexEdit = 0; + if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step + if (!writeTrig) {// in case autostep when simultaneous writeCV and stepCV (keep what was done in Write Input block above) + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; + } + } + } + else + phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 32);//phrases);// Commented for full edit capabilities + } + } + } + + // Step button presses + int stepPressed = -1; + for (int i = 0; i < 32; i++) { + if (stepTriggers[i].process(params[STEP_PHRASE_PARAMS + i].value)) + stepPressed = i; + } + if (stepPressed != -1) { + if (displayState == DISP_LENGTH) { + if (editingSequence) { + lengths[sequence] = (stepPressed % (16 * stepConfig)) + 1; + //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities + } + else { + phrases = stepPressed + 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + revertDisplay = (long) (revertDisplayTime * engineGetSampleRate()); + } + else { + if (!running || !attached) {// not running or detached + if (editingSequence) { + stepIndexEdit = stepPressed; + //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities + if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; + } + } + else { + phraseIndexEdit = stepPressed; + //if (phraseIndexEdit >= phrases)// Commented for full edit capabilities + //phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + } + else {// attached and running + if (editingSequence) { + if ((stepPressed < 16) && attachedChanB) + attachedChanB = false; + if ((stepPressed >= 16) && !attachedChanB) + attachedChanB = true; + } + } + displayState = DISP_NORMAL; + } + } + + // Mode/Length button + if (modeTrigger.process(params[RUNMODE_PARAM].value)) { + if (displayState == DISP_NORMAL || displayState == DISP_TRANSPOSE || displayState == DISP_ROTATE) + displayState = DISP_LENGTH; + else if (displayState == DISP_LENGTH) + displayState = DISP_MODE; + else + displayState = DISP_NORMAL; + } + + // Transpose/Rotate button + if (transposeTrigger.process(params[TRAN_ROT_PARAM].value)) { + if (editingSequence) { + if (displayState == DISP_NORMAL || displayState == DISP_MODE || displayState == DISP_LENGTH) { + displayState = DISP_TRANSPOSE; + transposeOffset = 0; + } + else if (displayState == DISP_TRANSPOSE) { + displayState = DISP_ROTATE; + rotateOffset = 0; + } + else + displayState = DISP_NORMAL; + } + } + + // Sequence knob + float seqParamValue = params[SEQUENCE_PARAM].value; + int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); + if (seqParamValue == 0.0f)// true when constructor or fromJson() occured + sequenceKnob = newSequenceKnob; + int deltaKnob = newSequenceKnob - sequenceKnob; + if (deltaKnob != 0) { + if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) + if (editingPpqn != 0) { + editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); + pulsesPerStep = indexToPps(clamp(ppsToIndex() + deltaKnob, 0, 3)); + } + else if (editingGateLength != 0) { + if (editingGateLength > 0) { + editingGateLength = (long) (editGateLengthTime * engineGetSampleRate()); + setGate1Mode(sequence, stepIndexEdit, calcNewGateMode(getGate1Mode(sequence, stepIndexEdit), deltaKnob)); + } + else { + editingGateLength = (long) (-1 * editGateLengthTime * engineGetSampleRate()); + setGate2Mode(sequence, stepIndexEdit, calcNewGateMode(getGate2Mode(sequence, stepIndexEdit), deltaKnob)); + } + } + else if (displayState == DISP_MODE) { + if (editingSequence) { + if (!inputs[MODECV_INPUT].active) { + runModeSeq[sequence] += deltaKnob; + if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0; + if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1; + } + } + else { + runModeSong += deltaKnob; + if (runModeSong < 0) runModeSong = 0; + if (runModeSong >= 5) runModeSong = 5 - 1; + } + } + else if (displayState == DISP_LENGTH) { + if (editingSequence) { + lengths[sequence] += deltaKnob; + if (lengths[sequence] > (16 * stepConfig)) lengths[sequence] = (16 * stepConfig); + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + //if ( (stepIndexEdit % (16 * stepConfig)) >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = (lengths[sequence] - 1) + 16 * (stepIndexEdit / (16 * stepConfig));// Commented for full edit capabilities + } + else { + phrases += deltaKnob; + if (phrases > 32) phrases = 32; + if (phrases < 1 ) phrases = 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + } + else if (displayState == DISP_TRANSPOSE) { + if (editingSequence) { + transposeOffset += deltaKnob; + if (transposeOffset > 99) transposeOffset = 99; + if (transposeOffset < -99) transposeOffset = -99; + // Tranpose by this number of semi-tones: deltaKnob + float transposeOffsetCV = ((float)(deltaKnob))/12.0f; + if (stepConfig == 1){ // 2x16 (transpose only the 16 steps corresponding to row where stepIndexEdit is located) + int offset = stepIndexEdit < 16 ? 0 : 16; + for (int s = offset; s < offset + 16; s++) + cv[sequence][s] += transposeOffsetCV; + } + else { // 1x32 (transpose all 32 steps) + for (int s = 0; s < 32; s++) + cv[sequence][s] += transposeOffsetCV; + } + } + } + else if (displayState == DISP_ROTATE) { + if (editingSequence) { + rotateOffset += deltaKnob; + if (rotateOffset > 99) rotateOffset = 99; + if (rotateOffset < -99) rotateOffset = -99; + if (deltaKnob > 0 && deltaKnob < 99) {// Rotate right, 99 is safety + for (int i = deltaKnob; i > 0; i--) + rotateSeq(sequence, true, lengths[sequence], stepConfig == 1 && stepIndexEdit >= 16); + } + if (deltaKnob < 0 && deltaKnob > -99) {// Rotate left, 99 is safety + for (int i = deltaKnob; i < 0; i++) + rotateSeq(sequence, false, lengths[sequence], stepConfig == 1 && stepIndexEdit >= 16); + } + } + } + else {// DISP_NORMAL + if (editingSequence) { + if (!inputs[SEQCV_INPUT].active) { + sequence += deltaKnob; + if (sequence < 0) sequence = 0; + if (sequence >= 32) sequence = (32 - 1); + //if (stepConfig == 1) {// Commented for full edit capabilities (whole if/else) + // if (stepIndexEdit >= 16 && (stepIndexEdit - 16) >= lengths[sequence]) + // stepIndexEdit = 16 + lengths[sequence] - 1; + // else if (stepIndexEdit < 16 && (stepIndexEdit) >= lengths[sequence]) + // stepIndexEdit = lengths[sequence] - 1; + //} + //else { + // if (stepIndexEdit >= lengths[sequence]) + // stepIndexEdit = lengths[sequence] - 1; + //} + } + } + else { + // // no roll over + // phrase[phraseIndexEdit] += deltaKnob; + // if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0; + // if (phrase[phraseIndexEdit] >= 32) phrase[phraseIndexEdit] = (32 - 1); + + // roll over + int newPhrase = phrase[phraseIndexEdit] + deltaKnob; + if (newPhrase < 0) + newPhrase += (1 - newPhrase / 32) * 32;// newPhrase now positive + newPhrase = newPhrase % 32; + phrase[phraseIndexEdit] = newPhrase; + + } + } + } + sequenceKnob = newSequenceKnob; + } + + // Octave buttons + int newOct = -1; + for (int i = 0; i < 7; i++) { + if (octTriggers[i].process(params[OCTAVE_PARAM + i].value)) { + newOct = 6 - i; + displayState = DISP_NORMAL; + } + } + if (newOct >= 0 && newOct <= 6) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else { + float newCV = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages + newCV = newCV - floor(newCV) + (float) (newOct - 3); + if (newCV >= -3.0f && newCV < 4.0f) { + cv[sequence][stepIndexEdit] = newCV; + applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); + } + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; + } + } + } + + // Keyboard buttons + for (int i = 0; i < 12; i++) { + if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) { + if (params[KEY_PARAMS + i].value > 1.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32); + else + tiedWarning = tiedWarningInit; + } + else { + cv[sequence][stepIndexEdit] = floor(cv[sequence][stepIndexEdit]) + ((float) i) / 12.0f; + applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + editingChannel = (stepIndexEdit >= 16 * stepConfig) ? 1 : 0; + if (params[KEY_PARAMS + i].value > 1.5f) { + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 32); + editingGateKeyLight = i; + } + } + } + displayState = DISP_NORMAL; + } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied buttons + if (gate1Trigger.process(params[GATE1_PARAM].value)) { + if (editingSequence) { + //if (getTied(sequence,stepIndexEdit)) + //tiedWarning = tiedWarningInit; + //else { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1; + if (!running) { + if (pulsesPerStep != 1) + editingGateLength = getGate1(sequence,stepIndexEdit) ? (long) (editGateLengthTime * engineGetSampleRate()) : 0l; + gate1HoldDetect = (long) (gateHoldDetectTime * engineGetSampleRate()); + } + //} + } + displayState = DISP_NORMAL; + } + if (gate1ProbTrigger.process(params[GATE1_PROB_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1P; + } + displayState = DISP_NORMAL; + } + if (gate2Trigger.process(params[GATE2_PARAM].value)) { + if (editingSequence) { + //if (getTied(sequence,stepIndexEdit)) + //tiedWarning = tiedWarningInit; + //else { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; + if (!running) { + if (pulsesPerStep != 1) + editingGateLength = getGate2(sequence,stepIndexEdit) ? (long) (-1 * editGateLengthTime * engineGetSampleRate()) : 0l; + } + //} + } + displayState = DISP_NORMAL; + } + if (slideTrigger.process(params[SLIDE_BTN_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_SLIDE; + } + displayState = DISP_NORMAL; + } + if (tiedTrigger.process(params[TIE_PARAM].value)) { + if (editingSequence) { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_TIED; + if (getTied(sequence,stepIndexEdit)) { + attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + applyTiedStep(sequence, stepIndexEdit, ((stepIndexEdit >= 16 && stepConfig == 1) ? 16 : 0) + lengths[sequence]); + } + else + attributes[sequence][stepIndexEdit] |= (ATT_MSK_GATE1 | ATT_MSK_GATE2); + } + displayState = DISP_NORMAL; + } + + + //********** Clock and reset ********** + + // Clock + if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { + if (ppqnCount >= (pulsesPerStep - 1)) { + if (running && clockIgnoreOnReset == 0l) { + float slideFromCV[2] = {0.0f, 0.0f}; + float slideToCV[2] = {0.0f, 0.0f}; + if (editingSequence) { + for (int i = 0; i < 2; i += stepConfig) + slideFromCV[i] = cv[sequence][(i * 16) + stepIndexRun]; + moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); + for (int i = 0; i < 2; i += stepConfig) { + slideToCV[i] = cv[sequence][(i * 16) + stepIndexRun]; + gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(sequence, (i * 16) + stepIndexRun));// must be calculated on clock edge only + } + } + else { + for (int i = 0; i < 2; i += stepConfig) + slideFromCV[i] = cv[phrase[phraseIndexRun]][(i * 16) + stepIndexRun]; + if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) { + moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed + } + for (int i = 0; i < 2; i += stepConfig) { + slideToCV[i] = cv[phrase[phraseIndexRun]][(i * 16) + stepIndexRun]; + gate1RandomEnable[i] = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], (i * 16) + stepIndexRun));// must be calculated on clock edge only + } + } + + // Slide + for (int i = 0; i < 2; i += stepConfig) { + if ( ( editingSequence && ((attributes[sequence][(i * 16) + stepIndexRun] & ATT_MSK_SLIDE) != 0) ) || + (!editingSequence && ((attributes[phrase[phraseIndexRun]][(i * 16) + stepIndexRun] & ATT_MSK_SLIDE) != 0) ) ) { + // avtivate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) + slideStepsRemain[i] = (unsigned long) (((float)clockPeriod) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) + //slideStepsRemain[i] = (unsigned long) (engineGetSampleRate() * params[SLIDE_KNOB_PARAM].value );// 0-2s slide + slideCVdelta[i] = (slideToCV[i] - slideFromCV[i])/(float)slideStepsRemain[i]; + } + } + } + clockPeriod = 0ul; + ppqnCount = 0; + } + else + ppqnCount++; + } + clockPeriod++; + + // Reset + if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + //stepIndexEdit = 0; + //sequence = 0; + initRun(stepConfig, true);// must be after sequence reset + resetLight = 1.0f; + displayState = DISP_NORMAL; + clockTrigger.reset(); + } + else + resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); + + + //********** Outputs and lights ********** + + // CV and gates outputs + int seq = editingSequence ? (sequence) : (running ? phrase[phraseIndexRun] : phrase[phraseIndexEdit]); + int step = editingSequence ? (running ? stepIndexRun : stepIndexEdit) : (stepIndexRun); + if (running) { + float slideOffset[2]; + for (int i = 0; i < 2; i += stepConfig) + slideOffset[i] = (slideStepsRemain[i] > 0ul ? (slideCVdelta[i] * (float)slideStepsRemain[i]) : 0.0f); + if (stepConfig == 1) { + outputs[CVA_OUTPUT].value = cv[seq][step] - slideOffset[0]; + outputs[GATE1A_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable[0] && + ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; + outputs[GATE2A_OUTPUT].value = (clockTrigger.isHigh() && + ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + outputs[CVB_OUTPUT].value = cv[seq][16 + step] - slideOffset[1]; + outputs[GATE1B_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable[1] && + ((attributes[seq][16 + step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; + outputs[GATE2B_OUTPUT].value = (clockTrigger.isHigh() && + ((attributes[seq][16 + step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + } + else { + outputs[CVA_OUTPUT].value = cv[seq][step] - slideOffset[0]; + outputs[GATE1A_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable[0] && + ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; + outputs[GATE2A_OUTPUT].value = (clockTrigger.isHigh() && + ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + outputs[CVB_OUTPUT].value = 0.0f; + outputs[GATE1B_OUTPUT].value = 0.0f; + outputs[GATE2B_OUTPUT].value = 0.0f; + } + } + else {// not running + if (editingChannel == 0) { + outputs[CVA_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step]; + outputs[GATE1A_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + outputs[GATE2A_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + outputs[CVB_OUTPUT].value = 0.0f; + outputs[GATE1B_OUTPUT].value = 0.0f; + outputs[GATE2B_OUTPUT].value = 0.0f; + } + else { + outputs[CVA_OUTPUT].value = 0.0f; + outputs[GATE1A_OUTPUT].value = 0.0f; + outputs[GATE2A_OUTPUT].value = 0.0f; + outputs[CVB_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step]; + outputs[GATE1B_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + outputs[GATE2B_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + } + } + + // Step/phrase lights + if (infoCopyPaste != 0l) { + for (int i = 0; i < 32; i++) { + if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 32) ) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.5f;// Green when copy interval + else + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.0f; // Green (nothing) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f;// Red (nothing) + } + } + else { + for (int i = 0; i < 32; i++) { + if (displayState == DISP_LENGTH) { + if (editingSequence) { + int col = i % (16 * stepConfig); + if (col < (lengths[sequence] - 1)) + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f); + else if (col == (lengths[sequence] - 1)) + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 1.0f, 0.0f); + else + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.0f, 0.0f); + } + else { + if (i < phrases - 1) + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, 0.1f, 0.0f); + else + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, (i == phrases - 1) ? 1.0f : 0.0f, 0.0f); + } + } + else {// normal led display (i.e. not length) + float red = 0.0f; + float green = 0.0f; + + // Run cursor (green) + if (editingSequence) + green = ((running && (i % (16 * stepConfig)) == stepIndexRun)) ? 1.0f : 0.0f; + else { + green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); + green += ((running && ((i % (16 * stepConfig)) == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); + green = clamp(green, 0.0f, 1.0f); + } + // Edit cursor (red) + if (editingSequence) + red = (i == stepIndexEdit ? 1.0f : 0.0f); + else + red = (i == phraseIndexEdit ? 1.0f : 0.0f); + + setGreenRed(STEP_PHRASE_LIGHTS + i * 2, green, red); + } + } + } + + // Octave lights + float octCV = 0.0f; + if (editingSequence) + octCV = cv[sequence][stepIndexEdit]; + else + octCV = cv[phrase[phraseIndexEdit]][stepIndexRun]; + int octLightIndex = (int) floor(octCV + 3.0f); + for (int i = 0; i < 7; i++) { + if (!editingSequence && (!attached || !running || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3]) + // [1] makes no sense, can't mod steps and stepping though seq that may not be playing + // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display + // [3] makes no sense, which sequence would be displayed, top or bottom row! + lights[OCTAVE_LIGHTS + i].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[OCTAVE_LIGHTS + i].value = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f; + } + else + lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f); + } + } + + // Keyboard lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations) + float cvValOffset; + if (editingSequence) + cvValOffset = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages + else + cvValOffset = cv[phrase[phraseIndexEdit]][stepIndexRun] + 10.0f;//to properly handle negative note voltages + int keyLightIndex = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); + for (int i = 0; i < 12; i++) { + if (!editingSequence && (!attached || !running || (stepConfig == 1)))// no oct lights when song mode and either (detached [1] or stopped [2] or 2x16config [3]) + // [1] makes no sense, can't mod steps and stepping though seq that may not be playing + // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display + // [3] makes no sense, which sequence would be displayed, top or bottom row! + lights[KEY_LIGHTS + i].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[KEY_LIGHTS + i].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; + } + else { + if (editingGate > 0ul && editingGateKeyLight != -1) + lights[KEY_LIGHTS + i].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * engineGetSampleRate())) : 0.0f); + else + lights[KEY_LIGHTS + i].value = (i == keyLightIndex ? 1.0f : 0.0f); + } + } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied lights (can only show channel A when running attached in 1x16 mode, does not pose problem for all other situations) + int attributesVal = attributes[sequence][stepIndexEdit]; + if (!editingSequence) + attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; + // + lights[GATE1_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; + lights[GATE1_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE1_LIGHT + 1].value : 0.0f); + lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; + lights[GATE2_LIGHT + 1].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; + lights[GATE2_LIGHT + 0].value = ((pulsesPerStep != 1) ? lights[GATE2_LIGHT + 1].value : 0.0f); + lights[SLIDE_LIGHT].value = ((attributesVal & ATT_MSK_SLIDE) != 0) ? 1.0f : 0.0f; + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f; + } + else + lights[TIE_LIGHT].value = ((attributesVal & ATT_MSK_TIED) != 0) ? 1.0f : 0.0f; + + // Attach light + lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f; + + // Reset light + lights[RESET_LIGHT].value = resetLight; + + // Run light + lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; + + if (editingGate > 0ul) + editingGate--; + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + for (int i = 0; i < 2; i++) + if (slideStepsRemain[i] > 0ul) + slideStepsRemain[i]--; + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; + if (tiedWarning > 0l) + tiedWarning--; + if (gate1HoldDetect > 0l) { + if (params[GATE1_PARAM].value < 0.5f) + gate1HoldDetect = 0l; + else { + if (gate1HoldDetect == 1l) { + attributes[sequence][stepIndexEdit] |= ATT_MSK_GATE1; + if (pulsesPerStep == 1) { + pulsesPerStep = 4;// default + } + editingPpqn = (long) (editGateLengthTime * engineGetSampleRate()); + } + gate1HoldDetect--; + } + } + if (editingGateLength != 0l) { + if (editingGateLength > 0l) + editingGateLength --; + if (editingGateLength < 0l) + editingGateLength ++; + } + if (editingPpqn > 0l) + editingPpqn--; + if (revertDisplay > 0l) { + if (revertDisplay == 1) + displayState = DISP_NORMAL; + revertDisplay--; + } + + }// step() + + void setGreenRed(int id, float green, float red) { + lights[id + 0].value = green; + lights[id + 1].value = red; + } + + void applyTiedStep(int seqNum, int indexTied, int seqLength) { + // Start on indexTied and loop until seqLength + // Called because either: + // case A: tied was activated for given step + // case B: the given step's CV was modified + // These cases are mutually exclusive + + // copy previous CV over to current step if tied + if (getTied(seqNum,indexTied) && (indexTied > 0)) + cv[seqNum][indexTied] = cv[seqNum][indexTied - 1]; + + // Affect downstream CVs of subsequent tied note chain (can be 0 length if next note is not tied) + for (int i = indexTied + 1; i < seqLength && getTied(seqNum,i); i++) + cv[seqNum][i] = cv[seqNum][indexTied]; + } + + int calcNewGateMode(int currentGateMode, int deltaKnob) { + return clamp(currentGateMode + deltaKnob, 0, NUM_GATES - 1); + } +}; + + + +struct PhraseSeq32Widget : ModuleWidget { + PhraseSeq32 *module; + DynamicSVGPanel *panel; + int oldExpansion; + int expWidth = 60; + IMPort* expPorts[5]; + + struct SequenceDisplayWidget : TransparentWidget { + PhraseSeq32 *module; + std::shared_ptr font; + char displayStr[4]; + + SequenceDisplayWidget() { + font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); + } + + void runModeToStr(int num) { + if (num >= 0 && num < NUM_MODES) + snprintf(displayStr, 4, "%s", modeLabels[num].c_str()); + } + void gateModeToStr(int num) { + if (num >= 0 && num < NUM_GATES) + snprintf(displayStr, 4, "%s", gateLabels[num].c_str()); + } + + 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); + if (module->infoCopyPaste != 0l) { + if (module->infoCopyPaste > 0l) {// if copy display "CPY" + snprintf(displayStr, 4, "CPY"); + } + else {// if paste display "PST" + snprintf(displayStr, 4, "PST"); + } + } + else { + if (module->displayState == PhraseSeq32::DISP_MODE) { + if (module->editingSequence) + runModeToStr(module->runModeSeq[module->sequence]); + else + runModeToStr(module->runModeSong); + } + else if (module->displayState == PhraseSeq32::DISP_LENGTH) { + if (module->editingSequence) + snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); + else + snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases); + } + else if (module->editingPpqn != 0ul) { + snprintf(displayStr, 4, "x%2u", (unsigned) module->pulsesPerStep); + } + else if (module->editingGateLength != 0l) { + if (module->editingGateLength > 0l) + gateModeToStr(module->getGate1Mode(module->sequence, module->stepIndexEdit)); + else + gateModeToStr(module->getGate2Mode(module->sequence, module->stepIndexEdit)); + } + else if (module->displayState == PhraseSeq32::DISP_TRANSPOSE) { + snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); + if (module->transposeOffset < 0) + displayStr[0] = '-'; + } + else if (module->displayState == PhraseSeq32::DISP_ROTATE) { + snprintf(displayStr, 4, ")%2u", (unsigned) abs(module->rotateOffset)); + if (module->rotateOffset < 0) + displayStr[0] = '('; + } + else {// DISP_NORMAL + snprintf(displayStr, 4, " %2u", (unsigned) (module->editingSequence ? + module->sequence : module->phrase[module->phraseIndexEdit]) + 1 ); + } + } + displayStr[3] = 0;// more safety + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + struct PanelThemeItem : MenuItem { + PhraseSeq32 *module; + int theme; + void onAction(EventAction &e) override { + module->panelTheme = theme; + } + void step() override { + rightText = (module->panelTheme == theme) ? "✔" : ""; + } + }; + struct ExpansionItem : MenuItem { + PhraseSeq32 *module; + void onAction(EventAction &e) override { + module->expansion = module->expansion == 1 ? 0 : 1; + } + }; + struct ResetOnRunItem : MenuItem { + PhraseSeq32 *module; + void onAction(EventAction &e) override { + module->resetOnRun = !module->resetOnRun; + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + PhraseSeq32 *module = dynamic_cast(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); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); + rorItem->module = module; + menu->addChild(rorItem); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *expansionLabel = new MenuLabel(); + expansionLabel->text = "Expansion module"; + menu->addChild(expansionLabel); + + ExpansionItem *expItem = MenuItem::create(expansionMenuLabel, CHECKMARK(module->expansion != 0)); + expItem->module = module; + menu->addChild(expItem); + + return menu; + } + + void step() override { + if(module->expansion != oldExpansion) { + if (oldExpansion!= -1 && module->expansion == 0) {// if just removed expansion panel, disconnect wires to those jacks + for (int i = 0; i < 5; i++) + rack::global_ui->app.gRackWidget->wireContainer->removeAllWires(expPorts[i]); + } + oldExpansion = module->expansion; + } + box.size.x = panel->box.size.x - (1 - module->expansion) * expWidth; + Widget::step(); + } + + PhraseSeq32Widget(PhraseSeq32 *module) : ModuleWidget(module) { + this->module = module; + oldExpansion = -1; + + // Main panel from Inkscape + panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->expWidth = &expWidth; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/PhraseSeq32.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/PhraseSeq32_dark.svg"))); + box.size = panel->box.size; + box.size.x = box.size.x - (1 - module->expansion) * expWidth; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(panel->box.size.x-30-expWidth, 365), &module->panelTheme)); + + + + // ****** Top row ****** + + static const int rowRulerT0 = 48; + static const int columnRulerT0 = 18;//30;// Step/Phase LED buttons + static const int columnRulerT3 = 377;// Attach + static const int columnRulerT4 = 430;// Config + + // Step/Phrase LED buttons + int posX = columnRulerT0; + static int spacingSteps = 20; + static int spacingSteps4 = 4; + for (int x = 0; x < 16; x++) { + // First row + addParam(ParamWidget::create(Vec(posX, rowRulerT0 - 10 + 3 - 4.4f), module, PhraseSeq32::STEP_PHRASE_PARAMS + x, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(posX + 4.4f, rowRulerT0 - 10 + 3), module, PhraseSeq32::STEP_PHRASE_LIGHTS + (x * 2))); + // Second row + addParam(ParamWidget::create(Vec(posX, rowRulerT0 + 10 + 3 - 4.4f), module, PhraseSeq32::STEP_PHRASE_PARAMS + x + 16, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(posX + 4.4f, rowRulerT0 + 10 + 3), module, PhraseSeq32::STEP_PHRASE_LIGHTS + ((x + 16) * 2))); + // step position to next location and handle groups of four + posX += spacingSteps; + if ((x + 1) % 4 == 0) + posX += spacingSteps4; + } + // Attach button and light + addParam(ParamWidget::create(Vec(columnRulerT3 - 4, rowRulerT0 - 6 + 2 + offsetTL1105), module, PhraseSeq32::ATTACH_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerT3 + 12 + offsetMediumLight, rowRulerT0 - 6 + offsetMediumLight), module, PhraseSeq32::ATTACH_LIGHT)); + // Config switch + addParam(ParamWidget::create(Vec(columnRulerT4 + hOffsetCKSS + 1, rowRulerT0 - 6 + vOffsetCKSS), module, PhraseSeq32::CONFIG_PARAM, 0.0f, 1.0f, PhraseSeq32::CONFIG_PARAM_INIT_VALUE)); + + + + // ****** Octave and keyboard area ****** + + // Octave LED buttons + static const float octLightsIntY = 20.0f; + for (int i = 0; i < 7; i++) { + addParam(ParamWidget::create(Vec(15 + 3, 82 + 24 + i * octLightsIntY- 4.4f), module, PhraseSeq32::OCTAVE_PARAM + i, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(15 + 3 + 4.4f, 82 + 24 + i * octLightsIntY), module, PhraseSeq32::OCTAVE_LIGHTS + i)); + } + // Keys and Key lights + static const int keyNudgeX = 7; + static const int keyNudgeY = 2; + static const int offsetKeyLEDx = 6; + static const int offsetKeyLEDy = 28; + // Black keys and lights + addParam(ParamWidget::create( Vec(65+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(65+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 1)); + addParam(ParamWidget::create( Vec(93+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(93+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 3)); + addParam(ParamWidget::create( Vec(150+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(150+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 6)); + addParam(ParamWidget::create( Vec(178+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(178+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 8)); + addParam(ParamWidget::create( Vec(206+keyNudgeX, 89+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(206+keyNudgeX+offsetKeyLEDx, 89+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 10)); + // White keys and lights + addParam(ParamWidget::create( Vec(51+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(51+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 0)); + addParam(ParamWidget::create( Vec(79+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(79+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 2)); + addParam(ParamWidget::create( Vec(107+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(107+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 4)); + addParam(ParamWidget::create( Vec(136+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(136+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 5)); + addParam(ParamWidget::create( Vec(164+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(164+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 7)); + addParam(ParamWidget::create( Vec(192+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(192+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 9)); + addParam(ParamWidget::create( Vec(220+keyNudgeX, 139+keyNudgeY), module, PhraseSeq32::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(220+keyNudgeX+offsetKeyLEDx, 139+keyNudgeY+offsetKeyLEDy), module, PhraseSeq32::KEY_LIGHTS + 11)); + + + + // ****** Right side control area ****** + + static const int rowRulerMK0 = 101;// Edit mode row + static const int rowRulerMK1 = rowRulerMK0 + 56; // Run row + static const int rowRulerMK2 = rowRulerMK1 + 54; // Copy-paste Tran/rot row + static const int columnRulerMK0 = 278;// Edit mode column + static const int columnRulerMK2 = columnRulerT4;// Mode/Len column + static const int columnRulerMK1 = 353;// Display column + + // Edit mode switch + addParam(ParamWidget::create(Vec(columnRulerMK0 + hOffsetCKSS, rowRulerMK0 + vOffsetCKSS), module, PhraseSeq32::EDIT_PARAM, 0.0f, 1.0f, PhraseSeq32::EDIT_PARAM_INIT_VALUE)); + // Sequence display + SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget(); + displaySequence->box.pos = Vec(columnRulerMK1-15, rowRulerMK0 + 3 + vOffsetDisplay); + displaySequence->box.size = Vec(55, 30);// 3 characters + displaySequence->module = module; + addChild(displaySequence); + // Run mode button + addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK0 + 0 + offsetCKD6b), module, PhraseSeq32::RUNMODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Autostep + addParam(ParamWidget::create(Vec(columnRulerMK0 + hOffsetCKSS, rowRulerMK1 + 7 + vOffsetCKSS), module, PhraseSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); + // Sequence knob + addParam(createDynamicParam(Vec(columnRulerMK1 + 1 + offsetIMBigKnob, rowRulerMK0 + 55 + offsetIMBigKnob), module, PhraseSeq32::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); + // Transpose/rotate button + addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK1 + 4 + offsetCKD6b), module, PhraseSeq32::TRAN_ROT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Reset LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerMK0 - 15 + offsetLEDbezel, rowRulerMK2 + 5 + offsetLEDbezel), module, PhraseSeq32::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerMK0 - 15 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK2 + 5 + offsetLEDbezel + offsetLEDbezelLight), module, PhraseSeq32::RESET_LIGHT)); + // Run LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerMK0 + 20 + offsetLEDbezel, rowRulerMK2 + 5 + offsetLEDbezel), module, PhraseSeq32::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerMK0 + 20 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK2 + 5 + offsetLEDbezel + offsetLEDbezelLight), module, PhraseSeq32::RUN_LIGHT)); + // Copy/paste buttons + addParam(ParamWidget::create(Vec(columnRulerMK1 - 10, rowRulerMK2 + 5 + offsetTL1105), module, PhraseSeq32::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(columnRulerMK1 + 20, rowRulerMK2 + 5 + offsetTL1105), module, PhraseSeq32::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Copy-paste mode switch (3 position) + addParam(ParamWidget::create(Vec(columnRulerMK2 + hOffsetCKSS + 1, rowRulerMK2 - 3 + vOffsetCKSSThree), module, PhraseSeq32::CPMODE_PARAM, 0.0f, 2.0f, 2.0f)); // 0.0f is top position + + + + // ****** Gate and slide section ****** + + static const int rowRulerMB0 = 214; + static const int columnRulerMBspacing = 70; + static const int columnRulerMB2 = 130;// Gate2 + static const int columnRulerMB1 = columnRulerMB2 - columnRulerMBspacing;// Gate1 + static const int columnRulerMB3 = columnRulerMB2 + columnRulerMBspacing;// Tie + static const int posLEDvsButton = + 25; + + // Gate 1 light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB1 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq32::GATE1_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB1 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq32::GATE1_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Gate 2 light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB2 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq32::GATE2_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB2 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq32::GATE2_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Tie light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB3 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, PhraseSeq32::TIE_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB3 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, PhraseSeq32::TIE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + + + // ****** Bottom two rows ****** + + static const int inputJackSpacingX = 54; + static const int outputJackSpacingX = 45; + static const int rowRulerB0 = 323; + static const int rowRulerB1 = 269; + static const int columnRulerB0 = 22; + static const int columnRulerB1 = columnRulerB0 + inputJackSpacingX; + static const int columnRulerB2 = columnRulerB1 + inputJackSpacingX; + static const int columnRulerB3 = columnRulerB2 + inputJackSpacingX; + static const int columnRulerB4 = columnRulerB3 + inputJackSpacingX; + static const int columnRulerB8 = columnRulerMK2 + 1; + static const int columnRulerB7 = columnRulerB8 - outputJackSpacingX; + static const int columnRulerB6 = columnRulerB7 - outputJackSpacingX; + static const int columnRulerB5 = columnRulerB6 - outputJackSpacingX - 4;// clock and reset + + + // Gate 1 probability light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerB0 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, PhraseSeq32::GATE1_PROB_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerB0 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, PhraseSeq32::GATE1_PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Gate 1 probability knob + addParam(createDynamicParam(Vec(columnRulerB1 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, PhraseSeq32::GATE1_KNOB_PARAM, 0.0f, 1.0f, 1.0f, &module->panelTheme)); + // Slide light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerB2 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, PhraseSeq32::SLIDE_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerB2 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, PhraseSeq32::SLIDE_BTN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Slide knob + addParam(createDynamicParam(Vec(columnRulerB3 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, PhraseSeq32::SLIDE_KNOB_PARAM, 0.0f, 2.0f, 0.2f, &module->panelTheme)); + // CV in + addInput(createDynamicPort(Vec(columnRulerB4, rowRulerB1), Port::INPUT, module, PhraseSeq32::CV_INPUT, &module->panelTheme)); + // Clock input + addInput(createDynamicPort(Vec(columnRulerB5, rowRulerB1), Port::INPUT, module, PhraseSeq32::CLOCK_INPUT, &module->panelTheme)); + // Channel A outputs + addOutput(createDynamicPort(Vec(columnRulerB6, rowRulerB1), Port::OUTPUT, module, PhraseSeq32::CVA_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerB7, rowRulerB1), Port::OUTPUT, module, PhraseSeq32::GATE1A_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerB8, rowRulerB1), Port::OUTPUT, module, PhraseSeq32::GATE2A_OUTPUT, &module->panelTheme)); + + + // CV control Inputs + addInput(createDynamicPort(Vec(columnRulerB0, rowRulerB0), Port::INPUT, module, PhraseSeq32::LEFTCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB1, rowRulerB0), Port::INPUT, module, PhraseSeq32::RIGHTCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB2, rowRulerB0), Port::INPUT, module, PhraseSeq32::SEQCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB3, rowRulerB0), Port::INPUT, module, PhraseSeq32::RUNCV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerB4, rowRulerB0), Port::INPUT, module, PhraseSeq32::WRITE_INPUT, &module->panelTheme)); + // Reset input + addInput(createDynamicPort(Vec(columnRulerB5, rowRulerB0), Port::INPUT, module, PhraseSeq32::RESET_INPUT, &module->panelTheme)); + // Channel B outputs + addOutput(createDynamicPort(Vec(columnRulerB6, rowRulerB0), Port::OUTPUT, module, PhraseSeq32::CVB_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerB7, rowRulerB0), Port::OUTPUT, module, PhraseSeq32::GATE1B_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerB8, rowRulerB0), Port::OUTPUT, module, PhraseSeq32::GATE2B_OUTPUT, &module->panelTheme)); + + + // Expansion module + static const int rowRulerExpTop = 65; + static const int rowSpacingExp = 60; + static const int colRulerExp = 497; + addInput(expPorts[0] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 0), Port::INPUT, module, PhraseSeq32::GATE1CV_INPUT, &module->panelTheme)); + addInput(expPorts[1] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 1), Port::INPUT, module, PhraseSeq32::GATE2CV_INPUT, &module->panelTheme)); + addInput(expPorts[2] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 2), Port::INPUT, module, PhraseSeq32::TIEDCV_INPUT, &module->panelTheme)); + addInput(expPorts[3] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 3), Port::INPUT, module, PhraseSeq32::SLIDECV_INPUT, &module->panelTheme)); + addInput(expPorts[4] = createDynamicPort(Vec(colRulerExp, rowRulerExpTop + rowSpacingExp * 4), Port::INPUT, module, PhraseSeq32::MODECV_INPUT, &module->panelTheme)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, PhraseSeq32) { + Model *modelPhraseSeq32 = Model::create("Impromptu Modular", "Phrase-Seq-32", "SEQ - Phrase-Seq-32", SEQUENCER_TAG); + return modelPhraseSeq32; +} + +/*CHANGE LOG + +0.6.9: +add FW2, FW3 and FW4 run modes for sequences (but not for song) +right click on notes now does same as left click but with autostep + +0.6.8: +allow rollover when selecting sequences in a song phrase (easier access to higher numbered seqs) + +0.6.7: +allow full edit capabilities in Seq and song mode +no reset on run by default, with switch added in context menu +reset does not revert seq or song number to 1 +gate 2 is off by default +fix emitted monitoring gates to depend on gate states instead of always triggering + +0.6.6: +config and knob bug fixes when loading patch + +0.6.5: +paste 4, 8 doesn't loop over to overwrite first steps +small adjustements to gates and CVs used in monitoring write operations +add GATE1, GATE2, TIED, SLIDE CV inputs +add MODE CV input (affects only selected sequence and in Seq mode) +add expansion panel option +swap MODE/LEN so that length happens first (update manual) + +0.6.4: +initial release of PS32 +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp b/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp new file mode 100644 index 00000000..95f62a4d --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/SemiModularSynth.cpp @@ -0,0 +1,1785 @@ +//*********************************************************************************************** +//Semi-Modular Synthesizer module for VCV Rack by Marc Boulé and Xavier Belmont +// +//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 +// +//Module concept, desing and layout by Xavier Belmont +//Code by Marc Boulé +// +//Acknowledgements: please see README.md +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "FundamentalUtil.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct SemiModularSynth : Module { + enum ParamIds { + // SEQUENCER + LEFT_PARAM, + RIGHT_PARAM, + LENGTH_PARAM, + EDIT_PARAM, + SEQUENCE_PARAM, + RUN_PARAM, + COPY_PARAM, + PASTE_PARAM, + RESET_PARAM, + ENUMS(OCTAVE_PARAM, 7), + GATE1_PARAM, + GATE2_PARAM, + SLIDE_BTN_PARAM, + SLIDE_KNOB_PARAM, + ATTACH_PARAM, + AUTOSTEP_PARAM, + ENUMS(KEY_PARAMS, 12), + RUNMODE_PARAM, + TRAN_ROT_PARAM, + GATE1_KNOB_PARAM, + GATE1_PROB_PARAM, + TIE_PARAM,// Legato + CPMODE_PARAM, + + // VCO + VCO_MODE_PARAM, + VCO_OCT_PARAM, + VCO_FREQ_PARAM, + VCO_FINE_PARAM, + VCO_FM_PARAM, + VCO_PW_PARAM, + VCO_PWM_PARAM, + + // CLK + CLK_FREQ_PARAM, + CLK_PW_PARAM, + + // VCA + VCA_LEVEL1_PARAM, + + // ADSR + ADSR_ATTACK_PARAM, + ADSR_DECAY_PARAM, + ADSR_SUSTAIN_PARAM, + ADSR_RELEASE_PARAM, + + // VCF + VCF_FREQ_PARAM, + VCF_RES_PARAM, + VCF_FREQ_CV_PARAM, + VCF_DRIVE_PARAM, + + // LFO + LFO_FREQ_PARAM, + LFO_GAIN_PARAM, + LFO_OFFSET_PARAM, + + NUM_PARAMS + }; + enum InputIds { + // SEQUENCER + WRITE_INPUT, + CV_INPUT, + RESET_INPUT, + CLOCK_INPUT, + LEFTCV_INPUT, + RIGHTCV_INPUT, + RUNCV_INPUT, + SEQCV_INPUT, + + // VCO + VCO_PITCH_INPUT, + VCO_FM_INPUT, + VCO_SYNC_INPUT, + VCO_PW_INPUT, + + // CLK + // none + + // VCA + VCA_LIN1_INPUT, + VCA_IN1_INPUT, + + // ADSR + ADSR_GATE_INPUT, + + // VCF + VCF_FREQ_INPUT, + VCF_RES_INPUT, + VCF_DRIVE_INPUT, + VCF_IN_INPUT, + + // LFO + LFO_RESET_INPUT, + + NUM_INPUTS + }; + enum OutputIds { + // SEQUENCER + CV_OUTPUT, + GATE1_OUTPUT, + GATE2_OUTPUT, + + // VCO + VCO_SIN_OUTPUT, + VCO_TRI_OUTPUT, + VCO_SAW_OUTPUT, + VCO_SQR_OUTPUT, + + // CLK + CLK_OUT_OUTPUT, + + // VCA + VCA_OUT1_OUTPUT, + + // ADSR + ADSR_ENVELOPE_OUTPUT, + + // VCF + VCF_LPF_OUTPUT, + VCF_HPF_OUTPUT, + + // LFO + LFO_SIN_OUTPUT, + LFO_TRI_OUTPUT, + + NUM_OUTPUTS + }; + enum LightIds { + // SEQUENCER + ENUMS(STEP_PHRASE_LIGHTS, 16 * 2),// room for GreenRed + ENUMS(OCTAVE_LIGHTS, 7),// octaves 1 to 7 + ENUMS(KEY_LIGHTS, 12), + RUN_LIGHT, + RESET_LIGHT, + GATE1_LIGHT, + GATE2_LIGHT, + SLIDE_LIGHT, + ATTACH_LIGHT, + GATE1_PROB_LIGHT, + TIE_LIGHT, + + // VCO, CLK, VCA + // none + + NUM_LIGHTS + }; + + enum DisplayStateIds {DISP_NORMAL, DISP_MODE, DISP_TRANSPOSE, DISP_ROTATE}; + enum AttributeBitMasks {ATT_MSK_GATE1 = 0x01, ATT_MSK_GATE1P = 0x02, ATT_MSK_GATE2 = 0x04, ATT_MSK_SLIDE = 0x08, ATT_MSK_TIED = 0x10}; + + + // SEQUENCER + + // Need to save + int panelTheme = 1; + int portTheme = 1; + bool running; + int runModeSeq[16]; + int runModeSong; + // + int sequence; + int lengths[16];//1 to 16 + // + int phrase[16];// This is the song (series of phases; a phrase is a patten number) + int phrases;//1 to 16 + // + float cv[16][16];// [-3.0 : 3.917]. First index is patten number, 2nd index is step + int attributes[16][16];// First index is patten number, 2nd index is step (see enum AttributeBitMasks for details) + // + bool resetOnRun; + bool attached; + + // No need to save + float resetLight = 0.0f; + int stepIndexEdit; + int stepIndexRun; + int phraseIndexEdit; + int phraseIndexRun; + unsigned long editingLength;// 0 when not editing length, downward step counter timer when editing length + long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste + unsigned long editingGate;// 0 when no edit gate, downward step counter timer when edit gate + float editingGateCV;// no need to initialize, this is a companion to editingGate (output this only when editingGate > 0) + int editingGateKeyLight;// no need to initialize, this is a companion to editingGate (use this only when editingGate > 0) + int stepIndexRunHistory;// no need to initialize + int phraseIndexRunHistory;// no need to initialize + int displayState; + unsigned long slideStepsRemain;// 0 when no slide under way, downward step counter when sliding + float slideCVdelta;// no need to initialize, this is a companion to slideStepsRemain + float cvCPbuffer[16];// copy paste buffer for CVs + int attributesCPbuffer[16];// copy paste buffer for attributes + int lengthCPbuffer; + int modeCPbuffer; + int countCP;// number of steps to paste (in case CPMODE_PARAM changes between copy and paste) + int transposeOffset;// no need to initialize, this is companion to displayMode = DISP_TRANSPOSE + int rotateOffset;// no need to initialize, this is companion to displayMode = DISP_ROTATE + long clockIgnoreOnReset; + const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) + unsigned long clockPeriod;// counts number of step() calls upward from last clock (reset after clock processed) + long tiedWarning;// 0 when no warning, positive downward step counter timer when warning + int sequenceKnob = 0; + bool gate1RandomEnable; + + static constexpr float EDIT_PARAM_INIT_VALUE = 1.0f;// so that module constructor is coherent with widget initialization, since module created before widget + bool editingSequence; + bool editingSequenceLast; + + // VCO + // none + + // CLK + float clkValue; + + // VCA + // none + + // ADSR + bool decaying = false; + float env = 0.0f; + + // VCF + LadderFilter filter; + + + SchmittTrigger resetTrigger; + SchmittTrigger leftTrigger; + SchmittTrigger rightTrigger; + SchmittTrigger runningTrigger; + SchmittTrigger clockTrigger; + SchmittTrigger octTriggers[7]; + SchmittTrigger octmTrigger; + SchmittTrigger gate1Trigger; + SchmittTrigger gate1ProbTrigger; + SchmittTrigger gate2Trigger; + SchmittTrigger slideTrigger; + SchmittTrigger lengthTrigger; + SchmittTrigger keyTriggers[12]; + SchmittTrigger writeTrigger; + SchmittTrigger attachedTrigger; + SchmittTrigger copyTrigger; + SchmittTrigger pasteTrigger; + SchmittTrigger modeTrigger; + SchmittTrigger rotateTrigger; + SchmittTrigger transposeTrigger; + SchmittTrigger tiedTrigger; + + + inline bool getGate1(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1) != 0;} + inline bool getGate2(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE2) != 0;} + inline bool getGate1P(int seq, int step) {return (attributes[seq][step] & ATT_MSK_GATE1P) != 0;} + inline bool getTied(int seq, int step) {return (attributes[seq][step] & ATT_MSK_TIED) != 0;} + inline bool isEditingSequence(void) {return params[EDIT_PARAM].value > 0.5f;} + inline bool calcGate1RandomEnable(bool gate1P) {return (randomUniform() < (params[GATE1_KNOB_PARAM].value)) || !gate1P;}// randomUniform is [0.0, 1.0), see include/util/common.hpp + + + LowFrequencyOscillator oscillatorClk; + LowFrequencyOscillator oscillatorLfo; + VoltageControlledOscillator oscillatorVco; + + + SemiModularSynth() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + + void onReset() override { + // SEQUENCER + running = false; + runModeSong = MODE_FWD; + stepIndexEdit = 0; + phraseIndexEdit = 0; + sequence = 0; + phrases = 4; + for (int i = 0; i < 16; i++) { + for (int s = 0; s < 16; s++) { + cv[i][s] = 0.0f; + attributes[i][s] = ATT_MSK_GATE1; + } + runModeSeq[i] = MODE_FWD; + phrase[i] = 0; + lengths[i] = 16; + cvCPbuffer[i] = 0.0f; + attributesCPbuffer[i] = ATT_MSK_GATE1; + } + initRun(true); + lengthCPbuffer = 16; + modeCPbuffer = MODE_FWD; + countCP = 16; + editingLength = 0ul; + editingGate = 0ul; + infoCopyPaste = 0l; + displayState = DISP_NORMAL; + slideStepsRemain = 0ul; + attached = true; + clockPeriod = 0ul; + tiedWarning = 0ul; + editingSequence = EDIT_PARAM_INIT_VALUE > 0.5f; + editingSequenceLast = editingSequence; + resetOnRun = false; + + // VCO + // none + + // CLK + clkValue = 0.0f; + + // VCF + filter.reset(); + } + + + void onRandomize() override { + running = false; + runModeSong = randomu32() % 5; + stepIndexEdit = 0; + phraseIndexEdit = 0; + sequence = randomu32() % 16; + phrases = 1 + (randomu32() % 16); + for (int i = 0; i < 16; i++) { + for (int s = 0; s < 16; s++) { + cv[i][s] = ((float)(randomu32() % 7)) + ((float)(randomu32() % 12)) / 12.0f - 3.0f; + attributes[i][s] = randomu32() % 32;// 32 because 5 attributes + if (getTied(i,s)) { + attributes[i][s] = ATT_MSK_TIED;// clear other attributes if tied + applyTiedStep(i, s, lengths[i]); + } + } + runModeSeq[i] = randomu32() % NUM_MODES; + phrase[i] = randomu32() % 16; + lengths[i] = 1 + (randomu32() % 16); + cvCPbuffer[i] = 0.0f; + attributesCPbuffer[i] = ATT_MSK_GATE1; + } + initRun(true); + lengthCPbuffer = 16; + modeCPbuffer = MODE_FWD; + countCP = 16; + editingLength = 0ul; + editingGate = 0ul; + infoCopyPaste = 0l; + displayState = DISP_NORMAL; + slideStepsRemain = 0ul; + attached = true; + clockPeriod = 0ul; + tiedWarning = 0ul; + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + resetOnRun = false; + } + + + void initRun(bool hard) {// run button activated or run edge in run input jack or edit mode toggled + if (hard) { + phraseIndexRun = (runModeSong == MODE_REV ? phrases - 1 : 0); + if (editingSequence) + stepIndexRun = (runModeSeq[sequence] == MODE_REV ? lengths[sequence] - 1 : 0); + else + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0); + } + gate1RandomEnable = false; + if (editingSequence) + gate1RandomEnable = calcGate1RandomEnable(getGate1P(sequence, stepIndexRun)); + else + gate1RandomEnable = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun], stepIndexRun)); + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme and portTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + json_object_set_new(rootJ, "portTheme", json_integer(portTheme)); + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // runModeSeq + json_t *runModeSeqJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(runModeSeqJ, i, json_integer(runModeSeq[i])); + json_object_set_new(rootJ, "runModeSeq2", runModeSeqJ);// "2" appended so no break patches + + // runModeSong + json_object_set_new(rootJ, "runModeSong", json_integer(runModeSong)); + + // sequence + json_object_set_new(rootJ, "sequence", json_integer(sequence)); + + // lengths + json_t *lengthsJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(lengthsJ, i, json_integer(lengths[i])); + json_object_set_new(rootJ, "lengths", lengthsJ); + + // phrase + json_t *phraseJ = json_array(); + for (int i = 0; i < 16; i++) + json_array_insert_new(phraseJ, i, json_integer(phrase[i])); + json_object_set_new(rootJ, "phrase", phraseJ); + + // phrases + json_object_set_new(rootJ, "phrases", json_integer(phrases)); + + // CV + json_t *cvJ = json_array(); + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_array_insert_new(cvJ, s + (i * 16), json_real(cv[i][s])); + } + json_object_set_new(rootJ, "cv", cvJ); + + // attributes + json_t *attributesJ = json_array(); + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_array_insert_new(attributesJ, s + (i * 16), json_integer(attributes[i][s])); + } + json_object_set_new(rootJ, "attributes", attributesJ); + + // attached + json_object_set_new(rootJ, "attached", json_boolean(attached)); + + // resetOnRun + json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); + + return rootJ; + } + + void fromJson(json_t *rootJ) override { + // panelTheme and portTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + json_t *portThemeJ = json_object_get(rootJ, "portTheme"); + if (portThemeJ) + portTheme = json_integer_value(portThemeJ); + + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // runModeSeq + json_t *runModeSeqJ = json_object_get(rootJ, "runModeSeq2");// "2" was in PS16. No legacy in SMS16 though + if (runModeSeqJ) { + for (int i = 0; i < 16; i++) + { + json_t *runModeSeqArrayJ = json_array_get(runModeSeqJ, i); + if (runModeSeqArrayJ) + runModeSeq[i] = json_integer_value(runModeSeqArrayJ); + } + } + + // runModeSong + json_t *runModeSongJ = json_object_get(rootJ, "runModeSong"); + if (runModeSongJ) + runModeSong = json_integer_value(runModeSongJ); + + // sequence + json_t *sequenceJ = json_object_get(rootJ, "sequence"); + if (sequenceJ) + sequence = json_integer_value(sequenceJ); + + // lengths + json_t *lengthsJ = json_object_get(rootJ, "lengths"); + if (lengthsJ) { + for (int i = 0; i < 16; i++) + { + json_t *lengthsArrayJ = json_array_get(lengthsJ, i); + if (lengthsArrayJ) + lengths[i] = json_integer_value(lengthsArrayJ); + } + } + + // phrase + json_t *phraseJ = json_object_get(rootJ, "phrase"); + if (phraseJ) + for (int i = 0; i < 16; i++) + { + json_t *phraseArrayJ = json_array_get(phraseJ, i); + if (phraseArrayJ) + phrase[i] = json_integer_value(phraseArrayJ); + } + + // phrases + json_t *phrasesJ = json_object_get(rootJ, "phrases"); + if (phrasesJ) + phrases = json_integer_value(phrasesJ); + + // CV + json_t *cvJ = json_object_get(rootJ, "cv"); + if (cvJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *cvArrayJ = json_array_get(cvJ, s + (i * 16)); + if (cvArrayJ) + cv[i][s] = json_real_value(cvArrayJ); + } + } + + // attributes + json_t *attributesJ = json_object_get(rootJ, "attributes"); + if (attributesJ) { + for (int i = 0; i < 16; i++) + for (int s = 0; s < 16; s++) { + json_t *attributesArrayJ = json_array_get(attributesJ, s + (i * 16)); + if (attributesArrayJ) + attributes[i][s] = json_integer_value(attributesArrayJ); + } + } + + // attached + json_t *attachedJ = json_object_get(rootJ, "attached"); + if (attachedJ) + attached = json_is_true(attachedJ); + + // resetOnRun + json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); + if (resetOnRunJ) + resetOnRun = json_is_true(resetOnRunJ); + + // Initialize dependants after everything loaded + initRun(true); + editingSequence = isEditingSequence(); + editingSequenceLast = editingSequence; + } + + + void rotateSeq(int seqNum, bool directionRight, int seqLength) { + float rotCV; + int rotAttributes; + int iStart = 0; + int iEnd = seqLength - 1; + int iRot = iStart; + int iDelta = 1; + if (directionRight) { + iRot = iEnd; + iDelta = -1; + } + rotCV = cv[seqNum][iRot]; + rotAttributes = attributes[seqNum][iRot]; + for ( ; ; iRot += iDelta) { + if (iDelta == 1 && iRot >= iEnd) break; + if (iDelta == -1 && iRot <= iStart) break; + cv[seqNum][iRot] = cv[seqNum][iRot + iDelta]; + attributes[seqNum][iRot] = attributes[seqNum][iRot + iDelta]; + } + cv[seqNum][iRot] = rotCV; + attributes[seqNum][iRot] = rotAttributes; + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + + // SEQUENCER + + static const float gateTime = 0.4f;// seconds + static const float copyPasteInfoTime = 0.5f;// seconds + static const float editLengthTime = 2.0f;// seconds + static const float tiedWarningTime = 0.7f;// seconds + long tiedWarningInit = (long) (tiedWarningTime * engineGetSampleRate()); + + + //********** Buttons, knobs, switches and inputs ********** + + // Notes: + // * a tied step's attributes can not be modified by any of the following: + // write input, oct and keyboard buttons, gate1, gate1Prob, gate2 and slide buttons + // however, paste, transpose, rotate obviously can. + // * Whenever cv[][] is modified or tied[] is activated for a step, call applyTiedStep(sequence,stepIndexEdit,steps) + + // Edit mode + editingSequence = isEditingSequence();// true = editing sequence, false = editing song + if (editingSequenceLast != editingSequence) { + if (running) + initRun(true); + displayState = DISP_NORMAL; + editingSequenceLast = editingSequence; + } + + // Seq CV input + if (inputs[SEQCV_INPUT].active) { + sequence = (int) clamp( round(inputs[SEQCV_INPUT].value * (16.0f - 1.0f) / 10.0f), 0.0f, (16.0f - 1.0f) ); + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + + // Run button + if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { + running = !running; + if (running) + initRun(resetOnRun); + displayState = DISP_NORMAL; + } + + // Attach button + if (attachedTrigger.process(params[ATTACH_PARAM].value)) { + if (running) + attached = !attached; + displayState = DISP_NORMAL; + } + if (running && attached) { + if (editingSequence) + stepIndexEdit = stepIndexRun; + else + phraseIndexEdit = phraseIndexRun; + } + + // Copy button + if (copyTrigger.process(params[COPY_PARAM].value)) { + if (editingSequence) { + infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); + //CPinfo must be set to 0 for copy/paste all, and 0x1ii for copy/paste 4 at pos ii, 0x2ii for copy/paste 8 at 0xii + int sStart = stepIndexEdit; + int sCount = 16; + if (params[CPMODE_PARAM].value > 1.5f)// all + sStart = 0; + else if (params[CPMODE_PARAM].value < 0.5f)// 4 + sCount = 4; + else// 8 + sCount = 8; + countCP = sCount; + for (int i = 0, s = sStart; i < countCP; i++, s++) { + if (s >= 16) s = 0; + cvCPbuffer[i] = cv[sequence][s]; + attributesCPbuffer[i] = attributes[sequence][s]; + if ((--sCount) <= 0) + break; + } + lengthCPbuffer = lengths[sequence]; + modeCPbuffer = runModeSeq[sequence]; + } + displayState = DISP_NORMAL; + } + // Paste button + if (pasteTrigger.process(params[PASTE_PARAM].value)) { + if (editingSequence) { + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + int sStart = ((countCP == 16) ? 0 : stepIndexEdit); + int sCount = countCP; + for (int i = 0, s = sStart; i < countCP; i++, s++) { + if (s >= 16) + break; + cv[sequence][s] = cvCPbuffer[i]; + attributes[sequence][s] = attributesCPbuffer[i]; + if ((--sCount) <= 0) + break; + } + if (params[CPMODE_PARAM].value > 1.5f) {// all + lengths[sequence] = lengthCPbuffer; + runModeSeq[sequence] = modeCPbuffer; + } + } + displayState = DISP_NORMAL; + } + + // Length button + if (lengthTrigger.process(params[LENGTH_PARAM].value)) { + if (editingLength > 0ul) + editingLength = 0ul;// allow user to quickly leave editing mode when re-press + else + editingLength = (unsigned long) (editLengthTime * engineGetSampleRate()); + displayState = DISP_NORMAL; + } + + // Write input (must be before Left and Right in case route gate simultaneously to Right and Write for example) + // (write must be to correct step) + bool writeTrig = writeTrigger.process(inputs[WRITE_INPUT].value); + if (writeTrig) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else { + cv[sequence][stepIndexEdit] = inputs[CV_INPUT].value; + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + // Autostep (after grab all active inputs) + if (params[AUTOSTEP_PARAM].value > 0.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16);//lengths[sequence]);// Commented for full edit capabilities + } + } + displayState = DISP_NORMAL; + } + // Left and Right CV inputs and buttons + int delta = 0; + if (leftTrigger.process(inputs[LEFTCV_INPUT].value + params[LEFT_PARAM].value)) { + delta = -1; + displayState = DISP_NORMAL; + } + if (rightTrigger.process(inputs[RIGHTCV_INPUT].value + params[RIGHT_PARAM].value)) { + delta = +1; + displayState = DISP_NORMAL; + } + if (delta != 0) { + if (editingLength > 0ul) { + editingLength = (unsigned long) (editLengthTime * engineGetSampleRate());// restart editing length timer + if (editingSequence) { + lengths[sequence] += delta; + if (lengths[sequence] > 16) lengths[sequence] = 16; + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + else { + phrases += delta; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + //if (phraseIndexEdit >= phrases) phraseIndexEdit = phrases - 1;// Commented for full edit capabilities + } + } + else { + if (!running || !attached) {// don't move heads when attach and running + if (editingSequence) { + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + delta, 16);//lengths[sequence]);// Commented for full edit capabilities + if (!getTied(sequence,stepIndexEdit)) {// play if non-tied step + if (!writeTrig) {// in case autostep when simultaneous writeCV and stepCV (keep what was done in Write Input block above) + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + } + } + } + else + phraseIndexEdit = moveIndex(phraseIndexEdit, phraseIndexEdit + delta, 16);//phrases);// Commented for full edit capabilities + } + } + } + + // Mode and Transpose/Rotate buttons + if (modeTrigger.process(params[RUNMODE_PARAM].value)) { + if (displayState != DISP_MODE) + displayState = DISP_MODE; + else + displayState = DISP_NORMAL; + } + if (transposeTrigger.process(params[TRAN_ROT_PARAM].value)) { + if (editingSequence) { + if (displayState == DISP_NORMAL || displayState == DISP_MODE) { + displayState = DISP_TRANSPOSE; + transposeOffset = 0; + } + else if (displayState == DISP_TRANSPOSE) { + displayState = DISP_ROTATE; + rotateOffset = 0; + } + else + displayState = DISP_NORMAL; + } + } + + // Sequence knob + float seqParamValue = params[SEQUENCE_PARAM].value; + int newSequenceKnob = (int)roundf(seqParamValue * 7.0f); + if (seqParamValue == 0.0f)// true when constructor or fromJson() occured + sequenceKnob = newSequenceKnob; + int deltaKnob = newSequenceKnob - sequenceKnob; + if (deltaKnob != 0) { + if (abs(deltaKnob) <= 3) {// avoid discontinuous step (initialize for example) + if (editingLength > 0ul) { + editingLength = (unsigned long) (editLengthTime * engineGetSampleRate());// restart editing length timer + if (editingSequence) { + lengths[sequence] += deltaKnob; + if (lengths[sequence] > 16) lengths[sequence] = 16 ; + if (lengths[sequence] < 1 ) lengths[sequence] = 1; + } + else { + phrases += deltaKnob; + if (phrases > 16) phrases = 16; + if (phrases < 1 ) phrases = 1; + } + } + else if (displayState == DISP_MODE) { + if (editingSequence) { + runModeSeq[sequence] += deltaKnob; + if (runModeSeq[sequence] < 0) runModeSeq[sequence] = 0; + if (runModeSeq[sequence] >= NUM_MODES) runModeSeq[sequence] = NUM_MODES - 1; + } + else { + runModeSong += deltaKnob; + if (runModeSong < 0) runModeSong = 0; + if (runModeSong >= 5) runModeSong = 5 - 1; + } + } + else if (displayState == DISP_TRANSPOSE) { + if (editingSequence) { + transposeOffset += deltaKnob; + if (transposeOffset > 99) transposeOffset = 99; + if (transposeOffset < -99) transposeOffset = -99; + // Tranpose by this number of semi-tones: deltaKnob + float transposeOffsetCV = ((float)(deltaKnob))/12.0f; + for (int s = 0; s < 16; s++) { + cv[sequence][s] += transposeOffsetCV; + } + } + } + else if (displayState == DISP_ROTATE) { + if (editingSequence) { + rotateOffset += deltaKnob; + if (rotateOffset > 99) rotateOffset = 99; + if (rotateOffset < -99) rotateOffset = -99; + if (deltaKnob > 0 && deltaKnob < 99) {// Rotate right, 99 is safety + for (int i = deltaKnob; i > 0; i--) + rotateSeq(sequence, true, lengths[sequence]); + } + if (deltaKnob < 0 && deltaKnob > -99) {// Rotate left, 99 is safety + for (int i = deltaKnob; i < 0; i++) + rotateSeq(sequence, false, lengths[sequence]); + } + } + } + else {// DISP_NORMAL + if (editingSequence) { + if (!inputs[SEQCV_INPUT].active) { + sequence += deltaKnob; + if (sequence < 0) sequence = 0; + if (sequence >= 16) sequence = (16 - 1); + //if (stepIndexEdit >= lengths[sequence])// Commented for full edit capabilities + //stepIndexEdit = lengths[sequence] - 1;// Commented for full edit capabilities + } + } + else { + phrase[phraseIndexEdit] += deltaKnob; + if (phrase[phraseIndexEdit] < 0) phrase[phraseIndexEdit] = 0; + if (phrase[phraseIndexEdit] >= 16) phrase[phraseIndexEdit] = (16 - 1); + } + } + } + sequenceKnob = newSequenceKnob; + } + + // Octave buttons + int newOct = -1; + for (int i = 0; i < 7; i++) { + if (octTriggers[i].process(params[OCTAVE_PARAM + i].value)) { + newOct = 6 - i; + displayState = DISP_NORMAL; + } + } + if (newOct >= 0 && newOct <= 6) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else { + float newCV = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages + newCV = newCV - floor(newCV) + (float) (newOct - 3); + if (newCV >= -3.0f && newCV < 4.0f) { + cv[sequence][stepIndexEdit] = newCV; + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + } + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + } + } + } + + // Keyboard buttons + for (int i = 0; i < 12; i++) { + if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) { + if (params[KEY_PARAMS + i].value > 1.5f) + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16); + else + tiedWarning = tiedWarningInit; + } + else { + cv[sequence][stepIndexEdit] = floor(cv[sequence][stepIndexEdit]) + ((float) i) / 12.0f; + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + editingGate = (unsigned long) (gateTime * engineGetSampleRate()); + editingGateCV = cv[sequence][stepIndexEdit]; + editingGateKeyLight = -1; + if (params[KEY_PARAMS + i].value > 1.5f) { + stepIndexEdit = moveIndex(stepIndexEdit, stepIndexEdit + 1, 16); + editingGateKeyLight = i; + } + } + } + displayState = DISP_NORMAL; + } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied buttons + if (gate1Trigger.process(params[GATE1_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1; + } + displayState = DISP_NORMAL; + } + if (gate1ProbTrigger.process(params[GATE1_PROB_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE1P; + } + displayState = DISP_NORMAL; + } + if (gate2Trigger.process(params[GATE2_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_GATE2; + } + displayState = DISP_NORMAL; + } + if (slideTrigger.process(params[SLIDE_BTN_PARAM].value)) { + if (editingSequence) { + if (getTied(sequence,stepIndexEdit)) + tiedWarning = tiedWarningInit; + else + attributes[sequence][stepIndexEdit] ^= ATT_MSK_SLIDE; + } + displayState = DISP_NORMAL; + } + if (tiedTrigger.process(params[TIE_PARAM].value)) { + if (editingSequence) { + attributes[sequence][stepIndexEdit] ^= ATT_MSK_TIED; + if (getTied(sequence,stepIndexEdit)) { + attributes[sequence][stepIndexEdit] = ATT_MSK_TIED;// clear other attributes if tied + applyTiedStep(sequence, stepIndexEdit, lengths[sequence]); + } + else + attributes[sequence][stepIndexEdit] |= (ATT_MSK_GATE1 | ATT_MSK_GATE2); + } + displayState = DISP_NORMAL; + } + + + //********** Clock and reset ********** + + // Clock + float clockInput = inputs[CLOCK_INPUT].active ? inputs[CLOCK_INPUT].value : clkValue;// Pre-patching + if (clockTrigger.process(clockInput)) { + if (running && clockIgnoreOnReset == 0l) { + float slideFromCV = 0.0f; + float slideToCV = 0.0f; + if (editingSequence) { + slideFromCV = cv[sequence][stepIndexRun]; + moveIndexRunMode(&stepIndexRun, lengths[sequence], runModeSeq[sequence], &stepIndexRunHistory); + slideToCV = cv[sequence][stepIndexRun]; + gate1RandomEnable = calcGate1RandomEnable(getGate1P(sequence,stepIndexRun));// must be calculated on clock edge only + } + else { + slideFromCV = cv[phrase[phraseIndexRun]][stepIndexRun]; + if (moveIndexRunMode(&stepIndexRun, lengths[phrase[phraseIndexRun]], runModeSeq[phrase[phraseIndexRun]], &stepIndexRunHistory)) { + moveIndexRunMode(&phraseIndexRun, phrases, runModeSong, &phraseIndexRunHistory); + stepIndexRun = (runModeSeq[phrase[phraseIndexRun]] == MODE_REV ? lengths[phrase[phraseIndexRun]] - 1 : 0);// must always refresh after phraseIndexRun has changed + } + slideToCV = cv[phrase[phraseIndexRun]][stepIndexRun]; + gate1RandomEnable = calcGate1RandomEnable(getGate1P(phrase[phraseIndexRun],stepIndexRun));// must be calculated on clock edge only + } + + // Slide + if ( (editingSequence && ((attributes[sequence][stepIndexRun] & ATT_MSK_SLIDE) != 0) ) || + (!editingSequence && ((attributes[phrase[phraseIndexRun]][stepIndexRun] & ATT_MSK_SLIDE) != 0) ) ) { + // avtivate sliding (slideStepsRemain can be reset, else runs down to 0, either way, no need to reinit) + slideStepsRemain = (unsigned long) (((float)clockPeriod) * params[SLIDE_KNOB_PARAM].value / 2.0f);// 0-T slide, where T is clock period (can be too long when user does clock gating) + //slideStepsRemain = (unsigned long) (engineGetSampleRate() * params[SLIDE_KNOB_PARAM].value );// 0-2s slide + slideCVdelta = (slideToCV - slideFromCV)/(float)slideStepsRemain; + } + } + clockPeriod = 0ul; + } + clockPeriod++; + + // Reset + if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + //stepIndexEdit = 0; + //sequence = 0; + initRun(true);// must be after sequence reset + resetLight = 1.0f; + displayState = DISP_NORMAL; + clockTrigger.reset(); + } + else + resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); + + + //********** Outputs and lights ********** + + // CV and gates outputs + int seq = editingSequence ? (sequence) : (running ? phrase[phraseIndexRun] : phrase[phraseIndexEdit]); + int step = editingSequence ? (running ? stepIndexRun : stepIndexEdit) : (stepIndexRun); + if (running) { + float slideOffset = (slideStepsRemain > 0ul ? (slideCVdelta * (float)slideStepsRemain) : 0.0f); + outputs[CV_OUTPUT].value = cv[seq][step] - slideOffset; + outputs[GATE1_OUTPUT].value = (clockTrigger.isHigh() && gate1RandomEnable && + ((attributes[seq][step] & ATT_MSK_GATE1) != 0)) ? 10.0f : 0.0f; + outputs[GATE2_OUTPUT].value = (clockTrigger.isHigh() && + ((attributes[seq][step] & ATT_MSK_GATE2) != 0)) ? 10.0f : 0.0f; + } + else {// not running + outputs[CV_OUTPUT].value = (editingGate > 0ul) ? editingGateCV : cv[seq][step]; + outputs[GATE1_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + outputs[GATE2_OUTPUT].value = (editingGate > 0ul) ? 10.0f : 0.0f; + } + + // Step/phrase lights + if (infoCopyPaste != 0l) { + for (int i = 0; i < 16; i++) { + if ( (i >= stepIndexEdit && i < (stepIndexEdit + countCP)) || (countCP == 16) ) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.5f;// Green when copy interval + else + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = 0.0f; // Green (nothing) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f;// Red (nothing) + } + } + else { + for (int i = 0; i < 16; i++) { + if (editingLength > 0ul) { + // Length (green) + if (editingSequence) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < lengths[sequence]) ? 0.5f : 0.0f); + else + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((i < phrases) ? 0.5f : 0.0f); + // Nothing (red) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = 0.0f; + } + else { + // Run cursor (green) + if (editingSequence) + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = ((running && (i == stepIndexRun)) ? 1.0f : 0.0f); + else { + float green = ((running && (i == phraseIndexRun)) ? 1.0f : 0.0f); + green += ((running && (i == stepIndexRun) && i != phraseIndexEdit) ? 0.1f : 0.0f); + lights[STEP_PHRASE_LIGHTS + (i<<1)].value = clamp(green, 0.0f, 1.0f); + } + // Edit cursor (red) + if (editingSequence) + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == stepIndexEdit ? 1.0f : 0.0f); + else + lights[STEP_PHRASE_LIGHTS + (i<<1) + 1].value = (i == phraseIndexEdit ? 1.0f : 0.0f); + } + } + } + + // Octave lights + float octCV = 0.0f; + if (editingSequence) + octCV = cv[sequence][stepIndexEdit]; + else + octCV = cv[phrase[phraseIndexEdit]][stepIndexRun]; + int octLightIndex = (int) floor(octCV + 3.0f); + for (int i = 0; i < 7; i++) { + if (!editingSequence && (!attached || !running))// no oct lights when song mode and either (detached [1] or stopped [2]) + // [1] makes no sense, can't mod steps and stepping though seq that may not be playing + // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display + lights[OCTAVE_LIGHTS + i].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[OCTAVE_LIGHTS + i].value = (warningFlashState && (i == (6 - octLightIndex))) ? 1.0f : 0.0f; + } + else + lights[OCTAVE_LIGHTS + i].value = (i == (6 - octLightIndex) ? 1.0f : 0.0f); + } + } + + // Keyboard lights + float cvValOffset; + if (editingSequence) + cvValOffset = cv[sequence][stepIndexEdit] + 10.0f;//to properly handle negative note voltages + else + cvValOffset = cv[phrase[phraseIndexEdit]][stepIndexRun] + 10.0f;//to properly handle negative note voltages + int keyLightIndex = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); + for (int i = 0; i < 12; i++) { + if (!editingSequence && (!attached || !running))// no keyboard lights when song mode and either (detached [1] or stopped [2]) + // [1] makes no sense, can't mod steps and stepping though seq that may not be playing + // [2] CV is set to 0V when not running and in song mode, so cv[][] makes no sense to display + lights[KEY_LIGHTS + i].value = 0.0f; + else { + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[KEY_LIGHTS + i].value = (warningFlashState && i == keyLightIndex) ? 1.0f : 0.0f; + } + else { + if (editingGate > 0ul && editingGateKeyLight != -1) + lights[KEY_LIGHTS + i].value = (i == editingGateKeyLight ? ((float) editingGate / (float)(gateTime * engineGetSampleRate())) : 0.0f); + else + lights[KEY_LIGHTS + i].value = (i == keyLightIndex ? 1.0f : 0.0f); + } + } + } + + // Gate1, Gate1Prob, Gate2, Slide and Tied lights + int attributesVal = attributes[sequence][stepIndexEdit]; + if (!editingSequence) + attributesVal = attributes[phrase[phraseIndexEdit]][stepIndexRun]; + // + lights[GATE1_LIGHT].value = ((attributesVal & ATT_MSK_GATE1) != 0) ? 1.0f : 0.0f; + lights[GATE1_PROB_LIGHT].value = ((attributesVal & ATT_MSK_GATE1P) != 0) ? 1.0f : 0.0f; + lights[GATE2_LIGHT].value = ((attributesVal & ATT_MSK_GATE2) != 0) ? 1.0f : 0.0f; + lights[SLIDE_LIGHT].value = ((attributesVal & ATT_MSK_SLIDE) != 0) ? 1.0f : 0.0f; + if (tiedWarning > 0l) { + bool warningFlashState = calcWarningFlash(tiedWarning, tiedWarningInit); + lights[TIE_LIGHT].value = (warningFlashState) ? 1.0f : 0.0f; + } + else + lights[TIE_LIGHT].value = ((attributesVal & ATT_MSK_TIED) != 0) ? 1.0f : 0.0f; + + // Attach light + lights[ATTACH_LIGHT].value = (running && attached) ? 1.0f : 0.0f; + + // Reset light + lights[RESET_LIGHT].value = resetLight; + + // Run light + lights[RUN_LIGHT].value = lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; + + if (editingLength > 0ul) + editingLength--; + if (editingGate > 0ul) + editingGate--; + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (slideStepsRemain > 0ul) + slideStepsRemain--; + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; + if (tiedWarning > 0l) + tiedWarning--; + + + // VCO + oscillatorVco.analog = params[VCO_MODE_PARAM].value > 0.0f; + oscillatorVco.soft = false;//params[VCO_SYNC_PARAM].value <= 0.0f; + float pitchFine = 3.0f * quadraticBipolar(params[VCO_FINE_PARAM].value); + float pitchCv = 12.0f * (inputs[VCO_PITCH_INPUT].active ? inputs[VCO_PITCH_INPUT].value : outputs[CV_OUTPUT].value);// Pre-patching + float pitchOctOffset = 12.0f * params[VCO_OCT_PARAM].value; + if (inputs[VCO_FM_INPUT].active) { + pitchCv += quadraticBipolar(params[VCO_FM_PARAM].value) * 12.0f * inputs[VCO_FM_INPUT].value; + } + oscillatorVco.setPitch(params[VCO_FREQ_PARAM].value, pitchFine + pitchCv + pitchOctOffset); + oscillatorVco.setPulseWidth(params[VCO_PW_PARAM].value + params[VCO_PWM_PARAM].value * inputs[VCO_PW_INPUT].value / 10.0f); + oscillatorVco.syncEnabled = inputs[VCO_SYNC_INPUT].active; + oscillatorVco.process(engineGetSampleTime(), inputs[VCO_SYNC_INPUT].value); + if (outputs[VCO_SIN_OUTPUT].active) + outputs[VCO_SIN_OUTPUT].value = 5.0f * oscillatorVco.sin(); + if (outputs[VCO_TRI_OUTPUT].active) + outputs[VCO_TRI_OUTPUT].value = 5.0f * oscillatorVco.tri(); + if (outputs[VCO_SAW_OUTPUT].active) + outputs[VCO_SAW_OUTPUT].value = 5.0f * oscillatorVco.saw(); + //if (outputs[VCO_SQR_OUTPUT].active) + outputs[VCO_SQR_OUTPUT].value = 5.0f * oscillatorVco.sqr(); + + + // CLK + oscillatorClk.setPitch(params[CLK_FREQ_PARAM].value); + oscillatorClk.setPulseWidth(params[CLK_PW_PARAM].value); + oscillatorClk.offset = true;//(params[OFFSET_PARAM].value > 0.0f); + oscillatorClk.invert = false;//(params[INVERT_PARAM].value <= 0.0f); + oscillatorClk.step(engineGetSampleTime()); + oscillatorClk.setReset(inputs[RESET_INPUT].value + params[RESET_PARAM].value + params[RUN_PARAM].value + inputs[RUNCV_INPUT].value);//inputs[RESET_INPUT].value); + clkValue = 5.0f * oscillatorClk.sqr(); + outputs[CLK_OUT_OUTPUT].value = clkValue; + + + // VCA + float vcaIn = inputs[VCA_IN1_INPUT].active ? inputs[VCA_IN1_INPUT].value : outputs[VCO_SQR_OUTPUT].value;// Pre-patching + float vcaLin = inputs[VCA_LIN1_INPUT].active ? inputs[VCA_LIN1_INPUT].value : outputs[ADSR_ENVELOPE_OUTPUT].value;// Pre-patching + float v = vcaIn * params[VCA_LEVEL1_PARAM].value; + v *= clamp(vcaLin / 10.0f, 0.0f, 1.0f); + outputs[VCA_OUT1_OUTPUT].value = v; + + + // ADSR + float attack = clamp(params[ADSR_ATTACK_PARAM].value, 0.0f, 1.0f); + float decay = clamp(params[ADSR_DECAY_PARAM].value, 0.0f, 1.0f); + float sustain = clamp(params[ADSR_SUSTAIN_PARAM].value, 0.0f, 1.0f); + float release = clamp(params[ADSR_RELEASE_PARAM].value, 0.0f, 1.0f); + // Gate + float adsrIn = inputs[ADSR_GATE_INPUT].active ? inputs[ADSR_GATE_INPUT].value : outputs[GATE1_OUTPUT].value;// Pre-patching + bool gated = adsrIn >= 1.0f; + const float base = 20000.0f; + const float maxTime = 10.0f; + if (gated) { + if (decaying) { + // Decay + if (decay < 1e-4) { + env = sustain; + } + else { + env += powf(base, 1 - decay) / maxTime * (sustain - env) * engineGetSampleTime(); + } + } + else { + // Attack + // Skip ahead if attack is all the way down (infinitely fast) + if (attack < 1e-4) { + env = 1.0f; + } + else { + env += powf(base, 1 - attack) / maxTime * (1.01f - env) * engineGetSampleTime(); + } + if (env >= 1.0f) { + env = 1.0f; + decaying = true; + } + } + } + else { + // Release + if (release < 1e-4) { + env = 0.0f; + } + else { + env += powf(base, 1 - release) / maxTime * (0.0f - env) * engineGetSampleTime(); + } + decaying = false; + } + outputs[ADSR_ENVELOPE_OUTPUT].value = 10.0f * env; + + + // VCF + if (outputs[VCF_LPF_OUTPUT].active || outputs[VCF_HPF_OUTPUT].active) { + + float input = (inputs[VCF_IN_INPUT].active ? inputs[VCF_IN_INPUT].value : outputs[VCA_OUT1_OUTPUT].value) / 5.0f;// Pre-patching + float drive = clamp(params[VCF_DRIVE_PARAM].value + inputs[VCF_DRIVE_INPUT].value / 10.0f, 0.f, 1.f); + float gain = powf(1.f + drive, 5); + input *= gain; + // Add -60dB noise to bootstrap self-oscillation + input += 1e-6f * (2.f * randomUniform() - 1.f); + // Set resonance + float res = clamp(params[VCF_RES_PARAM].value + inputs[VCF_RES_INPUT].value / 10.f, 0.f, 1.f); + filter.resonance = powf(res, 2) * 10.f; + // Set cutoff frequency + float pitch = 0.f; + if (inputs[VCF_FREQ_INPUT].active) + pitch += inputs[VCF_FREQ_INPUT].value * quadraticBipolar(params[VCF_FREQ_CV_PARAM].value); + pitch += params[VCF_FREQ_PARAM].value * 10.f - 5.f; + //pitch += quadraticBipolar(params[FINE_PARAM].value * 2.f - 1.f) * 7.f / 12.f; + float cutoff = 261.626f * powf(2.f, pitch); + cutoff = clamp(cutoff, 1.f, 8000.f); + filter.setCutoff(cutoff); + filter.process(input, engineGetSampleTime()); + outputs[VCF_LPF_OUTPUT].value = 5.f * filter.lowpass; + outputs[VCF_HPF_OUTPUT].value = 5.f * filter.highpass; + } + else { + outputs[VCF_LPF_OUTPUT].value = 0.0f; + outputs[VCF_HPF_OUTPUT].value = 0.0f; + } + + // LFO + if (outputs[LFO_SIN_OUTPUT].active || outputs[LFO_TRI_OUTPUT].active) { + oscillatorLfo.setPitch(params[LFO_FREQ_PARAM].value); + oscillatorLfo.setPulseWidth(0.5f);//params[PW_PARAM].value + params[PWM_PARAM].value * inputs[PW_INPUT].value / 10.0f); + oscillatorLfo.offset = false;//(params[OFFSET_PARAM].value > 0.0f); + oscillatorLfo.invert = false;//(params[INVERT_PARAM].value <= 0.0f); + oscillatorLfo.step(engineGetSampleTime()); + oscillatorLfo.setReset(inputs[LFO_RESET_INPUT].value + inputs[RESET_INPUT].value + params[RESET_PARAM].value + params[RUN_PARAM].value + inputs[RUNCV_INPUT].value); + float lfoGain = params[LFO_GAIN_PARAM].value; + float lfoOffset = (2.0f - lfoGain) * params[LFO_OFFSET_PARAM].value; + outputs[LFO_SIN_OUTPUT].value = 5.0f * (lfoOffset + lfoGain * oscillatorLfo.sin()); + outputs[LFO_TRI_OUTPUT].value = 5.0f * (lfoOffset + lfoGain * oscillatorLfo.tri()); + } + else { + outputs[LFO_SIN_OUTPUT].value = 0.0f; + outputs[LFO_TRI_OUTPUT].value = 0.0f; + } + + }// step() + + void applyTiedStep(int seqNum, int indexTied, int seqLength) { + // Start on indexTied and loop until seqLength + // Called because either: + // case A: tied was activated for given step + // case B: the given step's CV was modified + // These cases are mutually exclusive + + // copy previous CV over to current step if tied + if (getTied(seqNum,indexTied) && (indexTied > 0)) + cv[seqNum][indexTied] = cv[seqNum][indexTied - 1]; + + // Affect downstream CVs of subsequent tied note chain (can be 0 length if next note is not tied) + for (int i = indexTied + 1; i < seqLength && getTied(seqNum,i); i++) + cv[seqNum][i] = cv[seqNum][indexTied]; + } +}; + + + +struct SemiModularSynthWidget : ModuleWidget { + SemiModularSynth *module; + DynamicSVGPanel *panel; + + struct SequenceDisplayWidget : TransparentWidget { + SemiModularSynth *module; + std::shared_ptr font; + char displayStr[4]; + //std::string modeLabels[5]={"FWD","REV","PPG","BRN","RND"}; + + SequenceDisplayWidget() { + font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); + } + + void runModeToStr(int num) { + if (num >= 0 && num < NUM_MODES) + snprintf(displayStr, 4, "%s", modeLabels[num].c_str()); + } + + 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); + if (module->infoCopyPaste != 0l) { + if (module->infoCopyPaste > 0l) {// if copy display "CPY" + snprintf(displayStr, 4, "CPY"); + } + else {// if paste display "PST" + snprintf(displayStr, 4, "PST"); + } + } + else { + if (module->displayState == SemiModularSynth::DISP_MODE) { + if (module->editingSequence) + runModeToStr(module->runModeSeq[module->sequence]); + else + runModeToStr(module->runModeSong); + } + else if (module->editingLength > 0ul) { + if (module->editingSequence) + snprintf(displayStr, 4, "L%2u", (unsigned) module->lengths[module->sequence]); + else + snprintf(displayStr, 4, "L%2u", (unsigned) module->phrases); + } + else if (module->displayState == SemiModularSynth::DISP_TRANSPOSE) { + snprintf(displayStr, 4, "+%2u", (unsigned) abs(module->transposeOffset)); + if (module->transposeOffset < 0) + displayStr[0] = '-'; + } + else if (module->displayState == SemiModularSynth::DISP_ROTATE) { + snprintf(displayStr, 4, ")%2u", (unsigned) abs(module->rotateOffset)); + if (module->rotateOffset < 0) + displayStr[0] = '('; + } + else {// DISP_NORMAL + snprintf(displayStr, 4, " %2u", (unsigned) (module->editingSequence ? + module->sequence : module->phrase[module->phraseIndexEdit]) + 1 ); + } + } + displayStr[3] = 0;// more safety + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + struct PanelThemeItem : MenuItem { + SemiModularSynth *module; + int panelTheme; + int portTheme; + void onAction(EventAction &e) override { + module->panelTheme = panelTheme; + module->portTheme = portTheme; + } + void step() override { + rightText = ((module->panelTheme == panelTheme) && (module->portTheme == portTheme)) ? "✔" : ""; + } + }; + struct ResetOnRunItem : MenuItem { + SemiModularSynth *module; + void onAction(EventAction &e) override { + module->resetOnRun = !module->resetOnRun; + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + SemiModularSynth *module = dynamic_cast(this->module); + assert(module); + + MenuLabel *themeLabel = new MenuLabel(); + themeLabel->text = "Panel Theme"; + menu->addChild(themeLabel); + + PanelThemeItem *classicItem = new PanelThemeItem(); + classicItem->text = lightPanelID;// ImpromptuModular.hpp + classicItem->module = module; + classicItem->panelTheme = 0; + classicItem->portTheme = 0; + menu->addChild(classicItem); + + PanelThemeItem *lightItem = new PanelThemeItem(); + lightItem->text = "Light"; + lightItem->module = module; + lightItem->panelTheme = 0; + lightItem->portTheme = 1; + menu->addChild(lightItem); + + PanelThemeItem *darkItem = new PanelThemeItem(); + darkItem->text = darkPanelID;// ImpromptuModular.hpp + darkItem->module = module; + darkItem->panelTheme = 1; + darkItem->portTheme = 1; + menu->addChild(darkItem); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); + rorItem->module = module; + menu->addChild(rorItem); + + return menu; + } + + SemiModularSynthWidget(SemiModularSynth *module) : ModuleWidget(module) { + this->module = module; + + // SEQUENCER + + // Main panel from Inkscape + panel = new DynamicSVGPanel(); + panel->mode = &module->panelTheme; + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/SemiModular.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/SemiModular_dark.svg"))); + box.size = panel->box.size; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->portTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->portTheme)); + addChild(createDynamicScrew(Vec((box.size.x - 90) * 1 / 3 + 30 , 0), &module->portTheme)); + addChild(createDynamicScrew(Vec((box.size.x - 90) * 1 / 3 + 30 , 365), &module->portTheme)); + addChild(createDynamicScrew(Vec((box.size.x - 90) * 2 / 3 + 45 , 0), &module->portTheme)); + addChild(createDynamicScrew(Vec((box.size.x - 90) * 2 / 3 + 45 , 365), &module->portTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->portTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 365), &module->portTheme)); + + + + // ****** Top row ****** + + static const int rowRulerT0 = 52; + static const int columnRulerT0 = 19;// Length button + static const int columnRulerT1 = columnRulerT0 + 47;// Left/Right buttons + static const int columnRulerT2 = columnRulerT1 + 75;// Step/Phase lights + static const int columnRulerT3 = columnRulerT2 + 263;// Attach (also used to align rest of right side of module) + + // Length button + addParam(createDynamicParam(Vec(columnRulerT0 + offsetCKD6b, rowRulerT0 + offsetCKD6b), module, SemiModularSynth::LENGTH_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Left/Right buttons + addParam(createDynamicParam(Vec(columnRulerT1 + offsetCKD6b, rowRulerT0 + offsetCKD6b), module, SemiModularSynth::LEFT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addParam(createDynamicParam(Vec(columnRulerT1 + 38 + offsetCKD6b, rowRulerT0 + offsetCKD6b), module, SemiModularSynth::RIGHT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Step/Phrase lights + static const int spLightsSpacing = 15; + for (int i = 0; i < 16; i++) { + addChild(ModuleLightWidget::create>(Vec(columnRulerT2 + spLightsSpacing * i + offsetMediumLight, rowRulerT0 + offsetMediumLight), module, SemiModularSynth::STEP_PHRASE_LIGHTS + (i*2))); + } + // Attach button and light + addParam(ParamWidget::create(Vec(columnRulerT3 - 4, rowRulerT0 + 2 + offsetTL1105), module, SemiModularSynth::ATTACH_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerT3 + 12 + offsetMediumLight, rowRulerT0 + offsetMediumLight), module, SemiModularSynth::ATTACH_LIGHT)); + + + + // ****** Octave and keyboard area ****** + + // Octave LED buttons + static const float octLightsIntY = 20.0f; + for (int i = 0; i < 7; i++) { + addParam(ParamWidget::create(Vec(columnRulerT0 + 3, 86 + 24 + i * octLightsIntY- 4.4f), module, SemiModularSynth::OCTAVE_PARAM + i, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerT0 + 3 + 4.4f, 86 + 24 + i * octLightsIntY), module, SemiModularSynth::OCTAVE_LIGHTS + i)); + } + // Keys and Key lights + static const int keyNudgeX = 7; + static const int keyNudgeY = 2; + static const int offsetKeyLEDx = 6; + static const int offsetKeyLEDy = 28; + // Black keys and lights + addParam(ParamWidget::create( Vec(69+keyNudgeX, 93+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(69+keyNudgeX+offsetKeyLEDx, 93+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 1)); + addParam(ParamWidget::create( Vec(97+keyNudgeX, 93+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(97+keyNudgeX+offsetKeyLEDx, 93+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 3)); + addParam(ParamWidget::create( Vec(154+keyNudgeX, 93+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(154+keyNudgeX+offsetKeyLEDx, 93+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 6)); + addParam(ParamWidget::create( Vec(182+keyNudgeX, 93+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(182+keyNudgeX+offsetKeyLEDx, 93+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 8)); + addParam(ParamWidget::create( Vec(210+keyNudgeX, 93+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(210+keyNudgeX+offsetKeyLEDx, 93+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 10)); + // White keys and lights + addParam(ParamWidget::create( Vec(55+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(55+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 0)); + addParam(ParamWidget::create( Vec(83+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(83+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 2)); + addParam(ParamWidget::create( Vec(111+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(111+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 4)); + addParam(ParamWidget::create( Vec(140+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(140+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 5)); + addParam(ParamWidget::create( Vec(168+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(168+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 7)); + addParam(ParamWidget::create( Vec(196+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(196+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 9)); + addParam(ParamWidget::create( Vec(224+keyNudgeX, 143+keyNudgeY), module, SemiModularSynth::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(224+keyNudgeX+offsetKeyLEDx, 143+keyNudgeY+offsetKeyLEDy), module, SemiModularSynth::KEY_LIGHTS + 11)); + + + + // ****** Right side control area ****** + + static const int rowRulerMK0 = 105;// Edit mode row + static const int rowRulerMK1 = rowRulerMK0 + 56; // Run row + static const int rowRulerMK2 = rowRulerMK1 + 54; // Reset row + static const int columnRulerMK0 = 280;// Edit mode column + static const int columnRulerMK1 = columnRulerMK0 + 59;// Display column + static const int columnRulerMK2 = columnRulerT3;// Run mode column + + // Edit mode switch + addParam(ParamWidget::create(Vec(columnRulerMK0 + hOffsetCKSS, rowRulerMK0 + vOffsetCKSS), module, SemiModularSynth::EDIT_PARAM, 0.0f, 1.0f, SemiModularSynth::EDIT_PARAM_INIT_VALUE)); + // Sequence display + SequenceDisplayWidget *displaySequence = new SequenceDisplayWidget(); + displaySequence->box.pos = Vec(columnRulerMK1-15, rowRulerMK0 + 3 + vOffsetDisplay); + displaySequence->box.size = Vec(55, 30);// 3 characters + displaySequence->module = module; + addChild(displaySequence); + // Run mode button + addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK0 + 0 + offsetCKD6b), module, SemiModularSynth::RUNMODE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Run LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerMK0 + offsetLEDbezel, rowRulerMK1 + 7 + offsetLEDbezel), module, SemiModularSynth::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerMK0 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK1 + 7 + offsetLEDbezel + offsetLEDbezelLight), module, SemiModularSynth::RUN_LIGHT)); + // Sequence knob + addParam(createDynamicParam(Vec(columnRulerMK1 + 1 + offsetIMBigKnob, rowRulerMK0 + 55 + offsetIMBigKnob), module, SemiModularSynth::SEQUENCE_PARAM, -INFINITY, INFINITY, 0.0f, &module->portTheme)); + // Transpose/rotate button + addParam(createDynamicParam(Vec(columnRulerMK2 + offsetCKD6b, rowRulerMK1 + 4 + offsetCKD6b), module, SemiModularSynth::TRAN_ROT_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Reset LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerMK0 + offsetLEDbezel, rowRulerMK2 + 5 + offsetLEDbezel), module, SemiModularSynth::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerMK0 + offsetLEDbezel + offsetLEDbezelLight, rowRulerMK2 + 5 + offsetLEDbezel + offsetLEDbezelLight), module, SemiModularSynth::RESET_LIGHT)); + // Copy/paste buttons + addParam(ParamWidget::create(Vec(columnRulerMK1 - 10, rowRulerMK2 + 5 + offsetTL1105), module, SemiModularSynth::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(columnRulerMK1 + 20, rowRulerMK2 + 5 + offsetTL1105), module, SemiModularSynth::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Copy-paste mode switch (3 position) + addParam(ParamWidget::create(Vec(columnRulerMK2 + hOffsetCKSS + 1, rowRulerMK2 - 3 + vOffsetCKSSThree), module, SemiModularSynth::CPMODE_PARAM, 0.0f, 2.0f, 2.0f)); // 0.0f is top position + + + + // ****** Gate and slide section ****** + + static const int rowRulerMB0 = 218; + static const int columnRulerMBspacing = 70; + static const int columnRulerMB2 = 134;// Gate2 + static const int columnRulerMB1 = columnRulerMB2 - columnRulerMBspacing;// Gate1 + static const int columnRulerMB3 = columnRulerMB2 + columnRulerMBspacing;// Tie + static const int posLEDvsButton = + 25; + + // Gate 1 light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB1 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, SemiModularSynth::GATE1_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB1 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, SemiModularSynth::GATE1_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Gate 2 light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB2 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, SemiModularSynth::GATE2_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB2 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, SemiModularSynth::GATE2_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Tie light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerMB3 + posLEDvsButton + offsetMediumLight, rowRulerMB0 + 4 + offsetMediumLight), module, SemiModularSynth::TIE_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerMB3 + offsetCKD6b, rowRulerMB0 + 4 + offsetCKD6b), module, SemiModularSynth::TIE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + + + // ****** Bottom two rows ****** + + static const int outputJackSpacingX = 54; + static const int rowRulerB0 = 327; + static const int rowRulerB1 = 273; + static const int columnRulerB0 = 26; + static const int columnRulerB1 = columnRulerB0 + outputJackSpacingX; + static const int columnRulerB2 = columnRulerB1 + outputJackSpacingX; + static const int columnRulerB3 = columnRulerB2 + outputJackSpacingX; + static const int columnRulerB4 = columnRulerB3 + outputJackSpacingX; + static const int columnRulerB7 = columnRulerMK2 + 1; + static const int columnRulerB6 = columnRulerB7 - outputJackSpacingX; + static const int columnRulerB5 = columnRulerB6 - outputJackSpacingX; + + + // Gate 1 probability light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerB0 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, SemiModularSynth::GATE1_PROB_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerB0 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, SemiModularSynth::GATE1_PROB_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Gate 1 probability knob + addParam(createDynamicParam(Vec(columnRulerB1 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, SemiModularSynth::GATE1_KNOB_PARAM, 0.0f, 1.0f, 1.0f, &module->portTheme)); + // Slide light and button + addChild(ModuleLightWidget::create>(Vec(columnRulerB2 + posLEDvsButton + offsetMediumLight, rowRulerB1 + offsetMediumLight), module, SemiModularSynth::SLIDE_LIGHT)); + addParam(createDynamicParam(Vec(columnRulerB2 + offsetCKD6b, rowRulerB1 + offsetCKD6b), module, SemiModularSynth::SLIDE_BTN_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Slide knob + addParam(createDynamicParam(Vec(columnRulerB3 + offsetIMSmallKnob, rowRulerB1 + offsetIMSmallKnob), module, SemiModularSynth::SLIDE_KNOB_PARAM, 0.0f, 2.0f, 0.2f, &module->portTheme)); + // Autostep + addParam(ParamWidget::create(Vec(columnRulerB4 + hOffsetCKSS, rowRulerB1 + vOffsetCKSS), module, SemiModularSynth::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); + // CV in + addInput(createDynamicPort(Vec(columnRulerB5, rowRulerB1), Port::INPUT, module, SemiModularSynth::CV_INPUT, &module->portTheme)); + // Reset + addInput(createDynamicPort(Vec(columnRulerB6, rowRulerB1), Port::INPUT, module, SemiModularSynth::RESET_INPUT, &module->portTheme)); + // Clock + addInput(createDynamicPort(Vec(columnRulerB7, rowRulerB1), Port::INPUT, module, SemiModularSynth::CLOCK_INPUT, &module->portTheme)); + + + + // ****** Bottom row (all aligned) ****** + + + // CV control Inputs + addInput(createDynamicPort(Vec(columnRulerB0, rowRulerB0), Port::INPUT, module, SemiModularSynth::LEFTCV_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(columnRulerB1, rowRulerB0), Port::INPUT, module, SemiModularSynth::RIGHTCV_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(columnRulerB2, rowRulerB0), Port::INPUT, module, SemiModularSynth::SEQCV_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(columnRulerB3, rowRulerB0), Port::INPUT, module, SemiModularSynth::RUNCV_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(columnRulerB4, rowRulerB0), Port::INPUT, module, SemiModularSynth::WRITE_INPUT, &module->portTheme)); + // Outputs + addOutput(createDynamicPort(Vec(columnRulerB5, rowRulerB0), Port::OUTPUT, module, SemiModularSynth::CV_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(columnRulerB6, rowRulerB0), Port::OUTPUT, module, SemiModularSynth::GATE1_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(columnRulerB7, rowRulerB0), Port::OUTPUT, module, SemiModularSynth::GATE2_OUTPUT, &module->portTheme)); + + + // VCO + static const int rowRulerVCO0 = 66;// Freq + static const int rowRulerVCO1 = rowRulerVCO0 + 40;// Fine, PW + static const int rowRulerVCO2 = rowRulerVCO1 + 35;// FM, PWM, exact value from svg + static const int rowRulerVCO3 = rowRulerVCO2 + 46;// switches (Don't change this, tweak the switches' v offset instead since jacks lines up with this) + static const int rowRulerSpacingJacks = 35;// exact value from svg + static const int rowRulerVCO4 = rowRulerVCO3 + rowRulerSpacingJacks;// jack row 1 + static const int rowRulerVCO5 = rowRulerVCO4 + rowRulerSpacingJacks;// jack row 2 + static const int rowRulerVCO6 = rowRulerVCO5 + rowRulerSpacingJacks;// jack row 3 + static const int rowRulerVCO7 = rowRulerVCO6 + rowRulerSpacingJacks;// jack row 4 + static const int colRulerVCO0 = 460; + static const int colRulerVCO1 = colRulerVCO0 + 55;// exact value from svg + + addParam(createDynamicParam(Vec(colRulerVCO0 + offsetIMBigKnob + 55 / 2, rowRulerVCO0 + offsetIMBigKnob), module, SemiModularSynth::VCO_FREQ_PARAM, -54.0f, 54.0f, 0.0f, &module->portTheme)); + + addParam(createDynamicParam(Vec(colRulerVCO0 + offsetIMSmallKnob, rowRulerVCO1 + offsetIMSmallKnob), module, SemiModularSynth::VCO_FINE_PARAM, -1.0f, 1.0f, 0.0f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVCO1 + offsetIMSmallKnob, rowRulerVCO1 + offsetIMSmallKnob), module, SemiModularSynth::VCO_PW_PARAM, 0.0f, 1.0f, 0.5f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVCO0 + offsetIMSmallKnob, rowRulerVCO2 + offsetIMSmallKnob), module, SemiModularSynth::VCO_FM_PARAM, 0.0f, 1.0f, 0.0f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVCO1 + offsetIMSmallKnob, rowRulerVCO2 + offsetIMSmallKnob), module, SemiModularSynth::VCO_PWM_PARAM, 0.0f, 1.0f, 0.0f, &module->portTheme)); + + addParam(ParamWidget::create(Vec(colRulerVCO0 + hOffsetCKSS, rowRulerVCO3 + vOffsetCKSS), module, SemiModularSynth::VCO_MODE_PARAM, 0.0f, 1.0f, 1.0f)); + addParam(createDynamicParam(Vec(colRulerVCO1 + offsetIMSmallKnob, rowRulerVCO3 + offsetIMSmallKnob), module, SemiModularSynth::VCO_OCT_PARAM, -2.0f, 2.0f, 0.0f, &module->portTheme)); + + addOutput(createDynamicPort(Vec(colRulerVCO0, rowRulerVCO4), Port::OUTPUT, module, SemiModularSynth::VCO_SIN_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVCO1, rowRulerVCO4), Port::OUTPUT, module, SemiModularSynth::VCO_TRI_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVCO0, rowRulerVCO5), Port::OUTPUT, module, SemiModularSynth::VCO_SAW_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVCO1, rowRulerVCO5), Port::OUTPUT, module, SemiModularSynth::VCO_SQR_OUTPUT, &module->portTheme)); + + addInput(createDynamicPort(Vec(colRulerVCO0, rowRulerVCO6), Port::INPUT, module, SemiModularSynth::VCO_PITCH_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCO1, rowRulerVCO6), Port::INPUT, module, SemiModularSynth::VCO_FM_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCO0, rowRulerVCO7), Port::INPUT, module, SemiModularSynth::VCO_SYNC_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCO1, rowRulerVCO7), Port::INPUT, module, SemiModularSynth::VCO_PW_INPUT, &module->portTheme)); + + + // CLK + static const int rowRulerClk0 = 41; + static const int rowRulerClk1 = rowRulerClk0 + 45;// exact value from svg + static const int rowRulerClk2 = rowRulerClk1 + 38; + static const int colRulerClk0 = colRulerVCO1 + 55;// exact value from svg + addParam(createDynamicParam(Vec(colRulerClk0 + offsetIMSmallKnob, rowRulerClk0 + offsetIMSmallKnob), module, SemiModularSynth::CLK_FREQ_PARAM, -4.0f, 6.0f, 1.0f, &module->portTheme));// 120 BMP when default value + addParam(createDynamicParam(Vec(colRulerClk0 + offsetIMSmallKnob, rowRulerClk1 + offsetIMSmallKnob), module, SemiModularSynth::CLK_PW_PARAM, 0.0f, 1.0f, 0.5f, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerClk0, rowRulerClk2), Port::OUTPUT, module, SemiModularSynth::CLK_OUT_OUTPUT, &module->portTheme)); + + + // VCA + static const int colRulerVca1 = colRulerClk0 + 55;// exact value from svg + addParam(createDynamicParam(Vec(colRulerClk0 + offsetIMSmallKnob, rowRulerVCO3 + offsetIMSmallKnob), module, SemiModularSynth::VCA_LEVEL1_PARAM, 0.0f, 1.0f, 1.0f, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerClk0, rowRulerVCO4), Port::INPUT, module, SemiModularSynth::VCA_LIN1_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerClk0, rowRulerVCO5), Port::INPUT, module, SemiModularSynth::VCA_IN1_INPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVca1, rowRulerVCO5), Port::OUTPUT, module, SemiModularSynth::VCA_OUT1_OUTPUT, &module->portTheme)); + + + // ADSR + static const int rowRulerAdsr0 = rowRulerClk0; + static const int rowRulerAdsr3 = rowRulerVCO2 + 6; + static const int rowRulerAdsr1 = rowRulerAdsr0 + (rowRulerAdsr3 - rowRulerAdsr0) * 1 / 3; + static const int rowRulerAdsr2 = rowRulerAdsr0 + (rowRulerAdsr3 - rowRulerAdsr0) * 2 / 3; + addParam(createDynamicParam(Vec(colRulerVca1 + offsetIMSmallKnob, rowRulerAdsr0 + offsetIMSmallKnob), module, SemiModularSynth::ADSR_ATTACK_PARAM, 0.0f, 1.0f, 0.1f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVca1 + offsetIMSmallKnob, rowRulerAdsr1 + offsetIMSmallKnob), module, SemiModularSynth::ADSR_DECAY_PARAM, 0.0f, 1.0f, 0.5f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVca1 + offsetIMSmallKnob, rowRulerAdsr2 + offsetIMSmallKnob), module, SemiModularSynth::ADSR_SUSTAIN_PARAM, 0.0f, 1.0f, 0.5f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVca1 + offsetIMSmallKnob, rowRulerAdsr3 + offsetIMSmallKnob), module, SemiModularSynth::ADSR_RELEASE_PARAM, 0.0f, 1.0f, 0.5f, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVca1, rowRulerVCO3), Port::OUTPUT, module, SemiModularSynth::ADSR_ENVELOPE_OUTPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVca1, rowRulerVCO4), Port::INPUT, module, SemiModularSynth::ADSR_GATE_INPUT, &module->portTheme)); + + + // VCF + static const int colRulerVCF0 = colRulerVca1 + 55;// exact value from svg + static const int colRulerVCF1 = colRulerVCF0 + 55;// exact value from svg + addParam(createDynamicParam(Vec(colRulerVCF0 + offsetIMBigKnob + 55 / 2, rowRulerVCO0 + offsetIMBigKnob), module, SemiModularSynth::VCF_FREQ_PARAM, 0.0f, 1.0f, 0.666f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVCF0 + offsetIMSmallKnob + 55 / 2, rowRulerVCO1 + offsetIMSmallKnob), module, SemiModularSynth::VCF_RES_PARAM, 0.0f, 1.0f, 0.0f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVCF0 + offsetIMSmallKnob , rowRulerVCO2 + offsetIMSmallKnob), module, SemiModularSynth::VCF_FREQ_CV_PARAM, -1.0f, 1.0f, 0.0f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerVCF1 + offsetIMSmallKnob , rowRulerVCO2 + offsetIMSmallKnob), module, SemiModularSynth::VCF_DRIVE_PARAM, 0.0f, 1.0f, 0.0f, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCF0, rowRulerVCO3), Port::INPUT, module, SemiModularSynth::VCF_FREQ_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCF1, rowRulerVCO3), Port::INPUT, module, SemiModularSynth::VCF_RES_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCF0, rowRulerVCO4), Port::INPUT, module, SemiModularSynth::VCF_DRIVE_INPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerVCF0, rowRulerVCO5), Port::INPUT, module, SemiModularSynth::VCF_IN_INPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVCF1, rowRulerVCO4), Port::OUTPUT, module, SemiModularSynth::VCF_HPF_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerVCF1, rowRulerVCO5), Port::OUTPUT, module, SemiModularSynth::VCF_LPF_OUTPUT, &module->portTheme)); + + + // LFO + static const int colRulerLfo = colRulerVCF1 + 55;// exact value from svg + static const int rowRulerLfo0 = rowRulerAdsr0; + static const int rowRulerLfo2 = rowRulerVCO2; + static const int rowRulerLfo1 = rowRulerLfo0 + (rowRulerLfo2 - rowRulerLfo0) / 2; + addParam(createDynamicParam(Vec(colRulerLfo + offsetIMSmallKnob, rowRulerLfo0 + offsetIMSmallKnob), module, SemiModularSynth::LFO_FREQ_PARAM, -8.0f, 6.0f, -1.0f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerLfo + offsetIMSmallKnob, rowRulerLfo1 + offsetIMSmallKnob), module, SemiModularSynth::LFO_GAIN_PARAM, 0.0f, 1.0f, 0.5f, &module->portTheme)); + addParam(createDynamicParam(Vec(colRulerLfo + offsetIMSmallKnob, rowRulerLfo2 + offsetIMSmallKnob), module, SemiModularSynth::LFO_OFFSET_PARAM, -1.0f, 1.0f, 0.0f, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerLfo, rowRulerVCO3), Port::OUTPUT, module, SemiModularSynth::LFO_TRI_OUTPUT, &module->portTheme)); + addOutput(createDynamicPort(Vec(colRulerLfo, rowRulerVCO4), Port::OUTPUT, module, SemiModularSynth::LFO_SIN_OUTPUT, &module->portTheme)); + addInput(createDynamicPort(Vec(colRulerLfo, rowRulerVCO5), Port::INPUT, module, SemiModularSynth::LFO_RESET_INPUT, &module->portTheme)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, SemiModularSynth) { + Model *modelSemiModularSynth = Model::create("Impromptu Modular", "Semi-Modular Synth", "MISC - Semi-Modular Synth", SEQUENCER_TAG, OSCILLATOR_TAG); + return modelSemiModularSynth; +} + +/*CHANGE LOG + +0.6.10: +allow main knob to also change length when length editing is active + +0.6.9: +add FW2, FW3 and FW4 run modes for sequences (but not for song) +update VCF code to match new Fundamental code (existing patches may sound different) +right click on notes now does same as left click but with autostep + +0.6.8: +show length in display when editing length + +0.6.7: +allow full edit capabilities in Seq and song mode +no reset on run by default, with switch added in context menu +reset does not revert seq or song number to 1 +gate 2 is off by default +fix emitted monitoring gates to depend on gate states instead of always triggering + +0.6.6: +knob bug fixes when loading patch +dark by default when create new instance of module + +0.6.5: +initial release of SMS +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/Tact.cpp b/plugins/community/repos/ImpromptuModular/src/Tact.cpp new file mode 100644 index 00000000..155ba18f --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/Tact.cpp @@ -0,0 +1,520 @@ +//*********************************************************************************************** +//Tactile CV controller module for VCV Rack by Marc Boulé +// +//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 +//See ./res/fonts/ for font licenses +// +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct Tact : Module { + static const int numLights = 10;// number of lights per channel + + enum ParamIds { + ENUMS(TACT_PARAMS, 2),// touch pads + ENUMS(ATTV_PARAMS, 2),// max knobs + ENUMS(RATE_PARAMS, 2),// rate knobs + LINK_PARAM, + ENUMS(SLIDE_PARAMS, 2),// slide switches + ENUMS(STORE_PARAMS, 2),// store buttons + // -- 0.6.7 ^^ + EXP_PARAM, + NUM_PARAMS + }; + enum InputIds { + ENUMS(TOP_INPUTS, 2), + ENUMS(BOT_INPUTS, 2), + ENUMS(RECALL_INPUTS, 2), + NUM_INPUTS + }; + enum OutputIds { + ENUMS(CV_OUTPUTS, 2), + // -- 0.6.7 ^^ + ENUMS(EOC_OUTPUTS, 2), + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(TACT_LIGHTS, numLights * 2 + numLights * 2), // first N lights for channel L, other N for channel R (*2 for GreenRed) + ENUMS(CVIN_LIGHTS, 2 * 2),// GreenRed + NUM_LIGHTS + }; + + + // Constants + static constexpr float storeInfoTime = 0.5f;// seconds + + // Need to save, with reset + double cv[2];// actual Tact CV since Tactknob can be different than these when transitioning + float storeCV[2]; + + // Need to save, no reset + int panelTheme; + + // No need to save, with reset + // none + + // No need to save, no reset + bool scheduledReset; + long infoStore;// 0 when no info, positive downward step counter when store left channel, negative upward for right + float infoCVinLight[2]; + SchmittTrigger topTriggers[2]; + SchmittTrigger botTriggers[2]; + SchmittTrigger storeTriggers[2]; + SchmittTrigger recallTriggers[2]; + PulseGenerator eocPulses[2]; + float paramReadRequest[2]; + + + inline bool isLinked(void) {return params[LINK_PARAM].value > 0.5f;} + inline bool isExpSliding(void) {return params[EXP_PARAM].value > 0.5f;} + + + Tact() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + // Need to save, no reset + panelTheme = 0; + // No need to save, no reset + scheduledReset = false; + infoStore = 0l; + for (int i = 0; i < 2; i++) { + infoCVinLight[i] = 0.0f; + topTriggers[i].reset(); + botTriggers[i].reset(); + storeTriggers[i].reset(); + recallTriggers[i].reset(); + eocPulses[i].reset(); + paramReadRequest[i] = -10.0f;// -10.0f when no request being made, value to read otherwize + } + + 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 + for (int i = 0; i < 2; i++) { + cv[i] = 0.0f; + storeCV[i] = 0.0f; + } + // 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 + for (int i = 0; i < 2; i++) { + cv[i] = clamp(params[TACT_PARAMS + i].value, 0.0f, 10.0f); + storeCV[i] = 0.0f; + } + // 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) + + // cv + json_object_set_new(rootJ, "cv0", json_real(cv[0])); + json_object_set_new(rootJ, "cv1", json_real(cv[1])); + + // storeCV + json_object_set_new(rootJ, "storeCV0", json_real(storeCV[0])); + json_object_set_new(rootJ, "storeCV1", json_real(storeCV[1])); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + 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) + + // cv + json_t *cv0J = json_object_get(rootJ, "cv0"); + if (cv0J) + cv[0] = json_real_value(cv0J); + json_t *cv1J = json_object_get(rootJ, "cv1"); + if (cv1J) + cv[1] = json_real_value(cv1J); + + // storeCV[0] + json_t *storeCV0J = json_object_get(rootJ, "storeCV0"); + if (storeCV0J) + storeCV[0] = json_real_value(storeCV0J); + json_t *storeCV1J = json_object_get(rootJ, "storeCV1"); + if (storeCV1J) + storeCV[1] = json_real_value(storeCV1J); + + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // No need to save, with reset + // none + + scheduledReset = true; + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + float sampleRate = engineGetSampleRate(); + float sampleTime = engineGetSampleTime(); + long initInfoStore = (long) (storeInfoTime * sampleRate); + + // Scheduled reset (just the parts that do not have a place below in rest of function) + if (scheduledReset) { + infoStore = 0l; + for (int i = 0; i < 2; i++) { + infoCVinLight[i] = 0.0f; + topTriggers[i].reset(); + botTriggers[i].reset(); + storeTriggers[i].reset(); + recallTriggers[i].reset(); + eocPulses[i].reset(); + paramReadRequest[i] = -10.0f;// -10.0f when no request being made, value to read otherwize + } + } + + // store buttons + for (int i = 0; i < 2; i++) { + if (storeTriggers[i].process(params[STORE_PARAMS + i].value)) { + if ( !(i == 1 && isLinked()) ) {// ignore right channel store-button press when linked + storeCV[i] = cv[i]; + infoStore = initInfoStore * (i == 0 ? 1l : -1l); + } + } + } + + // top/bot/recall CV inputs + for (int i = 0; i < 2; i++) { + if (topTriggers[i].process(inputs[TOP_INPUTS + i].value)) { + if ( !(i == 1 && isLinked()) ) {// ignore right channel top cv in when linked + //tactWidgets[i]->changeValue(10.0f); + paramReadRequest[i] = 10.0f; + infoCVinLight[i] = 1.0f; + } + } + if (botTriggers[i].process(inputs[BOT_INPUTS + i].value)) { + if ( !(i == 1 && isLinked()) ) {// ignore right channel bot cv in when linked + //tactWidgets[i]->changeValue(0.0f); + paramReadRequest[i] = 0.0f; + infoCVinLight[i] = 1.0f; + } + } + if (recallTriggers[i].process(inputs[RECALL_INPUTS + i].value)) {// ignore right channel recall cv in when linked + if ( !(i == 1 && isLinked()) ) { + //tactWidgets[i]->changeValue(storeCV[i]); + paramReadRequest[i] = storeCV[i]; + if (params[SLIDE_PARAMS + i].value < 0.5f) //if no slide + cv[i]=storeCV[i]; + infoCVinLight[i] = 1.0f; + } + } + } + + + // cv + bool expSliding = isExpSliding(); + for (int i = 0; i < 2; i++) { + if (paramReadRequest[i] != -10.0f) + continue; + float newParamValue = clamp(params[TACT_PARAMS + i].value, 0.0f, 10.0f); + if (newParamValue != cv[i]) { + double transitionRate = params[RATE_PARAMS + i].value; // s/V + double dt = sampleTime; + if ((newParamValue - cv[i]) > 0.001f && transitionRate > 0.001) { + double dV = expSliding ? (cv[i] + 1.0) * (pow(11.0, dt / (10.0 * transitionRate)) - 1.0) : dt/transitionRate; + double newCV = cv[i] + dV; + if (newCV > newParamValue) { + cv[i] = newParamValue; + eocPulses[i].trigger(0.001f); + } + else + cv[i] = (float)newCV; + } + else if ((newParamValue - cv[i]) < -0.001f && transitionRate > 0.001) { + dt *= -1.0; + double dV = expSliding ? (cv[i] + 1.0) * (pow(11.0, dt / (10.0 * transitionRate)) - 1.0) : dt/transitionRate; + double newCV = cv[i] + dV; + if (newCV < newParamValue) { + cv[i] = newParamValue; + eocPulses[i].trigger(0.001f); + } + else + cv[i] = (float)newCV; + } + else {// too close to target or rate too fast, thus no slide + if (fabs(cv[i] - newParamValue) > 1e-6) + eocPulses[i].trigger(0.001f); + cv[i] = newParamValue; + } + } + } + + + // CV and EOC Outputs + bool eocValues[2] = {eocPulses[0].process((float)sampleTime), eocPulses[1].process((float)sampleTime)}; + for (int i = 0; i < 2; i++) { + int readChan = isLinked() ? 0 : i; + outputs[CV_OUTPUTS + i].value = (float)cv[readChan] * params[ATTV_PARAMS + readChan].value; + outputs[EOC_OUTPUTS + i].value = eocValues[readChan]; + } + + + // Tactile lights + if (infoStore > 0l) + setTLightsStore(0, infoStore, initInfoStore); + else + setTLights(0); + if (infoStore < 0l) + setTLightsStore(1, infoStore * -1l, initInfoStore); + else + setTLights(1); + + // CV input lights + for (int i = 0; i < 2; i++) + lights[CVIN_LIGHTS + i * 2].value = infoCVinLight[i]; + + for (int i = 0; i < 2; i++) { + infoCVinLight[i] -= (infoCVinLight[i] / lightLambda) * engineGetSampleTime(); + } + if (isLinked()) { + cv[1] = clamp(params[TACT_PARAMS + 1].value, 0.0f, 10.0f); + } + if (infoStore != 0l) { + if (infoStore > 0l) + infoStore --; + if (infoStore < 0l) + infoStore ++; + } + + scheduledReset = false; + } + + void setTLights(int chan) { + int readChan = isLinked() ? 0 : chan; + float cvValue = (float)cv[readChan]; + for (int i = 0; i < numLights; i++) { + float level = clamp( cvValue - ((float)(i)), 0.0f, 1.0f); + // Green diode + lights[TACT_LIGHTS + (chan * numLights * 2) + (numLights - 1 - i) * 2 + 0].setBrightness(level); + // Red diode + lights[TACT_LIGHTS + (chan * numLights * 2) + (numLights - 1 - i) * 2 + 1].value = 0.0f; + } + } + void setTLightsStore(int chan, long infoCount, long initInfoStore) { + for (int i = 0; i < numLights; i++) { + float level = (i == (int) round((float(infoCount)) / ((float)initInfoStore) * (float)(numLights - 1)) ? 1.0f : 0.0f); + // Green diode + lights[TACT_LIGHTS + (chan * numLights * 2) + (numLights - 1 - i) * 2 + 0].setBrightness(level); + // Red diode + lights[TACT_LIGHTS + (chan * numLights * 2) + (numLights - 1 - i) * 2 + 1].value = 0.0f; + } + } +}; + + +struct TactWidget : ModuleWidget { + + struct PanelThemeItem : MenuItem { + Tact *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); + + Tact *module = dynamic_cast(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; + } + + TactWidget(Tact *module) : ModuleWidget(module) { + // Main panel from Inkscape + DynamicSVGPanel *panel = new DynamicSVGPanel(); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/Tact.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/Tact_dark.svg"))); + box.size = panel->box.size; + panel->mode = &module->panelTheme; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 365), &module->panelTheme)); + + + static const int rowRuler0 = 34; + static const int colRulerPadL = 73; + static const int colRulerPadR = 136; + + // Tactile touch pads + // Right (no dynamic width, but must do first so that left will get mouse events when wider overlaps) + addParam(createDynamicParam2(Vec(colRulerPadR, rowRuler0), module, Tact::TACT_PARAMS + 1, -1.0f, 11.0f, 0.0f, nullptr, &module->paramReadRequest[1])); + // Left (with width dependant on Link value) + addParam(createDynamicParam2(Vec(colRulerPadL, rowRuler0), module, Tact::TACT_PARAMS + 0, -1.0f, 11.0f, 0.0f, &module->params[Tact::LINK_PARAM].value, &module->paramReadRequest[0])); + + + + static const int colRulerLedL = colRulerPadL - 20; + static const int colRulerLedR = colRulerPadR + 56; + static const int lightsOffsetY = 19; + static const int lightsSpacingY = 17; + + // Tactile lights + for (int i = 0 ; i < Tact::numLights; i++) { + addChild(ModuleLightWidget::create>(Vec(colRulerLedL, rowRuler0 + lightsOffsetY + i * lightsSpacingY), module, Tact::TACT_LIGHTS + i * 2)); + addChild(ModuleLightWidget::create>(Vec(colRulerLedR, rowRuler0 + lightsOffsetY + i * lightsSpacingY), module, Tact::TACT_LIGHTS + (Tact::numLights + i) * 2)); + } + + + static const int colRulerCenter = 115;// not real center, but pos so that a jack would be centered + static const int rowRuler2 = 265;// outputs and link + static const int colRulerC3L = colRulerCenter - 101 - 1; + static const int colRulerC3R = colRulerCenter + 101; + + // Recall CV inputs + addInput(createDynamicPort(Vec(colRulerC3L, rowRuler2), Port::INPUT, module, Tact::RECALL_INPUTS + 0, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC3R, rowRuler2), Port::INPUT, module, Tact::RECALL_INPUTS + 1, &module->panelTheme)); + + + static const int rowRuler1d = rowRuler2 - 54; + + // Slide switches + addParam(ParamWidget::create(Vec(colRulerC3L + hOffsetCKSS, rowRuler1d + vOffsetCKSS), module, Tact::SLIDE_PARAMS + 0, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(colRulerC3R + hOffsetCKSS, rowRuler1d + vOffsetCKSS), module, Tact::SLIDE_PARAMS + 1, 0.0f, 1.0f, 0.0f)); + + + static const int rowRuler1c = rowRuler1d - 46; + + // Store buttons + addParam(ParamWidget::create(Vec(colRulerC3L + offsetTL1105, rowRuler1c + offsetTL1105), module, Tact::STORE_PARAMS + 0, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(colRulerC3R + offsetTL1105, rowRuler1c + offsetTL1105), module, Tact::STORE_PARAMS + 1, 0.0f, 1.0f, 0.0f)); + + + static const int rowRuler1b = rowRuler1c - 59; + + // Attv knobs + addParam(createDynamicParam(Vec(colRulerC3L + offsetIMSmallKnob, rowRuler1b + offsetIMSmallKnob), module, Tact::ATTV_PARAMS + 0, -1.0f, 1.0f, 1.0f, &module->panelTheme)); + addParam(createDynamicParam(Vec(colRulerC3R + offsetIMSmallKnob, rowRuler1b + offsetIMSmallKnob), module, Tact::ATTV_PARAMS + 1, -1.0f, 1.0f, 1.0f, &module->panelTheme)); + + + static const int rowRuler1a = rowRuler1b - 59; + + // Rate knobs + addParam(createDynamicParam(Vec(colRulerC3L + offsetIMSmallKnob, rowRuler1a + offsetIMSmallKnob), module, Tact::RATE_PARAMS + 0, 0.0f, 4.0f, 0.2f, &module->panelTheme)); + addParam(createDynamicParam(Vec(colRulerC3R + offsetIMSmallKnob, rowRuler1a + offsetIMSmallKnob), module, Tact::RATE_PARAMS + 1, 0.0f, 4.0f, 0.2f, &module->panelTheme)); + + + static const int colRulerC1L = colRulerCenter - 30 - 1; + static const int colRulerC1R = colRulerCenter + 30; + static const int colRulerC2L = colRulerCenter - 65 - 1; + static const int colRulerC2R = colRulerCenter + 65 + 1; + + // Exp switch + addParam(ParamWidget::create(Vec(colRulerCenter + hOffsetCKSS, rowRuler2 + vOffsetCKSS), module, Tact::EXP_PARAM, 0.0f, 1.0f, 0.0f)); + + // Top/bot CV Inputs + addInput(createDynamicPort(Vec(colRulerC2L, rowRuler2), Port::INPUT, module, Tact::TOP_INPUTS + 0, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC1L, rowRuler2), Port::INPUT, module, Tact::BOT_INPUTS + 0, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC1R, rowRuler2), Port::INPUT, module, Tact::BOT_INPUTS + 1, &module->panelTheme)); + addInput(createDynamicPort(Vec(colRulerC2R, rowRuler2), Port::INPUT, module, Tact::TOP_INPUTS + 1, &module->panelTheme)); + + + static const int rowRuler3 = rowRuler2 + 54; + + // Link switch + addParam(ParamWidget::create(Vec(colRulerCenter + hOffsetCKSS, rowRuler3 + vOffsetCKSS), module, Tact::LINK_PARAM, 0.0f, 1.0f, 0.0f)); + + // Outputs + addOutput(createDynamicPort(Vec(colRulerCenter - 49 - 1, rowRuler3), Port::OUTPUT, module, Tact::CV_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerCenter + 49, rowRuler3), Port::OUTPUT, module, Tact::CV_OUTPUTS + 1, &module->panelTheme)); + + // EOC + addOutput(createDynamicPort(Vec(colRulerCenter - 89 - 1, rowRuler3), Port::OUTPUT, module, Tact::EOC_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(colRulerCenter + 89, rowRuler3), Port::OUTPUT, module, Tact::EOC_OUTPUTS + 1, &module->panelTheme)); + + + // Lights + addChild(ModuleLightWidget::create>(Vec(colRulerCenter - 47 - 1 + offsetMediumLight, rowRuler2 - 24 + offsetMediumLight), module, Tact::CVIN_LIGHTS + 0 * 2)); + addChild(ModuleLightWidget::create>(Vec(colRulerCenter + 47 + 1 + offsetMediumLight, rowRuler2 - 24 + offsetMediumLight), module, Tact::CVIN_LIGHTS + 1 * 2)); + + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, Tact) { + Model *modelTact = Model::create("Impromptu Modular", "Tact", "CTRL - Tact", CONTROLLER_TAG); + return modelTact; +} + +/*CHANGE LOG + +0.6.9: +move EOC outputs to main panel and remove expansion panel + +0.6.8: +remove widget pointer dependancy in module for changing a tact param value +implement exponential sliding +add expansion panel with EOC outputs + +0.6.7: +created + +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp b/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp new file mode 100644 index 00000000..72be17bb --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/TwelveKey.cpp @@ -0,0 +1,360 @@ +//*********************************************************************************************** +//Chain-able keyboard module for VCV Rack by Marc Boulé +// +//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 +//See ./res/fonts/ for font licenses +// +//Module inspired by: +// * the Autodafe keyboard by Antonio Grazioli +// * the cf mixer by Clément Foulc +// * Twisted Electrons' KeyChain +// +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct TwelveKey : Module { + enum ParamIds { + OCTINC_PARAM, + OCTDEC_PARAM, + ENUMS(KEY_PARAMS, 12), + NUM_PARAMS + }; + enum InputIds { + GATE_INPUT, + CV_INPUT, + OCT_INPUT, + NUM_INPUTS + }; + enum OutputIds { + GATE_OUTPUT, + CV_OUTPUT, + OCT_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + PRESS_LIGHT,// no longer used + ENUMS(KEY_LIGHTS, 12), + NUM_LIGHTS + }; + + // Need to save + int panelTheme = 0; + int octaveNum;// 0 to 9 + float cv; + bool stateInternal;// false when pass through CV and Gate, true when CV and gate from this module + + // No need to save + //float gateLight = 0.0f; + unsigned long noteLightCounter;// 0 when no key to light, downward step counter timer when key lit + int lastKeyPressed;// 0 to 11 + + + SchmittTrigger keyTriggers[12]; + SchmittTrigger gateInputTrigger; + SchmittTrigger octIncTrigger; + SchmittTrigger octDecTrigger; + + + TwelveKey() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + void onReset() override { + octaveNum = 4; + cv = 0.0f; + stateInternal = inputs[GATE_INPUT].active ? false : true; + noteLightCounter = 0ul; + lastKeyPressed = 0; + } + + void onRandomize() override { + octaveNum = randomu32() % 10; + cv = ((float)(octaveNum - 4)) + ((float)(randomu32() % 12)) / 12.0f; + stateInternal = inputs[GATE_INPUT].active ? false : true; + noteLightCounter = 0ul; + lastKeyPressed = 0; + } + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // cv + json_object_set_new(rootJ, "cv", json_real(cv)); + + // octave + json_object_set_new(rootJ, "octave", json_integer(octaveNum)); + + // stateInternal + json_object_set_new(rootJ, "stateInternal", json_boolean(stateInternal)); + + return rootJ; + } + + void fromJson(json_t *rootJ) override { + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // cv + json_t *cvJ = json_object_get(rootJ, "cv"); + if (cvJ) + cv = json_real_value(cvJ); + + // octave + json_t *octaveJ = json_object_get(rootJ, "octave"); + if (octaveJ) + octaveNum = json_integer_value(octaveJ); + + // stateInternal + json_t *stateInternalJ = json_object_get(rootJ, "stateInternal"); + if (stateInternalJ) + stateInternal = json_is_true(stateInternalJ); + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + static const float noteLightTime = 0.5f;// seconds + + + //********** Buttons, knobs, switches and inputs ********** + + // Octave buttons and input + if (octIncTrigger.process(params[OCTINC_PARAM].value)) + octaveNum++; + if (octDecTrigger.process(params[OCTDEC_PARAM].value)) + octaveNum--; + if (inputs[OCT_INPUT].active) + octaveNum = ((int) floor(inputs[OCT_INPUT].value)); + if (octaveNum > 9) octaveNum = 9; + if (octaveNum < 0) octaveNum = 0; + + // Keyboard buttons and gate input + for (int i = 0; i < 12; i++) { + if (keyTriggers[i].process(params[KEY_PARAMS + i].value)) { + cv = ((float)(octaveNum - 4)) + ((float) i) / 12.0f; + stateInternal = true; + noteLightCounter = (unsigned long) (noteLightTime * engineGetSampleRate()); + lastKeyPressed = i; + } + } + if (gateInputTrigger.process(inputs[GATE_INPUT].value)) { + cv = inputs[CV_INPUT].value; + stateInternal = false; + } + + + //********** Outputs and lights ********** + + // Gate light (with fade) + int pressed = 0; + for (int i = 0; i < 12; i++) + if (params[KEY_PARAMS + i].value > 0.5f) + pressed++; + /*if (pressed != 0) + gateLight = 1.0f; + else + gateLight -= (gateLight / lightLambda) * engineGetSampleTime(); + lights[PRESS_LIGHT].value = gateLight;*/ + + // cv output + outputs[CV_OUTPUT].value = cv; + + // gate output + if (stateInternal == false) {// if receiving a key from left chain + outputs[GATE_OUTPUT].value = inputs[GATE_INPUT].value; + } + else {// key from this + outputs[GATE_OUTPUT].value = (pressed != 0 ? 10.0f : 0.0f); + } + + // Octave output + outputs[OCT_OUTPUT].value = round( (float)(octaveNum + 1) ); + + // Key lights + for (int i = 0; i < 12; i++) + lights[KEY_LIGHTS + i].value = (( i == lastKeyPressed && (noteLightCounter > 0ul || params[KEY_PARAMS + i].value > 0.5f)) ? 1.0f : 0.0f); + + if (noteLightCounter > 0ul) + noteLightCounter--; + } +}; + + +struct TwelveKeyWidget : ModuleWidget { + + struct OctaveNumDisplayWidget : TransparentWidget { + int *octaveNum; + std::shared_ptr font; + + OctaveNumDisplayWidget() { + 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[2]; + displayStr[0] = 0x30 + (char) *octaveNum; + displayStr[1] = 0; + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + + struct PanelThemeItem : MenuItem { + TwelveKey *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); + + TwelveKey *module = dynamic_cast(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; + } + + + TwelveKeyWidget(TwelveKey *module) : ModuleWidget(module) { + // Main panel from Inkscape + DynamicSVGPanel *panel = new DynamicSVGPanel(); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/TwelveKey.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/TwelveKey_dark.svg"))); + box.size = panel->box.size; + panel->mode = &module->panelTheme; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 365), &module->panelTheme)); + + + + // ****** Top portion (keys) ****** + + static const int offsetKeyLEDx = 12; + static const int offsetKeyLEDy = 41; + + // Black keys + addParam(ParamWidget::create(Vec(30, 40), module, TwelveKey::KEY_PARAMS + 1, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(30+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 1)); + addParam(ParamWidget::create(Vec(71, 40), module, TwelveKey::KEY_PARAMS + 3, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(71+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 3)); + addParam(ParamWidget::create(Vec(154, 40), module, TwelveKey::KEY_PARAMS + 6, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(154+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 6)); + addParam(ParamWidget::create(Vec(195, 40), module, TwelveKey::KEY_PARAMS + 8, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(195+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 8)); + addParam(ParamWidget::create(Vec(236, 40), module, TwelveKey::KEY_PARAMS + 10, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(236+offsetKeyLEDx, 40+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 10)); + + // White keys + addParam(ParamWidget::create(Vec(10, 112), module, TwelveKey::KEY_PARAMS + 0, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(10+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 0)); + addParam(ParamWidget::create(Vec(51, 112), module, TwelveKey::KEY_PARAMS + 2, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(51+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 2)); + addParam(ParamWidget::create(Vec(92, 112), module, TwelveKey::KEY_PARAMS + 4, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(92+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 4)); + addParam(ParamWidget::create(Vec(133, 112), module, TwelveKey::KEY_PARAMS + 5, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(133+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 5)); + addParam(ParamWidget::create(Vec(174, 112), module, TwelveKey::KEY_PARAMS + 7, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(174+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 7)); + addParam(ParamWidget::create(Vec(215, 112), module, TwelveKey::KEY_PARAMS + 9, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(215+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 9)); + addParam(ParamWidget::create(Vec(256, 112), module, TwelveKey::KEY_PARAMS + 11, 0.0, 1.0, 0.0)); + addChild(ModuleLightWidget::create>(Vec(256+offsetKeyLEDx, 112+offsetKeyLEDy), module, TwelveKey::KEY_LIGHTS + 11)); + + + // ****** Bottom portion ****** + + // Column rulers (horizontal positions) + static const int columnRulerL = 30; + static const int columnRulerR = box.size.x - 25 - columnRulerL; + static const int columnRulerM = box.size.x / 2 - 14; + + // Row rulers (vertical positions) + static const int rowRuler0 = 220; + static const int rowRulerStep = 49; + static const int rowRuler1 = rowRuler0 + rowRulerStep; + static const int rowRuler2 = rowRuler1 + rowRulerStep; + + // Left side inputs + + + addInput(createDynamicPort(Vec(columnRulerL, rowRuler0), Port::INPUT, module, TwelveKey::CV_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerL, rowRuler1), Port::INPUT, module, TwelveKey::GATE_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRulerL, rowRuler2), Port::INPUT, module, TwelveKey::OCT_INPUT, &module->panelTheme)); + + // Middle + // Press LED (moved other controls below up by 16 px when removed, to better center) + //addChild(ModuleLightWidget::create>(Vec(columnRulerM + offsetMediumLight, rowRuler0 - 31 + offsetMediumLight), module, TwelveKey::PRESS_LIGHT)); + // Octave display + OctaveNumDisplayWidget *octaveNumDisplay = new OctaveNumDisplayWidget(); + octaveNumDisplay->box.pos = Vec(columnRulerM + 2, rowRuler1 - 27 + vOffsetDisplay); + octaveNumDisplay->box.size = Vec(24, 30);// 1 character + octaveNumDisplay->octaveNum = &module->octaveNum; + addChild(octaveNumDisplay); + + // Octave buttons + addParam(createDynamicParam(Vec(columnRulerM - 20 + offsetCKD6b, rowRuler2 - 26 + offsetCKD6b), module, TwelveKey::OCTDEC_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addParam(createDynamicParam(Vec(columnRulerM + 22 + offsetCKD6b, rowRuler2 - 26 + offsetCKD6b), module, TwelveKey::OCTINC_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + + // Right side outputs + addOutput(createDynamicPort(Vec(columnRulerR, rowRuler0), Port::OUTPUT, module, TwelveKey::CV_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerR, rowRuler1), Port::OUTPUT, module, TwelveKey::GATE_OUTPUT, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRulerR, rowRuler2), Port::OUTPUT, module, TwelveKey::OCT_OUTPUT, &module->panelTheme)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, TwelveKey) { + Model *modelTwelveKey = Model::create("Impromptu Modular", "Twelve-Key", "CTRL - Twelve-Key", CONTROLLER_TAG); + return modelTwelveKey; +} diff --git a/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp b/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp new file mode 100644 index 00000000..2e173858 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/WriteSeq32.cpp @@ -0,0 +1,786 @@ +//*********************************************************************************************** +//Three channel 32-step writable 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 +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct WriteSeq32 : Module { + enum ParamIds { + SHARP_PARAM, + ENUMS(WINDOW_PARAM, 4), + QUANTIZE_PARAM, + ENUMS(GATE_PARAM, 8), + CHANNEL_PARAM, + COPY_PARAM, + PASTE_PARAM, + RUN_PARAM, + WRITE_PARAM, + STEPL_PARAM, + MONITOR_PARAM, + STEPR_PARAM, + STEPS_PARAM, + AUTOSTEP_PARAM, + PASTESYNC_PARAM, + NUM_PARAMS + }; + enum InputIds { + CHANNEL_INPUT,// no longer used + CV_INPUT, + GATE_INPUT, + WRITE_INPUT, + STEPL_INPUT, + STEPR_INPUT, + CLOCK_INPUT, + RESET_INPUT, + // -- 0.6.2 + RUNCV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + ENUMS(CV_OUTPUTS, 3), + ENUMS(GATE_OUTPUTS, 3), + NUM_OUTPUTS + }; + enum LightIds { + ENUMS(WINDOW_LIGHTS, 4), + ENUMS(STEP_LIGHTS, 8), + ENUMS(GATE_LIGHTS, 8), + ENUMS(CHANNEL_LIGHTS, 4), + RUN_LIGHT, + ENUMS(WRITE_LIGHT, 2),// room for GreenRed + PENDING_LIGHT, + NUM_LIGHTS + }; + + // Need to save + int panelTheme = 0; + bool running; + int indexStep; + int indexStepStage; + int indexChannel; + float cv[4][32]; + bool gates[4][32]; + bool resetOnRun; + + // No need to save + int notesPos[8]; // used for rendering notes in LCD_24, 8 gate and 8 step LEDs + float cvCPbuffer[32];// copy paste buffer for CVs + bool gateCPbuffer[32];// copy paste buffer for gates + long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste + int pendingPaste;// 0 = nothing to paste, 1 = paste on clk, 2 = paste on seq, destination channel in next msbits + long clockIgnoreOnReset; + const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) + + + SchmittTrigger clockTrigger; + SchmittTrigger resetTrigger; + SchmittTrigger runningTrigger; + SchmittTrigger channelTrigger; + SchmittTrigger stepLTrigger; + SchmittTrigger stepRTrigger; + SchmittTrigger copyTrigger; + SchmittTrigger pasteTrigger; + SchmittTrigger writeTrigger; + SchmittTrigger gateTriggers[8]; + SchmittTrigger windowTriggers[4]; + + + WriteSeq32() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + void onReset() override { + running = true; + indexStep = 0; + indexStepStage = 0; + indexChannel = 0; + for (int s = 0; s < 32; s++) { + for (int c = 0; c < 4; c++) { + cv[c][s] = 0.0f; + gates[c][s] = true; + } + cvCPbuffer[s] = 0.0f; + gateCPbuffer[s] = true; + } + infoCopyPaste = 0l; + pendingPaste = 0; + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + resetOnRun = false; + } + + void onRandomize() override { + running = true; + indexStep = 0; + indexStepStage = 0; + indexChannel = 0; + for (int s = 0; s < 32; s++) { + for (int c = 0; c < 4; c++) { + cv[c][s] = quantize((randomUniform() *10.0f) - 4.0f, params[QUANTIZE_PARAM].value > 0.5f); + gates[c][s] = (randomUniform() > 0.5f); + } + cvCPbuffer[s] = 0.0f; + gateCPbuffer[s] = true; + } + infoCopyPaste = 0l; + pendingPaste = 0; + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + resetOnRun = false; + } + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // indexStep + json_object_set_new(rootJ, "indexStep", json_integer(indexStep)); + + // indexStepStage + json_object_set_new(rootJ, "indexStepStage", json_integer(indexStepStage)); + + // indexChannel + json_object_set_new(rootJ, "indexChannel", json_integer(indexChannel)); + + // CV + json_t *cvJ = json_array(); + for (int c = 0; c < 4; c++) + for (int s = 0; s < 32; s++) { + json_array_insert_new(cvJ, s + (c<<5), json_real(cv[c][s])); + } + json_object_set_new(rootJ, "cv", cvJ); + + // Gates + json_t *gatesJ = json_array(); + for (int c = 0; c < 4; c++) + for (int s = 0; s < 32; s++) { + json_array_insert_new(gatesJ, s + (c<<5), json_integer((int) gates[c][s]));// json_boolean wil break patches + } + json_object_set_new(rootJ, "gates", gatesJ); + + // resetOnRun + json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); + + return rootJ; + } + + void fromJson(json_t *rootJ) override { + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // indexStep + json_t *indexStepJ = json_object_get(rootJ, "indexStep"); + if (indexStepJ) + indexStep = json_integer_value(indexStepJ); + + // indexStepStage + json_t *indexStepStageJ = json_object_get(rootJ, "indexStepStage"); + if (indexStepStageJ) + indexStepStage = json_integer_value(indexStepStageJ); + + // indexChannel + json_t *indexChannelJ = json_object_get(rootJ, "indexChannel"); + if (indexChannelJ) + indexChannel = json_integer_value(indexChannelJ); + + // CV + json_t *cvJ = json_object_get(rootJ, "cv"); + if (cvJ) { + for (int c = 0; c < 4; c++) + for (int s = 0; s < 32; s++) { + json_t *cvArrayJ = json_array_get(cvJ, s + (c<<5)); + if (cvArrayJ) + cv[c][s] = json_real_value(cvArrayJ); + } + } + + // Gates + json_t *gatesJ = json_object_get(rootJ, "gates"); + if (gatesJ) { + for (int c = 0; c < 4; c++) + for (int s = 0; s < 32; s++) { + json_t *gateJ = json_array_get(gatesJ, s + (c<<5)); + if (gateJ) + gates[c][s] = !!json_integer_value(gateJ);// json_is_true() will break patches + } + } + + // resetOnRun + json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); + if (resetOnRunJ) + resetOnRun = json_is_true(resetOnRunJ); + } + + + inline float quantize(float cv, bool enable) { + return enable ? (roundf(cv * 12.0f) / 12.0f) : cv; + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + static const float copyPasteInfoTime = 0.5f;// seconds + + + //********** Buttons, knobs, switches and inputs ********** + + int numSteps = (int) clamp(roundf(params[STEPS_PARAM].value), 1.0f, 32.0f); + + // Run state button + if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { + running = !running; + //pendingPaste = 0;// no pending pastes across run state toggles + if (running && resetOnRun) { + indexStep = 0; + indexStepStage = 0; + } + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + // Copy button + if (copyTrigger.process(params[COPY_PARAM].value)) { + infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); + for (int s = 0; s < 32; s++) { + cvCPbuffer[s] = cv[indexChannel][s]; + gateCPbuffer[s] = gates[indexChannel][s]; + } + pendingPaste = 0; + } + // Paste button + if (pasteTrigger.process(params[PASTE_PARAM].value)) { + if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 3) { + // Paste realtime, no pending to schedule + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + for (int s = 0; s < 32; s++) { + cv[indexChannel][s] = cvCPbuffer[s]; + gates[indexChannel][s] = gateCPbuffer[s]; + } + pendingPaste = 0; + } + else { + pendingPaste = params[PASTESYNC_PARAM].value > 1.5f ? 2 : 1; + pendingPaste |= indexChannel<<2; // add paste destination channel into pendingPaste + } + } + + // Channel selection button + if (channelTrigger.process(params[CHANNEL_PARAM].value)) { + indexChannel++; + if (indexChannel >= 4) + indexChannel = 0; + } + + // Gate buttons + for (int index8 = 0, iGate = 0; index8 < 8; index8++) { + if (gateTriggers[index8].process(params[GATE_PARAM + index8].value)) { + iGate = ( (indexChannel == 3 ? indexStepStage : indexStep) & 0x18) | index8; + if (iGate < numSteps)// don't toggle gates beyond steps + gates[indexChannel][iGate] = !gates[indexChannel][iGate]; + } + } + + bool canEdit = !running || (indexChannel == 3); + + // Steps knob will not trigger anything in step(), and if user goes lower than current step, lower the index accordingly + if (indexStep >= numSteps) + indexStep = numSteps - 1; + if (indexStepStage >= numSteps) + indexStepStage = numSteps - 1; + + // Write button (must be before StepL and StepR in case route gate simultaneously to Step R and Write for example) + // (write must be to correct step) + if (writeTrigger.process(params[WRITE_PARAM].value + inputs[WRITE_INPUT].value)) { + if (canEdit) { + int index = (indexChannel == 3 ? indexStepStage : indexStep); + // CV + cv[indexChannel][index] = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f); + // Gate + if (inputs[GATE_INPUT].active) + gates[indexChannel][index] = (inputs[GATE_INPUT].value >= 1.0f) ? true : false; + // Autostep + if (params[AUTOSTEP_PARAM].value > 0.5f) { + if (indexChannel == 3) + indexStepStage = moveIndex(indexStepStage, indexStepStage + 1, numSteps); + else + indexStep = moveIndex(indexStep, indexStep + 1, numSteps); + } + } + } + // Step L button + if (stepLTrigger.process(params[STEPL_PARAM].value + inputs[STEPL_INPUT].value)) { + if (canEdit) { + if (indexChannel == 3) + indexStepStage = moveIndex(indexStepStage, indexStepStage - 1, numSteps); + else + indexStep = moveIndex(indexStep, indexStep - 1, numSteps); + } + } + // Step R button + if (stepRTrigger.process(params[STEPR_PARAM].value + inputs[STEPR_INPUT].value)) { + if (canEdit) { + if (indexChannel == 3) + indexStepStage = moveIndex(indexStepStage, indexStepStage + 1, numSteps); + else + indexStep = moveIndex(indexStep, indexStep + 1, numSteps); + } + } + + // Window buttons + for (int i = 0; i < 4; i++) { + if (windowTriggers[i].process(params[WINDOW_PARAM+i].value)) { + if (canEdit) { + if (indexChannel == 3) + indexStepStage = (i<<3) | (indexStepStage&0x7); + else + indexStep = (i<<3) | (indexStep&0x7); + } + } + } + + + //********** Clock and reset ********** + + // Clock + if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { + if (running && clockIgnoreOnReset == 0l) { + indexStep = moveIndex(indexStep, indexStep + 1, numSteps); + + // Pending paste on clock or end of seq + if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep == 0) ) { + int pasteChannel = pendingPaste>>2; + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + for (int s = 0; s < 32; s++) { + cv[pasteChannel][s] = cvCPbuffer[s]; + gates[pasteChannel][s] = gateCPbuffer[s]; + } + pendingPaste = 0; + } + } + } + + // Reset + if (resetTrigger.process(inputs[RESET_INPUT].value)) { + indexStep = 0; + indexStepStage = 0; + pendingPaste = 0; + //indexChannel = 0; + clockTrigger.reset(); + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + + //********** Outputs and lights ********** + + // CV and gate outputs (staging area not used) + if (running) { + for (int i = 0; i < 3; i++) { + outputs[CV_OUTPUTS + i].value = cv[i][indexStep]; + outputs[GATE_OUTPUTS + i].value = (clockTrigger.isHigh() && gates[i][indexStep]) ? 10.0f : 0.0f; + } + } + else { + bool muteGate = false;// (params[WRITE_PARAM].value + params[STEPL_PARAM].value + params[STEPR_PARAM].value) > 0.5f; // set to false if don't want mute gate on button push + for (int i = 0; i < 3; i++) { + // CV + if (params[MONITOR_PARAM].value > 0.5f) + outputs[CV_OUTPUTS + i].value = cv[i][indexStep];// each CV out monitors the current step CV of that channel + else + outputs[CV_OUTPUTS + i].value = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f);// all CV outs monitor the CV in (only current channel will have a gate though) + + // Gate + outputs[GATE_OUTPUTS + i].value = ((i == indexChannel) && !muteGate) ? 10.0f : 0.0f; + } + } + + int index = (indexChannel == 3 ? indexStepStage : indexStep); + // Window lights + for (int i = 0; i < 4; i++) { + lights[WINDOW_LIGHTS + i].value = ((i == (index >> 3))?1.0f:0.0f); + } + // Step and gate lights + for (int index8 = 0, iGate = 0; index8 < 8; index8++) { + lights[STEP_LIGHTS + index8].value = (index8 == (index&0x7)) ? 1.0f : 0.0f; + iGate = (index&0x18) | index8; + lights[GATE_LIGHTS + index8].value = (gates[indexChannel][iGate] && iGate < numSteps) ? 1.0f : 0.0f; + } + + // Channel lights + lights[CHANNEL_LIGHTS + 0].value = (indexChannel == 0) ? 1.0f : 0.0f;// green + lights[CHANNEL_LIGHTS + 1].value = (indexChannel == 1) ? 1.0f : 0.0f;// yellow + lights[CHANNEL_LIGHTS + 2].value = (indexChannel == 2) ? 1.0f : 0.0f;// orange + lights[CHANNEL_LIGHTS + 3].value = (indexChannel == 3) ? 1.0f : 0.0f;// blue + + // Run light + lights[RUN_LIGHT].value = running; + + // Write allowed light + lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; + lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; + + // Pending paste light + lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); + + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; + } +}; + + +struct WriteSeq32Widget : ModuleWidget { + + struct NotesDisplayWidget : TransparentWidget { + WriteSeq32 *module; + std::shared_ptr font; + char text[4]; + + NotesDisplayWidget() { + font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); + } + + void cvToStr(int index8) { + if (module->infoCopyPaste != 0l) { + if (index8 == 0) { + if (module->infoCopyPaste > 0l) + snprintf(text, 4, "COP"); + else + snprintf(text, 4, "PAS"); + } + else if (index8 == 1) { + if (module->infoCopyPaste > 0l) + snprintf(text, 4, "Y "); + else + snprintf(text, 4, "TE "); + } + else { + snprintf(text, 4, " "); + } + } + else { + int index = (module->indexChannel == 3 ? module->indexStepStage : module->indexStep); + if ( ( (index&0x18) |index8) >= (int) clamp(roundf(module->params[WriteSeq32::STEPS_PARAM].value), 1.0f, 32.0f) ) { + text[0] = ' '; + text[1] = ' '; + text[2] = ' '; + } + else { + float cvVal = module->cv[module->indexChannel][index8|(index&0x18)]; + float cvValOffset = cvVal +10.0f;//to properly handle negative note voltages + int indexNote = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); + bool sharp = (module->params[WriteSeq32::SHARP_PARAM].value > 0.5f) ? true : false; + + // note letter + text[0] = sharp ? noteLettersSharp[indexNote] : noteLettersFlat[indexNote]; + + // octave number + int octave = (int) roundf(floorf(cvVal)+4.0f); + if (octave < 0 || octave > 9) + text[1] = (octave > 9) ? ':' : '_'; + else + text[1] = (char) ( 0x30 + octave); + + // sharp/flat + text[2] = ' '; + if (isBlackKey[indexNote] == 1) + text[2] = (sharp ? '\"' : '^' ); + } + } + // end of string + text[3] = 0; + } + + void draw(NVGcontext *vg) override { + NVGcolor textColor = prepareDisplay(vg, &box); + nvgFontFaceId(vg, font->handle); + nvgTextLetterSpacing(vg, -1.5); + + for (int i = 0; i < 8; i++) { + Vec textPos = Vec(module->notesPos[i], 24); + nvgFillColor(vg, nvgTransRGBA(textColor, 16)); + nvgText(vg, textPos.x, textPos.y, "~~~", NULL); + nvgFillColor(vg, textColor); + cvToStr(i); + nvgText(vg, textPos.x, textPos.y, text, NULL); + } + } + }; + + + struct StepsDisplayWidget : TransparentWidget { + float *valueKnob; + std::shared_ptr 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) clamp(roundf(*valueKnob), 1.0f, 32.0f) ); + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + + struct PanelThemeItem : MenuItem { + WriteSeq32 *module; + int theme; + void onAction(EventAction &e) override { + module->panelTheme = theme; + } + void step() override { + rightText = (module->panelTheme == theme) ? "✔" : ""; + } + }; + struct ResetOnRunItem : MenuItem { + WriteSeq32 *module; + void onAction(EventAction &e) override { + module->resetOnRun = !module->resetOnRun; + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + WriteSeq32 *module = dynamic_cast(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); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); + rorItem->module = module; + menu->addChild(rorItem); + + return menu; + } + + + WriteSeq32Widget(WriteSeq32 *module) : ModuleWidget(module) { + // Main panel from Inkscape + DynamicSVGPanel *panel = new DynamicSVGPanel(); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/WriteSeq32.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/WriteSeq32_dark.svg"))); + box.size = panel->box.size; + panel->mode = &module->panelTheme; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 365), &module->panelTheme)); + + // Column rulers (horizontal positions) + static const int columnRuler0 = 25; + static const int columnnRulerStep = 69; + static const int columnRuler1 = columnRuler0 + columnnRulerStep; + static const int columnRuler2 = columnRuler1 + columnnRulerStep; + static const int columnRuler3 = columnRuler2 + columnnRulerStep; + static const int columnRuler4 = columnRuler3 + columnnRulerStep; + static const int columnRuler5 = columnRuler4 + columnnRulerStep - 15; + + // Row rulers (vertical positions) + static const int rowRuler0 = 172; + static const int rowRulerStep = 49; + static const int rowRuler1 = rowRuler0 + rowRulerStep; + static const int rowRuler2 = rowRuler1 + rowRulerStep; + static const int rowRuler3 = rowRuler2 + rowRulerStep + 4; + + + // ****** Top portion ****** + + static const int yRulerTopLEDs = 42; + static const int yRulerTopSwitches = yRulerTopLEDs-11; + + // Autostep, sharp/flat and quantize switches + // Autostep + addParam(ParamWidget::create(Vec(columnRuler0+3+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); + // Sharp/flat + addParam(ParamWidget::create(Vec(columnRuler4+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::SHARP_PARAM, 0.0f, 1.0f, 1.0f)); + // Quantize + addParam(ParamWidget::create(Vec(columnRuler5+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::QUANTIZE_PARAM, 0.0f, 1.0f, 1.0f)); + + // Window LED buttons + static const float wLightsPosX = 140.0f; + static const float wLightsIntX = 35.0f; + for (int i = 0; i < 4; i++) { + addParam(ParamWidget::create(Vec(wLightsPosX + i * wLightsIntX, yRulerTopLEDs - 4.4f), module, WriteSeq32::WINDOW_PARAM + i, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(wLightsPosX + 4.4f + i * wLightsIntX, yRulerTopLEDs), module, WriteSeq32::WINDOW_LIGHTS + i)); + } + + // Prepare 8 positions for step lights, gate lights and notes display + module->notesPos[0] = 9;// this is also used to help line up LCD digits with LEDbuttons and avoid bad horizontal scaling with long str in display + for (int i = 1; i < 8; i++) { + module->notesPos[i] = module->notesPos[i-1] + 46; + } + + // Notes display + NotesDisplayWidget *displayNotes = new NotesDisplayWidget(); + displayNotes->box.pos = Vec(12, 76); + displayNotes->box.size = Vec(381, 30); + displayNotes->module = module; + addChild(displayNotes); + + // Step LEDs (must be done after Notes display such that LED glow will overlay the notes display + static const int yRulerStepLEDs = 65; + for (int i = 0; i < 8; i++) { + addChild(ModuleLightWidget::create>(Vec(module->notesPos[i]+25.0f+1.5f, yRulerStepLEDs), module, WriteSeq32::STEP_LIGHTS + i)); + } + + // Gates LED buttons + static const int yRulerT2 = 119.0f; + for (int i = 0; i < 8; i++) { + addParam(ParamWidget::create(Vec(module->notesPos[i]+25.0f-4.4f, yRulerT2-4.4f), module, WriteSeq32::GATE_PARAM + i, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(module->notesPos[i]+25.0f, yRulerT2), module, WriteSeq32::GATE_LIGHTS + i)); + } + + + // ****** Bottom portion ****** + + // Column 0 + // Channel button + addParam(createDynamicParam(Vec(columnRuler0+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::CHANNEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Channel LEDS + static const int chanLEDoffsetX = 25; + static const int chanLEDoffsetY[4] = {-20, -8, 4, 16}; + addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[0] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 0)); + addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[1] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 1)); + addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[2] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 2)); + addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[3] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 3)); + // Copy/paste switches + addParam(ParamWidget::create(Vec(columnRuler0-10, rowRuler1+offsetTL1105), module, WriteSeq32::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(columnRuler0+20, rowRuler1+offsetTL1105), module, WriteSeq32::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Paste sync (and light) + addParam(ParamWidget::create(Vec(columnRuler0+hOffsetCKSS, rowRuler2+vOffsetCKSSThree), module, WriteSeq32::PASTESYNC_PARAM, 0.0f, 2.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRuler0 + 41, rowRuler2 + 14), module, WriteSeq32::PENDING_LIGHT)); + // Run CV input + addInput(createDynamicPort(Vec(columnRuler0, rowRuler3), Port::INPUT, module, WriteSeq32::RUNCV_INPUT, &module->panelTheme)); + + + // Column 1 + // Step L button + addParam(createDynamicParam(Vec(columnRuler1+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::STEPL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Run LED bezel and light + addParam(ParamWidget::create(Vec(columnRuler1+offsetLEDbezel, rowRuler1+offsetLEDbezel), module, WriteSeq32::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRuler1+offsetLEDbezel+offsetLEDbezelLight, rowRuler1+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq32::RUN_LIGHT)); + // Gate input + addInput(createDynamicPort(Vec(columnRuler1, rowRuler2), Port::INPUT, module, WriteSeq32::GATE_INPUT, &module->panelTheme)); + // Step L input + addInput(createDynamicPort(Vec(columnRuler1, rowRuler3), Port::INPUT, module, WriteSeq32::STEPL_INPUT, &module->panelTheme)); + + + // Column 2 + // Step R button + addParam(createDynamicParam(Vec(columnRuler2+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::STEPR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Write button and light + addParam(createDynamicParam(Vec(columnRuler2+offsetCKD6b, rowRuler1+offsetCKD6b), module, WriteSeq32::WRITE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addChild(ModuleLightWidget::create>(Vec(columnRuler2 -12, rowRuler1 - 12), module, WriteSeq32::WRITE_LIGHT)); + // CV input + addInput(createDynamicPort(Vec(columnRuler2, rowRuler2), Port::INPUT, module, WriteSeq32::CV_INPUT, &module->panelTheme)); + // Step R input + addInput(createDynamicPort(Vec(columnRuler2, rowRuler3), Port::INPUT, module, WriteSeq32::STEPR_INPUT, &module->panelTheme)); + + + // Column 3 + // Steps display + StepsDisplayWidget *displaySteps = new StepsDisplayWidget(); + displaySteps->box.pos = Vec(columnRuler3-7, rowRuler0+vOffsetDisplay); + displaySteps->box.size = Vec(40, 30);// 2 characters + displaySteps->valueKnob = &module->params[WriteSeq32::STEPS_PARAM].value; + addChild(displaySteps); + // Steps knob + addParam(createDynamicParam(Vec(columnRuler3+offsetIMBigKnob, rowRuler1+offsetIMBigKnob), module, WriteSeq32::STEPS_PARAM, 1.0f, 32.0f, 32.0f, &module->panelTheme)); + // Monitor + addParam(ParamWidget::create(Vec(columnRuler3+hOffsetCKSSH, rowRuler2+vOffsetCKSSH), module, WriteSeq32::MONITOR_PARAM, 0.0f, 1.0f, 0.0f)); + // Write input + addInput(createDynamicPort(Vec(columnRuler3, rowRuler3), Port::INPUT, module, WriteSeq32::WRITE_INPUT, &module->panelTheme)); + + + // Column 4 + // Outputs + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler0), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler1), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 1, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler2), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 2, &module->panelTheme)); + // Reset + addInput(createDynamicPort(Vec(columnRuler4, rowRuler3), Port::INPUT, module, WriteSeq32::RESET_INPUT, &module->panelTheme)); + + + // Column 5 + // Gates + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler0), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler1), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 1, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler2), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 2, &module->panelTheme)); + // Clock + addInput(createDynamicPort(Vec(columnRuler5, rowRuler3), Port::INPUT, module, WriteSeq32::CLOCK_INPUT, &module->panelTheme)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, WriteSeq32) { + Model *modelWriteSeq32 = Model::create("Impromptu Modular", "Write-Seq-32", "SEQ - Write-Seq-32", SEQUENCER_TAG); + return modelWriteSeq32; +} + +/*CHANGE LOG + +0.6.7: +no reset on run by default, with switch added in context menu +reset does not revert chan number to 1 +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp b/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp new file mode 100644 index 00000000..78d5759e --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/WriteSeq64.cpp @@ -0,0 +1,852 @@ +//*********************************************************************************************** +//Four channel 64-step writable 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 +//*********************************************************************************************** + + +#include "ImpromptuModular.hpp" +#include "dsp/digital.hpp" + +namespace rack_plugin_ImpromptuModular { + +struct WriteSeq64 : Module { + enum ParamIds { + SHARP_PARAM, + QUANTIZE_PARAM, + GATE_PARAM, + CHANNEL_PARAM, + COPY_PARAM, + PASTE_PARAM, + RUN_PARAM, + WRITE_PARAM, + STEPL_PARAM, + MONITOR_PARAM, + STEPR_PARAM, + STEPS_PARAM, + STEP_PARAM, + AUTOSTEP_PARAM, + RESET_PARAM, + PASTESYNC_PARAM, + NUM_PARAMS + }; + enum InputIds { + CHANNEL_INPUT,// no longer used + CV_INPUT, + GATE_INPUT, + WRITE_INPUT, + STEPL_INPUT, + STEPR_INPUT, + CLOCK12_INPUT, + CLOCK34_INPUT, + RESET_INPUT, + // -- 0.6.2 + RUNCV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + ENUMS(CV_OUTPUTS, 4), + ENUMS(GATE_OUTPUTS, 4), + NUM_OUTPUTS + }; + enum LightIds { + GATE_LIGHT, + RUN_LIGHT, + RESET_LIGHT, + ENUMS(WRITE_LIGHT, 2),// room for GreenRed + PENDING_LIGHT, + NUM_LIGHTS + }; + + // Need to save + int panelTheme = 0; + bool running; + int indexChannel; + int indexStep[5];// [0;63] each + int indexSteps[5];// [1;64] each + float cv[5][64]; + bool gates[5][64]; + bool resetOnRun; + + // No need to save + float cvCPbuffer[64];// copy paste buffer for CVs + float gateCPbuffer[64];// copy paste buffer for gates + int stepsCPbuffer; + long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste + float resetLight = 0.0f; + int pendingPaste;// 0 = nothing to paste, 1 = paste on clk, 2 = paste on seq, destination channel in next msbits + long clockIgnoreOnReset; + const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) + int stepKnob = 0; + int stepsKnob = 0; + + + SchmittTrigger clock12Trigger; + SchmittTrigger clock34Trigger; + SchmittTrigger resetTrigger; + SchmittTrigger runningTrigger; + SchmittTrigger channelTrigger; + SchmittTrigger stepLTrigger; + SchmittTrigger stepRTrigger; + SchmittTrigger copyTrigger; + SchmittTrigger pasteTrigger; + SchmittTrigger writeTrigger; + SchmittTrigger gateTrigger; + + + WriteSeq64() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { + onReset(); + } + + void onReset() override { + running = true; + indexChannel = 0; + for (int c = 0; c < 5; c++) { + indexStep[c] = 0; + indexSteps[c] = 64; + for (int s = 0; s < 64; s++) { + cv[c][s] = 0.0f; + gates[c][s] = true; + } + } + for (int s = 0; s < 64; s++) { + cvCPbuffer[s] = 0.0f; + gateCPbuffer[s] = true; + } + stepsCPbuffer = 64; + infoCopyPaste = 0l; + pendingPaste = 0; + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + resetOnRun = false; + } + + void onRandomize() override { + running = true; + indexChannel = 0; + for (int c = 0; c < 5; c++) { + indexStep[c] = 0; + indexSteps[c] = 64; + for (int s = 0; s < 64; s++) { + cv[c][s] = quantize((randomUniform() *10.0f) - 4.0f, params[QUANTIZE_PARAM].value > 0.5f); + gates[c][s] = (randomUniform() > 0.5f); + } + } + for (int s = 0; s < 64; s++) { + cvCPbuffer[s] = 0.0f; + gateCPbuffer[s] = true; + } + stepsCPbuffer = 64; + infoCopyPaste = 0l; + pendingPaste = 0; + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + resetOnRun = false; + } + + json_t *toJson() override { + json_t *rootJ = json_object(); + + // panelTheme + json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); + + // running + json_object_set_new(rootJ, "running", json_boolean(running)); + + // indexChannel + json_object_set_new(rootJ, "indexChannel", json_integer(indexChannel)); + + // indexStep + json_t *indexStepJ = json_array(); + for (int c = 0; c < 5; c++) + json_array_insert_new(indexStepJ, c, json_integer(indexStep[c])); + json_object_set_new(rootJ, "indexStep", indexStepJ); + + // indexSteps + json_t *indexStepsJ = json_array(); + for (int c = 0; c < 5; c++) + json_array_insert_new(indexStepsJ, c, json_integer(indexSteps[c])); + json_object_set_new(rootJ, "indexSteps", indexStepsJ); + + // CV + json_t *cvJ = json_array(); + for (int c = 0; c < 5; c++) + for (int s = 0; s < 64; s++) { + json_array_insert_new(cvJ, s + (c<<6), json_real(cv[c][s])); + } + json_object_set_new(rootJ, "cv", cvJ); + + // gates + json_t *gatesJ = json_array(); + for (int c = 0; c < 5; c++) + for (int s = 0; s < 64; s++) { + json_array_insert_new(gatesJ, s + (c<<6), json_integer((int) gates[c][s]));// json_boolean wil break patches + } + json_object_set_new(rootJ, "gates", gatesJ); + + // resetOnRun + json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); + + return rootJ; + } + + void fromJson(json_t *rootJ) override { + // panelTheme + json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); + if (panelThemeJ) + panelTheme = json_integer_value(panelThemeJ); + + // running + json_t *runningJ = json_object_get(rootJ, "running"); + if (runningJ) + running = json_is_true(runningJ); + + // indexChannel + json_t *indexChannelJ = json_object_get(rootJ, "indexChannel"); + if (indexChannelJ) + indexChannel = json_integer_value(indexChannelJ); + + // indexStep + json_t *indexStepJ = json_object_get(rootJ, "indexStep"); + if (indexStepJ) + for (int c = 0; c < 5; c++) + { + json_t *indexStepArrayJ = json_array_get(indexStepJ, c); + if (indexStepArrayJ) + indexStep[c] = json_integer_value(indexStepArrayJ); + } + + // indexSteps + json_t *indexStepsJ = json_object_get(rootJ, "indexSteps"); + if (indexStepsJ) + for (int c = 0; c < 5; c++) + { + json_t *indexStepsArrayJ = json_array_get(indexStepsJ, c); + if (indexStepsArrayJ) + indexSteps[c] = json_integer_value(indexStepsArrayJ); + } + + // CV + json_t *cvJ = json_object_get(rootJ, "cv"); + if (cvJ) { + for (int c = 0; c < 5; c++) + for (int i = 0; i < 64; i++) { + json_t *cvArrayJ = json_array_get(cvJ, i + (c<<6)); + if (cvArrayJ) + cv[c][i] = json_real_value(cvArrayJ); + } + } + + // gates + json_t *gatesJ = json_object_get(rootJ, "gates"); + if (gatesJ) { + for (int c = 0; c < 5; c++) + for (int i = 0; i < 64; i++) { + json_t *gateJ = json_array_get(gatesJ, i + (c<<6)); + if (gateJ) + gates[c][i] = !!json_integer_value(gateJ);// json_is_true() will break patches + } + } + + // resetOnRun + json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); + if (resetOnRunJ) + resetOnRun = json_is_true(resetOnRunJ); + } + + inline float quantize(float cv, bool enable) { + return enable ? (roundf(cv * 12.0f) / 12.0f) : cv; + } + + + // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() + void step() override { + static const float copyPasteInfoTime = 0.5f;// seconds + + + //********** Buttons, knobs, switches and inputs ********** + + // Run state button + if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { + running = !running; + //pendingPaste = 0;// no pending pastes across run state toggles + if (running && resetOnRun) { + for (int c = 0; c < 5; c++) + indexStep[c] = 0; + } + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + + // Copy button + if (copyTrigger.process(params[COPY_PARAM].value)) { + infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate()); + for (int s = 0; s < 64; s++) { + cvCPbuffer[s] = cv[indexChannel][s]; + gateCPbuffer[s] = gates[indexChannel][s]; + } + stepsCPbuffer = indexSteps[indexChannel]; + pendingPaste = 0; + } + // Paste button + if (pasteTrigger.process(params[PASTE_PARAM].value)) { + if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 4) { + // Paste realtime, no pending to schedule + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + for (int s = 0; s < 64; s++) { + cv[indexChannel][s] = cvCPbuffer[s]; + gates[indexChannel][s] = gateCPbuffer[s]; + } + indexSteps[indexChannel] = stepsCPbuffer; + if (indexStep[indexChannel] >= stepsCPbuffer) + indexStep[indexChannel] = stepsCPbuffer - 1; + pendingPaste = 0; + } + else { + pendingPaste = params[PASTESYNC_PARAM].value > 1.5f ? 2 : 1; + pendingPaste |= indexChannel<<2; // add paste destination channel into pendingPaste + } + } + + // Channel selection button + if (channelTrigger.process(params[CHANNEL_PARAM].value)) { + indexChannel++; + if (indexChannel >= 5) + indexChannel = 0; + } + + // Gate button + if (gateTrigger.process(params[GATE_PARAM].value)) { + gates[indexChannel][indexStep[indexChannel]] = !gates[indexChannel][indexStep[indexChannel]]; + } + + bool canEdit = !running || (indexChannel == 4); + + // Steps knob + float stepsParamValue = params[STEPS_PARAM].value; + int newStepsKnob = (int)roundf(stepsParamValue * 10.0f); + if (stepsParamValue == 0.0f)// true when constructor or fromJson() occured + stepsKnob = newStepsKnob; + if (newStepsKnob != stepsKnob) { + if (abs(newStepsKnob - stepsKnob) <= 3) // avoid discontinuous step (initialize for example) + indexSteps[indexChannel] = clamp( indexSteps[indexChannel] + newStepsKnob - stepsKnob, 1, 64); + stepsKnob = newStepsKnob; + } + // Step knob + float stepParamValue = params[STEP_PARAM].value; + int newStepKnob = (int)roundf(stepParamValue * 10.0f); + if (stepsParamValue == 0.0f)// true when constructor or fromJson() occured + stepKnob = newStepKnob; + if (newStepKnob != stepKnob) { + if (canEdit && (abs(newStepKnob - stepKnob) <= 3) ) // avoid discontinuous step (initialize for example) + indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] + newStepKnob - stepKnob, indexSteps[indexChannel]); + stepKnob = newStepKnob;// must do this step whether running or not + } + // If steps knob goes down past step, step knob will not get triggered above, so reduce accordingly + for (int c = 0; c < 5; c++) + if (indexStep[c] >= indexSteps[c]) + indexStep[c] = indexSteps[c] - 1; + + // Write button and input (must be before StepL and StepR in case route gate simultaneously to Step R and Write for example) + // (write must be to correct step) + if (writeTrigger.process(params[WRITE_PARAM].value + inputs[WRITE_INPUT].value)) { + if (canEdit) { + // CV + cv[indexChannel][indexStep[indexChannel]] = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f); + // Gate + if (inputs[GATE_INPUT].active) + gates[indexChannel][indexStep[indexChannel]] = (inputs[GATE_INPUT].value >= 1.0f) ? true : false; + // Autostep + if (params[AUTOSTEP_PARAM].value > 0.5f) + indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] + 1, indexSteps[indexChannel]); + } + } + // Step L button + if (stepLTrigger.process(params[STEPL_PARAM].value + inputs[STEPL_INPUT].value)) { + if (canEdit) { + indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] - 1, indexSteps[indexChannel]); + } + } + // Step R button + if (stepRTrigger.process(params[STEPR_PARAM].value + inputs[STEPR_INPUT].value)) { + if (canEdit) { + indexStep[indexChannel] = moveIndex(indexStep[indexChannel], indexStep[indexChannel] + 1, indexSteps[indexChannel]); + } + } + + + //********** Clock and reset ********** + + // Clock + bool clk12step = clock12Trigger.process(inputs[CLOCK12_INPUT].value); + bool clk34step = ((!inputs[CLOCK34_INPUT].active) && clk12step) || + clock34Trigger.process(inputs[CLOCK34_INPUT].value); + if (running && clockIgnoreOnReset == 0l) { + if (clk12step) { + indexStep[0] = moveIndex(indexStep[0], indexStep[0] + 1, indexSteps[0]); + indexStep[1] = moveIndex(indexStep[1], indexStep[1] + 1, indexSteps[1]); + } + if (clk34step) { + indexStep[2] = moveIndex(indexStep[2], indexStep[2] + 1, indexSteps[2]); + indexStep[3] = moveIndex(indexStep[3], indexStep[3] + 1, indexSteps[3]); + } + + // Pending paste on clock or end of seq + if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep[indexChannel] == 0) ) { + if ( (clk12step && (indexChannel == 0 || indexChannel == 1)) || + (clk34step && (indexChannel == 2 || indexChannel == 3)) ) { + infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate()); + int pasteChannel = pendingPaste>>2; + for (int s = 0; s < 64; s++) { + cv[pasteChannel][s] = cvCPbuffer[s]; + gates[pasteChannel][s] = gateCPbuffer[s]; + } + indexSteps[pasteChannel] = stepsCPbuffer; + if (indexStep[pasteChannel] >= stepsCPbuffer) + indexStep[pasteChannel] = stepsCPbuffer - 1; + pendingPaste = 0; + } + } + } + + // Reset + if (resetTrigger.process(inputs[RESET_INPUT].value + params[RESET_PARAM].value)) { + for (int t = 0; t < 5; t++) + indexStep[t] = 0; + resetLight = 1.0f; + pendingPaste = 0; + //indexChannel = 0; + clock12Trigger.reset(); + clock34Trigger.reset(); + clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); + } + else + resetLight -= (resetLight / lightLambda) * engineGetSampleTime(); + + + //********** Outputs and lights ********** + + // CV and gate outputs (staging area not used) + if (running) { + bool clockHigh = false; + for (int i = 0; i < 4; i++) { + // CV + outputs[CV_OUTPUTS + i].value = cv[i][indexStep[i]]; + + // Gate + clockHigh = i < 2 ? clock12Trigger.isHigh() : clock34Trigger.isHigh(); + outputs[GATE_OUTPUTS + i].value = (clockHigh && gates[i][indexStep[i]]) ? 10.0f : 0.0f; + } + } + else { + bool muteGate = false;// (params[WRITE_PARAM].value + params[STEPL_PARAM].value + params[STEPR_PARAM].value) > 0.5f; // set to false if don't want mute gate on button push + for (int i = 0; i < 4; i++) { + // CV + if (params[MONITOR_PARAM].value > 0.5f) + outputs[CV_OUTPUTS + i].value = cv[i][indexStep[i]];// each CV out monitors the current step CV of that channel + else + outputs[CV_OUTPUTS + i].value = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f);// all CV outs monitor the CV in (only current channel will have a gate though) + + // Gate + outputs[GATE_OUTPUTS + i].value = ((i == indexChannel) && !muteGate) ? 10.0f : 0.0f; + } + } + + // Gate light + lights[GATE_LIGHT].value = gates[indexChannel][indexStep[indexChannel]] ? 1.0f : 0.0f; + + // Reset light + lights[RESET_LIGHT].value = resetLight; + + // Run light + lights[RUN_LIGHT].value = running; + + // Write allowed light + lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; + lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; + + // Pending paste light + lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); + + if (infoCopyPaste != 0l) { + if (infoCopyPaste > 0l) + infoCopyPaste --; + if (infoCopyPaste < 0l) + infoCopyPaste ++; + } + if (clockIgnoreOnReset > 0l) + clockIgnoreOnReset--; + } +}; + + +struct WriteSeq64Widget : ModuleWidget { + + struct NoteDisplayWidget : TransparentWidget { + WriteSeq64 *module; + std::shared_ptr font; + char text[7]; + + NoteDisplayWidget() { + font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); + } + + void cvToStr(void) { + float cvVal = module->cv[module->indexChannel][module->indexStep[module->indexChannel]]; + if (module->infoCopyPaste != 0l) { + if (module->infoCopyPaste > 0l) {// if copy then display "Copy" + snprintf(text, 7, "COPY"); + } + else {// paste then display "Paste" + snprintf(text, 7, "PASTE"); + } + } + else { + if (module->params[WriteSeq64::SHARP_PARAM].value > 0.5f) {// show notes + float cvValOffset = cvVal +10.0f;// to properly handle negative note voltages + int indexNote = clamp( (int)roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0, 11); + bool sharp = (module->params[WriteSeq64::SHARP_PARAM].value < 1.5f) ? true : false; + + text[0] = ' '; + + // note letter + text[1] = sharp ? noteLettersSharp[indexNote] : noteLettersFlat[indexNote]; + + // octave number + int octave = (int) roundf(floorf(cvVal)+4.0f); + if (octave < 0 || octave > 9) + text[2] = (octave > 9) ? ':' : '_'; + else + text[2] = (char) ( 0x30 + octave); + + // sharp/flat + text[3] = ' '; + if (isBlackKey[indexNote] == 1) + text[3] = (sharp ? '\"' : '^' ); + + // end of string + text[4] = 0; + } + else {// show volts + float cvValPrint = fabs(cvVal); + cvValPrint = (cvValPrint > 9.999f) ? 9.999f : cvValPrint; + snprintf(text, 7, " %4.3f", cvValPrint);// Four-wide, three positions after the decimal, left-justified + text[0] = (cvVal<0.0f) ? '-' : ' '; + text[2] = ','; + } + } + } + + void draw(NVGcontext *vg) override { + NVGcolor textColor = prepareDisplay(vg, &box); + nvgFontFaceId(vg, font->handle); + nvgTextLetterSpacing(vg, -1.5); + + Vec textPos = Vec(6, 24); + nvgFillColor(vg, nvgTransRGBA(textColor, 16)); + nvgText(vg, textPos.x, textPos.y, "~~~~~~", NULL); + nvgFillColor(vg, textColor); + cvToStr(); + nvgText(vg, textPos.x, textPos.y, text, NULL); + } + }; + + + struct StepsDisplayWidget : TransparentWidget { + WriteSeq64 *module; + std::shared_ptr 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) module->indexSteps[module->indexChannel]); + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + + struct StepDisplayWidget : TransparentWidget { + WriteSeq64 *module; + std::shared_ptr font; + + StepDisplayWidget() { + 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) module->indexStep[module->indexChannel] + 1); + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + + struct ChannelDisplayWidget : TransparentWidget { + int *indexTrack; + std::shared_ptr font; + + ChannelDisplayWidget() { + 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[2]; + displayStr[0] = 0x30 + (char) (*indexTrack + 1); + displayStr[1] = 0; + nvgText(vg, textPos.x, textPos.y, displayStr, NULL); + } + }; + + + struct PanelThemeItem : MenuItem { + WriteSeq64 *module; + int theme; + void onAction(EventAction &e) override { + module->panelTheme = theme; + } + void step() override { + rightText = (module->panelTheme == theme) ? "✔" : ""; + } + }; + struct ResetOnRunItem : MenuItem { + WriteSeq64 *module; + void onAction(EventAction &e) override { + module->resetOnRun = !module->resetOnRun; + } + }; + Menu *createContextMenu() override { + Menu *menu = ModuleWidget::createContextMenu(); + + MenuLabel *spacerLabel = new MenuLabel(); + menu->addChild(spacerLabel); + + WriteSeq64 *module = dynamic_cast(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); + + menu->addChild(new MenuLabel());// empty line + + MenuLabel *settingsLabel = new MenuLabel(); + settingsLabel->text = "Settings"; + menu->addChild(settingsLabel); + + ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); + rorItem->module = module; + menu->addChild(rorItem); + + return menu; + } + + + WriteSeq64Widget(WriteSeq64 *module) : ModuleWidget(module) { + // Main panel from Inkscape + DynamicSVGPanel *panel = new DynamicSVGPanel(); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/WriteSeq64.svg"))); + panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/WriteSeq64_dark.svg"))); + box.size = panel->box.size; + panel->mode = &module->panelTheme; + addChild(panel); + + // Screws + addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); + addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); + addChild(createDynamicScrew(Vec(box.size.x-30, 365), &module->panelTheme)); + + + // ****** Top portion ****** + + static const int rowRulerT0 = 56; + static const int columnRulerT0 = 22; + static const int columnRulerT1 = columnRulerT0 + 58; + static const int columnRulerT2 = columnRulerT1 + 50; + static const int columnRulerT3 = columnRulerT2 + 43; + static const int columnRulerT4 = columnRulerT3 + 175; + + // Channel display + ChannelDisplayWidget *channelTrack = new ChannelDisplayWidget(); + channelTrack->box.pos = Vec(columnRulerT0+1, rowRulerT0+vOffsetDisplay); + channelTrack->box.size = Vec(24, 30);// 1 character + channelTrack->indexTrack = &module->indexChannel; + addChild(channelTrack); + // Step display + StepDisplayWidget *displayStep = new StepDisplayWidget(); + displayStep->box.pos = Vec(columnRulerT1-8, rowRulerT0+vOffsetDisplay); + displayStep->box.size = Vec(40, 30);// 2 characters + displayStep->module = module; + addChild(displayStep); + // Gate LED + addChild(ModuleLightWidget::create>(Vec(columnRulerT2+offsetLEDbutton+offsetLEDbuttonLight, rowRulerT0+offsetLEDbutton+offsetLEDbuttonLight), module, WriteSeq64::GATE_LIGHT)); + // Note display + NoteDisplayWidget *displayNote = new NoteDisplayWidget(); + displayNote->box.pos = Vec(columnRulerT3, rowRulerT0+vOffsetDisplay); + displayNote->box.size = Vec(98, 30);// 6 characters (ex.: "-1,234") + displayNote->module = module; + addChild(displayNote); + // Volt/sharp/flat switch + addParam(ParamWidget::create(Vec(columnRulerT3+114+hOffsetCKSS, rowRulerT0+vOffsetCKSSThree), module, WriteSeq64::SHARP_PARAM, 0.0f, 2.0f, 1.0f)); + // Steps display + StepsDisplayWidget *displaySteps = new StepsDisplayWidget(); + displaySteps->box.pos = Vec(columnRulerT4-7, rowRulerT0+vOffsetDisplay); + displaySteps->box.size = Vec(40, 30);// 2 characters + displaySteps->module = module; + addChild(displaySteps); + + static const int rowRulerT1 = 105; + + // Channel button + addParam(createDynamicParam(Vec(columnRulerT0+offsetCKD6b, rowRulerT1+offsetCKD6b), module, WriteSeq64::CHANNEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Step knob + addParam(createDynamicParam(Vec(columnRulerT1+offsetIMBigKnob, rowRulerT1+offsetIMBigKnob), module, WriteSeq64::STEP_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); + // Gate button + addParam(createDynamicParam(Vec(columnRulerT2-1+offsetCKD6b, rowRulerT1+offsetCKD6b), module, WriteSeq64::GATE_PARAM , 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Autostep + addParam(ParamWidget::create(Vec(columnRulerT2+53+hOffsetCKSS, rowRulerT1+6+vOffsetCKSS), module, WriteSeq64::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); + // Quantize switch + addParam(ParamWidget::create(Vec(columnRulerT2+110+hOffsetCKSS, rowRulerT1+6+vOffsetCKSS), module, WriteSeq64::QUANTIZE_PARAM, 0.0f, 1.0f, 1.0f)); + // Reset LED bezel and light + addParam(ParamWidget::create(Vec(columnRulerT2+164+offsetLEDbezel, rowRulerT1+6+offsetLEDbezel), module, WriteSeq64::RESET_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRulerT2+164+offsetLEDbezel+offsetLEDbezelLight, rowRulerT1+6+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq64::RESET_LIGHT)); + // Steps knob + addParam(createDynamicParam(Vec(columnRulerT4+offsetIMBigKnob, rowRulerT1+offsetIMBigKnob), module, WriteSeq64::STEPS_PARAM, -INFINITY, INFINITY, 0.0f, &module->panelTheme)); + + + // ****** Bottom portion ****** + + // Column rulers (horizontal positions) + static const int columnRuler0 = 25; + static const int columnnRulerStep = 69; + static const int columnRuler1 = columnRuler0 + columnnRulerStep; + static const int columnRuler2 = columnRuler1 + columnnRulerStep; + static const int columnRuler3 = columnRuler2 + columnnRulerStep; + static const int columnRuler4 = columnRuler3 + columnnRulerStep; + static const int columnRuler5 = columnRuler4 + columnnRulerStep - 15; + + // Row rulers (vertical positions) + static const int rowRuler0 = 172; + static const int rowRulerStep = 49; + static const int rowRuler1 = rowRuler0 + rowRulerStep; + static const int rowRuler2 = rowRuler1 + rowRulerStep; + static const int rowRuler3 = rowRuler2 + rowRulerStep; + + // Column 0 + // Copy/paste switches + addParam(ParamWidget::create(Vec(columnRuler0-10, rowRuler0+offsetTL1105), module, WriteSeq64::COPY_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(columnRuler0+20, rowRuler0+offsetTL1105), module, WriteSeq64::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); + // Paste sync (and light) + addParam(ParamWidget::create(Vec(columnRuler0+hOffsetCKSS, rowRuler1+vOffsetCKSSThree), module, WriteSeq64::PASTESYNC_PARAM, 0.0f, 2.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRuler0 + 41, rowRuler1 + 14), module, WriteSeq64::PENDING_LIGHT)); + // Gate input + addInput(createDynamicPort(Vec(columnRuler0, rowRuler2), Port::INPUT, module, WriteSeq64::GATE_INPUT, &module->panelTheme)); + // Run CV input + addInput(createDynamicPort(Vec(columnRuler0, rowRuler3), Port::INPUT, module, WriteSeq64::RUNCV_INPUT, &module->panelTheme)); + + + // Column 1 + // Step L button + addParam(createDynamicParam(Vec(columnRuler1+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq64::STEPL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Run LED bezel and light + addParam(ParamWidget::create(Vec(columnRuler1+offsetLEDbezel, rowRuler1+offsetLEDbezel), module, WriteSeq64::RUN_PARAM, 0.0f, 1.0f, 0.0f)); + addChild(ModuleLightWidget::create>(Vec(columnRuler1+offsetLEDbezel+offsetLEDbezelLight, rowRuler1+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq64::RUN_LIGHT)); + // CV input + addInput(createDynamicPort(Vec(columnRuler1, rowRuler2), Port::INPUT, module, WriteSeq64::CV_INPUT, &module->panelTheme)); + // Step L input + addInput(createDynamicPort(Vec(columnRuler1, rowRuler3), Port::INPUT, module, WriteSeq64::STEPL_INPUT, &module->panelTheme)); + + + // Column 2 + // Step R button + addParam(createDynamicParam(Vec(columnRuler2+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq64::STEPR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + // Write button and light + addParam(createDynamicParam(Vec(columnRuler2+offsetCKD6b, rowRuler1+offsetCKD6b), module, WriteSeq64::WRITE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); + addChild(ModuleLightWidget::create>(Vec(columnRuler2 -12, rowRuler1 - 12), module, WriteSeq64::WRITE_LIGHT)); + // Monitor + addParam(ParamWidget::create(Vec(columnRuler2+hOffsetCKSSH, rowRuler2+vOffsetCKSSH), module, WriteSeq64::MONITOR_PARAM, 0.0f, 1.0f, 0.0f)); + // Step R input + addInput(createDynamicPort(Vec(columnRuler2, rowRuler3), Port::INPUT, module, WriteSeq64::STEPR_INPUT, &module->panelTheme)); + + + // Column 3 + // Clocks + addInput(createDynamicPort(Vec(columnRuler3, rowRuler0), Port::INPUT, module, WriteSeq64::CLOCK12_INPUT, &module->panelTheme)); + addInput(createDynamicPort(Vec(columnRuler3, rowRuler1), Port::INPUT, module, WriteSeq64::CLOCK34_INPUT, &module->panelTheme)); + // Reset + addInput(createDynamicPort(Vec(columnRuler3, rowRuler2), Port::INPUT, module, WriteSeq64::RESET_INPUT, &module->panelTheme)); + // Write input + addInput(createDynamicPort(Vec(columnRuler3, rowRuler3), Port::INPUT, module, WriteSeq64::WRITE_INPUT, &module->panelTheme)); + + + // Column 4 (CVs) + // Outputs + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler0), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler1), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 1, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler2), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 2, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler4, rowRuler3), Port::OUTPUT, module, WriteSeq64::CV_OUTPUTS + 3, &module->panelTheme)); + + + // Column 5 (Gates) + // Gates + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler0), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 0, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler1), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 1, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler2), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 2, &module->panelTheme)); + addOutput(createDynamicPort(Vec(columnRuler5, rowRuler3), Port::OUTPUT, module, WriteSeq64::GATE_OUTPUTS + 3, &module->panelTheme)); + } +}; + +} // namespace rack_plugin_ImpromptuModular + +using namespace rack_plugin_ImpromptuModular; + +RACK_PLUGIN_MODEL_INIT(ImpromptuModular, WriteSeq64) { + Model *modelWriteSeq64 = Model::create("Impromptu Modular", "Write-Seq-64", "SEQ - Write-Seq-64", SEQUENCER_TAG); + return modelWriteSeq64; +} + +/*CHANGE LOG + +0.6.7: +no reset on run by default, with switch added in context menu +reset does not revert chan number to 1 +*/ diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/Binasc.cpp b/plugins/community/repos/ImpromptuModular/src/midifile/Binasc.cpp new file mode 100644 index 00000000..32995378 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/Binasc.cpp @@ -0,0 +1,1968 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program. +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src-library/Binasc.cpp +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// description: Interface to convert bytes between binary and ASCII forms. +// + +#include "Binasc.h" + +#include +#include + + +namespace smf { + +////////////////////////////// +// +// Binasc::Binasc -- Constructor: set the default option values. +// + +Binasc::Binasc(void) { + m_bytesQ = 1; // for printing HEX bytes when converting to ASCII + m_commentsQ = 0; // for printing text comments when converting to ASCII + m_midiQ = 0; // for printing ASCII as parsed MIDI file. + m_maxLineLength = 75; + m_maxLineBytes = 25; +} + + + +////////////////////////////// +// +// Binasc::~Binasc -- Destructor. +// + +Binasc::~Binasc() { + // do nothing +} + + + +////////////////////////////// +// +// Binasc::setLineLength -- Set the maximum length of a line when converting +// binary content into ASCII bytes. If the input size is less than one, +// set to the default value of 75 characters per line. +// + +int Binasc::setLineLength(int length) { + if (length < 1) { + m_maxLineLength = 75; + } else { + m_maxLineLength = length; + } + return m_maxLineLength; +} + + + +////////////////////////////// +// +// Binasc::getLineLength -- Set the maximum length of a line when converting +// binary content into ASCII bytes. +// + +int Binasc::getLineLength(void) { + return m_maxLineLength; +} + + + +////////////////////////////// +// +// Binasc::setLineBytes -- Set the maximum number of hex bytes in ASCII output. +// If the input size is less than one, set to the default value of 25 +// hex bytes per line. +// + +int Binasc::setLineBytes(int length) { + if (length < 1) { + m_maxLineBytes = 25; + } else { + m_maxLineBytes = length; + } + return m_maxLineBytes; +} + + + +////////////////////////////// +// +// Binasc::getLineBytes -- Get the maximum number of hex bytes in ASCII output. +// + +int Binasc::getLineBytes(void) { + return m_maxLineLength; +} + + + +////////////////////////////// +// +// Binasc::setComments -- Display or not display printable characters +// as comments when converting binary files to ASCII byte codes. +// + +void Binasc::setComments(int state) { + m_commentsQ = state ? 1 : 0; +} + + +void Binasc::setCommentsOn(void) { + setComments(true); +} + + +void Binasc::setCommentsOff(void) { + setComments(false); +} + + + +////////////////////////////// +// +// Binasc::getComments -- Get the comment display style for +// showing comments in ASCII output; +// + +int Binasc::getComments(void) { + return m_commentsQ; +} + + + +////////////////////////////// +// +// Binasc::setBytes -- Display or not display hex codes (only +// print ASCII printable characters). +// + +void Binasc::setBytes(int state) { + m_bytesQ = state ? 1 : 0; +} + + +void Binasc::setBytesOn(void) { + setBytes(true); +} + + +void Binasc::setBytesOff(void) { + setBytes(false); +} + + +////////////////////////////// +// +// Binasc::getBytes -- Get hex byte display status. +// + +int Binasc::getBytes(void) { + return m_bytesQ; +} + + +////////////////////////////// +// +// Binasc::setMidi -- Display or not display parsed MIDI data. +// + +void Binasc::setMidi(int state) { + m_midiQ = state ? 1 : 0; +} + + +void Binasc::setMidiOn(void) { + setMidi(true); +} + + +void Binasc::setMidiOff(void) { + setMidi(false); +} + + + +////////////////////////////// +// +// Binasc::getMidi -- Get the MIDI file printing style option state. +// + +int Binasc::getMidi(void) { + return m_midiQ; +} + + + +////////////////////////////// +// +// Binasc::writeToBinary -- Convert an ASCII representation of bytes into +// the binary file that it describes. Returns 0 if there was a problem +// otherwise returns 1. +// + +int Binasc::writeToBinary(const std::string& outfile, + const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = writeToBinary(output, input); + input.close(); + output.close(); + return status; +} + + +int Binasc::writeToBinary(const std::string& outfile, std::istream& input) { + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = writeToBinary(output, input); + output.close(); + return status; +} + + +int Binasc::writeToBinary(std::ostream& out, const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = writeToBinary(out, input); + input.close(); + return status; +} + + +int Binasc::writeToBinary(std::ostream& out, std::istream& input) { + char inputLine[1024] = {0}; // current line being processed + int lineNum = 0; // current line number + + input.getline(inputLine, 1024, '\n'); + lineNum++; + while (!input.eof()) { + int status = processLine(out, inputLine, lineNum); + if (!status) { + return 0; + } + input.getline(inputLine, 1024, '\n'); + lineNum++; + } + return 1; +} + + + +////////////////////////////// +// +// Binasc::readFromBinary -- convert an ASCII representation of bytes into +// the binary file that it describes. +// + +int Binasc::readFromBinary(const std::string& outfile, const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = readFromBinary(output, input); + input.close(); + output.close(); + return status; +} + + +int Binasc::readFromBinary(const std::string& outfile, std::istream& input) { + std::ofstream output; + output.open(outfile.c_str()); + if (!output.is_open()) { + std::cerr << "Cannot open " << outfile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = readFromBinary(output, input); + output.close(); + return status; +} + + +int Binasc::readFromBinary(std::ostream& out, const std::string& infile) { + std::ifstream input; + input.open(infile.c_str()); + if (!input.is_open()) { + std::cerr << "Cannot open " << infile + << " for reading in binasc." << std::endl; + return 0; + } + + int status = readFromBinary(out, input); + input.close(); + return status; +} + + +int Binasc::readFromBinary(std::ostream& out, std::istream& input) { + int status; + if (m_midiQ) { + status = outputStyleMidi(out, input); + } else if (!m_bytesQ) { + status = outputStyleAscii(out, input); + } else if (m_bytesQ && m_commentsQ) { + status = outputStyleBoth(out, input); + } else { + status = outputStyleBinary(out, input); + } + return status; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// protected functions -- +// + +////////////////////////////// +// +// Binasc::outputStyleAscii -- read an input file and output bytes in ascii +// form, not displaying any blank lines. Output words are not +// broken unless they are longer than 75 characters. +// + +int Binasc::outputStyleAscii(std::ostream& out, std::istream& input) { + uchar outputWord[256] = {0}; // storage for current word + int index = 0; // current length of word + int lineCount = 0; // current length of line + int type = 0; // 0=space, 1=printable + uchar ch; // current input byte + + ch = input.get(); + while (!input.eof()) { + int lastType = type; + type = (isprint(ch) && !isspace(ch)) ? 1 : 0; + + if ((type == 1) && (lastType == 0)) { + // start of a new word. check where to put old word + if (index + lineCount >= m_maxLineLength) { // put on next line + outputWord[index] = '\0'; + out << '\n' << outputWord; + lineCount = index; + index = 0; + } else { // put on current line + outputWord[index] = '\0'; + if (lineCount != 0) { + out << ' '; + lineCount++; + } + out << outputWord; + lineCount += index; + index = 0; + } + } + if (type == 1) { + outputWord[index++] = ch; + } + ch = input.get(); + } + + if (index != 0) { + out << std::endl; + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::outputStyleBinary -- read an input binary file and output bytes +// in ascii form, hexadecimal numbers only. +// + +int Binasc::outputStyleBinary(std::ostream& out, std::istream& input) { + int currentByte = 0; // current byte output in line + uchar ch; // current input byte + + ch = input.get(); + if (input.eof()) { + std::cerr << "End of the file right away!" << std::endl; + return 0; + } + + while (!input.eof()) { + if (ch < 0x10) { + out << '0'; + } + out << std::hex << (int)ch << ' '; + currentByte++; + if (currentByte >= m_maxLineBytes) { + out << '\n'; + currentByte = 0; + } + ch = input.get(); + } + + if (currentByte != 0) { + out << std::endl; + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::outputStyleBoth -- read an input file and output bytes in ASCII +// form with both hexadecimal numbers and ascii representation +// + +int Binasc::outputStyleBoth(std::ostream& out, std::istream& input) { + uchar asciiLine[256] = {0}; // storage for output line + int currentByte = 0; // current byte output in line + int index = 0; // current character in asciiLine + uchar ch; // current input byte + + ch = input.get(); + while (!input.eof()) { + if (index == 0) { + asciiLine[index++] = ';'; + out << ' '; + } + if (ch < 0x10) { + out << '0'; + } + out << std::hex << (int)ch << ' '; + currentByte++; + + asciiLine[index++] = ' '; + if (isprint(ch)) { + asciiLine[index++] = ch; + } else { + asciiLine[index++] = ' '; + } + asciiLine[index++] = ' '; + + if (currentByte >= m_maxLineBytes) { + out << '\n'; + asciiLine[index] = '\0'; + out << asciiLine << "\n\n"; + currentByte = 0; + index = 0; + } + ch = input.get(); + } + + if (currentByte != 0) { + out << '\n'; + asciiLine[index] = '\0'; + out << asciiLine << '\n' << std::endl; + } + + return 1; +} + + + +/////////////////////////////// +// +// processLine -- read a line of input and output any specified bytes +// + +int Binasc::processLine(std::ostream& out, const std::string& input, + int lineCount) { + int status = 1; + int i = 0; + int length = (int)input.size(); + std::string word; + while (i 2)) { + status = processBinaryWord(out, word, lineCount); + } else { + status = processHexWord(out, word, lineCount); + } + } + + if (status == 0) { + return 0; + } + + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::getWord -- extract a sub string, stopping at any of the given +// terminator characters. +// + +int Binasc::getWord(std::string& word, const std::string& input, + const std::string& terminators, int index) { + word.resize(0); + int i = index; + int escape = 0; + int ecount = 0; + if (terminators.find('"') != std::string::npos) { + escape = 1; + } + while (i < (int)input.size()) { + if (escape && input[i] == '\"') { + ecount++; + i++; + if (ecount >= 2) { + break; + } + } + if (escape && (i<(int)input.size()-1) && (input[i] == '\\') + && (input[i+1] == '"')) { + word.push_back(input[i+1]); + i += 2; + } else if (terminators.find(input[i]) == std::string::npos) { + word.push_back(input[i]); + i++; + } else { + i++; + return i; + } + } + return i; +} + + + +/////////////////////////////// +// +// Binasc::getVLV -- read a Variable-Length Value from the file +// + +int Binasc::getVLV(std::istream& infile, int& trackbytes) { + int output = 0; + uchar ch; + infile.read((char*)&ch, 1); + trackbytes++; + output = (output << 7) | (0x7f & ch); + while (ch >= 0x80) { + infile.read((char*)&ch, 1); + trackbytes++; + output = (output << 7) | (0x7f & ch); + } + return output; +} + + + +////////////////////////////// +// +// Binasc::readMidiEvent -- Read a delta time and then a MIDI message +// (or meta message). Returns 1 if not end-of-track meta message; +// 0 otherwise. +// + +int Binasc::readMidiEvent(std::ostream& out, std::istream& infile, + int& trackbytes, int& command) { + + // Read and print Variable Length Value for delta ticks + int vlv = getVLV(infile, trackbytes); + + std::stringstream output; + + output << "v" << std::dec << vlv << "\t"; + + std::string comment; + + int status = 1; + uchar ch; + char byte1, byte2; + infile.read((char*)&ch, 1); + trackbytes++; + if (ch < 0x80) { + // running status: command byte is previous one in data stream + output << " "; + } else { + // midi command byte + output << std::hex << (int)ch; + command = ch; + infile.read((char*)&ch, 1); + trackbytes++; + } + byte1 = ch; + switch (command & 0xf0) { + case 0x80: // note-off: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "note-off " + keyToPitchName(byte1); + } + break; + case 0x90: // note-on: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + if (byte2 == 0) { + comment += "note-off " + keyToPitchName(byte1); + } else { + comment += "note-on " + keyToPitchName(byte1); + } + } + break; + case 0xA0: // aftertouch: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "after-touch"; + } + break; + case 0xB0: // continuous controller: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "controller"; + } + break; + case 0xE0: // pitch-bend: 2 bytes + output << " '" << std::dec << (int)byte1; + infile.read((char*)&ch, 1); + trackbytes++; + byte2 = ch; + output << " '" << std::dec << (int)byte2; + if (m_commentsQ) { + comment += "pitch-bend"; + } + break; + case 0xC0: // patch change: 1 bytes + output << " '" << std::dec << (int)byte1; + if (m_commentsQ) { + output << "\t"; + comment += "patch-change"; + } + break; + case 0xD0: // channel pressure: 1 bytes + output << " '" << std::dec << (int)byte1; + if (m_commentsQ) { + comment += "channel pressure"; + } + break; + case 0xF0: // various system bytes: variable bytes + switch (command) { + case 0xf0: + break; + case 0xf7: + // Read the first byte which is either 0xf0 or 0xf7. + // Then a VLV byte count for the number of bytes + // that remain in the message will follow. + // Then read that number of bytes. + { + infile.putback(byte1); + trackbytes--; + int length = getVLV(infile, trackbytes); + output << " v" << std::dec << length; + for (int i=0; i 0) { + tempout << "\t\t\t; unknown header bytes"; + tempout << std::endl; + } + + for (i=0; i 127 || tempLong < -128) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "Decimal number out of range from -128 to 127" << std::endl; + return 0; + } + char charOutput = (char)tempLong; + out << charOutput; + return 1; + } else { + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + uchar ucharOutput = (uchar)tempLong; + if (tempLong > 255) { // || (tempLong < 0)) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "Decimal number out of range from 0 to 255" << std::endl; + return 0; + } + out << ucharOutput; + return 1; + } + } + + // left with an integer number with a specified number of bytes + switch (byteCount) { + case 1: + if (signIndex != -1) { + long tempLong = atoi(&word[quoteIndex + 1]); + char charOutput = (char)tempLong; + out << charOutput; + return 1; + } else { + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + uchar ucharOutput = (uchar)tempLong; + out << ucharOutput; + return 1; + } + break; + case 2: + if (signIndex != -1) { + long tempLong = atoi(&word[quoteIndex + 1]); + short shortOutput = (short)tempLong; + if (endianIndex == -1) { + writeBigEndianShort(out, shortOutput); + } else { + writeLittleEndianShort(out, shortOutput); + } + return 1; + } else { + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + ushort ushortOutput = (ushort)tempLong; + if (endianIndex == -1) { + writeBigEndianUShort(out, ushortOutput); + } else { + writeLittleEndianUShort(out, ushortOutput); + } + return 1; + } + break; + case 3: + { + if (signIndex != -1) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "negative decimal numbers cannot be stored in 3 bytes" + << std::endl; + return 0; + } + ulong tempLong = (ulong)atoi(&word[quoteIndex + 1]); + uchar byte1 = (uchar)((tempLong & 0x00ff0000) >> 16); + uchar byte2 = (uchar)((tempLong & 0x0000ff00) >> 8); + uchar byte3 = (uchar)((tempLong & 0x000000ff)); + if (endianIndex == -1) { + out << byte1; + out << byte2; + out << byte3; + } else { + out << byte3; + out << byte2; + out << byte1; + } + return 1; + } + break; + case 4: + if (signIndex != -1) { + long tempLong = atoi(&word[quoteIndex + 1]); + if (endianIndex == -1) { + writeBigEndianLong(out, tempLong); + } else { + writeLittleEndianLong(out, tempLong); + } + return 1; + } else { + ulong tempuLong = (ulong)atoi(&word[quoteIndex + 1]); + if (endianIndex == -1) { + writeBigEndianULong(out, tempuLong); + } else { + writeLittleEndianULong(out, tempuLong); + } + return 1; + } + break; + default: + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "invalid byte count specification for decimal number" << std::endl; + return 0; + } + + return 1; +} + + + +////////////////////////////// +// +// Binasc::processHexWord -- interprets a hexadecimal word and converts into +// its binary byte form. +// + +int Binasc::processHexWord(std::ostream& out, const std::string& word, + int lineNum) { + int length = (int)word.size(); + uchar outputByte; + + if (length > 2) { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "Size of hexadecimal number is too large. Max is ff." << std::endl; + return 0; + } + + if (!isxdigit(word[0]) || (length == 2 && !isxdigit(word[1]))) { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "Invalid character in hexadecimal number." << std::endl; + return 0; + } + + outputByte = (uchar)strtol(word.c_str(), (char**)NULL, 16); + out << outputByte; + return 1; +} + + + +////////////////////////////// +// +// Binasc::processStringWord -- interprets a binary word into +// its constituent byte +// + +int Binasc::processStringWord(std::ostream& out, const std::string& word, + int /* lineNum */) { + out << word; + return 1; +} + + + +////////////////////////////// +// +// Binasc::processAsciiWord -- interprets a binary word into +// its constituent byte +// + +int Binasc::processAsciiWord(std::ostream& out, const std::string& word, + int lineNum) { + int length = (int)word.size(); + uchar outputByte; + + if (word[0] != '+') { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "character byte must start with \'+\' sign: " << std::endl; + return 0; + } + + if (length > 2) { + std::cerr << "Error on line " << lineNum << " at token: " << word << std::endl; + std::cerr << "character byte word is too long -- specify only one character" + << std::endl; + return 0; + } + + if (length == 2) { + outputByte = (uchar)word[1]; + } else { + outputByte = ' '; + } + out << outputByte; + return 1; +} + + + +////////////////////////////// +// +// Binasc::processBinaryWord -- interprets a binary word into +// its constituent byte +// + +int Binasc::processBinaryWord(std::ostream& out, const std::string& word, + int lineNum) { + int length = (int)word.size(); // length of ascii binary number + int commaIndex = -1; // index location of comma in number + int leftDigits = -1; // number of digits to left of comma + int rightDigits = -1; // number of digits to right of comma + int i = 0; + + // make sure that all characters are valid + for (i=0; i 8) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "too many digits in binary number" << std::endl; + return 0; + } + // if there is a comma, then there cannot be more than 4 digits on a side + if (leftDigits > 4) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "too many digits to left of comma" << std::endl; + return 0; + } + if (rightDigits > 4) { + std::cerr << "Error on line " << lineNum << " at token: " << word + << std::endl; + std::cerr << "too many digits to right of comma" << std::endl; + return 0; + } + + // OK, we have a valid binary number, so calculate the byte + + uchar output = 0; + + // if no comma in binary number + if (commaIndex == -1) { + for (i=0; i> 28) & 0x7f; + byte[1] = (value >> 21) & 0x7f; + byte[2] = (value >> 14) & 0x7f; + byte[3] = (value >> 7) & 0x7f; + byte[4] = (value >> 0) & 0x7f; + + int i; + int flag = 0; + for (i=0; i<4; i++) { + if (byte[i] != 0) { + flag = 1; + } + if (flag) { + byte[i] |= 0x80; + } + } + + for (i=0; i<5; i++) { + if (byte[i] >= 0x80 || i == 4) { + out << byte[i]; + } + } + + return 1; +} + + + +//////////////////////////// +// +// Binasc::processMidiTempoWord -- convert a floating point tempo into +// a three-byte number of microseconds per beat per minute value. +// + +int Binasc::processMidiTempoWord(std::ostream& out, const std::string& word, + int lineNum) { + if (word.size() < 2) { + std::cerr << "Error on line: " << lineNum + << ": 't' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + if (!(isdigit(word[1]) || word[1] == '.' || word[1] == '-' + || word[1] == '+')) { + std::cerr << "Error on line: " << lineNum + << ": 't' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + double value = strtod(&word[1], NULL); + + if (value < 0.0) { + value = -value; + } + + int intval = int(60.0 * 1000000.0 / value + 0.5); + + uchar byte0 = intval & 0xff; + uchar byte1 = (intval >> 8) & 0xff; + uchar byte2 = (intval >> 16) & 0xff; + out << byte2 << byte1 << byte0; + return 1; +} + + + +//////////////////////////// +// +// Binasc::processMidiPitchBendWord -- convert a floating point number in +// the range from +1.0 to -1.0 into a 14-point integer with -1.0 mapping +// to 0 and +1.0 mapping to 2^15-1. This integer will be packed into +// two bytes, with the LSB coming first and containing the bottom +// 7-bits of the 14-bit value, then the MSB coming second and containing +// the top 7-bits of the 14-bit value. + +int Binasc::processMidiPitchBendWord(std::ostream& out, const std::string& word, + int lineNum) { + if (word.size() < 2) { + std::cerr << "Error on line: " << lineNum + << ": 'p' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + if (!(isdigit(word[1]) || word[1] == '.' || word[1] == '-' + || word[1] == '+')) { + std::cerr << "Error on line: " << lineNum + << ": 'p' needs to be followed immediately by " + << "a floating-point number" << std::endl; + return 0; + } + double value = strtod(&word[1], NULL); + + if (value > 1.0) { + value = 1.0; + } + if (value < -1.0) { + value = -1.0; + } + + int intval = (int)(((1 << 13)-0.5) * (value + 1.0) + 0.5); + uchar LSB = intval & 0x7f; + uchar MSB = (intval >> 7) & 0x7f; + out << LSB << MSB; + return 1; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// Ordered byte writing functions -- +// + +////////////////////////////// +// +// Binasc::writeLittleEndianUShort -- +// + +std::ostream& Binasc::writeLittleEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianUShort -- +// + +std::ostream& Binasc::writeBigEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianShort -- +// + +std::ostream& Binasc::writeLittleEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// writeBigEndianShort -- +// + +std::ostream& Binasc::writeBigEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianULong -- +// + +std::ostream& Binasc::writeLittleEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; ulong ul; } data; + data.ul = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianULong -- +// + +std::ostream& Binasc::writeBigEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; long ul; } data; + data.ul = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianLong -- +// + +std::ostream& Binasc::writeLittleEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianLong -- +// + +std::ostream& Binasc::writeBigEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; + +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianFloat -- +// + +std::ostream& Binasc::writeBigEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianFloat -- +// + +std::ostream& Binasc::writeLittleEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeBigEndianDouble -- +// + +std::ostream& Binasc::writeBigEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[7]; + out << data.bytes[6]; + out << data.bytes[5]; + out << data.bytes[4]; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// Binasc::writeLittleEndianDouble -- +// + +std::ostream& Binasc::writeLittleEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + out << data.bytes[4]; + out << data.bytes[5]; + out << data.bytes[6]; + out << data.bytes[7]; + return out; +} + + +} // end namespace smf + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/Binasc.h b/plugins/community/repos/ImpromptuModular/src/midifile/Binasc.h new file mode 100644 index 00000000..6aa57f09 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/Binasc.h @@ -0,0 +1,159 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Mon Feb 16 12:26:32 PST 2015 Adapted from binasc program. +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/Binasc.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// description: Interface to convert bytes between binary and ASCII forms. +// + +#ifndef _BINASC_H_INCLUDED +#define _BINASC_H_INCLUDED + +#include +#include +#include +#include +#include /* needed for MinGW */ + +namespace smf { + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long ulong; + +class Binasc { + + public: + Binasc (void); + + ~Binasc (); + + // functions for setting options: + int setLineLength (int length); + int getLineLength (void); + int setLineBytes (int length); + int getLineBytes (void); + void setComments (int state); + void setCommentsOn (void); + void setCommentsOff (void); + int getComments (void); + void setBytes (int state); + void setBytesOn (void); + void setBytesOff (void); + int getBytes (void); + void setMidi (int state); + void setMidiOn (void); + void setMidiOff (void); + int getMidi (void); + + // functions for converting into a binary file: + int writeToBinary (const std::string& outfile, + const std::string& infile); + int writeToBinary (const std::string& outfile, + std::istream& input); + int writeToBinary (std::ostream& out, + const std::string& infile); + int writeToBinary (std::ostream& out, + std::istream& input); + + // functions for converting into an ASCII file with hex bytes: + int readFromBinary (const std::string& + outfile, + const std::string& infile); + int readFromBinary (const std::string& outfile, + std::istream& input); + int readFromBinary (std::ostream& out, + const std::string& infile); + int readFromBinary (std::ostream& out, + std::istream& input); + + // static functions for writing ordered bytes: + static std::ostream& writeLittleEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeBigEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeLittleEndianShort (std::ostream& out, + short value); + static std::ostream& writeBigEndianShort (std::ostream& out, + short value); + static std::ostream& writeLittleEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeBigEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeLittleEndianLong (std::ostream& out, + long value); + static std::ostream& writeBigEndianLong (std::ostream& out, + long value); + static std::ostream& writeLittleEndianFloat (std::ostream& out, + float value); + static std::ostream& writeBigEndianFloat (std::ostream& out, + float value); + static std::ostream& writeLittleEndianDouble (std::ostream& out, + double value); + static std::ostream& writeBigEndianDouble (std::ostream& out, + double value); + + static std::string keyToPitchName (int key); + + protected: + int m_bytesQ; // option for printing hex bytes in ASCII output. + int m_commentsQ; // option for printing comments in ASCII output. + int m_midiQ; // output ASCII data as parsed MIDI file. + int m_maxLineLength;// number of character in ASCII output on a line. + int m_maxLineBytes; // number of hex bytes in ASCII output on a line. + + private: + // helper functions for reading ASCII content to conver to binary: + int processLine (std::ostream& out, + const std::string& input, + int lineNum); + int processAsciiWord (std::ostream& out, + const std::string& input, + int lineNum); + int processStringWord (std::ostream& out, + const std::string& input, + int lineNum); + int processBinaryWord (std::ostream& out, + const std::string& input, + int lineNum); + int processDecimalWord (std::ostream& out, + const std::string& input, + int lineNum); + int processHexWord (std::ostream& out, + const std::string& input, + int lineNum); + int processVlvWord (std::ostream& out, + const std::string& input, + int lineNum); + int processMidiPitchBendWord(std::ostream& out, + const std::string& input, + int lineNum); + int processMidiTempoWord (std::ostream& out, + const std::string& input, + int lineNum); + + // helper functions for reading binary content to convert to ASCII: + int outputStyleAscii (std::ostream& out, std::istream& input); + int outputStyleBinary (std::ostream& out, std::istream& input); + int outputStyleBoth (std::ostream& out, std::istream& input); + int outputStyleMidi (std::ostream& out, std::istream& input); + + // MIDI parsing helper functions: + int readMidiEvent (std::ostream& out, std::istream& infile, + int& trackbytes, int& command); + int getVLV (std::istream& infile, int& trackbytes); + int getWord (std::string& word, const std::string& input, + const std::string& terminators, int index); + +}; + +} // end of namespace smf + +#endif /* _BINASC_H_INCLUDED */ + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiEvent.cpp b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEvent.cpp new file mode 100644 index 00000000..08e478ab --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEvent.cpp @@ -0,0 +1,284 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:40:14 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src-library/MidiEvent.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which stores a MidiMessage and a timestamp +// for the MidiFile class. +// + +#include "MidiEvent.h" + +#include + + +namespace smf { + +////////////////////////////// +// +// MidiEvent::MidiEvent -- Constructor classes +// + +MidiEvent::MidiEvent(void) : MidiMessage() { + clearVariables(); +} + + +MidiEvent::MidiEvent(int command) : MidiMessage(command) { + clearVariables(); +} + + +MidiEvent::MidiEvent(int command, int p1) : MidiMessage(command, p1) { + clearVariables(); +} + + +MidiEvent::MidiEvent(int command, int p1, int p2) + : MidiMessage(command, p1, p2) { + clearVariables(); +} + + +MidiEvent::MidiEvent(int aTime, int aTrack, vector& message) + : MidiMessage(message) { + track = aTrack; + tick = aTime; + seconds = 0.0; + seq = 0; + m_eventlink = NULL; +} + + +MidiEvent::MidiEvent(const MidiEvent& mfevent) : MidiMessage() { + track = mfevent.track; + tick = mfevent.tick; + seconds = mfevent.seconds; + seq = mfevent.seq; + m_eventlink = NULL; + + this->resize(mfevent.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = mfevent[i]; + } +} + + + +////////////////////////////// +// +// MidiEvent::~MidiEvent -- MidiFile Event destructor +// + +MidiEvent::~MidiEvent() { + track = -1; + tick = -1; + seconds = -1.0; + seq = -1; + this->resize(0); + m_eventlink = NULL; +} + + +////////////////////////////// +// +// MidiEvent::clearVariables -- Clear everything except MidiMessage data. +// + +void MidiEvent::clearVariables(void) { + track = 0; + tick = 0; + seconds = 0.0; + seq = 0; + m_eventlink = NULL; +} + + +////////////////////////////// +// +// MidiEvent::operator= -- Copy the contents of another MidiEvent. +// + +MidiEvent& MidiEvent::operator=(const MidiEvent& mfevent) { + if (this == &mfevent) { + return *this; + } + tick = mfevent.tick; + track = mfevent.track; + seconds = mfevent.seconds; + seq = mfevent.seq; + m_eventlink = NULL; + this->resize(mfevent.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = mfevent[i]; + } + return *this; +} + + +MidiEvent& MidiEvent::operator=(const MidiMessage& message) { + if (this == &message) { + return *this; + } + clearVariables(); + this->resize(message.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = message[i]; + } + return *this; +} + + +MidiEvent& MidiEvent::operator=(const vector& bytes) { + clearVariables(); + this->resize(bytes.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = bytes[i]; + } + return *this; +} + + +MidiEvent& MidiEvent::operator=(const vector& bytes) { + clearVariables(); + setMessage(bytes); + return *this; +} + + +MidiEvent& MidiEvent::operator=(const vector& bytes) { + clearVariables(); + setMessage(bytes); + return *this; +} + + + +////////////////////////////// +// +// MidiEvent::unlinkEvent -- Disassociate this event with another. +// Also tell the other event to disassociate from this event. +// + +void MidiEvent::unlinkEvent(void) { + if (m_eventlink == NULL) { + return; + } + MidiEvent* mev = m_eventlink; + m_eventlink = NULL; + mev->unlinkEvent(); +} + + + +////////////////////////////// +// +// MidiEvent::linkEvent -- Make a link between two messages. +// Unlinking +// + +void MidiEvent::linkEvent(MidiEvent* mev) { + if (mev->m_eventlink != NULL) { + // unlink other event if it is linked to something else; + mev->unlinkEvent(); + } + // if this is already linked to something else, then unlink: + if (m_eventlink != NULL) { + m_eventlink->unlinkEvent(); + } + unlinkEvent(); + + mev->m_eventlink = this; + m_eventlink = mev; +} + + +void MidiEvent::linkEvent(MidiEvent& mev) { + linkEvent(&mev); +} + + + +////////////////////////////// +// +// MidiEvent::getLinkedEvent -- Returns a linked event. Usually +// this is the note-off message for a note-on message and vice-versa. +// Returns null if there are no links. +// + +MidiEvent* MidiEvent::getLinkedEvent(void) { + return m_eventlink; +} + + +const MidiEvent* MidiEvent::getLinkedEvent(void) const { + return m_eventlink; +} + + + +////////////////////////////// +// +// MidiEvent::isLinked -- Returns true if there is an event which is not +// NULL. This function is similar to getLinkedEvent(). +// + +int MidiEvent::isLinked(void) const { + return m_eventlink == NULL ? 0 : 1; +} + + + +////////////////////////////// +// +// MidiEvent::getTickDuration -- For linked events (note-ons and note-offs), +// return the absolute tick time difference between the two events. +// The tick values are presumed to be in absolute tick mode rather than +// delta tick mode. Returns 0 if not linked. +// + +int MidiEvent::getTickDuration(void) const { + const MidiEvent* mev = getLinkedEvent(); + if (mev == NULL) { + return 0; + } + int tick2 = mev->tick; + if (tick2 > tick) { + return tick2 - tick; + } else { + return tick - tick2; + } +} + + + +////////////////////////////// +// +// MidiEvent::getDurationInSeconds -- For linked events (note-ons and +// note-offs), return the duration of the note in seconds. The +// seconds analysis must be done first; otherwise the duration will be +// reported as zero. +// + +double MidiEvent::getDurationInSeconds(void) const { + const MidiEvent* mev = getLinkedEvent(); + if (mev == NULL) { + return 0; + } + double seconds2 = mev->seconds; + if (seconds2 > seconds) { + return seconds2 - seconds; + } else { + return seconds - seconds2; + } +} + + +} // end namespace smf + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiEvent.h b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEvent.h new file mode 100644 index 00000000..39f141ee --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEvent.h @@ -0,0 +1,71 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:47:39 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiEvent.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which stores a MidiMessage and a timestamp +// for the MidiFile class. +// + +#ifndef _MIDIEVENT_H_INCLUDED +#define _MIDIEVENT_H_INCLUDED + +#include "MidiMessage.h" +#include + +namespace smf { + +class MidiEvent : public MidiMessage { + public: + MidiEvent (void); + MidiEvent (int command); + MidiEvent (int command, int param1); + MidiEvent (int command, int param1, int param2); + MidiEvent (const MidiMessage& message); + MidiEvent (const MidiEvent& mfevent); + MidiEvent (int aTime, int aTrack, + std::vector& message); + + ~MidiEvent (); + + MidiEvent& operator= (const MidiEvent& mfevent); + MidiEvent& operator= (const MidiMessage& message); + MidiEvent& operator= (const std::vector& bytes); + MidiEvent& operator= (const std::vector& bytes); + MidiEvent& operator= (const std::vector& bytes); + + void clearVariables (void); + + // functions related to event linking (note-ons to note-offs). + void unlinkEvent (void); + void unlinkEvents (void); + void linkEvent (MidiEvent* mev); + void linkEvents (MidiEvent* mev); + void linkEvent (MidiEvent& mev); + void linkEvents (MidiEvent& mev); + int isLinked (void) const; + MidiEvent* getLinkedEvent (void); + const MidiEvent* getLinkedEvent (void) const; + int getTickDuration (void) const; + double getDurationInSeconds (void) const; + + int tick; // delta or absolute MIDI ticks + int track; // [original] track number of event in MIDI file + double seconds; // calculated time in sec. (after doTimeAnalysis()) + int seq; // sorting sequence number of event + + private: + MidiEvent* m_eventlink; // used to match note-ons and note-offs + +}; + +} // end of namespace smf + +#endif /* _MIDIEVENT_H_INCLUDED */ + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiEventList.cpp b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEventList.cpp new file mode 100644 index 00000000..cf136e0d --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEventList.cpp @@ -0,0 +1,625 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:55:38 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src-library/MidiEventList.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which stores a MidiEvents for a MidiFile track. +// + + +#include "MidiEventList.h" + +#include +#include +#include +#include + +#include "stdlib.h" + +namespace smf { + +////////////////////////////// +// +// MidiEventList::MidiEventList -- Constructor. +// + +MidiEventList::MidiEventList(void) { + reserve(1000); +} + + + +////////////////////////////// +// +// MidiEventList::MidiEventList(MidiEventList&) -- Copy constructor. +// + +MidiEventList::MidiEventList(const MidiEventList& other) { + list.reserve(other.list.size()); + auto it = other.list.begin(); + std::generate_n(std::back_inserter(list), other.list.size(), [&]() -> MidiEvent* { + return new MidiEvent(**it++); + }); +} + + + +////////////////////////////// +// +// MidiEventList::MidiEventList(MidiEventList&&) -- Move constructor. +// + +MidiEventList::MidiEventList(MidiEventList&& other) { + list = std::move(other.list); + other.list.clear(); +} + + + +////////////////////////////// +// +// MidiEventList::~MidiEventList -- Deconstructor. Deallocate all stored +// data. +// + +MidiEventList::~MidiEventList() { + clear(); +} + + + +////////////////////////////// +// +// MidiEventList::operator[] -- +// + +MidiEvent& MidiEventList::operator[](int index) { + return *list[index]; +} + + +const MidiEvent& MidiEventList::operator[](int index) const { + return *list[index]; +} + + + +////////////////////////////// +// +// MidiEventList::back -- Return the last element in the list. +// + +MidiEvent& MidiEventList::back(void) { + return *list.back(); +} + + +const MidiEvent& MidiEventList::back(void) const { + return *list.back(); +} + +// +// MidiEventList::last -- Alias for MidiEventList::back(). +// + +MidiEvent& MidiEventList::last(void) { + return back(); +} + + +const MidiEvent& MidiEventList::last(void) const { + return back(); +} + + + +////////////////////////////// +// +// MidiEventList::getEvent -- The same thing as operator[], for +// internal use when operator[] would look more messy. +// + +MidiEvent& MidiEventList::getEvent(int index) { + return *list[index]; +} + + +const MidiEvent& MidiEventList::getEvent(int index) const { + return *list[index]; +} + + + +////////////////////////////// +// +// MidiEventList::clear -- De-allocate any MidiEvents present in the list +// and set the size of the list to 0. +// + +void MidiEventList::clear(void) { + for (int i=0; i<(int)list.size(); i++) { + if (list[i] != NULL) { + delete list[i]; + list[i] = NULL; + } + } + list.resize(0); +} + + + +////////////////////////////// +// +// MidiEventList::data -- Return the low-level array of MidiMessage +// pointers. This is useful for applying your own sorting +// function to the list. +// + +MidiEvent** MidiEventList::data(void) { + return list.data(); +} + + + +////////////////////////////// +// +// MidiEventList::reserve -- Pre-allocate space in the list for storing +// elements. +// + +void MidiEventList::reserve(int rsize) { + if (rsize > (int)list.size()) { + list.reserve(rsize); + } +} + + +////////////////////////////// +// +// MidiEventList::getSize -- Return the number of MidiEvents stored +// in the list. +// + +int MidiEventList::getSize(void) const { + return (int)list.size(); +} + +// +// MidiEventList::size -- Alias for MidiEventList::getSize(). +// + +int MidiEventList::size(void) const { + return getSize(); +} + +// +// MidiEventList::getEventCount -- Alias for MidiEventList::getSize(). +// + +int MidiEventList::getEventCount(void) const { + return getSize(); +} + + + +////////////////////////////// +// +// MidiEventList::append -- add a MidiEvent at the end of the list. Returns +// the index of the appended event. +// + +int MidiEventList::append(MidiEvent& event) { + MidiEvent* ptr = new MidiEvent(event); + list.push_back(ptr); + return (int)list.size()-1; +} + +// +// MidiEventList::push -- Alias for MidiEventList::append(). +// + +int MidiEventList::push(MidiEvent& event) { + return append(event); +} + +// +// MidiEventList::push_back -- Alias for MidiEventList::append(). +// + +int MidiEventList::push_back(MidiEvent& event) { + return append(event); +} + + + +////////////////////////////// +// +// MidiEventList::removeEmpties -- Remove any MIDI message which contain no +// bytes. This function first deallocates any empty MIDI events, and then +// removes them from the list of events. +// + +void MidiEventList::removeEmpties(void) { + int count = 0; + for (int i=0; i<(int)list.size(); i++) { + if (list[i]->empty()) { + delete list[i]; + list[i] = NULL; + count++; + } + } + if (count == 0) { + return; + } + std::vector newlist; + newlist.reserve(list.size() - count); + for (int i=0; i<(int)list.size(); i++) { + if (list[i]) { + newlist.push_back(list[i]); + } + } + list.swap(newlist); +} + + + +////////////////////////////// +// +// MidiEventList::linkNotePairs -- Match note-ones and note-offs together +// There are two models that can be done if two notes are overlapping +// on the same pitch: the first note-off affects the last note-on, +// or the first note-off affects the first note-on. Currently the +// first note-off affects the last note-on, but both methods could +// be implemented with user selectability. The current state of the +// track is assumed to be in time-sorted order. Returns the number +// of linked notes (note-on/note-off pairs). +// + +int MidiEventList::linkEventPairs(void) { + return linkNotePairs(); +} + + +int MidiEventList::linkNotePairs(void) { + + // Note-on states: + // dimension 1: MIDI channel (0-15) + // dimension 2: MIDI key (0-127) (but 0 not used for note-ons) + // dimension 3: List of active note-ons or note-offs. + std::vector>> noteons; + noteons.resize(16); + int i; + for (i=0; i<(int)noteons.size(); i++) { + noteons[i].resize(128); + } + + // Controller linking: The following General MIDI controller numbers are + // also monitored for linking within the track (but not between tracks). + // hex dec name range + // 40 64 Hold pedal (Sustain) on/off 0..63=off 64..127=on + // 41 65 Portamento on/off 0..63=off 64..127=on + // 42 66 Sustenuto Pedal on/off 0..63=off 64..127=on + // 43 67 Soft Pedal on/off 0..63=off 64..127=on + // 44 68 Legato Pedal on/off 0..63=off 64..127=on + // 45 69 Hold Pedal 2 on/off 0..63=off 64..127=on + // 50 80 General Purpose Button 0..63=off 64..127=on + // 51 81 General Purpose Button 0..63=off 64..127=on + // 52 82 General Purpose Button 0..63=off 64..127=on + // 53 83 General Purpose Button 0..63=off 64..127=on + // 54 84 Undefined on/off 0..63=off 64..127=on + // 55 85 Undefined on/off 0..63=off 64..127=on + // 56 86 Undefined on/off 0..63=off 64..127=on + // 57 87 Undefined on/off 0..63=off 64..127=on + // 58 88 Undefined on/off 0..63=off 64..127=on + // 59 89 Undefined on/off 0..63=off 64..127=on + // 5A 90 Undefined on/off 0..63=off 64..127=on + // 7A 122 Local Keyboard On/Off 0..63=off 64..127=on + + // first keep track of whether the controller is an on/off switch: + std::vector> contmap; + contmap.resize(128); + std::pair zero(0, 0); + std::fill(contmap.begin(), contmap.end(), zero); + contmap[64].first = 1; contmap[64].second = 0; + contmap[65].first = 1; contmap[65].second = 1; + contmap[66].first = 1; contmap[66].second = 2; + contmap[67].first = 1; contmap[67].second = 3; + contmap[68].first = 1; contmap[68].second = 4; + contmap[69].first = 1; contmap[69].second = 5; + contmap[80].first = 1; contmap[80].second = 6; + contmap[81].first = 1; contmap[81].second = 7; + contmap[82].first = 1; contmap[82].second = 8; + contmap[83].first = 1; contmap[83].second = 9; + contmap[84].first = 1; contmap[84].second = 10; + contmap[85].first = 1; contmap[85].second = 11; + contmap[86].first = 1; contmap[86].second = 12; + contmap[87].first = 1; contmap[87].second = 13; + contmap[88].first = 1; contmap[88].second = 14; + contmap[89].first = 1; contmap[89].second = 15; + contmap[90].first = 1; contmap[90].second = 16; + contmap[122].first = 1; contmap[122].second = 17; + + // dimensions: + // 1: mapped controller (0 to 17) + // 2: channel (0 to 15) + std::vector> contevents; + contevents.resize(18); + std::vector> oldstates; + oldstates.resize(18); + for (int i=0; i<18; i++) { + contevents[i].resize(16); + std::fill(contevents[i].begin(), contevents[i].end(), nullptr); + oldstates[i].resize(16); + std::fill(oldstates[i].begin(), oldstates[i].end(), -1); + } + + // Now iterate through the MidiEventList keeping track of note and + // select controller states and linking notes/controllers as needed. + int channel; + int key; + int contnum; + int contval; + int conti; + int contstate; + int counter = 0; + MidiEvent* mev; + MidiEvent* noteon; + for (i=0; iunlinkEvent(); + if (mev->isNoteOn()) { + // store the note-on to pair later with a note-off message. + key = mev->getKeyNumber(); + channel = mev->getChannel(); + noteons[channel][key].push_back(mev); + } else if (mev->isNoteOff()) { + key = mev->getKeyNumber(); + channel = mev->getChannel(); + if (noteons[channel][key].size() > 0) { + noteon = noteons[channel][key].back(); + noteons[channel][key].pop_back(); + noteon->linkEvent(mev); + counter++; + } + } else if (mev->isController()) { + contnum = mev->getP1(); + if (contmap[contnum].first) { + conti = contmap[contnum].second; + channel = mev->getChannel(); + contval = mev->getP2(); + contstate = contval < 64 ? 0 : 1; + if ((oldstates[conti][channel] == -1) && contstate) { + // a newly initialized onstate was detected, so store for + // later linking to an off state. + contevents[conti][channel] = mev; + oldstates[conti][channel] = contstate; + } else if (oldstates[conti][channel] == contstate) { + // the controller state is redundant and will be ignored. + } else if ((oldstates[conti][channel] == 0) && contstate) { + // controller is currently off, so store on-state for next link + contevents[conti][channel] = mev; + oldstates[conti][channel] = contstate; + } else if ((oldstates[conti][channel] == 1) && (contstate == 0)) { + // controller has just been turned off, so link to + // stored on-message. + contevents[conti][channel]->linkEvent(mev); + oldstates[conti][channel] = contstate; + // not necessary, but maybe use for something later: + contevents[conti][channel] = mev; + } + } + } + } + return counter; +} + + + +////////////////////////////// +// +// MidiEventList::clearLinks -- remove all note-on/note-off links. +// + +void MidiEventList::clearLinks(void) { + for (int i=0; i<(int)getSize(); i++) { + getEvent(i).unlinkEvent(); + } +} + + + +////////////////////////////// +// +// MidiEventList::clearSequence -- Remove any seqence serial numbers from +// MidiEvents in the list. This will cause the default ordering by +// sortTracks() to be used, in which case the ordering of MidiEvents +// occurring at the same tick may switch their ordering. +// + +void MidiEventList::clearSequence(void) { + for (int i=0; i bevent.tick) { + // aevent occurs after bevent + return +1; + } else if (aevent.tick < bevent.tick) { + // aevent occurs before bevent + return -1; + } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq > bevent.seq)) { + // aevent sequencing state occurs after bevent + // see MidiEventList::markSequence() + return +1; + } else if ((aevent.seq != 0) && (bevent.seq != 0) && (aevent.seq < bevent.seq)) { + // aevent sequencing state occurs before bevent + // see MidiEventList::markSequence() + return -1; + } else if (aevent.getP0() == 0xff && aevent.getP1() == 0x2f) { + // end-of-track meta-message should always be last (but won't really + // matter since the writing function ignores all end-of-track messages + // and writes its own. + return +1; + } else if (bevent.getP0() == 0xff && bevent.getP1() == 0x2f) { + // end-of-track meta-message should always be last (but won't really + // matter since the writing function ignores all end-of-track messages + // and writes its own. + return -1; + } else if (aevent.getP0() == 0xff && bevent.getP0() != 0xff) { + // other meta-messages are placed before real MIDI messages + return -1; + } else if (aevent.getP0() != 0xff && bevent.getP0() == 0xff) { + // other meta-messages are placed before real MIDI messages + return +1; + } else if (((aevent.getP0() & 0xf0) == 0x90) && (aevent.getP2() != 0)) { + // note-ons come after all other types of MIDI messages + return +1; + } else if (((bevent.getP0() & 0xf0) == 0x90) && (bevent.getP2() != 0)) { + // note-ons come after all other types of MIDI messages + return -1; + } else if (((aevent.getP0() & 0xf0) == 0x90) || ((aevent.getP0() & 0xf0) == 0x80)) { + // note-offs come after all other MIDI messages (except note-ons) + return +1; + } else if (((bevent.getP0() & 0xf0) == 0x90) || ((bevent.getP0() & 0xf0) == 0x80)) { + // note-offs come after all other MIDI messages (except note-ons) + return -1; + } else if (((aevent.getP0() & 0xf0) == 0xb0) && ((bevent.getP0() & 0xf0) == 0xb0)) { + // both events are continuous controllers. Sort them by controller number + if (aevent.getP1() > bevent.getP1()) { + return +1; + } if (aevent.getP1() < bevent.getP1()) { + return -1; + } else { + // same controller number, so sort by data value + if (aevent.getP2() > bevent.getP2()) { + return +1; + } if (aevent.getP2() < bevent.getP2()) { + return -1; + } else { + return 0; + } + } + } else { + return 0; + } +} + + +} // end namespace smf + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiEventList.h b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEventList.h new file mode 100644 index 00000000..e3bfb13c --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiEventList.h @@ -0,0 +1,80 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 21:55:38 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiEventList.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class that stores a MidiEvents for a MidiFile track. +// + +#ifndef _MIDIEVENTLIST_H_INCLUDED +#define _MIDIEVENTLIST_H_INCLUDED + +#include "MidiEvent.h" +#include + +namespace smf { + +class MidiEventList { + public: + MidiEventList (void); + MidiEventList (const MidiEventList& other); + MidiEventList (MidiEventList&& other); + + ~MidiEventList (); + + MidiEventList& operator= (MidiEventList& other); + MidiEvent& operator[] (int index); + const MidiEvent& operator[] (int index) const; + + MidiEvent& back (void); + const MidiEvent& back (void) const; + MidiEvent& last (void); + const MidiEvent& last (void) const; + MidiEvent& getEvent (int index); + const MidiEvent& getEvent (int index) const; + void clear (void); + void reserve (int rsize); + int getEventCount (void) const; + int getSize (void) const; + int size (void) const; + void removeEmpties (void); + int linkNotePairs (void); + int linkEventPairs (void); + void clearLinks (void); + void clearSequence (void); + int markSequence (int sequence = 1); + + int push (MidiEvent& event); + int push_back (MidiEvent& event); + int append (MidiEvent& event); + + // careful when using these, intended for internal use in MidiFile class: + void detach (void); + int push_back_no_copy (MidiEvent* event); + + // access to the list of MidiEvents for sorting with an external function: + MidiEvent** data (void); + + protected: + std::vector list; + + private: + void sort (void); + + // MidiFile class calls sort() + friend class MidiFile; +}; + + +int eventcompare(const void* a, const void* b); + +} // end of namespace smf + +#endif /* _MIDIEVENTLIST_H_INCLUDED */ + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiFile.cpp b/plugins/community/repos/ImpromptuModular/src/midifile/MidiFile.cpp new file mode 100644 index 00000000..e60c2c6b --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiFile.cpp @@ -0,0 +1,3174 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Fri Nov 26 14:12:01 PST 1999 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/src-library/MidiFile.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class which can read/write Standard MIDI files. +// MIDI data is stored by track in an array. This +// class is used for example in the MidiPerform class. +// + +#include "MidiFile.h" +#include "Binasc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace smf { + +////////////////////////////// +// +// MidiFile::MidiFile -- Constuctor. +// + +MidiFile::MidiFile(void) { + m_events.resize(m_trackCount); + for (int i=0; iMidiEventList* { + return new MidiEventList(**it++); + } + ); + m_ticksPerQuarterNote = other.m_ticksPerQuarterNote; + m_trackCount = other.m_trackCount; + m_theTrackState = other.m_theTrackState; + m_theTimeState = other.m_theTimeState; + m_readFileName = other.m_readFileName; + m_timemapvalid = other.m_timemapvalid; + m_timemap = other.m_timemap; + m_rwstatus = other.m_rwstatus; + if (other.m_linkedEventsQ) { + linkEventPairs(); + } + return *this; +} + + +MidiFile& MidiFile::operator=(MidiFile&& other) { + m_events = std::move(other.m_events); + m_linkedEventsQ = other.m_linkedEventsQ; + other.m_linkedEventsQ = false; + other.m_events.clear(); + other.m_events.emplace_back(new MidiEventList); + m_ticksPerQuarterNote = other.m_ticksPerQuarterNote; + m_trackCount = other.m_trackCount; + m_theTrackState = other.m_theTrackState; + m_theTimeState = other.m_theTimeState; + m_readFileName = other.m_readFileName; + m_timemapvalid = other.m_timemapvalid; + m_timemap = other.m_timemap; + m_rwstatus = other.m_rwstatus; + return *this; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// reading/writing functions -- +// + +////////////////////////////// +// +// MidiFile::read -- Parse a Standard MIDI File and store its contents +// in the object. +// + +bool MidiFile::read(const std::string& filename) { + m_timemapvalid = 0; + setFilename(filename); + m_rwstatus = true; + + std::fstream input; + input.open(filename.c_str(), std::ios::binary | std::ios::in); + + if (!input.is_open()) { + m_rwstatus = false; + return m_rwstatus; + } + + m_rwstatus = read(input); + return m_rwstatus; +} + +// +// istream version of read(). +// + +bool MidiFile::read(std::istream& input) { + m_rwstatus = true; + if (input.peek() != 'M') { + // If the first byte in the input stream is not 'M', then presume that + // the MIDI file is in the binasc format which is an ASCII representation + // of the MIDI file. Convert the binasc content into binary content and + // then continue reading with this function. + std::stringstream binarydata; + Binasc binasc; + binasc.writeToBinary(binarydata, input); + binarydata.seekg(0, std::ios_base::beg); + if (binarydata.peek() != 'M') { + std::cerr << "Bad MIDI data input" << std::endl; + m_rwstatus = false; + return m_rwstatus; + } else { + m_rwstatus = read(binarydata); + return m_rwstatus; + } + } + + std::string filename = getFilename(); + + int character; + // uchar buffer[123456] = {0}; + ulong longdata; + ushort shortdata; + + + // Read the MIDI header (4 bytes of ID, 4 byte data size, + // anticipated 6 bytes of data. + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'M' at first byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'M') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'M' at first byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'T' at second byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'T') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'T' at second byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'h' at third byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'h') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'h' at third byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + character = input.get(); + if (character == EOF) { + std::cerr << "In file " << filename << ": unexpected end of file." << std::endl; + std::cerr << "Expecting 'd' at fourth byte, but found nothing." << std::endl; + m_rwstatus = false; return m_rwstatus; + } else if (character != 'd') { + std::cerr << "File " << filename << " is not a MIDI file" << std::endl; + std::cerr << "Expecting 'd' at fourth byte but got '" + << (char)character << "'" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + // read header size (allow larger header size?) + longdata = readLittleEndian4Bytes(input); + if (longdata != 6) { + std::cerr << "File " << filename + << " is not a MIDI 1.0 Standard MIDI file." << std::endl; + std::cerr << "The header size is " << longdata << " bytes." << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + // Header parameter #1: format type + int type; + shortdata = readLittleEndian2Bytes(input); + switch (shortdata) { + case 0: + type = 0; + break; + case 1: + type = 1; + break; + case 2: + // Type-2 MIDI files should probably be allowed as well, + // but I have never seen one in the wild to test with. + default: + std::cerr << "Error: cannot handle a type-" << shortdata + << " MIDI file" << std::endl; + m_rwstatus = false; return m_rwstatus; + } + + // Header parameter #2: track count + int tracks; + shortdata = readLittleEndian2Bytes(input); + if (type == 0 && shortdata != 1) { + std::cerr << "Error: Type 0 MIDI file can only contain one track" << std::endl; + std::cerr << "Instead track count is: " << shortdata << std::endl; + m_rwstatus = false; return m_rwstatus; + } else { + tracks = shortdata; + } + clear(); + if (m_events[0] != NULL) { + delete m_events[0]; + } + m_events.resize(tracks); + for (int z=0; zreserve(10000); // Initialize with 10,000 event storage. + m_events[z]->clear(); + } + + // Header parameter #3: Ticks per quarter note + shortdata = readLittleEndian2Bytes(input); + if (shortdata >= 0x8000) { + int framespersecond = 255 - ((shortdata >> 8) & 0x00ff) + 1; + int subframes = shortdata & 0x00ff; + switch (framespersecond) { + case 25: framespersecond = 25; break; + case 24: framespersecond = 24; break; + case 29: framespersecond = 29; break; // really 29.97 for color television + case 30: framespersecond = 30; break; + default: + std::cerr << "Warning: unknown FPS: " << framespersecond << std::endl; + std::cerr << "Using non-standard FPS: " << framespersecond << std::endl; + } + m_ticksPerQuarterNote = framespersecond * subframes; + + // std::cerr << "SMPTE ticks: " << m_ticksPerQuarterNote << " ticks/sec" << std::endl; + // std::cerr << "SMPTE frames per second: " << framespersecond << std::endl; + // std::cerr << "SMPTE subframes per frame: " << subframes << std::endl; + } else { + m_ticksPerQuarterNote = shortdata; + } + + + ////////////////////////////////////////////////// + // + // now read individual tracks: + // + + uchar runningCommand; + MidiEvent event; + std::vector bytes; + int xstatus; + // int barline; + + for (int i=0; ireserve((int)longdata/2); + m_events[i]->clear(); + + // process the track + int absticks = 0; + // barline = 1; + while (!input.eof()) { + longdata = readVLValue(input); + //std::cout << "ticks = " << longdata << std::endl; + absticks += longdata; + xstatus = extractMidiData(input, bytes, runningCommand); + if (xstatus == 0) { + m_rwstatus = false; return m_rwstatus; + } + event.setMessage(bytes); + //std::cout << "command = " << std::hex << (int)event.data[0] << std::dec << std::endl; + if (bytes[0] == 0xff && (bytes[1] == 1 || + bytes[1] == 2 || bytes[1] == 3 || bytes[1] == 4)) { + // mididata.push_back('\0'); + // std::cout << '\t'; + // for (int m=0; mpush_back(event); + break; + } + + if (bytes[0] != 0xff && bytes[0] != 0xf0) { + event.tick = absticks; + event.track = i; + m_events[i]->push_back(event); + } else { + event.tick = absticks; + event.track = i; + m_events[i]->push_back(event); + } + + } + + } + + m_theTimeState = TIME_STATE_ABSOLUTE; + markSequence(); + return m_rwstatus; +} + + + +////////////////////////////// +// +// MidiFile::write -- write a standard MIDI file to a file or an output +// stream. +// + +bool MidiFile::write(const std::string& filename) { + std::fstream output(filename.c_str(), std::ios::binary | std::ios::out); + + if (!output.is_open()) { + std::cerr << "Error: could not write: " << filename << std::endl; + return false; + } + m_rwstatus = write(output); + output.close(); + return m_rwstatus; +} + +// +// ostream version of MidiFile::write(). +// + +bool MidiFile::write(std::ostream& out) { + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_ABSOLUTE) { + makeDeltaTicks(); + } + + // write the header of the Standard MIDI File + char ch; + // 1. The characters "MThd" + ch = 'M'; out << ch; + ch = 'T'; out << ch; + ch = 'h'; out << ch; + ch = 'd'; out << ch; + + // 2. write the size of the header (always a "6" stored in unsigned long + // (4 bytes). + ulong longdata = 6; + writeBigEndianULong(out, longdata); + + // 3. MIDI file format, type 0, 1, or 2 + ushort shortdata; + shortdata = (getNumTracks() == 1) ? 0 : 1; + writeBigEndianUShort(out,shortdata); + + // 4. write out the number of tracks. + shortdata = getNumTracks(); + writeBigEndianUShort(out, shortdata); + + // 5. write out the number of ticks per quarternote. (avoiding SMTPE for now) + shortdata = getTicksPerQuarterNote(); + writeBigEndianUShort(out, shortdata); + + // now write each track. + std::vector trackdata; + uchar endoftrack[4] = {0, 0xff, 0x2f, 0x00}; + int i, j, k; + int size; + for (i=0; isize(); j++) { + if ((*m_events[i])[j].empty()) { + // Don't write empty m_events (probably a delete message). + continue; + } + if ((*m_events[i])[j].isEndOfTrack()) { + // Suppress end-of-track meta messages (one will be added + // automatically after all track data has been written). + continue; + } + writeVLValue((*m_events[i])[j].tick, trackdata); + if (((*m_events[i])[j].getCommandByte() == 0xf0) || + ((*m_events[i])[j].getCommandByte() == 0xf7)) { + // 0xf0 == Complete sysex message (0xf0 is part of the raw MIDI). + // 0xf7 == Raw byte message (0xf7 not part of the raw MIDI). + // Print the first byte of the message (0xf0 or 0xf7), then + // print a VLV length for the rest of the bytes in the message. + // In other words, when creating a 0xf0 or 0xf7 MIDI message, + // do not insert the VLV byte length yourself, as this code will + // do it for you automatically. + trackdata.push_back((*m_events[i])[j][0]); // 0xf0 or 0xf7; + writeVLValue(((int)(*m_events[i])[j].size())-1, trackdata); + for (k=1; k<(int)(*m_events[i])[j].size(); k++) { + trackdata.push_back((*m_events[i])[j][k]); + } + } else { + // non-sysex type of message, so just output the + // bytes of the message: + for (k=0; k<(int)(*m_events[i])[j].size(); k++) { + trackdata.push_back((*m_events[i])[j][k]); + } + } + } + size = (int)trackdata.size(); + if ((size < 3) || !((trackdata[size-3] == 0xff) + && (trackdata[size-2] == 0x2f))) { + trackdata.push_back(endoftrack[0]); + trackdata.push_back(endoftrack[1]); + trackdata.push_back(endoftrack[2]); + trackdata.push_back(endoftrack[3]); + } + + // now ready to write to MIDI file. + + // first write the track ID marker "MTrk": + ch = 'M'; out << ch; + ch = 'T'; out << ch; + ch = 'r'; out << ch; + ch = 'k'; out << ch; + + // A. write the size of the MIDI data to follow: + longdata = (int)trackdata.size(); + writeBigEndianULong(out, longdata); + + // B. write the actual data + out.write((char*)trackdata.data(), trackdata.size()); + } + + if (oldTimeState == TIME_STATE_ABSOLUTE) { + makeAbsoluteTicks(); + } + + return true; +} + + + +////////////////////////////// +// +// MidiFile::writeHex -- print the Standard MIDI file as a list of +// ASCII Hex bytes, formatted 25 to a line by default, and +// two digits for each hex byte code. If the input width is 0, +// then don't wrap lines. +// +// default value: width=25 +// + +bool MidiFile::writeHex(const std::string& filename, int width) { + std::fstream output(filename.c_str(), std::ios::out); + if (!output.is_open()) { + std::cerr << "Error: could not write: " << filename << std::endl; + return false; + } + m_rwstatus = writeHex(output, width); + output.close(); + return m_rwstatus; +} + +// +// ostream version of MidiFile::writeHex(). +// + +bool MidiFile::writeHex(std::ostream& out, int width) { + std::stringstream tempstream; + MidiFile::write(tempstream); + int len = (int)tempstream.str().length(); + int wordcount = 1; + int linewidth = width >= 0 ? width : 25; + for (int i=0; iremoveEmpties(); + } +} + + + +////////////////////////////// +// +// MidiFile::markSequence -- Assign a sequence serial number to +// every MidiEvent in every track in the MIDI file. This is +// useful if you want to preseve the order of MIDI messages in +// a track when they occur at the same tick time. Particularly +// for use with joinTracks() or sortTracks(). markSequence will +// be done automatically when a MIDI file is read, in case the +// ordering of m_events occuring at the same time is important. +// Use clearSequence() to use the default sorting behavior of +// sortTracks(). +// + +void MidiFile::markSequence(void) { + int sequence = 1; + for (int i=0; i= 0) && (track < getTrackCount())) { + operator[](track).markSequence(sequence); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + + + +////////////////////////////// +// +// MidiFile::clearSequence -- Remove any seqence serial numbers from +// MidiEvents in the MidiFile. This will cause the default ordering by +// sortTracks() to be used, in which case the ordering of MidiEvents +// occurring at the same tick may switch their ordering. +// + +void MidiFile::clearSequence(void) { + for (int i=0; i= 0) && (track < getTrackCount())) { + operator[](track).clearSequence(); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + + + +////////////////////////////// +// +// MidiFile::joinTracks -- Interleave the data from all tracks, +// but keeping the identity of the tracks unique so that +// the function splitTracks can be called to split the +// tracks into separate units again. The style of the +// MidiFile when read from a file is with tracks split. +// The original track index is stored in the MidiEvent::track +// variable. +// + +void MidiFile::joinTracks(void) { + if (getTrackState() == TRACK_STATE_JOINED) { + return; + } + if (getNumTracks() == 1) { + m_theTrackState = TRACK_STATE_JOINED; + return; + } + + MidiEventList* joinedTrack; + joinedTrack = new MidiEventList; + + int messagesum = 0; + int length = getNumTracks(); + int i, j; + for (i=0; ireserve((int)(messagesum + 32 + messagesum * 0.1)); + + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + for (i=0; isize(); j++) { + joinedTrack->push_back_no_copy(&(*m_events[i])[j]); + } + } + + clear_no_deallocate(); + + delete m_events[0]; + m_events.resize(0); + m_events.push_back(joinedTrack); + sortTracks(); + if (oldTimeState == TIME_STATE_DELTA) { + makeDeltaTicks(); + } + + m_theTrackState = TRACK_STATE_JOINED; +} + + + +////////////////////////////// +// +// MidiFile::splitTracks -- Take the joined tracks and split them +// back into their separate track identities. +// + +void MidiFile::splitTracks(void) { + if (getTrackState() == TRACK_STATE_SPLIT) { + return; + } + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + + int maxTrack = 0; + int i; + int length = m_events[0]->size(); + for (i=0; i maxTrack) { + maxTrack = (*m_events[0])[i].track; + } + } + int m_trackCount = maxTrack + 1; + + if (m_trackCount <= 1) { + return; + } + + MidiEventList* olddata = m_events[0]; + m_events[0] = NULL; + m_events.resize(m_trackCount); + for (i=0; ipush_back_no_copy(&(*olddata)[i]); + } + + olddata->detach(); + delete olddata; + + if (oldTimeState == TIME_STATE_DELTA) { + makeDeltaTicks(); + } + + m_theTrackState = TRACK_STATE_SPLIT; +} + + + +////////////////////////////// +// +// MidiFile::splitTracksByChannel -- Take the joined tracks and split them +// back into their separate track identities. +// + +void MidiFile::splitTracksByChannel(void) { + joinTracks(); + if (getTrackState() == TRACK_STATE_SPLIT) { + return; + } + + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + + int maxTrack = 0; + int i; + MidiEventList& eventlist = *m_events[0]; + MidiEventList* olddata = &eventlist; + int length = eventlist.size(); + for (i=0; i 0) { + trackValue = (eventlist[i][0] & 0x0f) + 1; + } + m_events[trackValue]->push_back_no_copy(&eventlist[i]); + } + + olddata->detach(); + delete olddata; + + if (oldTimeState == TIME_STATE_DELTA) { + makeDeltaTicks(); + } + + m_theTrackState = TRACK_STATE_SPLIT; +} + + + +////////////////////////////// +// +// MidiFile::getTrackState -- returns what type of track method +// is being used: either TRACK_STATE_JOINED or TRACK_STATE_SPLIT. +// + +int MidiFile::getTrackState(void) const { + return m_theTrackState; +} + + + +////////////////////////////// +// +// MidiFile::hasJoinedTracks -- Returns true if the MidiFile tracks +// are in a joined state. +// + +int MidiFile::hasJoinedTracks(void) const { + return m_theTrackState == TRACK_STATE_JOINED; +} + + + +////////////////////////////// +// +// MidiFile::hasSplitTracks -- Returns true if the MidiFile tracks +// are in a split state. +// + +int MidiFile::hasSplitTracks(void) const { + return m_theTrackState == TRACK_STATE_SPLIT; +} + + + +////////////////////////////// +// +// MidiFile::getSplitTrack -- Return the track index when the MidiFile +// is in the split state. This function returns the original track +// when the MidiFile is in the joined state. The MidiEvent::track +// variable is used to store the original track index when the +// MidiFile is converted to the joined-track state. +// + +int MidiFile::getSplitTrack(int track, int index) const { + if (hasSplitTracks()) { + return track; + } else { + return getEvent(track, index).track; + } +} + +// +// When the parameter is void, assume track 0: +// + +int MidiFile::getSplitTrack(int index) const { + if (hasSplitTracks()) { + return 0; + } else { + return getEvent(0, index).track; + } +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// tick-related functions -- +// + +////////////////////////////// +// +// MidiFile::makeDeltaTicks -- convert the time data to +// delta time, which means that the time field +// in the MidiEvent struct represents the time +// since the last event was played. When a MIDI file +// is read from a file, this is the default setting. +// + +void MidiFile::makeDeltaTicks(void) { + if (getTickState() == TIME_STATE_DELTA) { + return; + } + int i, j; + int temp; + int length = getNumTracks(); + int *timedata = new int[length]; + for (i=0; isize() > 0) { + timedata[i] = (*m_events[i])[0].tick; + } else { + continue; + } + for (j=1; j<(int)m_events[i]->size(); j++) { + temp = (*m_events[i])[j].tick; + int deltatick = temp - timedata[i]; + if (deltatick < 0) { + std::cerr << "Error: negative delta tick value: " << deltatick << std::endl + << "Timestamps must be sorted first" + << " (use MidiFile::sortTracks() before writing)." << std::endl; + } + (*m_events[i])[j].tick = deltatick; + timedata[i] = temp; + } + } + m_theTimeState = TIME_STATE_DELTA; + delete [] timedata; +} + +// +// MidiFile::deltaTicks -- Alias for MidiFile::makeDeltaTicks(). +// + +void MidiFile::deltaTicks(void) { + makeDeltaTicks(); +} + + + +////////////////////////////// +// +// MidiFile::makeAbsoluteTicks -- convert the time data to +// absolute time, which means that the time field +// in the MidiEvent struct represents the exact tick +// time to play the event rather than the time since +// the last event to wait untill playing the current +// event. +// + +void MidiFile::makeAbsoluteTicks(void) { + if (getTickState() == TIME_STATE_ABSOLUTE) { + return; + } + int i, j; + int length = getNumTracks(); + int* timedata = new int[length]; + for (i=0; isize() > 0) { + timedata[i] = (*m_events[i])[0].tick; + } else { + continue; + } + for (j=1; j<(int)m_events[i]->size(); j++) { + timedata[i] += (*m_events[i])[j].tick; + (*m_events[i])[j].tick = timedata[i]; + } + } + m_theTimeState = TIME_STATE_ABSOLUTE; + delete [] timedata; +} + +// +// MidiFile::absoluteTicks -- Alias for MidiFile::makeAbsoluteTicks(). +// + +void MidiFile::absoluteTicks(void) { + makeAbsoluteTicks(); +} + + + +////////////////////////////// +// +// MidiFile::getTickState -- returns what type of time method is +// being used: either TIME_STATE_ABSOLUTE or TIME_STATE_DELTA. +// + +int MidiFile::getTickState(void) const { + return m_theTimeState; +} + + + +////////////////////////////// +// +// MidiFile::isDeltaTicks -- Returns true if MidiEvent .tick +// variables are in delta time mode. +// + +bool MidiFile::isDeltaTicks(void) const { + return m_theTimeState == TIME_STATE_DELTA ? true : false; +} + + + +////////////////////////////// +// +// MidiFile::isAbsoluteTicks -- Returns true if MidiEvent .tick +// variables are in absolute time mode. +// + +bool MidiFile::isAbsoluteTicks(void) const { + return m_theTimeState == TIME_STATE_ABSOLUTE ? true : false; +} + + + +////////////////////////////// +// +// MidiFile::getFileDurationInTicks -- Returns the largest +// tick value in any track. The tracks must be sorted +// before calling this function, since this function +// assumes that the last MidiEvent in the track has the +// highest tick timestamp. The file state can be in delta +// ticks since this function will temporarily go to absolute +// tick mode for the calculation of the max tick. +// + +int MidiFile::getFileDurationInTicks(void) { + bool revertToDelta = false; + if (isDeltaTicks()) { + makeAbsoluteTicks(); + revertToDelta = true; + } + const MidiFile& mf = *this; + int output = 0; + for (int i=0; i output) { + output = mf[i].back().tick; + } + } + if (revertToDelta) { + deltaTicks(); + } + return output; +} + + + +/////////////////////////////// +// +// MidiFile::getFileDurationInQuarters -- Returns the Duration of the MidiFile +// in units of quarter notes. If the MidiFile is in delta tick mode, +// then temporarily got into absolute tick mode to do the calculations. +// Note that this is expensive, so you should normally call this function +// while in aboslute tick (default) mode. +// + +double MidiFile::getFileDurationInQuarters(void) { + return (double)getFileDurationInTicks() / (double)getTicksPerQuarterNote(); +} + + + +////////////////////////////// +// +// MidiFile::getFileDurationInSeconds -- returns the duration of the +// logest track in the file. The tracks must be sorted before +// calling this function, since this function assumes that the +// last MidiEvent in the track has the highest timestamp. +// The file state can be in delta ticks since this function +// will temporarily go to absolute tick mode for the calculation +// of the max time. + +double MidiFile::getFileDurationInSeconds(void) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + bool revertToDelta = false; + if (isDeltaTicks()) { + makeAbsoluteTicks(); + revertToDelta = true; + } + const MidiFile& mf = *this; + double output = 0.0; + for (int i=0; i output) { + output = mf[i].back().seconds; + } + } + if (revertToDelta) { + deltaTicks(); + } + return output; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// physical-time analysis functions -- +// + +////////////////////////////// +// +// MidiFile::doTimeAnalysis -- Identify the real-time position of +// all events by monitoring the tempo in relations to the tick +// times in the file. +// + +void MidiFile::doTimeAnalysis(void) { + buildTimeMap(); +} + + + +////////////////////////////// +// +// MidiFile::getTimeInSeconds -- return the time in seconds for +// the current message. +// + +double MidiFile::getTimeInSeconds(int aTrack, int anIndex) { + return getTimeInSeconds(getEvent(aTrack, anIndex).tick); +} + + +double MidiFile::getTimeInSeconds(int tickvalue) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + _TickTime key; + key.tick = tickvalue; + key.seconds = -1; + + void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(), + sizeof(_TickTime), ticksearch); + + if (ptr == NULL) { + // The specific tick value was not found, so do a linear + // search for the two tick values which occur before and + // after the tick value, and do a linear interpolation of + // the time in seconds values to figure out the final + // time in seconds. + // Since the code is not yet written, kill the program at this point: + return linearSecondInterpolationAtTick(tickvalue); + } else { + return ((_TickTime*)ptr)->seconds; + } +} + + + +////////////////////////////// +// +// MidiFile::getAbsoluteTickTime -- return the tick value represented +// by the input time in seconds. If there is not tick entry at +// the given time in seconds, then interpolate between two values. +// + +double MidiFile::getAbsoluteTickTime(double starttime) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + } + + _TickTime key; + key.tick = -1; + key.seconds = starttime; + + void* ptr = bsearch(&key, m_timemap.data(), m_timemap.size(), + sizeof(_TickTime), secondsearch); + + if (ptr == NULL) { + // The specific seconds value was not found, so do a linear + // search for the two time values which occur before and + // after the given time value, and do a linear interpolation of + // the time in tick values to figure out the final time in ticks. + return linearTickInterpolationAtSecond(starttime); + } else { + return ((_TickTime*)ptr)->tick; + } + +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// note-analysis functions -- +// + +////////////////////////////// +// +// MidiFile::linkNotePairs -- Link note-ons to note-offs separately +// for each track. Returns the total number of note message pairs +// that were linked. +// + +int MidiFile::linkNotePairs(void) { + int i; + int sum = 0; + for (i=0; ilinkNotePairs(); + } + m_linkedEventsQ = true; + return sum; +} + +// +// MidiFile::linkEventPairs -- Alias for MidiFile::linkNotePairs(). +// + +int MidiFile::linkEventPairs(void) { + return linkNotePairs(); +} + + +/////////////////////////////////////////////////////////////////////////// +// +// filename functions -- +// + +////////////////////////////// +// +// MidiFile::setFilename -- sets the filename of the MIDI file. +// Currently removed any directory path. +// + +void MidiFile::setFilename(const std::string& aname) { + auto loc = aname.rfind('/'); + if (loc != std::string::npos) { + m_readFileName = aname.substr(loc+1); + } else { + m_readFileName = aname; + } +} + + + +////////////////////////////// +// +// MidiFile::getFilename -- returns the name of the file read into the +// structure (if the data was read from a file). +// + +const char* MidiFile::getFilename(void) const { + return m_readFileName.c_str(); +} + + + +////////////////////////////// +// +// MidiFile::addEvent -- +// + +MidiEvent* MidiFile::addEvent(int aTrack, int aTick, + std::vector& midiData) { + m_timemapvalid = 0; + MidiEvent* me = new MidiEvent; + me->tick = aTick; + me->track = aTrack; + me->setMessage(midiData); + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addEvent -- Some bug here when joinedTracks(), but track==1... +// + +MidiEvent* MidiFile::addEvent(MidiEvent& mfevent) { + if (getTrackState() == TRACK_STATE_JOINED) { + m_events[0]->push_back(mfevent); + return &m_events[0]->back(); + } else { + m_events.at(mfevent.track)->push_back(mfevent); + return &m_events.at(mfevent.track)->back(); + } +} + +// +// Variant where the track is an input parameter: +// + +MidiEvent* MidiFile::addEvent(int aTrack, MidiEvent& mfevent) { + if (getTrackState() == TRACK_STATE_JOINED) { + m_events[0]->push_back(mfevent); + m_events[0]->back().track = aTrack; + return &m_events[0]->back(); + } else { + m_events.at(aTrack)->push_back(mfevent); + m_events.at(aTrack)->back().track = aTrack; + return &m_events.at(aTrack)->back(); + } +} + + + +/////////////////////////////// +// +// MidiFile::addMetaEvent -- +// + +MidiEvent* MidiFile::addMetaEvent(int aTrack, int aTick, int aType, + std::vector& metaData) { + m_timemapvalid = 0; + int i; + int length = (int)metaData.size(); + std::vector fulldata; + uchar size[23] = {0}; + int lengthsize = makeVLV(size, length); + + fulldata.resize(2+lengthsize+length); + fulldata[0] = 0xff; + fulldata[1] = aType & 0x7F; + for (i=0; i buffer; + buffer.resize(length); + int i; + for (i=0; imakeText(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addCopyright -- Add a copyright notice meta-message (#2). +// + +MidiEvent* MidiFile::addCopyright(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeCopyright(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTrackName -- Add an track name meta-message (#3). +// + +MidiEvent* MidiFile::addTrackName(int aTrack, int aTick, const std::string& name) { + MidiEvent* me = new MidiEvent; + me->makeTrackName(name); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addInstrumentName -- Add an instrument name meta-message (#4). +// + +MidiEvent* MidiFile::addInstrumentName(int aTrack, int aTick, + const std::string& name) { + MidiEvent* me = new MidiEvent; + me->makeInstrumentName(name); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addLyric -- Add a lyric meta-message (meta #5). +// + +MidiEvent* MidiFile::addLyric(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeLyric(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addMarker -- Add a marker meta-message (meta #6). +// + +MidiEvent* MidiFile::addMarker(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeMarker(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addCue -- Add a cue-point meta-message (meta #7). +// + +MidiEvent* MidiFile::addCue(int aTrack, int aTick, const std::string& text) { + MidiEvent* me = new MidiEvent; + me->makeCue(text); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTempo -- Add a tempo meta message (meta #0x51). +// + +MidiEvent* MidiFile::addTempo(int aTrack, int aTick, double aTempo) { + MidiEvent* me = new MidiEvent; + me->makeTempo(aTempo); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTimeSignature -- Add a time signature meta message +// (meta #0x58). The "bottom" parameter must be a power of two; +// otherwise, it will be set to the next highest power of two. +// +// Default values: +// clocksPerClick == 24 (quarter note) +// num32ndsPerQuarter == 8 (8 32nds per quarter note) +// +// Time signature of 4/4 would be: +// top = 4 +// bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2). +// clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// +// Time signature of 6/8 would be: +// top = 6 +// bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2). +// clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// + +MidiEvent* MidiFile::addTimeSignature(int aTrack, int aTick, int top, int bottom, + int clocksPerClick, int num32ndsPerQuarter) { + MidiEvent* me = new MidiEvent; + me->makeTimeSignature(top, bottom, clocksPerClick, num32ndsPerQuarter); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addCompoundTimeSignature -- Add a time signature meta message +// (meta #0x58), where the clocksPerClick parameter is set to three +// eighth notes for compount meters such as 6/8 which represents +// two beats per measure. +// +// Default values: +// clocksPerClick == 36 (quarter note) +// num32ndsPerQuarter == 8 (8 32nds per quarter note) +// + +MidiEvent* MidiFile::addCompoundTimeSignature(int aTrack, int aTick, int top, + int bottom, int clocksPerClick, int num32ndsPerQuarter) { + return addTimeSignature(aTrack, aTick, top, bottom, clocksPerClick, + num32ndsPerQuarter); +} + + + +////////////////////////////// +// +// MidiFile::makeVLV -- This function is used to create +// size byte(s) for meta-messages. If the size of the data +// in the meta-message is greater than 127, then the size +// should (?) be specified as a VLV. +// + +int MidiFile::makeVLV(uchar *buffer, int number) { + + unsigned long value = (unsigned long)number; + + if (value >= (1 << 28)) { + std::cerr << "Error: Meta-message size too large to handle" << std::endl; + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + return 1; + } + + buffer[0] = (value >> 21) & 0x7f; + buffer[1] = (value >> 14) & 0x7f; + buffer[2] = (value >> 7) & 0x7f; + buffer[3] = (value >> 0) & 0x7f; + + int i; + int flag = 0; + int length = -1; + for (i=0; i<3; i++) { + if (buffer[i] != 0) { + flag = 1; + } + if (flag) { + buffer[i] |= 0x80; + } + if (length == -1 && buffer[i] >= 0x80) { + length = 4-i; + } + } + + if (length == -1) { + length = 1; + } + + if (length < 4) { + for (i=0; imakeNoteOn(aChannel, key, vel); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addNoteOff -- Add a note-off message (using 0x80 messages). +// + +MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key, + int vel) { + MidiEvent* me = new MidiEvent; + me->makeNoteOff(aChannel, key, vel); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addNoteOff -- Add a note-off message (using 0x90 messages with +// zero attack velocity). +// + +MidiEvent* MidiFile::addNoteOff(int aTrack, int aTick, int aChannel, int key) { + MidiEvent* me = new MidiEvent; + me->makeNoteOff(aChannel, key); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addController -- Add a controller message in the given +// track at the given tick time in the given channel. +// + +MidiEvent* MidiFile::addController(int aTrack, int aTick, int aChannel, + int num, int value) { + MidiEvent* me = new MidiEvent; + me->makeController(aChannel, num, value); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addPatchChange -- Add a patch-change message in the given +// track at the given tick time in the given channel. +// + +MidiEvent* MidiFile::addPatchChange(int aTrack, int aTick, int aChannel, + int patchnum) { + MidiEvent* me = new MidiEvent; + me->makePatchChange(aChannel, patchnum); + me->tick = aTick; + m_events[aTrack]->push_back_no_copy(me); + return me; +} + + + +////////////////////////////// +// +// MidiFile::addTimbre -- Add a patch-change message in the given +// track at the given tick time in the given channel. Alias for +// MidiFile::addPatchChange(). +// + +MidiEvent* MidiFile::addTimbre(int aTrack, int aTick, int aChannel, int patchnum) { + return addPatchChange(aTrack, aTick, aChannel, patchnum); +} + + + +////////////////////////////// +// +// MidiFile::addPitchBend -- convert number in the range from -1 to +1 +// into two 7-bit numbers (smallest piece first) +// +// -1.0 maps to 0 (0x0000) +// 0.0 maps to 8192 (0x2000 --> 0x40 0x00) +// +1.0 maps to 16383 (0x3FFF --> 0x7F 0x7F) +// + +MidiEvent* MidiFile::addPitchBend(int aTrack, int aTick, int aChannel, double amount) { + m_timemapvalid = 0; + amount += 1.0; + int value = int(amount * 8192 + 0.5); + + // prevent any wrap-around in case of round-off errors + if (value > 0x3fff) { + value = 0x3fff; + } + if (value < 0) { + value = 0; + } + + int lsbint = 0x7f & value; + int msbint = 0x7f & (value >> 7); + + std::vector mididata; + mididata.resize(3); + if (aChannel < 0) { + aChannel = 0; + } else if (aChannel > 15) { + aChannel = 15; + } + mididata[0] = uchar(0xe0 | aChannel); + mididata[1] = uchar(lsbint); + mididata[2] = uchar(msbint); + + return addEvent(aTrack, aTick, mididata); +} + + +/////////////////////////////////////////////////////////////////////////// +// +// Controller message adding convenience functions: +// + +////////////////////////////// +// +// MidiFile::addSustain -- Add a continuous controller message for the sustain pedal. +// + +MidiEvent* MidiFile::addSustain(int aTrack, int aTick, int aChannel, int value) { + return addController(aTrack, aTick, aChannel, 64, value); +} + +// +// MidiFile::addSustainPedal -- Alias for MidiFile::addSustain(). +// + +MidiEvent* MidiFile::addSustainPedal(int aTrack, int aTick, int aChannel, int value) { + return addSustain(aTrack, aTick, aChannel, value); +} + + + +////////////////////////////// +// +// MidiFile::addSustainOn -- Add a continuous controller message for the sustain pedal on. +// + +MidiEvent* MidiFile::addSustainOn(int aTrack, int aTick, int aChannel) { + return addSustain(aTrack, aTick, aChannel, 127); +} + +// +// MidiFile::addSustainPedalOn -- Alias for MidiFile::addSustainOn(). +// + +MidiEvent* MidiFile::addSustainPedalOn(int aTrack, int aTick, int aChannel) { + return addSustainOn(aTrack, aTick, aChannel); +} + + + +////////////////////////////// +// +// MidiFile::addSustainOff -- Add a continuous controller message for the sustain pedal off. +// + +MidiEvent* MidiFile::addSustainOff(int aTrack, int aTick, int aChannel) { + return addSustain(aTrack, aTick, aChannel, 0); +} + +// +// MidiFile::addSustainPedalOff -- Alias for MidiFile::addSustainOff(). +// + +MidiEvent* MidiFile::addSustainPedalOff(int aTrack, int aTick, int aChannel) { + return addSustainOff(aTrack, aTick, aChannel); +} + + + +////////////////////////////// +// +// MidiFile::addTrack -- adds a blank track at end of the +// track list. Returns the track number of the added +// track. +// + +int MidiFile::addTrack(void) { + int length = getNumTracks(); + m_events.resize(length+1); + m_events[length] = new MidiEventList; + m_events[length]->reserve(10000); + m_events[length]->clear(); + return length; +} + +int MidiFile::addTrack(int count) { + int length = getNumTracks(); + m_events.resize(length+count); + int i; + for (i=0; ireserve(10000); + m_events[length + i]->clear(); + } + return length + count - 1; +} + +// +// MidiFile::addTracks -- Alias for MidiFile::addTrack(). +// + +int MidiFile::addTracks(int count) { + return addTrack(count); +} + + + + +////////////////////////////// +// +// MidiFile::allocateEvents -- +// + +void MidiFile::allocateEvents(int track, int aSize) { + int oldsize = m_events[track]->size(); + if (oldsize < aSize) { + m_events[track]->reserve(aSize); + } +} + + + +////////////////////////////// +// +// MidiFile::deleteTrack -- remove a track from the MidiFile. +// Tracks are numbered starting at track 0. +// + +void MidiFile::deleteTrack(int aTrack) { + int length = getNumTracks(); + if (aTrack < 0 || aTrack >= length) { + return; + } + if (length == 1) { + return; + } + delete m_events[aTrack]; + for (int i=aTrack; isize(); +} + + +int MidiFile::getNumEvents(int aTrack) const { + return m_events[aTrack]->size(); +} + + + +////////////////////////////// +// +// MidiFile::mergeTracks -- combine the data from two +// tracks into one. Placing the data in the first +// track location listed, and Moving the other tracks +// in the file around to fill in the spot where Track2 +// used to be. The results of this function call cannot +// be reversed. +// + +void MidiFile::mergeTracks(int aTrack1, int aTrack2) { + MidiEventList* mergedTrack; + mergedTrack = new MidiEventList; + int oldTimeState = getTickState(); + if (oldTimeState == TIME_STATE_DELTA) { + makeAbsoluteTicks(); + } + int length = getNumTracks(); + for (int i=0; i<(int)m_events[aTrack1]->size(); i++) { + mergedTrack->push_back((*m_events[aTrack1])[i]); + } + for (int j=0; j<(int)m_events[aTrack2]->size(); j++) { + (*m_events[aTrack2])[j].track = aTrack1; + mergedTrack->push_back((*m_events[aTrack2])[j]); + } + + mergedTrack->sort(); + + delete m_events[aTrack1]; + + m_events[aTrack1] = mergedTrack; + + for (int i=aTrack2; isize(); j++) { + (*m_events[i])[j].track = i; + } + } + + m_events[length-1] = NULL; + m_events.resize(length-1); + + if (oldTimeState == TIME_STATE_DELTA) { + deltaTicks(); + } +} + + + +////////////////////////////// +// +// MidiFile::setTicksPerQuarterNote -- +// + +void MidiFile::setTicksPerQuarterNote(int ticks) { + m_ticksPerQuarterNote = ticks; +} + +// +// Alias for setTicksPerQuarterNote: +// + +void MidiFile::setTPQ(int ticks) { + setTicksPerQuarterNote(ticks); +} + + +////////////////////////////// +// +// MidiFile::setMillisecondTicks -- set the ticks per quarter note +// value to milliseconds. The format for this specification is +// highest 8-bits: SMPTE Frame rate (as a negative 2's compliment value). +// lowest 8-bits: divisions per frame (as a positive number). +// for millisecond resolution, the SMPTE value is -25, and the +// frame rate is 40 frame per division. In hexadecimal, these +// values are: -25 = 1110,0111 = 0xE7 and 40 = 0010,1000 = 0x28 +// So setting the ticks per quarter note value to 0xE728 will cause +// delta times in the MIDI file to represent milliseconds. Calling +// this function will not change any exiting timestamps, it will +// only change the meaning of the timestamps. +// + +void MidiFile::setMillisecondTicks(void) { + m_ticksPerQuarterNote = 0xE728; +} + + + +////////////////////////////// +// +// MidiFile::sortTrack -- Sort the specified track in tick order. +// If the MidiEvent::seq variables have been filled in with +// a sequence value, this will preserve the order of the +// events that occur at the same tick time before the sort +// was done. +// + +void MidiFile::sortTrack(int track) { + if ((track >= 0) && (track < getTrackCount())) { + m_events.at(track)->sort(); + } else { + std::cerr << "Warning: track " << track << " does not exist." << std::endl; + } +} + + + +////////////////////////////// +// +// MidiFile::sortTracks -- sort all tracks in the MidiFile. +// + +void MidiFile::sortTracks(void) { + if (m_theTimeState == TIME_STATE_ABSOLUTE) { + for (int i=0; isort(); + } + } else { + std::cerr << "Warning: Sorting only allowed in absolute tick mode."; + } +} + + + +////////////////////////////// +// +// MidiFile::getTrackCountAsType1 -- Return the number of tracks in the +// MIDI file. Returns the size of the events if not in joined state. +// If in joined state, reads track 0 to find the maximum track +// value from the original unjoined tracks. +// + +int MidiFile::getTrackCountAsType1(void) { + if (getTrackState() == TRACK_STATE_JOINED) { + int output = 0; + int i; + for (i=0; i<(int)m_events[0]->size(); i++) { + if (getEvent(0,i).track > output) { + output = getEvent(0,i).track; + } + } + return output+1; // I think the track values are 0 offset... + } else { + return (int)m_events.size(); + } +} + + + +////////////////////////////// +// +// MidiFile::clearLinks -- +// + +void MidiFile::clearLinks(void) { + for (int i=0; iclearLinks(); + } + m_linkedEventsQ = false; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// private functions +// + +////////////////////////////// +// +// MidiFile::linearTickInterpolationAtSecond -- return the tick value at the +// given input time. +// + +double MidiFile::linearTickInterpolationAtSecond(double seconds) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + int i; + double lasttime = m_timemap[m_timemap.size()-1].seconds; + // give an error value of -1 if time is out of range of data. + if (seconds < 0.0) { + return -1.0; + } + if (seconds > m_timemap[m_timemap.size()-1].seconds) { + return -1.0; + } + + // Guess which side of the list is closest to target: + // Could do a more efficient algorithm since time values are sorted, + // but good enough for now... + int startindex = -1; + if (seconds < lasttime / 2) { + for (i=0; i<(int)m_timemap.size(); i++) { + if (m_timemap[i].seconds > seconds) { + startindex = i-1; + break; + } else if (m_timemap[i].seconds == seconds) { + startindex = i; + break; + } + } + } else { + for (i=(int)m_timemap.size()-1; i>0; i--) { + if (m_timemap[i].seconds < seconds) { + startindex = i+1; + break; + } else if (m_timemap[i].seconds == seconds) { + startindex = i; + break; + } + } + } + + if (startindex < 0) { + return -1.0; + } + if (startindex >= (int)m_timemap.size()-1) { + return -1.0; + } + + double x1 = m_timemap[startindex].seconds; + double x2 = m_timemap[startindex+1].seconds; + double y1 = m_timemap[startindex].tick; + double y2 = m_timemap[startindex+1].tick; + double xi = seconds; + + return (xi-x1) * ((y2-y1)/(x2-x1)) + y1; +} + + + +////////////////////////////// +// +// MidiFile::linearSecondInterpolationAtTick -- return the time in seconds +// value at the given input tick time. (Ticks input could be made double). +// + +double MidiFile::linearSecondInterpolationAtTick(int ticktime) { + if (m_timemapvalid == 0) { + buildTimeMap(); + if (m_timemapvalid == 0) { + return -1.0; // something went wrong + } + } + + int i; + double lasttick = m_timemap[m_timemap.size()-1].tick; + // give an error value of -1 if time is out of range of data. + if (ticktime < 0.0) { + return -1; + } + if (ticktime > m_timemap.back().tick) { + return -1; // don't try to extrapolate + } + + // Guess which side of the list is closest to target: + // Could do a more efficient algorithm since time values are sorted, + // but good enough for now... + int startindex = -1; + if (ticktime < lasttick / 2) { + for (i=0; i<(int)m_timemap.size(); i++) { + if (m_timemap[i].tick > ticktime) { + startindex = i-1; + break; + } else if (m_timemap[i].tick == ticktime) { + startindex = i; + break; + } + } + } else { + for (i=(int)m_timemap.size()-1; i>0; i--) { + if (m_timemap[i].tick < ticktime) { + startindex = i; + break; + } else if (m_timemap[i].tick == ticktime) { + startindex = i; + break; + } + } + } + + if (startindex < 0) { + return -1; + } + if (startindex >= (int)m_timemap.size()-1) { + return -1; + } + + if (m_timemap[startindex].tick == ticktime) { + return m_timemap[startindex].seconds; + } + + double x1 = m_timemap[startindex].tick; + double x2 = m_timemap[startindex+1].tick; + double y1 = m_timemap[startindex].seconds; + double y2 = m_timemap[startindex+1].seconds; + double xi = ticktime; + + return (xi-x1) * ((y2-y1)/(x2-x1)) + y1; +} + + + +////////////////////////////// +// +// MidiFile::buildTimeMap -- build an index of the absolute tick values +// found in a MIDI file, and their corresponding time values in +// seconds, taking into consideration tempo change messages. If no +// tempo messages are given (or untill they are given, then the +// tempo is set to 120 beats per minute). If SMPTE time code is +// used, then ticks are actually time values. So don't build +// a time map for SMPTE ticks, and just calculate the time in +// seconds from the tick value (1000 ticks per second SMPTE +// is the only mode tested (25 frames per second and 40 subframes +// per frame). +// + +void MidiFile::buildTimeMap(void) { + + // convert the MIDI file to absolute time representation + // in single track mode (and undo if the MIDI file was not + // in that state when this function was called. + // + int trackstate = getTrackState(); + int timestate = getTickState(); + + makeAbsoluteTicks(); + joinTracks(); + + int allocsize = getNumEvents(0); + m_timemap.reserve(allocsize+10); + m_timemap.clear(); + + _TickTime value; + + int lasttick = 0; + int tickinit = 0; + + int i; + int tpq = getTicksPerQuarterNote(); + double defaultTempo = 120.0; + double secondsPerTick = 60.0 / (defaultTempo * tpq); + + double lastsec = 0.0; + double cursec = 0.0; + + for (i=0; i lasttick) || !tickinit) { + tickinit = 1; + + // calculate the current time in seconds: + cursec = lastsec + (curtick - lasttick) * secondsPerTick; + getEvent(0, i).seconds = cursec; + + // store the new tick to second mapping + value.tick = curtick; + value.seconds = cursec; + m_timemap.push_back(value); + lasttick = curtick; + lastsec = cursec; + } + + // update the tempo if needed: + if (getEvent(0,i).isTempo()) { + secondsPerTick = getEvent(0,i).getTempoSPT(getTicksPerQuarterNote()); + } + } + + // reset the states of the tracks or time values if necessary here: + if (timestate == TIME_STATE_DELTA) { + deltaTicks(); + } + if (trackstate == TRACK_STATE_SPLIT) { + splitTracks(); + } + + m_timemapvalid = 1; + +} + + + +////////////////////////////// +// +// MidiFile::extractMidiData -- Extract MIDI data from input +// stream. Return value is 0 if failure; otherwise, returns 1. +// + +int MidiFile::extractMidiData(std::istream& input, std::vector& array, + uchar& runningCommand) { + + int character; + uchar byte; + array.clear(); + int runningQ; + + character = input.get(); + if (character == EOF) { + std::cerr << "Error: unexpected end of file." << std::endl; + return 0; + } else { + byte = (uchar)character; + } + + if (byte < 0x80) { + runningQ = 1; + if (runningCommand == 0) { + std::cerr << "Error: running command with no previous command" << std::endl; + return 0; + } + if (runningCommand >= 0xf0) { + std::cerr << "Error: running status not permitted with meta and sysex" + << " event." << std::endl; + std::cerr << "Byte is 0x" << std::hex << (int)byte << std::dec << std::endl; + return 0; + } + } else { + runningCommand = byte; + runningQ = 0; + } + + array.push_back(runningCommand); + if (runningQ) { + array.push_back(byte); + } + + switch (runningCommand & 0xf0) { + case 0x80: // note off (2 more bytes) + case 0x90: // note on (2 more bytes) + case 0xA0: // aftertouch (2 more bytes) + case 0xB0: // cont. controller (2 more bytes) + case 0xE0: // pitch wheel (2 more bytes) + byte = readByte(input); + if (!status()) { return m_rwstatus; } + if (byte > 0x7f) { + std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; + m_rwstatus = false; return m_rwstatus; + } + array.push_back(byte); + if (!runningQ) { + byte = readByte(input); + if (!status()) { return m_rwstatus; } + if (byte > 0x7f) { + std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; + m_rwstatus = false; return m_rwstatus; + } + array.push_back(byte); + } + break; + case 0xC0: // patch change (1 more byte) + case 0xD0: // channel pressure (1 more byte) + if (!runningQ) { + byte = readByte(input); + if (!status()) { return m_rwstatus; } + if (byte > 0x7f) { + std::cerr << "MIDI data byte too large: " << (int)byte << std::endl; + m_rwstatus = false; return m_rwstatus; + } + array.push_back(byte); + } + break; + case 0xF0: + switch (runningCommand) { + case 0xff: // meta event + { + if (!runningQ) { + byte = readByte(input); // meta type + if (!status()) { return m_rwstatus; } + array.push_back(byte); + } + ulong length = 0; + uchar byte1 = 0; + uchar byte2 = 0; + uchar byte3 = 0; + uchar byte4 = 0; + byte1 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte1); + if (byte1 >= 0x80) { + byte2 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte2); + if (byte2 > 0x80) { + byte3 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte3); + if (byte3 >= 0x80) { + byte4 = readByte(input); + if (!status()) { return m_rwstatus; } + array.push_back(byte4); + if (byte4 >= 0x80) { + std::cerr << "Error: cannot handle large VLVs" << std::endl; + m_rwstatus = false; return m_rwstatus; + } else { + length = unpackVLV(byte1, byte2, byte3, byte4); + if (!m_rwstatus) { return m_rwstatus; } + } + } else { + length = unpackVLV(byte1, byte2, byte3); + if (!m_rwstatus) { return m_rwstatus; } + } + } else { + length = unpackVLV(byte1, byte2); + if (!m_rwstatus) { return m_rwstatus; } + } + } else { + length = byte1; + } + for (int j=0; j<(int)length; j++) { + byte = readByte(input); // meta type + if (!status()) { return m_rwstatus; } + array.push_back(byte); + } + } + break; + + // The 0xf0 and 0xf7 meta commands deal with system-exclusive + // messages. 0xf0 is used to either start a message or to store + // a complete message. The 0xf0 is part of the outgoing MIDI + // bytes. The 0xf7 message is used to send arbitrary bytes, + // typically the middle or ends of system exclusive messages. The + // 0xf7 byte at the start of the message is not part of the + // outgoing raw MIDI bytes, but is kept in the MidiFile message + // to indicate a raw MIDI byte message (typically a partial + // system exclusive message). + case 0xf7: // Raw bytes. 0xf7 is not part of the raw + // bytes, but are included to indicate + // that this is a raw byte message. + case 0xf0: // System Exclusive message + { // (complete, or start of message). + int length = (int)readVLValue(input); + for (int i=0; i 0x7f)) { + count++; + } + count++; + if (count >= 6) { + std::cerr << "VLV number is too large" << std::endl; + m_rwstatus = false; + return 0; + } + + ulong output = 0; + for (int i=0; i& outdata) { + uchar bytes[4] = {0}; + + if ((unsigned long)aValue >= (1 << 28)) { + std::cerr << "Error: number too large to convert to VLV" << std::endl; + aValue = 0x0FFFffff; + } + + bytes[0] = (uchar)(((ulong)aValue >> 21) & 0x7f); // most significant 7 bits + bytes[1] = (uchar)(((ulong)aValue >> 14) & 0x7f); + bytes[2] = (uchar)(((ulong)aValue >> 7) & 0x7f); + bytes[3] = (uchar)(((ulong)aValue) & 0x7f); // least significant 7 bits + + int start = 0; + while ((start<4) && (bytes[start] == 0)) start++; + + for (int i=start; i<3; i++) { + bytes[i] = bytes[i] | 0x80; + outdata.push_back(bytes[i]); + } + outdata.push_back(bytes[3]); +} + + + +////////////////////////////// +// +// MidiFile::clear_no_deallocate -- Similar to clear() but does not +// delete the Events in the lists. This is primarily used internally +// to the MidiFile class, so don't use unless you really know what you +// are doing (otherwise you will end up with memory leaks or +// segmentation faults). +// + +void MidiFile::clear_no_deallocate(void) { + for (int i=0; idetach(); + delete m_events[i]; + m_events[i] = NULL; + } + m_events.resize(1); + m_events[0] = new MidiEventList; + m_timemapvalid=0; + m_timemap.clear(); + // m_events.resize(0); // causes a memory leak [20150205 Jorden Thatcher] +} + + + +////////////////////////////// +// +// MidiFile::ticksearch -- for finding a tick entry in the time map. +// + +int MidiFile::ticksearch(const void* A, const void* B) { + _TickTime& a = *((_TickTime*)A); + _TickTime& b = *((_TickTime*)B); + + if (a.tick < b.tick) { + return -1; + } else if (a.tick > b.tick) { + return 1; + } + return 0; +} + + + +////////////////////////////// +// +// MidiFile::secondsearch -- for finding a second entry in the time map. +// + +int MidiFile::secondsearch(const void* A, const void* B) { + _TickTime& a = *((_TickTime*)A); + _TickTime& b = *((_TickTime*)B); + + if (a.seconds < b.seconds) { + return -1; + } else if (a.seconds > b.seconds) { + return 1; + } + return 0; +} + + +/////////////////////////////////////////////////////////////////////////// +// +// Static functions: +// + + +////////////////////////////// +// +// MidiFile::readLittleEndian4Bytes -- Read four bytes which are in +// little-endian order (smallest byte is first). Then flip +// the order of the bytes to create the return value. +// + +ulong MidiFile::readLittleEndian4Bytes(std::istream& input) { + uchar buffer[4] = {0}; + input.read((char*)buffer, 4); + if (input.eof()) { + std::cerr << "Error: unexpected end of file." << std::endl; + return 0; + } + return buffer[3] | (buffer[2] << 8) | (buffer[1] << 16) | (buffer[0] << 24); +} + + + +////////////////////////////// +// +// MidiFile::readLittleEndian2Bytes -- Read two bytes which are in +// little-endian order (smallest byte is first). Then flip +// the order of the bytes to create the return value. +// + +ushort MidiFile::readLittleEndian2Bytes(std::istream& input) { + uchar buffer[2] = {0}; + input.read((char*)buffer, 2); + if (input.eof()) { + std::cerr << "Error: unexpected end of file." << std::endl; + return 0; + } + return buffer[1] | (buffer[0] << 8); +} + + + +////////////////////////////// +// +// MidiFile::readByte -- Read one byte from input stream. Set +// fail status error if there was a problem (calling function +// has to check this status for an error after reading). +// + +uchar MidiFile::readByte(std::istream& input) { + uchar buffer[1] = {0}; + input.read((char*)buffer, 1); + if (input.eof()) { + std::cerr << "Error: unexpected end of file." << std::endl; + m_rwstatus = false; + return 0; + } + return buffer[0]; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianUShort -- +// + +std::ostream& MidiFile::writeLittleEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianUShort -- +// + +std::ostream& MidiFile::writeBigEndianUShort(std::ostream& out, ushort value) { + union { char bytes[2]; ushort us; } data; + data.us = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianShort -- +// + +std::ostream& MidiFile::writeLittleEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[0]; + out << data.bytes[1]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianShort -- +// + +std::ostream& MidiFile::writeBigEndianShort(std::ostream& out, short value) { + union { char bytes[2]; short s; } data; + data.s = value; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianULong -- +// + +std::ostream& MidiFile::writeLittleEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; ulong ul; } data; + data.ul = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianULong -- +// + +std::ostream& MidiFile::writeBigEndianULong(std::ostream& out, ulong value) { + union { char bytes[4]; long ul; } data; + data.ul = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianLong -- +// + +std::ostream& MidiFile::writeLittleEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianLong -- +// + +std::ostream& MidiFile::writeBigEndianLong(std::ostream& out, long value) { + union { char bytes[4]; long l; } data; + data.l = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; + +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianFloat -- +// + +std::ostream& MidiFile::writeBigEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianFloat -- +// + +std::ostream& MidiFile::writeLittleEndianFloat(std::ostream& out, float value) { + union { char bytes[4]; float f; } data; + data.f = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeBigEndianDouble -- +// + +std::ostream& MidiFile::writeBigEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[7]; + out << data.bytes[6]; + out << data.bytes[5]; + out << data.bytes[4]; + out << data.bytes[3]; + out << data.bytes[2]; + out << data.bytes[1]; + out << data.bytes[0]; + return out; +} + + + +////////////////////////////// +// +// MidiFile::writeLittleEndianDouble -- +// + +std::ostream& MidiFile::writeLittleEndianDouble(std::ostream& out, double value) { + union { char bytes[8]; double d; } data; + data.d = value; + out << data.bytes[0]; + out << data.bytes[1]; + out << data.bytes[2]; + out << data.bytes[3]; + out << data.bytes[4]; + out << data.bytes[5]; + out << data.bytes[6]; + out << data.bytes[7]; + return out; +} + + +} // end namespace smf + + + +/////////////////////////////////////////////////////////////////////////// +// +// external functions +// + +////////////////////////////// +// +// operator<< -- for printing an ASCII version of the MIDI file +// + +std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile) { + aMidiFile.writeBinascWithComments(out); + return out; +} + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiFile.h b/plugins/community/repos/ImpromptuModular/src/midifile/MidiFile.h new file mode 100644 index 00000000..94bb7a38 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiFile.h @@ -0,0 +1,307 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Fri Nov 26 14:12:01 PST 1999 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiFile.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: A class that can read/write Standard MIDI files. +// MIDI data is stored by track in an array. +// + +#ifndef _MIDIFILE_H_INCLUDED +#define _MIDIFILE_H_INCLUDED + +#include "MidiEventList.h" + +#include +#include +#include +#include + +#define TIME_STATE_DELTA 0 +#define TIME_STATE_ABSOLUTE 1 + +#define TRACK_STATE_SPLIT 0 +#define TRACK_STATE_JOINED 1 + +namespace smf { + +class _TickTime { + public: + int tick; + double seconds; +}; + + +class MidiFile { + public: + MidiFile (void); + MidiFile (const std::string& filename); + MidiFile (std::istream& input); + MidiFile (const MidiFile& other); + MidiFile (MidiFile&& other); + + ~MidiFile (); + + MidiFile& operator= (const MidiFile& other); + MidiFile& operator= (MidiFile&& other); + + // reading/writing functions: + bool read (const std::string& filename); + bool read (std::istream& instream); + bool write (const std::string& filename); + bool write (std::ostream& out); + bool writeHex (const std::string& filename, + int width = 25); + bool writeHex (std::ostream& out, + int width = 25); + bool writeBinasc (const std::string& filename); + bool writeBinasc (std::ostream& out); + bool writeBinascWithComments (const std::string& filename); + bool writeBinascWithComments (std::ostream& out); + bool status (void) const; + + // track-related functions: + const MidiEventList& operator[] (int aTrack) const; + MidiEventList& operator[] (int aTrack); + int getTrackCount (void) const; + int getNumTracks (void) const; + int size (void) const; + void removeEmpties (void); + + // tick-related functions: + void makeDeltaTicks (void); + void deltaTicks (void); + void makeAbsoluteTicks (void); + void absoluteTicks (void); + int getTickState (void) const; + bool isDeltaTicks (void) const; + bool isAbsoluteTicks (void) const; + + // join/split track functionality: + void joinTracks (void); + void splitTracks (void); + void splitTracksByChannel (void); + int getTrackState (void) const; + int hasJoinedTracks (void) const; + int hasSplitTracks (void) const; + int getSplitTrack (int track, int index) const; + int getSplitTrack (int index) const; + + // track sorting funcionality: + void sortTrack (int track); + void sortTracks (void); + void markSequence (void); + void markSequence (int track, int sequence = 1); + void clearSequence (void); + void clearSequence (int track); + + // track manipulation functionality: + int addTrack (void); + int addTrack (int count); + int addTracks (int count); + void deleteTrack (int aTrack); + void mergeTracks (int aTrack1, int aTrack2); + int getTrackCountAsType1 (void); + + // ticks-per-quarter related functions: + void setMillisecondTicks (void); + int getTicksPerQuarterNote (void) const; + int getTPQ (void) const; + void setTicksPerQuarterNote (int ticks); + void setTPQ (int ticks); + + // physical-time analysis functions: + void doTimeAnalysis (void); + double getTimeInSeconds (int aTrack, int anIndex); + double getTimeInSeconds (int tickvalue); + double getAbsoluteTickTime (double starttime); + int getFileDurationInTicks (void); + double getFileDurationInQuarters (void); + double getFileDurationInSeconds (void); + + // note-analysis functions: + int linkNotePairs (void); + int linkEventPairs (void); + void clearLinks (void); + + // filename functions: + void setFilename (const std::string& aname); + const char* getFilename (void) const; + + // event functionality: + MidiEvent* addEvent (int aTrack, int aTick, + std::vector& midiData); + MidiEvent* addEvent (MidiEvent& mfevent); + MidiEvent* addEvent (int aTrack, MidiEvent& mfevent); + MidiEvent& getEvent (int aTrack, int anIndex); + const MidiEvent& getEvent (int aTrack, int anIndex) const; + int getEventCount (int aTrack) const; + int getNumEvents (int aTrack) const; + void allocateEvents (int track, int aSize); + void erase (void); + void clear (void); + void clear_no_deallocate (void); + + // MIDI message adding convenience functions: + MidiEvent* addNoteOn (int aTrack, int aTick, + int aChannel, int key, + int vel); + MidiEvent* addNoteOff (int aTrack, int aTick, + int aChannel, int key, + int vel); + MidiEvent* addNoteOff (int aTrack, int aTick, + int aChannel, int key); + MidiEvent* addController (int aTrack, int aTick, + int aChannel, int num, + int value); + MidiEvent* addPatchChange (int aTrack, int aTick, + int aChannel, int patchnum); + MidiEvent* addTimbre (int aTrack, int aTick, + int aChannel, int patchnum); + MidiEvent* addPitchBend (int aTrack, int aTick, + int aChannel, double amount); + + // Controller message adding convenience functions: + MidiEvent* addSustain (int aTrack, int aTick, + int aChannel, int value); + MidiEvent* addSustainPedal (int aTrack, int aTick, + int aChannel, int value); + MidiEvent* addSustainOn (int aTrack, int aTick, + int aChannel); + MidiEvent* addSustainPedalOn (int aTrack, int aTick, + int aChannel); + MidiEvent* addSustainOff (int aTrack, int aTick, + int aChannel); + MidiEvent* addSustainPedalOff (int aTrack, int aTick, + int aChannel); + + // Meta-event adding convenience functions: + MidiEvent* addMetaEvent (int aTrack, int aTick, + int aType, + std::vector& metaData); + MidiEvent* addMetaEvent (int aTrack, int aTick, + int aType, + const std::string& metaData); + MidiEvent* addText (int aTrack, int aTick, + const std::string& text); + MidiEvent* addCopyright (int aTrack, int aTick, + const std::string& text); + MidiEvent* addTrackName (int aTrack, int aTick, + const std::string& name); + MidiEvent* addInstrumentName (int aTrack, int aTick, + const std::string& name); + MidiEvent* addLyric (int aTrack, int aTick, + const std::string& text); + MidiEvent* addMarker (int aTrack, int aTick, + const std::string& text); + MidiEvent* addCue (int aTrack, int aTick, + const std::string& text); + MidiEvent* addTempo (int aTrack, int aTick, + double aTempo); + MidiEvent* addTimeSignature (int aTrack, int aTick, + int top, int bottom, + int clocksPerClick = 24, + int num32dsPerQuarter = 8); + MidiEvent* addCompoundTimeSignature(int aTrack, int aTick, + int top, int bottom, + int clocksPerClick = 36, + int num32dsPerQuarter = 8); + + uchar readByte (std::istream& input); + + // static functions: + static ushort readLittleEndian2Bytes (std::istream& input); + static ulong readLittleEndian4Bytes (std::istream& input); + static std::ostream& writeLittleEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeBigEndianUShort (std::ostream& out, + ushort value); + static std::ostream& writeLittleEndianShort (std::ostream& out, + short value); + static std::ostream& writeBigEndianShort (std::ostream& out, + short value); + static std::ostream& writeLittleEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeBigEndianULong (std::ostream& out, + ulong value); + static std::ostream& writeLittleEndianLong (std::ostream& out, + long value); + static std::ostream& writeBigEndianLong (std::ostream& out, + long value); + static std::ostream& writeLittleEndianFloat (std::ostream& out, + float value); + static std::ostream& writeBigEndianFloat (std::ostream& out, + float value); + static std::ostream& writeLittleEndianDouble (std::ostream& out, + double value); + static std::ostream& writeBigEndianDouble (std::ostream& out, + double value); + + protected: + // m_events == Lists of MidiEvents for each MIDI file track. + std::vector m_events; + + // m_ticksPerQuarterNote == A value for the MIDI file header + // which represents the number of ticks in a quarter note + // that are used as units for the delta times for MIDI events + // in MIDI file track data. + int m_ticksPerQuarterNote = 120; + + // m_trackCount == the number of tracks in the file. + int m_trackCount = 1; + + // m_theTrackState == state variable for whether the tracks + // are joined or split. + int m_theTrackState = TRACK_STATE_SPLIT; + + // m_theTimeState == state variable for whether the MidiEvent::tick + // variable contain absolute ticks since the start of the file's + // time, or delta ticks since the last MIDI event in the track. + int m_theTimeState = TIME_STATE_ABSOLUTE; + + // m_readFileName == the filename of the last file read into + // the object. + std::string m_readFileName; + + // m_timemapvalid == + bool m_timemapvalid = false; + + // m_timemap == + std::vector<_TickTime> m_timemap; + + // m_rwstatus == True if last read was successful, false if a problem. + bool m_rwstatus = true; + + // m_linkedEventQ == True if link analysis has been done. + bool m_linkedEventsQ = false; + + private: + int extractMidiData (std::istream& inputfile, + std::vector& array, + uchar& runningCommand); + ulong readVLValue (std::istream& inputfile); + ulong unpackVLV (uchar a = 0, uchar b = 0, + uchar c = 0, uchar d = 0, + uchar e = 0); + void writeVLValue (long aValue, + std::vector& data); + int makeVLV (uchar *buffer, int number); + static int ticksearch (const void* A, const void* B); + static int secondsearch (const void* A, const void* B); + void buildTimeMap (void); + double linearTickInterpolationAtSecond (double seconds); + double linearSecondInterpolationAtTick (int ticktime); +}; + +} // end of namespace smf + +std::ostream& operator<<(std::ostream& out, smf::MidiFile& aMidiFile); + +#endif /* _MIDIFILE_H_INCLUDED */ + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiMessage.cpp b/plugins/community/repos/ImpromptuModular/src/midifile/MidiMessage.cpp new file mode 100644 index 00000000..7f3e6987 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiMessage.cpp @@ -0,0 +1,1820 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 20:49:21 PST 2015 +// Last Modified: Sun Apr 15 11:11:05 PDT 2018 Added event removal system. +// Filename: midifile/src-library/MidiMessage.cpp +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: Storage for bytes of a MIDI message for Standard +// MIDI Files. +// + +#include "MidiMessage.h" + +#include +#include +#include + + +namespace smf { + +////////////////////////////// +// +// MidiMessage::MidiMessage -- Constructor. +// + +MidiMessage::MidiMessage(void) : vector() { + // do nothing +} + + +MidiMessage::MidiMessage(int command) : vector(1, (uchar)command) { + // do nothing +} + + +MidiMessage::MidiMessage(int command, int p1) : vector(2) { + (*this)[0] = (uchar)command; + (*this)[1] = (uchar)p1; +} + + +MidiMessage::MidiMessage(int command, int p1, int p2) : vector(3) { + (*this)[0] = (uchar)command; + (*this)[1] = (uchar)p1; + (*this)[2] = (uchar)p2; +} + + +MidiMessage::MidiMessage(const MidiMessage& message) : vector() { + (*this) = message; +} + + +MidiMessage::MidiMessage(const std::vector& message) : vector() { + setMessage(message); +} + + +MidiMessage::MidiMessage(const std::vector& message) : vector() { + setMessage(message); +} + + +MidiMessage::MidiMessage(const std::vector& message) : vector() { + setMessage(message); +} + + + +////////////////////////////// +// +// MidiMessage::~MidiMessage -- Deconstructor. +// + +MidiMessage::~MidiMessage() { + resize(0); +} + + + +////////////////////////////// +// +// MidiMessage::operator= -- +// + +MidiMessage& MidiMessage::operator=(const MidiMessage& message) { + if (this == &message) { + return *this; + } + (*this) = message; + return *this; +} + + +MidiMessage& MidiMessage::operator=(const std::vector& bytes) { + if (this == &bytes) { + return *this; + } + setMessage(bytes); + return *this; +} + + +MidiMessage& MidiMessage::operator=(const std::vector& bytes) { + setMessage(bytes); + return *this; +} + + +MidiMessage& MidiMessage::operator=(const std::vector& bytes) { + setMessage(bytes); + return *this; +} + + + +////////////////////////////// +// +// MidiMessage::setSize -- Change the size of the message byte list. +// If the size is increased, then the new bytes are not initialized +// to any specific values. +// + +void MidiMessage::setSize(int asize) { + this->resize(asize); +} + + + +////////////////////////////// +// +// MidiMessage::getSize -- Return the size of the MIDI message bytes. +// + +int MidiMessage::getSize(void) const { + return (int)this->size(); +} + + + +////////////////////////////// +// +// MidiMessage::setSizeToCommand -- Set the number of parameters if the +// command byte is set in the range from 0x80 to 0xef. Any newly +// added parameter bytes will be set to 0. Commands in the range +// of 0xF) should not use this function, and they will ignore +// modification by this command. +// + +int MidiMessage::setSizeToCommand(void) { + int osize = (int)this->size(); + if (osize < 1) { + return 0; + } + int command = getCommandNibble(); + if (command < 0) { + return 0; + } + int bytecount = 1; + switch (command) { + case 0x80: bytecount = 2; break; // Note Off + case 0x90: bytecount = 2; break; // Note On + case 0xA0: bytecount = 2; break; // Aftertouch + case 0xB0: bytecount = 2; break; // Continuous Controller + case 0xC0: bytecount = 1; break; // Patch Change + case 0xD0: bytecount = 1; break; // Channel Pressure + case 0xE0: bytecount = 2; break; // Pitch Bend + case 0xF0: + default: + return (int)size(); + } + if (bytecount + 1 < osize) { + resize(bytecount+1); + for (int i=osize; i <32nds> + return false; + } else { + return true; + } +} + + + +////////////////////////////// +// +// MidiMessage::isKeySignature -- Returns true if message is +// a meta message describing a key signature (meta message +// type 0x59). +// + +bool MidiMessage::isKeySignature(void) const { + if (!isMetaMessage()) { + return false; + } else if ((*this)[1] != 0x59) { + return false; + } else if (size() != 5) { + // Meta key signature message can only be 5 bytes long: + // FF 59 + return false; + } else { + return true; + } +} + + +////////////////////////////// +// +// MidiMessage::isEndOfTrack -- Returns true if message is a meta message +// for end-of-track (meta message type 0x2f). +// + +bool MidiMessage::isEndOfTrack(void) const { + return getMetaType() == 0x2f ? 1 : 0; +} + + + +////////////////////////////// +// +// MidiMessage::getP0 -- Return index 1 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP0(void) const { + return size() < 1 ? -1 : (*this)[0]; +} + + + +////////////////////////////// +// +// MidiMessage::getP1 -- Return index 1 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP1(void) const { + return size() < 2 ? -1 : (*this)[1]; +} + + + +////////////////////////////// +// +// MidiMessage::getP2 -- Return index 2 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP2(void) const { + return size() < 3 ? -1 : (*this)[2]; +} + + + +////////////////////////////// +// +// MidiMessage::getP3 -- Return index 3 byte, or -1 if it doesn't exist. +// + +int MidiMessage::getP3(void) const { + return size() < 4 ? -1 : (*this)[3]; +} + + + +////////////////////////////// +// +// MidiMessage::getKeyNumber -- Return the key number (such as 60 for +// middle C). If the message does not have a note parameter, then +// return -1; if the key is invalid (above 127 in value), then +// limit to the range 0 to 127. +// + +int MidiMessage::getKeyNumber(void) const { + if (isNote() || isAftertouch()) { + int output = getP1(); + if (output < 0) { + return output; + } else { + return 0xff & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::getVelocity -- Return the key veolocity. If the message +// is not a note-on or a note-off, then return -1. If the value is +// out of the range 0-127, then chop off the high-bits. +// + +int MidiMessage::getVelocity(void) const { + if (isNote()) { + int output = getP2(); + if (output < 0) { + return output; + } else { + return 0xff & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::getControllerNumber -- Return the controller number (such as 1 +// for modulation wheel). If the message does not have a controller number +// parameter, then return -1. If the controller number is invalid (above 127 +// in value), then limit the range to to 0-127. +// + +int MidiMessage::getControllerNumber(void) const { + if (isController()) { + int output = getP1(); + if (output < 0) { + // -1 means no P1, although isController() is false in such a case. + return output; + } else { + return 0x7f & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::getControllerValue -- Return the controller value. If the +// message is not a control change message, then return -1. If the value is +// out of the range 0-127, then chop off the high-bits. +// + +int MidiMessage::getControllerValue(void) const { + if (isController()) { + int output = getP2(); + if (output < 0) { + // -1 means no P2, although isController() is false in such a case. + return output; + } else { + return 0x7f & output; + } + } else { + return -1; + } +} + + + +////////////////////////////// +// +// MidiMessage::setP0 -- Set the command byte. +// If the MidiMessage is too short, add extra spaces to +// allow for P0. The value should be in the range from +// 128 to 255, but this function will not babysit you. +// + +void MidiMessage::setP0(int value) { + if (getSize() < 1) { + resize(1); + } + (*this)[0] = value; +} + + + +////////////////////////////// +// +// MidiMessage::setP1 -- Set the first parameter value. +// If the MidiMessage is too short, add extra spaces to +// allow for P1. The command byte will be undefined if +// it was added. The value should be in the range from +// 0 to 127, but this function will not babysit you. +// + +void MidiMessage::setP1(int value) { + if (getSize() < 2) { + resize(2); + } + (*this)[1] = value; +} + + + +////////////////////////////// +// +// MidiMessage::setP2 -- Set the second paramter value. +// If the MidiMessage is too short, add extra spaces +// to allow for P2. The command byte and/or the P1 value +// will be undefined if extra space needs to be added and +// those slots are created. The value should be in the range +// from 0 to 127, but this function will not babysit you. +// + +void MidiMessage::setP2(int value) { + if (getSize() < 3) { + resize(3); + } + (*this)[2] = value; +} + + + +////////////////////////////// +// +// MidiMessage::setP3 -- Set the third paramter value. +// If the MidiMessage is too short, add extra spaces +// to allow for P3. The command byte and/or the P1/P2 values +// will be undefined if extra space needs to be added and +// those slots are created. The value should be in the range +// from 0 to 127, but this function will not babysit you. +// + +void MidiMessage::setP3(int value) { + if (getSize() < 4) { + resize(4); + } + (*this)[3] = value; +} + + + +////////////////////////////// +// +// MidiMessage::setKeyNumber -- Set the note on/off key number (or +// aftertouch key). Ignore if not note or aftertouch message. +// Limits the input value to the range from 0 to 127. +// + +void MidiMessage::setKeyNumber(int value) { + if (isNote() || isAftertouch()) { + setP1(value & 0xff); + } else { + // don't do anything since this is not a note-related message. + } +} + + + +////////////////////////////// +// +// MidiMessage::setVelocity -- Set the note on/off velocity; ignore +// if note a note message. Limits the input value to the range +// from 0 to 127. +// + +void MidiMessage::setVelocity(int value) { + if (isNote()) { + setP2(value & 0xff); + } else { + // don't do anything since this is not a note-related message. + } +} + + + +////////////////////////////// +// +// MidiMessage::getCommandNibble -- Returns the top 4 bits of the (*this)[0] +// entry, or -1 if there is not (*this)[0]. +// + +int MidiMessage::getCommandNibble(void) const { + if (size() < 1) { + return -1; + } else { + return (*this)[0] & 0xf0; + } +} + + + +////////////////////////////// +// +// MidiMessage::getCommandByte -- Return the command byte or -1 if not +// allocated. +// + +int MidiMessage::getCommandByte(void) const { + if (size() < 1) { + return -1; + } else { + return (*this)[0]; + } +} + + + +////////////////////////////// +// +// MidiMessage::getChannelNibble -- Returns the bottom 4 bites of the +// (*this)[0] entry, or -1 if there is not (*this)[0]. Should be refined +// to return -1 if the top nibble is 0xf0, since those commands are +// not channel specific. +// + +int MidiMessage::getChannelNibble(void) const { + if (size() < 1) { + return -1; + } else { + return (*this)[0] & 0x0f; + } +} + + +int MidiMessage::getChannel(void) const { + return getChannelNibble(); +} + + + +////////////////////////////// +// +// MidiMessage::setCommandByte -- +// + +void MidiMessage::setCommandByte(int value) { + if (size() < 1) { + resize(1); + } else { + (*this)[0] = (uchar)(value & 0xff); + } +} + +void MidiMessage::setCommand(int value) { + setCommandByte(value); +} + + + +////////////////////////////// +// +// MidiMessage::setCommand -- Set the command byte and parameter bytes +// for a MidiMessage. The size of the message will be adjusted to +// the number of input parameters. +// + +void MidiMessage::setCommand(int value, int p1) { + this->resize(2); + (*this)[0] = (uchar)value; + (*this)[1] = (uchar)p1; +} + + +void MidiMessage::setCommand(int value, int p1, int p2) { + this->resize(3); + (*this)[0] = (uchar)value; + (*this)[1] = (uchar)p1; + (*this)[2] = (uchar)p2; +} + + + +////////////////////////////// +// +// MidiMessage::setCommandNibble -- +// + +void MidiMessage::setCommandNibble(int value) { + if (this->size() < 1) { + this->resize(1); + } + if (value <= 0x0f) { + (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)((value << 4) & 0xf0)); + } else { + (*this)[0] = ((*this)[0] & 0x0f) | ((uchar)(value & 0xf0)); + } +} + + + + +////////////////////////////// +// +// MidiMessage::setChannelNibble -- +// + +void MidiMessage::setChannelNibble(int value) { + if (this->size() < 1) { + this->resize(1); + } + (*this)[0] = ((*this)[0] & 0xf0) | ((uchar)(value & 0x0f)); +} + + +void MidiMessage::setChannel(int value) { + setChannelNibble(value); +} + + + +////////////////////////////// +// +// MidiMessage::setParameters -- Set the second and optionally the +// third MIDI byte of a MIDI message. The command byte will not +// be altered, and will be set to 0 if it currently does not exist. +// + +void MidiMessage::setParameters(int p1) { + int oldsize = (int)size(); + resize(2); + (*this)[1] = (uchar)p1; + if (oldsize < 1) { + (*this)[0] = 0; + } +} + + +void MidiMessage::setParameters(int p1, int p2) { + int oldsize = (int)size(); + resize(3); + (*this)[1] = (uchar)p1; + (*this)[2] = (uchar)p2; + if (oldsize < 1) { + (*this)[0] = 0; + } +} + + +////////////////////////////// +// +// MidiMessage::setMessage -- Set the contents of MIDI bytes to the +// input list of bytes. +// + +void MidiMessage::setMessage(const std::vector& message) { + this->resize(message.size()); + for (int i=0; i<(int)this->size(); i++) { + (*this)[i] = message[i]; + } +} + + +void MidiMessage::setMessage(const std::vector& message) { + resize(message.size()); + for (int i=0; i<(int)size(); i++) { + (*this)[i] = (uchar)message[i]; + } +} + + +void MidiMessage::setMessage(const std::vector& message) { + resize(message.size()); + for (int i=0; i<(int)size(); i++) { + (*this)[i] = (uchar)message[i]; + } +} + + + +////////////////////////////// +// +// MidiMessage::setSpelling -- Encode a MidiPlus accidental state for a note. +// For example, if a note's key number is 60, the enharmonic pitch name +// could be any of these possibilities: +// C, B-sharp, D-double-flat +// MIDI note 60 is ambiguous as to which of these names are intended, +// so MIDIPlus allows these mappings to be preserved for later recovery. +// See Chapter 5 (pp. 99-104) of Beyond MIDI (1997). +// +// The first parameter is the diatonic pitch number (or pitch class +// if the octave is set to 0): +// octave * 7 + 0 = C pitches +// octave * 7 + 1 = D pitches +// octave * 7 + 2 = E pitches +// octave * 7 + 3 = F pitches +// octave * 7 + 4 = G pitches +// octave * 7 + 5 = A pitches +// octave * 7 + 6 = B pitches +// +// The second parameter is the semitone alteration (accidental). +// 0 = natural state, 1 = sharp, 2 = double sharp, -1 = flat, +// -2 = double flat. +// +// Only note-on messages can be processed (other messages will be +// silently ignored). +// + +void MidiMessage::setSpelling(int base7, int accidental) { + if (!isNoteOn()) { + return; + } + // The bottom two bits of the attack velocity are used for the + // spelling, so need to make sure the velocity will not accidentally + // be set to zero (and make the note-on a note-off). + if (getVelocity() < 4) { + setVelocity(4); + } + int dpc = base7 % 7; + uchar spelling = 0; + + // Table 5.1, page 101 in Beyond MIDI (1997) + // http://beyondmidi.ccarh.org/beyondmidi-600dpi.pdf + switch (dpc) { + + case 0: + switch (accidental) { + case -2: spelling = 1; break; // Cbb + case -1: spelling = 1; break; // Cb + case 0: spelling = 2; break; // C + case +1: spelling = 2; break; // C# + case +2: spelling = 3; break; // C## + } + break; + + case 1: + switch (accidental) { + case -2: spelling = 1; break; // Dbb + case -1: spelling = 1; break; // Db + case 0: spelling = 2; break; // D + case +1: spelling = 3; break; // D# + case +2: spelling = 3; break; // D## + } + break; + + case 2: + switch (accidental) { + case -2: spelling = 1; break; // Ebb + case -1: spelling = 2; break; // Eb + case 0: spelling = 2; break; // E + case +1: spelling = 3; break; // E# + case +2: spelling = 3; break; // E## + } + break; + + case 3: + switch (accidental) { + case -2: spelling = 1; break; // Fbb + case -1: spelling = 1; break; // Fb + case 0: spelling = 2; break; // F + case +1: spelling = 2; break; // F# + case +2: spelling = 3; break; // F## + case +3: spelling = 3; break; // F### + } + break; + + case 4: + switch (accidental) { + case -2: spelling = 1; break; // Gbb + case -1: spelling = 1; break; // Gb + case 0: spelling = 2; break; // G + case +1: spelling = 2; break; // G# + case +2: spelling = 3; break; // G## + } + break; + + case 5: + switch (accidental) { + case -2: spelling = 1; break; // Abb + case -1: spelling = 1; break; // Ab + case 0: spelling = 2; break; // A + case +1: spelling = 3; break; // A# + case +2: spelling = 3; break; // A## + } + break; + + case 6: + switch (accidental) { + case -2: spelling = 1; break; // Bbb + case -1: spelling = 2; break; // Bb + case 0: spelling = 2; break; // B + case +1: spelling = 3; break; // B# + case +2: spelling = 3; break; // B## + } + break; + + } + + uchar vel = getVelocity(); + // suppress any previous content in the first two bits: + vel = vel & 0xFC; + // insert the spelling code: + vel = vel | spelling; + setVelocity(vel); +} + + + +////////////////////////////// +// +// MidiMessage::getSpelling -- Return the diatonic pitch class and accidental +// for a note-on's key number. The MIDI file must be encoded with MIDIPlus +// pitch spelling codes for this function to return valid data; otherwise, +// it will return a neutral fixed spelling for each MIDI key. +// +// The first parameter will be filled in with the base-7 diatonic pitch: +// pc + octave * 7 +// where pc is the numbers 0 through 6 representing the pitch classes +// C through B, the octave is MIDI octave (not the scientific pitch +// octave which is one less than the MIDI ocatave, such as C4 = middle C). +// The second number is the accidental for the base-7 pitch. +// + +void MidiMessage::getSpelling(int& base7, int& accidental) { + if (!isNoteOn()) { + return; + } + base7 = -123456; + accidental = 123456; + int base12 = getKeyNumber(); + int octave = base12 / 12; + int base12pc = base12 - octave * 12; + int base7pc = 0; + int spelling = 0x03 & getVelocity(); + + // Table 5.1, page 101 in Beyond MIDI (1997) + // http://beyondmidi.ccarh.org/beyondmidi-600dpi.pdf + switch (base12pc) { + + case 0: + switch (spelling) { + case 1: base7pc = 1; accidental = -2; break; // Dbb + case 0: case 2: base7pc = 0; accidental = 0; break; // C + case 3: base7pc = 6; accidental = +1; octave--; break; // B# + } + break; + + case 1: + switch (spelling) { + case 1: base7pc = 1; accidental = -1; break; // Db + case 0: case 2: base7pc = 0; accidental = +1; break; // C# + case 3: base7pc = 6; accidental = +2; octave--; break; // B## + } + break; + + case 2: + switch (spelling) { + case 1: base7pc = 2; accidental = -2; break; // Ebb + case 0: case 2: base7pc = 1; accidental = 0; break; // D + case 3: base7pc = 0; accidental = +2; break; // C## + } + break; + + case 3: + switch (spelling) { + case 1: base7pc = 3; accidental = -2; break; // Fbb + case 0: case 2: base7pc = 2; accidental = -1; break; // Eb + case 3: base7pc = 1; accidental = +1; break; // D# + } + break; + + case 4: + switch (spelling) { + case 1: base7pc = 3; accidental = -1; break; // Fb + case 0: case 2: base7pc = 2; accidental = 0; break; // E + case 3: base7pc = 1; accidental = +2; break; // D## + } + break; + + case 5: + switch (spelling) { + case 1: base7pc = 4; accidental = -2; break; // Gbb + case 0: case 2: base7pc = 3; accidental = 0; break; // F + case 3: base7pc = 2; accidental = +1; break; // E# + } + break; + + case 6: + switch (spelling) { + case 1: base7pc = 4; accidental = -1; break; // Gb + case 0: case 2: base7pc = 3; accidental = +1; break; // F# + case 3: base7pc = 2; accidental = +2; break; // E## + } + break; + + case 7: + switch (spelling) { + case 1: base7pc = 5; accidental = -2; break; // Abb + case 0: case 2: base7pc = 4; accidental = 0; break; // G + case 3: base7pc = 3; accidental = +2; break; // F## + } + break; + + case 8: + switch (spelling) { + case 1: base7pc = 5; accidental = -1; break; // Ab + case 0: case 2: base7pc = 4; accidental = +1; break; // G# + case 3: base7pc = 3; accidental = +3; break; // F### + } + break; + + case 9: + switch (spelling) { + case 1: base7pc = 6; accidental = -2; break; // Bbb + case 0: case 2: base7pc = 5; accidental = 0; break; // A + case 3: base7pc = 4; accidental = +2; break; // G## + } + break; + + case 10: + switch (spelling) { + case 1: base7pc = 0; accidental = -2; octave++; break; // Cbb + case 0: case 2: base7pc = 6; accidental = -1; break; // Bb + case 3: base7pc = 5; accidental = +1; break; // A# + } + break; + + case 11: + switch (spelling) { + case 1: base7pc = 0; accidental = -1; octave++; break; // Cb + case 0: case 2: base7pc = 6; accidental = 0; break; // B + case 3: base7pc = 5; accidental = +2; break; // A## + } + break; + + } + + base7 = base7pc + 7 * octave; +} + + + +////////////////////////////// +// +// MidiMessage::getMetaContent -- Returns the bytes of the meta +// message after the length (which is a variable-length-value). +// + +std::string MidiMessage::getMetaContent(void) { + std::string output; + if (!isMetaMessage()) { + return output; + } + int start = 3; + if (operator[](2) > 0x7f) { + start++; + if (operator[](3) > 0x7f) { + start++; + if (operator[](4) > 0x7f) { + start++; + if (operator[](5) > 0x7f) { + start++; + // maximum of 5 bytes in VLV, so last must be < 0x80 + } + } + } + } + output.reserve(this->size()); + for (int i=start; i<(int)this->size(); i++) { + output.push_back(operator[](i)); + } + return output; +} + + + +////////////////////////////// +// +// MidiMessage::setMetaContent - Set the content of a meta-message. This +// function handles the size of the message starting at byte 3 in the +// message, and it does not alter the meta message type. The message +// must be a meta-message before calling this function and be assigne +// a meta-message type. +// + +void MidiMessage::setMetaContent(const std::string& content) { + if (this->size() < 2) { + // invalid message, so ignore request + return; + } + if (operator[](0) != 0xFF) { + // not a meta message, so ignore request + return; + } + this->resize(2); + + // add the size of the meta message data (VLV) + int dsize = (int)content.size(); + if (dsize < 128) { + push_back((uchar)dsize); + } else { + // calculate VLV bytes and insert into message + uchar byte1 = dsize & 0x7f; + uchar byte2 = (dsize >> 7) & 0x7f; + uchar byte3 = (dsize >> 14) & 0x7f; + uchar byte4 = (dsize >> 21) & 0x7f; + uchar byte5 = (dsize >> 28) & 0x7f; + if (byte5) { + byte4 |= 0x80; + } + if (byte4) { + byte4 |= 0x80; + byte3 |= 0x80; + } + if (byte3) { + byte3 |= 0x80; + byte2 |= 0x80; + } + if (byte2) { + byte2 |= 0x80; + } + if (byte5) { push_back(byte5); } + if (byte4) { push_back(byte4); } + if (byte3) { push_back(byte3); } + if (byte2) { push_back(byte2); } + push_back(byte1); + } + std::copy(content.begin(), content.end(), std::back_inserter(*this)); +} + + + +////////////////////////////// +// +// MidiMessage::setMetaTempo -- Input tempo is in quarter notes per minute +// (meta message #0x51). +// + +void MidiMessage::setMetaTempo(double tempo) { + int microseconds = (int)(60.0 / tempo * 1000000.0 + 0.5); + setTempoMicroseconds(microseconds); +} + + + +////////////////////////////// +// +// MidiMessage::setTempo -- Alias for MidiMessage::setMetaTempo(). +// + +void MidiMessage::setTempo(double tempo) { + setMetaTempo(tempo); +} + + + +////////////////////////////// +// +// MidiMessage::setTempoMicroseconds -- Set the tempo in terms +// of microseconds per quarter note. +// + +void MidiMessage::setTempoMicroseconds(int microseconds) { + resize(6); + (*this)[0] = 0xff; + (*this)[1] = 0x51; + (*this)[2] = 3; + (*this)[3] = (microseconds >> 16) & 0xff; + (*this)[4] = (microseconds >> 8) & 0xff; + (*this)[5] = (microseconds >> 0) & 0xff; +} + + + +////////////////////////////// +// +// MidiMessage::makeTimeSignature -- create a time signature meta message +// (meta #0x58). The "bottom" parameter should be a power of two; +// otherwise, it will be forced to be the next highest power of two, +// as MIDI time signatures must have a power of two in the denominator. +// +// Default values: +// clocksPerClick == 24 (quarter note) +// num32ndsPerQuarter == 8 (8 32nds per quarter note) +// +// Time signature of 4/4 would be: +// top = 4 +// bottom = 4 (converted to 2 in the MIDI file for 2nd power of 2). +// clocksPerClick = 24 (2 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// +// Time signature of 6/8 would be: +// top = 6 +// bottom = 8 (converted to 3 in the MIDI file for 3rd power of 2). +// clocksPerClick = 36 (3 eighth notes based on num32ndsPerQuarter) +// num32ndsPerQuarter = 8 +// + +void MidiMessage::makeTimeSignature(int top, int bottom, int clocksPerClick, + int num32ndsPerQuarter) { + int base2 = 0; + while (bottom >>= 1) base2++; + resize(7); + (*this)[0] = 0xff; + (*this)[1] = 0x58; + (*this)[2] = 4; + (*this)[3] = 0xff & top; + (*this)[4] = 0xff & base2; + (*this)[5] = 0xff & clocksPerClick; + (*this)[6] = 0xff & num32ndsPerQuarter; +} + + + +/////////////////////////////////////////////////////////////////////////// +// +// make functions to create various MIDI message -- +// + + +////////////////////////////// +// +// MidiMessage::makeNoteOn -- create a note-on message. +// +// default value: channel = 0 +// +// Note: The channel parameter used to be last, but makes more sense to +// have it first... +// + +void MidiMessage::makeNoteOn(int channel, int key, int velocity) { + resize(3); + (*this)[0] = 0x90 | (0x0f & channel); + (*this)[1] = key & 0x7f; + (*this)[2] = velocity & 0x7f; +} + + + +////////////////////////////// +// +// MidiMessage::makeNoteOff -- create a note-off message. If no +// parameters are given, the current contents is presumed to be a +// note-on message, which will be converted into a note-off message. +// +// default value: channel = 0 +// +// Note: The channel parameter used to be last, but makes more sense to +// have it first... +// + + +void MidiMessage::makeNoteOff(int channel, int key, int velocity) { + resize(3); + (*this)[0] = 0x80 | (0x0f & channel); + (*this)[1] = key & 0x7f; + (*this)[2] = velocity & 0x7f; +} + + +void MidiMessage::makeNoteOff(int channel, int key) { + resize(3); + (*this)[0] = 0x90 | (0x0f & channel); + (*this)[1] = key & 0x7f; + (*this)[2] = 0x00; +} + +// +// MidiMessage::makeNoteOff(void) -- create a 0x90 note message with +// The key and velocity set to 0. +// + +void MidiMessage::makeNoteOff(void) { + if (!isNoteOn()) { + resize(3); + (*this)[0] = 0x90; + (*this)[1] = 0; + (*this)[2] = 0; + } else { + (*this)[2] = 0; + } +} + + + +///////////////////////////// +// +// MidiMessage::makePatchChange -- Create a patch-change message. +// + +void MidiMessage::makePatchChange(int channel, int patchnum) { + resize(0); + push_back(0xc0 | (0x0f & channel)); + push_back(0x7f & patchnum); +} + +// +// MidiMessage::makeTimbre -- alias for MidiMessage::makePatchChange(). +// + +void MidiMessage::makeTimbre(int channel, int patchnum) { + makePatchChange(channel, patchnum); +} + + +///////////////////////////// +// +// MidiMessage::makeController -- Create a controller message. +// + +void MidiMessage::makeController(int channel, int num, int value) { + resize(0); + push_back(0xb0 | (0x0f & channel)); + push_back(0x7f & num); + push_back(0x7f & value); +} + + + +///////////////////////////// +// +// MidiMessage::makeSustain -- Create a sustain pedal message. +// Value in 0-63 range is a sustain off. Value in the +// 64-127 value is a sustain on. +// + +void MidiMessage::makeSustain(int channel, int value) { + makeController(channel, 64, value); +} + +// +// MidiMessage::makeSustain -- Alias for MidiMessage::makeSustain(). +// + +void MidiMessage::makeSustainPedal(int channel, int value) { + makeSustain(channel, value); +} + + + +///////////////////////////// +// +// MidiMessage::makeSustainOn -- Create sustain-on controller message. +// + +void MidiMessage::makeSustainOn(int channel) { + makeController(channel, 64, 127); +} + +// +// MidiMessage::makeSustainPedalOn -- Alias for MidiMessage::makeSustainOn(). +// + +void MidiMessage::makeSustainPedalOn(int channel) { + makeSustainOn(channel); +} + + + +///////////////////////////// +// +// MidiMessage::makeSustainOff -- Create a sustain-off controller message. +// + +void MidiMessage::makeSustainOff(int channel) { + makeController(channel, 64, 0); +} + +// +// MidiMessage::makeSustainPedalOff -- Alias for MidiMessage::makeSustainOff(). +// + +void MidiMessage::makeSustainPedalOff(int channel) { + makeSustainOff(channel); +} + + + +////////////////////////////// +// +// MidiMessage::makeMetaMessage -- Create a Meta event with the +// given text string as the parameter. The length of the string should +// is a VLV. If the length is larger than 127 byte, then the length +// will contain more than one byte. +// + +void MidiMessage::makeMetaMessage(int mnum, const std::string& data) { + resize(0); + push_back(0xff); + push_back(mnum & 0x7f); // max meta-message number is 0x7f. + setMetaContent(data); +} + + + +////////////////////////////// +// +// MidiMessage::makeText -- Create a metaevent text message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeText(const std::string& text) { + makeMetaMessage(0x01, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeCopyright -- Create a metaevent copyright message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeCopyright(const std::string& text) { + makeMetaMessage(0x02, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeTrackName -- Create a metaevent track name message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeTrackName(const std::string& name) { + makeMetaMessage(0x03, name); +} + + + +////////////////////////////// +// +// MidiMessage::makeTrackName -- Create a metaevent instrument name message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeInstrumentName(const std::string& name) { + makeMetaMessage(0x04, name); +} + + + +////////////////////////////// +// +// MidiMessage::makeLyric -- Create a metaevent lyrics/text message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeLyric(const std::string& text) { + makeMetaMessage(0x05, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeMarker -- Create a metaevent marker message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeMarker(const std::string& text) { + makeMetaMessage(0x06, text); +} + + + +////////////////////////////// +// +// MidiMessage::makeCue -- Create a metaevent cue-point message. +// This is not a real MIDI message, but rather a pretend message for use +// within Standard MIDI Files. +// + +void MidiMessage::makeCue(const std::string& text) { + makeMetaMessage(0x07, text); +} + + +} // end namespace smf + + + diff --git a/plugins/community/repos/ImpromptuModular/src/midifile/MidiMessage.h b/plugins/community/repos/ImpromptuModular/src/midifile/MidiMessage.h new file mode 100644 index 00000000..29f76569 --- /dev/null +++ b/plugins/community/repos/ImpromptuModular/src/midifile/MidiMessage.h @@ -0,0 +1,170 @@ +// +// Programmer: Craig Stuart Sapp +// Creation Date: Sat Feb 14 20:36:32 PST 2015 +// Last Modified: Sat Apr 21 10:52:19 PDT 2018 Removed using namespace std; +// Filename: midifile/include/MidiMessage.h +// Website: http://midifile.sapp.org +// Syntax: C++11 +// vim: ts=3 noexpandtab +// +// Description: Storage for bytes of a MIDI message for use in MidiFile +// class. +// + +#ifndef _MIDIMESSAGE_H_INCLUDED +#define _MIDIMESSAGE_H_INCLUDED + +#include +#include + +namespace smf { + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long ulong; + +class MidiMessage : public std::vector { + + public: + MidiMessage (void); + MidiMessage (int command); + MidiMessage (int command, int p1); + MidiMessage (int command, int p1, int p2); + MidiMessage (const MidiMessage& message); + MidiMessage (const std::vector& message); + MidiMessage (const std::vector& message); + MidiMessage (const std::vector& message); + + ~MidiMessage (); + + MidiMessage& operator= (const MidiMessage& message); + MidiMessage& operator= (const std::vector& bytes); + MidiMessage& operator= (const std::vector& bytes); + MidiMessage& operator= (const std::vector& bytes); + + void sortTrack (void); + void sortTrackWithSequence(void); + + // data access convenience functions (returns -1 if not present): + int getP0 (void) const; + int getP1 (void) const; + int getP2 (void) const; + int getP3 (void) const; + void setP0 (int value); + void setP1 (int value); + void setP2 (int value); + void setP3 (int value); + + int getSize (void) const; + void setSize (int asize); + int setSizeToCommand (void); + int resizeToCommand (void); + + // note-message convenience functions: + int getKeyNumber (void) const; + int getVelocity (void) const; + void setKeyNumber (int value); + void setVelocity (int value); + void setSpelling (int base7, int accidental); + void getSpelling (int& base7, int& accidental); + + // controller-message convenience functions: + int getControllerNumber (void) const; + int getControllerValue (void) const; + + int getCommandNibble (void) const; + int getCommandByte (void) const; + int getChannelNibble (void) const; + int getChannel (void) const; + + void setCommandByte (int value); + void setCommand (int value); + void setCommand (int value, int p1); + void setCommand (int value, int p1, int p2); + void setCommandNibble (int value); + void setChannelNibble (int value); + void setChannel (int value); + void setParameters (int p1, int p2); + void setParameters (int p1); + void setMessage (const std::vector& message); + void setMessage (const std::vector& message); + void setMessage (const std::vector& message); + + // message-type convenience functions: + bool isMetaMessage (void) const; + bool isMeta (void) const; + bool isNoteOff (void) const; + bool isNoteOn (void) const; + bool isNote (void) const; + bool isAftertouch (void) const; + bool isController (void) const; + bool isTimbre (void) const; + bool isPatchChange (void) const; + bool isPressure (void) const; + bool isPitchbend (void) const; + bool isEmpty (void) const; + + // helper functions to create various MidiMessages: + void makeNoteOn (int channel, int key, int velocity); + void makeNoteOff (int channel, int key, int velocity); + void makeNoteOff (int channel, int key); + void makeNoteOff (void); + void makePatchChange (int channel, int patchnum); + void makeTimbre (int channel, int patchnum); + void makeController (int channel, int num, int value); + + // helper functions to create various continuous controller messages: + void makeSustain (int channel, int value); + void makeSustainPedal (int channel, int value); + void makeSustainOn (int channel); + void makeSustainPedalOn (int channel); + void makeSustainOff (int channel); + void makeSustainPedalOff (int channel); + + // meta-message creation and helper functions: + void makeMetaMessage (int mnum, const std::string& data); + void makeText (const std::string& name); + void makeCopyright (const std::string& text); + void makeTrackName (const std::string& name); + void makeInstrumentName (const std::string& name); + void makeLyric (const std::string& text); + void makeMarker (const std::string& text); + void makeCue (const std::string& text); + void makeTimeSignature (int top, int bottom, + int clocksPerClick = 24, + int num32dsPerQuarter = 8); + + void makeTempo (double tempo) { setTempo(tempo); } + int getTempoMicro (void) const; + int getTempoMicroseconds (void) const; + double getTempoSeconds (void) const; + double getTempoBPM (void) const; + double getTempoTPS (int tpq) const; + double getTempoSPT (int tpq) const; + + int getMetaType (void) const; + bool isText (void) const; + bool isCopyright (void) const; + bool isTrackName (void) const; + bool isInstrumentName (void) const; + bool isLyricText (void) const; + bool isMarkerText (void) const; + bool isTempo (void) const; + bool isTimeSignature (void) const; + bool isKeySignature (void) const; + bool isEndOfTrack (void) const; + + std::string getMetaContent (void); + void setMetaContent (const std::string& content); + void setTempo (double tempo); + void setTempoMicroseconds (int microseconds); + void setMetaTempo (double tempo); + +}; + +} // end of namespace smf + +#endif /* _MIDIMESSAGE_H_INCLUDED */ + + + diff --git a/plugins/community/repos/LindenbergResearch/README.md b/plugins/community/repos/LindenbergResearch/README.md index 691f5996..45445b2f 100644 --- a/plugins/community/repos/LindenbergResearch/README.md +++ b/plugins/community/repos/LindenbergResearch/README.md @@ -3,26 +3,33 @@ LRT Rack modules is a collection of modules made for [VCV Rack](https://vcvrack. Copyright (C) 2017-2018 by Lindenberg Research / Patrick Lindenberg -![SCREENSHOT](doc/LRTRackModules_0.6.0.png) +![SCREENSHOT](doc/LRTRackModules_0.6.1.png) ## 1. Installation -You can find the latest release here: https://github.com/lindenbergresearch/LRTRack/releases -Just download fpr you architecture and unzip in your _**Rack**_ folder located in your documents. +You can obtain the latest release via the VCV Rack package manager, or download and install it manually: + +https://github.com/lindenbergresearch/LRTRack/releases + + +Just download for you architecture and unzip in your _**Rack**_ folder located in your documents. ## 2. Build from source _**NOTE:**_ +You have to install a C++ environment including a compiler and other commandline tools. +For further information about this, check the documentation found here: [VCV Rack GIT](https://github.com/VCVRack/Rack) + + The current master has been migrated to 0.6.0dev! So you have to install the correct version of Rack in order to get it running. I suggest to fetch a clean 0.6.xx version of Rack via git and build all from scratch. -You can build build the latest release from source, to do so use the following steps: -Note that you have to install a C++ environment with a compiler and other needed tools. -For further information about this, check the documentation found here of: [VCV Rack GIT](https://github.com/VCVRack/Rack) +You can build the latest release from source, to do so use the following steps: + Clone git to local machine (into Rack/plugins): diff --git a/plugins/community/repos/LindenbergResearch/doc/LRTRackModules_0.6.1.png b/plugins/community/repos/LindenbergResearch/doc/LRTRackModules_0.6.1.png new file mode 100644 index 00000000..e801eea2 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/doc/LRTRackModules_0.6.1.png differ diff --git a/plugins/community/repos/LindenbergResearch/doc/VCO.svg b/plugins/community/repos/LindenbergResearch/doc/VCO.svg new file mode 100644 index 00000000..74e939a3 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/doc/VCO.svg @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.afdesign b/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.afdesign new file mode 100644 index 00000000..c5b674cd Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.pdf b/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.pdf new file mode 100644 index 00000000..24d2bac4 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.pdf differ diff --git a/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.svg b/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.svg new file mode 100644 index 00000000..a9c1578f --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.svg @@ -0,0 +1,1204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/make.objects b/plugins/community/repos/LindenbergResearch/make.objects index 1b169ffe..411c781e 100644 --- a/plugins/community/repos/LindenbergResearch/make.objects +++ b/plugins/community/repos/LindenbergResearch/make.objects @@ -1,8 +1,11 @@ ALL_OBJ= \ src/dsp/DSPMath.o \ src/dsp/LadderFilter.o \ + src/dsp/Lockhart.o \ src/dsp/MS20zdf.o \ src/dsp/Oscillator.o \ + src/dsp/SergeWavefolder.o \ + src/dsp/WaveShaper.o \ src/AlmaFilter.o \ src/BlankPanel.o \ src/BlankPanelM1.o \ @@ -10,7 +13,6 @@ ALL_OBJ= \ src/LindenbergResearch.o \ src/MS20Filter.o \ src/ReShaper.o \ - src/SimpleFilter.o - -# src/VCO.o - + src/SimpleFilter.o \ + src/VCO.o \ + src/Westcoast.o diff --git a/plugins/community/repos/LindenbergResearch/res/AlternateBigKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/AlternateBigKnob.afdesign new file mode 100644 index 00000000..d90429aa Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/AlternateBigKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/AlternateBigKnob.svg b/plugins/community/repos/LindenbergResearch/res/AlternateBigKnob.svg new file mode 100644 index 00000000..7b8ea0db --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/AlternateBigKnob.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/AlternateMiddleKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/AlternateMiddleKnob.afdesign new file mode 100644 index 00000000..26d1ebf5 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/AlternateMiddleKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/AlternateMiddleKnob.svg b/plugins/community/repos/LindenbergResearch/res/AlternateMiddleKnob.svg new file mode 100644 index 00000000..333cb5fe --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/AlternateMiddleKnob.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/AlternateSmallKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/AlternateSmallKnob.afdesign new file mode 100644 index 00000000..00abe87a Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/AlternateSmallKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/AlternateSmallKnob.svg b/plugins/community/repos/LindenbergResearch/res/AlternateSmallKnob.svg new file mode 100644 index 00000000..0dc956ad --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/AlternateSmallKnob.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/BigKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/BigKnob.afdesign index 11c930eb..40e1d8d4 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/BigKnob.afdesign and b/plugins/community/repos/LindenbergResearch/res/BigKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/BigKnob.svg b/plugins/community/repos/LindenbergResearch/res/BigKnob.svg index 4452da95..7edad1b0 100644 --- a/plugins/community/repos/LindenbergResearch/res/BigKnob.svg +++ b/plugins/community/repos/LindenbergResearch/res/BigKnob.svg @@ -1,11 +1,15 @@ - - + + - + - - + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/BlankPanel.afdesign b/plugins/community/repos/LindenbergResearch/res/BlankPanel.afdesign index 28deae76..1a2685a1 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/BlankPanel.afdesign and b/plugins/community/repos/LindenbergResearch/res/BlankPanel.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/BlankPanel.svg b/plugins/community/repos/LindenbergResearch/res/BlankPanel.svg index 0041548f..412669c0 100644 --- a/plugins/community/repos/LindenbergResearch/res/BlankPanel.svg +++ b/plugins/community/repos/LindenbergResearch/res/BlankPanel.svg @@ -1,63 +1,102 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - diff --git a/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.afdesign b/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.afdesign index 636bad40..4f55f05f 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.afdesign and b/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.svg b/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.svg index f1568a37..6adc7c13 100644 --- a/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.svg +++ b/plugins/community/repos/LindenbergResearch/res/BlankPanelM1.svg @@ -1,14 +1,5 @@ - - - - - - - - - - - + + diff --git a/plugins/community/repos/LindenbergResearch/res/BlankPanelSmall.afdesign b/plugins/community/repos/LindenbergResearch/res/BlankPanelSmall.afdesign new file mode 100644 index 00000000..f1bb4e94 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/BlankPanelSmall.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/BlankPanelSmall.svg b/plugins/community/repos/LindenbergResearch/res/BlankPanelSmall.svg new file mode 100644 index 00000000..c65e2c07 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/BlankPanelSmall.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/IOPortB.afdesign b/plugins/community/repos/LindenbergResearch/res/IOPortB.afdesign index 4b398ac9..70f0d878 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/IOPortB.afdesign and b/plugins/community/repos/LindenbergResearch/res/IOPortB.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/IOPortB.svg b/plugins/community/repos/LindenbergResearch/res/IOPortB.svg index 7e071f33..686eb3ff 100644 --- a/plugins/community/repos/LindenbergResearch/res/IOPortB.svg +++ b/plugins/community/repos/LindenbergResearch/res/IOPortB.svg @@ -1,45 +1,11 @@ - - - - - - - - - - - ]> - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/IOPortC.afdesign b/plugins/community/repos/LindenbergResearch/res/IOPortC.afdesign new file mode 100644 index 00000000..5e7e28bb Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/IOPortC.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/IOPortC.svg b/plugins/community/repos/LindenbergResearch/res/IOPortC.svg new file mode 100644 index 00000000..7a5882e9 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/IOPortC.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/MS20.afdesign b/plugins/community/repos/LindenbergResearch/res/MS20.afdesign index c83071c2..c97af5e0 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/MS20.afdesign and b/plugins/community/repos/LindenbergResearch/res/MS20.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/MS20.svg b/plugins/community/repos/LindenbergResearch/res/MS20.svg index d38ccbb1..c4e01cd3 100644 --- a/plugins/community/repos/LindenbergResearch/res/MS20.svg +++ b/plugins/community/repos/LindenbergResearch/res/MS20.svg @@ -1,8 +1,8 @@ - + - + @@ -129,75 +129,75 @@ - - - - + + + + - - - - + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - + + - - - + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - @@ -249,37 +239,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/MiddleIncremental.afdesign b/plugins/community/repos/LindenbergResearch/res/MiddleIncremental.afdesign new file mode 100644 index 00000000..f6e815b7 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/MiddleIncremental.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/MiddleIncremental.svg b/plugins/community/repos/LindenbergResearch/res/MiddleIncremental.svg new file mode 100644 index 00000000..333cb5fe --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/MiddleIncremental.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/MiddleKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/MiddleKnob.afdesign index 19cfdb95..c8439656 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/MiddleKnob.afdesign and b/plugins/community/repos/LindenbergResearch/res/MiddleKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/MiddleKnob.svg b/plugins/community/repos/LindenbergResearch/res/MiddleKnob.svg index a2a6907c..6910c89b 100644 --- a/plugins/community/repos/LindenbergResearch/res/MiddleKnob.svg +++ b/plugins/community/repos/LindenbergResearch/res/MiddleKnob.svg @@ -1,13 +1,15 @@ - + - + - - - + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/ReShaper.afdesign b/plugins/community/repos/LindenbergResearch/res/ReShaper.afdesign index d94ed539..76baa418 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/ReShaper.afdesign and b/plugins/community/repos/LindenbergResearch/res/ReShaper.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/ReShaper.svg b/plugins/community/repos/LindenbergResearch/res/ReShaper.svg index 4f742811..d06e983b 100644 --- a/plugins/community/repos/LindenbergResearch/res/ReShaper.svg +++ b/plugins/community/repos/LindenbergResearch/res/ReShaper.svg @@ -1,7 +1,7 @@ - + diff --git a/plugins/community/repos/LindenbergResearch/res/ScrewLight.afdesign b/plugins/community/repos/LindenbergResearch/res/ScrewLight.afdesign new file mode 100644 index 00000000..468dde85 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/ScrewLight.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/ScrewLight.svg b/plugins/community/repos/LindenbergResearch/res/ScrewLight.svg new file mode 100644 index 00000000..c2442435 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/ScrewLight.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/SimpleFilter.afdesign b/plugins/community/repos/LindenbergResearch/res/SimpleFilter.afdesign index 1403b5c0..64c69b5c 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/SimpleFilter.afdesign and b/plugins/community/repos/LindenbergResearch/res/SimpleFilter.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/SimpleFilter.svg b/plugins/community/repos/LindenbergResearch/res/SimpleFilter.svg index 01b4adc0..5d09a1b2 100644 --- a/plugins/community/repos/LindenbergResearch/res/SimpleFilter.svg +++ b/plugins/community/repos/LindenbergResearch/res/SimpleFilter.svg @@ -2,7 +2,7 @@ - + diff --git a/plugins/community/repos/LindenbergResearch/res/SmallKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/SmallKnob.afdesign index 3eecb4fa..5eba8b72 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/SmallKnob.afdesign and b/plugins/community/repos/LindenbergResearch/res/SmallKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/SmallKnob.svg b/plugins/community/repos/LindenbergResearch/res/SmallKnob.svg index e0e1896f..9098dade 100644 --- a/plugins/community/repos/LindenbergResearch/res/SmallKnob.svg +++ b/plugins/community/repos/LindenbergResearch/res/SmallKnob.svg @@ -1,16 +1,16 @@ - + - + - - + + - + diff --git a/plugins/community/repos/LindenbergResearch/res/Switch0.afdesign b/plugins/community/repos/LindenbergResearch/res/Switch0.afdesign index 2c694a3d..7faacc53 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/Switch0.afdesign and b/plugins/community/repos/LindenbergResearch/res/Switch0.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/Switch0.svg b/plugins/community/repos/LindenbergResearch/res/Switch0.svg index 83fd938e..7c112a3b 100644 --- a/plugins/community/repos/LindenbergResearch/res/Switch0.svg +++ b/plugins/community/repos/LindenbergResearch/res/Switch0.svg @@ -1,17 +1,22 @@ - - - - - + + + + + + + + + + + + + + + + - - - - - - diff --git a/plugins/community/repos/LindenbergResearch/res/Switch1.afdesign b/plugins/community/repos/LindenbergResearch/res/Switch1.afdesign index a4325846..5fabba88 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/Switch1.afdesign and b/plugins/community/repos/LindenbergResearch/res/Switch1.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/Switch1.svg b/plugins/community/repos/LindenbergResearch/res/Switch1.svg index b025ee9a..5cbbd911 100644 --- a/plugins/community/repos/LindenbergResearch/res/Switch1.svg +++ b/plugins/community/repos/LindenbergResearch/res/Switch1.svg @@ -1,17 +1,22 @@ - - - - - + + + + + + + + + + + + + + + + - - - - - - diff --git a/plugins/community/repos/LindenbergResearch/res/ToggleKnob.afdesign b/plugins/community/repos/LindenbergResearch/res/ToggleKnob.afdesign index 12db0625..9b3cdc63 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/ToggleKnob.afdesign and b/plugins/community/repos/LindenbergResearch/res/ToggleKnob.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/ToggleKnob.svg b/plugins/community/repos/LindenbergResearch/res/ToggleKnob.svg index 67672e14..a177b16c 100644 --- a/plugins/community/repos/LindenbergResearch/res/ToggleKnob.svg +++ b/plugins/community/repos/LindenbergResearch/res/ToggleKnob.svg @@ -1,24 +1,23 @@ - - + + - - - - - - + + + + + - - + + - + - - + + diff --git a/plugins/community/repos/LindenbergResearch/res/VCF.afdesign b/plugins/community/repos/LindenbergResearch/res/VCF.afdesign index 527f4a01..eb3d40bf 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/VCF.afdesign and b/plugins/community/repos/LindenbergResearch/res/VCF.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/VCF.svg b/plugins/community/repos/LindenbergResearch/res/VCF.svg index e8c1b323..e0edf4fd 100644 --- a/plugins/community/repos/LindenbergResearch/res/VCF.svg +++ b/plugins/community/repos/LindenbergResearch/res/VCF.svg @@ -1,7 +1,7 @@ - - + + @@ -17,36 +17,36 @@ - - - - - + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - + + + + + @@ -203,29 +203,29 @@ - - + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/VCO.afdesign b/plugins/community/repos/LindenbergResearch/res/VCO.afdesign index 0c887bd8..39c6b9ff 100644 Binary files a/plugins/community/repos/LindenbergResearch/res/VCO.afdesign and b/plugins/community/repos/LindenbergResearch/res/VCO.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/VCO.svg b/plugins/community/repos/LindenbergResearch/res/VCO.svg index dddf021c..d7f4be2e 100644 --- a/plugins/community/repos/LindenbergResearch/res/VCO.svg +++ b/plugins/community/repos/LindenbergResearch/res/VCO.svg @@ -1,10 +1,10 @@ - + - + - + @@ -39,30 +39,259 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + @@ -129,4 +336,16 @@ + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/res/Westcoast.afdesign b/plugins/community/repos/LindenbergResearch/res/Westcoast.afdesign new file mode 100644 index 00000000..64b375b9 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch/res/Westcoast.afdesign differ diff --git a/plugins/community/repos/LindenbergResearch/res/Westcoast.svg b/plugins/community/repos/LindenbergResearch/res/Westcoast.svg new file mode 100644 index 00000000..13430e4d --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/res/Westcoast.svg @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/src/AlmaFilter.cpp b/plugins/community/repos/LindenbergResearch/src/AlmaFilter.cpp index d6e4834d..a3aad989 100644 --- a/plugins/community/repos/LindenbergResearch/src/AlmaFilter.cpp +++ b/plugins/community/repos/LindenbergResearch/src/AlmaFilter.cpp @@ -34,7 +34,7 @@ struct AlmaFilter : LRModule { NUM_LIGHTS }; - LadderFilter filter; + dsp::LadderFilter *filter = new dsp::LadderFilter(engineGetSampleRate()); LRBigKnob *frqKnob = NULL; LRMiddleKnob *peakKnob = NULL; @@ -45,6 +45,7 @@ struct AlmaFilter : LRModule { void step() override; + void onSampleRateChange() override; }; @@ -53,10 +54,10 @@ void AlmaFilter::step() { float rescv = inputs[RESONANCE_CV_INPUT].value * 0.1f * quadraticBipolar(params[RESONANCE_CV_PARAM].value); float drvcv = inputs[DRIVE_CV_INPUT].value * 0.1f * quadraticBipolar(params[DRIVE_CV_PARAM].value); - filter.setFrequency(params[CUTOFF_PARAM].value + frqcv); - filter.setResonance(params[RESONANCE_PARAM].value + rescv); - filter.setDrive(params[DRIVE_PARAM].value + drvcv); - filter.setSlope(params[SLOPE_PARAM].value); + filter->setFrequency(params[CUTOFF_PARAM].value + frqcv); + filter->setResonance(params[RESONANCE_PARAM].value + rescv); + filter->setDrive(params[DRIVE_PARAM].value + drvcv); + filter->setSlope(params[SLOPE_PARAM].value); /* pass modulated parameter to knob widget for cv indicator */ @@ -73,13 +74,19 @@ void AlmaFilter::step() { float y = inputs[FILTER_INPUT].value; - filter.setIn(y); - filter.process(); + filter->setIn(y); + filter->process(); - outputs[LP_OUTPUT].value = filter.getLpOut(); + outputs[LP_OUTPUT].value = filter->getLpOut(); - lights[OVERLOAD_LIGHT].value = filter.getLightValue(); + lights[OVERLOAD_LIGHT].value = filter->getLightValue(); +} + + +void AlmaFilter::onSampleRateChange() { + Module::onSampleRateChange(); + filter->setSamplerate(engineGetSampleRate()); } @@ -92,9 +99,6 @@ struct AlmaFilterWidget : LRModuleWidget { AlmaFilterWidget::AlmaFilterWidget(AlmaFilter *module) : LRModuleWidget(module) { - //setPanel(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); - - panel = new LRPanel(); panel->setBackground(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); addChild(panel); @@ -125,21 +129,21 @@ AlmaFilterWidget::AlmaFilterWidget(AlmaFilter *module) : LRModuleWidget(module) addParam(ParamWidget::create(Vec(78, 106), module, AlmaFilter::CUTOFF_CV_PARAM, -1.f, 1.f, 0.f)); addParam(ParamWidget::create(Vec(127.1, 106), module, AlmaFilter::DRIVE_CV_PARAM, -1.f, 1.f, 0.f)); - addInput(Port::create(Vec(26, 50), Port::INPUT, module, AlmaFilter::RESONANCE_CV_INPUT)); - addInput(Port::create(Vec(76, 50), Port::INPUT, module, AlmaFilter::CUTOFF_CV_INPUT)); - addInput(Port::create(Vec(125, 50), Port::INPUT, module, AlmaFilter::DRIVE_CV_INPUT)); + addInput(Port::create(Vec(26, 50), Port::INPUT, module, AlmaFilter::RESONANCE_CV_INPUT)); + addInput(Port::create(Vec(76, 50), Port::INPUT, module, AlmaFilter::CUTOFF_CV_INPUT)); + addInput(Port::create(Vec(125, 50), Port::INPUT, module, AlmaFilter::DRIVE_CV_INPUT)); // ***** CV INPUTS ******* // ***** INPUTS ********** - addInput(Port::create(Vec(25, 326.5), Port::INPUT, module, AlmaFilter::FILTER_INPUT)); + addInput(Port::create(Vec(25, 326.5), Port::INPUT, module, AlmaFilter::FILTER_INPUT)); // ***** INPUTS ********** // ***** OUTPUTS ********* - addOutput(Port::create(Vec(124.5, 326.5), Port::OUTPUT, module, AlmaFilter::LP_OUTPUT)); + addOutput(Port::create(Vec(124.5, 326.5), Port::OUTPUT, module, AlmaFilter::LP_OUTPUT)); // ***** OUTPUTS ********* // ***** LIGHTS ********** - addChild(ModuleLightWidget::create(Vec(85, 247), module, AlmaFilter::OVERLOAD_LIGHT)); + addChild(ModuleLightWidget::create(Vec(85, 247), module, AlmaFilter::OVERLOAD_LIGHT)); // ***** LIGHTS ********** } diff --git a/plugins/community/repos/LindenbergResearch/src/BlankPanel.cpp b/plugins/community/repos/LindenbergResearch/src/BlankPanel.cpp index 667ea109..04212390 100644 --- a/plugins/community/repos/LindenbergResearch/src/BlankPanel.cpp +++ b/plugins/community/repos/LindenbergResearch/src/BlankPanel.cpp @@ -16,8 +16,6 @@ struct BlankPanel : Module { NUM_LIGHTS }; - LCDWidget *lcd1 = new LCDWidget(LCD_COLOR_FG, 15); - BlankPanel() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} @@ -40,18 +38,16 @@ struct BlankPanelWidget : LRModuleWidget { BlankPanelWidget::BlankPanelWidget(BlankPanel *module) : LRModuleWidget(module) { - //setPanel(SVG::load(assetPlugin(plugin, "res/BlankPanel.svg"))); - panel = new LRPanel(); panel->setBackground(SVG::load(assetPlugin(plugin, "res/BlankPanel.svg"))); addChild(panel); box.size = panel->box.size; - float speed = 0.002; + float speed = 0.004; - addChild(SVGRotator::create(Vec(140.5, 83), SVG::load(assetPlugin(plugin, "res/CogBig.svg")), speed)); - addChild(SVGRotator::create(Vec(120, 114.7), SVG::load(assetPlugin(plugin, "res/CogSmall.svg")), -speed * 1.6)); + addChild(SVGRotator::create(Vec(140.5, 65), SVG::load(assetPlugin(plugin, "res/CogBig.svg")), speed)); + addChild(SVGRotator::create(Vec(120, 96.7), SVG::load(assetPlugin(plugin, "res/CogSmall.svg")), -speed * 1.6)); // ***** SCREWS ********** @@ -60,12 +56,6 @@ BlankPanelWidget::BlankPanelWidget(BlankPanel *module) : LRModuleWidget(module) addChild(Widget::create(Vec(15, 366))); addChild(Widget::create(Vec(box.size.x - 30, 366))); // ***** SCREWS ********** - - // ***** LCD ************* - /* module->lcd1->box.pos = Vec(34, 355); - addChild(module->lcd1); - module->lcd1->text = VERSION_STR;*/ - // ***** LCD ************* } } // namespace rack_plugin_LindenbergResearch diff --git a/plugins/community/repos/LindenbergResearch/src/BlankPanelM1.cpp b/plugins/community/repos/LindenbergResearch/src/BlankPanelM1.cpp index 40f84e8a..c4e5a32a 100644 --- a/plugins/community/repos/LindenbergResearch/src/BlankPanelM1.cpp +++ b/plugins/community/repos/LindenbergResearch/src/BlankPanelM1.cpp @@ -28,6 +28,108 @@ void BlankPanelM1::step() { } +struct BlankPanelSmall : Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + M1_INPUT, + M2_INPUT, + NUM_INPUTS + }; + enum OutputIds { + M1_OUTPUT, + M2_OUTPUT, + M3_OUTPUT, + M4_OUTPUT, + M5_OUTPUT, + M6_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + + BlankPanelSmall() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} + + + LRIOPort *ioports[8]; + bool multiple = false; + + void step() override; + void createPorts(); + + + void showPorts() { + /* set all to visible */ + for (int i = 0; i < 8; i++) { + ioports[i]->visible = true; + } + } + + + void hidePorts() { + /* set all to invisible */ + for (int i = 0; i < 8; i++) { + ioports[i]->visible = false; + } + } + + + void onReset() override { + multiple = false; + } + + + json_t *toJson() override { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "multiple", json_boolean(multiple)); + return rootJ; + } + + + void fromJson(json_t *rootJ) override { + json_t *multJ = json_object_get(rootJ, "multiple"); + if (multJ) + multiple = json_boolean_value(multJ); + } +}; + + +void BlankPanelSmall::step() { + + for (int i = 0; i < 8; i++) { + if (ioports[i] == nullptr) return; + } + + if (multiple) { + if (!ioports[0]->visible) { + showPorts(); + } + + if (inputs[M1_INPUT].active) { + float sig = inputs[M1_INPUT].value; + outputs[M1_OUTPUT].value = sig; + outputs[M2_OUTPUT].value = sig; + outputs[M3_OUTPUT].value = sig; + } + + + if (inputs[M2_INPUT].active) { + float sig = inputs[M2_INPUT].value; + outputs[M4_OUTPUT].value = sig; + outputs[M5_OUTPUT].value = sig; + outputs[M6_OUTPUT].value = sig; + } + } else { + if (ioports[0]->visible) { + hidePorts(); + } + } +} + + /** * @brief Blank Panel Mark I */ @@ -36,9 +138,16 @@ struct BlankPanelWidgetM1 : LRModuleWidget { }; -BlankPanelWidgetM1::BlankPanelWidgetM1(BlankPanelM1 *module) : LRModuleWidget(module) { - // setPanel(SVG::load(assetPlugin(plugin, "res/BlankPanelM1.svg"))); +/** + * @brief Blank Panel Small + */ +struct BlankPanelWidgetSmall : LRModuleWidget { + BlankPanelWidgetSmall(BlankPanelSmall *module); + void appendContextMenu(Menu *menu) override; +}; + +BlankPanelWidgetM1::BlankPanelWidgetM1(BlankPanelM1 *module) : LRModuleWidget(module) { panel = new LRPanel(); panel->setBackground(SVG::load(assetPlugin(plugin, "res/BlankPanelM1.svg"))); addChild(panel); @@ -53,6 +162,97 @@ BlankPanelWidgetM1::BlankPanelWidgetM1(BlankPanelM1 *module) : LRModuleWidget(mo // ***** SCREWS ********** } + +void BlankPanelSmall::createPorts() { + /* INPUTS */ + ioports[0] = Port::create(Vec(16.5, 19.5), Port::INPUT, this, BlankPanelSmall::M1_INPUT); + ioports[1] = Port::create(Vec(16.5, 228.5), Port::INPUT, this, BlankPanelSmall::M2_INPUT); + + /* OUTPUTS */ + ioports[2] = Port::create(Vec(16.5, 53.5), Port::OUTPUT, this, BlankPanelSmall::M1_OUTPUT); + ioports[3] = Port::create(Vec(16.5, 87.5), Port::OUTPUT, this, BlankPanelSmall::M2_OUTPUT); + ioports[4] = Port::create(Vec(16.5, 120.5), Port::OUTPUT, this, BlankPanelSmall::M3_OUTPUT); + ioports[5] = Port::create(Vec(16.5, 262.5), Port::OUTPUT, this, BlankPanelSmall::M4_OUTPUT); + ioports[6] = Port::create(Vec(16.5, 296.5), Port::OUTPUT, this, BlankPanelSmall::M5_OUTPUT); + ioports[7] = Port::create(Vec(16.5, 329.5), Port::OUTPUT, this, BlankPanelSmall::M6_OUTPUT); + + hidePorts(); +} + + +BlankPanelWidgetSmall::BlankPanelWidgetSmall(BlankPanelSmall *module) : LRModuleWidget(module) { + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/BlankPanelSmall.svg"))); + addChild(panel); + + box.size = panel->box.size; + + module->createPorts(); + + // ***** SCREWS ********** + addChild(Widget::create(Vec(23.4, 1))); + addChild(Widget::create(Vec(23.4, 366))); + // ***** SCREWS ********** + + + // ***** IO-PORTS ********** + addInput(module->ioports[0]); + addInput(module->ioports[1]); + + addOutput(module->ioports[2]); + addOutput(module->ioports[3]); + addOutput(module->ioports[4]); + addOutput(module->ioports[5]); + addOutput(module->ioports[6]); + addOutput(module->ioports[7]); + // ***** IO-PORTS ********** +} + + +struct BlankPanelMultiple : MenuItem { + BlankPanelSmall *blankPanelSmall; + + + void onAction(EventAction &e) override { + if (blankPanelSmall->multiple) { + for (int i = 0; i < 2; i++) { + if (blankPanelSmall->inputs[i].active) { + blankPanelSmall->multiple = true; + return; + } + } + + for (int i = 0; i < 6; i++) { + if (blankPanelSmall->outputs[i].active) { + blankPanelSmall->multiple = true; + return; + } + + blankPanelSmall->multiple = false; + } + } else { + blankPanelSmall->multiple = true; + } + } + + + void step() override { + rightText = CHECKMARK(blankPanelSmall->multiple); + } +}; + + +void BlankPanelWidgetSmall::appendContextMenu(Menu *menu) { + menu->addChild(MenuEntry::create()); + + BlankPanelSmall *blankPanelSmall = dynamic_cast(module); + assert(blankPanelSmall); + + BlankPanelMultiple *mergeItem = MenuItem::create("Dual Multiple"); + mergeItem->blankPanelSmall = blankPanelSmall; + menu->addChild(mergeItem); +} + } // namespace rack_plugin_LindenbergResearch using namespace rack_plugin_LindenbergResearch; @@ -62,3 +262,10 @@ RACK_PLUGIN_MODEL_INIT(LindenbergResearch, BlankPanelM1) { BLANK_TAG); return modelBlankPanelM1; } + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, BlankPanelSmall) { + Model *modelBlankPanelSmall = Model::create("Lindenberg Research", "BlankPanel Small", + "Blank Panel Small", + BLANK_TAG); + return modelBlankPanelSmall; +} diff --git a/plugins/community/repos/LindenbergResearch/src/LRComponents.cpp b/plugins/community/repos/LindenbergResearch/src/LRComponents.cpp index 2d586370..c2bd7b4a 100644 --- a/plugins/community/repos/LindenbergResearch/src/LRComponents.cpp +++ b/plugins/community/repos/LindenbergResearch/src/LRComponents.cpp @@ -4,22 +4,23 @@ /** * @brief Constructor of LCD Widget */ -LCDWidget::LCDWidget(NVGcolor fg, unsigned char length) { +LCDWidget::LCDWidget(NVGcolor fg, unsigned char length, std::string format, LCDType type, float fontsize) { /** load LCD ttf font */ - gLCDFont_DIG7 = Font::load(assetPlugin(plugin, LCD_FONT_DIG7)); + ttfLCDDig7 = Font::load(assetPlugin(plugin, LCD_FONT_DIG7)); + LCDWidget::fontsize = fontsize; - auto r = (unsigned char) (fg.r * 0xFF); - auto g = (unsigned char) (fg.g * 0xFF); - auto b = (unsigned char) (fg.b * 0xFF); + LCDWidget::type = type; LCDWidget::length = length; + LCDWidget::format = format; LCDWidget::fg = fg; - LCDWidget::bg = nvgRGBA(r - 0x40, g - 0x40, b - 0x40, 0x40); -} - + LCDWidget::bg = nvgRGBAf(fg.r, fg.g, fg.b, 0.15f); -LRModuleWidget::LRModuleWidget(Module *module) : ModuleWidget(module) { + for (int i = 0; i < LCDWidget::length; ++i) { + s1.append("O"); + s2.append("X"); + } } @@ -28,38 +29,58 @@ LRModuleWidget::LRModuleWidget(Module *module) : ModuleWidget(module) { * @param vg */ void LCDWidget::draw(NVGcontext *vg) { - nvgFontSize(vg, LCD_FONTSIZE); - nvgFontFaceId(vg, gLCDFont_DIG7->handle); + nvgFontSize(vg, fontsize); + nvgFontFaceId(vg, ttfLCDDig7->handle); nvgTextLetterSpacing(vg, LCD_LETTER_SPACING); nvgFillColor(vg, bg); + std::string str; - std::string s1; - std::string s2; + nvgTextBox(vg, 0, 0, 220, s1.c_str(), nullptr); + nvgTextBox(vg, 0, 0, 220, s2.c_str(), nullptr); - for (int i = 0; i < LCDWidget::length; ++i) { - s1.append("8"); - s2.append(":"); + /** if set to inactive just draw the background segments */ + if (!active) return; + + + // if set to numeric, do some formatting + if (type == NUMERIC) { + str = stringf(format.c_str(), value); + + // quick and dirty, urgs + if (value < 10) + text = "0" + text; } - nvgTextBox(vg, 0, 0, 220, s1.c_str(), nullptr); - nvgTextBox(vg, 0, 0, 220, s2.c_str(), nullptr); + // on text mode just format + if (type == TEXT) { + str = stringf(format.c_str(), text.c_str()); + } + + // on list mode get current item out of the current value + if (type == LIST) { + unsigned long index; + long current = lround(value); + + if (current < 0) { + index = 0; + } else if ((unsigned long) current >= items.size()) { + index = items.size() - 1; + } else { + index = (unsigned long) current; + } + + str = stringf(format.c_str(), items[index].c_str()); + } nvgFillColor(vg, fg); - nvgTextBox(vg, 0, 0, 220, text.c_str(), nullptr); + nvgTextBox(vg, 0, 0, 220, str.c_str(), nullptr); } -void LRRedLight::draw(NVGcontext *vg) { - //LightWidget::draw(vg); - +void LRLight::draw(NVGcontext *vg) { float radius = box.size.x / 1.5f; - float oradius = radius + 10.0f; - - /* color.r = clampf(color.r, 0.0f, 1.0f); - color.g = clampf(color.g, 0.0f, 1.0f); - color.b = clampf(color.b, 0.0f, 1.0f); - color.a = clampf(color.a, 0.0f, 1.0f);*/ + float oradius = radius + 14.0f; // Solid nvgBeginPath(vg); @@ -84,7 +105,7 @@ void LRRedLight::draw(NVGcontext *vg) { nvgRect(vg, radius - oradius, radius - oradius, 2 * oradius, 2 * oradius); NVGpaint paint; NVGcolor icol = color; - icol.a *= 0.40f; + icol.a *= 0.30f; NVGcolor ocol = color; ocol.a = 0.00f; paint = nvgRadialGradient(vg, radius, radius, radius, oradius, icol, ocol); @@ -96,8 +117,8 @@ void LRRedLight::draw(NVGcontext *vg) { /** * @brief Constructor */ -LRRedLight::LRRedLight() { - addBaseColor(COLOR_RED); +LRLight::LRLight() { + addBaseColor(nvgRGBAf(0.1, 0.3, 0.9, 0.99)); } @@ -236,6 +257,16 @@ void LRPanel::setOuter(const NVGcolor &outer) { LRPanel::LRPanel() {} +const NVGcolor &LRPanel::getInner() const { + return inner; +} + + +const NVGcolor &LRPanel::getOuter() const { + return outer; +} + + SVGRotator::SVGRotator() : FramebufferWidget() { tw = new TransformWidget(); addChild(tw); @@ -272,4 +303,12 @@ void SVGRotator::step() { dirty = true; FramebufferWidget::step(); +} + + +/** + * @brief + * @param module + */ +LRModuleWidget::LRModuleWidget(Module *module) : ModuleWidget(module) { } \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch/src/LRComponents.hpp b/plugins/community/repos/LindenbergResearch/src/LRComponents.hpp index 48d30922..ad428c8b 100644 --- a/plugins/community/repos/LindenbergResearch/src/LRComponents.hpp +++ b/plugins/community/repos/LindenbergResearch/src/LRComponents.hpp @@ -5,10 +5,13 @@ #include "widgets.hpp" #define LCD_FONT_DIG7 "res/digital-7.ttf" -#define LCD_COLOR_FG nvgRGBA(0x00, 0xE1, 0xE4, 0xFF) -#define LCD_FONTSIZE 8 +#define LCD_FONTSIZE 11 #define LCD_LETTER_SPACING 0 +/* show values of all knobs */ +#define DEBUG_VALUES false + +typedef std::shared_ptr TrueType; using namespace rack; #ifdef USE_VST2 @@ -57,23 +60,45 @@ struct LRModuleWidget : ModuleWidget { * @brief Emulation of a LCD monochrome display */ struct LCDWidget : Label { - std::shared_ptr gLCDFont_DIG7; + + enum LCDType { + NUMERIC, + TEXT, + LIST + }; + + TrueType ttfLCDDig7; + float fontsize; + + LCDType type; + NVGcolor fg; NVGcolor bg; + + bool active = true; + float value = 0.0; unsigned char length = 0; + std::string format; + std::vector items; + std::string s1; + std::string s2; /** * @brief Constructor */ - LCDWidget(NVGcolor fg, unsigned char length); - + LCDWidget(NVGcolor fg, unsigned char length, std::string format, LCDType type, float fontsize = LCD_FONTSIZE); /** * @brief Draw LCD display * @param vg */ void draw(NVGcontext *vg) override; + + + inline void addItem(std::string name) { + items.push_back(name); + } }; @@ -175,7 +200,9 @@ private: /** setup indicator with default values */ Indicator idc = Indicator(15.f, ANGLE); - LRShadow shadow = LRShadow(); + + bool debug = DEBUG_VALUES; + TrueType font; /** snap mode */ bool snap = false; @@ -184,6 +211,11 @@ private: /** snap sensitivity */ float snapSens = 0.1; +protected: + /** shader */ + LRShadow shadow = LRShadow(); + + public: /** * @brief Default constructor @@ -191,6 +223,8 @@ public: LRKnob() { minAngle = -ANGLE * (float) M_PI; maxAngle = ANGLE * (float) M_PI; + + font = Font::load(assetGlobal("res/fonts/ShareTechMono-Regular.ttf")); } @@ -243,16 +277,6 @@ public: } - /** - * @brief Route setter to shadow - * @param x - * @param y - */ - void setShadowPosition(float x, float y) { - shadow.setShadowPosition(x, y); - } - - /** * @brief Creates a new instance of a LRKnob child * @tparam TParamWidget Subclass of LRKnob @@ -287,8 +311,31 @@ public: /** component */ FramebufferWidget::draw(vg); +/* + nvgBeginPath(vg); + nvgRect(vg, -30, -30, box.size.x + 60, box.size.y + 60); + + NVGcolor icol = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.3f); + NVGcolor ocol = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.0f);; + + NVGpaint paint = nvgRadialGradient(vg, box.size.x / 2, box.size.y / 2, + 0.f, box.size.x /2.f * 0.9f, icol, ocol); + nvgFillPaint(vg, paint); + nvgFill(vg);*/ + /** indicator */ idc.draw(vg); + + if (debug) { + auto text = stringf("%4.2f", value); + nvgFontSize(vg, 15); + nvgFontFaceId(vg, font->handle); + + nvgFillColor(vg, nvgRGBAf(1.f, 1.f, 1.0f, 1.0f)); + nvgText(vg, box.size.x - 5, box.size.y + 10, text.c_str(), NULL); + } + + } @@ -317,6 +364,7 @@ public: * @param e */ void onChange(EventChange &e) override { + // if the value still inside snap-tolerance keep the value zero if (snap && value > -snapSens + snapAt && value < snapSens + snapAt) value = 0; SVGKnob::onChange(e); } @@ -328,11 +376,15 @@ public: */ struct LRToggleKnob : LRKnob { LRToggleKnob(float length = 0.5f) { - minAngle = -length * (float) M_PI; + //TODO: parametrize start and end angle + minAngle = -0.666666f * (float) M_PI; maxAngle = length * (float) M_PI; setSVG(SVG::load(assetPlugin(plugin, "res/ToggleKnob.svg"))); - setShadowPosition(2, 2); + shadow.setShadowPosition(3, 4); + + shadow.setStrength(1.2f); + shadow.setSize(0.7f); speed = 2.f; } @@ -345,6 +397,34 @@ struct LRToggleKnob : LRKnob { }; +/** + * @brief Quantize position to odd numbers to simulate a toggle switch + */ +struct LRMiddleIncremental : LRKnob { + LRMiddleIncremental(float length = 0.5f) { + minAngle = -length * (float) M_PI; + maxAngle = length * (float) M_PI; + + setSVG(SVG::load(assetPlugin(plugin, "res/MiddleIncremental.svg"))); + shadow.setShadowPosition(3, 4); + + shadow.setStrength(1.2f); + shadow.setSize(0.7f); + + speed = 3.f; + } + + + void onChange(EventChange &e) override { + + value = lround(value); + + //value = round(value); + SVGKnob::onChange(e); + } +}; + + /** * @brief LR Big Knob */ @@ -352,7 +432,7 @@ struct LRBigKnob : LRKnob { LRBigKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/BigKnob.svg"))); setIndicatorDistance(15); - setShadowPosition(5, 6); + shadow.setShadowPosition(5, 6); } }; @@ -364,7 +444,7 @@ struct LRMiddleKnob : LRKnob { LRMiddleKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/MiddleKnob.svg"))); setIndicatorDistance(12); - setShadowPosition(4, 4); + shadow.setShadowPosition(4, 4); } }; @@ -375,11 +455,49 @@ struct LRMiddleKnob : LRKnob { struct LRSmallKnob : LRKnob { LRSmallKnob() { setSVG(SVG::load(assetPlugin(plugin, "res/SmallKnob.svg"))); - setShadowPosition(3, 3); + shadow.setShadowPosition(3, 3); + setSnap(0.0f, 0.03f); + + + speed = 0.9f; + } +}; + + +/** + * @brief LR Alternate Small Knob + */ +struct LRAlternateSmallKnob : LRKnob { + LRAlternateSmallKnob() { + setSVG(SVG::load(assetPlugin(plugin, "res/AlternateSmallKnob.svg"))); + shadow.setShadowPosition(3, 3); setSnap(0.0f, 0.03f); - speed = 0.7f; + speed = 0.9f; + } +}; + +/** + * @brief LR Middle Knob + */ +struct LRAlternateMiddleKnob : LRKnob { + LRAlternateMiddleKnob() { + setSVG(SVG::load(assetPlugin(plugin, "res/AlternateMiddleKnob.svg"))); + setIndicatorDistance(12); + shadow.setShadowPosition(4, 4); + } +}; + + +/** + * @brief LR Big Knob + */ +struct LRAlternateBigKnob : LRKnob { + LRAlternateBigKnob() { + setSVG(SVG::load(assetPlugin(plugin, "res/AlternateBigKnob.svg"))); + setIndicatorDistance(15); + shadow.setShadowPosition(5, 6); } }; @@ -387,12 +505,12 @@ struct LRSmallKnob : LRKnob { /** * @brief Alternative IO Port */ -struct IOPort : SVGPort { +struct LRIOPort : SVGPort { private: LRShadow shadow = LRShadow(); public: - IOPort() { + LRIOPort() { background->svg = SVG::load(assetPlugin(plugin, "res/IOPortB.svg")); background->wrap(); box.size = background->box.size; @@ -400,7 +518,7 @@ public: /** inherit dimensions */ shadow.setBox(box); shadow.setSize(0.50); - shadow.setShadowPosition(2, 2); + shadow.setShadowPosition(2, 1); } @@ -415,6 +533,36 @@ public: }; +/** + * @brief Alternative IO Port + */ +struct LRIOPortC : SVGPort { +private: + LRShadow shadow = LRShadow(); + +public: + LRIOPortC() { + background->svg = SVG::load(assetPlugin(plugin, "res/IOPortC.svg")); + background->wrap(); + box.size = background->box.size; + + /** inherit dimensions */ + shadow.setBox(box); + shadow.setSize(0.50); + shadow.setShadowPosition(2, 1); + } + + + /** + * @brief Hook into draw method + * @param vg + */ + void draw(NVGcontext *vg) override { + shadow.draw(vg); + SVGPort::draw(vg); + } +}; + /** * @brief Alternative screw head A */ @@ -427,6 +575,17 @@ struct ScrewDarkA : SVGScrew { }; +/** + * @brief Alternative screw head A + */ +struct ScrewLight : SVGScrew { + ScrewLight() { + sw->svg = SVG::load(assetPlugin(plugin, "res/ScrewLight.svg")); + sw->wrap(); + box.size = sw->box.size; + } +}; + /** * @brief Custom switch based on original Rack files */ @@ -441,8 +600,8 @@ struct LRSwitch : SVGSwitch, ToggleSwitch { /** * @brief Standard LED Redlight */ -struct LRRedLight : SmallLight { - LRRedLight(); +struct LRLight : SmallLight { + LRLight(); void draw(NVGcontext *vg) override; }; @@ -454,17 +613,16 @@ struct LRRedLight : SmallLight { struct LRPanel : SVGPanel { private: /** margin of gradient box */ - static constexpr float MARGIN = 20; + static constexpr float MARGIN = 10; /** gradient colors */ - NVGcolor inner = nvgRGBAf(.6f, 0.7f, 0.9f, 0.12f); - NVGcolor outer = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.0f);; + NVGcolor inner = nvgRGBAf(1.5f * .369f, 1.5f * 0.357f, 1.5f * 0.3333f, 0.25f); + NVGcolor outer = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.34f);; /** gradient offset */ - Vec offset = Vec(-40, -50); + Vec offset = Vec(30, -50); + - void setInner(const NVGcolor &inner); - void setOuter(const NVGcolor &outer); public: LRPanel(); @@ -475,6 +633,11 @@ public: } + void setInner(const NVGcolor &inner); + void setOuter(const NVGcolor &outer); + const NVGcolor &getInner() const; + const NVGcolor &getOuter() const; + void draw(NVGcontext *vg) override; }; diff --git a/plugins/community/repos/LindenbergResearch/src/LindenbergResearch.cpp b/plugins/community/repos/LindenbergResearch/src/LindenbergResearch.cpp index b660c7b1..af5ba16c 100644 --- a/plugins/community/repos/LindenbergResearch/src/LindenbergResearch.cpp +++ b/plugins/community/repos/LindenbergResearch/src/LindenbergResearch.cpp @@ -6,19 +6,29 @@ RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, SimpleFilter); RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, MS20Filter); RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, AlmaFilter); RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, ReShaper); +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, Westcoast); + +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, VCO); + RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, BlankPanel); RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, BlankPanelM1); +// RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, BlankPanelSmall); RACK_PLUGIN_INIT(LindenbergResearch) { RACK_PLUGIN_INIT_ID(); + RACK_PLUGIN_INIT_WEBSITE("https://github.com/lindenbergresearch/LRTRack"); + RACK_PLUGIN_INIT_MANUAL("https://github.com/lindenbergresearch/LRTRack"); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, SimpleFilter); RACK_PLUGIN_MODEL_ADD(LindenbergResearch, MS20Filter); RACK_PLUGIN_MODEL_ADD(LindenbergResearch, AlmaFilter); RACK_PLUGIN_MODEL_ADD(LindenbergResearch, ReShaper); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, Westcoast); - //TODO: RACK_PLUGIN_MODEL_ADD(LindenbergResearch, VCO); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, VCO); RACK_PLUGIN_MODEL_ADD(LindenbergResearch, BlankPanel); RACK_PLUGIN_MODEL_ADD(LindenbergResearch, BlankPanelM1); + // RACK_PLUGIN_MODEL_ADD(LindenbergResearch, BlankPanelSmall); // crash } diff --git a/plugins/community/repos/LindenbergResearch/src/MS20Filter.cpp b/plugins/community/repos/LindenbergResearch/src/MS20Filter.cpp index d3c91d01..6a47ccb5 100644 --- a/plugins/community/repos/LindenbergResearch/src/MS20Filter.cpp +++ b/plugins/community/repos/LindenbergResearch/src/MS20Filter.cpp @@ -110,9 +110,9 @@ MS20FilterWidget::MS20FilterWidget(MS20Filter *module) : LRModuleWidget(module) // ***** SCREWS ********** // ***** MAIN KNOBS ****** - module->frqKnob = LRKnob::create(Vec(102.f, 64.9f), module, MS20Filter::FREQUENCY_PARAM, 0.f, 1.f, 1.f); - module->peakKnob = LRKnob::create(Vec(110.f, 160.8f), module, MS20Filter::PEAK_PARAM, 0.0f, 1.0f, 0.0f); - module->driveKnob = LRKnob::create(Vec(110.f, 230.6f), module, MS20Filter::DRIVE_PARAM, 0.f, 1.0f, 0.0f); + module->frqKnob = LRKnob::create(Vec(102, 64.9), module, MS20Filter::FREQUENCY_PARAM, 0.f, 1.f, 1.f); + module->peakKnob = LRKnob::create(Vec(110, 160.8), module, MS20Filter::PEAK_PARAM, 0.0f, 1.0, 0.0f); + module->driveKnob = LRKnob::create(Vec(110, 230.6), module, MS20Filter::DRIVE_PARAM, 0.f, 1.0, 0.0f); addParam(module->frqKnob); addParam(module->peakKnob); @@ -121,25 +121,25 @@ MS20FilterWidget::MS20FilterWidget(MS20Filter *module) : LRModuleWidget(module) // ***** MAIN KNOBS ****** // ***** CV INPUTS ******* - addParam(ParamWidget::create(Vec(61.f, 169.3f), module, MS20Filter::PEAK_CV_PARAM, -1.f, 1.0f, 0.f)); - addParam(ParamWidget::create(Vec(61.f, 82.4f), module, MS20Filter::CUTOFF_CV_PARAM, -1.f, 1.f, 0.f)); - addParam(ParamWidget::create(Vec(61.f, 239.f), module, MS20Filter::GAIN_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(61, 169.3), module, MS20Filter::PEAK_CV_PARAM, -1.f, 1.0f, 0.f)); + addParam(ParamWidget::create(Vec(61, 82.4), module, MS20Filter::CUTOFF_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(61, 239), module, MS20Filter::GAIN_CV_PARAM, -1.f, 1.f, 0.f)); - addInput(Port::create(Vec(18, 168.5), Port::INPUT, module, MS20Filter::PEAK_CV_INPUT)); - addInput(Port::create(Vec(18, 81.5), Port::INPUT, module, MS20Filter::CUTOFF_CV_INPUT)); - addInput(Port::create(Vec(18, 239), Port::INPUT, module, MS20Filter::GAIN_CV_INPUT)); + addInput(Port::create(Vec(18, 168.5), Port::INPUT, module, MS20Filter::PEAK_CV_INPUT)); + addInput(Port::create(Vec(18, 81.5), Port::INPUT, module, MS20Filter::CUTOFF_CV_INPUT)); + addInput(Port::create(Vec(18, 239), Port::INPUT, module, MS20Filter::GAIN_CV_INPUT)); // ***** CV INPUTS ******* // ***** INPUTS ********** - addInput(Port::create(Vec(17.999f, 326.05f), Port::INPUT, module, MS20Filter::FILTER_INPUT)); + addInput(Port::create(Vec(17.999, 326.05), Port::INPUT, module, MS20Filter::FILTER_INPUT)); // ***** INPUTS ********** // ***** OUTPUTS ********* - addOutput(Port::create(Vec(58.544f, 326.05f), Port::OUTPUT, module, MS20Filter::FILTER_OUTPUT)); + addOutput(Port::create(Vec(58.544, 326.05), Port::OUTPUT, module, MS20Filter::FILTER_OUTPUT)); // ***** OUTPUTS ********* // ***** SWITCH ********* - addParam(ParamWidget::create(Vec(119.f, 331.f), module, MS20Filter::MODE_SWITCH_PARAM, 0.0f, 1.0f, 1.0f)); + addParam(ParamWidget::create(Vec(119, 331), module, MS20Filter::MODE_SWITCH_PARAM, 0.0, 1.0, 1.0)); // ***** SWITCH ********* } diff --git a/plugins/community/repos/LindenbergResearch/src/ReShaper.cpp b/plugins/community/repos/LindenbergResearch/src/ReShaper.cpp index 161815cd..2cca315f 100644 --- a/plugins/community/repos/LindenbergResearch/src/ReShaper.cpp +++ b/plugins/community/repos/LindenbergResearch/src/ReShaper.cpp @@ -77,12 +77,12 @@ ReShaperWidget::ReShaperWidget(ReShaper *module) : LRModuleWidget(module) { // ***** INPUTS ********** - addInput(Port::create(Vec(21, 60), Port::INPUT, module, ReShaper::RESHAPER_INPUT)); - addInput(Port::create(Vec(71, 60), Port::INPUT, module, ReShaper::RESHAPER_CV_INPUT)); + addInput(Port::create(Vec(21, 60), Port::INPUT, module, ReShaper::RESHAPER_INPUT)); + addInput(Port::create(Vec(71, 60), Port::INPUT, module, ReShaper::RESHAPER_CV_INPUT)); // ***** INPUTS ********** // ***** OUTPUTS ********* - addOutput(Port::create(Vec(46, 320), Port::OUTPUT, module, ReShaper::RESHAPER_OUTPUT)); + addOutput(Port::create(Vec(46, 320), Port::OUTPUT, module, ReShaper::RESHAPER_OUTPUT)); // ***** OUTPUTS ********* } diff --git a/plugins/community/repos/LindenbergResearch/src/Release.h b/plugins/community/repos/LindenbergResearch/src/Release.h index 5535e9ab..f384d391 100644 --- a/plugins/community/repos/LindenbergResearch/src/Release.h +++ b/plugins/community/repos/LindenbergResearch/src/Release.h @@ -1,6 +1,6 @@ #pragma once -#define VERSION_MAJOR "0" +#define VERSION_MAJOR "2" #define VERSION_MINOR "6" #define VERSION_PATCH "0" #define VERSION_STR "Version " VERSION_MAJOR "." VERSION_MINOR "." VERSION_PATCH diff --git a/plugins/community/repos/LindenbergResearch/src/SimpleFilter.cpp b/plugins/community/repos/LindenbergResearch/src/SimpleFilter.cpp index f099931c..2d38522c 100644 --- a/plugins/community/repos/LindenbergResearch/src/SimpleFilter.cpp +++ b/plugins/community/repos/LindenbergResearch/src/SimpleFilter.cpp @@ -159,16 +159,16 @@ SimpleFilterWidget::SimpleFilterWidget(SimpleFilter *module) : LRModuleWidget(mo addParam(ParamWidget::create(Vec(39 - 12, 120), module, SimpleFilter::CUTOFF_CV_PARAM, 0.f, 1.f, 0.f)); addParam(ParamWidget::create(Vec(111 - 12, 120), module, SimpleFilter::RESONANCE_CV_PARAM, 0.f, 1.f, 0.f)); - addInput(Port::create(Vec(39 - 14, 60), Port::INPUT, module, SimpleFilter::CUTOFF_CV_INPUT)); - addInput(Port::create(Vec(111 - 14, 60), Port::INPUT, module, SimpleFilter::RESONANCE_CV_INPUT)); + addInput(Port::create(Vec(39 - 14, 60), Port::INPUT, module, SimpleFilter::CUTOFF_CV_INPUT)); + addInput(Port::create(Vec(111 - 14, 60), Port::INPUT, module, SimpleFilter::RESONANCE_CV_INPUT)); // ***** CV INPUTS ******* // ***** INPUTS ********** - addInput(Port::create(Vec(39 - 14, 320), Port::INPUT, module, SimpleFilter::FILTER_INPUT)); + addInput(Port::create(Vec(39 - 14, 320), Port::INPUT, module, SimpleFilter::FILTER_INPUT)); // ***** INPUTS ********** // ***** OUTPUTS ********* - addOutput(Port::create(Vec(111 - 14, 320), Port::OUTPUT, module, SimpleFilter::FILTER_OUTPUT)); + addOutput(Port::create(Vec(111 - 14, 320), Port::OUTPUT, module, SimpleFilter::FILTER_OUTPUT)); // ***** OUTPUTS ********* } diff --git a/plugins/community/repos/LindenbergResearch/src/VCO.cpp b/plugins/community/repos/LindenbergResearch/src/VCO.cpp index c1d3d545..7ff3cb7b 100644 --- a/plugins/community/repos/LindenbergResearch/src/VCO.cpp +++ b/plugins/community/repos/LindenbergResearch/src/VCO.cpp @@ -8,17 +8,18 @@ struct VCO : LRModule { FREQUENCY_PARAM, OCTAVE_PARAM, FM_CV_PARAM, - SHAPE_CV_PARAM, PW_CV_PARAM, - SHAPE_PARAM, - PW_PARAM, + SAW_PARAM, + PULSE_PARAM, + SINE_PARAM, + TRI_PARAM, NUM_PARAMS }; enum InputIds { - VOCT_INPUT, + VOCT1_INPUT, FM_CV_INPUT, PW_CV_INPUT, - SHAPE_CV_INPUT, + VOCT2_INPUT, NUM_INPUTS }; enum OutputIds { @@ -26,57 +27,88 @@ struct VCO : LRModule { PULSE_OUTPUT, SINE_OUTPUT, TRI_OUTPUT, + NOISE_OUTPUT, + MIX_OUTPUT, NUM_OUTPUTS }; enum LightIds { + LFO_LIGHT, NUM_LIGHTS }; - dsp::BLITOscillator *osc = new dsp::BLITOscillator(); - LCDWidget *label1 = new LCDWidget(COLOR_CYAN, 6); + dsp::DSPBLOscillator *osc = new dsp::DSPBLOscillator(engineGetSampleRate()); + LCDWidget *lcd = new LCDWidget(nvgRGBAf(0.9, 0.2, 0.1, 1.0), 9, "%00004.3f Hz", LCDWidget::NUMERIC); + LRBigKnob *frqKnob = NULL; + VCO() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; + void onSampleRateChange() override; }; void VCO::step() { LRModule::step(); - float fm = clamp(inputs[FM_CV_INPUT].value, -10.f, 10.f) * 400.f * quadraticBipolar(params[FM_CV_PARAM].value); - - osc->updatePitch(inputs[VOCT_INPUT].value, clamp(fm, -10000.f, 10000.f), params[FREQUENCY_PARAM].value, params[OCTAVE_PARAM].value); + float fm = clamp(inputs[FM_CV_INPUT].value, -CV_BOUNDS, CV_BOUNDS) * 0.4f * quadraticBipolar(params[FM_CV_PARAM].value); + float tune = params[FREQUENCY_PARAM].value; + float pw; - float shape = quadraticBipolar(params[SHAPE_PARAM].value); - float pw = params[PW_CV_PARAM].value; - - if (osc->shape != shape) { - osc->setShape(shape); + if (inputs[PW_CV_INPUT].active) { + pw = clamp(inputs[PW_CV_INPUT].value, -CV_BOUNDS, CV_BOUNDS) * 0.6f * quadraticBipolar(params[PW_CV_PARAM].value / 2.f) + 1; + pw = clamp(pw, 0.01, 1.99); + } else { + pw = params[PW_CV_PARAM].value * 0.99f + 1; } - if (osc->pw != pw) { - osc->setPulseWidth(pw); + if (frqKnob != NULL) { + frqKnob->setIndicatorActive(inputs[FM_CV_INPUT].active); + frqKnob->setIndicatorValue((params[FREQUENCY_PARAM].value + 1) / 2 + (fm / 2)); } - osc->proccess(); + osc->setInputs(inputs[VOCT1_INPUT].value, inputs[VOCT2_INPUT].value, fm, tune, params[OCTAVE_PARAM].value); + osc->setPulseWidth(pw); + + osc->process(); - outputs[SAW_OUTPUT].value = osc->saw; + outputs[SAW_OUTPUT].value = osc->getSawWave(); + outputs[PULSE_OUTPUT].value = osc->getPulseWave(); + outputs[SINE_OUTPUT].value = osc->getSineWave(); + outputs[TRI_OUTPUT].value = osc->getTriWave(); + outputs[NOISE_OUTPUT].value = osc->getNoise(); - outputs[PULSE_OUTPUT].value = osc->pulse; - outputs[SINE_OUTPUT].value = osc->sine; - outputs[TRI_OUTPUT].value = osc->tri; + if (outputs[MIX_OUTPUT].active) { + float mix = 0.f; - if (cnt % 1200 == 0) { - label1->text = stringf("%.2f Hz", osc->getFrequency()); + mix += osc->getSawWave() * params[SAW_PARAM].value; + mix += osc->getPulseWave() * params[PULSE_PARAM].value; + mix += osc->getSineWave() * params[SINE_PARAM].value; + mix += osc->getTriWave() * params[TRI_PARAM].value; + + outputs[MIX_OUTPUT].value = mix; } + + /* for LFO mode */ + if (osc->isLFO()) + lights[LFO_LIGHT].value = (osc->getTriWave() + 2.5f) / 10.f; + else lights[LFO_LIGHT].value = 0.f; + + lcd->active = osc->isLFO(); + lcd->value = osc->getFrequency(); +} + + +void VCO::onSampleRateChange() { + Module::onSampleRateChange(); + osc->updateSampleRate(engineGetSampleRate()); } /** - * @brief Woldemar VCO + * @brief Woldemar VCO Widget */ struct VCOWidget : LRModuleWidget { VCOWidget(VCO *module); @@ -84,14 +116,19 @@ struct VCOWidget : LRModuleWidget { VCOWidget::VCOWidget(VCO *module) : LRModuleWidget(module) { - // setPanel(SVG::load(assetPlugin(plugin, "res/VCO.svg"))); - - panel = new LRPanel(20,40); + panel = new LRPanel(20, 40); panel->setBackground(SVG::load(assetPlugin(plugin, "res/VCO.svg"))); addChild(panel); box.size = panel->box.size; + // **** SETUP LCD ******** + module->lcd->box.pos = Vec(24, 239); + module->lcd->format = "%00004.3f Hz"; + addChild(module->lcd); + // **** SETUP LCD ******** + + // ***** SCREWS ********** addChild(Widget::create(Vec(15, 1))); addChild(Widget::create(Vec(box.size.x - 30, 1))); @@ -101,39 +138,42 @@ VCOWidget::VCOWidget(VCO *module) : LRModuleWidget(module) { // ***** MAIN KNOBS ****** - addParam(ParamWidget::create(Vec(83, 172.0), module, VCO::FREQUENCY_PARAM, -15.f, 15.f, 0.f)); - addParam(ParamWidget::create(Vec(85, 240), module, VCO::OCTAVE_PARAM, -3.f, 3.f, 0.f)); + module->frqKnob = LRKnob::create(Vec(126.0, 64.7), module, VCO::FREQUENCY_PARAM, -1.f, 1.f, 0.f); + + addParam(module->frqKnob); + addParam(ParamWidget::create(Vec(134.6, 171.9), module, VCO::OCTAVE_PARAM, -4.f, 3.f, 0.f)); - addParam(ParamWidget::create(Vec(118, 111.5), module, VCO::PW_PARAM, -.1f, 1.f, 1.f)); - addParam(ParamWidget::create(Vec(65, 60), module, VCO::SHAPE_CV_PARAM, -1.f, 1.f, 0.f)); - addParam(ParamWidget::create(Vec(15, 267), module, VCO::FM_CV_PARAM, -1.f, 1.f, 0.f)); - addParam(ParamWidget::create(Vec(65, 111.5), module, VCO::PW_CV_PARAM, 0.02f, 1.f, 1.f)); - addParam(ParamWidget::create(Vec(118, 59), module, VCO::SHAPE_PARAM, 1.f, 5.f, 1.f)); + addParam(ParamWidget::create(Vec(69.5, 121.5), module, VCO::FM_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(69.5, 174.8), module, VCO::PW_CV_PARAM, -1, 1, 0.f)); + addParam(ParamWidget::create(Vec(22.8, 270.1), module, VCO::SAW_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(58.3, 270.1), module, VCO::PULSE_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(93.1, 270.1), module, VCO::SINE_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(128.1, 270.1), module, VCO::TRI_PARAM, -1.f, 1.f, 0.f)); // ***** MAIN KNOBS ****** // ***** INPUTS ********** - addInput(Port::create(Vec(15, 182), Port::INPUT, module, VCO::VOCT_INPUT)); - addInput(Port::create(Vec(15, 228), Port::INPUT, module, VCO::FM_CV_INPUT)); - addInput(Port::create(Vec(15, 112), Port::INPUT, module, VCO::PW_CV_INPUT)); - addInput(Port::create(Vec(15, 60), Port::INPUT, module, VCO::SHAPE_CV_INPUT)); - - // addInput(createInput(Vec(71, 60), module, VCO::RESHAPER_CV_INPUT)); + addInput(Port::create(Vec(20.8, 67.9), Port::INPUT, module, VCO::VOCT1_INPUT)); + addInput(Port::create(Vec(68.0, 67.9), Port::INPUT, module, VCO::VOCT2_INPUT)); + addInput(Port::create(Vec(20.8, 121.5), Port::INPUT, module, VCO::FM_CV_INPUT)); + addInput(Port::create(Vec(20.8, 174.8), Port::INPUT, module, VCO::PW_CV_INPUT)); // ***** INPUTS ********** + // ***** OUTPUTS ********* - // addOutput(createOutput(Vec(20, 320), module, VCO::SAW_OUTPUT)); - addOutput(Port::create(Vec(20.8, 304.5), Port::OUTPUT, module, VCO::SAW_OUTPUT)); - addOutput(Port::create(Vec(57.2, 304.5), Port::OUTPUT, module, VCO::PULSE_OUTPUT)); - addOutput(Port::create(Vec(96.1, 304.5), Port::OUTPUT, module, VCO::SINE_OUTPUT)); - addOutput(Port::create(Vec(132, 304.5), Port::OUTPUT, module, VCO::TRI_OUTPUT)); + addOutput(Port::create(Vec(21, 305.8), Port::OUTPUT, module, VCO::SAW_OUTPUT)); + addOutput(Port::create(Vec(56.8, 305.8), Port::OUTPUT, module, VCO::PULSE_OUTPUT)); + addOutput(Port::create(Vec(91.6, 305.8), Port::OUTPUT, module, VCO::SINE_OUTPUT)); + addOutput(Port::create(Vec(126.6, 305.8), Port::OUTPUT, module, VCO::TRI_OUTPUT)); + addOutput(Port::create(Vec(162.0, 305.8), Port::OUTPUT, module, VCO::NOISE_OUTPUT)); + addOutput(Port::create(Vec(162.0, 269.1), Port::OUTPUT, module, VCO::MIX_OUTPUT)); // ***** OUTPUTS ********* - module->label1->box.pos = Vec(30,110); - addChild(module->label1); + // ***** LIGHTS ********** + addChild(ModuleLightWidget::create(Vec(181.8, 210), module, VCO::LFO_LIGHT)); } } // namespace rack_plugin_LindenbergResearch diff --git a/plugins/community/repos/LindenbergResearch/src/Westcoast.cpp b/plugins/community/repos/LindenbergResearch/src/Westcoast.cpp new file mode 100644 index 00000000..f7edfb60 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/Westcoast.cpp @@ -0,0 +1,154 @@ +#include "dsp/SergeWavefolder.hpp" +#include "dsp/Lockhart.hpp" +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct Westcoast : LRModule { + + enum ParamIds { + GAIN_PARAM, + CV_GAIN_PARAM, + CV_BIAS_PARAM, + BIAS_PARAM, + TYPE_PARAM, + DCBLOCK_PARAM, + NUM_PARAMS + }; + + enum InputIds { + SHAPER_INPUT, + CV_GAIN_INPUT, + CV_BIAS_INPUT, + NUM_INPUTS + }; + + enum OutputIds { + SHAPER_OUTPUT, + SG_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds { + NUM_LIGHTS + }; + + + Westcoast() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + + + dsp::LockhartWavefolder *hs = new dsp::LockhartWavefolder(engineGetSampleRate()); + dsp::SergeWavefolder *sg = new dsp::SergeWavefolder(engineGetSampleRate()); + + void step() override; + void onSampleRateChange() override; +}; + + +void Westcoast::step() { + hs->setGain((params[GAIN_PARAM].value)); + hs->setBias(params[BIAS_PARAM].value); + hs->setIn(inputs[SHAPER_INPUT].value); + + sg->setGain((params[GAIN_PARAM].value)); + sg->setBias(params[BIAS_PARAM].value); + sg->setIn(inputs[SHAPER_INPUT].value); + + + if (params[DCBLOCK_PARAM].value == 1) + hs->setBlockDC(true); + else + hs->setBlockDC(false); + + hs->process(); + sg->process(); + + // [bsp] let's hear what this sounds like :-) + // (note) too much gain makes all following modules go quiet permanently + float hsOut = hs->getOut(); + float sgOut = sg->getOut(); + + switch((int)params[TYPE_PARAM].value) { + default: + case -2: + if(params[GAIN_PARAM].value < 9.8f) + outputs[SHAPER_OUTPUT].value = hsOut; + else + outputs[SHAPER_OUTPUT].value = 0.0f; + break; + + case -1: + if(params[GAIN_PARAM].value < 15.8f) + outputs[SHAPER_OUTPUT].value = sgOut; + else + outputs[SHAPER_OUTPUT].value = 0.0f; + break; + } + + outputs[SG_OUTPUT].value = sgOut; +} + + +void Westcoast::onSampleRateChange() { + Module::onSampleRateChange(); + hs->setSamplerate(engineGetSampleRate()); + sg->setSamplerate(engineGetSampleRate()); +} + + +struct WestcoastWidget : LRModuleWidget { + WestcoastWidget(Westcoast *module); +}; + + +WestcoastWidget::WestcoastWidget(Westcoast *module) : LRModuleWidget(module) { + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/Westcoast.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + // ***** MAIN KNOBS ****** + addParam(LRKnob::create(Vec(128.7, 63.0), module, Westcoast::GAIN_PARAM, 0.25, 20.f, 1.f)); + addParam(LRKnob::create(Vec(136.4, 153.3), module, Westcoast::BIAS_PARAM, -0.5f, 0.5f, 0.f)); + addParam(LRKnob::create(Vec(85, 274.3), module, Westcoast::TYPE_PARAM, -3, 3, 0)); + + addParam(LRKnob::create(Vec(83.4, 101.00), module, Westcoast::CV_GAIN_PARAM, -1.f, 1.f, 0.f)); + addParam(LRKnob::create(Vec(83.4, 183.0), module, Westcoast::CV_BIAS_PARAM, -1.f, 1.f, 0.f)); + // ***** MAIN KNOBS ****** + + // ***** CV INPUTS ******* + addInput(Port::create(Vec(32.4, 99.0), Port::INPUT, module, Westcoast::CV_GAIN_INPUT)); + addInput(Port::create(Vec(32.4, 179.8), Port::INPUT, module, Westcoast::CV_BIAS_INPUT)); + // ***** CV INPUTS ******* + + // ***** INPUTS ********** + addInput(Port::create(Vec(22.4, 324.6), Port::INPUT, module, Westcoast::SHAPER_INPUT)); + // ***** INPUTS ********** + + // ***** OUTPUTS ********* + addOutput(Port::create(Vec(159.4, 324.6), Port::OUTPUT, module, Westcoast::SHAPER_OUTPUT)); + // addOutput(Port::create(Vec(159.4, 300.7), Port::OUTPUT, module, Westcoast::SG_OUTPUT)); + // ***** OUTPUTS ********* + + // ***** SWITCH ********* + // addParam(ParamWidget::create(Vec(119, 331), module, Westcoast::DCBLOCK_PARAM, 0.0, 1.0, 1.0)); + // ***** SWITCH ********* +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, Westcoast) { + Model *modelWestcoast = Model::create("Lindenberg Research", "Westcoast VCS", + "Westcoast Complex Shaper", WAVESHAPER_TAG); + return modelWestcoast; +} diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/DSPEffect.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/DSPEffect.hpp index 6bc283a1..c90bd309 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/DSPEffect.hpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/DSPEffect.hpp @@ -1,12 +1,45 @@ #pragma once -namespace rack { +#include +#include "dsp/ringbuffer.hpp" + +#define RS_BUFFER_SIZE 512 + + +namespace dsp { /** * @brief Base class for all signal processors */ struct DSPEffect { + protected: + + + public: + + float sr = 44100.0; + + + explicit DSPEffect(float sr) : sr(sr) { + init(); + } + + + float getSamplerate() const { + return sr; + } + + + void setSamplerate(float sr) { + DSPEffect::sr = sr; + invalidate(); + } + + + virtual void init() {}; + + /** * @brief Method for mark parameters as invalidate to trigger recalculation */ @@ -20,4 +53,189 @@ namespace rack { virtual void process() {}; }; -} \ No newline at end of file + + /** The normalized sinc function. https://en.wikipedia.org/wiki/Sinc_function */ + inline double sinc(double x) { + if (x == 0.) + return 1.; + x *= M_PI; + return sin(x) / x; + } + + + /** Computes the impulse response of a boxcar lowpass filter */ + inline void boxcarLowpassIR(double *out, int len, double cutoff = 0.5) { + for (int i = 0; i < len; i++) { + double t = i - (len - 1) / 2.; + out[i] = 2 * cutoff * sinc(2 * cutoff * t); + } + } + + + inline void blackmanHarrisWindow(double *x, int len) { + // Constants from https://en.wikipedia.org/wiki/Window_function#Blackman%E2%80%93Harris_window + const double a0 = 0.35875; + const double a1 = 0.48829; + const double a2 = 0.14128; + const double a3 = 0.01168; + double factor = 2 * M_PI / (len - 1); + for (int i = 0; i < len; i++) { + x[i] *= +a0 + - a1 * cos(1 * factor * i) + + a2 * cos(2 * factor * i) + - a3 * cos(3 * factor * i); + } + } + + + struct Decimator { + double inBuffer[RS_BUFFER_SIZE]; + double kernel[RS_BUFFER_SIZE]; + int inIndex; + int oversample, quality; + double cutoff = 0.9; + + + Decimator(int oversample, int quality) { + Decimator::oversample = oversample; + Decimator::quality = quality; + + boxcarLowpassIR(kernel, oversample * quality, cutoff * 0.5 / oversample); + blackmanHarrisWindow(kernel, oversample * quality); + reset(); + } + + + void reset() { + inIndex = 0; + memset(inBuffer, 0, sizeof(inBuffer)); + } + + + /** `in` must be length OVERSAMPLE */ + float process(double *in) { + // Copy input to buffer + memcpy(&inBuffer[inIndex], in, oversample * sizeof(double)); + // Advance index + inIndex += oversample; + inIndex %= oversample * quality; + // Perform naive convolution + double out = 0.; + 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; + } + }; + + + struct Upsampler { + double inBuffer[RS_BUFFER_SIZE]; + double kernel[RS_BUFFER_SIZE]; + int inIndex; + int oversample, quality; + double cutoff = 0.9; + + Upsampler(int oversample, int quality) { + Upsampler::oversample = oversample; + Upsampler::quality = quality; + + boxcarLowpassIR(kernel, oversample * quality, cutoff * 0.5 / oversample); + blackmanHarrisWindow(kernel, oversample * quality); + reset(); + } + + + void reset() { + inIndex = 0; + memset(inBuffer, 0, sizeof(inBuffer)); + } + + + /** `out` must be length OVERSAMPLE */ + void process(double in, double *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.0; + 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; + } + } + }; + + + /** + * @brief NEW oversampling class + */ + template + struct Resampler { + double up[CHANNELS][RS_BUFFER_SIZE] = {}; + double data[CHANNELS][RS_BUFFER_SIZE] = {}; + + Decimator *decimator[CHANNELS]; + Upsampler *interpolator[CHANNELS]; + + int oversample; + + + /** + * @brief Constructor + * @param factor Oversampling factor + */ + Resampler(int oversample) { + Resampler::oversample = oversample; + + for (int i = 0; i < CHANNELS; i++) { + decimator[i] = new Decimator(oversample, 1); + interpolator[i] = new Upsampler(oversample, 1); + } + } + + + int getFactor() { + return oversample; + } + + + /** + * @brief Create up-sampled data out of two basic values + */ + void doUpsample(int channel, double in) { + interpolator[channel]->process(in, up[channel]); + } + + + /** + * @brief Downsampled data from a given channel + * @param channel Channel to proccess + * @return Downsampled point + */ + double getDownsampled(int channel) { + return decimator[channel]->process(data[channel]); + } + + + /** + * @brief Upsampled data from a given channel + * @param channel Channel to retrieve + * @return Pointer to the upsampled data + */ + double *getUpsampled(int channel) { + return up[channel]; + } + }; + +} diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.cpp index 3a56c6ee..21be09f2 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.cpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.cpp @@ -1,5 +1,7 @@ #include "DSPMath.hpp" +namespace dsp { + /** * @brief Clip signal at bottom by value * @param in Sample input @@ -87,8 +89,8 @@ float Integrator::add(float x, float Fn) { * @param x Input sample * @return Filtered sample */ -float DCBlocker::filter(float x) { - float y = x - xm1 + R * ym1; +double DCBlocker::filter(double x) { + double y = x - xm1 + r * ym1; xm1 = x; ym1 = y; @@ -96,6 +98,9 @@ float DCBlocker::filter(float x) { } +DCBlocker::DCBlocker(double r) : r(r) {} + + /** * @brief Filter function for simple 6dB lowpass filter * @param x Input sample @@ -182,4 +187,6 @@ double overdrive(double input) { const double x = input * 0.686306; const double a = 1 + exp(sqrt(fabs(x)) * -0.75); return (exp(x) - exp(-x * a)) / (exp(x) + exp(-x)); -} \ No newline at end of file +} + +} // namespace rack_plugin_LindenbergResearch diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.hpp index ce6d8f6e..4703e423 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.hpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/DSPMath.hpp @@ -3,12 +3,16 @@ #include #include #include "rack.hpp" -#include "dsp/decimator.hpp" +#include "dsp/resampler.hpp" +#include "DSPEffect.hpp" +#define LAMBERT_W_THRESHOLD 10e-10 using namespace rack; const static float TWOPI = (float) M_PI * 2; +namespace dsp { + /** * @brief Basic leaky integrator @@ -31,15 +35,17 @@ struct Integrator { * @brief Filter out DC offset / 1-Pole HP Filter */ struct DCBlocker { - const float R = 0.999; - float xm1 = 0.f, ym1 = 0.f; + double r = 0.999; + double xm1 = 0.f, ym1 = 0.f; + + DCBlocker(double r); /** * @brief Filter signal * @param x Input sample * @return Filtered output */ - float filter(float x); + double filter(double x); }; @@ -87,9 +93,7 @@ public: */ struct Noise { - Noise() { - - } + Noise() {} float nextFloat(float gain) { @@ -102,6 +106,7 @@ struct Noise { /** * @brief Simple oversampling class + * @deprecated Use resampler instead */ template struct OverSampler { @@ -113,7 +118,7 @@ struct OverSampler { Vector y[CHANNELS] = {}; float up[CHANNELS][OVERSAMPLE] = {}; float data[CHANNELS][OVERSAMPLE] = {}; - Decimator decimator[CHANNELS]; + rack::Decimator decimator[CHANNELS]; int factor = OVERSAMPLE; @@ -311,4 +316,137 @@ inline float cubicShape(float x) { */ inline float atanShaper(float x) { return x / (1.f + (0.28f * x * x)); -} \ No newline at end of file +} + + +/** Generate chebyshev polynoms + * @brief + * @param x Input sample + * @param A ? + * @param order Polynom order + * @return + */ +inline float chebyshev(float x, float A[], int order) { + // To = 1 + // T1 = x + // Tn = 2.x.Tn-1 - Tn-2 + // out = sum(Ai*Ti(x)) , i C {1,..,order} + float Tn_2 = 1.0f; + float Tn_1 = x; + float Tn; + float out = A[0] * Tn_1; + + for (int n = 2; n <= order; n++) { + Tn = 2.0f * x * Tn_1 - Tn_2; + out += A[n - 1] * Tn; + Tn_2 = Tn_1; + Tn_1 = Tn; + } + return out; +} + + +/** + * @brief Signum function + * @param x + * @return + */ +inline double sign(double x) { + if (x > 0) return 1; + if (x < 0) return -1; + return 0; +} + + +/** + * @brief Lambert-W function using Halley's method + * see: http://smc2017.aalto.fi/media/materials/proceedings/SMC17_p336.pdf + * @param x + * @param ln1 + * @return + */ +inline double lambert_W(double x, double ln1) { + double w; + double p, r, s, err; + double expw; + + // if (!isnan(ln1) || !isfinite(ln1)) ln1 = 0.; + + // initial guess, previous value + w = ln1; + +// debug("x: %f ln1: %f", x, ln1); + + // Haley's method (Sec. 4.2 of the paper) + for (int i = 0; i < 100; i++) { + expw = pow(M_E, w); + + p = w * expw - x; + r = (w + 1.) * expw; + s = (w + 2.) / (2. * (w + 1.)); + err = (p / (r - (p * s))); + + if (abs(err) < 10e-12) { + break; + } + + w = w - err; + } + + return w; +} + + +/** + * @brief + * + * This function evaluates the upper branch of the Lambert-W function for + * real input x. + * + * Function written by F. Esqueda 2/10/17 based on implementation presented + * by Darko Veberic - "Lambert W Function for Applications in Physics" + * Available at https://arxiv.org/pdf/1209.0735.pdf + * + * @param x input + * @return W(x) + */ +inline double lambert_W_Fritsch(double x) { + double num, den; + double w, w1, a, b, ia; + double z, q, e; + + if (x < 0.14546954290661823) { + num = 1 + 5.931375839364438 * x + 11.39220550532913 * x * x + 7.33888339911111 * x * x * x + 0.653449016991959 * x * x * x * x; + den = 1 + 6.931373689597704 * x + 16.82349461388016 * x * x + 16.43072324143226 * x * x * x + 5.115235195211697 * x * x * x * x; + + w = x * num / den; + } else if (x < 8.706658967856612) { + num = 1 + 2.4450530707265568 * x + 1.3436642259582265 * x * x + 0.14844005539759195 * x * x * x + + 0.0008047501729130 * x * x * x * x; + den = 1 + 3.4447089864860025 * x + 3.2924898573719523 * x * x + 0.9164600188031222 * x * x * x + + 0.05306864044833221 * x * x * x * x; + + w = x * num / den; + } else { + a = log(x); + b = log(a); + ia = 1. / a; + w = a - b + (b * ia) * 0.5 * b * (b - 2.) * (ia * ia) + (1. / 6.) * (2. * b * b - 9. * b + 6.) * (ia * ia * ia); + } + + + for (int i = 0; i < 20; i++) { + w1 = w + 1.; + z = log(x) - log(w) - w; + q = 2. * w1 * (w1 + (2. / 3.) * z); + e = (z / w1) * ((q - z) / (q - 2. * z)); + + if (abs(e) < 10e-12) { + break; + } + } + + return w; +} + +} // namespace dsp diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/DSPSystem.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/DSPSystem.hpp index c08b1b29..7bcd902c 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/DSPSystem.hpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/DSPSystem.hpp @@ -50,8 +50,8 @@ namespace dsp { protected: #ifdef _MSC_VER - DSPPort input[NUM_IN]; - DSPPort output[NUM_OUT]; + DSPPort input[NUM_IN + 1]; + DSPPort output[NUM_OUT + 1]; DSPParam param[NUM_PARAM + 1]; #else DSPPort input[NUM_IN] = {}; @@ -78,7 +78,7 @@ namespace dsp { explicit DSPSystem(float sr) : sr(sr) {} - /** + virtual /** * @brief Update sample rate on change * @param sr */ @@ -141,16 +141,6 @@ namespace dsp { } - /** - * @brief Get current output value - * @param id Output ID - * @return - */ - float getOut(int id) { - return output[id].value; - } - - /** * @brief Method for mark parameters as invalidate to trigger recalculation */ diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/HQTrig.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/HQTrig.hpp new file mode 100644 index 00000000..970d4ddf --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/HQTrig.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "DSPEffect.hpp" + +namespace dsp { + + + struct HQTanh : DSPEffect { + + /* oversampling channel */ + static const int STD_CHANNEL = 0; + + int factor; + double in, out, xn1, fn1; + Resampler<1> *rs; + + + HQTanh(float sr, int factor) : DSPEffect(sr) { + HQTanh::factor = factor; + + rs = new Resampler<1>(factor); + } + + + /** + * @brief Returns the actual sample-rate which is used by oversampled computation + * @return + */ + float getOversampledRate() { + return sr * rs->getFactor(); + } + + + void init() override { + DSPEffect::init(); + } + + + void invalidate() override { + DSPEffect::invalidate(); + } + + + /** + * @brief Compute next value + * @param x + * @return + */ + double next(double x) { + in = x; + process(); + + return out; + } + + + /** + * @brief Generate an anti-aliased tanh + * @param x + * @return + */ + double computeAA(double x) { + double fn = log(cosh(x)); + double xn, out; + + if (abs(x - xn1) < 10e-10) { + xn = (x + xn1) / 2.; + out = tanh(xn); + } else { + out = (fn - fn1) / (x - xn1); + } + + fn1 = fn; + xn1 = x; + + return out; + } + + + /** + * @brief Compute tanh + */ + void process() override { + rs->doUpsample(STD_CHANNEL, in); + + for (int i = 0; i < rs->getFactor(); i++) { + double x = rs->getUpsampled(STD_CHANNEL)[i]; + rs->data[STD_CHANNEL][i] = computeAA(x); + } + + out = rs->getDownsampled(STD_CHANNEL); + } + + }; + + +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.cpp index be4e3321..2d8b44fd 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.cpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.cpp @@ -1,12 +1,7 @@ -#include "LadderFilter.hpp" - -using namespace rack; +#include "LadderFilter.hpp" -/** - * @brief Constructor - */ -LadderFilter::LadderFilter() {} +using namespace dsp; /** @@ -26,11 +21,10 @@ void LadderFilter::invalidate() { * @return */ void LadderFilter::process() { - os.next(LOWPASS, in); - os.doUpsample(LOWPASS); + rs->doUpsample(LOWPASS, in); - for (int i = 0; i < os.factor; i++) { - float x = os.up[LOWPASS][i]; + for (int i = 0; i < rs->getFactor(); i++) { + float x = rs->getUpsampled(LOWPASS)[i]; // non linear feedback with nice saturation x -= fastatan(bx * q); @@ -65,10 +59,10 @@ void LadderFilter::process() { // overdrive with fast atan, which folds back the waves at high input and creates a noisy bright sound - os.data[LOWPASS][i] = fastatan(y); + rs->data[LOWPASS][i] = fastatan(y); } - lpOut = os.getDownsampled(LOWPASS) * (INPUT_GAIN / (drive * 20 + 1) * (quadraticBipolar(drive * 3) + 1)); + lpOut = rs->getDownsampled(LOWPASS) * (INPUT_GAIN / (drive * 20 + 1) * (quadraticBipolar(drive * 3) + 1)); } @@ -90,7 +84,7 @@ void LadderFilter::setFrequency(float frequency) { LadderFilter::frequency = frequency; // translate frequency to logarithmic scale freqHz = 20.f * powf(1000.f, frequency); - freqExp = clamp(freqHz * (1.f / (engineGetSampleRate() * OVERSAMPLE / 2.f)), 0.f, 1.f); + freqExp = clamp(freqHz * (1.f / (sr * OVERSAMPLE / 2.f)), 0.f, 1.f); updateResExp(); invalidate(); @@ -215,3 +209,8 @@ float LadderFilter::getLightValue() const { void LadderFilter::setLightValue(float lightValue) { LadderFilter::lightValue = lightValue; } + + +LadderFilter::LadderFilter(float sr) : DSPEffect(sr) { + rs = new Resampler<1>(OVERSAMPLE); +} diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.hpp index a6ceb723..0dc0a408 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.hpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/LadderFilter.hpp @@ -5,13 +5,13 @@ #include "engine.hpp" #include "DSPMath.hpp" -namespace rack { +namespace dsp { struct LadderFilter : DSPEffect { static const int OVERSAMPLE = 8; // factor of internal oversampling static constexpr float NOISE_GAIN = 10e-10f; // internal noise gain used for self-oscillation - static constexpr float INPUT_GAIN = 20.f; // input level + static constexpr float INPUT_GAIN = 20.f; // input level enum FXChannel { LOWPASS @@ -25,16 +25,34 @@ namespace rack { float in, lpOut; float lightValue; - OverSampler os; + Resampler<1> *rs; Noise noise; void updateResExp(); public: - LadderFilter(); - void invalidate() override; + LadderFilter(float sr); + + + void init() override { + f = 0; + p = 0; + q = 0; + b0 = 0; + b1 = 0; + b2 = 0; + b3 = 0; + b4 = 0; + b5 = 0; + bx = 0; + t1 = 0; + t2 = 0; + lightValue = 0.0f; + } + + void invalidate() override; void process() override; float getFrequency() const; diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/Lockhart.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/Lockhart.cpp new file mode 100644 index 00000000..fdc00018 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/Lockhart.cpp @@ -0,0 +1,94 @@ +#include "Lockhart.hpp" + +using namespace dsp; + + +double LockhartWFStage::compute(double x) { + double out; + double l, u, ln, fn, xn; + + // Compute Antiderivative + l = sign(x); + u = d * pow(M_E, l * b * x); + ln = lambert_W_Fritsch(u); + fn = (0.5 * LOCKHART_VT / b) * (ln * (ln + 2.)) - 0.5 * a * x * x; + + // Check for ill-conditioning + if (abs(x - xn1) < LOCKHART_THRESHOLD) { + // Compute Averaged Wavefolder Output + xn = 0.5 * (x + xn1); + u = d * pow(M_E, l * b * xn); + ln = lambert_W_Fritsch(u); + out = l * LOCKHART_VT * ln - a * xn; + + } else { + // Apply AA Form + out = (fn - fn1) / (x - xn1); + } + + fn1 = fn; + xn1 = x; + + return out; +} + + +LockhartWFStage::LockhartWFStage() { + fn1 = 0; + xn1 = 0; + + a = 2. * LOCKHART_RL / LOCKHART_R; + b = (LOCKHART_R + 2. * LOCKHART_RL) / (LOCKHART_VT * LOCKHART_R); + d = (LOCKHART_RL * LOCKHART_Is) / LOCKHART_VT; +} + + +void LockhartWavefolder::init() { + dsp::WaveShaper::rs = new dsp::Resampler<1>(1); +} + + +void LockhartWavefolder::invalidate() { +} + + +void LockhartWavefolder::process() { + WaveShaper::process(); +} + + +double LockhartWavefolder::compute(double x) { + double out; + double in = (x / 8. + bias) * gain; + + in *= 0.5; + + in = lh1.compute(in); + in = lh2.compute(in); + in = lh3.compute(in); + in = lh4.compute(in); + + in *= 2.f; + + if (blockDC) in = dc->filter(in); + + out = tanh1->next(in / 2.); + + return out * 20 * 2; +} + + +LockhartWavefolder::LockhartWavefolder(float sr) : WaveShaper(sr) { + init(); + tanh1 = new HQTanh(sr, 4); +} + + +bool LockhartWavefolder::isBlockDC() const { + return blockDC; +} + + +void LockhartWavefolder::setBlockDC(bool blockDC) { + LockhartWavefolder::blockDC = blockDC; +} diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/Lockhart.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/Lockhart.hpp new file mode 100644 index 00000000..9440c539 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/Lockhart.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "WaveShaper.hpp" +#include "HQTrig.hpp" + +// constants for Lockhart waveshaper model +#define LOCKHART_RL 7.5e3 +#define LOCKHART_R 15e3 +#define LOCKHART_VT 25.864e-3 +#define LOCKHART_Is 10e-16 +#define LOCKHART_THRESHOLD 10e-10 + +#define DCBLOCK_ALPHA 0.9999 +namespace dsp { + + /** + * @brief Represents one stage of the Lockhart Wavefolder + */ + struct LockhartWFStage { + private: + + double fn1, xn1; + double a, b, d; + + public: + + LockhartWFStage(); + + double compute(double x); + }; + + + /** + * Lockhart Wavefolder class + */ + struct LockhartWavefolder : WaveShaper { + + private: + LockhartWFStage lh1, lh2, lh3, lh4; + DCBlocker *dc = new DCBlocker(DCBLOCK_ALPHA); + HQTanh *tanh1; + bool blockDC = false; + + + public: + explicit LockhartWavefolder(float sr); + + void init() override; + void invalidate() override; + void process() override; + double compute(double x) override; + + + bool isBlockDC() const; + void setBlockDC(bool blockDC); + + }; + +} diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.cpp index aa5ca67f..f29f143a 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.cpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.cpp @@ -24,16 +24,16 @@ void MS20zdf::invalidate() { * @brief Proccess one sample of filter */ void MS20zdf::process() { - os.next(IN, input[IN].value); - os.doUpsample(IN); + //rs.next(IN, input[IN].value); + rs->doUpsample(IN, input[IN].value); float s1, s2; float gain = quadraticBipolar(param[DRIVE].value) * DRIVE_GAIN + 1.f; float type = param[TYPE].value; float x = 0; - for (int i = 0; i < os.factor; i++) { - x = os.up[IN][i]; + for (int i = 0; i < rs->getFactor(); i++) { + x = rs->getUpsampled(IN)[i]; zdf1.set(x - ky, g); s1 = zdf1.s; @@ -43,17 +43,17 @@ void MS20zdf::process() { y = 1.f / (g2 * k - g * k + 1.f) * (g2 * x + g * s1 + s2); - ky = k * atanf(y / 70.f) * 70.f; + ky = k * fastatan(y / 70.f) * 70.f; if (type > 0) { - os.data[IN][i] = atanShaper(gain * y / 10.f) * 10.f; + rs->data[IN][i] = atanShaper(gain * y / 10.f) * 10.f; } else { - os.data[IN][i] = atanf(gain * y / 10.f) * 10.f; + rs->data[IN][i] = fastatan(gain * y / 10.f) * 10.f; } } - float out = os.getDownsampled(IN); + float out = rs->getDownsampled(IN); output[OUT].value = out; } @@ -63,5 +63,7 @@ void MS20zdf::process() { * @brief Inherit constructor * @param sr sample rate */ -MS20zdf::MS20zdf(float sr) : DSPSystem(sr) {} +MS20zdf::MS20zdf(float sr) : DSPSystem(sr) { + rs = new Resampler<1>(OVERSAMPLE); +} diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.hpp index 319298f1..e2cfef48 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.hpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/MS20zdf.hpp @@ -70,7 +70,7 @@ namespace dsp { float freqHz = 0; MS20ZDF zdf1, zdf2; - OverSampler os; + Resampler<1> *rs; public: explicit MS20zdf(float sr); diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.cpp index 05489d15..43a719f4 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.cpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.cpp @@ -5,163 +5,35 @@ using namespace dsp; /** - * @brief Set oscillator state back + * @brief Construct a Oscillator + * @param sr SampleRate */ -void BLITOscillator::reset() { - freq = 0.f; - pw = 1.f; - phase = 0.f; - incr = 0.f; - shape = 1.f; - detune = noise.nextFloat(0.32); - drift = 0.f; - warmup = 0.f; - - saw = 0.f; - ramp = 0.f; - pulse = 0.f; - sine = 0.f; - tri = 0.f; - - shape = 1.f; - n = 0; - - _cv = 0.f; - _oct = 0.f; - - _base = 1.f; - _coeff = 1.f; - _tune = 0.f; - _biqufm = 0.f; - - /* force recalculation of variables */ - setFrequency(NOTE_C4); -} - - -/** - * @brief Default constructor - */ -BLITOscillator::BLITOscillator() { +DSPBLOscillator::DSPBLOscillator(float sr) : DSPSystem(sr) { + lfo = new DSPSineLFO(sr); reset(); } /** - * @brief Default destructor + * @brief Trigger recalculation of internal state */ -BLITOscillator::~BLITOscillator() {} - - -/** - * @brief Get current frequency - * @return - */ -float BLITOscillator::getFrequency() const { - return freq; +void DSPBLOscillator::invalidate() { + incr = getPhaseIncrement(param[FREQUENCY].value); + n = (int) floorf(BLIT_HARMONICS / param[FREQUENCY].value); } /** - * @brief Set frequency - * @param freq + * @brief Process one sample */ -void BLITOscillator::setFrequency(float freq) { - /* just set if frequency differs from old value */ - if (BLITOscillator::freq != freq) { - BLITOscillator::freq = freq; - - /* force recalculation of variables */ - invalidate(); - } -} - - -/** - * @brief Get current pulse-width - * @return - */ -float BLITOscillator::getPulseWidth() const { - return pw; -} - - -/** - * @brief Set current pulse-width - * @param pw - */ -void dsp::BLITOscillator::setPulseWidth(float pw) { - if (pw < 0.1f) { - BLITOscillator::pw = 0.1f; - return; - } - - if (pw > 1.f) { - BLITOscillator::pw = 1.f; - return; - } - - BLITOscillator::pw = pw; - - /* force recalculation of variables */ - invalidate(); -} - - -/** - * @brief Ramp waveform current - * @return - */ -float BLITOscillator::getRampWave() const { - return ramp; -} - - -/** - * @brief Saw waveform current - * @return - */ -float BLITOscillator::getSawWave() const { - return saw; -} - - -/** - * @brief Pulse waveform current - * @return - */ -float BLITOscillator::getPulseWave() const { - return pulse; -} - +void DSPBLOscillator::process() { + updatePitch(); -/** - * @brief SawTri waveform current - * @return - */ -float BLITOscillator::getSawTriWave() const { - return sine; -} - - -/** - * @brief Triangle waveform current - * @return - */ -float BLITOscillator::getTriangleWave() const { - return tri; -} - - -/** - * @brief Process band-limited oscillator - */ -void dsp::BLITOscillator::proccess() { /* phase locked loop */ phase = wrapTWOPI(incr + phase); /* pulse width */ - float w = pw * (float) M_PI; + float w = param[PULSEWIDTH].value * (float) M_PI; /* get impulse train */ float blit1 = BLIT(n, phase); @@ -178,78 +50,146 @@ void dsp::BLITOscillator::proccess() { float beta = int3.add(delta, incr) * 1.8f; /* compute RAMP waveform */ - ramp = int1.value * 0.5f; + float ramp = int1.value * 0.5f; + /* compute pulse waveform */ - pulse = delta; + output[PULSE].value = delta * 2.f; + /* compute SAW waveform */ - saw = ramp * -1; + output[SAW].value = ramp * -5; /* compute triangle */ - tri = (float) M_PI / w * beta; - /* compute sine */ - sine = fastSin(phase); - - //TODO: warmup oscillator with: y(x)=1-e^-(x/n) and slope - - saw *= 5; + output[TRI].value = beta * 5.f; + /* compute sine */ + output[SINE].value = fastSin(phase) * 5.f; -/* sine = shape2(shape, sine); - tri = shape2(shape, tri); - pulse = shape2(shape, pulse);*/ + /* compute noise: act as S&H in LFO mode, update next random only every cycle */ + if (!lfoMode || phase - incr <= -M_PI) + output[NOISE].value = noise.nextFloat(10.f) - 5.f; } -/** - * @brief ReCompute basic parameter - */ -void BLITOscillator::invalidate() { - incr = getPhaseIncrement(freq); - n = (int) floorf(BLIT_HARMONICS / freq); -} +void DSPBLOscillator::reset() { + param[FREQUENCY].value = 0.f; + param[PULSEWIDTH].value = 1.f; + phase = 0.f; + incr = 0.f; + detune = noise.nextFloat(DETUNE_AMOUNT); + drift = 0.f; + warmup = 0.f; + warmupTau = sr * 1.5f; + tick = round(sr * 0.7f); + lfo->reset(); + lfo->setPhase(noise.nextFloat(TWOPI)); + lfo->setFrequency(DRIFT_FREQ + noise.nextFloat(DRIFT_VARIANZ)); -/** - * @brief Get saturation - * @return - */ -float BLITOscillator::getSaturate() const { - return shape; -} + n = 0; + + _cv = 0.f; + _oct = 0.f; + _base = 1.f; + _coeff = 1.f; + _tune = 0.f; + _biqufm = 0.f; -/** - * @brief Set saturation - * @param saturate - */ -void BLITOscillator::setShape(float saturate) { - BLITOscillator::shape = saturate; + /* force recalculation of variables */ + setParam(FREQUENCY, NOTE_C4 + detune, true); } /** - * @brief Translate from control voltage to frequency - * @param cv ControlVoltage from MIDI2CV - * @param fm Frequency modulation - * @param oct Octave + * @brief Constructs the correct pitch in Hz out of all parameters + * @param cv V/OCT CVs + * @param fm Frequency modulation CVs + * @param tune Tune knob value + * @param oct Octave knob value */ -void dsp::BLITOscillator::updatePitch(float cv, float fm, float tune, float oct) { +void DSPBLOscillator::updatePitch() { // CV is at 1V/OCt, C0 = 16.3516Hz, C4 = 261.626Hz // 10.3V = 20614.33hz + // give it 30s to warmup + if (tick++ < sr * 30) { + if (tick < sr * 1.8f) { + tick += 6; // accelerated detune + warmup = 1 - powf((float) M_E, -(tick / warmupTau)); + } else + warmup = 1 - powf((float) M_E, -(tick / warmupTau)); + } + + lfo->process(); + drift = lfo->getSine() * DRIFT_AMOUNT; + + float cv = input[VOCT1].value + input[VOCT2].value; + float fm; + float tune; + float oct; + + if (lfoMode) { + /* convert knob value to unipolar */ + fm = input[FM_CV].value; + tune = quadraticBipolar((input[TUNE].value + 1) / 2); + tune *= LFO_SCALE; + fm *= LFO_SCALE; + oct = -8; + } else { + fm = input[FM_CV].value * TUNE_SCALE; + tune = input[TUNE].value * TUNE_SCALE; + oct = input[OCTAVE].value; + } + /* optimize the usage of expensive exp function and other computations */ float coeff = (_oct != oct) ? powf(2.f, oct) : _coeff; float base = (_cv != cv) ? powf(2.f, cv) : _base; - float biqufm = (_tune != tune) ? quadraticBipolar(tune) : _biqufm; + float biqufm = (_tune != tune + fm) ? quadraticBipolar(tune + fm) : _biqufm; - setFrequency((NOTE_C4 + biqufm) * base * coeff + detune + fm); + if (lfoMode) + setFrequency(tune + fm); + else + setFrequency((NOTE_C4 + drift + detune + biqufm) * base * coeff * warmup); /* save states */ _cv = cv; _oct = oct; _base = base; _coeff = coeff; - _tune = tune; + _tune = tune + fm; _biqufm = biqufm; -} \ No newline at end of file +} + + +void DSPBLOscillator::setFrequency(float frq) { + setParam(FREQUENCY, clamp(frq, 0.00001f, 18000.f), true); +} + + +void DSPBLOscillator::setPulseWidth(float width) { + setParam(PULSEWIDTH, width, true); +} + + +void DSPBLOscillator::setInputs(float voct1, float voct2, float fm, float tune, float oct) { + setInput(VOCT1, voct1); + setInput(VOCT2, voct2); + setInput(FM_CV, fm); + setInput(TUNE, tune); + setInput(OCTAVE, oct); + + /* check for lowest value on toggle knob */ + lfoMode = oct == LFO_MODE; +} + + +/** + * @brief Pass changed samplerate to LFO + * @param sr + */ +void DSPBLOscillator::updateSampleRate(float sr) { + DSPSystem::updateSampleRate(sr); + lfo->updateSampleRate(sr); +} + diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.hpp index 735aa4a2..eddcb323 100644 --- a/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.hpp +++ b/plugins/community/repos/LindenbergResearch/src/dsp/Oscillator.hpp @@ -3,11 +3,79 @@ #include "DSPMath.hpp" #include "DSPSystem.hpp" -#define BLIT_HARMONICS 21000.f -#define NOTE_C4 261.626f +#define LFO_SCALE 25.f +#define TUNE_SCALE 17.3f +#define LFO_MODE -4 +#define CV_BOUNDS 10.f +#define DETUNE_AMOUNT 2.0f +#define DRIFT_AMOUNT 1.4f +#define DRIFT_FREQ 0.005f +#define DRIFT_VARIANZ 0.004 namespace dsp { + /** + * @brief Simple and fast LFO + */ + struct DSPSineLFO : DSPSystem<0, 1, 1> { + enum Outputs { + SINE + }; + + enum Params { + FREQ + }; + + private: + float frac, phase; + + public: + + DSPSineLFO(float sr) : DSPSystem(sr) { + setFrequency(1.f); + } + + void setFrequency(float freq) { + setParam(FREQ, freq, true); + } + + + float getFrequency() { + return getParam(FREQ); + } + + + void invalidate() override { + frac = TWOPI / sr * param[FREQ].value; + } + + + void process() override { + phase = wrapTWOPI(phase + frac); + output[SINE].value = fastSin(phase); + } + + + float getSine() { + return output[SINE].value; + } + + + void setPhase(float phase) { + DSPSineLFO::phase = phase; + } + + + void reset() { + phase = 0; + } + + }; + + + /** + * DSP model of a leaky integrator + */ struct DSPIntegrator : DSPSystem<1, 1, 1> { enum Inputs { IN @@ -18,93 +86,148 @@ namespace dsp { }; enum Params { - D + LEAK }; - float d = 0.25; - float x = 0; + private: + float z = 0; + + public: + void process() override { + output[OUT].value = z; + z = input[IN].value + (param[LEAK].value * z); + } + /** + * @brief Add a value to integrator and push output + * @param x Input value + * @return Current of integrator + */ + float add(float x, float leak) { + param[LEAK].value = 0.999; + input[IN].value = x; + process(); + + return output[OUT].value; + } + + + /** + * @brief Returns the current integrator state + * @return + */ + float value() { + return z; + } }; -/** - * @brief Oscillator base class - */ - struct BLITOscillator { + struct DSPBLOscillator : DSPSystem<4, 6, 10> { + /** + * Bandwidth-limited threshold in hz. + * Should be at least SR/2 ! + * */ + static constexpr float BLIT_HARMONICS = 18000.f; + static constexpr float NOTE_C4 = 261.626f; - public: - enum SIGNAL { + enum Inputs { + VOCT1, VOCT2, + FM_CV, + TUNE, + OCTAVE + }; + + enum Outputs { SAW, PULSE, SINE, - TRI + TRI, + NOISE }; + enum Params { + FREQUENCY, + PULSEWIDTH + }; - float freq; // oscillator frequency - float pw; // pulse-width value + private: float phase; // current phase float incr; // current phase increment for PLL float detune; // analogue detune float drift; // oscillator drift float warmup; // oscillator warmup detune + float warmupTau; // time factor for warmup detune + int tick; + int n; + bool lfoMode; // LFO mode? Noise noise; // randomizer - float shape; - int n; + Integrator int1; + Integrator int2; + Integrator int3; + + DSPSineLFO *lfo; - /* currents of waveforms */ - float ramp; - float saw; - float pulse; - float sine; - float tri; + + void reset(); /* saved frequency states */ float _cv, _oct, _base, _coeff, _tune, _biqufm; - /* leaky integrators */ - Integrator int1; - Integrator int2; - Integrator int3; - BLITOscillator(); - ~BLITOscillator(); + public: + explicit DSPBLOscillator(float sr); - /** - * @brief Proccess next sample for output - */ - void proccess(); + void updatePitch(); + void setFrequency(float frq); - /** - * @brief ReCompute states on change - */ - void invalidate(); + void setInputs(float voct1, float voct2, float fm, float tune, float oct); + + + float getFrequency() { return param[FREQUENCY].value; } + + + bool isLFO() { + return lfoMode; + } + + + void setPulseWidth(float width); - /** - * @brief Reset oscillator - */ - void reset(); + float getSawWave() { + return getOutput(SAW); + } - void updatePitch(float cv, float fm, float tune, float oct); - /* common getter and setter */ - float getFrequency() const; - void setFrequency(float freq); - float getPulseWidth() const; - void setPulseWidth(float pw); + float getPulseWave() { + return getOutput(PULSE); + } - float getRampWave() const; - float getSawWave() const; - float getPulseWave() const; - float getSawTriWave() const; - float getTriangleWave() const; - float getSaturate() const; - void setShape(float saturate); + + float getSineWave() { + return getOutput(SINE); + } + + + float getTriWave() { + return getOutput(TRI); + } + + + float getNoise() { + return getOutput(NOISE); + } + + + void updateSampleRate(float sr) override; + + void invalidate() override; + void process() override; }; + } \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/SergeWavefolder.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/SergeWavefolder.cpp new file mode 100644 index 00000000..68bdd4c3 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/SergeWavefolder.cpp @@ -0,0 +1,75 @@ +#include "SergeWavefolder.hpp" + +using namespace dsp; + + +double SergeWFStage::compute(double x) { + double out; + double l, u, ln, fn, xn; + + l = sign(x); + u = (SERGE_R1 * SERGE_IS) / (SERGE_N * SERGE_VT) * pow(M_E, (l * x) / (SERGE_N * SERGE_VT)); + ln = lambert_W_Fritsch(u); + fn = x * x / 2 - SERGE_VT * SERGE_N * SERGE_N * SERGE_VT * (ln * (ln + 2)); + + // Check for ill-conditioning + if (abs(x - xn1) < SERGE_THRESHOLD) { + // Compute Averaged Wavefolder Output + xn = 0.5 * (x + xn1); + u = (SERGE_R1 * SERGE_IS) / (SERGE_N * SERGE_VT) * pow(M_E, (l * xn) / (SERGE_VT * SERGE_N)); + ln = lambert_W_Fritsch(u); + out = xn - 2 * l * SERGE_N * SERGE_VT * ln; + } else { + // Apply AA Form + out = (fn - fn1) / (x - xn1); + } + + fn1 = fn; + xn1 = x; + + return out; +} + + +SergeWFStage::SergeWFStage() { + fn1 = 0; + xn1 = 0; +} + + +SergeWavefolder::SergeWavefolder(float sr) : WaveShaper(sr) { + init(); + tanh1 = new HQTanh(sr, 4); +} + + +void SergeWavefolder::init() { + dsp::WaveShaper::rs = new dsp::Resampler<1>(1); +} + + +void SergeWavefolder::process() { + WaveShaper::process(); +} + + +double SergeWavefolder::compute(double x) { + double out; + double in = (x / 8. + bias) * gain; + + // in *= 0.5; + + in = sg1.compute(in); + in = sg2.compute(in); + in = sg3.compute(in); + in = sg4.compute(in); + in = sg5.compute(in); + in = sg6.compute(in); + + in *= 4.f; + + out = in;// tanh1->next(in / 2.); + + return out; +} + diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/SergeWavefolder.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/SergeWavefolder.hpp new file mode 100644 index 00000000..03900479 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/SergeWavefolder.hpp @@ -0,0 +1,45 @@ +#pragma once + + +#include "WaveShaper.hpp" +#include "HQTrig.hpp" + +#define SERGE_R1 33e3 +#define SERGE_IS 2.52e-9 +#define SERGE_VT 25.864e-3 +#define SERGE_N 1.752 + +#define SERGE_THRESHOLD 10e-10 + +namespace dsp { + + struct SergeWFStage { + private: + double fn1, xn1; + + public: + SergeWFStage(); + double compute(double x); + }; + + + struct SergeWavefolder : WaveShaper { + + private: + SergeWFStage sg1, sg2, sg3, sg4, sg5, sg6; + // DCBlocker *dc = new DCBlocker(DCBLOCK_ALPHA); + HQTanh *tanh1; + bool blockDC = false; + + + public: + explicit SergeWavefolder(float sr); + + void init() override; + void process() override; + double compute(double x) override; + + }; + + +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/WaveShaper.cpp b/plugins/community/repos/LindenbergResearch/src/dsp/WaveShaper.cpp new file mode 100644 index 00000000..40522594 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/WaveShaper.cpp @@ -0,0 +1,120 @@ + +#include "WaveShaper.hpp" + +using namespace dsp; + + +double WaveShaper::getIn() const { + return in; +} + + +void WaveShaper::setIn(double in) { + WaveShaper::in = in; +} + + +double WaveShaper::getGain() const { + return gain; +} + + +void WaveShaper::setGain(double gain) { + WaveShaper::gain = gain; +} + + +double WaveShaper::getBias() const { + return bias; +} + + +void WaveShaper::setBias(double bias) { + WaveShaper::bias = bias; +} + + +double WaveShaper::getK() const { + return k; +} + + +void WaveShaper::setK(double k) { + WaveShaper::k = k; +} + + +double WaveShaper::getOut() const { + return out; +} + + +void WaveShaper::setOut(double out) { + WaveShaper::out = out; +} + + +const Vec &WaveShaper::getAmplitude() const { + return amp; +} + + +void WaveShaper::process() { + /* if no oversampling set up */ + if (rs->getFactor() == 1) { + out = compute(in); + //debug("%f", out); + return; + } + + rs->doUpsample(STD_CHANNEL, in); + + for (int i = 0; i < rs->getFactor(); i++) { + double x = rs->getUpsampled(STD_CHANNEL)[i]; + rs->data[STD_CHANNEL][i] = compute(x); + } + + out = rs->getDownsampled(STD_CHANNEL); +} + + +WaveShaper::WaveShaper(float sr) : DSPEffect(sr) {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch/src/dsp/WaveShaper.hpp b/plugins/community/repos/LindenbergResearch/src/dsp/WaveShaper.hpp new file mode 100644 index 00000000..4b414402 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch/src/dsp/WaveShaper.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "DSPMath.hpp" +#include "DSPEffect.hpp" + +namespace dsp { + + /** + * @brief Basic WaveShaper class with build-in dynamic oversampling + * @tparam OVERSAMPLE + */ + struct WaveShaper : DSPEffect { + /* oversampling channel */ + static const int STD_CHANNEL = 0; + static constexpr double MAX_BIAS_LEVEL = 5.0; // +/- 5V + + protected: + Resampler<1> *rs; + + double in, gain, bias, k; + double out; + Vec amp; + + public: + + WaveShaper(float sr); + + double getIn() const; + void setIn(double in); + double getGain() const; + void setGain(double gain); + double getBias() const; + void setBias(double bias); + double getK() const; + void setK(double k); + double getOut() const; + void setOut(double out); + + + /** + * @brief Returns the actual sample-rate which is used by oversampled computation + * @return + */ + double getOversampledRate() { + return sr * rs->getFactor(); + } + + + void setAmplitude(double kpos, double kneg) { + amp = Vec(kpos, kneg); + } + + + const Vec &getAmplitude() const; + + + /** + * @brief Implements the oversamping of compute method + */ + void process() override; + + + void init() override { + gain = 0; + out = 0; + k = 0; + bias = 0; + amp = Vec(0, 0); + } + + + /** + * @brief To be implemented by subclass, automaticaly oversampled + * + * @param x Input sample + * @return Output sample + */ + virtual double compute(double x) { return x; } + }; + + +} + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/.idea/dictionaries/patricklindenberg.xml b/plugins/community/repos/LindenbergResearch__ORIG/.idea/dictionaries/patricklindenberg.xml new file mode 100644 index 00000000..bfe001cf --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/.idea/dictionaries/patricklindenberg.xml @@ -0,0 +1,10 @@ + + + + courve + lindenberg + rastered + shaper + + + \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/CMakeLists.txt b/plugins/community/repos/LindenbergResearch__ORIG/CMakeLists.txt new file mode 100644 index 00000000..839cd6e8 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.8) +project(LRT) + +set(CMAKE_CXX_STANDARD 11) + +set(SOURCE_FILES + src/LindenbergResearch.cpp + src/LindenbergResearch.hpp + src/AlmaFilter.cpp + src/SimpleFilter.cpp + src/ReShaper.cpp + src/BlankPanel.cpp + src/BlankPanelM1.cpp + src/VCO.cpp + src/dsp/DSPMath.cpp + src/dsp/DSPMath.hpp + src/Release.h + src/dsp/Oscillator.cpp + src/dsp/Oscillator.hpp + src/dsp/DSPSystem.hpp + src/dsp/LadderFilter.hpp + src/dsp/LadderFilter.cpp + src/dsp/MS20zdf.hpp src/dsp/MS20zdf.cpp src/MS20Filter.cpp src/LRComponents.cpp src/LRComponents.hpp) + +include_directories(.) +include_directories(src) +include_directories(src/dsp) +include_directories(../../include) +include_directories(../../include) +include_directories(../../include/dsp) +include_directories(../../dep/include) + +add_executable(LRT ${SOURCE_FILES} src/dsp/DSPMath.cpp src/dsp/DSPMath.hpp src/Release.h) \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/LICENSE b/plugins/community/repos/LindenbergResearch__ORIG/LICENSE new file mode 100644 index 00000000..0287067d --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2017-2018, Lindenberg Research / Patrick Lindenberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Commercial redistribution of the code, or parts, in any form + must be granted by the author. + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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 Lindenberg Research 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. diff --git a/plugins/community/repos/LindenbergResearch__ORIG/Makefile b/plugins/community/repos/LindenbergResearch__ORIG/Makefile new file mode 100644 index 00000000..6ccd8700 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/Makefile @@ -0,0 +1,9 @@ +SLUG = LindenbergResearch +VERSION = 0.6.0 + +SOURCES += $(wildcard src/*.cpp src/dsp/*.cpp) + +DISTRIBUTABLES += $(wildcard LICENSE*) res + +RACK_DIR ?= ../.. +include $(RACK_DIR)/plugin.mk diff --git a/plugins/community/repos/LindenbergResearch__ORIG/README.md b/plugins/community/repos/LindenbergResearch__ORIG/README.md new file mode 100644 index 00000000..691f5996 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/README.md @@ -0,0 +1,58 @@ +# Lindenberg Research Tec. - VCV Rack modules +LRT Rack modules is a collection of modules made for [VCV Rack](https://vcvrack.com) virtual modular system. + +Copyright (C) 2017-2018 by Lindenberg Research / Patrick Lindenberg + +![SCREENSHOT](doc/LRTRackModules_0.6.0.png) + + +## 1. Installation + +You can find the latest release here: https://github.com/lindenbergresearch/LRTRack/releases +Just download fpr you architecture and unzip in your _**Rack**_ folder located in your documents. + + +## 2. Build from source + +_**NOTE:**_ + +The current master has been migrated to 0.6.0dev! So you have to install the correct version +of Rack in order to get it running. I suggest to fetch a clean 0.6.xx version of Rack via git and build +all from scratch. + +You can build build the latest release from source, to do so use the following steps: +Note that you have to install a C++ environment with a compiler and other needed tools. +For further information about this, check the documentation found here of: [VCV Rack GIT](https://github.com/VCVRack/Rack) + +Clone git to local machine (into Rack/plugins): + + git clone https://github.com/lindenbergresearch/LRTRack.git + +After that you should have the latest sources. If you want to build some release, you **HAVE** +to switch to the corresponding TAG! For every released version a TAG exists, **be aware that if you check out the current master it could be +possible that it contains errors or unfinished modules... it is a current workstate!** + +To view all tags use: + + git tag + +To checkout to a specific tag simply use: + + git checkout + +Build from sources: + + make dist + +If the release could be successfully compiled you can find a zipped file under: + + 'dist/LindenbergResearch-xx.xx.xx-.zip'. + +Where xx.xx.xx is filled with the version of the release and with the system you are on (win/mac/lin). + +## 3. Bugs, requests and other issues + +Bug reports, change requests, genius ideas and other stuff goes here: [ISSUES](https://github.com/lindenbergresearch/LRTRack/issues) +Thanks to all people who helped to improve the modules and created releases for other architectures in the past! :) + + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/_config.yml b/plugins/community/repos/LindenbergResearch__ORIG/_config.yml new file mode 100644 index 00000000..cc35c1df --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-modernist \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/build.sh b/plugins/community/repos/LindenbergResearch__ORIG/build.sh new file mode 100644 index 00000000..f0535392 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# script argument +ARG=$1 + +# Rack base dir +RACK_DIR="../.." + +# build plugin +make + +# quit if compiling fails +[[ $? != 0 ]] && exit 1 + +# build and run +if [[ ${ARG} == "run" ]]; then + pushd $RACK_DIR + make run + popd +fi + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.5.210.png b/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.5.210.png new file mode 100644 index 00000000..5f13f88f Binary files /dev/null and b/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.5.210.png differ diff --git a/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.5.3.png b/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.5.3.png new file mode 100644 index 00000000..8d653754 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.5.3.png differ diff --git a/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.6.0.png b/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.6.0.png new file mode 100644 index 00000000..3e9eb744 Binary files /dev/null and b/plugins/community/repos/LindenbergResearch__ORIG/doc/LRTRackModules_0.6.0.png differ diff --git a/plugins/community/repos/LindenbergResearch__ORIG/make.objects b/plugins/community/repos/LindenbergResearch__ORIG/make.objects new file mode 100644 index 00000000..1b169ffe --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/make.objects @@ -0,0 +1,16 @@ +ALL_OBJ= \ + src/dsp/DSPMath.o \ + src/dsp/LadderFilter.o \ + src/dsp/MS20zdf.o \ + src/dsp/Oscillator.o \ + src/AlmaFilter.o \ + src/BlankPanel.o \ + src/BlankPanelM1.o \ + src/LRComponents.o \ + src/LindenbergResearch.o \ + src/MS20Filter.o \ + src/ReShaper.o \ + src/SimpleFilter.o + +# src/VCO.o + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/makefile.msvc b/plugins/community/repos/LindenbergResearch__ORIG/makefile.msvc new file mode 100644 index 00000000..8dbb7a85 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/makefile.msvc @@ -0,0 +1,7 @@ +SLUG=LindenbergResearch + +include ../../../build_plugin_pre.mk + +include make.objects + +include ../../../build_plugin_post.mk diff --git a/vst2_bin/plugins/LindenbergResearch/res/BigKnob.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/BigKnob.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/BigKnob.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/BigKnob.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/BigKnob.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/BigKnob.svg new file mode 100644 index 00000000..4452da95 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/BigKnob.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/BlankPanel.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanel.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/BlankPanel.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanel.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanel.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanel.svg new file mode 100644 index 00000000..0041548f --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanel.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/BlankPanelM1.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanelM1.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/BlankPanelM1.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanelM1.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanelM1.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanelM1.svg new file mode 100644 index 00000000..f1568a37 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/BlankPanelM1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/CogBig.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/CogBig.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/CogBig.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/CogBig.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/CogBig.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/CogBig.svg new file mode 100644 index 00000000..79e51ef3 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/CogBig.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/CogSmall.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/CogSmall.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/CogSmall.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/CogSmall.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/CogSmall.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/CogSmall.svg new file mode 100644 index 00000000..5e88f371 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/CogSmall.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/IOPortB.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/IOPortB.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/IOPortB.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/IOPortB.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/IOPortB.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/IOPortB.svg new file mode 100644 index 00000000..7e071f33 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/IOPortB.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + ]> + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/MS20.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/MS20.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/MS20.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/MS20.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/MS20.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/MS20.svg new file mode 100644 index 00000000..d38ccbb1 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/MS20.svg @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/MiddleKnob.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/MiddleKnob.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/MiddleKnob.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/MiddleKnob.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/MiddleKnob.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/MiddleKnob.svg new file mode 100644 index 00000000..a2a6907c --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/MiddleKnob.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/ReShaper.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/ReShaper.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/ReShaper.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/ReShaper.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/ReShaper.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/ReShaper.svg new file mode 100644 index 00000000..4f742811 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/ReShaper.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/ScrewDark.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/ScrewDark.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/ScrewDark.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/ScrewDark.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/ScrewDark.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/ScrewDark.svg new file mode 100644 index 00000000..7a212c8a --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/ScrewDark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/SimpleFilter.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/SimpleFilter.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/SimpleFilter.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/SimpleFilter.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/SimpleFilter.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/SimpleFilter.svg new file mode 100644 index 00000000..01b4adc0 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/SimpleFilter.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/SmallKnob.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/SmallKnob.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/SmallKnob.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/SmallKnob.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/SmallKnob.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/SmallKnob.svg new file mode 100644 index 00000000..e0e1896f --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/SmallKnob.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/Switch0.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/Switch0.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/Switch0.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/Switch0.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/Switch0.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/Switch0.svg new file mode 100644 index 00000000..83fd938e --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/Switch0.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/Switch1.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/Switch1.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/Switch1.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/Switch1.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/Switch1.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/Switch1.svg new file mode 100644 index 00000000..b025ee9a --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/Switch1.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/ToggleKnob.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/ToggleKnob.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/ToggleKnob.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/ToggleKnob.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/ToggleKnob.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/ToggleKnob.svg new file mode 100644 index 00000000..67672e14 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/ToggleKnob.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/VCF.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/VCF.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/VCF.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/VCF.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/VCF.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/VCF.svg new file mode 100644 index 00000000..e8c1b323 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/VCF.svg @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/VCO.afdesign b/plugins/community/repos/LindenbergResearch__ORIG/res/VCO.afdesign similarity index 100% rename from vst2_bin/plugins/LindenbergResearch/res/VCO.afdesign rename to plugins/community/repos/LindenbergResearch__ORIG/res/VCO.afdesign diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/VCO.svg b/plugins/community/repos/LindenbergResearch__ORIG/res/VCO.svg new file mode 100644 index 00000000..dddf021c --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/res/VCO.svg @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/res/digital-7.ttf b/plugins/community/repos/LindenbergResearch__ORIG/res/digital-7.ttf new file mode 100644 index 00000000..a481b97b Binary files /dev/null and b/plugins/community/repos/LindenbergResearch__ORIG/res/digital-7.ttf differ diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/AlmaFilter.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/AlmaFilter.cpp new file mode 100644 index 00000000..d6e4834d --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/AlmaFilter.cpp @@ -0,0 +1,153 @@ +#include "dsp/LadderFilter.hpp" +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct AlmaFilter : LRModule { + + enum ParamIds { + CUTOFF_PARAM, + RESONANCE_PARAM, + DRIVE_PARAM, + SLOPE_PARAM, + CUTOFF_CV_PARAM, + RESONANCE_CV_PARAM, + DRIVE_CV_PARAM, + NUM_PARAMS + }; + + enum InputIds { + FILTER_INPUT, + CUTOFF_CV_INPUT, + RESONANCE_CV_INPUT, + DRIVE_CV_INPUT, + NUM_INPUTS + }; + + enum OutputIds { + LP_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds { + OVERLOAD_LIGHT, + NUM_LIGHTS + }; + + LadderFilter filter; + + LRBigKnob *frqKnob = NULL; + LRMiddleKnob *peakKnob = NULL; + LRMiddleKnob *driveKnob = NULL; + + + AlmaFilter() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + + + void step() override; +}; + + +void AlmaFilter::step() { + float frqcv = inputs[CUTOFF_CV_INPUT].value * 0.1f * quadraticBipolar(params[CUTOFF_CV_PARAM].value); + float rescv = inputs[RESONANCE_CV_INPUT].value * 0.1f * quadraticBipolar(params[RESONANCE_CV_PARAM].value); + float drvcv = inputs[DRIVE_CV_INPUT].value * 0.1f * quadraticBipolar(params[DRIVE_CV_PARAM].value); + + filter.setFrequency(params[CUTOFF_PARAM].value + frqcv); + filter.setResonance(params[RESONANCE_PARAM].value + rescv); + filter.setDrive(params[DRIVE_PARAM].value + drvcv); + filter.setSlope(params[SLOPE_PARAM].value); + + + /* pass modulated parameter to knob widget for cv indicator */ + if (frqKnob != NULL && peakKnob != NULL && driveKnob != NULL) { + frqKnob->setIndicatorActive(inputs[CUTOFF_CV_INPUT].active); + peakKnob->setIndicatorActive(inputs[RESONANCE_CV_INPUT].active); + driveKnob->setIndicatorActive(inputs[DRIVE_CV_INPUT].active); + + frqKnob->setIndicatorValue(params[CUTOFF_PARAM].value + frqcv); + peakKnob->setIndicatorValue(params[RESONANCE_PARAM].value + rescv); + driveKnob->setIndicatorValue(params[DRIVE_PARAM].value + drvcv); + } + + + float y = inputs[FILTER_INPUT].value; + + filter.setIn(y); + filter.process(); + + outputs[LP_OUTPUT].value = filter.getLpOut(); + + + lights[OVERLOAD_LIGHT].value = filter.getLightValue(); +} + + +/** + * @brief ALMA filter + */ +struct AlmaFilterWidget : LRModuleWidget { + AlmaFilterWidget(AlmaFilter *module); +}; + + +AlmaFilterWidget::AlmaFilterWidget(AlmaFilter *module) : LRModuleWidget(module) { + //setPanel(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); + + + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + // ***** MAIN KNOBS ****** + module->frqKnob = LRKnob::create(Vec(62, 150), module, AlmaFilter::CUTOFF_PARAM, 0.f, 1.f, 0.8f); + module->peakKnob = LRKnob::create(Vec(24, 229), module, AlmaFilter::RESONANCE_PARAM, -0.f, 1.5, 0.0f); + module->driveKnob = LRKnob::create(Vec(116, 228), module, AlmaFilter::DRIVE_PARAM, 0.0f, 1.f, 0.0f); + + addParam(module->frqKnob); + addParam(module->peakKnob); + addParam(module->driveKnob); + + addParam(ParamWidget::create(Vec(70, 288), module, AlmaFilter::SLOPE_PARAM, 0.0f, 4.f, 2.0f)); + // ***** MAIN KNOBS ****** + + // ***** CV INPUTS ******* + addParam(ParamWidget::create(Vec(27.5, 106), module, AlmaFilter::RESONANCE_CV_PARAM, -1.f, 1.0f, 0.f)); + addParam(ParamWidget::create(Vec(78, 106), module, AlmaFilter::CUTOFF_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(127.1, 106), module, AlmaFilter::DRIVE_CV_PARAM, -1.f, 1.f, 0.f)); + + addInput(Port::create(Vec(26, 50), Port::INPUT, module, AlmaFilter::RESONANCE_CV_INPUT)); + addInput(Port::create(Vec(76, 50), Port::INPUT, module, AlmaFilter::CUTOFF_CV_INPUT)); + addInput(Port::create(Vec(125, 50), Port::INPUT, module, AlmaFilter::DRIVE_CV_INPUT)); + // ***** CV INPUTS ******* + + // ***** INPUTS ********** + addInput(Port::create(Vec(25, 326.5), Port::INPUT, module, AlmaFilter::FILTER_INPUT)); + // ***** INPUTS ********** + + // ***** OUTPUTS ********* + addOutput(Port::create(Vec(124.5, 326.5), Port::OUTPUT, module, AlmaFilter::LP_OUTPUT)); + // ***** OUTPUTS ********* + + // ***** LIGHTS ********** + addChild(ModuleLightWidget::create(Vec(85, 247), module, AlmaFilter::OVERLOAD_LIGHT)); + // ***** LIGHTS ********** +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, AlmaFilter) { + Model *modelAlmaFilter = Model::create("Lindenberg Research", "VCF", "Alma Ladder Filter", FILTER_TAG); + return modelAlmaFilter; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/BlankPanel.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/BlankPanel.cpp new file mode 100644 index 00000000..667ea109 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/BlankPanel.cpp @@ -0,0 +1,78 @@ +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct BlankPanel : Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + LCDWidget *lcd1 = new LCDWidget(LCD_COLOR_FG, 15); + + + BlankPanel() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} + + + void step() override; +}; + + +void BlankPanel::step() { + +} + + +/** + * @brief Blank Panel with Logo + */ +struct BlankPanelWidget : LRModuleWidget { + BlankPanelWidget(BlankPanel *module); +}; + + +BlankPanelWidget::BlankPanelWidget(BlankPanel *module) : LRModuleWidget(module) { + //setPanel(SVG::load(assetPlugin(plugin, "res/BlankPanel.svg"))); + + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/BlankPanel.svg"))); + addChild(panel); + + box.size = panel->box.size; + + float speed = 0.002; + + addChild(SVGRotator::create(Vec(140.5, 83), SVG::load(assetPlugin(plugin, "res/CogBig.svg")), speed)); + addChild(SVGRotator::create(Vec(120, 114.7), SVG::load(assetPlugin(plugin, "res/CogSmall.svg")), -speed * 1.6)); + + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + // ***** LCD ************* + /* module->lcd1->box.pos = Vec(34, 355); + addChild(module->lcd1); + module->lcd1->text = VERSION_STR;*/ + // ***** LCD ************* +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, BlankPanel) { + Model *modelBlankPanel = Model::create("Lindenberg Research", "BlankPanel 01", "Blank Panel 20TE", BLANK_TAG); + return modelBlankPanel; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/BlankPanelM1.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/BlankPanelM1.cpp new file mode 100644 index 00000000..40f84e8a --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/BlankPanelM1.cpp @@ -0,0 +1,64 @@ +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct BlankPanelM1 : Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + + BlankPanelM1() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} + + + void step() override; +}; + + +void BlankPanelM1::step() { +} + + +/** + * @brief Blank Panel Mark I + */ +struct BlankPanelWidgetM1 : LRModuleWidget { + BlankPanelWidgetM1(BlankPanelM1 *module); +}; + + +BlankPanelWidgetM1::BlankPanelWidgetM1(BlankPanelM1 *module) : LRModuleWidget(module) { + // setPanel(SVG::load(assetPlugin(plugin, "res/BlankPanelM1.svg"))); + + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/BlankPanelM1.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, BlankPanelM1) { + Model *modelBlankPanelM1 = Model::create("Lindenberg Research", "BlankPanel 02", "Blank Panel 12TE", + BLANK_TAG); + return modelBlankPanelM1; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/LRComponents.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/LRComponents.cpp new file mode 100644 index 00000000..2d586370 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/LRComponents.cpp @@ -0,0 +1,275 @@ +#include "LRComponents.hpp" + + +/** + * @brief Constructor of LCD Widget + */ +LCDWidget::LCDWidget(NVGcolor fg, unsigned char length) { + /** load LCD ttf font */ + gLCDFont_DIG7 = Font::load(assetPlugin(plugin, LCD_FONT_DIG7)); + + auto r = (unsigned char) (fg.r * 0xFF); + auto g = (unsigned char) (fg.g * 0xFF); + auto b = (unsigned char) (fg.b * 0xFF); + + LCDWidget::length = length; + + LCDWidget::fg = fg; + LCDWidget::bg = nvgRGBA(r - 0x40, g - 0x40, b - 0x40, 0x40); +} + + +LRModuleWidget::LRModuleWidget(Module *module) : ModuleWidget(module) { +} + + +/** + * @brief Draw method of custom LCD widget + * @param vg + */ +void LCDWidget::draw(NVGcontext *vg) { + nvgFontSize(vg, LCD_FONTSIZE); + nvgFontFaceId(vg, gLCDFont_DIG7->handle); + nvgTextLetterSpacing(vg, LCD_LETTER_SPACING); + + nvgFillColor(vg, bg); + + std::string s1; + std::string s2; + + for (int i = 0; i < LCDWidget::length; ++i) { + s1.append("8"); + s2.append(":"); + } + + nvgTextBox(vg, 0, 0, 220, s1.c_str(), nullptr); + nvgTextBox(vg, 0, 0, 220, s2.c_str(), nullptr); + + nvgFillColor(vg, fg); + nvgTextBox(vg, 0, 0, 220, text.c_str(), nullptr); +} + + +void LRRedLight::draw(NVGcontext *vg) { + //LightWidget::draw(vg); + + float radius = box.size.x / 1.5f; + float oradius = radius + 10.0f; + + /* color.r = clampf(color.r, 0.0f, 1.0f); + color.g = clampf(color.g, 0.0f, 1.0f); + color.b = clampf(color.b, 0.0f, 1.0f); + color.a = clampf(color.a, 0.0f, 1.0f);*/ + + // Solid + nvgBeginPath(vg); + nvgCircle(vg, radius, radius, radius); + nvgFillColor(vg, bgColor); + nvgFill(vg); + + // Border + nvgStrokeWidth(vg, 1.0f); + NVGcolor borderColor = bgColor; + borderColor.a *= 0.5f; + nvgStrokeColor(vg, borderColor); + nvgStroke(vg); + + // Inner glow + nvgGlobalCompositeOperation(vg, NVG_LIGHTER); + nvgFillColor(vg, color); + nvgFill(vg); + + // Outer glow + nvgBeginPath(vg); + nvgRect(vg, radius - oradius, radius - oradius, 2 * oradius, 2 * oradius); + NVGpaint paint; + NVGcolor icol = color; + icol.a *= 0.40f; + NVGcolor ocol = color; + ocol.a = 0.00f; + paint = nvgRadialGradient(vg, radius, radius, radius, oradius, icol, ocol); + nvgFillPaint(vg, paint); + nvgFill(vg); +} + + +/** + * @brief Constructor + */ +LRRedLight::LRRedLight() { + addBaseColor(COLOR_RED); +} + + +/** + * @brief Draw routine for cv indicator + * @param vg + */ +void Indicator::draw(NVGcontext *vg) { + NVGcolor current = normalColor; + + if (active) { + /** underrun */ + if (cv < 0.f - OVERFLOW_THRESHOLD) { + cv = 0.f - OVERFLOW_THRESHOLD; + current = overflowColor; + } + + /** overrun */ + if (cv > 1.f + OVERFLOW_THRESHOLD) { + cv = 1.f + OVERFLOW_THRESHOLD; + current = overflowColor; + } + + + float a = -angle + cv * angle2; + float d = distance - 4.f; + Vec p1, p2, p3; + + /** compute correct point of indicator on circle */ + p1.x = middle.x - sin(-a * (float) M_PI) * distance; + p1.y = middle.y - cos(-a * (float) M_PI) * distance; + + p2.x = middle.x - sin(-(a + 0.1f) * (float) M_PI) * d; + p2.y = middle.y - cos(-(a + 0.1f) * (float) M_PI) * d; + + p3.x = middle.x - sin(-(a - 0.1f) * (float) M_PI) * d; + p3.y = middle.y - cos(-(a - 0.1f) * (float) M_PI) * d; + + nvgBeginPath(vg); + nvgMoveTo(vg, p1.x, p1.y); + nvgLineTo(vg, p2.x, p2.y); + nvgLineTo(vg, p3.x, p3.y); + nvgLineTo(vg, p1.x, p1.y); + nvgClosePath(vg); + + nvgFillColor(vg, current); + nvgFill(vg); + } +} + + +/** + * @brief Draw shadow for circular knobs + * @param vg NVGcontext + * @param strength Alpha value of outside gradient + * @param size Outer size + * @param shift XY Offset shift from middle + */ +void LRShadow::drawShadow(NVGcontext *vg, float strength, float size) { + // add shadow + nvgBeginPath(vg); + nvgRect(vg, -20, -20, box.size.x + 40, box.size.y + 40); + + NVGcolor icol = nvgRGBAf(0.0f, 0.0f, 0.0f, strength); + NVGcolor ocol = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.f);; + + NVGpaint paint = nvgRadialGradient(vg, box.size.x / 2 + shadowPos.x, box.size.y / 2 + shadowPos.y, + box.size.x * 0.3f, box.size.x * size, icol, ocol); + nvgFillPaint(vg, paint); + nvgFill(vg); +} + + +/** + * @brief Hook into widget draw routine to simulate shadow + * @param vg + */ +void LRShadow::draw(NVGcontext *vg) { + drawShadow(vg, strength, size); +} + + +/** + * @brief Setter for box dimensions + * @param box + */ +void LRShadow::setBox(const Rect &box) { + LRShadow::box = box; +} + + +/** + * @brief Setter for outer radius size + * @param size + */ +void LRShadow::setSize(float size) { + LRShadow::size = size; +} + + +/** + * @brief Setter for draw strength of shadow + * @param strength + */ +void LRShadow::setStrength(float strength) { + LRShadow::strength = strength; +} + + +/** + * @brief Extention for panel background + * @param vg + */ +void LRPanel::draw(NVGcontext *vg) { + FramebufferWidget::draw(vg); + + nvgBeginPath(vg); + nvgRect(vg, -MARGIN, -MARGIN, box.size.x + MARGIN * 2, box.size.y + MARGIN * 2); + + NVGpaint paint = nvgLinearGradient(vg, offset.x, offset.y, box.size.x, box.size.y, inner, outer); + nvgFillPaint(vg, paint); + nvgFill(vg); +} + + +void LRPanel::setInner(const NVGcolor &inner) { + LRPanel::inner = inner; +} + + +void LRPanel::setOuter(const NVGcolor &outer) { + LRPanel::outer = outer; +} + + +LRPanel::LRPanel() {} + + +SVGRotator::SVGRotator() : FramebufferWidget() { + tw = new TransformWidget(); + addChild(tw); + + sw = new SVGWidget(); + tw->addChild(sw); +} + + +/** + * @brief Set SVG image to rotator + * @param svg + */ +void SVGRotator::setSVG(std::shared_ptr svg) { + sw->setSVG(svg); + tw->box.size = sw->box.size; + box.size = sw->box.size; +} + + +/** + * @brief Rotate one step + */ +void SVGRotator::step() { + tw->identity(); + + angle = fmodf(angle + inc, 2 * M_PI);; + + Vec center = sw->box.getCenter(); + tw->translate(center); + tw->rotate(angle); + tw->translate(center.neg()); + + dirty = true; + + FramebufferWidget::step(); +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/LRComponents.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/LRComponents.hpp new file mode 100644 index 00000000..48d30922 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/LRComponents.hpp @@ -0,0 +1,515 @@ +#pragma once + +#include "rack.hpp" +#include "asset.hpp" +#include "widgets.hpp" + +#define LCD_FONT_DIG7 "res/digital-7.ttf" +#define LCD_COLOR_FG nvgRGBA(0x00, 0xE1, 0xE4, 0xFF) +#define LCD_FONTSIZE 8 +#define LCD_LETTER_SPACING 0 + +using namespace rack; + +#ifdef USE_VST2 +#define plugin "LindenbergResearch" +#else +extern Plugin *plugin; +#endif // USE_VST2 + + +/** + * @brief Standard LRT module + */ +struct LRModule : Module { + long cnt = 0; + + + /** + * @brief Overtake default constructor for module to be compatible + * @param numParams + * @param numInputs + * @param numOutputs + * @param numLights + */ + LRModule(int numParams, int numInputs, int numOutputs, int numLights = 0) : + Module(numParams, numInputs, numOutputs, numLights) {} + + + void step() override { + Module::step(); + + // increment counter + cnt++; + } +}; + + +/** + * @brief Standard LRT ModuleWidget definition + */ +struct LRModuleWidget : ModuleWidget { + explicit LRModuleWidget(Module *module); +}; + + +/** + * @brief Emulation of a LCD monochrome display + */ +struct LCDWidget : Label { + std::shared_ptr gLCDFont_DIG7; + NVGcolor fg; + NVGcolor bg; + unsigned char length = 0; + + + /** + * @brief Constructor + */ + LCDWidget(NVGcolor fg, unsigned char length); + + + /** + * @brief Draw LCD display + * @param vg + */ + void draw(NVGcontext *vg) override; +}; + + +/** + * @brief Indicator for control voltages on knobs + */ +struct Indicator { + static constexpr float OVERFLOW_THRESHOLD = 0.01f; + + /** flag to control drawing */ + bool active = false; + + /** color of indicator */ + NVGcolor normalColor = nvgRGBA(0x00, 0x00, 0x00, 0xBB); + NVGcolor overflowColor = nvgRGBA(0xBB, 0x00, 0x00, 0xBB); + + /** radius from middle */ + float distance; + + /** normalized control voltage. must between [0..1] */ + float cv = 0.f; + + /** draw angle */ + float angle; + float angle2; + + /** middle of parent */ + Vec middle; + + + /** + * @brief Init indicator + * @param distance Radius viewed from the middle + * @param angle Angle of active knob area + */ + Indicator(float distance, float angle) { + Indicator::distance = distance; + Indicator::angle = angle; + + /** for optimization */ + angle2 = 2 * angle; + } + + + /** + * @brief Draw routine for cv indicator + * @param vg + */ + void draw(NVGcontext *vg); + +}; + + +/** + * @brief Standard LR Shadow + */ +struct LRShadow { +private: + Rect box; + float size = 0.65; + float strength = 1.f; + + /** shadow shift */ + Vec shadowPos = Vec(3, 5); +public: + /** + * @brief Set the new offset of the shadow gradient + * @param x + * @param y + */ + void setShadowPosition(float x, float y) { + shadowPos = Vec(x, y); + } + + + void setBox(const Rect &box); + void setSize(float size); + void setStrength(float strength); + + /** + * @brief Draw shadow for circular knobs + * @param vg NVGcontext + * @param strength Alpha value of outside gradient + * @param size Outer size + * @param shift XY Offset shift from middle + */ + void drawShadow(NVGcontext *vg, float strength, float size); + + void draw(NVGcontext *vg); +}; + + +/** + * @brief The base of all knobs used in LR panels, includes a indicator + */ +struct LRKnob : SVGKnob { +private: + static constexpr float ANGLE = 0.83f; + + /** setup indicator with default values */ + Indicator idc = Indicator(15.f, ANGLE); + LRShadow shadow = LRShadow(); + + /** snap mode */ + bool snap = false; + /** position to snap */ + float snapAt = 0.0f; + /** snap sensitivity */ + float snapSens = 0.1; + +public: + /** + * @brief Default constructor + */ + LRKnob() { + minAngle = -ANGLE * (float) M_PI; + maxAngle = ANGLE * (float) M_PI; + } + + + /** + * @brief Set the value of the indicator + * @param value + */ + void setIndicatorValue(float value) { + idc.cv = value; + } + + + /** + * @brief Switch indicator on/off + * @param active + */ + void setIndicatorActive(bool active) { + idc.active = active; + } + + + /** + * @brief Get indicator state + * @return + */ + bool isIndicatorActive() { + return idc.active; + } + + + /** + * @brief Setup distance of indicator from middle + * @param distance + */ + void setIndicatorDistance(float distance) { + idc.distance = distance; + } + + + /** + * @brief Hook into setSVG() method to setup box dimensions correct for indicator + * @param svg + */ + void setSVG(std::shared_ptr svg) { + SVGKnob::setSVG(svg); + + /** inherit dimensions after loaded svg */ + idc.middle = Vec(box.size.x / 2, box.size.y / 2); + shadow.setBox(box); + } + + + /** + * @brief Route setter to shadow + * @param x + * @param y + */ + void setShadowPosition(float x, float y) { + shadow.setShadowPosition(x, y); + } + + + /** + * @brief Creates a new instance of a LRKnob child + * @tparam TParamWidget Subclass of LRKnob + * @param pos Position + * @param module Module pointer + * @param paramId Parameter ID + * @param minValue Min + * @param maxValue Max + * @param defaultValue Default + * @return Pointer to new subclass of LRKnob + */ + template + static TParamWidget *create(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { + auto *param = new TParamWidget(); + param->box.pos = pos; + param->module = module; + param->paramId = paramId; + param->setLimits(minValue, maxValue); + param->setDefaultValue(defaultValue); + return param; + } + + + /** + * @brief Draw knob + * @param vg + */ + void draw(NVGcontext *vg) override { + /** shadow */ + shadow.draw(vg); + + /** component */ + FramebufferWidget::draw(vg); + + /** indicator */ + idc.draw(vg); + } + + + /** + * @brief Setup knob snapping + * @param position + * @param sensitivity + */ + void setSnap(float position, float sensitivity) { + snap = true; + snapSens = sensitivity; + snapAt = position; + } + + + /** + * @brief Remove knob snaping + */ + void unsetSnap() { + snap = false; + } + + + /** + * @brief Snapping mode for knobs + * @param e + */ + void onChange(EventChange &e) override { + if (snap && value > -snapSens + snapAt && value < snapSens + snapAt) value = 0; + SVGKnob::onChange(e); + } +}; + + +/** + * @brief Quantize position to odd numbers to simulate a toggle switch + */ +struct LRToggleKnob : LRKnob { + LRToggleKnob(float length = 0.5f) { + minAngle = -length * (float) M_PI; + maxAngle = length * (float) M_PI; + + setSVG(SVG::load(assetPlugin(plugin, "res/ToggleKnob.svg"))); + setShadowPosition(2, 2); + + speed = 2.f; + } + + + void onChange(EventChange &e) override { + value = round(value); + SVGKnob::onChange(e); + } +}; + + +/** + * @brief LR Big Knob + */ +struct LRBigKnob : LRKnob { + LRBigKnob() { + setSVG(SVG::load(assetPlugin(plugin, "res/BigKnob.svg"))); + setIndicatorDistance(15); + setShadowPosition(5, 6); + } +}; + + +/** + * @brief LR Middle Knob + */ +struct LRMiddleKnob : LRKnob { + LRMiddleKnob() { + setSVG(SVG::load(assetPlugin(plugin, "res/MiddleKnob.svg"))); + setIndicatorDistance(12); + setShadowPosition(4, 4); + } +}; + + +/** + * @brief LR Small Knob + */ +struct LRSmallKnob : LRKnob { + LRSmallKnob() { + setSVG(SVG::load(assetPlugin(plugin, "res/SmallKnob.svg"))); + setShadowPosition(3, 3); + setSnap(0.0f, 0.03f); + + + speed = 0.7f; + } +}; + + +/** + * @brief Alternative IO Port + */ +struct IOPort : SVGPort { +private: + LRShadow shadow = LRShadow(); + +public: + IOPort() { + background->svg = SVG::load(assetPlugin(plugin, "res/IOPortB.svg")); + background->wrap(); + box.size = background->box.size; + + /** inherit dimensions */ + shadow.setBox(box); + shadow.setSize(0.50); + shadow.setShadowPosition(2, 2); + } + + + /** + * @brief Hook into draw method + * @param vg + */ + void draw(NVGcontext *vg) override { + shadow.draw(vg); + SVGPort::draw(vg); + } +}; + + +/** + * @brief Alternative screw head A + */ +struct ScrewDarkA : SVGScrew { + ScrewDarkA() { + sw->svg = SVG::load(assetPlugin(plugin, "res/ScrewDark.svg")); + sw->wrap(); + box.size = sw->box.size; + } +}; + + +/** + * @brief Custom switch based on original Rack files + */ +struct LRSwitch : SVGSwitch, ToggleSwitch { + LRSwitch() { + addFrame(SVG::load(assetPlugin(plugin, "res/Switch0.svg"))); + addFrame(SVG::load(assetPlugin(plugin, "res/Switch1.svg"))); + } +}; + + +/** + * @brief Standard LED Redlight + */ +struct LRRedLight : SmallLight { + LRRedLight(); + + void draw(NVGcontext *vg) override; +}; + + +/** + * @brief Standard LR module Panel + */ +struct LRPanel : SVGPanel { +private: + /** margin of gradient box */ + static constexpr float MARGIN = 20; + + /** gradient colors */ + NVGcolor inner = nvgRGBAf(.6f, 0.7f, 0.9f, 0.12f); + NVGcolor outer = nvgRGBAf(0.0f, 0.0f, 0.0f, 0.0f);; + + /** gradient offset */ + Vec offset = Vec(-40, -50); + + void setInner(const NVGcolor &inner); + void setOuter(const NVGcolor &outer); +public: + LRPanel(); + + + LRPanel(float x, float y) { + offset.x = x; + offset.y = y; + } + + + void draw(NVGcontext *vg) override; +}; + + +/** + * @brief Passive rotating SVG image + */ +struct SVGRotator : FramebufferWidget { + TransformWidget *tw; + SVGWidget *sw; + + /** angle to rotate per step */ + float angle = 0; + float inc; + + + SVGRotator(); + + + /** + * @brief Factory method + * @param pos Position + * @param svg Pointer to SVG image + * @param angle Increment angle per step + */ + SVGRotator static *create(Vec pos, std::shared_ptr svg, float inc) { + SVGRotator *rotator = FramebufferWidget::create(pos); + + rotator->setSVG(svg); + rotator->inc = inc; + + return rotator; + } + + + void setSVG(std::shared_ptr svg); + void step() override; +}; diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/LindenbergResearch.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/LindenbergResearch.cpp new file mode 100644 index 00000000..b660c7b1 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/LindenbergResearch.cpp @@ -0,0 +1,24 @@ +#include "LindenbergResearch.hpp" + +using namespace rack; + +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, SimpleFilter); +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, MS20Filter); +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, AlmaFilter); +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, ReShaper); +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, BlankPanel); +RACK_PLUGIN_MODEL_DECLARE(LindenbergResearch, BlankPanelM1); + +RACK_PLUGIN_INIT(LindenbergResearch) { + RACK_PLUGIN_INIT_ID(); + + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, SimpleFilter); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, MS20Filter); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, AlmaFilter); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, ReShaper); + + //TODO: RACK_PLUGIN_MODEL_ADD(LindenbergResearch, VCO); + + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, BlankPanel); + RACK_PLUGIN_MODEL_ADD(LindenbergResearch, BlankPanelM1); +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/LindenbergResearch.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/LindenbergResearch.hpp new file mode 100644 index 00000000..83838d8d --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/LindenbergResearch.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "rack.hpp" +#include "asset.hpp" +#include "widgets.hpp" +#include "LRComponents.hpp" + +using namespace rack; + +RACK_PLUGIN_DECLARE(LindenbergResearch); + +#ifdef USE_VST2 +#define plugin "LindenbergResearch" +#endif // USE_VST2 diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/MS20Filter.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/MS20Filter.cpp new file mode 100644 index 00000000..d3c91d01 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/MS20Filter.cpp @@ -0,0 +1,153 @@ +#include "dsp/MS20zdf.hpp" +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct MS20Filter : LRModule { + + enum ParamIds { + FREQUENCY_PARAM, + PEAK_PARAM, + DRIVE_PARAM, + CUTOFF_CV_PARAM, + PEAK_CV_PARAM, + GAIN_CV_PARAM, + MODE_SWITCH_PARAM, + NUM_PARAMS + }; + + enum InputIds { + FILTER_INPUT, + CUTOFF_CV_INPUT, + PEAK_CV_INPUT, + GAIN_CV_INPUT, + NUM_INPUTS + }; + + enum OutputIds { + FILTER_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds { + NUM_LIGHTS + }; + + dsp::MS20zdf *ms20zdf = new dsp::MS20zdf(engineGetSampleRate()); + + LRBigKnob *frqKnob = NULL; + LRMiddleKnob *peakKnob = NULL; + LRMiddleKnob *driveKnob = NULL; + + + MS20Filter() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + + + void step() override; + void onSampleRateChange() override; +}; + + +void MS20Filter::step() { + /* compute control voltages */ + float frqcv = inputs[CUTOFF_CV_INPUT].value * 0.1f * quadraticBipolar(params[CUTOFF_CV_PARAM].value); + float peakcv = inputs[PEAK_CV_INPUT].value * 0.1f * quadraticBipolar(params[PEAK_CV_PARAM].value); + float gaincv = inputs[GAIN_CV_INPUT].value * 0.1f * quadraticBipolar(params[GAIN_CV_PARAM].value); + + /* set cv modulated parameters */ + ms20zdf->setFrequency(params[FREQUENCY_PARAM].value + frqcv); + ms20zdf->setPeak(params[PEAK_PARAM].value + peakcv); + ms20zdf->setDrive(params[DRIVE_PARAM].value + gaincv); + + /* pass modulated parameter to knob widget for cv indicator */ + if (frqKnob != NULL && peakKnob != NULL && driveKnob != NULL) { + frqKnob->setIndicatorActive(inputs[CUTOFF_CV_INPUT].active); + peakKnob->setIndicatorActive(inputs[PEAK_CV_INPUT].active); + driveKnob->setIndicatorActive(inputs[GAIN_CV_INPUT].active); + + frqKnob->setIndicatorValue(params[FREQUENCY_PARAM].value + frqcv); + peakKnob->setIndicatorValue(params[PEAK_PARAM].value + peakcv); + driveKnob->setIndicatorValue(params[DRIVE_PARAM].value + gaincv); + } + + /* process signal */ + ms20zdf->setType(params[MODE_SWITCH_PARAM].value); + ms20zdf->setIn(inputs[FILTER_INPUT].value); + ms20zdf->process(); + + outputs[FILTER_OUTPUT].value = ms20zdf->getLPOut(); +} + + +void MS20Filter::onSampleRateChange() { + Module::onSampleRateChange(); + ms20zdf->updateSampleRate(engineGetSampleRate()); +} + + +/** + * @brief Valerie MS20 filter + */ +struct MS20FilterWidget : LRModuleWidget { + MS20FilterWidget(MS20Filter *module); +}; + + +MS20FilterWidget::MS20FilterWidget(MS20Filter *module) : LRModuleWidget(module) { + //setPanel(SVG::load(assetPlugin(plugin, "res/MS20.svg"))); + + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/MS20.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + // ***** MAIN KNOBS ****** + module->frqKnob = LRKnob::create(Vec(102.f, 64.9f), module, MS20Filter::FREQUENCY_PARAM, 0.f, 1.f, 1.f); + module->peakKnob = LRKnob::create(Vec(110.f, 160.8f), module, MS20Filter::PEAK_PARAM, 0.0f, 1.0f, 0.0f); + module->driveKnob = LRKnob::create(Vec(110.f, 230.6f), module, MS20Filter::DRIVE_PARAM, 0.f, 1.0f, 0.0f); + + addParam(module->frqKnob); + addParam(module->peakKnob); + addParam(module->driveKnob); + + // ***** MAIN KNOBS ****** + + // ***** CV INPUTS ******* + addParam(ParamWidget::create(Vec(61.f, 169.3f), module, MS20Filter::PEAK_CV_PARAM, -1.f, 1.0f, 0.f)); + addParam(ParamWidget::create(Vec(61.f, 82.4f), module, MS20Filter::CUTOFF_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(61.f, 239.f), module, MS20Filter::GAIN_CV_PARAM, -1.f, 1.f, 0.f)); + + addInput(Port::create(Vec(18, 168.5), Port::INPUT, module, MS20Filter::PEAK_CV_INPUT)); + addInput(Port::create(Vec(18, 81.5), Port::INPUT, module, MS20Filter::CUTOFF_CV_INPUT)); + addInput(Port::create(Vec(18, 239), Port::INPUT, module, MS20Filter::GAIN_CV_INPUT)); + // ***** CV INPUTS ******* + + // ***** INPUTS ********** + addInput(Port::create(Vec(17.999f, 326.05f), Port::INPUT, module, MS20Filter::FILTER_INPUT)); + // ***** INPUTS ********** + + // ***** OUTPUTS ********* + addOutput(Port::create(Vec(58.544f, 326.05f), Port::OUTPUT, module, MS20Filter::FILTER_OUTPUT)); + // ***** OUTPUTS ********* + + // ***** SWITCH ********* + addParam(ParamWidget::create(Vec(119.f, 331.f), module, MS20Filter::MODE_SWITCH_PARAM, 0.0f, 1.0f, 1.0f)); + // ***** SWITCH ********* +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, MS20Filter) { + Model *modelMS20Filter = Model::create("Lindenberg Research", "MS20 VCF", "Valerie MS20 Filter", FILTER_TAG); + return modelMS20Filter; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/ReShaper.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/ReShaper.cpp new file mode 100644 index 00000000..161815cd --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/ReShaper.cpp @@ -0,0 +1,96 @@ +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct ReShaper : Module { + enum ParamIds { + RESHAPER_AMOUNT, + RESHAPER_CV_AMOUNT, + NUM_PARAMS + }; + + enum InputIds { + RESHAPER_INPUT, + RESHAPER_CV_INPUT, + NUM_INPUTS + }; + + enum OutputIds { + RESHAPER_OUTPUT, + NUM_OUTPUTS + }; + + enum LightIds { + NUM_LIGHTS + }; + + + ReShaper() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} + + + void step() override; +}; + + +void ReShaper::step() { + // normalize signal input to [-1.0...+1.0] + float x = clamp(inputs[RESHAPER_INPUT].value * 0.1f, -1.f, 1.f); + float cv = inputs[RESHAPER_CV_INPUT].value * params[RESHAPER_CV_AMOUNT].value; + float a = clamp(params[RESHAPER_AMOUNT].value + cv, 1.f, 50.f); + + // do the acid! + float out = x * (fabs(x) + a) / (x * x + (a - 1) * fabs(x) + 1); + + outputs[RESHAPER_OUTPUT].value = out * 5.0f; +} + + +/** + * @brief Reshaper Panel + */ +struct ReShaperWidget : LRModuleWidget { + ReShaperWidget(ReShaper *module); +}; + + +ReShaperWidget::ReShaperWidget(ReShaper *module) : LRModuleWidget(module) { + // setPanel(SVG::load(assetPlugin(plugin, "res/ReShaper.svg"))); + + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/ReShaper.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + + // ***** MAIN KNOBS ****** + addParam(ParamWidget::create(Vec(32, 216), module, ReShaper::RESHAPER_AMOUNT, 1.f, 50.f, 1.f)); + addParam(ParamWidget::create(Vec(48, 126), module, ReShaper::RESHAPER_CV_AMOUNT, 0.f, 5.f, 0.f)); + // ***** MAIN KNOBS ****** + + + // ***** INPUTS ********** + addInput(Port::create(Vec(21, 60), Port::INPUT, module, ReShaper::RESHAPER_INPUT)); + addInput(Port::create(Vec(71, 60), Port::INPUT, module, ReShaper::RESHAPER_CV_INPUT)); + // ***** INPUTS ********** + + // ***** OUTPUTS ********* + addOutput(Port::create(Vec(46, 320), Port::OUTPUT, module, ReShaper::RESHAPER_OUTPUT)); + // ***** OUTPUTS ********* +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, ReShaper) { + Model *modelReShaper = Model::create("Lindenberg Research", "ReShaper", "ReShaper Wavefolder", WAVESHAPER_TAG); + return modelReShaper; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/Release.h b/plugins/community/repos/LindenbergResearch__ORIG/src/Release.h new file mode 100644 index 00000000..5535e9ab --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/Release.h @@ -0,0 +1,6 @@ +#pragma once + +#define VERSION_MAJOR "0" +#define VERSION_MINOR "6" +#define VERSION_PATCH "0" +#define VERSION_STR "Version " VERSION_MAJOR "." VERSION_MINOR "." VERSION_PATCH diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/SimpleFilter.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/SimpleFilter.cpp new file mode 100644 index 00000000..f099931c --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/SimpleFilter.cpp @@ -0,0 +1,184 @@ +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct SimpleFilter : Module { + enum ParamIds { + CUTOFF_PARAM, + RESONANCE_PARAM, + CUTOFF_CV_PARAM, + RESONANCE_CV_PARAM, + NUM_PARAMS + }; + enum InputIds { + FILTER_INPUT, + CUTOFF_CV_INPUT, + RESONANCE_CV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + FILTER_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + float f, p, q; + float b0, b1, b2, b3, b4; + float t1, t2; + float frequency, resonance, in; + + + SimpleFilter() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { + f = 0; + p = 0; + q = 0; + b0 = 0; + b1 = 0; + b2 = 0; + b3 = 0; + b4 = 0; + t1 = 0; + t2 = 0; + frequency = 0; + resonance = 0; + in = 0; + } + + + 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 + // - reset, randomize: implements special behavior when user clicks these from the context menu +}; + + +float clip(float in, float level) { + // clipping high + if (in > level) { + in = level; + } + + // clipping low + if (in < -level) { + in = -level; + } + + return in; +} + + +void SimpleFilter::step() { + // Moog 24 dB/oct resonant lowpass VCF + // References: CSound source code, Stilson/Smith CCRMA paper. + // Modified by paul.kellett@maxim.abel.co.uk July 2000 + // + // Adapted for VCV Rack by Lindenberg Research + // http://musicdsp.org/showArchiveComment.php?ArchiveID=25 + + // calculate CV inputs + float cutoffCVValue = (inputs[CUTOFF_CV_INPUT].value * 0.05f * params[CUTOFF_CV_PARAM].value); + float resonanceCVValue = (inputs[RESONANCE_CV_INPUT].value * 0.1f * params[RESONANCE_CV_PARAM].value); + + // translate frequency to logarithmic scale + float freqHz = 20.f * powf(1000.f, params[CUTOFF_PARAM].value + cutoffCVValue); + frequency = clip(freqHz * (1.f / (engineGetSampleRate() / 2.0f)), 1.f); + resonance = clip(params[RESONANCE_PARAM].value + resonanceCVValue, 1.f); + + // normalize signal input to [-1.0...+1.0] + // filter starts to be very unstable for input gain above 1.f and below 0.f + in = clip(inputs[FILTER_INPUT].value * 0.1f, 1.0f); + + // Set coefficients given frequency & resonance [0.0...1.0] + q = 1.0f - frequency; + p = frequency + 0.8f * frequency * q; + f = p + p - 1.0f; + q = resonance * (1.0f + 0.5f * q * (1.0f - q + 5.6f * q * q)); + + + in -= q * b4; + + t1 = b1; + b1 = (in + b0) * p - b1 * f; + + t2 = b2; + b2 = (b1 + t1) * p - b2 * f; + + t1 = b3; + b3 = (b2 + t2) * p - b3 * f; + + b4 = (b3 + t1) * p - b4 * f; + + b4 = b4 - b4 * b4 * b4 * 0.166666667f; + b0 = in; + + // Lowpass output: b4 + // Highpass output: in - b4; + // Bandpass output: 3.0f * (b3 - b4); + + + // scale normalized output back to +/-5V + outputs[FILTER_OUTPUT].value = clip(b4, 1.0f) * 5.0f; +} + + +/** + * @brief Recover of old filer + */ +struct SimpleFilterWidget : LRModuleWidget { + SimpleFilterWidget(SimpleFilter *module); +}; + + +SimpleFilterWidget::SimpleFilterWidget(SimpleFilter *module) : LRModuleWidget(module) { + //setPanel(SVG::load(assetPlugin(plugin, "res/SimpleFilter.svg"))); + + panel = new LRPanel(); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/SimpleFilter.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + // ***** MAIN KNOBS ****** + addParam(ParamWidget::create(Vec(75 - 28, 167), module, SimpleFilter::CUTOFF_PARAM, 0.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(75 - 21, 252), module, SimpleFilter::RESONANCE_PARAM, -0.f, 1.f, 0.0f)); + // ***** MAIN KNOBS ****** + + // ***** CV INPUTS ******* + addParam(ParamWidget::create(Vec(39 - 12, 120), module, SimpleFilter::CUTOFF_CV_PARAM, 0.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(111 - 12, 120), module, SimpleFilter::RESONANCE_CV_PARAM, 0.f, 1.f, 0.f)); + + addInput(Port::create(Vec(39 - 14, 60), Port::INPUT, module, SimpleFilter::CUTOFF_CV_INPUT)); + addInput(Port::create(Vec(111 - 14, 60), Port::INPUT, module, SimpleFilter::RESONANCE_CV_INPUT)); + // ***** CV INPUTS ******* + + // ***** INPUTS ********** + addInput(Port::create(Vec(39 - 14, 320), Port::INPUT, module, SimpleFilter::FILTER_INPUT)); + // ***** INPUTS ********** + + // ***** OUTPUTS ********* + addOutput(Port::create(Vec(111 - 14, 320), Port::OUTPUT, module, SimpleFilter::FILTER_OUTPUT)); + // ***** OUTPUTS ********* +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, SimpleFilter) { + Model *modelSimpleFilter = Model::create("Lindenberg Research", "LPFilter24dB", "24dB Lowpass Filter", + FILTER_TAG); + return modelSimpleFilter; +} + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/VCO.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/VCO.cpp new file mode 100644 index 00000000..c1d3d545 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/VCO.cpp @@ -0,0 +1,146 @@ +#include "dsp/Oscillator.hpp" +#include "LindenbergResearch.hpp" + +namespace rack_plugin_LindenbergResearch { + +struct VCO : LRModule { + enum ParamIds { + FREQUENCY_PARAM, + OCTAVE_PARAM, + FM_CV_PARAM, + SHAPE_CV_PARAM, + PW_CV_PARAM, + SHAPE_PARAM, + PW_PARAM, + NUM_PARAMS + }; + enum InputIds { + VOCT_INPUT, + FM_CV_INPUT, + PW_CV_INPUT, + SHAPE_CV_INPUT, + NUM_INPUTS + }; + enum OutputIds { + SAW_OUTPUT, + PULSE_OUTPUT, + SINE_OUTPUT, + TRI_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + dsp::BLITOscillator *osc = new dsp::BLITOscillator(); + LCDWidget *label1 = new LCDWidget(COLOR_CYAN, 6); + + VCO() : LRModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} + + + void step() override; +}; + + +void VCO::step() { + LRModule::step(); + + float fm = clamp(inputs[FM_CV_INPUT].value, -10.f, 10.f) * 400.f * quadraticBipolar(params[FM_CV_PARAM].value); + + osc->updatePitch(inputs[VOCT_INPUT].value, clamp(fm, -10000.f, 10000.f), params[FREQUENCY_PARAM].value, params[OCTAVE_PARAM].value); + + float shape = quadraticBipolar(params[SHAPE_PARAM].value); + float pw = params[PW_CV_PARAM].value; + + if (osc->shape != shape) { + osc->setShape(shape); + } + + if (osc->pw != pw) { + osc->setPulseWidth(pw); + } + + osc->proccess(); + + outputs[SAW_OUTPUT].value = osc->saw; + + outputs[PULSE_OUTPUT].value = osc->pulse; + outputs[SINE_OUTPUT].value = osc->sine; + + outputs[TRI_OUTPUT].value = osc->tri; + + if (cnt % 1200 == 0) { + label1->text = stringf("%.2f Hz", osc->getFrequency()); + } +} + + +/** + * @brief Woldemar VCO + */ +struct VCOWidget : LRModuleWidget { + VCOWidget(VCO *module); +}; + + +VCOWidget::VCOWidget(VCO *module) : LRModuleWidget(module) { + // setPanel(SVG::load(assetPlugin(plugin, "res/VCO.svg"))); + + panel = new LRPanel(20,40); + panel->setBackground(SVG::load(assetPlugin(plugin, "res/VCO.svg"))); + addChild(panel); + + box.size = panel->box.size; + + // ***** SCREWS ********** + addChild(Widget::create(Vec(15, 1))); + addChild(Widget::create(Vec(box.size.x - 30, 1))); + addChild(Widget::create(Vec(15, 366))); + addChild(Widget::create(Vec(box.size.x - 30, 366))); + // ***** SCREWS ********** + + + // ***** MAIN KNOBS ****** + addParam(ParamWidget::create(Vec(83, 172.0), module, VCO::FREQUENCY_PARAM, -15.f, 15.f, 0.f)); + addParam(ParamWidget::create(Vec(85, 240), module, VCO::OCTAVE_PARAM, -3.f, 3.f, 0.f)); + + addParam(ParamWidget::create(Vec(118, 111.5), module, VCO::PW_PARAM, -.1f, 1.f, 1.f)); + addParam(ParamWidget::create(Vec(65, 60), module, VCO::SHAPE_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(15, 267), module, VCO::FM_CV_PARAM, -1.f, 1.f, 0.f)); + addParam(ParamWidget::create(Vec(65, 111.5), module, VCO::PW_CV_PARAM, 0.02f, 1.f, 1.f)); + addParam(ParamWidget::create(Vec(118, 59), module, VCO::SHAPE_PARAM, 1.f, 5.f, 1.f)); + + + // ***** MAIN KNOBS ****** + + + // ***** INPUTS ********** + addInput(Port::create(Vec(15, 182), Port::INPUT, module, VCO::VOCT_INPUT)); + addInput(Port::create(Vec(15, 228), Port::INPUT, module, VCO::FM_CV_INPUT)); + addInput(Port::create(Vec(15, 112), Port::INPUT, module, VCO::PW_CV_INPUT)); + addInput(Port::create(Vec(15, 60), Port::INPUT, module, VCO::SHAPE_CV_INPUT)); + + // addInput(createInput(Vec(71, 60), module, VCO::RESHAPER_CV_INPUT)); + // ***** INPUTS ********** + + // ***** OUTPUTS ********* + // addOutput(createOutput(Vec(20, 320), module, VCO::SAW_OUTPUT)); + addOutput(Port::create(Vec(20.8, 304.5), Port::OUTPUT, module, VCO::SAW_OUTPUT)); + addOutput(Port::create(Vec(57.2, 304.5), Port::OUTPUT, module, VCO::PULSE_OUTPUT)); + addOutput(Port::create(Vec(96.1, 304.5), Port::OUTPUT, module, VCO::SINE_OUTPUT)); + addOutput(Port::create(Vec(132, 304.5), Port::OUTPUT, module, VCO::TRI_OUTPUT)); + // ***** OUTPUTS ********* + + module->label1->box.pos = Vec(30,110); + + addChild(module->label1); +} + +} // namespace rack_plugin_LindenbergResearch + +using namespace rack_plugin_LindenbergResearch; + +RACK_PLUGIN_MODEL_INIT(LindenbergResearch, VCO) { + Model *modelVCO = Model::create("Lindenberg Research", "VCO", "Voltage Controlled Oscillator", OSCILLATOR_TAG); + return modelVCO; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPEffect.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPEffect.hpp new file mode 100644 index 00000000..6bc283a1 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPEffect.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace rack { + + /** + * @brief Base class for all signal processors + */ + struct DSPEffect { + + /** + * @brief Method for mark parameters as invalidate to trigger recalculation + */ + virtual void invalidate() {}; + + + /** + * @brief Process one step and return the computed sample + * @return + */ + virtual void process() {}; + }; + +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPMath.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPMath.cpp new file mode 100644 index 00000000..3a56c6ee --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPMath.cpp @@ -0,0 +1,185 @@ +#include "DSPMath.hpp" + +/** + * @brief Clip signal at bottom by value + * @param in Sample input + * @param clip Clipping value + * @return Clipped sample + */ +float clipl(float in, float clip) { + if (in < clip) return clip; + else return in; +} + + +/** + * @brief Clip signal at top by value + * @param in Sample input + * @param clip Clipping value + * @return Clipped sample + */ +float cliph(float in, float clip) { + if (in > clip) return clip; + else return in; +} + + +/** + * @brief Wrap input number between -PI..PI + * @param n Input number + * @return Wrapped value + */ +float wrapTWOPI(float n) { + float b = 1.f / TWOPI * n; + return (b - lround(b)) * TWOPI; +} + + +/** + * @brief Get PLL increment depending on frequency + * @param frq Frequency + * @return PLL increment + */ +float getPhaseIncrement(float frq) { + return TWOPI * frq / engineGetSampleRate(); +} + + +/** + * @brief Actual BLIT core computation + * @param N Harmonics + * @param phase Current phase value + * @return + */ +float BLITcore(float N, float phase) { + float a = wrapTWOPI((clipl(N - 1, 0.f) + 0.5f) * phase); + float x = fastSin(a) * 1.f / fastSin(0.5f * phase); + return (x - 1.f) * 2.f; +} + + +/** + * @brief BLIT generator based on current phase + * @param N Harmonics + * @param phase Current phase of PLL + * @return + */ +float BLIT(float N, float phase) { + if (phase == 0.f) return 1.f; + else return BLITcore(N, phase); +} + + +/** + * @brief Add value to integrator + * @param x Input + * @param Fn + * @return + */ +float Integrator::add(float x, float Fn) { + value = (x - value) * (d * Fn) + value; + return value; +} + + +/** + * @brief Filter function for DC block + * @param x Input sample + * @return Filtered sample + */ +float DCBlocker::filter(float x) { + float y = x - xm1 + R * ym1; + xm1 = x; + ym1 = y; + + return y; +} + + +/** + * @brief Filter function for simple 6dB lowpass filter + * @param x Input sample + * @return + */ +float LP6DBFilter::filter(float x) { + float y = y0 + (alpha * (x - y0)); + y0 = y; + + return y; +} + + +/** + * @brief Update filter parameter + * @param fc Cutoff frequency + */ +void LP6DBFilter::updateFrequency(float fc, int factor) { + LP6DBFilter::fc = fc; + RC = 1.f / (LP6DBFilter::fc * TWOPI); + dt = 1.f / engineGetSampleRate() * factor; + alpha = dt / (RC + dt); +} + + +/** + * @brief Shaper type 1 (Saturate) + * @param a Amount from 0 - x + * @param x Input sample + * @return + */ +float shape1(float a, float x) { + float k = 2 * a / (1 - a); + float b = (1 + k) * (x * 0.5f) / (1 + k * fabsf(x * 0.5f)); + + return b * 4; +} + + +/** + * @brief Waveshaper as used in ReShaper. Input should be in the range -1..+1 + * @param a Shaping factor + * @param x Input sample + * @return + */ +float shape2(float a, float x) { + return atanf(x * a);//x * (fabs(x) + a) / (x * x + (a - 1) * fabs(x) + 1); +} + +/** + * @brief Soft saturating with a clip of a. Works only with positive values so use 'b' as helper here. + * @param x Input sample + * @param a Saturating threshold + * @return + */ +double saturate(double x, double a) { + double b = 1; + + /* turn negative values positive and remind in b as coefficient */ + if (x < 0) { + b = -1; + x *= -1; + } + + // nothing to do + if (x <= a) return x * b; + + double d = (a + (x - a) / (1 + pow((x - a) / (1 - a), 2))); + + if (d > 1) { + return (a + 1) / 2 * b; + } else { + return d * b; + } +} + + +/** + * @brief + * @param input + * @return + */ +double overdrive(double input) { + const double x = input * 0.686306; + const double a = 1 + exp(sqrt(fabs(x)) * -0.75); + return (exp(x) - exp(-x * a)) / (exp(x) + exp(-x)); +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPMath.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPMath.hpp new file mode 100644 index 00000000..ce6d8f6e --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPMath.hpp @@ -0,0 +1,314 @@ +#pragma once + +#include +#include +#include "rack.hpp" +#include "dsp/decimator.hpp" + +using namespace rack; + +const static float TWOPI = (float) M_PI * 2; + + +/** + * @brief Basic leaky integrator + */ +struct Integrator { + float d = 0.25f; + float value = 0.f; + + /** + * @brief Add value to integrator + * @param x Input sample + * @param Fn + * @return Current integrator state + */ + float add(float x, float Fn); +}; + + +/** + * @brief Filter out DC offset / 1-Pole HP Filter + */ +struct DCBlocker { + const float R = 0.999; + float xm1 = 0.f, ym1 = 0.f; + + /** + * @brief Filter signal + * @param x Input sample + * @return Filtered output + */ + float filter(float x); +}; + + +/** + * @brief Simple 6dB lowpass filter + */ +struct LP6DBFilter { +private: + float RC; + float dt; + float alpha; + float y0; + float fc; + +public: + + /** + * @brief Create a new filter with a given cutoff frequency + * @param fc cutoff frequency + * @param factor Oversampling factor + */ + LP6DBFilter(float fc, int factor) { + updateFrequency(fc, factor); + y0 = 0.f; + } + + + /** + * @brief Set new cutoff frequency + * @param fc cutoff frequency + */ + void updateFrequency(float fc, int factor); + + /** + * @brief Filter signal + * @param x Input sample + * @return Filtered output + */ + float filter(float x); +}; + + +/** + * @brief Simple noise generator + */ +struct Noise { + + Noise() { + + } + + + float nextFloat(float gain) { + static std::default_random_engine e; + static std::uniform_real_distribution<> dis(0, 1); // rage 0 - 1 + return (float) dis(e) * gain; + } +}; + + +/** + * @brief Simple oversampling class + */ +template +struct OverSampler { + + struct Vector { + float y0, y1; + }; + + Vector y[CHANNELS] = {}; + float up[CHANNELS][OVERSAMPLE] = {}; + float data[CHANNELS][OVERSAMPLE] = {}; + Decimator decimator[CHANNELS]; + int factor = OVERSAMPLE; + + + /** + * @brief Constructor + * @param factor Oversampling factor + */ + OverSampler() {} + + + /** + * @brief Return linear interpolated position + * @param point Point in oversampled data + * @return + */ + float interpolate(int channel, int point) { + return y[channel].y0 + (point / factor) * (y[channel].y1 - y[channel].y0); + } + + + /** + * @brief Create up-sampled data out of two basic values + */ + void doUpsample(int channel) { + for (int i = 0; i < factor; i++) { + up[channel][i] = interpolate(channel, i + 1); + } + } + + + /** + * @brief Downsample data from a given channel + * @param channel Channel to proccess + * @return Downsampled point + */ + float getDownsampled(int channel) { + return decimator[channel].process(data[channel]); + } + + + /** + * @brief Step to next sample point + * @param y Next sample point + */ + void next(int channel, float n) { + y[channel].y0 = y[channel].y1; + y[channel].y1 = n; + } +}; + + +/** + * @brief Fast sin approximation + * @param angle Angle + * @return App. value + */ +inline float fastSin(float angle) { + float sqr = angle * angle; + float result = -2.39e-08f; + result *= sqr; + result += 2.7526e-06f; + result *= sqr; + result -= 1.98409e-04f; + result *= sqr; + result += 8.3333315e-03f; + result *= sqr; + result -= 1.666666664e-01f; + result *= sqr; + result += 1.0f; + result *= angle; + return result; +} + + +float wrapTWOPI(float n); + +float getPhaseIncrement(float frq); + +float clipl(float in, float clip); + +float cliph(float in, float clip); + +float BLIT(float N, float phase); + +float shape1(float a, float x); + +double saturate(double x, double a); + +double overdrive(double input); + +float shape2(float a, float x); + + +/** + * @brief Double version of clamp + * @param x + * @param min + * @param max + * @return + */ +inline double clampd(double x, double min, double max) { + return fmax(fmin(x, max), min); +} + + +/** + * @brief Soft clipping + * @param x + * @param sat + * @param satinv + * @return + */ +inline float clip(float x, float sat, float satinv) { + float v2 = (x * satinv > 1 ? 1 : + (x * satinv < -1 ? -1 : + x * satinv)); + return (sat * (v2 - (1.f / 3.f) * v2 * v2 * v2)); +} + + +/** + * @brief Clamp without branching + * @param input Input sample + * @return + */ +inline double saturate2(double input) { //clamp without branching + const double _limit = 0.3; + double x1 = fabs(input + _limit); + double x2 = fabs(input - _limit); + return 0.5 * (x1 - x2); +} + + +/** + * @brief Fast arctan approximation, corresponds to tanhf() but decreases y to infinity + * @param x + * @return + */ +inline float fastatan(float x) { + return (x / (1.0f + 0.28f * (x * x))); +} + + +/** + * @brief Linear fade of two points + * @param a Point 1 + * @param b Point 2 + * @param n Fade value + * @return + */ +inline float fade2(float a, float b, float n) { + return (1 - n) * a + n * b; +} + + +/** + * @brief Linear fade of five points + * @param a Point 1 + * @param b Point 2 + * @param c Point 3 + * @param d Point 4 + * @param e Point 5 + * @param n Fade value + * @return + */ +inline float fade5(float a, float b, float c, float d, float e, float n) { + if (n >= 0 && n < 1) { + return fade2(a, b, n); + } else if (n >= 1 && n < 2) { + return fade2(b, c, n - 1); + } else if (n >= 2 && n < 3) { + return fade2(c, d, n - 2); + } else if (n >= 3 && n < 4) { + return fade2(d, e, n - 3); + } + + return e; +} + + +/** +* @brief Shapes between 0..1 with a log type courve +* @param x +* @return +*/ +inline float cubicShape(float x) { + return (x - 1.f) * (x - 1.f) * (x - 1.f) + 1.f; +} + + +/** + * @brief ArcTan like shaper for foldback distortion + * @param x + * @return + */ +inline float atanShaper(float x) { + return x / (1.f + (0.28f * x * x)); +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPSystem.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPSystem.hpp new file mode 100644 index 00000000..c08b1b29 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/DSPSystem.hpp @@ -0,0 +1,310 @@ +#pragma once +#define DEFAULT_SR 44100.0f +#define TRIGGER_PROCESSING true + +/** + * @brief Basic DSP types + */ + +namespace dsp { + + /** + * @brief Represents an IOPort of a DSP system + */ + struct DSPPort { + float value; + }; + + + /** + * @brief Represents a parameter of a DSP system + */ + struct DSPParam { + float value; + }; + + + /** + * @brief Base class for all signal processors + */ + template + struct DSPSystem { + + /** + * @brief Enumerate all Inputs + */ + enum Inputs { + }; + + /** + * @brief Enumerate all Outputs + */ + enum Outputs { + }; + + /** + * @brief Enumerate all Parameters + */ + enum Params { + }; + + protected: +#ifdef _MSC_VER + DSPPort input[NUM_IN]; + DSPPort output[NUM_OUT]; + DSPParam param[NUM_PARAM + 1]; +#else + DSPPort input[NUM_IN] = {}; + DSPPort output[NUM_OUT] = {}; + DSPParam param[NUM_PARAM] = {}; +#endif + + float sr; + + public: + + /** + * @brief Default constructor + */ + DSPSystem() { + sr = DEFAULT_SR; + } + + + /** + * @brief Init system with sample rate + * @param sr + */ + explicit DSPSystem(float sr) : sr(sr) {} + + + /** + * @brief Update sample rate on change + * @param sr + */ + void updateSampleRate(float sr) { + DSPSystem::sr = sr; + invalidate(); + } + + + /** + * @brief Update a parameter of the system + * @param id Parameter ID + * @param value Value + * @param trigger Trigger call of invalidate() - use false to supress + */ + void setParam(int id, float value, bool trigger = true) { + if (param[id].value != value) { + param[id].value = value; + + /* setup of new parameter triggers invalidation per default */ + if (trigger) { + invalidate(); + } + } + + } + + + /** + * @brief Get the current parameter value by ID + * @param id Parameter ID + * @return current value + */ + float getParam(int id) { + return param[id].value; + } + + + /** + * @brief Get the current value of the output by ID + * @param id + * @return + */ + float getOutput(int id) { + return output[id].value; + } + + + /** + * @brief Set input port to new value + * @param id Port ID + * @param value + */ + void setInput(int id, float value, bool proccess = false) { + input[id].value = value; + + if (proccess) { + process(); + } + } + + + /** + * @brief Get current output value + * @param id Output ID + * @return + */ + float getOut(int id) { + return output[id].value; + } + + + /** + * @brief Method for mark parameters as invalidate to trigger recalculation + */ + virtual void invalidate() {}; + + + /** + * @brief Process one step and return the computed sample + * @return + */ + virtual void process() {}; + }; + + + /** + * @brief Basic 1 in and 1 out system definition + */ + struct DSPSystem1x1 : DSPSystem<1, 1, 0> { + enum Inputs { + IN + }; + + enum Outputs { + OUT + }; + + + /** + * @brief Get the delayed sample from signal, to processing are triggered + * @return + */ + float get() { + return output[OUT].value; + } + + + /** + * @brief Set new value to Input and trigger processing + * @param value + */ + void set(float value) { + setInput(IN, value, TRIGGER_PROCESSING); + } + }; + + +/** + * @brief Basic 1 in and 1 out system definition + */ + struct DSPSystem2x1 : DSPSystem<2, 1, 0> { + enum Inputs { + IN1, + IN2 + }; + + enum Outputs { + OUT + }; + + + /** + * @brief Get the delayed sample from signal, to processing are triggered + * @return + */ + float get() { + return output[OUT].value; + } + + + /** + * @brief Set new value to Input and trigger processing + * @param value + */ + void set(float in1, float in2, bool proccess = true) { + setInput(IN1, in1); + setInput(IN2, in2, proccess); + + } + }; + + +/** + * @brief Basic 1 in and 1 out system definition + */ + struct DSPSystem2x2 : DSPSystem<2, 2, 0> { + enum Inputs { + IN1, + IN2 + }; + + enum Outputs { + OUT1, + OUT2 + }; + + + /** + * @brief Get the delayed sample from signal, to processing are triggered + * @return + */ + float get(int out = 0) { + return output[out].value; + } + + + /** + * @brief Set new value to Input and trigger processing + * @param value + */ + void set(float in1, float in2, bool process = true) { + setInput(IN1, in1); + setInput(IN2, in2, process); + + } + }; + /** + * @brief Delayed signal model + * @tparam SIZE + */ + template + struct DSPDelay : DSPSystem1x1 { + + private: + float buffer[SIZE] = {}; + + + /** + * @brief Shift left all elements + */ + void shift() { + for (int i = 0; i < SIZE - 1; i++) { + buffer[i] = buffer[i + 1]; + } + } + + + public: + + /** + * @brief Proccess the Delay + */ + void process() override { + /* shift all elements left */ + shift(); + /* set last element to current input */ + buffer[SIZE - 1] = input[IN].value; + /* set output */ + output[OUT].value = buffer[0]; + } + }; + + + /** + * @brief Shortcut for a classic z^-1 delay (1-Sample) + */ + typedef DSPDelay<1> DSPDelay1; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/LadderFilter.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/LadderFilter.cpp new file mode 100644 index 00000000..be4e3321 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/LadderFilter.cpp @@ -0,0 +1,217 @@ +#include "LadderFilter.hpp" + +using namespace rack; + + +/** + * @brief Constructor + */ +LadderFilter::LadderFilter() {} + + +/** + * @brief Check parameter + */ +void LadderFilter::invalidate() { + // Set coefficients given frequency & resonance [0.0...1.0] + q = 1.0f - freqExp; + p = freqExp + 0.8f * freqExp * q; + f = p + p - 1.0f; + q = resExp * (1.0f + 0.5f * q * (1.0f - q + 5.6f * q * q)); +} + + +/** + * @brief Calculate new sample + * @return + */ +void LadderFilter::process() { + os.next(LOWPASS, in); + os.doUpsample(LOWPASS); + + for (int i = 0; i < os.factor; i++) { + float x = os.up[LOWPASS][i]; + + // non linear feedback with nice saturation + x -= fastatan(bx * q); + + t1 = b1; + b1 = ((x + b0) * p - b1 * f); + + t2 = b2; + b2 = ((b1 + t1) * p - b2 * f); + + t1 = b3; + b3 = ((b2 + t2) * p - b3 * f); + + t2 = b4; + b4 = ((b3 + t1) * p - b4 * f); + + b5 = ((b4 + t2) * p - b5 * f); + + // fade over filter poles from 3dB/oct (1P) => 48dB/oct (5P) + bx = fade5(b1, b2, b3, b4, b5, slope); + + // saturate and add very low noise to have self oscillation with no input and high res + b0 = fastatan(x + noise.nextFloat(NOISE_GAIN)); + + float y = bx * (1 + drive * 40); + + if (fabs(y) > 1) { + lightValue = (lightValue + fabs(y) / 5) / 2; + } else { + lightValue *= 0.99; + } + + + // overdrive with fast atan, which folds back the waves at high input and creates a noisy bright sound + os.data[LOWPASS][i] = fastatan(y); + } + + lpOut = os.getDownsampled(LOWPASS) * (INPUT_GAIN / (drive * 20 + 1) * (quadraticBipolar(drive * 3) + 1)); +} + + +/** + * @brief Return cutoff frequency in the range of 0..1 + * @return + */ +float LadderFilter::getFrequency() const { + return frequency; +} + + +/** + * @brief Update cutoff frequency in the range of 0..1 + * @param frequency + */ +void LadderFilter::setFrequency(float frequency) { + if (LadderFilter::frequency != frequency) { + LadderFilter::frequency = frequency; + // translate frequency to logarithmic scale + freqHz = 20.f * powf(1000.f, frequency); + freqExp = clamp(freqHz * (1.f / (engineGetSampleRate() * OVERSAMPLE / 2.f)), 0.f, 1.f); + + updateResExp(); + invalidate(); + } +} + + +/** + * @brief Update resonance factor + */ +void LadderFilter::updateResExp() { + resExp = clamp(resonance, 0.f, 1.5f); +} + + +/** + * @brief Get resonance + * @return + */ +float LadderFilter::getResonance() const { + return resExp; +} + + +/** + * @brief Set resonance + * @param resonance + */ +void LadderFilter::setResonance(float resonance) { + if (LadderFilter::resonance != resonance) { + LadderFilter::resonance = resonance; + + updateResExp(); + invalidate(); + } +} + + +/** + * @brief Get overdrive + * @return + */ +float LadderFilter::getDrive() const { + return drive; +} + + +/** + * @brief Set overdrive + * @param drive + */ +void LadderFilter::setDrive(float drive) { + if (LadderFilter::drive != drive) { + LadderFilter::drive = clamp(drive, 0.f, 1.f); + + updateResExp(); + invalidate(); + } +} + + +/** + * @brief Set input channel with sample + * @param in + */ +void LadderFilter::setIn(float in) { + float x = clamp(in / INPUT_GAIN, -0.8f, 0.8f); + + LadderFilter::in = x; +} + + +/** + * @brief Get lowpass output + * @return + */ +float LadderFilter::getLpOut() { + return lpOut; +} + + +/** + * @brief Get frequency of cutoff in Hz + * @return + */ +float LadderFilter::getFreqHz() const { + return freqHz; +} + + +/** + * @brief Get filter slope + * @return + */ +float LadderFilter::getSlope() const { + return slope; +} + + +/** + * @brief Set filter slope + * @param slope + */ +void LadderFilter::setSlope(float slope) { + LadderFilter::slope = clamp(slope, 0.f, 4.f); +} + + +/** + * @brief Get the current light value for overload + * @return + */ +float LadderFilter::getLightValue() const { + return lightValue; +} + + +/** + * @brief Set value for overload + * @param lightValue + */ +void LadderFilter::setLightValue(float lightValue) { + LadderFilter::lightValue = lightValue; +} diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/LadderFilter.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/LadderFilter.hpp new file mode 100644 index 00000000..a6ceb723 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/LadderFilter.hpp @@ -0,0 +1,55 @@ +#pragma once + + +#include "DSPEffect.hpp" +#include "engine.hpp" +#include "DSPMath.hpp" + +namespace rack { + + struct LadderFilter : DSPEffect { + + static const int OVERSAMPLE = 8; // factor of internal oversampling + static constexpr float NOISE_GAIN = 10e-10f; // internal noise gain used for self-oscillation + static constexpr float INPUT_GAIN = 20.f; // input level + + enum FXChannel { + LOWPASS + }; + + private: + float f, p, q; + float b0, b1, b2, b3, b4, b5, bx; + float t1, t2; + float freqExp, freqHz, frequency, resExp, resonance, drive, slope; + float in, lpOut; + float lightValue; + + OverSampler os; + Noise noise; + + void updateResExp(); + + public: + LadderFilter(); + + void invalidate() override; + + void process() override; + + float getFrequency() const; + void setFrequency(float frequency); + float getResonance() const; + void setResonance(float resonance); + float getDrive() const; + void setDrive(float drive); + float getFreqHz() const; + + float getSlope() const; + void setSlope(float slope); + void setIn(float in); + float getLpOut(); + float getLightValue() const; + void setLightValue(float lightValue); + }; +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/MS20zdf.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/MS20zdf.cpp new file mode 100644 index 00000000..aa5ca67f --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/MS20zdf.cpp @@ -0,0 +1,67 @@ +#include "MS20zdf.hpp" + +using namespace dsp; + + +/** + * @brief Calculate prewarped vars on parameter change + */ +void MS20zdf::invalidate() { + // translate frequency to logarithmic scale + // freqHz = 20.f * powf(860.f, param[FREQUENCY].value) - 20.f; + freqHz = 20.f * powf(950.f, param[FREQUENCY].value) - 20.f; + + b = tanf(freqHz * (float) M_PI / sr / OVERSAMPLE); + g = b / (1 + b); + + /* use shifted negative cubic shape for logarithmic like shaping of the peak parameter */ + k = 2.f * cubicShape(param[PEAK].value) * 1.0001f; + g2 = g * g; +} + + +/** + * @brief Proccess one sample of filter + */ +void MS20zdf::process() { + os.next(IN, input[IN].value); + os.doUpsample(IN); + + float s1, s2; + float gain = quadraticBipolar(param[DRIVE].value) * DRIVE_GAIN + 1.f; + float type = param[TYPE].value; + float x = 0; + + for (int i = 0; i < os.factor; i++) { + x = os.up[IN][i]; + + zdf1.set(x - ky, g); + s1 = zdf1.s; + + zdf2.set(zdf1.y + ky, g); + s2 = zdf2.s; + + y = 1.f / (g2 * k - g * k + 1.f) * (g2 * x + g * s1 + s2); + + ky = k * atanf(y / 70.f) * 70.f; + + if (type > 0) { + os.data[IN][i] = atanShaper(gain * y / 10.f) * 10.f; + } else { + os.data[IN][i] = atanf(gain * y / 10.f) * 10.f; + + } + } + + float out = os.getDownsampled(IN); + + output[OUT].value = out; +} + + +/** + * @brief Inherit constructor + * @param sr sample rate + */ +MS20zdf::MS20zdf(float sr) : DSPSystem(sr) {} + diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/MS20zdf.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/MS20zdf.hpp new file mode 100644 index 00000000..319298f1 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/MS20zdf.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include "DSPSystem.hpp" +#include "DSPMath.hpp" + +namespace dsp { + + + /** + * @brief + */ + struct MS20TPT : DSPSystem2x1 { + float s = 0; + DSPDelay1 z; + + + void process() override { + float gx = input[IN1].value * input[IN2].value; + + z.set(gx + z.get() + gx); + s = z.get(); + } + }; + + + /** + * @brief Zero Delay Feedback + */ + struct MS20ZDF : DSPSystem2x2 { + float y = 0; + float s = 0; + MS20TPT tpt; + + + void process() override { + y = input[IN1].value * input[IN2].value + s; + + tpt.set(input[IN1].value - y, input[IN2].value); + s = tpt.s; + } + + }; + + + /** + * @brief MS20 Filter class + */ + struct MS20zdf : DSPSystem<1, 2, 4> { + static const int OVERSAMPLE = 8; // factor of internal oversampling + static constexpr float DRIVE_GAIN = 20.f; // max drive gain + + enum Inputs { + IN + }; + + enum Params { + FREQUENCY, + PEAK, + DRIVE, + TYPE + }; + + enum Outputs { + OUT, + }; + + private: + float g = 0, g2 = 0, b = 0, k = 0; + float ky = 0, y = 0; + float freqHz = 0; + + MS20ZDF zdf1, zdf2; + OverSampler os; + + public: + explicit MS20zdf(float sr); + + + float getFrequency() { + return getParam(FREQUENCY); + } + + + float getFrequencyHz() const { + return freqHz; + } + + + void setFrequency(float value) { + setParam(FREQUENCY, clamp(value, 0.f, 1.1f)); + } + + + void setDrive(float value) { + setParam(DRIVE, clamp(value, 0.f, 1.1f)); + } + + + float getDrive() { + return getParam(DRIVE); + } + + + float getPeak() { + return getParam(PEAK); + } + + + void setPeak(float value) { + setParam(PEAK, clamp(value, 0.f, 1.1f)); + } + + + void setIn(float value) { + setInput(IN, value); + } + + + float getLPOut() { + return getOutput(OUT); + } + + + float getType() { + return getParam(TYPE); + } + + + void setType(float value) { + setParam(TYPE, value); + } + + void invalidate() override; + void process() override; + }; + + +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/Oscillator.cpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/Oscillator.cpp new file mode 100644 index 00000000..05489d15 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/Oscillator.cpp @@ -0,0 +1,255 @@ +#include "DSPMath.hpp" +#include "Oscillator.hpp" + +using namespace dsp; + + +/** + * @brief Set oscillator state back + */ +void BLITOscillator::reset() { + freq = 0.f; + pw = 1.f; + phase = 0.f; + incr = 0.f; + shape = 1.f; + detune = noise.nextFloat(0.32); + drift = 0.f; + warmup = 0.f; + + saw = 0.f; + ramp = 0.f; + pulse = 0.f; + sine = 0.f; + tri = 0.f; + + shape = 1.f; + n = 0; + + _cv = 0.f; + _oct = 0.f; + + _base = 1.f; + _coeff = 1.f; + _tune = 0.f; + _biqufm = 0.f; + + /* force recalculation of variables */ + setFrequency(NOTE_C4); +} + + +/** + * @brief Default constructor + */ +BLITOscillator::BLITOscillator() { + reset(); +} + + +/** + * @brief Default destructor + */ +BLITOscillator::~BLITOscillator() {} + + +/** + * @brief Get current frequency + * @return + */ +float BLITOscillator::getFrequency() const { + return freq; +} + + +/** + * @brief Set frequency + * @param freq + */ +void BLITOscillator::setFrequency(float freq) { + /* just set if frequency differs from old value */ + if (BLITOscillator::freq != freq) { + BLITOscillator::freq = freq; + + /* force recalculation of variables */ + invalidate(); + } +} + + +/** + * @brief Get current pulse-width + * @return + */ +float BLITOscillator::getPulseWidth() const { + return pw; +} + + +/** + * @brief Set current pulse-width + * @param pw + */ +void dsp::BLITOscillator::setPulseWidth(float pw) { + if (pw < 0.1f) { + BLITOscillator::pw = 0.1f; + return; + } + + if (pw > 1.f) { + BLITOscillator::pw = 1.f; + return; + } + + BLITOscillator::pw = pw; + + /* force recalculation of variables */ + invalidate(); +} + + +/** + * @brief Ramp waveform current + * @return + */ +float BLITOscillator::getRampWave() const { + return ramp; +} + + +/** + * @brief Saw waveform current + * @return + */ +float BLITOscillator::getSawWave() const { + return saw; +} + + +/** + * @brief Pulse waveform current + * @return + */ +float BLITOscillator::getPulseWave() const { + return pulse; +} + + +/** + * @brief SawTri waveform current + * @return + */ +float BLITOscillator::getSawTriWave() const { + return sine; +} + + +/** + * @brief Triangle waveform current + * @return + */ +float BLITOscillator::getTriangleWave() const { + return tri; +} + + +/** + * @brief Process band-limited oscillator + */ +void dsp::BLITOscillator::proccess() { + /* phase locked loop */ + phase = wrapTWOPI(incr + phase); + + /* pulse width */ + float w = pw * (float) M_PI; + + /* get impulse train */ + float blit1 = BLIT(n, phase); + float blit2 = BLIT(n, wrapTWOPI(w + phase)); + + /* feed integrator */ + int1.add(blit1, incr); + int2.add(blit2, incr); + + /* integrator delta */ + float delta = int1.value - int2.value; + + /* 3rd integrator */ + float beta = int3.add(delta, incr) * 1.8f; + + /* compute RAMP waveform */ + ramp = int1.value * 0.5f; + /* compute pulse waveform */ + pulse = delta; + /* compute SAW waveform */ + saw = ramp * -1; + + /* compute triangle */ + tri = (float) M_PI / w * beta; + /* compute sine */ + sine = fastSin(phase); + + //TODO: warmup oscillator with: y(x)=1-e^-(x/n) and slope + + saw *= 5; + + +/* sine = shape2(shape, sine); + tri = shape2(shape, tri); + pulse = shape2(shape, pulse);*/ + +} + + +/** + * @brief ReCompute basic parameter + */ +void BLITOscillator::invalidate() { + incr = getPhaseIncrement(freq); + n = (int) floorf(BLIT_HARMONICS / freq); +} + + +/** + * @brief Get saturation + * @return + */ +float BLITOscillator::getSaturate() const { + return shape; +} + + +/** + * @brief Set saturation + * @param saturate + */ +void BLITOscillator::setShape(float saturate) { + BLITOscillator::shape = saturate; +} + + +/** + * @brief Translate from control voltage to frequency + * @param cv ControlVoltage from MIDI2CV + * @param fm Frequency modulation + * @param oct Octave + */ +void dsp::BLITOscillator::updatePitch(float cv, float fm, float tune, float oct) { + // CV is at 1V/OCt, C0 = 16.3516Hz, C4 = 261.626Hz + // 10.3V = 20614.33hz + + /* optimize the usage of expensive exp function and other computations */ + float coeff = (_oct != oct) ? powf(2.f, oct) : _coeff; + float base = (_cv != cv) ? powf(2.f, cv) : _base; + float biqufm = (_tune != tune) ? quadraticBipolar(tune) : _biqufm; + + setFrequency((NOTE_C4 + biqufm) * base * coeff + detune + fm); + + /* save states */ + _cv = cv; + _oct = oct; + _base = base; + _coeff = coeff; + _tune = tune; + _biqufm = biqufm; +} \ No newline at end of file diff --git a/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/Oscillator.hpp b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/Oscillator.hpp new file mode 100644 index 00000000..735aa4a2 --- /dev/null +++ b/plugins/community/repos/LindenbergResearch__ORIG/src/dsp/Oscillator.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include "DSPMath.hpp" +#include "DSPSystem.hpp" + +#define BLIT_HARMONICS 21000.f +#define NOTE_C4 261.626f + +namespace dsp { + + struct DSPIntegrator : DSPSystem<1, 1, 1> { + enum Inputs { + IN + }; + + enum Outputs { + OUT + }; + + enum Params { + D + }; + + float d = 0.25; + float x = 0; + + + }; + + +/** + * @brief Oscillator base class + */ + struct BLITOscillator { + + public: + enum SIGNAL { + SAW, + PULSE, + SINE, + TRI + }; + + + float freq; // oscillator frequency + float pw; // pulse-width value + float phase; // current phase + float incr; // current phase increment for PLL + float detune; // analogue detune + float drift; // oscillator drift + float warmup; // oscillator warmup detune + Noise noise; // randomizer + + float shape; + int n; + + /* currents of waveforms */ + float ramp; + float saw; + float pulse; + float sine; + float tri; + + /* saved frequency states */ + float _cv, _oct, _base, _coeff, _tune, _biqufm; + + /* leaky integrators */ + Integrator int1; + Integrator int2; + Integrator int3; + + BLITOscillator(); + ~BLITOscillator(); + + /** + * @brief Proccess next sample for output + */ + void proccess(); + + + /** + * @brief ReCompute states on change + */ + void invalidate(); + + + /** + * @brief Reset oscillator + */ + void reset(); + + + void updatePitch(float cv, float fm, float tune, float oct); + + /* common getter and setter */ + float getFrequency() const; + void setFrequency(float freq); + float getPulseWidth() const; + void setPulseWidth(float pw); + + float getRampWave() const; + float getSawWave() const; + float getPulseWave() const; + float getSawTriWave() const; + float getTriangleWave() const; + float getSaturate() const; + void setShape(float saturate); + }; + +} \ No newline at end of file diff --git a/plugins/community/repos/Ohmer/src/KlokSpid.cpp b/plugins/community/repos/Ohmer/src/KlokSpid.cpp index fb16f6a1..7ae20f8c 100644 --- a/plugins/community/repos/Ohmer/src/KlokSpid.cpp +++ b/plugins/community/repos/Ohmer/src/KlokSpid.cpp @@ -1331,7 +1331,7 @@ void KlokSpidModule::step() { } } // Sending pulse, using pulse generator. - sendPulse[i].pulseTime = pulseDuration[i]; + sendPulse[i].triggerDuration = pulseDuration[i]; sendPulse[i].trigger(pulseDuration[i]); canPulse[i] = false; } diff --git a/plugins/community/repos/Template_shared/Template_shared.dll b/plugins/community/repos/Template_shared/Template_shared.dll index 25d89e04..6eb970f8 100644 Binary files a/plugins/community/repos/Template_shared/Template_shared.dll and b/plugins/community/repos/Template_shared/Template_shared.dll differ diff --git a/plugins/community/repos/dBiz/dBiz.dll b/plugins/community/repos/dBiz/dBiz.dll index 2dccf57f..64d2d3e7 100644 Binary files a/plugins/community/repos/dBiz/dBiz.dll and b/plugins/community/repos/dBiz/dBiz.dll differ diff --git a/plugins/makefile.msvc b/plugins/makefile.msvc index 0dbda338..6878ed1e 100644 --- a/plugins/makefile.msvc +++ b/plugins/makefile.msvc @@ -11,6 +11,7 @@ endef .PHONY: bin bin: # $(foreach pname,$(PLUGINS),$(eval ($(call run_make,$(pname),bin)))) + $(call run_make,21kHz,bin) $(call run_make,Alikins,bin) $(call run_make,AS,bin) $(call run_make,AudibleInstruments,bin) @@ -30,6 +31,7 @@ bin: $(call run_make,Gratrix,bin) $(call run_make,HetrickCV,bin) $(call run_make,huaba,bin) + $(call run_make,ImpromptuModular,bin) $(call run_make,JW-Modules,bin) $(call run_make,Koralfx-Modules,bin) $(call run_make,LindenbergResearch,bin) @@ -60,6 +62,7 @@ bin: clean: # $(foreach pname,$(PLUGINS),$(eval $(call run_make,$(pname),clean))) # $(foreach pname,$(PLUGINS),$(eval echo $(pname))) + $(call run_make,21kHz,clean) $(call run_make,Alikins,clean) $(call run_make,AS,clean) $(call run_make,AudibleInstruments,clean) @@ -79,6 +82,7 @@ clean: $(call run_make,Gratrix,clean) $(call run_make,HetrickCV,clean) $(call run_make,huaba,clean) + $(call run_make,ImpromptuModular,clean) $(call run_make,JW-Modules,clean) $(call run_make,Koralfx-Modules,clean) $(call run_make,LindenbergResearch,clean) diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index fa736e37..32563f9b 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -181,7 +181,7 @@ static OversampleChannelSetting oversample_channel_settings[] = { /* 0 */ { "Oversample: 0 in, 1 out", 0, 1 }, /* 1 */ { "Oversample: 0 in, 2 out", 0, 2 }, /* 2 */ { "Oversample: 0 in, 4 out", 0, 4 }, - /* 3 */ { "Oversample: 0 in, 8 out", 8, 8 }, + /* 3 */ { "Oversample: 0 in, 8 out", 0, 8 }, /* 4 */ { "Oversample: 2 in, 2 out", 2, 2 }, /* 5 */ { "Oversample: 2 in, 4 out", 2, 4 }, /* 6 */ { "Oversample: 4 in, 8 out", 4, 8 }, @@ -216,7 +216,7 @@ struct SampleRateButton : TooltipIconButton { pauseItem->text = global->gPaused ? "Resume engine" : "Pause engine"; menu->addChild(pauseItem); -#ifdef USE_VST2 +#if defined(USE_VST2) && defined(RACK_HOST) { int numIn; int numOut; @@ -258,7 +258,7 @@ struct SampleRateButton : TooltipIconButton { item->sampleRate = sampleRate; menu->addChild(item); } -#endif // USE_VST2 +#endif // USE_VST2 && RACK_HOST } }; @@ -339,5 +339,4 @@ void Toolbar::draw(NVGcontext *vg) { Widget::draw(vg); } - } // namespace rack diff --git a/src/plugin.cpp b/src/plugin.cpp index 8998ec93..e9c6518b 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -602,6 +602,7 @@ Model *pluginGetModel(std::string pluginSlug, std::string modelSlug) { #ifdef USE_VST2 #ifndef RACK_PLUGIN extern "C" { +extern void init_plugin_21kHz (rack::Plugin *p); extern void init_plugin_Alikins (rack::Plugin *p); extern void init_plugin_AS (rack::Plugin *p); extern void init_plugin_AudibleInstruments (rack::Plugin *p); @@ -621,6 +622,7 @@ extern void init_plugin_Fundamental (rack::Plugin *p); extern void init_plugin_Gratrix (rack::Plugin *p); extern void init_plugin_HetrickCV (rack::Plugin *p); extern void init_plugin_huaba (rack::Plugin *p); +extern void init_plugin_ImpromptuModular (rack::Plugin *p); extern void init_plugin_JW_Modules (rack::Plugin *p); extern void init_plugin_Koralfx (rack::Plugin *p); extern void init_plugin_LindenbergResearch (rack::Plugin *p); @@ -674,6 +676,7 @@ static void vst2_load_static_rack_plugin(const char *_name, InitCallback _initCa } void vst2_load_static_rack_plugins(void) { + vst2_load_static_rack_plugin("21kHz", &init_plugin_21kHz); vst2_load_static_rack_plugin("Alikins", &init_plugin_Alikins); vst2_load_static_rack_plugin("AS", &init_plugin_AS); vst2_load_static_rack_plugin("AudibleInstruments", &init_plugin_AudibleInstruments); @@ -693,6 +696,7 @@ void vst2_load_static_rack_plugins(void) { vst2_load_static_rack_plugin("Gratrix", &init_plugin_Gratrix); vst2_load_static_rack_plugin("HetrickCV", &init_plugin_HetrickCV); vst2_load_static_rack_plugin("huaba", &init_plugin_huaba); + vst2_load_static_rack_plugin("ImpromptuModular", &init_plugin_ImpromptuModular); vst2_load_static_rack_plugin("JW_Modules", &init_plugin_JW_Modules); vst2_load_static_rack_plugin("Koralfx-Modules", &init_plugin_Koralfx); vst2_load_static_rack_plugin("LindenbergResearch", &init_plugin_LindenbergResearch); diff --git a/src/settings.cpp b/src/settings.cpp index 9965b648..0766754a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -19,7 +19,8 @@ extern void vst2_oversample_channels_set (int _numIn, int _numOut); namespace rack { -extern bool b_touchkeyboard_enable; +bool b_touchkeyboard_enable = false; // true=support effEditKey* + static json_t *settingsToJson() { // root diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index 421c4f83..43f64b5d 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -39,10 +39,12 @@ void TextField::onMouseDown(EventMouseDown &e) { } OpaqueWidget::onMouseDown(e); +#ifdef RACK_HOST if(b_touchkeyboard_enable) { lglw_touchkeyboard_show(rack::global_ui->window.lglw, LGLW_TRUE); } +#endif // RACK_HOST } void TextField::onMouseMove(EventMouseMove &e) { @@ -187,10 +189,12 @@ void TextField::onKey(EventKey &e) { } else { EventAction e; +#ifdef RACK_HOST if(b_touchkeyboard_enable) { lglw_touchkeyboard_show(rack::global_ui->window.lglw, LGLW_FALSE); } +#endif // RACK_HOST onAction(e); } break; diff --git a/src/vst2_main.cpp b/src/vst2_main.cpp index 599fc1df..bb29b4a9 100644 --- a/src/vst2_main.cpp +++ b/src/vst2_main.cpp @@ -68,8 +68,8 @@ extern void vst2_set_shared_plugin_tls_globals (void); extern "C" extern int vst2_handle_effeditkeydown (unsigned int _vkey); namespace rack { + extern bool b_touchkeyboard_enable; extern void settingsLoad(std::string filename, bool bWindowSizeOnly); - bool b_touchkeyboard_enable = false; // true=support effEditKey* } diff --git a/vst2_bin/CHANGELOG_VST.txt b/vst2_bin/CHANGELOG_VST.txt index 9fa7bf4a..20779309 100644 --- a/vst2_bin/CHANGELOG_VST.txt +++ b/vst2_bin/CHANGELOG_VST.txt @@ -1,3 +1,27 @@ +** August 14th, 2018 +- fix shared_lib build +- add module 21kHz.D_Inf +- add module 21kHz.PalmLoop +- add module ImpromptuModular.Tact +- add module ImpromptuModular.TwelveKey +- add module ImpromptuModular.Clocked +- add module ImpromptuModular.MidiFile +- add module ImpromptuModular.PhraseSeq16 +- add module ImpromptuModular.PhraseSeq32 +- add module ImpromptuModular.GateSeq64 +- add module ImpromptuModular.WriteSeq32 +- add module ImpromptuModular.WriteSeq64 +- add module ImpromptuModular.BigButtonSeq +- add module ImpromptuModular.SemiModularSynth +- add module ImpromptuModular.BlankPanel +- update module LindenbergResearch.SimpleFilter +- update module LindenbergResearch.MS20Filter +- update module LindenbergResearch.AlmaFilter +- update module LindenbergResearch.ReShaper +- add module LindenbergResearch.VCO +- add module LindenbergResearch.Westcoast (preview) + + ** August 11th, 2018 - add settings.json:"oversampleFactor" option (0,25..16) - add settings.json:"oversampleQuality" option (0..10) diff --git a/vst2_bin/README_vst2.txt b/vst2_bin/README_vst2.txt index 1edc43a9..36055b8b 100644 --- a/vst2_bin/README_vst2.txt +++ b/vst2_bin/README_vst2.txt @@ -1,4 +1,4 @@ -VeeSeeVST Rack VST 2.4 Plugin -- August 11th, 2018 +VeeSeeVST Rack VST 2.4 Plugin -- August 14th, 2018 ================================================== !!!------------------------------------------------------------------------------ @@ -32,7 +32,29 @@ Tested in - according to users: works in Ableton Live -The VST2 plugin includes the following add-on modules: +The binary distribution contains the following dynamically loaded add-on modules: + - dBiz.dBizBlank + - dBiz.Multiple + - dBiz.Contorno + - dBiz.Chord + - dBiz.Utility + - dBiz.Transpose + - dBiz.Bene + - dBiz.Bene2 + - dBiz.BenePads + - dBiz.SubMix + - dBiz.Remix + - dBiz.PerfMixer + - dBiz.VCA530 + - dBiz.Verbo + - dBiz.DVCO + - dBiz.DAOSC + - Template_shared.MyModule + + +The following (478) add-on modules are statically linked with the VST plugin: + - 21kHz.D_Inf + - 21kHz.PalmLoop - Alikins.IdleSwitch - Alikins.MomentaryOnButtons - Alikins.BigMuteButton @@ -195,22 +217,6 @@ The VST2 plugin includes the following add-on modules: - cf.PATCH - cf.LEDS - cf.DAVE - - dBiz.dBizBlank - - dBiz.Multiple - - dBiz.Contorno - - dBiz.Chord - - dBiz.Utility - - dBiz.Transpose - - dBiz.Bene - - dBiz.Bene2 - - dBiz.BenePads - - dBiz.SubMix - - dBiz.Remix - - dBiz.PerfMixer - - dBiz.VCA530 - - dBiz.Verbo - - dBiz.DVCO - - dBiz.DAOSC - DHE-Modules.BoosterStage - DHE-Modules.Cubic - DHE-Modules.Hostage @@ -298,6 +304,18 @@ The VST2 plugin includes the following add-on modules: - HetrickCV.Waveshape - huaba.EQ3 - 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.BouncyBalls - JW_Modules.FullScope @@ -322,6 +340,8 @@ The VST2 plugin includes the following add-on modules: - LindenbergResearch.ReShaper - LindenbergResearch.BlankPanel - LindenbergResearch.BlankPanelM1 + - LindenbergResearch.VCO + - LindenbergResearch.Westcoast (preview) - LOGinstruments.constant - LOGinstruments.constant2 - LOGinstruments.Speck diff --git a/vst2_bin/log.txt b/vst2_bin/log.txt index c731f044..e50b305d 100644 --- a/vst2_bin/log.txt +++ b/vst2_bin/log.txt @@ -1,124 +1,126 @@ [0.000 info src/main.cpp:59] VeeSeeVST Rack 0.6.1 [0.000 info src/main.cpp:62] Global directory: f:\git\VeeSeeVSTRack\vst2_bin\/ [0.000 info src/main.cpp:63] Local directory: f:\git\VeeSeeVSTRack\vst2_bin\/ -[0.000 info src/plugin.cpp:673] vcvrack: Loaded static plugin Alikins 0.6.1 -[0.000 info src/plugin.cpp:673] vcvrack: Loaded static plugin AS 0.6.1 -[0.001 info src/plugin.cpp:673] vcvrack: Loaded static plugin AudibleInstruments 0.6.1 -[0.001 info src/plugin.cpp:673] vcvrack: Loaded static plugin BaconMusic 0.6.1 -[0.002 info src/plugin.cpp:673] vcvrack: Loaded static plugin Befaco 0.6.1 -[0.002 info src/plugin.cpp:673] vcvrack: Loaded static plugin Bidoo 0.6.1 -[0.002 info src/plugin.cpp:673] vcvrack: Loaded static plugin Bogaudio 0.6.1 -[0.003 info src/plugin.cpp:673] vcvrack: Loaded static plugin cf 0.6.1 -[0.003 info src/plugin.cpp:673] vcvrack: Loaded static plugin DHE-Modules 0.6.1 -[0.003 info src/plugin.cpp:673] vcvrack: Loaded static plugin DrumKit 0.6.1 -[0.003 info src/plugin.cpp:673] vcvrack: Loaded static plugin ErraticInstruments 0.6.1 -[0.003 info src/plugin.cpp:673] vcvrack: Loaded static plugin ESeries 0.6.1 -[0.003 info src/plugin.cpp:673] vcvrack: Loaded static plugin FrozenWasteland 0.6.1 -[0.004 info src/plugin.cpp:673] vcvrack: Loaded static plugin Fundamental 0.6.1 -[0.004 info src/plugin.cpp:673] vcvrack: Loaded static plugin Gratrix 0.6.1 -[0.004 info src/plugin.cpp:673] vcvrack: Loaded static plugin HetrickCV 0.6.1 -[0.004 info src/plugin.cpp:673] vcvrack: Loaded static plugin huaba 0.6.1 -[0.004 info src/plugin.cpp:673] vcvrack: Loaded static plugin JW-Modules 0.6.1 -[0.004 info src/plugin.cpp:673] vcvrack: Loaded static plugin Koralfx-Modules 0.6.1 -[0.005 info src/plugin.cpp:673] vcvrack: Loaded static plugin LindenbergResearch 0.6.1 -[0.005 info src/plugin.cpp:673] vcvrack: Loaded static plugin LOGinstruments 0.6.1 -[0.005 info src/plugin.cpp:673] vcvrack: Loaded static plugin ML_modules 0.6.1 -[0.005 info src/plugin.cpp:673] vcvrack: Loaded static plugin moDllz 0.6.1 -[0.005 info src/plugin.cpp:673] vcvrack: Loaded static plugin modular80 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin mscHack 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin mtsch-plugins 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin NauModular 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin Ohmer 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin Qwelk 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin RJModules 0.6.1 -[0.006 info src/plugin.cpp:673] vcvrack: Loaded static plugin SerialRacker 0.6.1 -[0.007 info src/plugin.cpp:673] vcvrack: Loaded static plugin SonusModular 0.6.1 -[0.007 info src/plugin.cpp:673] vcvrack: Loaded static plugin Southpole-parasites 0.6.1 -[0.007 info src/plugin.cpp:673] vcvrack: Loaded static plugin squinkylabs-plug1 0.6.1 -[0.007 info src/plugin.cpp:673] vcvrack: Loaded static plugin SubmarineFree 0.6.1 -[0.007 info src/plugin.cpp:673] vcvrack: Loaded static plugin Template 0.6.1 -[0.008 info src/plugin.cpp:673] vcvrack: Loaded static plugin trowaSoft 0.6.1 -[0.008 info src/plugin.cpp:673] vcvrack: Loaded static plugin unless_modules 0.6.1 -[0.008 info src/plugin.cpp:673] vcvrack: Loaded static plugin Valley 0.6.1 -[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Alikins/plugin.dll does not exist -[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/AS/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/AudibleInstruments/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/BaconMusic/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Befaco/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Bidoo/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Bogaudio/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/BOKONTEPByteBeatMachine/plugin.dll does not exist -[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/cf/plugin.dll does not exist -[0.010 info src/plugin.cpp:155] Loaded plugin dBiz 0.6.1 from f:\git\VeeSeeVSTRack\vst2_bin\/plugins/dBiz/plugin.dll -[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/DHE-Modules/plugin.dll does not exist -[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/DrumKit/plugin.dll does not exist -[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ErraticInstruments/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ESeries/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/FrozenWasteland/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Fundamental/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Gratrix/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/HetrickCV/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/huaba/plugin.dll does not exist -[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/JW-Modules/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Koralfx-Modules/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/LindenbergResearch/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/LOGinstruments/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ML_modules/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/moDllz/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/modular80/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/mscHack/plugin.dll does not exist -[0.012 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/mtsch-plugins/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/NauModular/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Ohmer/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Qwelk/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/RJModules/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/SerialRacker/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/SonusModular/plugin.dll does not exist -[0.013 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Southpole-parasites/plugin.dll does not exist -[0.014 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/squinkylabs-plug1/plugin.dll does not exist -[0.014 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/SubmarineFree/plugin.dll does not exist -[0.014 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Template/plugin.dll does not exist -[0.015 info src/plugin.cpp:155] Loaded plugin Template_shared 0.6.1 from f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Template_shared/plugin.dll -[0.015 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/trowaSoft/plugin.dll does not exist -[0.015 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/unless_modules/plugin.dll does not exist -[0.015 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Valley/plugin.dll does not exist -[0.015 info src/settings.cpp:286] Loading settings f:\git\VeeSeeVSTRack\vst2_bin\/settings.json -[0.032 info src/window.cpp:599] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/DejaVuSans.ttf -[0.033 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_146097_cc.svg -[0.033 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_31859_cc.svg -[0.033 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1343816_cc.svg -[0.034 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1343811_cc.svg -[0.034 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1084369_cc.svg -[0.034 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1745061_cc.svg -[0.035 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1240789_cc.svg -[0.035 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_305536_cc.svg -[0.035 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_468341_cc.svg -[0.035 info src/settings.cpp:286] Loading settings f:\git\VeeSeeVSTRack\vst2_bin\/settings.json -[0.037 info src/app/RackWidget.cpp:205] Loading patch from string -[0.039 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/AudioInterface.svg -[0.040 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/ScrewSilver.svg -[0.040 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/PJ301M.svg -[0.040 info src/window.cpp:599] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/ShareTechMono-Regular.ttf -[0.042 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/MIDIToCVInterface.svg -[0.046 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/XCO.svg -[0.046 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_68px.svg -[0.046 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_16px.svg -[0.047 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/button_9px_0.svg -[0.047 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/button_9px_1.svg -[0.047 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/knob_38px.svg -[0.047 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/slider_switch_2_14px_0.svg -[0.048 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/slider_switch_2_14px_1.svg -[0.048 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Bogaudio/res/port.svg -[0.050 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCA.svg -[0.050 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundLargeBlackKnob.svg -[0.052 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCF.svg -[0.052 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundHugeBlackKnob.svg -[0.054 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/ADSR.svg -[0.054 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-hexscrew.svg -[0.054 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-SlidePot.svg -[0.055 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-SlidePotHandle.svg -[0.055 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-PJ301M.svg -[0.058 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/VCO-1.svg -[0.059 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/CKSS_0.svg -[0.059 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/CKSS_1.svg -[22.077 info src/app/RackWidget.cpp:167] Saving patch to string +[0.000 info src/plugin.cpp:675] vcvrack: Loaded static plugin 21kHz 0.6.1 +[0.000 info src/plugin.cpp:675] vcvrack: Loaded static plugin Alikins 0.6.1 +[0.000 info src/plugin.cpp:675] vcvrack: Loaded static plugin AS 0.6.1 +[0.000 info src/plugin.cpp:675] vcvrack: Loaded static plugin AudibleInstruments 0.6.1 +[0.000 info src/plugin.cpp:675] vcvrack: Loaded static plugin BaconMusic 0.6.1 +[0.001 info src/plugin.cpp:675] vcvrack: Loaded static plugin Befaco 0.6.1 +[0.001 info src/plugin.cpp:675] vcvrack: Loaded static plugin Bidoo 0.6.1 +[0.001 info src/plugin.cpp:675] vcvrack: Loaded static plugin Bogaudio 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin cf 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin DHE-Modules 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin DrumKit 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin ErraticInstruments 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin ESeries 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin FrozenWasteland 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin Fundamental 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin Gratrix 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin HetrickCV 0.6.1 +[0.002 info src/plugin.cpp:675] vcvrack: Loaded static plugin huaba 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin ImpromptuModular 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin JW-Modules 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin Koralfx-Modules 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin LindenbergResearch 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin LOGinstruments 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin ML_modules 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin moDllz 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin modular80 0.6.1 +[0.003 info src/plugin.cpp:675] vcvrack: Loaded static plugin mscHack 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin mtsch-plugins 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin NauModular 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin Ohmer 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin Qwelk 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin RJModules 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin SerialRacker 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin SonusModular 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin Southpole-parasites 0.6.1 +[0.004 info src/plugin.cpp:675] vcvrack: Loaded static plugin squinkylabs-plug1 0.6.1 +[0.005 info src/plugin.cpp:675] vcvrack: Loaded static plugin SubmarineFree 0.6.1 +[0.005 info src/plugin.cpp:675] vcvrack: Loaded static plugin Template 0.6.1 +[0.005 info src/plugin.cpp:675] vcvrack: Loaded static plugin trowaSoft 0.6.1 +[0.005 info src/plugin.cpp:675] vcvrack: Loaded static plugin unless_modules 0.6.1 +[0.005 info src/plugin.cpp:675] vcvrack: Loaded static plugin Valley 0.6.1 +[0.005 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/21kHz/plugin.dll does not exist +[0.005 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Alikins/plugin.dll does not exist +[0.005 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/AS/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/AudibleInstruments/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/BaconMusic/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Befaco/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Bidoo/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Bogaudio/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/BOKONTEPByteBeatMachine/plugin.dll does not exist +[0.006 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/cf/plugin.dll does not exist +[0.006 info src/plugin.cpp:155] Loaded plugin dBiz 0.6.1 from f:\git\VeeSeeVSTRack\vst2_bin\/plugins/dBiz/plugin.dll +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/DHE-Modules/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/DrumKit/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ErraticInstruments/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ESeries/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/FrozenWasteland/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Fundamental/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Gratrix/plugin.dll does not exist +[0.007 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/HetrickCV/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/huaba/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ImpromptuModular/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/JW-Modules/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Koralfx-Modules/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/LindenbergResearch/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/LOGinstruments/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/ML_modules/plugin.dll does not exist +[0.008 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/moDllz/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/modular80/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/mscHack/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/mtsch-plugins/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/NauModular/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Ohmer/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Qwelk/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/RJModules/plugin.dll does not exist +[0.009 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/SerialRacker/plugin.dll does not exist +[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/SonusModular/plugin.dll does not exist +[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Southpole-parasites/plugin.dll does not exist +[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/squinkylabs-plug1/plugin.dll does not exist +[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/SubmarineFree/plugin.dll does not exist +[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Template/plugin.dll does not exist +[0.010 info src/plugin.cpp:155] Loaded plugin Template_shared 0.6.1 from f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Template_shared/plugin.dll +[0.010 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/trowaSoft/plugin.dll does not exist +[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/unless_modules/plugin.dll does not exist +[0.011 warn src/plugin.cpp:86] Plugin file f:\git\VeeSeeVSTRack\vst2_bin\/plugins/Valley/plugin.dll does not exist +[0.011 info src/settings.cpp:287] Loading settings f:\git\VeeSeeVSTRack\vst2_bin\/settings.json +[0.024 info src/window.cpp:599] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/DejaVuSans.ttf +[0.024 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_146097_cc.svg +[0.024 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_31859_cc.svg +[0.024 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1343816_cc.svg +[0.025 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1343811_cc.svg +[0.025 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1084369_cc.svg +[0.025 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1745061_cc.svg +[0.025 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_1240789_cc.svg +[0.026 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_305536_cc.svg +[0.026 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/icons/noun_468341_cc.svg +[0.026 info src/settings.cpp:287] Loading settings f:\git\VeeSeeVSTRack\vst2_bin\/settings.json +[0.028 info src/app/RackWidget.cpp:205] Loading patch from string +[0.029 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/AudioInterface.svg +[0.029 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/ScrewSilver.svg +[0.030 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/PJ301M.svg +[0.030 info src/window.cpp:599] Loaded font f:\git\VeeSeeVSTRack\vst2_bin\/res/fonts/ShareTechMono-Regular.ttf +[0.031 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/Core/MIDIToCVInterface.svg +[0.033 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/Delay.svg +[0.033 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundLargeBlackKnob.svg +[0.036 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/AtNuVrTr.svg +[0.036 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-hexscrew.svg +[0.037 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-knobBlack.svg +[0.037 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-knob.svg +[0.037 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/AS/res/as-PJ301M.svg +[0.039 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Alikins/res/SpecificValue.svg +[0.039 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg +[0.040 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Alikins/res/SmallPurpleTrimpot.svg +[0.040 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg +[0.040 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Alikins/res/PurpleTrimpot.svg +[0.041 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg +[0.041 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg +[0.043 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\plugins/Fundamental/res/LFO-1.svg +[0.043 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/CKSS_0.svg +[0.044 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/CKSS_1.svg +[0.044 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/RoundHugeBlackKnob.svg +[0.045 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg +[0.046 info src/window.cpp:654] Loaded SVG f:\git\VeeSeeVSTRack\vst2_bin\/res/ComponentLibrary/Trimpot.svg diff --git a/vst2_bin/plugins/21kHz/LICENSE.txt b/vst2_bin/plugins/21kHz/LICENSE.txt new file mode 100644 index 00000000..0534bf2f --- /dev/null +++ b/vst2_bin/plugins/21kHz/LICENSE.txt @@ -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. diff --git a/vst2_bin/plugins/21kHz/README.md b/vst2_bin/plugins/21kHz/README.md new file mode 100644 index 00000000..faef6f69 --- /dev/null +++ b/vst2_bin/plugins/21kHz/README.md @@ -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. + +drawing + +## 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. + +drawing + +## *D*∞ + +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. diff --git a/vst2_bin/plugins/21kHz/docs/d.png b/vst2_bin/plugins/21kHz/docs/d.png new file mode 100644 index 00000000..8fbbd954 Binary files /dev/null and b/vst2_bin/plugins/21kHz/docs/d.png differ diff --git a/vst2_bin/plugins/21kHz/docs/pl.png b/vst2_bin/plugins/21kHz/docs/pl.png new file mode 100644 index 00000000..33555803 Binary files /dev/null and b/vst2_bin/plugins/21kHz/docs/pl.png differ diff --git a/vst2_bin/plugins/21kHz/res/Components/kHzButton_0.svg b/vst2_bin/plugins/21kHz/res/Components/kHzButton_0.svg new file mode 100644 index 00000000..edcfad31 --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Components/kHzButton_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Components/kHzButton_1.svg b/vst2_bin/plugins/21kHz/res/Components/kHzButton_1.svg new file mode 100644 index 00000000..42ced401 --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Components/kHzButton_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Components/kHzKnob.svg b/vst2_bin/plugins/21kHz/res/Components/kHzKnob.svg new file mode 100644 index 00000000..8933dad4 --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Components/kHzKnob.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Components/kHzKnobSmall.svg b/vst2_bin/plugins/21kHz/res/Components/kHzKnobSmall.svg new file mode 100644 index 00000000..fa0818a5 --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Components/kHzKnobSmall.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Components/kHzPort.svg b/vst2_bin/plugins/21kHz/res/Components/kHzPort.svg new file mode 100644 index 00000000..448664e3 --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Components/kHzPort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Components/kHzScrew.svg b/vst2_bin/plugins/21kHz/res/Components/kHzScrew.svg new file mode 100644 index 00000000..61cff8ef --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Components/kHzScrew.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Panels/D_Inf.svg b/vst2_bin/plugins/21kHz/res/Panels/D_Inf.svg new file mode 100644 index 00000000..ac9bbd1b --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Panels/D_Inf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/21kHz/res/Panels/PalmLoop.svg b/vst2_bin/plugins/21kHz/res/Panels/PalmLoop.svg new file mode 100644 index 00000000..a15e5e13 --- /dev/null +++ b/vst2_bin/plugins/21kHz/res/Panels/PalmLoop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/LICENSE.txt b/vst2_bin/plugins/ImpromptuModular/LICENSE.txt new file mode 100644 index 00000000..951a369a --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/LICENSE.txt @@ -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. diff --git a/vst2_bin/plugins/ImpromptuModular/README.md b/vst2_bin/plugins/ImpromptuModular/README.md new file mode 100644 index 00000000..11f210cc --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/README.md @@ -0,0 +1,317 @@ +![IM](res/img/Blank.jpg) + +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 + +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 +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 + +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 + +![IM](res/img/Tact.jpg) + +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 + +![IM](res/img/TwelveKey.jpg) + +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 + +![IM](res/img/Clocked.jpg) + +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 + +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 + +![IM](res/img/PhraseSeq16.jpg) + +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!) + +![IM](res/img/PhraseSeq16BlockDiag.jpg) + +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 + +![IM](res/img/PhraseSeq32.jpg) + +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 + +![IM](res/img/GateSeq64.jpg) + +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 + +![IM](res/img/BigButtonSeq.jpg) + +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 + +![IM](res/img/SemiModularSynth.jpg) + +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 + +![IM](res/img/WriteSeqs.jpg) + +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)) diff --git a/vst2_bin/plugins/ImpromptuModular/res/PhraseSeq16BlockDiag.svg b/vst2_bin/plugins/ImpromptuModular/res/PhraseSeq16BlockDiag.svg new file mode 100644 index 00000000..596d7845 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/PhraseSeq16BlockDiag.svg @@ -0,0 +1 @@ +SEQSONGPHRASES (= SEQUENCE INDEXES)EACH SEQUENCE CAN HAVE UP TO 16 STEPS (NOT SHOWN)16 SEPARATE SEQUENCES CAN BE CREATEDA SONG CAN HAVE UP TO 16 PHRASES \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/comp/CKSSH_0.svg b/vst2_bin/plugins/ImpromptuModular/res/comp/CKSSH_0.svg new file mode 100644 index 00000000..f538b3b9 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/comp/CKSSH_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/comp/CKSSH_1.svg b/vst2_bin/plugins/ImpromptuModular/res/comp/CKSSH_1.svg new file mode 100644 index 00000000..7c3ec633 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/comp/CKSSH_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/BigButtonSeq_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/BigButtonSeq_dark.svg new file mode 100644 index 00000000..d7a1a62f --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/BigButtonSeq_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/BlankPanel_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/BlankPanel_dark.svg new file mode 100644 index 00000000..267e22d8 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/BlankPanel_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg new file mode 100644 index 00000000..53c10ee3 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/Clocked_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg new file mode 100644 index 00000000..a80aca89 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/GateSeq64_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg new file mode 100644 index 00000000..85477ddf --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq16_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg new file mode 100644 index 00000000..3c342d3c --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/PhraseSeq32_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/SemiModular_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/SemiModular_dark.svg new file mode 100644 index 00000000..94b841fd --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/SemiModular_dark.svg @@ -0,0 +1 @@ ++- \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/Tact_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/Tact_dark.svg new file mode 100644 index 00000000..dc01681a --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/Tact_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/TwelveKey_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/TwelveKey_dark.svg new file mode 100644 index 00000000..a529d7a5 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/TwelveKey_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/WriteSeq32_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/WriteSeq32_dark.svg new file mode 100644 index 00000000..cb5e6482 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/WriteSeq32_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/WriteSeq64_dark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/WriteSeq64_dark.svg new file mode 100644 index 00000000..50aa13e4 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/WriteSeq64_dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLarge.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLarge.svg new file mode 100644 index 00000000..cdffd8a5 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLarge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeEffects.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeEffects.svg new file mode 100644 index 00000000..e17c38c1 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeEffects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMark.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMark.svg new file mode 100644 index 00000000..b32c394d --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMarkEffects.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMarkEffects.svg new file mode 100644 index 00000000..e17c38c1 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/BlackKnobLargeWithMarkEffects.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/CKD6b_0.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/CKD6b_0.svg new file mode 100644 index 00000000..050c15b8 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/CKD6b_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/CKD6b_1.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/CKD6b_1.svg new file mode 100644 index 00000000..bf7875fb --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/CKD6b_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/PJ301M.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/PJ301M.svg new file mode 100644 index 00000000..a0410488 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/PJ301M.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/RoundSmallBlackKnob.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/RoundSmallBlackKnob.svg new file mode 100644 index 00000000..0a2f6286 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/RoundSmallBlackKnob.svg @@ -0,0 +1,88 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/RoundSmallBlackKnobEffects.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/RoundSmallBlackKnobEffects.svg new file mode 100644 index 00000000..988c29ba --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/RoundSmallBlackKnobEffects.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/ImpromptuModular/res/dark/comp/ScrewSilver.svg b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/ScrewSilver.svg new file mode 100644 index 00000000..8bfcafc8 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/dark/comp/ScrewSilver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/fonts/OFL_Segment14.txt b/vst2_bin/plugins/ImpromptuModular/res/fonts/OFL_Segment14.txt new file mode 100644 index 00000000..25a557fb --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/fonts/OFL_Segment14.txt @@ -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. diff --git a/vst2_bin/plugins/ImpromptuModular/res/fonts/OFL_Sniglet.txt b/vst2_bin/plugins/ImpromptuModular/res/fonts/OFL_Sniglet.txt new file mode 100644 index 00000000..b0a6d8c8 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/fonts/OFL_Sniglet.txt @@ -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. diff --git a/vst2_bin/plugins/ImpromptuModular/res/fonts/Segment14.ttf b/vst2_bin/plugins/ImpromptuModular/res/fonts/Segment14.ttf new file mode 100644 index 00000000..6baaa987 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/fonts/Segment14.ttf differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/fonts/Sniglet-Regular.ttf b/vst2_bin/plugins/ImpromptuModular/res/fonts/Sniglet-Regular.ttf new file mode 100644 index 00000000..a1c45c96 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/fonts/Sniglet-Regular.ttf differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/BigButtonSeq.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/BigButtonSeq.jpg new file mode 100644 index 00000000..e9f30e93 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/BigButtonSeq.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/Blank.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/Blank.jpg new file mode 100644 index 00000000..d3a41e02 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/Blank.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg new file mode 100644 index 00000000..4f591160 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/Clocked.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg new file mode 100644 index 00000000..6201b7be Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/GateSeq64.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg new file mode 100644 index 00000000..b74cee6f Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16BlockDiag.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16BlockDiag.jpg new file mode 100644 index 00000000..65aeeab9 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq16BlockDiag.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg new file mode 100644 index 00000000..23f56fb0 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/PhraseSeq32.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/SemiModularSynth.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/SemiModularSynth.jpg new file mode 100644 index 00000000..81621534 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/SemiModularSynth.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/Tact.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/Tact.jpg new file mode 100644 index 00000000..10ca0fa7 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/Tact.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/TwelveKey.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/TwelveKey.jpg new file mode 100644 index 00000000..bd169926 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/TwelveKey.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/img/WriteSeqs.jpg b/vst2_bin/plugins/ImpromptuModular/res/img/WriteSeqs.jpg new file mode 100644 index 00000000..ff47d794 Binary files /dev/null and b/vst2_bin/plugins/ImpromptuModular/res/img/WriteSeqs.jpg differ diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/BigButtonSeq.svg b/vst2_bin/plugins/ImpromptuModular/res/light/BigButtonSeq.svg new file mode 100644 index 00000000..379b7b28 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/BigButtonSeq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/BlankPanel.svg b/vst2_bin/plugins/ImpromptuModular/res/light/BlankPanel.svg new file mode 100644 index 00000000..17841474 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/BlankPanel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg b/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg new file mode 100644 index 00000000..a0979cff --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/Clocked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/EngTest1.svg b/vst2_bin/plugins/ImpromptuModular/res/light/EngTest1.svg new file mode 100644 index 00000000..51528c47 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/EngTest1.svg @@ -0,0 +1,931 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg b/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg new file mode 100644 index 00000000..7f233fc4 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/GateSeq64.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/MidiFile.svg b/vst2_bin/plugins/ImpromptuModular/res/light/MidiFile.svg new file mode 100644 index 00000000..679c2097 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/MidiFile.svg @@ -0,0 +1,375 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MIDI-FILE + diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg new file mode 100644 index 00000000..ed0f38d4 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq16.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg new file mode 100644 index 00000000..3bd8d3d8 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/PhraseSeq32.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/SemiModular.svg b/vst2_bin/plugins/ImpromptuModular/res/light/SemiModular.svg new file mode 100644 index 00000000..934edbc2 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/SemiModular.svg @@ -0,0 +1 @@ ++- \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/Tact.svg b/vst2_bin/plugins/ImpromptuModular/res/light/Tact.svg new file mode 100644 index 00000000..5f15c349 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/Tact.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/TwelveKey.svg b/vst2_bin/plugins/ImpromptuModular/res/light/TwelveKey.svg new file mode 100644 index 00000000..00e147f5 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/TwelveKey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/WriteSeq32.svg b/vst2_bin/plugins/ImpromptuModular/res/light/WriteSeq32.svg new file mode 100644 index 00000000..30ec4cf0 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/WriteSeq32.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/WriteSeq64.svg b/vst2_bin/plugins/ImpromptuModular/res/light/WriteSeq64.svg new file mode 100644 index 00000000..e083208f --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/WriteSeq64.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/comp/BlackKnobLarge.svg b/vst2_bin/plugins/ImpromptuModular/res/light/comp/BlackKnobLarge.svg new file mode 100644 index 00000000..d8fad831 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/comp/BlackKnobLarge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/comp/BlackKnobLargeWithMark.svg b/vst2_bin/plugins/ImpromptuModular/res/light/comp/BlackKnobLargeWithMark.svg new file mode 100644 index 00000000..0060c370 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/comp/BlackKnobLargeWithMark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/comp/CKD6b_0.svg b/vst2_bin/plugins/ImpromptuModular/res/light/comp/CKD6b_0.svg new file mode 100644 index 00000000..51f61708 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/comp/CKD6b_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/comp/CKD6b_1.svg b/vst2_bin/plugins/ImpromptuModular/res/light/comp/CKD6b_1.svg new file mode 100644 index 00000000..958ad14a --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/comp/CKD6b_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/ImpromptuModular/res/light/comp/RoundSmallBlackKnob.svg b/vst2_bin/plugins/ImpromptuModular/res/light/comp/RoundSmallBlackKnob.svg new file mode 100644 index 00000000..94178d97 --- /dev/null +++ b/vst2_bin/plugins/ImpromptuModular/res/light/comp/RoundSmallBlackKnob.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.5.210.png b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.5.210.png new file mode 100644 index 00000000..5f13f88f Binary files /dev/null and b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.5.210.png differ diff --git a/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.5.3.png b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.5.3.png new file mode 100644 index 00000000..8d653754 Binary files /dev/null and b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.5.3.png differ diff --git a/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.6.0.png b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.6.0.png new file mode 100644 index 00000000..3e9eb744 Binary files /dev/null and b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.6.0.png differ diff --git a/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.6.1.png b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.6.1.png new file mode 100644 index 00000000..e801eea2 Binary files /dev/null and b/vst2_bin/plugins/LindenbergResearch/doc/LRTRackModules_0.6.1.png differ diff --git a/vst2_bin/plugins/LindenbergResearch/doc/VCO.svg b/vst2_bin/plugins/LindenbergResearch/doc/VCO.svg new file mode 100644 index 00000000..74e939a3 --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/doc/VCO.svg @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.pdf b/vst2_bin/plugins/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.pdf new file mode 100644 index 00000000..24d2bac4 Binary files /dev/null and b/vst2_bin/plugins/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.pdf differ diff --git a/vst2_bin/plugins/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.svg b/vst2_bin/plugins/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.svg new file mode 100644 index 00000000..a9c1578f --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/doc/Woldemar_Manual_v0.6.1.svg @@ -0,0 +1,1204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/AlternateBigKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/AlternateBigKnob.svg new file mode 100644 index 00000000..7b8ea0db --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/AlternateBigKnob.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/AlternateMiddleKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/AlternateMiddleKnob.svg new file mode 100644 index 00000000..333cb5fe --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/AlternateMiddleKnob.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/AlternateSmallKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/AlternateSmallKnob.svg new file mode 100644 index 00000000..0dc956ad --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/AlternateSmallKnob.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/BigKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/BigKnob.svg index 4452da95..7edad1b0 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/BigKnob.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/BigKnob.svg @@ -1,11 +1,15 @@ - - + + - + - - + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/BlankPanel.svg b/vst2_bin/plugins/LindenbergResearch/res/BlankPanel.svg index 0041548f..412669c0 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/BlankPanel.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/BlankPanel.svg @@ -1,63 +1,102 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - diff --git a/vst2_bin/plugins/LindenbergResearch/res/BlankPanelM1.svg b/vst2_bin/plugins/LindenbergResearch/res/BlankPanelM1.svg index f1568a37..6adc7c13 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/BlankPanelM1.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/BlankPanelM1.svg @@ -1,14 +1,5 @@ - - - - - - - - - - - + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/BlankPanelSmall.svg b/vst2_bin/plugins/LindenbergResearch/res/BlankPanelSmall.svg new file mode 100644 index 00000000..c65e2c07 --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/BlankPanelSmall.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/IOPortB.svg b/vst2_bin/plugins/LindenbergResearch/res/IOPortB.svg index 7e071f33..686eb3ff 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/IOPortB.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/IOPortB.svg @@ -1,45 +1,11 @@ - - - - - - - - - - - ]> - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/IOPortC.svg b/vst2_bin/plugins/LindenbergResearch/res/IOPortC.svg new file mode 100644 index 00000000..7a5882e9 --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/IOPortC.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/MS20.svg b/vst2_bin/plugins/LindenbergResearch/res/MS20.svg index d38ccbb1..c4e01cd3 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/MS20.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/MS20.svg @@ -1,8 +1,8 @@ - + - + @@ -129,75 +129,75 @@ - - - - + + + + - - - - + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - + + - - - + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - @@ -249,37 +239,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/MiddleIncremental.svg b/vst2_bin/plugins/LindenbergResearch/res/MiddleIncremental.svg new file mode 100644 index 00000000..333cb5fe --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/MiddleIncremental.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/MiddleKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/MiddleKnob.svg index a2a6907c..6910c89b 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/MiddleKnob.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/MiddleKnob.svg @@ -1,13 +1,15 @@ - + - + - - - + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/ReShaper.svg b/vst2_bin/plugins/LindenbergResearch/res/ReShaper.svg index 4f742811..d06e983b 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/ReShaper.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/ReShaper.svg @@ -1,7 +1,7 @@ - + diff --git a/vst2_bin/plugins/LindenbergResearch/res/ScrewLight.svg b/vst2_bin/plugins/LindenbergResearch/res/ScrewLight.svg new file mode 100644 index 00000000..c2442435 --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/ScrewLight.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/SimpleFilter.svg b/vst2_bin/plugins/LindenbergResearch/res/SimpleFilter.svg index 01b4adc0..5d09a1b2 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/SimpleFilter.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/SimpleFilter.svg @@ -2,7 +2,7 @@ - + diff --git a/vst2_bin/plugins/LindenbergResearch/res/SmallKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/SmallKnob.svg index e0e1896f..9098dade 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/SmallKnob.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/SmallKnob.svg @@ -1,16 +1,16 @@ - + - + - - + + - + diff --git a/vst2_bin/plugins/LindenbergResearch/res/Switch0.svg b/vst2_bin/plugins/LindenbergResearch/res/Switch0.svg index 83fd938e..7c112a3b 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/Switch0.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/Switch0.svg @@ -1,17 +1,22 @@ - - - - - + + + + + + + + + + + + + + + + - - - - - - diff --git a/vst2_bin/plugins/LindenbergResearch/res/Switch1.svg b/vst2_bin/plugins/LindenbergResearch/res/Switch1.svg index b025ee9a..5cbbd911 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/Switch1.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/Switch1.svg @@ -1,17 +1,22 @@ - - - - - + + + + + + + + + + + + + + + + - - - - - - diff --git a/vst2_bin/plugins/LindenbergResearch/res/ToggleKnob.svg b/vst2_bin/plugins/LindenbergResearch/res/ToggleKnob.svg index 67672e14..a177b16c 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/ToggleKnob.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/ToggleKnob.svg @@ -1,24 +1,23 @@ - - + + - - - - - - + + + + + - - + + - + - - + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/VCF.svg b/vst2_bin/plugins/LindenbergResearch/res/VCF.svg index e8c1b323..e0edf4fd 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/VCF.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/VCF.svg @@ -1,7 +1,7 @@ - - + + @@ -17,36 +17,36 @@ - - - - - + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - + + + + + @@ -203,29 +203,29 @@ - - + + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/VCO.svg b/vst2_bin/plugins/LindenbergResearch/res/VCO.svg index dddf021c..d7f4be2e 100644 --- a/vst2_bin/plugins/LindenbergResearch/res/VCO.svg +++ b/vst2_bin/plugins/LindenbergResearch/res/VCO.svg @@ -1,10 +1,10 @@ - + - + - + @@ -39,30 +39,259 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + @@ -129,4 +336,16 @@ + + + + + + + + + + + + diff --git a/vst2_bin/plugins/LindenbergResearch/res/Westcoast.svg b/vst2_bin/plugins/LindenbergResearch/res/Westcoast.svg new file mode 100644 index 00000000..13430e4d --- /dev/null +++ b/vst2_bin/plugins/LindenbergResearch/res/Westcoast.svg @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vst2_bin/plugins/Template_shared/plugin.dll b/vst2_bin/plugins/Template_shared/plugin.dll index 25d89e04..6eb970f8 100644 Binary files a/vst2_bin/plugins/Template_shared/plugin.dll and b/vst2_bin/plugins/Template_shared/plugin.dll differ diff --git a/vst2_bin/plugins/dBiz/plugin.dll b/vst2_bin/plugins/dBiz/plugin.dll index 2dccf57f..64d2d3e7 100644 Binary files a/vst2_bin/plugins/dBiz/plugin.dll and b/vst2_bin/plugins/dBiz/plugin.dll differ diff --git a/vst2_common_staticlibs.mk b/vst2_common_staticlibs.mk index c944d3d2..7f31f4b0 100644 --- a/vst2_common_staticlibs.mk +++ b/vst2_common_staticlibs.mk @@ -1,4 +1,5 @@ # statically linked plugins (note: dynamic linking won't work due to VCV Rack's architecture (global vars!)) +EXTRALIBS+= $(call plugin_lib,21kHz) EXTRALIBS+= $(call plugin_lib,Alikins) EXTRALIBS+= $(call plugin_lib,AS) EXTRALIBS+= $(call plugin_lib,AudibleInstruments) @@ -18,6 +19,7 @@ EXTRALIBS+= $(call plugin_lib,Fundamental) EXTRALIBS+= $(call plugin_lib,Gratrix) EXTRALIBS+= $(call plugin_lib,HetrickCV) EXTRALIBS+= $(call plugin_lib,huaba) +EXTRALIBS+= $(call plugin_lib,ImpromptuModular) EXTRALIBS+= $(call plugin_lib,JW-Modules) EXTRALIBS+= $(call plugin_lib,Koralfx-Modules) EXTRALIBS+= $(call plugin_lib,LindenbergResearch)