Browse Source

Added MotionMTR prototype

pull/42/head
hemmer 2 years ago
parent
commit
42e275464a
6 changed files with 1588 additions and 0 deletions
  1. +71
    -0
      .github/workflows/build-plugin.yml
  2. +13
    -0
      plugin.json
  3. +1294
    -0
      res/panels/MotionMTR.svg
  4. +208
    -0
      src/MotionMTR.cpp
  5. +1
    -0
      src/plugin.cpp
  6. +1
    -0
      src/plugin.hpp

+ 71
- 0
.github/workflows/build-plugin.yml View File

@@ -0,0 +1,71 @@
name: Build VCV Rack Plugin
on: [push, pull_request]

env:
rack-sdk-version: 2.2.0

defaults:
run:
shell: bash

jobs:
build:
name: ${{ matrix.config.os }}
runs-on: ${{ matrix.config.os }}
strategy:
matrix:
config:
- os: ubuntu-latest
arch: lin
compiler: cc
install-dependencies: |
sudo apt-get update && sudo apt-get install -y libglu-dev
- os: macos-latest
arch: mac
compiler: cc
install-dependencies: |
brew install mesa
- os: windows-latest
arch: win
compiler: gcc
install-dependencies: |
choco install --no-progress -y zip
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Install Rack SDK
run: |
curl -o sdk.zip https://vcvrack.com/downloads/Rack-SDK-${{ env.rack-sdk-version }}-${{ matrix.config.arch }}.zip
unzip sdk.zip
- name: Install Dependencies
run: |
${{ matrix.config.install-dependencies }}
- name: Build
env:
RACK_DIR: Rack-SDK
CC: ${{ matrix.config.compiler }}
run: |
make dist
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
path: dist
name: ${{ matrix.config.arch }}.zip

publish:
name: Publish plugin
runs-on: ubuntu-18.04
needs: build
steps:
- uses: actions/download-artifact@v2
with:
path: _artifacts
- uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "latest"
prerelease: true
title: "Development Build"
files: |
_artifacts/**/*.vcvplugin

+ 13
- 0
plugin.json View File

@@ -269,6 +269,19 @@
"Oscillator",
"Waveshaper"
]
},
{
"slug": "MotionMTR",
"name": "MotionMTR",
"description": "CV and audio utility / realtime visualizer",
"manualUrl": "https://www.befaco.org/motion_mtr/",
"modularGridUrl": "https://www.modulargrid.net/e/befaco-motion-mtr",
"tags": [
"Attenuator",
"Hardware clone",
"Mixer",
"Visual"
]
}
]
}

+ 1294
- 0
res/panels/MotionMTR.svg
File diff suppressed because it is too large
View File


+ 208
- 0
src/MotionMTR.cpp View File

@@ -0,0 +1,208 @@
#include "plugin.hpp"


