Skip to content

Commit 4f06ee3

Browse files
committed
Add ScaleCV module
1 parent 2387aee commit 4f06ee3

7 files changed

Lines changed: 339 additions & 24 deletions

File tree

plugin.json

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
{
2-
"slug": "AaronStatic",
3-
"name": "Aaron Static",
4-
"version": "2.0.0",
5-
"license": "MIT",
6-
"brand": "Aaron Static",
7-
"author": "Aaron Static",
8-
"authorEmail": "aaronstatic@gmail.com",
9-
"authorUrl": "https://github.com/aaronstatic/",
10-
"pluginUrl": "https://github.com/aaronstatic/AaronStatic_modules",
11-
"manualUrl": "https://github.com/aaronstatic/AaronStatic_modules",
12-
"sourceUrl": "https://github.com/aaronstatic/AaronStatic_modules",
13-
"donateUrl": "https://paypal.me/AaronStaticAU",
14-
"changelogUrl": "",
15-
"modules": [
2+
"slug": "AaronStatic",
3+
"name": "Aaron Static",
4+
"version": "2.0.0",
5+
"license": "MIT",
6+
"brand": "Aaron Static",
7+
"author": "Aaron Static",
8+
"authorEmail": "aaronstatic@gmail.com",
9+
"authorUrl": "https://github.com/aaronstatic/",
10+
"pluginUrl": "https://github.com/aaronstatic/AaronStatic_modules",
11+
"manualUrl": "https://github.com/aaronstatic/AaronStatic_modules",
12+
"sourceUrl": "https://github.com/aaronstatic/AaronStatic_modules",
13+
"donateUrl": "https://paypal.me/AaronStaticAU",
14+
"changelogUrl": "",
15+
"modules": [
1616
{
17-
"slug": "ChordCV",
18-
"name": "ChordCV",
19-
"description": "Generates a chord",
20-
"tags": ["Polyphonic","Tuner"]
17+
"slug": "ChordCV",
18+
"name": "ChordCV",
19+
"description": "Generates a chord",
20+
"tags": ["Polyphonic","Tuner"]
21+
},
22+
{
23+
"slug": "ScaleCV",
24+
"name": "ScaleCV",
25+
"description": "Generates a scale",
26+
"tags": ["Polyphonic","Tuner"]
2127
}
2228
]
2329
}

res/ScaleCV.svg

Lines changed: 132 additions & 0 deletions
Loading

src/ScaleCV.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#include "plugin.hpp"
2+
#include "musiclib.hpp"
3+
4+
struct ScaleCV : Module {
5+
enum ParamIds {
6+
ROOT_PARAM,
7+
MODE_PARAM,
8+
NUM_PARAMS
9+
};
10+
enum InputIds {
11+
ROOT_INPUT,
12+
MODE_INPUT,
13+
NUM_INPUTS
14+
};
15+
enum OutputIds {
16+
POLY_OUTPUT,
17+
NUM_OUTPUTS
18+
};
19+
enum LightIds {
20+
NUM_LIGHTS
21+
};
22+
23+
int root_semi = 0;
24+
int mode = 0;
25+
26+
ScaleCV() {
27+
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
28+
configParam(ROOT_PARAM, -4.0, 4.0, 0.0, "Root Note");
29+
configParam(MODE_PARAM, -4.0, 4.0, -4.0, "Mode");
30+
31+
configInput(ROOT_INPUT, "1V/oct pitch");
32+
configInput(MODE_INPUT, "Mode");
33+
34+
configOutput(POLY_OUTPUT, "Polyphonic");
35+
}
36+
37+
void process(const ProcessArgs& args) override;
38+
};
39+
40+
void ScaleCV::process(const ProcessArgs &args){
41+
float value = params[ROOT_PARAM].getValue();
42+
if(inputs[ROOT_INPUT].isConnected()){
43+
value = inputs[ROOT_INPUT].getVoltage();
44+
}
45+
float mode_val = params[MODE_PARAM].getValue();
46+
if(inputs[MODE_INPUT].isConnected()){
47+
mode_val = inputs[MODE_INPUT].getVoltage();
48+
}
49+
mode_val = clamp(mode_val,-4.0f, 2.0f);
50+
mode = (int)floor(mode_val + 4.0f);
51+
52+
//quantize root note
53+
float octave = round(value);
54+
float semi = voltage_to_note(value);
55+
root_semi = voltage_to_note_int(value);
56+
int root_note = (octave + 4) * 12 + (int)semi;
57+
58+
//Make the scale
59+
struct scale s = get_scale(root_note, mode);
60+
61+
outputs[POLY_OUTPUT].setChannels(8);
62+
for(int t=0; t<8; t++){
63+
outputs[POLY_OUTPUT].setVoltage(note_to_voltage(s.notes[t]),t);
64+
}
65+
}
66+
67+
68+
struct ScaleCVWidget : ModuleWidget {
69+
struct ChordDisplayWidget : TransparentWidget {
70+
ScaleCV* module;
71+
std::shared_ptr<Font> font;
72+
char text[13];
73+
74+
ChordDisplayWidget(Vec _pos, Vec _size, ScaleCV* _module) {
75+
box.size = _size;
76+
box.pos = _pos.minus(_size.div(2));
77+
module = _module;
78+
font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/PixelOperator.ttf"));
79+
}
80+
81+
void draw(const DrawArgs &args) override {
82+
NVGcolor textColor = prepareDisplay(args.vg, &box, 22);
83+
nvgFontFaceId(args.vg, font->handle);
84+
nvgTextLetterSpacing(args.vg, -1.5);
85+
nvgTextAlign(args.vg, NVG_ALIGN_CENTER);
86+
87+
Vec textPos = Vec(box.size.x/2, 21.0f);
88+
nvgFillColor(args.vg, textColor);
89+
90+
if (module != NULL){
91+
get_scale_name(module->root_semi,module->mode,text);
92+
}else{
93+
snprintf(text, 13, " ");
94+
}
95+
96+
nvgText(args.vg, textPos.x, textPos.y, text, NULL);
97+
}
98+
99+
};
100+
101+
ScaleCVWidget(ScaleCV* module) {
102+
setModule(module);
103+
setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ScaleCV.svg")));
104+
105+
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
106+
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
107+
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
108+
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
109+
110+
const int centerX = box.size.x / 2;
111+
112+
ChordDisplayWidget* display = new ChordDisplayWidget(Vec(centerX, 55), Vec(box.size.x - 5, 29), module);
113+
addChild(display);
114+
115+
const int offsetXL = 40;
116+
117+
118+
addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,95), module, ScaleCV::ROOT_PARAM));
119+
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, 95), module, ScaleCV::ROOT_INPUT));
120+
121+
addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,140), module, ScaleCV::MODE_PARAM));
122+
addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, 140), module, ScaleCV::MODE_INPUT));
123+
124+
addOutput(createOutputCentered<PJ301MPort>(Vec(centerX, 330), module, ScaleCV::POLY_OUTPUT));
125+
}
126+
};
127+
128+
129+
Model* modelScaleCV = createModel<ScaleCV, ScaleCVWidget>("ScaleCV");

