@@ -1,34 +1,27 @@ | |||||
#include "Core.hpp" | #include "Core.hpp" | ||||
template <int N> | |||||
struct CCMidiOutput : midi::Output { | struct CCMidiOutput : midi::Output { | ||||
int ccs[N]; | |||||
int lastValues[N]; | |||||
int lastValues[128]; | |||||
CCMidiOutput() { | CCMidiOutput() { | ||||
reset(); | reset(); | ||||
} | } | ||||
void reset() { | void reset() { | ||||
for (int n = 0; n < N; n++) { | |||||
ccs[n] = n; | |||||
for (int n = 0; n < 128; n++) { | |||||
lastValues[n] = -1; | lastValues[n] = -1; | ||||
} | } | ||||
} | } | ||||
void setCC(int cc, int n) { | |||||
ccs[n] = cc; | |||||
} | |||||
void setValue(int value, int n) { | |||||
if (value == lastValues[n]) | |||||
void setValue(int value, int cc) { | |||||
if (value == lastValues[cc]) | |||||
return; | return; | ||||
lastValues[n] = value; | |||||
lastValues[cc] = value; | |||||
// CC | // CC | ||||
midi::Message m; | midi::Message m; | ||||
m.setStatus(0xb); | m.setStatus(0xb); | ||||
m.setNote(ccs[n]); | |||||
m.setNote(cc); | |||||
m.setValue(value); | m.setValue(value); | ||||
sendMessage(m); | sendMessage(m); | ||||
} | } | ||||
@@ -50,11 +43,23 @@ struct CV_CC : Module { | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
CCMidiOutput<16> midiOutput; | |||||
CCMidiOutput midiOutput; | |||||
float rateLimiterPhase = 0.f; | float rateLimiterPhase = 0.f; | ||||
int learningId = -1; | |||||
int learnedCcs[16] = {}; | |||||
CV_CC() { | CV_CC() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
onReset(); | |||||
} | |||||
void onReset() override { | |||||
for (int i = 0; i < 16; i++) { | |||||
learnedCcs[i] = i; | |||||
} | |||||
learningId = -1; | |||||
midiOutput.reset(); | |||||
midiOutput.midi::Output::reset(); | |||||
} | } | ||||
void step() override { | void step() override { | ||||
@@ -67,11 +72,39 @@ struct CV_CC : Module { | |||||
return; | return; | ||||
} | } | ||||
for (int n = 0; n < 16; n++) { | |||||
int value = (int) std::round(inputs[CC_INPUTS + n].getVoltage() / 10.f * 127); | |||||
for (int i = 0; i < 16; i++) { | |||||
int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127); | |||||
value = clamp(value, 0, 127); | value = clamp(value, 0, 127); | ||||
midiOutput.setValue(value, n); | |||||
midiOutput.setValue(value, learnedCcs[i]); | |||||
} | |||||
} | |||||
json_t *dataToJson() override { | |||||
json_t *rootJ = json_object(); | |||||
json_t *ccsJ = json_array(); | |||||
for (int i = 0; i < 16; i++) { | |||||
json_array_append_new(ccsJ, json_integer(learnedCcs[i])); | |||||
} | } | ||||
json_object_set_new(rootJ, "ccs", ccsJ); | |||||
json_object_set_new(rootJ, "midi", midiOutput.toJson()); | |||||
return rootJ; | |||||
} | |||||
void dataFromJson(json_t *rootJ) override { | |||||
json_t *ccsJ = json_object_get(rootJ, "ccs"); | |||||
if (ccsJ) { | |||||
for (int i = 0; i < 16; i++) { | |||||
json_t *ccJ = json_array_get(ccsJ, i); | |||||
if (ccJ) | |||||
learnedCcs[i] = json_integer_value(ccJ); | |||||
} | |||||
} | |||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiOutput.fromJson(midiJ); | |||||
} | } | ||||
}; | }; | ||||
@@ -103,11 +136,12 @@ struct CV_CCWidget : ModuleWidget { | |||||
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(31, 112)), module, CV_CC::CC_INPUTS + 14)); | addInput(createInputCentered<PJ301MPort>(mm2px(Vec(31, 112)), module, CV_CC::CC_INPUTS + 14)); | ||||
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(43, 112)), module, CV_CC::CC_INPUTS + 15)); | addInput(createInputCentered<PJ301MPort>(mm2px(Vec(43, 112)), module, CV_CC::CC_INPUTS + 15)); | ||||
MidiWidget *midiWidget = createWidget<MidiWidget>(mm2px(Vec(3.4, 14.839))); | |||||
typedef Grid16MidiWidget<CcChoice<CV_CC>> TMidiWidget; | |||||
TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||||
midiWidget->box.size = mm2px(Vec(44, 54.667)); | midiWidget->box.size = mm2px(Vec(44, 54.667)); | ||||
if (module) | if (module) | ||||
midiWidget->midiIO = &module->midiOutput; | midiWidget->midiIO = &module->midiOutput; | ||||
// midiWidget->createGridChoices(); | |||||
midiWidget->setModule(module); | |||||
addChild(midiWidget); | addChild(midiWidget); | ||||
} | } | ||||
}; | }; | ||||
@@ -1,28 +1,25 @@ | |||||
#include "Core.hpp" | #include "Core.hpp" | ||||
template <int N> | |||||
struct GateMidiOutput : midi::Output { | struct GateMidiOutput : midi::Output { | ||||
int vels[N]; | |||||
bool lastGates[N]; | |||||
int notes[N]; | |||||
int vels[128]; | |||||
bool lastGates[128]; | |||||
GateMidiOutput() { | GateMidiOutput() { | ||||
reset(); | reset(); | ||||
} | } | ||||
void reset() { | void reset() { | ||||
for (int n = 0; n < N; n++) { | |||||
vels[n] = 100; | |||||
lastGates[n] = false; | |||||
notes[n] = 60 + n; | |||||
for (int note = 0; note < 128; note++) { | |||||
vels[note] = 100; | |||||
lastGates[note] = false; | |||||
} | } | ||||
} | } | ||||
void panic() { | void panic() { | ||||
reset(); | reset(); | ||||
// Send all note off commands | // Send all note off commands | ||||
for (int note = 0; note <= 127; note++) { | |||||
for (int note = 0; note < 128; note++) { | |||||
// Note off | // Note off | ||||
midi::Message m; | midi::Message m; | ||||
m.setStatus(0x8); | m.setStatus(0x8); | ||||
@@ -32,48 +29,28 @@ struct GateMidiOutput : midi::Output { | |||||
} | } | ||||
} | } | ||||
void setVelocity(int vel, int n) { | |||||
vels[n] = vel; | |||||
void setVelocity(int vel, int note) { | |||||
vels[note] = vel; | |||||
} | } | ||||
void setGate(bool gate, int n) { | |||||
if (gate && !lastGates[n]) { | |||||
void setGate(bool gate, int note) { | |||||
if (gate && !lastGates[note]) { | |||||
// Note on | // Note on | ||||
midi::Message m; | midi::Message m; | ||||
m.setStatus(0x9); | m.setStatus(0x9); | ||||
m.setNote(notes[n]); | |||||
m.setValue(vels[n]); | |||||
m.setNote(note); | |||||
m.setValue(vels[note]); | |||||
sendMessage(m); | sendMessage(m); | ||||
} | } | ||||
else if (!gate && lastGates[n]) { | |||||
else if (!gate && lastGates[note]) { | |||||
// Note off | // Note off | ||||
midi::Message m; | midi::Message m; | ||||
m.setStatus(0x8); | m.setStatus(0x8); | ||||
m.setNote(notes[n]); | |||||
m.setValue(vels[n]); | |||||
m.setNote(note); | |||||
m.setValue(vels[note]); | |||||
sendMessage(m); | sendMessage(m); | ||||
} | } | ||||
lastGates[n] = gate; | |||||
} | |||||
void setNote(int note, int n) { | |||||
if (note == notes[n]) | |||||
return; | |||||
if (lastGates[n]) { | |||||
// Note off | |||||
midi::Message m1; | |||||
m1.setStatus(0x8); | |||||
m1.setNote(notes[n]); | |||||
m1.setValue(vels[n]); | |||||
sendMessage(m1); | |||||
// Note on | |||||
midi::Message m2; | |||||
m2.setStatus(0x9); | |||||
m2.setNote(note); | |||||
m2.setValue(vels[n]); | |||||
sendMessage(m2); | |||||
} | |||||
notes[n] = note; | |||||
lastGates[note] = gate; | |||||
} | } | ||||
}; | }; | ||||
@@ -93,27 +70,75 @@ struct CV_Gate : Module { | |||||
NUM_LIGHTS | NUM_LIGHTS | ||||
}; | }; | ||||
GateMidiOutput<16> midiOutput; | |||||
GateMidiOutput midiOutput; | |||||
bool velocityMode = false; | bool velocityMode = false; | ||||
int learningId = -1; | |||||
uint8_t learnedNotes[16] = {}; | |||||
CV_Gate() { | CV_Gate() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
onReset(); | |||||
} | |||||
void onReset() override { | |||||
for (int i = 0; i < 16; i++) { | |||||
learnedNotes[i] = i + 36; | |||||
} | |||||
learningId = -1; | |||||
midiOutput.reset(); | |||||
midiOutput.midi::Output::reset(); | |||||
} | } | ||||
void step() override { | void step() override { | ||||
for (int n = 0; n < 16; n++) { | |||||
for (int i = 0; i < 16; i++) { | |||||
int note = learnedNotes[i]; | |||||
if (velocityMode) { | if (velocityMode) { | ||||
int vel = (int) std::round(inputs[GATE_INPUTS + n].getVoltage() / 10.f * 127); | |||||
int vel = (int) std::round(inputs[GATE_INPUTS + i].getVoltage() / 10.f * 127); | |||||
vel = clamp(vel, 0, 127); | vel = clamp(vel, 0, 127); | ||||
midiOutput.setVelocity(vel, n); | |||||
midiOutput.setGate(vel > 0, n); | |||||
midiOutput.setVelocity(vel, note); | |||||
midiOutput.setGate(vel > 0, note); | |||||
} | } | ||||
else { | else { | ||||
bool gate = inputs[GATE_INPUTS + n].getVoltage() >= 1.f; | |||||
midiOutput.setVelocity(100, n); | |||||
midiOutput.setGate(gate, n); | |||||
bool gate = inputs[GATE_INPUTS + i].getVoltage() >= 1.f; | |||||
midiOutput.setVelocity(100, note); | |||||
midiOutput.setGate(gate, note); | |||||
} | |||||
} | |||||
} | |||||
json_t *dataToJson() override { | |||||
json_t *rootJ = json_object(); | |||||
json_t *notesJ = json_array(); | |||||
for (int i = 0; i < 16; i++) { | |||||
json_t *noteJ = json_integer(learnedNotes[i]); | |||||
json_array_append_new(notesJ, noteJ); | |||||
} | |||||
json_object_set_new(rootJ, "notes", notesJ); | |||||
json_object_set_new(rootJ, "velocity", json_boolean(velocityMode)); | |||||
json_object_set_new(rootJ, "midi", midiOutput.toJson()); | |||||
return rootJ; | |||||
} | |||||
void dataFromJson(json_t *rootJ) override { | |||||
json_t *notesJ = json_object_get(rootJ, "notes"); | |||||
if (notesJ) { | |||||
for (int i = 0; i < 16; i++) { | |||||
json_t *noteJ = json_array_get(notesJ, i); | |||||
if (noteJ) | |||||
learnedNotes[i] = json_integer_value(noteJ); | |||||
} | } | ||||
} | } | ||||
json_t *velocityJ = json_object_get(rootJ, "velocity"); | |||||
if (velocityJ) | |||||
velocityMode = json_boolean_value(velocityJ); | |||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiOutput.fromJson(midiJ); | |||||
} | } | ||||
}; | }; | ||||
@@ -145,11 +170,12 @@ struct CV_GateWidget : ModuleWidget { | |||||
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(31, 112)), module, CV_Gate::GATE_INPUTS + 14)); | addInput(createInputCentered<PJ301MPort>(mm2px(Vec(31, 112)), module, CV_Gate::GATE_INPUTS + 14)); | ||||
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(43, 112)), module, CV_Gate::GATE_INPUTS + 15)); | addInput(createInputCentered<PJ301MPort>(mm2px(Vec(43, 112)), module, CV_Gate::GATE_INPUTS + 15)); | ||||
MidiWidget *midiWidget = createWidget<MidiWidget>(mm2px(Vec(3.4, 14.839))); | |||||
typedef Grid16MidiWidget<NoteChoice<CV_Gate>> TMidiWidget; | |||||
TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||||
midiWidget->box.size = mm2px(Vec(44, 54.667)); | midiWidget->box.size = mm2px(Vec(44, 54.667)); | ||||
if (module) | if (module) | ||||
midiWidget->midiIO = &module->midiOutput; | midiWidget->midiIO = &module->midiOutput; | ||||
// midiWidget->createGridChoices(); | |||||
midiWidget->setModule(module); | |||||
addChild(midiWidget); | addChild(midiWidget); | ||||
} | } | ||||
}; | }; | ||||
@@ -233,6 +233,12 @@ struct CV_MIDI : Module { | |||||
CV_MIDI() { | CV_MIDI() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
onReset(); | |||||
} | |||||
void onReset() override { | |||||
midiOutput.reset(); | |||||
midiOutput.midi::Output::reset(); | |||||
} | } | ||||
void step() override { | void step() override { | ||||
@@ -292,6 +298,18 @@ struct CV_MIDI : Module { | |||||
bool cont = inputs[CONTINUE_INPUT].value >= 1.f; | bool cont = inputs[CONTINUE_INPUT].value >= 1.f; | ||||
midiOutput.setContinue(cont); | midiOutput.setContinue(cont); | ||||
} | } | ||||
json_t *dataToJson() override { | |||||
json_t *rootJ = json_object(); | |||||
json_object_set_new(rootJ, "midi", midiOutput.toJson()); | |||||
return rootJ; | |||||
} | |||||
void dataFromJson(json_t *rootJ) override { | |||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiOutput.fromJson(midiJ); | |||||
} | |||||
}; | }; | ||||
@@ -16,40 +16,36 @@ extern Model *modelBlank; | |||||
extern Model *modelNotes; | extern Model *modelNotes; | ||||
struct GridChoice : LedDisplayChoice { | |||||
virtual void setId(int id) {} | |||||
}; | |||||
template <class TChoice> | |||||
struct Grid16MidiWidget : MidiWidget { | struct Grid16MidiWidget : MidiWidget { | ||||
LedDisplaySeparator *hSeparators[4]; | LedDisplaySeparator *hSeparators[4]; | ||||
LedDisplaySeparator *vSeparators[4]; | LedDisplaySeparator *vSeparators[4]; | ||||
GridChoice *gridChoices[4][4]; | |||||
TChoice *choices[4][4]; | |||||
void createGridChoices() { | |||||
Grid16MidiWidget() { | |||||
Vec pos = channelChoice->box.getBottomLeft(); | Vec pos = channelChoice->box.getBottomLeft(); | ||||
// Add vSeparators | |||||
for (int x = 1; x < 4; x++) { | for (int x = 1; x < 4; x++) { | ||||
vSeparators[x] = createWidget<LedDisplaySeparator>(pos); | vSeparators[x] = createWidget<LedDisplaySeparator>(pos); | ||||
addChild(vSeparators[x]); | addChild(vSeparators[x]); | ||||
} | } | ||||
// Add hSeparators and choice widgets | |||||
for (int y = 0; y < 4; y++) { | for (int y = 0; y < 4; y++) { | ||||
hSeparators[y] = createWidget<LedDisplaySeparator>(pos); | hSeparators[y] = createWidget<LedDisplaySeparator>(pos); | ||||
addChild(hSeparators[y]); | addChild(hSeparators[y]); | ||||
for (int x = 0; x < 4; x++) { | for (int x = 0; x < 4; x++) { | ||||
GridChoice *gridChoice = createGridChoice(); | |||||
assert(gridChoice); | |||||
gridChoice->box.pos = pos; | |||||
gridChoice->setId(4*y + x); | |||||
gridChoices[x][y] = gridChoice; | |||||
addChild(gridChoice); | |||||
choices[x][y] = new TChoice; | |||||
choices[x][y]->box.pos = pos; | |||||
choices[x][y]->setId(4*y + x); | |||||
addChild(choices[x][y]); | |||||
} | } | ||||
pos = gridChoices[0][y]->box.getBottomLeft(); | |||||
pos = choices[0][y]->box.getBottomLeft(); | |||||
} | } | ||||
for (int x = 1; x < 4; x++) { | for (int x = 1; x < 4; x++) { | ||||
vSeparators[x]->box.size.y = pos.y - vSeparators[x]->box.pos.y; | vSeparators[x]->box.size.y = pos.y - vSeparators[x]->box.pos.y; | ||||
} | } | ||||
} | } | ||||
void step() override { | void step() override { | ||||
MidiWidget::step(); | MidiWidget::step(); | ||||
for (int x = 1; x < 4; x++) { | for (int x = 1; x < 4; x++) { | ||||
@@ -58,10 +54,145 @@ struct Grid16MidiWidget : MidiWidget { | |||||
for (int y = 0; y < 4; y++) { | for (int y = 0; y < 4; y++) { | ||||
hSeparators[y]->box.size.x = box.size.x; | hSeparators[y]->box.size.x = box.size.x; | ||||
for (int x = 0; x < 4; x++) { | for (int x = 0; x < 4; x++) { | ||||
gridChoices[x][y]->box.size.x = box.size.x / 4; | |||||
gridChoices[x][y]->box.pos.x = box.size.x / 4 * x; | |||||
choices[x][y]->box.size.x = box.size.x / 4; | |||||
choices[x][y]->box.pos.x = box.size.x / 4 * x; | |||||
} | |||||
} | |||||
} | |||||
template <class TModule> | |||||
void setModule(TModule *module) { | |||||
for (int y = 0; y < 4; y++) { | |||||
for (int x = 0; x < 4; x++) { | |||||
choices[x][y]->module = module; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
virtual GridChoice *createGridChoice() {return NULL;} | |||||
}; | |||||
}; | |||||
template <class TModule> | |||||
struct CcChoice : LedDisplayChoice { | |||||
TModule *module; | |||||
int id; | |||||
int focusCc; | |||||
CcChoice() { | |||||
box.size.y = mm2px(6.666); | |||||
textOffset.y -= 4; | |||||
} | |||||
void setId(int id) { | |||||
this->id = id; | |||||
} | |||||
void step() override { | |||||
if (!module) { | |||||
text = ""; | |||||
return; | |||||
} | |||||
if (module->learningId == id) { | |||||
if (0 <= focusCc) | |||||
text = string::f("%d", focusCc); | |||||
else | |||||
text = "LRN"; | |||||
color.a = 0.5; | |||||
} | |||||
else { | |||||
text = string::f("%d", module->learnedCcs[id]); | |||||
color.a = 1.0; | |||||
if (app()->event->selectedWidget == this) | |||||
app()->event->selectedWidget = NULL; | |||||
} | |||||
} | |||||
void onSelect(const event::Select &e) override { | |||||
e.consume(this); | |||||
if (!module) | |||||
return; | |||||
module->learningId = id; | |||||
focusCc = -1; | |||||
} | |||||
void onDeselect(const event::Deselect &e) override { | |||||
if (!module) | |||||
return; | |||||
if (0 <= focusCc && focusCc < 128) { | |||||
module->learnedCcs[id] = focusCc; | |||||
} | |||||
module->learningId = -1; | |||||
} | |||||
void onSelectText(const event::SelectText &e) override { | |||||
int c = e.codepoint - '0'; | |||||
if (0 <= c && c <= 9) { | |||||
if (focusCc < 0) | |||||
focusCc = 0; | |||||
focusCc = focusCc * 10 + c; | |||||
} | |||||
if (focusCc >= 128) | |||||
focusCc = 0; | |||||
e.consume(this); | |||||
} | |||||
void onSelectKey(const event::SelectKey &e) override { | |||||
if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && e.action == GLFW_PRESS && (e.mods & WINDOW_MOD_MASK) == 0) { | |||||
event::Deselect eDeselect; | |||||
onDeselect(eDeselect); | |||||
app()->event->selectedWidget = NULL; | |||||
e.consume(this); | |||||
} | |||||
} | |||||
}; | |||||
template <class TModule> | |||||
struct NoteChoice : LedDisplayChoice { | |||||
TModule *module; | |||||
int id; | |||||
NoteChoice() { | |||||
box.size.y = mm2px(6.666); | |||||
textOffset.y -= 4; | |||||
textOffset.x -= 4; | |||||
} | |||||
void setId(int id) { | |||||
this->id = id; | |||||
} | |||||
void step() override { | |||||
if (!module) | |||||
return; | |||||
if (module->learningId == id) { | |||||
text = "LRN"; | |||||
color.a = 0.5; | |||||
} | |||||
else { | |||||
uint8_t note = module->learnedNotes[id]; | |||||
static const char *noteNames[] = { | |||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" | |||||
}; | |||||
int oct = note / 12 - 1; | |||||
int semi = note % 12; | |||||
text = string::f("%s%d", noteNames[semi], oct); | |||||
color.a = 1.0; | |||||
if (app()->event->selectedWidget == this) | |||||
app()->event->selectedWidget = NULL; | |||||
} | |||||
} | |||||
void onSelect(const event::Select &e) override { | |||||
e.consume(this); | |||||
if (!module) | |||||
return; | |||||
module->learningId = id; | |||||
} | |||||
void onDeselect(const event::Deselect &e) override { | |||||
if (!module) | |||||
return; | |||||
module->learningId = -1; | |||||
} | |||||
}; |
@@ -1,5 +1,4 @@ | |||||
#include "Core.hpp" | #include "Core.hpp" | ||||
#include "midi.hpp" | |||||
struct MIDI_CC : Module { | struct MIDI_CC : Module { | ||||
@@ -20,7 +19,7 @@ struct MIDI_CC : Module { | |||||
midi::InputQueue midiInput; | midi::InputQueue midiInput; | ||||
int8_t values[128]; | int8_t values[128]; | ||||
int learningId = -1; | int learningId = -1; | ||||
int ccs[16] = {}; | |||||
int learnedCcs[16] = {}; | |||||
dsp::ExponentialFilter valueFilters[16]; | dsp::ExponentialFilter valueFilters[16]; | ||||
int8_t lastValues[16] = {}; | int8_t lastValues[16] = {}; | ||||
@@ -34,7 +33,7 @@ struct MIDI_CC : Module { | |||||
values[i] = 0; | values[i] = 0; | ||||
} | } | ||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
ccs[i] = i; | |||||
learnedCcs[i] = i; | |||||
} | } | ||||
learningId = -1; | learningId = -1; | ||||
midiInput.reset(); | midiInput.reset(); | ||||
@@ -51,7 +50,7 @@ struct MIDI_CC : Module { | |||||
if (!outputs[CC_OUTPUT + i].active) | if (!outputs[CC_OUTPUT + i].active) | ||||
continue; | continue; | ||||
int cc = ccs[i]; | |||||
int cc = learnedCcs[i]; | |||||
float value = rescale(values[cc], 0, 127, 0.f, 10.f); | float value = rescale(values[cc], 0, 127, 0.f, 10.f); | ||||
valueFilters[i].lambda = lambda; | valueFilters[i].lambda = lambda; | ||||
@@ -77,7 +76,7 @@ struct MIDI_CC : Module { | |||||
uint8_t cc = msg.getNote(); | uint8_t cc = msg.getNote(); | ||||
// Learn | // Learn | ||||
if (learningId >= 0 && values[cc] != msg.data2) { | if (learningId >= 0 && values[cc] != msg.data2) { | ||||
ccs[learningId] = cc; | |||||
learnedCcs[learningId] = cc; | |||||
learningId = -1; | learningId = -1; | ||||
} | } | ||||
// Allow CC to be negative if the 8th bit is set. | // Allow CC to be negative if the 8th bit is set. | ||||
@@ -93,7 +92,7 @@ struct MIDI_CC : Module { | |||||
json_t *ccsJ = json_array(); | json_t *ccsJ = json_array(); | ||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
json_array_append_new(ccsJ, json_integer(ccs[i])); | |||||
json_array_append_new(ccsJ, json_integer(learnedCcs[i])); | |||||
} | } | ||||
json_object_set_new(rootJ, "ccs", ccsJ); | json_object_set_new(rootJ, "ccs", ccsJ); | ||||
@@ -114,7 +113,7 @@ struct MIDI_CC : Module { | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
json_t *ccJ = json_array_get(ccsJ, i); | json_t *ccJ = json_array_get(ccsJ, i); | ||||
if (ccJ) | if (ccJ) | ||||
ccs[i] = json_integer_value(ccJ); | |||||
learnedCcs[i] = json_integer_value(ccJ); | |||||
} | } | ||||
} | } | ||||
@@ -135,90 +134,6 @@ struct MIDI_CC : Module { | |||||
}; | }; | ||||
struct MidiCcChoice : GridChoice { | |||||
MIDI_CC *module; | |||||
int id; | |||||
int focusCc; | |||||
MidiCcChoice() { | |||||
box.size.y = mm2px(6.666); | |||||
textOffset.y -= 4; | |||||
} | |||||
void setId(int id) override { | |||||
this->id = id; | |||||
} | |||||
void step() override { | |||||
if (!module) { | |||||
text = ""; | |||||
return; | |||||
} | |||||
if (module->learningId == id) { | |||||
if (0 <= focusCc) | |||||
text = string::f("%d", focusCc); | |||||
else | |||||
text = "LRN"; | |||||
color.a = 0.5; | |||||
} | |||||
else { | |||||
text = string::f("%d", module->ccs[id]); | |||||
color.a = 1.0; | |||||
if (app()->event->selectedWidget == this) | |||||
app()->event->selectedWidget = NULL; | |||||
} | |||||
} | |||||
void onSelect(const event::Select &e) override { | |||||
e.consume(this); | |||||
if (!module) | |||||
return; | |||||
module->learningId = id; | |||||
focusCc = -1; | |||||
} | |||||
void onDeselect(const event::Deselect &e) override { | |||||
if (!module) | |||||
return; | |||||
if (0 <= focusCc && focusCc < 128) { | |||||
module->ccs[id] = focusCc; | |||||
} | |||||
module->learningId = -1; | |||||
} | |||||
void onSelectText(const event::SelectText &e) override { | |||||
char c = e.codepoint; | |||||
if ('0' <= c && c <= '9') { | |||||
if (focusCc < 0) | |||||
focusCc = 0; | |||||
focusCc = focusCc * 10 + (c - '0'); | |||||
} | |||||
e.consume(this); | |||||
} | |||||
void onSelectKey(const event::SelectKey &e) override { | |||||
if (app()->event->selectedWidget == this) { | |||||
if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { | |||||
event::Deselect eDeselect; | |||||
onDeselect(eDeselect); | |||||
app()->event->selectedWidget = NULL; | |||||
e.consume(this); | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
struct MidiCcWidget : Grid16MidiWidget { | |||||
MIDI_CC *module; | |||||
GridChoice *createGridChoice() override { | |||||
MidiCcChoice *gridChoice = new MidiCcChoice; | |||||
gridChoice->module = module; | |||||
return gridChoice; | |||||
} | |||||
}; | |||||
struct MIDI_CCWidget : ModuleWidget { | struct MIDI_CCWidget : ModuleWidget { | ||||
MIDI_CCWidget(MIDI_CC *module) { | MIDI_CCWidget(MIDI_CC *module) { | ||||
setModule(module); | setModule(module); | ||||
@@ -246,12 +161,12 @@ struct MIDI_CCWidget : ModuleWidget { | |||||
addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_CC::CC_OUTPUT + 14)); | addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_CC::CC_OUTPUT + 14)); | ||||
addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_CC::CC_OUTPUT + 15)); | addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_CC::CC_OUTPUT + 15)); | ||||
MidiCcWidget *midiWidget = createWidget<MidiCcWidget>(mm2px(Vec(3.399621, 14.837339))); | |||||
midiWidget->module = module; | |||||
typedef Grid16MidiWidget<CcChoice<MIDI_CC>> TMidiWidget; | |||||
TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||||
midiWidget->box.size = mm2px(Vec(44, 54.667)); | midiWidget->box.size = mm2px(Vec(44, 54.667)); | ||||
if (module) | if (module) | ||||
midiWidget->midiIO = &module->midiInput; | midiWidget->midiIO = &module->midiInput; | ||||
midiWidget->createGridChoices(); | |||||
midiWidget->setModule(module); | |||||
addChild(midiWidget); | addChild(midiWidget); | ||||
} | } | ||||
}; | }; | ||||
@@ -1,5 +1,4 @@ | |||||
#include "Core.hpp" | #include "Core.hpp" | ||||
#include "midi.hpp" | |||||
#include <algorithm> | #include <algorithm> | ||||
@@ -1,6 +1,4 @@ | |||||
#include "Core.hpp" | #include "Core.hpp" | ||||
#include "midi.hpp" | |||||
#include "event.hpp" | |||||
struct MIDI_Gate : Module { | struct MIDI_Gate : Module { | ||||
@@ -25,7 +23,7 @@ struct MIDI_Gate : Module { | |||||
uint8_t velocities[16]; | uint8_t velocities[16]; | ||||
int learningId = -1; | int learningId = -1; | ||||
uint8_t learnedNotes[16] = {}; | uint8_t learnedNotes[16] = {}; | ||||
bool velocity = false; | |||||
bool velocityMode = false; | |||||
MIDI_Gate() { | MIDI_Gate() { | ||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | ||||
@@ -76,7 +74,7 @@ struct MIDI_Gate : Module { | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
if (gateTimes[i] > 0.f) { | if (gateTimes[i] > 0.f) { | ||||
outputs[TRIG_OUTPUT + i].setVoltage(velocity ? rescale(velocities[i], 0, 127, 0.f, 10.f) : 10.f); | |||||
outputs[TRIG_OUTPUT + i].setVoltage(velocityMode ? rescale(velocities[i], 0, 127, 0.f, 10.f) : 10.f); | |||||
// If the gate is off, wait 1 ms before turning the pulse off. | // If the gate is off, wait 1 ms before turning the pulse off. | ||||
// This avoids drum controllers sending a pulse with 0 ms duration. | // This avoids drum controllers sending a pulse with 0 ms duration. | ||||
if (!gates[i]) { | if (!gates[i]) { | ||||
@@ -119,8 +117,9 @@ struct MIDI_Gate : Module { | |||||
} | } | ||||
json_object_set_new(rootJ, "notes", notesJ); | json_object_set_new(rootJ, "notes", notesJ); | ||||
json_object_set_new(rootJ, "velocity", json_boolean(velocityMode)); | |||||
json_object_set_new(rootJ, "midi", midiInput.toJson()); | json_object_set_new(rootJ, "midi", midiInput.toJson()); | ||||
json_object_set_new(rootJ, "velocity", json_boolean(velocity)); | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
@@ -134,74 +133,13 @@ struct MIDI_Gate : Module { | |||||
} | } | ||||
} | } | ||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiInput.fromJson(midiJ); | |||||
json_t *velocityJ = json_object_get(rootJ, "velocity"); | json_t *velocityJ = json_object_get(rootJ, "velocity"); | ||||
if (velocityJ) | if (velocityJ) | ||||
velocity = json_boolean_value(velocityJ); | |||||
} | |||||
}; | |||||
struct MidiTrigChoice : GridChoice { | |||||
MIDI_Gate *module; | |||||
int id; | |||||
MidiTrigChoice() { | |||||
box.size.y = mm2px(6.666); | |||||
textOffset.y -= 4; | |||||
textOffset.x -= 4; | |||||
} | |||||
void setId(int id) override { | |||||
this->id = id; | |||||
} | |||||
void step() override { | |||||
if (!module) | |||||
return; | |||||
if (module->learningId == id) { | |||||
text = "LRN"; | |||||
color.a = 0.5; | |||||
} | |||||
else { | |||||
uint8_t note = module->learnedNotes[id]; | |||||
static const char *noteNames[] = { | |||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" | |||||
}; | |||||
int oct = note / 12 - 1; | |||||
int semi = note % 12; | |||||
text = string::f("%s%d", noteNames[semi], oct); | |||||
color.a = 1.0; | |||||
if (app()->event->selectedWidget == this) | |||||
app()->event->selectedWidget = NULL; | |||||
} | |||||
} | |||||
void onSelect(const event::Select &e) override { | |||||
e.consume(this); | |||||
if (!module) | |||||
return; | |||||
module->learningId = id; | |||||
} | |||||
velocityMode = json_boolean_value(velocityJ); | |||||
void onDeselect(const event::Deselect &e) override { | |||||
if (!module) | |||||
return; | |||||
module->learningId = -1; | |||||
} | |||||
}; | |||||
struct MidiTrigWidget : Grid16MidiWidget { | |||||
MIDI_Gate *module; | |||||
GridChoice *createGridChoice() override { | |||||
MidiTrigChoice *gridChoice = new MidiTrigChoice; | |||||
gridChoice->module = module; | |||||
return gridChoice; | |||||
json_t *midiJ = json_object_get(rootJ, "midi"); | |||||
if (midiJ) | |||||
midiInput.fromJson(midiJ); | |||||
} | } | ||||
}; | }; | ||||
@@ -233,12 +171,12 @@ struct MIDI_GateWidget : ModuleWidget { | |||||
addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 14)); | addOutput(createOutput<PJ301MPort>(mm2px(Vec(27.09498, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 14)); | ||||
addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 15)); | addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.693932, 108.14429)), module, MIDI_Gate::TRIG_OUTPUT + 15)); | ||||
MidiTrigWidget *midiWidget = createWidget<MidiTrigWidget>(mm2px(Vec(3.399621, 14.837339))); | |||||
midiWidget->module = module; | |||||
typedef Grid16MidiWidget<NoteChoice<MIDI_Gate>> TMidiWidget; | |||||
TMidiWidget *midiWidget = createWidget<TMidiWidget>(mm2px(Vec(3.399621, 14.837339))); | |||||
midiWidget->box.size = mm2px(Vec(44, 54.667)); | midiWidget->box.size = mm2px(Vec(44, 54.667)); | ||||
if (module) | if (module) | ||||
midiWidget->midiIO = &module->midiInput; | midiWidget->midiIO = &module->midiInput; | ||||
midiWidget->createGridChoices(); | |||||
midiWidget->setModule(module); | |||||
addChild(midiWidget); | addChild(midiWidget); | ||||
} | } | ||||
@@ -248,12 +186,12 @@ struct MIDI_GateWidget : ModuleWidget { | |||||
struct VelocityItem : MenuItem { | struct VelocityItem : MenuItem { | ||||
MIDI_Gate *module; | MIDI_Gate *module; | ||||
void onAction(const event::Action &e) override { | void onAction(const event::Action &e) override { | ||||
module->velocity ^= true; | |||||
module->velocityMode ^= true; | |||||
} | } | ||||
}; | }; | ||||
menu->addChild(new MenuEntry); | menu->addChild(new MenuEntry); | ||||
VelocityItem *velocityItem = createMenuItem<VelocityItem>("Velocity", CHECKMARK(module->velocity)); | |||||
VelocityItem *velocityItem = createMenuItem<VelocityItem>("Velocity", CHECKMARK(module->velocityMode)); | |||||
velocityItem->module = module; | velocityItem->module = module; | ||||
menu->addChild(velocityItem); | menu->addChild(velocityItem); | ||||
} | } | ||||