struct MotionMTR : Module {
enum ParamId {
MODE1_PARAM,
CTRL_1_PARAM,
MODE2_PARAM,
CTRL_2_PARAM,
MODE3_PARAM,
CTRL_3_PARAM,
PARAMS_LEN
};
enum InputId {
IN1_INPUT,
IN2_INPUT,
IN3_INPUT,
INPUTS_LEN
};
enum OutputId {
OUT1_OUTPUT,
OUT2_OUTPUT,
OUT3_OUTPUT,
OUTPUTS_LEN
};
enum LightDisplayType {
CV_INV,
CV_ATT,
AUDIO
};

const std::vector<std::string> modeLabels = {"CV Inversion", "CV Attentuation", "Audio"};
static const int NUM_LIGHTS_PER_DIAL = 20;

enum LightId {
ENUMS(LIGHT_1, NUM_LIGHTS_PER_DIAL * 3),
ENUMS(LIGHT_2, NUM_LIGHTS_PER_DIAL * 3),
ENUMS(LIGHT_3, NUM_LIGHTS_PER_DIAL * 3),
LIGHTS_LEN
};

dsp::VuMeter vuBar[3];

const int updateLEDRate = 16;
dsp::ClockDivider sliderUpdate;

MotionMTR() {
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
configSwitch(MODE1_PARAM, 0.f, 2.f, 0.f, "Channel 1 mode", modeLabels);
configParam(CTRL_1_PARAM, 0.f, 1.f, 0.f, "Channel 1 gain");
configSwitch(MODE2_PARAM, 0.f, 2.f, 0.f, "Channel 2 mode", modeLabels);
configParam(CTRL_2_PARAM, 0.f, 1.f, 0.f, "Channel 2 gain");
configSwitch(MODE3_PARAM, 0.f, 2.f, 0.f, "Channel 3 mode", modeLabels);
configParam(CTRL_3_PARAM, 0.f, 1.f, 0.f, "Channel 3 gain");
auto in1 = configInput(IN1_INPUT, "Channel 1");
in1->description = "Normalled to 10V";
auto in2 = configInput(IN2_INPUT, "Channel 2");
in2->description = "Normalled to 10V";
auto in3 = configInput(IN3_INPUT, "Channel 3");
in3->description = "Normalled to 10V";

configOutput(OUT1_OUTPUT, "Channel 1");
configOutput(OUT2_OUTPUT, "Channel 2");
configOutput(OUT3_OUTPUT, "Channel 3 (Mix)");

vuBar[0].dBInterval = 3;
vuBar[1].dBInterval = 3;
vuBar[2].dBInterval = 3;

// only poll EQ sliders every 16 samples
sliderUpdate.setDivision(updateLEDRate);
}

void process(const ProcessArgs& args) override {

const float in1 = inputs[IN1_INPUT].getNormalVoltage(10.f);
const float in2 = inputs[IN2_INPUT].getNormalVoltage(10.f);
const float in3 = inputs[IN3_INPUT].getNormalVoltage(10.f);

const LightDisplayType mode1 = (LightDisplayType) params[MODE1_PARAM].getValue();
const LightDisplayType mode2 = (LightDisplayType) params[MODE2_PARAM].getValue();
const LightDisplayType mode3 = (LightDisplayType) params[MODE3_PARAM].getValue();

const float out1 = in1 * params[CTRL_1_PARAM].getValue() * (mode1 == CV_INV ? -1 : +1);
const float out2 = in2 * params[CTRL_2_PARAM].getValue() * (mode2 == CV_INV ? -1 : +1);
float out3 = in3 * params[CTRL_3_PARAM].getValue() * (mode3 == CV_INV ? -1 : +1);

if (!outputs[OUT1_OUTPUT].isConnected()) {
out3 += out1;
}
if (!outputs[OUT2_OUTPUT].isConnected()) {
out3 += out2;
}

if (sliderUpdate.process()) {
lightsForSignal(mode1, LIGHT_1, out1, args, 0);
lightsForSignal(mode2, LIGHT_2, out2, args, 1);
lightsForSignal(mode3, LIGHT_3, out3, args, 2);
}

outputs[OUT1_OUTPUT].setVoltage(out1);
outputs[OUT2_OUTPUT].setVoltage(out2);
outputs[OUT3_OUTPUT].setVoltage(out3);
}

void setLightRGB(int lightId, const ProcessArgs& args, float R, float G, float B) {
// inverse time constant for LED smoothing
const float lambda = 10.f * updateLEDRate;

lights[lightId + 0].setBrightnessSmooth(R, args.sampleTime, lambda);
lights[lightId + 1].setBrightnessSmooth(G, args.sampleTime, lambda);
lights[lightId + 2].setBrightnessSmooth(B, args.sampleTime, lambda);
}

void lightsForSignal(LightDisplayType type, const LightId lightId, float signal, const ProcessArgs& args, const int channel) {

if (type == AUDIO) {
setLightRGB(lightId, args, 0.f, 1.0f, 0.f);

vuBar[channel].setValue(signal / 10.0f);

for (int i = 1; i < NUM_LIGHTS_PER_DIAL; i++) {
const float value = vuBar[channel].getBrightness(NUM_LIGHTS_PER_DIAL - i);
if (i < 15) {
// green
setLightRGB(lightId + 3 * i, args, 0.f, value, 0.f);
}
else if (i < NUM_LIGHTS_PER_DIAL - 1) {
// yellow
setLightRGB(lightId + 3 * i, args, value, 0.65 * value, 0.f);
}
else {
// red
setLightRGB(lightId + 3 * i, args, value, 0.f, 0.f);
}
}
}
else {
setLightRGB(lightId, args, 0.82f, 0.0f, 0.82f);

if (signal >= 0) {
for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) {
float value = 0.5f * (signal > (10 * (i + 1.) / (NUM_LIGHTS_PER_DIAL + 1)));
// purple
setLightRGB(lightId + 3 * i, args, 0.82f * value, 0.0f, 0.82f * value);
}
}
else {
for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) {
float value = (signal < (-10 * (NUM_LIGHTS_PER_DIAL - i + 1.) / (NUM_LIGHTS_PER_DIAL + 1.)));
setLightRGB(lightId + 3 * i, args, value, 0.65f * value, 0.f);
}
}
}
}

};


