Skip to content

Commit f40555c

Browse files
committed
Adds DiatonicCV module
1 parent 8a7cdad commit f40555c

9 files changed

Lines changed: 817 additions & 21 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ Generates a scale and outputs a polyphonic 1v/oct signal with 7 notes + quantize
3333
* **Mode**: Chooses the scale mode (input range -4v to 4v)
3434
* **Quantizers**: 4 Quantizers that will quantize a monophonic input to the selected scale
3535

36+
## DiatonicCV
37+
![DiatonicCV](https://i.imgur.com/aJNhwkL.jpg "DiatonicCV")
38+
39+
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.
40+
41+
* **Octave**: Which octave to transpose the chord to (1v/oct, input range -4v to 4v)
42+
* **Chord**: Which chord degree in the scale (I - VII, input range 0v to 6v)
43+
* **Type**: Which chord type (triad, seventh, ninth, input range 0v to 3v)
44+
* **Inversion**: Chooses chord inversion (input range 0v to 4v)
45+
* **Voicing**: Chooses the chord voicing (input range 0v to 4v, refer to ChordCV for more info)
46+
3647
## RandomNoteCV
3748
![RandomNoteCV](https://i.imgur.com/xK91S79.jpg "RandomNoteCV")
3849

plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
"description": "Generates a scale",
2626
"tags": ["Polyphonic","Tuner","Quantizer"]
2727
},
28+
{
29+
"slug": "DiatonicCV",
30+
"name": "DiatonicCV",
31+
"description": "Generates diatonic chords from the provided scale",
32+
"tags": ["Polyphonic","Tuner"]
33+
},
2834
{
2935
"slug": "RandomNoteCV",
3036
"name": "RandomNoteCV",

res/DiatonicCV.svg

Lines changed: 338 additions & 0 deletions
Loading

src/DiatonicCV.cpp

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#include "plugin.hpp"
2+
#include "musiclib.hpp"
3+
4+
struct DiatonicCV : Module {
5+
enum ParamIds {
6+
OCTAVE_PARAM,
7+
CHORD_PARAM,
8+
TYPE_PARAM,
9+
INVERSION_PARAM,
10+
VOICING_PARAM,
11+
NUM_PARAMS
12+
};
13+
enum InputIds {
14+
POLY_INPUT,
15+
OCTAVE_INPUT,
16+
CHORD_INPUT,
17+
TYPE_INPUT,
18+
INVERSION_INPUT,
19+
VOICING_INPUT,
20+
NUM_INPUTS
21+
};
22+
enum OutputIds {
23+
POLY_OUTPUT,
24+
NUM_OUTPUTS
25+
};
26+
enum LightIds {
27+
NUM_LIGHTS
28+
};
29+
30+
int octave = 4;
31+
int chord = 0;
32+
int chord_type = 0;
33+
bool inverted = false;
34+
bool hasPoly = true;
35+
int bass_note = 0;
36+
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};
37+
int polyNotes[16] = {48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48};
38+
int polyChannels = 0;
39+
int inversion = 0;
40+
int voicing = 0;
41+
struct chord playing_chord;
42+
struct scale cmajor;
43+
44+
RefreshCounter refresh;
45+
46+
DiatonicCV() {
47+
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
48+
configParam(OCTAVE_PARAM, -4.0, 4.0, 0.0, "Octave");
49+
configParam(CHORD_PARAM, 0, 6.0, 0.0, "Chord (I - VII)");
50+
configParam(TYPE_PARAM, 0, 2.0, 0.0, "Chord Type");
51+
configParam(INVERSION_PARAM, 0.0, 4.0, 0.0, "Inversion");
52+
configParam(VOICING_PARAM, 0.0, 4.0, 0.0, "Voicing");
53+
54+
configInput(POLY_INPUT, "Polyphonic");
55+
configInput(OCTAVE_INPUT, "Octave");
56+
configInput(CHORD_INPUT, "Chord");
57+
configInput(TYPE_INPUT, "Chord Type");
58+
configInput(INVERSION_INPUT, "Inversion");
59+
configInput(VOICING_INPUT, "Voicing");
60+
61+
configOutput(POLY_OUTPUT, "Polyphonic");
62+
63+
cmajor = get_scale(0,0);
64+
}
65+
66+
void process(const ProcessArgs& args) override;
67+
};
68+
69+
void DiatonicCV::process(const ProcessArgs &args){
70+
if (refresh.processInputs()) {
71+
if(inputs[POLY_INPUT].isConnected()){
72+
polyChannels = inputs[POLY_INPUT].getChannels();
73+
for (int c = 0; c < 16; c++) {
74+
float v = inputs[POLY_INPUT].getVoltage(c);
75+
polyNotes_v[c] = v;
76+
polyNotes[c] = voltage_to_note_int(v);
77+
}
78+
//sort the notes in ascending order
79+
std::sort(std::begin(polyNotes), polyNotes + polyChannels);
80+
}else{
81+
//Just make it C Major
82+
polyChannels = 7;
83+
for(int t=0; t<7; t++){
84+
polyNotes[t] = cmajor.notes[t];
85+
}
86+
}
87+
88+
float octave_v = params[OCTAVE_PARAM].getValue();
89+
if(inputs[OCTAVE_INPUT].isConnected()){
90+
octave_v = inputs[OCTAVE_INPUT].getVoltage();
91+
}
92+
octave = (int)round(octave_v) + 4;
93+
94+
float chord_v = params[CHORD_PARAM].getValue();
95+
if(inputs[CHORD_INPUT].isConnected()){
96+
chord_v = inputs[CHORD_INPUT].getVoltage();
97+
if(chord_v < 0.0f) chord_v = 0.0f;
98+
if(chord_v > 7.0f) chord_v = 7.0f;
99+
}
100+
chord = (int)round(chord_v);
101+
102+
float type_v = params[TYPE_PARAM].getValue();
103+
if(inputs[TYPE_INPUT].isConnected()){
104+
type_v = inputs[TYPE_INPUT].getVoltage();
105+
if(type_v < 0.0f) type_v = 0.0f;
106+
if(type_v > 3.0f) type_v = 3.0f;
107+
}
108+
chord_type = (int)round(type_v);
109+
110+
//inversion
111+
inversion = (int)round(params[INVERSION_PARAM].getValue());
112+
if(inputs[INVERSION_PARAM].isConnected()){
113+
inversion = (int)clamp(round(inputs[INVERSION_PARAM].getVoltage()),0.0f,3.0f);
114+
}
115+
116+
//voicing
117+
voicing = (int)round(params[VOICING_PARAM].getValue());
118+
if(inputs[VOICING_PARAM].isConnected()){
119+
voicing = (int)clamp(round(inputs[VOICING_PARAM].getVoltage()),0.0f,4.0f);
120+
}
121+
122+
//Make the chord
123+
playing_chord = get_diatonic_chord(polyNotes, polyChannels, octave, chord, chord_type, inversion, voicing);
124+
}
125+
126+
if (refresh.processLights()) {
127+
128+
}
129+
130+
outputs[POLY_OUTPUT].setChannels(playing_chord.num_notes);
131+
for(int t=0; t<playing_chord.num_notes; t++){
132+
outputs[POLY_OUTPUT].setVoltage(note_to_voltage(playing_chord.notes[t]),t);
133+
}
134+
}
135+
136+
137+
struct DiatonicCVWidget : ModuleWidget {
138+
struct ChordDisplayWidget : TransparentWidget {
139+
DiatonicCV* module;
140+
char text[10];
141+
142+
ChordDisplayWidget(Vec _pos, Vec _size, DiatonicCV* _module) {
143+
box.size = _size;
144+
box.pos = _pos.minus(_size.div(2));
145+
module = _module;
146+
}
147+
148+
void draw(const DrawArgs &args) override {
149+
std::shared_ptr<Font> font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/PixelOperator.ttf"));
150+
if(font){
151+
NVGcolor textColor = prepareDisplay(args.vg, &box, 22);
152+
nvgFontFaceId(args.vg, font->handle);
153+
nvgTextLetterSpacing(args.vg, -1.5);
154+
nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
155+
156+
Vec textPos = Vec(box.size.x/2, 21.0f);
157+
nvgFillColor(args.vg, textColor);
158+
159+
if (module != NULL && module->playing_chord.num_notes > 2){
160+
detect_chord_name_simple(module->playing_chord,text);
161+
}else{
162+
snprintf(text, 9, " ");
163+
}
164+
165+
nvgText(args.vg, textPos.x, textPos.y, text, NULL);
166+
}
167+
}
168+
169+
};
170+
171+
DiatonicCVWidget(DiatonicCV* module) {
172+
setModule(module);
173+
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DiatonicCV.svg")));
174+
175+
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
176+
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
177+
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
178+
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
179+
180+
const int centerX = box.size.x / 2;
181+
182+
ChordDisplayWidget* display = new ChordDisplayWidget(Vec(centerX, 55), Vec(76, 29), module);
183+
addChild(display);
184+
185+
addInput(createInputCentered<PJ301MPort>(Vec(centerX, 95), module, DiatonicCV::POLY_INPUT));
186+
187+
const int offsetXL = 40;
188+
const int spacingY = 45;
189+
const int y1 = 134;
190+
const int y2 = y1 + spacingY;
191+
const int y3 = y2 + spacingY;
192+
193+
194+
addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y1), module, DiatonicCV::OCTAVE_PARAM));
195+
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y1), module, DiatonicCV::OCTAVE_INPUT));
196+
197+
addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y2), module, DiatonicCV::CHORD_PARAM));
198+
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y2), module, DiatonicCV::CHORD_INPUT));
199+
200+
addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y3), module, DiatonicCV::TYPE_PARAM));
201+
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y3), module, DiatonicCV::TYPE_INPUT));
202+
203+
static const int offsetX2 = 14;
204+
static const int posY = 269;
205+
206+
const int offsetXL2 = 42;
207+
208+
addParam(createParamCentered<RoundSmallBlackKnob>(Vec(centerX - offsetX2,posY), module, DiatonicCV::INVERSION_PARAM));
209+
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL2, posY), module, DiatonicCV::INVERSION_INPUT));
210+
211+
addParam(createParamCentered<RoundSmallBlackKnob>(Vec(centerX + offsetX2,posY), module, DiatonicCV::VOICING_PARAM));
212+
addInput(createInputCentered<PJ301MPort>(Vec(centerX + offsetXL2, posY), module, DiatonicCV::VOICING_INPUT));
213+
214+
addOutput(createOutputCentered<PJ301MPort>(Vec(centerX, 332), module, DiatonicCV::POLY_OUTPUT));
215+
}
216+
};
217+
218+
219+
Model* modelDiatonicCV = createModel<DiatonicCV, DiatonicCVWidget>("DiatonicCV");

src/RandomNoteCV.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ void RandomNoteCV::process(const ProcessArgs &args){
6868
polyNotes_v[c] = v;
6969
polyNotes[c] = voltage_to_note_int(v);
7070
}
71+
//sort the notes in ascending order
72+
std::sort(std::begin(polyNotes), polyNotes + polyChannels);
7173
}else{
7274
hasPoly = false;
7375
}

0 commit comments

Comments
 (0)