Skip to content

Commit

Permalink
Adds DiatonicCV module
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronstatic committed Nov 6, 2021
1 parent 8a7cdad commit f40555c
Show file tree
Hide file tree
Showing 9 changed files with 817 additions and 21 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ Generates a scale and outputs a polyphonic 1v/oct signal with 7 notes + quantize
* **Mode**: Chooses the scale mode (input range -4v to 4v)
* **Quantizers**: 4 Quantizers that will quantize a monophonic input to the selected scale

## DiatonicCV
![DiatonicCV](https://i.imgur.com/aJNhwkL.jpg "DiatonicCV")

Generates a diatonic chord from the provided scale, if one is provided via the poly input (otherwise C Major is used). Best used in combination with ScaleCV above.

* **Octave**: Which octave to transpose the chord to (1v/oct, input range -4v to 4v)
* **Chord**: Which chord degree in the scale (I - VII, input range 0v to 6v)
* **Type**: Which chord type (triad, seventh, ninth, input range 0v to 3v)
* **Inversion**: Chooses chord inversion (input range 0v to 4v)
* **Voicing**: Chooses the chord voicing (input range 0v to 4v, refer to ChordCV for more info)

## RandomNoteCV
![RandomNoteCV](https://i.imgur.com/xK91S79.jpg "RandomNoteCV")

Expand Down
6 changes: 6 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
"description": "Generates a scale",
"tags": ["Polyphonic","Tuner","Quantizer"]
},
{
"slug": "DiatonicCV",
"name": "DiatonicCV",
"description": "Generates diatonic chords from the provided scale",
"tags": ["Polyphonic","Tuner"]
},
{
"slug": "RandomNoteCV",
"name": "RandomNoteCV",
Expand Down
338 changes: 338 additions & 0 deletions res/DiatonicCV.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
219 changes: 219 additions & 0 deletions src/DiatonicCV.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#include "plugin.hpp"
#include "musiclib.hpp"

struct DiatonicCV : Module {
enum ParamIds {
OCTAVE_PARAM,
CHORD_PARAM,
TYPE_PARAM,
INVERSION_PARAM,
VOICING_PARAM,
NUM_PARAMS
};
enum InputIds {
POLY_INPUT,
OCTAVE_INPUT,
CHORD_INPUT,
TYPE_INPUT,
INVERSION_INPUT,
VOICING_INPUT,
NUM_INPUTS
};
enum OutputIds {
POLY_OUTPUT,
NUM_OUTPUTS
};
enum LightIds {
NUM_LIGHTS
};

int octave = 4;
int chord = 0;
int chord_type = 0;
bool inverted = false;
bool hasPoly = true;
int bass_note = 0;
float polyNotes_v[16] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f};
int polyNotes[16] = {48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48};
int polyChannels = 0;
int inversion = 0;
int voicing = 0;
struct chord playing_chord;
struct scale cmajor;

RefreshCounter refresh;

DiatonicCV() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
configParam(OCTAVE_PARAM, -4.0, 4.0, 0.0, "Octave");
configParam(CHORD_PARAM, 0, 6.0, 0.0, "Chord (I - VII)");
configParam(TYPE_PARAM, 0, 2.0, 0.0, "Chord Type");
configParam(INVERSION_PARAM, 0.0, 4.0, 0.0, "Inversion");
configParam(VOICING_PARAM, 0.0, 4.0, 0.0, "Voicing");

configInput(POLY_INPUT, "Polyphonic");
configInput(OCTAVE_INPUT, "Octave");
configInput(CHORD_INPUT, "Chord");
configInput(TYPE_INPUT, "Chord Type");
configInput(INVERSION_INPUT, "Inversion");
configInput(VOICING_INPUT, "Voicing");

configOutput(POLY_OUTPUT, "Polyphonic");

cmajor = get_scale(0,0);
}

void process(const ProcessArgs& args) override;
};

