diff --git a/README.md b/README.md
index b5dc078..4671472 100644
--- a/README.md
+++ b/README.md
@@ -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")
diff --git a/plugin.json b/plugin.json
index 9b65a67..b2dd68c 100644
--- a/plugin.json
+++ b/plugin.json
@@ -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",
diff --git a/res/DiatonicCV.svg b/res/DiatonicCV.svg
new file mode 100644
index 0000000..09b221d
--- /dev/null
+++ b/res/DiatonicCV.svg
@@ -0,0 +1,338 @@
+
+
+
diff --git a/src/DiatonicCV.cpp b/src/DiatonicCV.cpp
new file mode 100644
index 0000000..34f67ce
--- /dev/null
+++ b/src/DiatonicCV.cpp
@@ -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 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(Vec(RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
+ addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
+ addChild(createWidget(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(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(Vec(centerX,y1), module, DiatonicCV::OCTAVE_PARAM));
+ addInput(createInputCentered(Vec(centerX - offsetXL, y1), module, DiatonicCV::OCTAVE_INPUT));
+
+ addParam(createParamCentered(Vec(centerX,y2), module, DiatonicCV::CHORD_PARAM));
+ addInput(createInputCentered(Vec(centerX - offsetXL, y2), module, DiatonicCV::CHORD_INPUT));
+
+ addParam(createParamCentered(Vec(centerX,y3), module, DiatonicCV::TYPE_PARAM));
+ addInput(createInputCentered(Vec(centerX - offsetXL, y3), module, DiatonicCV::TYPE_INPUT));
+
+ static const int offsetX2 = 14;
+ static const int posY = 269;
+
+ const int offsetXL2 = 42;
+
+ addParam(createParamCentered(Vec(centerX - offsetX2,posY), module, DiatonicCV::INVERSION_PARAM));
+ addInput(createInputCentered(Vec(centerX - offsetXL2, posY), module, DiatonicCV::INVERSION_INPUT));
+
+ addParam(createParamCentered(Vec(centerX + offsetX2,posY), module, DiatonicCV::VOICING_PARAM));
+ addInput(createInputCentered(Vec(centerX + offsetXL2, posY), module, DiatonicCV::VOICING_INPUT));
+
+ addOutput(createOutputCentered(Vec(centerX, 332), module, DiatonicCV::POLY_OUTPUT));
+ }
+};
+
+
+Model* modelDiatonicCV = createModel("DiatonicCV");
diff --git a/src/RandomNoteCV.cpp b/src/RandomNoteCV.cpp
index 156e691..554522b 100644
--- a/src/RandomNoteCV.cpp
+++ b/src/RandomNoteCV.cpp
@@ -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;
}
diff --git a/src/musiclib.cpp b/src/musiclib.cpp
index b20f783..a2d679d 100644
--- a/src/musiclib.cpp
+++ b/src/musiclib.cpp
@@ -25,19 +25,30 @@ int voltage_to_note_int(float value) {
return semi;
}
-
+//Intervals (for readability)
+const int MINOR_SECOND = 1;
+const int MAJOR_SECOND = 2;
+const int MINOR_THIRD = 3;
+const int MAJOR_THIRD = 4;
+const int PERFECT_FOURTH = 5;
+const int DIMINISHED_FIFTH = 6;
+const int PERFECT_FIFTH = 7;
+const int AUGMENTED_FIFTH = 8;
+const int MAJOR_SIXTH = 9;
+const int MINOR_SEVENTH = 10;
+const int MAJOR_SEVENTH = 11;
//Chords
static const int CHORD_DEGREES[9][3] = {
- {4,7,0}, //Major
- {3,7,0}, //Minor
- {4,7,10}, //Dominant 7
- {3,7,10}, //Minor 7
- {4,7,11}, //Major 7
- {2,7,0}, //sus2
- {5,7,0}, //sus4
- {3,6,0}, //dim
- {4,8,0} //aug
+ {MAJOR_THIRD,PERFECT_FIFTH,0}, //Major
+ {MINOR_THIRD,PERFECT_FIFTH,0}, //Minor
+ {MAJOR_THIRD,PERFECT_FIFTH,MINOR_SEVENTH}, //Dominant 7
+ {MINOR_THIRD,PERFECT_FIFTH,MINOR_SEVENTH}, //Minor 7
+ {MAJOR_THIRD,PERFECT_FIFTH,MAJOR_SEVENTH}, //Major 7
+ {MAJOR_SECOND,PERFECT_FIFTH,0}, //sus2
+ {PERFECT_FOURTH,PERFECT_FIFTH,0}, //sus4
+ {MINOR_THIRD,DIMINISHED_FIFTH,0}, //dim
+ {MAJOR_THIRD,AUGMENTED_FIFTH,0} //aug
};
struct chord get_chord(int root_note, int type, int inversion, int voicing){
@@ -50,6 +61,10 @@ struct chord get_chord(int root_note, int type, int inversion, int voicing){
int seventh_note = root_note + degrees[2];
if(degrees[2] == 0){ //Has 3 notes
+ return_chord.notes_pre[0] = root_note;
+ return_chord.notes_pre[1] = third_note;
+ return_chord.notes_pre[2] = fifth_note;
+
if(inversion == 3){
inversion = 2;
}
@@ -87,6 +102,11 @@ struct chord get_chord(int root_note, int type, int inversion, int voicing){
return_chord.num_notes = 3;
}else{ //Has 4 notes
+ return_chord.notes_pre[0] = root_note;
+ return_chord.notes_pre[1] = third_note;
+ return_chord.notes_pre[2] = fifth_note;
+ return_chord.notes_pre[3] = seventh_note;
+
if(inversion == 1){
root_note += 12;
int tmp = root_note;
@@ -138,20 +158,126 @@ struct chord get_chord(int root_note, int type, int inversion, int voicing){
return_chord.notes[3] = seventh_note;
return_chord.num_notes = 4;
}
+ return_chord.inversion = inversion;
return return_chord;
}
+struct chord get_diatonic_chord(int* notes, int num_notes, int octave, int chord, int type, int inversion, int voicing){
+ struct chord return_chord;
+
+ int chord_length = 3 + type;
+ int actual_length = 0;
+ if(inversion > chord_length - 1) inversion = chord_length - 1;
+ return_chord.inversion = inversion;
+
+ //Build the chord from the scale
+
+ for(int t=0; t < chord_length; t++){
+ int index = chord + (t * 2);
+ int transpose = 0;
+
+ //Wrap if needed
+ if(index >= num_notes){
+ index -= num_notes;
+ transpose++;
+ }
+
+ //Wrap again if needed
+ if(index >= num_notes){
+ index -= num_notes;
+ transpose++;
+ }
+
+ if(index < num_notes){
+ return_chord.notes_pre[t] = (transpose * 12) + notes[index];
+ actual_length++;
+ }
+ }
+
+ return_chord.num_notes = actual_length;
+
+ //Transpose the chord
+ for(int t=0; t < actual_length; t++){
+ return_chord.notes[t] = (octave * 12) + return_chord.notes_pre[t];
+ }
+
+ //Perform inversion
+ if(inversion > 0 && actual_length > 1){
+ int new_notes[6];
+ int index = inversion;
+ for(int t=0; t < actual_length; t++){
+ int i = index;
+ if(i >= actual_length) {
+ i -= actual_length;
+ new_notes[t] = return_chord.notes[i] + 12;
+ }else{
+ new_notes[t] = return_chord.notes[i];
+ }
+ index++;
+ }
+ for(int t=0; t < actual_length; t++){
+ return_chord.notes[t] = new_notes[t];
+ }
+ }
+
+ //Voice the chord
+ if(voicing > 0 && actual_length > 2){
+ int root_note = return_chord.notes[0];
+ if(root_note > 11){
+ if(voicing == 1){
+ return_chord.notes[0] -= 12;
+ }
+ if(voicing == 2){
+ return_chord.notes[0] -= 12;
+ return_chord.notes[2] -= 12;
+ }
+ if(voicing == 3){
+ return_chord.notes[0] -= 12;
+ return_chord.notes[1] += 12;
+ }
+ if(voicing == 4){
+ return_chord.notes[0] -= 12;
+ if(actual_length > 3){
+ return_chord.notes[3] -= 12;
+ }
+ }
+ }
+ }
+
+ return return_chord;
+}
+
static const char * NOTE_NAMES[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
static const char * CHORD_TYPE_NAMES[] = {
- "",
- "m",
- "7",
- "m7",
- "maj7",
- "sus2",
- "sus4",
- "dim",
- "+"
+ "", //0
+ "m", //1
+ "7", //2
+ "m7", //3
+ "maj7", //4
+ "sus2", //5
+ "sus4", //6
+ "dim", //7
+ "+", //8
+ "9", //9
+ "m9", //10
+ "maj9", //11
+ "+M7", //12
+ "+M9", //13
+ "mM7", //14
+ "mM9", //15
+ "dim7", //16
+ "dim9", //17
+ "m7b5", //18
+ "+7", //19
+ "mM7b5", //20
+ "7b5", //21
+ "M7b5", //22
+ "7b9", //23
+ "6/9", //24
+ "mb9", //25
+ "maj79", //26
+ "dimb9", //27
+ "dim9" //28
};
void get_chord_name(int root_semi, int chord_type, bool inverted, int bass_note, char* text) {
@@ -162,6 +288,92 @@ void get_chord_name(int root_semi, int chord_type, bool inverted, int bass_note,
sprintf(text, "%s%s%s", NOTE_NAMES[root_semi], CHORD_TYPE_NAMES[chord_type], inv);
}
+void detect_chord_name_simple(struct chord chord, char* text){
+ //Detect the chord type (if we can)
+ int chord_type = 0; //default to major
+
+ if(chord.num_notes > 2){
+ //don't bother unless theres at least 3 notes
+
+ //build an array of intervals
+ int intervals[5] = {0,0,0,0,0};
+ for(int t=1; t < chord.num_notes; t++){
+ intervals[t-1] = chord.notes_pre[t] - chord.notes_pre[t-1];
+ }
+
+
+
+ //Is there a better way to do this? if you know please submit a pull request xD
+ if(chord.num_notes > 3){
+ //Its a 7 or 9 chord
+ if(intervals[0] == MAJOR_THIRD){
+ if(intervals[1] == MINOR_THIRD){
+ //major triad
+ if(intervals[2] == MINOR_THIRD) {
+ chord_type = 2; //dominant seventh
+ if(chord.num_notes == 5 && intervals[3] == MINOR_THIRD) chord_type = 23; //dominant minor ninth
+ if(chord.num_notes == 5 && intervals[3] == MAJOR_THIRD) chord_type = 9; //dominant ninth
+ }
+ if(intervals[2] == MAJOR_THIRD) {
+ chord_type = 4; //major seventh
+ if(chord.num_notes == 5 && intervals[3] == MAJOR_THIRD) chord_type = 11; //major ninth
+ if(chord.num_notes == 5 && intervals[3] == MINOR_THIRD) chord_type = 26; //major seventh ninth
+ }
+ if(intervals[2] == MAJOR_SECOND) {
+ if(chord.num_notes == 5 && intervals[3] == PERFECT_FOURTH) chord_type = 24; //6/9
+ }
+ }
+ if(intervals[1] == MAJOR_THIRD){
+ //augmented triad
+ if(intervals[2] == MINOR_THIRD) chord_type = 12; //augmented major seventh
+ if(intervals[2] == MAJOR_SECOND) chord_type = 19; //augmented seventh
+ }
+ if(intervals[1] == MAJOR_SECOND){
+ if(intervals[2] == MAJOR_THIRD) chord_type = 21; //dominant seventh flat 5
+ if(intervals[2] == PERFECT_FOURTH) chord_type = 22; //major seventh flat 5
+ }
+ }
+ if(intervals[0] == MINOR_THIRD){
+ if(intervals[1] == MAJOR_THIRD){
+ //minor triad
+ if(intervals[2] == MINOR_THIRD) {
+ chord_type = 3; //minor seventh
+ if(chord.num_notes == 5 && intervals[3] == MAJOR_THIRD) chord_type = 10; //minor ninth
+ if(chord.num_notes == 5 && intervals[3] == MINOR_THIRD) chord_type = 25; //minor flat ninth
+ }
+ if(intervals[2] == MAJOR_THIRD) chord_type = 14; //minmaj seventh
+ }
+ if(intervals[1] == MINOR_THIRD){
+ //diminished triad
+ if(intervals[2] == MINOR_THIRD) chord_type = 16; //diminished seventh
+ if(intervals[2] == MAJOR_THIRD) {
+ chord_type = 18; //half diminished seventh
+ if(chord.num_notes == 5 && intervals[3] == MINOR_THIRD) chord_type = 27; //diminished flat ninth
+ if(chord.num_notes == 5 && intervals[3] == MAJOR_THIRD) chord_type = 28; //diminished ninth
+ }
+ if(intervals[2] == PERFECT_FOURTH) chord_type = 20; //diminished major seventh
+ }
+ }
+ }else{
+ //Its just a triad
+ if(intervals[0] == MINOR_THIRD){
+ if(intervals[1] == MAJOR_THIRD) chord_type = 1; //minor
+ if(intervals[1] == MINOR_THIRD) chord_type = 7; //diminished
+ }
+ if(intervals[0] == MAJOR_THIRD && intervals[1] == MAJOR_THIRD) chord_type = 8; //augmented
+ if(intervals[0] == MAJOR_SECOND && intervals[1] == PERFECT_FOURTH) chord_type = 5; //sus2
+ if(intervals[0] == PERFECT_FOURTH && intervals[1] == MAJOR_SECOND) chord_type = 6; //sus4
+ }
+ }
+
+ if(chord.inversion > 0){
+ int bass_note = chord.notes[0] % 12;
+ sprintf(text, "%s%s/%s", NOTE_NAMES[chord.notes_pre[0]], CHORD_TYPE_NAMES[chord_type], NOTE_NAMES[bass_note]);
+ }else{
+ sprintf(text, "%s%s", NOTE_NAMES[chord.notes_pre[0]], CHORD_TYPE_NAMES[chord_type]);
+ }
+}
+
void get_note_name(int note, char* text) {
sprintf(text, "%s", NOTE_NAMES[note % 12]);
}
diff --git a/src/musiclib.hpp b/src/musiclib.hpp
index 3387760..ee6dcd1 100644
--- a/src/musiclib.hpp
+++ b/src/musiclib.hpp
@@ -54,13 +54,19 @@ struct RefreshCounter {
//Chords
struct chord {
- int num_notes;
- int notes[4];
+ int num_notes; //How many notes in this chord
+ int notes[6]; //The notes (post-inversion and transpose)
+ int notes_pre[6]; //The notes (before inversion and transpose)
+ int inversion; //The inversion of this chord
+ int octave; //Octave of the root note
};
struct chord get_chord(int root_note, int type, int inversion, int voicing);
void get_chord_name(int root_semi, int chord_type, bool inverted, int bass_note, char* text);
+struct chord get_diatonic_chord(int* notes, int num_notes, int octave, int chord, int type, int inversion, int voicing);
+void detect_chord_name_simple(struct chord chord, char* text);
+
//Scales
struct scale {
int notes[7];
diff --git a/src/plugin.cpp b/src/plugin.cpp
index 0b2efdf..57b19d9 100644
--- a/src/plugin.cpp
+++ b/src/plugin.cpp
@@ -12,6 +12,7 @@ void init(Plugin* p) {
p->addModel(modelChordCV);
p->addModel(modelScaleCV);
p->addModel(modelRandomNoteCV);
+ p->addModel(modelDiatonicCV);
// Any other plugin initialization may go here.
// As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack.
diff --git a/src/plugin.hpp b/src/plugin.hpp
index 83afd90..a8a3f8a 100644
--- a/src/plugin.hpp
+++ b/src/plugin.hpp
@@ -12,6 +12,7 @@ extern Plugin* pluginInstance;
extern Model* modelChordCV;
extern Model* modelScaleCV;
extern Model* modelRandomNoteCV;
+extern Model* modelDiatonicCV;
static const int displayAlpha = 23;
NVGcolor prepareDisplay(NVGcontext *vg, Rect *box, int fontSize);