src/musiclib.cpp

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ struct chord get_chord(int root_note, int type, int inversion, int voicing){
141141
return return_chord;
142142
}
143143

144-
static const char * noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
145-
static const char * chordTypes[] = {
144+
static const char * NOTE_NAMES[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
145+
static const char * CHORD_TYPE_NAMES[] = {
146146
"",
147147
"m",
148148
"7",
@@ -155,11 +155,50 @@ static const char * chordTypes[] = {
155155
};
156156

157157
void get_chord_name(int root_semi, int chord_type, bool inverted, int bass_note, char* text) {
158-
int note = root_semi;
159-
int type = chord_type;
160158
char inv[4];
161159
if(inverted){
162-
sprintf(inv,"/%s",noteNames[bass_note]);
160+
sprintf(inv,"/%s",NOTE_NAMES[bass_note]);
163161
}
164-
sprintf(text, "%s%s%s", noteNames[note], chordTypes[type], inv);
162+
sprintf(text, "%s%s%s", NOTE_NAMES[root_semi], CHORD_TYPE_NAMES[chord_type], inv);
163+
}
164+
165+
//Scales
166+
static const char * MODE_NAMES[] = {
167+
"",
168+
" Dorian",
169+
" Phrygian",
170+
" Lydian",
171+
" Mixolydian",
172+
" Minor",
173+
" Locrian"
174+
};
175+
176+
void get_scale_name(int root_semi, int mode, char* text) {
177+
sprintf(text, "%s%s", NOTE_NAMES[root_semi], MODE_NAMES[mode]);
178+
}
179+
180+
static const int MODE_DEGREES[7][7] = {
181+
{2,2,1,2,2,2,1}, //Major (Ionian)
182+
{2,1,2,2,2,1,2}, //Dorian
183+
{1,2,2,2,1,2,2}, //Phrygian
184+
{2,2,2,1,2,2,1}, //Lydian
185+
{2,2,1,2,2,1,2}, //Mixolydian
186+
{2,1,2,2,1,2,2}, //Minor (Aeolian)
187+
{1,2,2,1,2,2,2}, //Locrian
188+
};
189+
190+
struct scale get_scale(int root_note, int mode){
191+
struct scale return_scale;
192+
193+
const int *degrees = MODE_DEGREES[mode];
194+
195+
return_scale.notes[0] = root_note;
196+
int current_note = root_note;
197+
198+
int t;
199+
for(t=1; t<8; t++){
200+
current_note += degrees[t-1];
201+
return_scale.notes[t] = current_note;
202+
}
203+
return return_scale;
165204
}

0 commit comments

Comments
 (0)