void DiatonicCV::process(const ProcessArgs &args){
if (refresh.processInputs()) {
if(inputs[POLY_INPUT].isConnected()){
polyChannels = inputs[POLY_INPUT].getChannels();
for (int c = 0; c < 16; c++) {
float v = inputs[POLY_INPUT].getVoltage(c);
polyNotes_v[c] = v;
polyNotes[c] = voltage_to_note_int(v);
}
//sort the notes in ascending order
std::sort(std::begin(polyNotes), polyNotes + polyChannels);
}else{
//Just make it C Major
polyChannels = 7;
for(int t=0; t<7; t++){
polyNotes[t] = cmajor.notes[t];
}
}

float octave_v = params[OCTAVE_PARAM].getValue();
if(inputs[OCTAVE_INPUT].isConnected()){
octave_v = inputs[OCTAVE_INPUT].getVoltage();
}
octave = (int)round(octave_v) + 4;

float chord_v = params[CHORD_PARAM].getValue();
if(inputs[CHORD_INPUT].isConnected()){
chord_v = inputs[CHORD_INPUT].getVoltage();
if(chord_v < 0.0f) chord_v = 0.0f;
if(chord_v > 7.0f) chord_v = 7.0f;
}
chord = (int)round(chord_v);

float type_v = params[TYPE_PARAM].getValue();
if(inputs[TYPE_INPUT].isConnected()){
type_v = inputs[TYPE_INPUT].getVoltage();
if(type_v < 0.0f) type_v = 0.0f;
if(type_v > 3.0f) type_v = 3.0f;
}
chord_type = (int)round(type_v);

//inversion
inversion = (int)round(params[INVERSION_PARAM].getValue());
if(inputs[INVERSION_PARAM].isConnected()){
inversion = (int)clamp(round(inputs[INVERSION_PARAM].getVoltage()),0.0f,3.0f);
}

//voicing
voicing = (int)round(params[VOICING_PARAM].getValue());
if(inputs[VOICING_PARAM].isConnected()){
voicing = (int)clamp(round(inputs[VOICING_PARAM].getVoltage()),0.0f,4.0f);
}

//Make the chord
playing_chord = get_diatonic_chord(polyNotes, polyChannels, octave, chord, chord_type, inversion, voicing);
}

if (refresh.processLights()) {

}

outputs[POLY_OUTPUT].setChannels(playing_chord.num_notes);
for(int t=0; t<playing_chord.num_notes; t++){
outputs[POLY_OUTPUT].setVoltage(note_to_voltage(playing_chord.notes[t]),t);
}
}


struct DiatonicCVWidget : ModuleWidget {
struct ChordDisplayWidget : TransparentWidget {
DiatonicCV* module;
char text[10];

ChordDisplayWidget(Vec _pos, Vec _size, DiatonicCV* _module) {
box.size = _size;
box.pos = _pos.minus(_size.div(2));
module = _module;
}

void draw(const DrawArgs &args) override {
std::shared_ptr<Font> font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/PixelOperator.ttf"));
if(font){
NVGcolor textColor = prepareDisplay(args.vg, &box, 22);
nvgFontFaceId(args.vg, font->handle);
nvgTextLetterSpacing(args.vg, -1.5);
nvgTextAlign(args.vg, NVG_ALIGN_CENTER);

Vec textPos = Vec(box.size.x/2, 21.0f);
nvgFillColor(args.vg, textColor);

if (module != NULL && module->playing_chord.num_notes > 2){
detect_chord_name_simple(module->playing_chord,text);
}else{
snprintf(text, 9, " ");
}

nvgText(args.vg, textPos.x, textPos.y, text, NULL);
}
}

};

DiatonicCVWidget(DiatonicCV* module) {
setModule(module);
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DiatonicCV.svg")));

addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

const int centerX = box.size.x / 2;

ChordDisplayWidget* display = new ChordDisplayWidget(Vec(centerX, 55), Vec(76, 29), module);
addChild(display);

addInput(createInputCentered<PJ301MPort>(Vec(centerX, 95), module, DiatonicCV::POLY_INPUT));

const int offsetXL = 40;
const int spacingY = 45;
const int y1 = 134;
const int y2 = y1 + spacingY;
const int y3 = y2 + spacingY;


addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y1), module, DiatonicCV::OCTAVE_PARAM));
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y1), module, DiatonicCV::OCTAVE_INPUT));

addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y2), module, DiatonicCV::CHORD_PARAM));
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y2), module, DiatonicCV::CHORD_INPUT));

addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y3), module, DiatonicCV::TYPE_PARAM));
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y3), module, DiatonicCV::TYPE_INPUT));

static const int offsetX2 = 14;
static const int posY = 269;

const int offsetXL2 = 42;

addParam(createParamCentered<RoundSmallBlackKnob>(Vec(centerX - offsetX2,posY), module, DiatonicCV::INVERSION_PARAM));
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL2, posY), module, DiatonicCV::INVERSION_INPUT));

addParam(createParamCentered<RoundSmallBlackKnob>(Vec(centerX + offsetX2,posY), module, DiatonicCV::VOICING_PARAM));
addInput(createInputCentered<PJ301MPort>(Vec(centerX + offsetXL2, posY), module, DiatonicCV::VOICING_INPUT));

addOutput(createOutputCentered<PJ301MPort>(Vec(centerX, 332), module, DiatonicCV::POLY_OUTPUT));
}
};


Model* modelDiatonicCV = createModel<DiatonicCV, DiatonicCVWidget>("DiatonicCV");
2 changes: 2 additions & 0 deletions src/RandomNoteCV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ void RandomNoteCV::process(const ProcessArgs &args){
polyNotes_v[c] = v;
polyNotes[c] = voltage_to_note_int(v);
}
//sort the notes in ascending order
std::sort(std::begin(polyNotes), polyNotes + polyChannels);
}else{
hasPoly = false;
}
Expand Down
Loading

0 comments on commit f40555c

Please sign in to comment.