struct MotionMTRWidget : ModuleWidget {
MotionMTRWidget(MotionMTR* module) {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/MotionMTR.svg")));

addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

addParam(createParam<CKSSThree>(mm2px(Vec(1.298, 17.851)), module, MotionMTR::MODE1_PARAM));
addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(18.217, 22.18)), module, MotionMTR::CTRL_1_PARAM));
addParam(createParam<CKSSThree>(mm2px(Vec(23.762, 46.679)), module, MotionMTR::MODE2_PARAM));
addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(11.777, 50.761)), module, MotionMTR::CTRL_2_PARAM));
addParam(createParam<CKSSThree>(mm2px(Vec(1.34, 74.461)), module, MotionMTR::MODE3_PARAM));
addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(18.31, 78.89)), module, MotionMTR::CTRL_3_PARAM));

addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.008, 100.315)), module, MotionMTR::IN1_INPUT));
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(14.993, 100.315)), module, MotionMTR::IN2_INPUT));
addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(24.978, 100.315)), module, MotionMTR::IN3_INPUT));

addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 113.207)), module, MotionMTR::OUT1_OUTPUT));
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(14.978, 113.185)), module, MotionMTR::OUT2_OUTPUT));
addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.014, 113.207)), module, MotionMTR::OUT3_OUTPUT));


struct LightRingDetails {
MotionMTR::LightId startingId;
float x, y; // centre
} ;

std::vector<LightRingDetails> ringDetails = {
{MotionMTR::LIGHT_1, 18.217, 22.18},
{MotionMTR::LIGHT_2, 11.777, 50.761},
{MotionMTR::LIGHT_3, 18.217, 78.85}
};

const float R = 9.65; // mm
for (auto detailForRing : ringDetails) {
for (int i = 0; i < MotionMTR::NUM_LIGHTS_PER_DIAL; ++i) {
float theta = 2 * M_PI * i / MotionMTR::NUM_LIGHTS_PER_DIAL;
float x = detailForRing.x + sin(theta) * R;
float y = detailForRing.y - cos(theta) * R;
addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(x, y)), module, detailForRing.startingId + 3 * i));
}
}
}
};


Model* modelMotionMTR = createModel<MotionMTR, MotionMTRWidget>("MotionMTR");

+ 1
- 0
src/plugin.cpp View File

@@ -26,4 +26,5 @@ void init(rack::Plugin *p) {
p->addModel(modelNoisePlethora);
p->addModel(modelChannelStrip);
p->addModel(modelPonyVCO);
p->addModel(modelMotionMTR);
}

+ 1
- 0
src/plugin.hpp View File

@@ -27,6 +27,7 @@ extern Model* modelMex;
extern Model* modelNoisePlethora;
extern Model* modelChannelStrip;
extern Model* modelPonyVCO;
extern Model* modelMotionMTR;

struct Knurlie : SvgScrew {
Knurlie() {


Loading…
Cancel
Save