Browse Source

Allow disabling smoothing for MIDI-CV (pitch and mod wheel), MIDI-CC, and MIDI-Map.

tags/v2.0.0
Andrew Belt 4 years ago
parent
commit
16a1b875e0
4 changed files with 200 additions and 119 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +28
    -8
      src/core/MIDI_CC.cpp
  3. +137
    -105
      src/core/MIDI_CV.cpp
  4. +34
    -6
      src/core/MIDI_Map.cpp

+ 1
- 0
CHANGELOG.md View File

@@ -39,6 +39,7 @@ In this document, Mod is Ctrl on Windows/Linux and Cmd on Mac.
- Use MIDI timestamps in MIDI-CV, MIDI-CC, MIDI-Gate, and MIDI-Map to improve overall timing and drastically reduce clock jitter. - Use MIDI timestamps in MIDI-CV, MIDI-CC, MIDI-Gate, and MIDI-Map to improve overall timing and drastically reduce clock jitter.
- Add red clip lights to VCV Audio-8/16 when signal reaches beyond ±10V. - Add red clip lights to VCV Audio-8/16 when signal reaches beyond ±10V.
- Reset notes in MIDI-CV and MIDI-Gate if an "all notes off" MIDI message is received. - Reset notes in MIDI-CV and MIDI-Gate if an "all notes off" MIDI message is received.
- Allow disabling smoothing for MIDI-CV (pitch and mod wheel), MIDI-CC, and MIDI-Map.
- API - API
- Add `Module::configInput()` and `Module::configOutput()` for adding names to ports. - Add `Module::configInput()` and `Module::configOutput()` for adding names to ports.
- Replace `ParamWidget::paramQuantity` with `ParamWidget::getParamQuantity()`. - Replace `ParamWidget::paramQuantity` with `ParamWidget::getParamQuantity()`.


+ 28
- 8
src/core/MIDI_CC.cpp View File

@@ -31,6 +31,7 @@ struct MIDI_CC : Module {
int learnedCcs[16]; int learnedCcs[16];
/** [cell][channel] */ /** [cell][channel] */
dsp::ExponentialFilter valueFilters[16][16]; dsp::ExponentialFilter valueFilters[16][16];
bool smooth;
bool mpeMode; bool mpeMode;
bool lsbEnabled; bool lsbEnabled;


@@ -63,6 +64,7 @@ struct MIDI_CC : Module {
learnedCcs[i] = i; learnedCcs[i] = i;
} }
midiInput.reset(); midiInput.reset();
smooth = true;
mpeMode = false; mpeMode = false;
lsbEnabled = false; lsbEnabled = false;
} }
@@ -88,23 +90,23 @@ struct MIDI_CC : Module {
int cc = learnedCcs[i]; int cc = learnedCcs[i];


for (int c = 0; c < channels; c++) { for (int c = 0; c < channels; c++) {
int16_t cellValue = ccValues[cc][c] * 128;
int16_t cellValue = int16_t(ccValues[cc][c]) * 128;
if (lsbEnabled && cc < 32) if (lsbEnabled && cc < 32)
cellValue += ccValues[cc + 32][c]; cellValue += ccValues[cc + 32][c];
// Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127.
float value = cellValue / float(128 * 127);
// Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127, because this is the maximum value that 7-bit controllers can send.
float value = float(cellValue) / (128 * 127);
// Support negative values because the gamepad MIDI driver generates nonstandard 8-bit CC values. // Support negative values because the gamepad MIDI driver generates nonstandard 8-bit CC values.
value = clamp(value, -1.f, 1.f); value = clamp(value, -1.f, 1.f);


// Detect behavior from MIDI buttons. // Detect behavior from MIDI buttons.
if (std::fabs(valueFilters[i][c].out - value) >= 1.f) {
// Jump value
valueFilters[i][c].out = value;
}
else {
if (smooth && std::fabs(valueFilters[i][c].out - value) < 1.f) {
// Smooth value with filter // Smooth value with filter
valueFilters[i][c].process(args.sampleTime, value); valueFilters[i][c].process(args.sampleTime, value);
} }
else {
// Jump value
valueFilters[i][c].out = value;
}
outputs[CC_OUTPUT + i].setVoltage(valueFilters[i][c].out * 10.f, c); outputs[CC_OUTPUT + i].setVoltage(valueFilters[i][c].out * 10.f, c);
} }
} }
@@ -168,6 +170,7 @@ struct MIDI_CC : Module {


json_object_set_new(rootJ, "midi", midiInput.toJson()); json_object_set_new(rootJ, "midi", midiInput.toJson());


json_object_set_new(rootJ, "smooth", json_boolean(smooth));
json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode)); json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode));
json_object_set_new(rootJ, "lsbEnabled", json_boolean(lsbEnabled)); json_object_set_new(rootJ, "lsbEnabled", json_boolean(lsbEnabled));
return rootJ; return rootJ;
@@ -197,6 +200,10 @@ struct MIDI_CC : Module {
if (midiJ) if (midiJ)
midiInput.fromJson(midiJ); midiInput.fromJson(midiJ);


json_t* smoothJ = json_object_get(rootJ, "smooth");
if (smoothJ)
smooth = json_boolean_value(smoothJ);

json_t* mpeModeJ = json_object_get(rootJ, "mpeMode"); json_t* mpeModeJ = json_object_get(rootJ, "mpeMode");
if (mpeModeJ) if (mpeModeJ)
mpeMode = json_boolean_value(mpeModeJ); mpeMode = json_boolean_value(mpeModeJ);
@@ -248,6 +255,19 @@ struct MIDI_CCWidget : ModuleWidget {


menu->addChild(new MenuSeparator); menu->addChild(new MenuSeparator);


struct SmoothItem : MenuItem {
MIDI_CC* module;
void onAction(const event::Action& e) override {
module->smooth ^= true;
}
};

SmoothItem* smoothItem = new SmoothItem;
smoothItem->text = "Smooth CC";
smoothItem->rightText = CHECKMARK(module->smooth);
smoothItem->module = module;
menu->addChild(smoothItem);

struct MpeModeItem : MenuItem { struct MpeModeItem : MenuItem {
MIDI_CC* module; MIDI_CC* module;
void onAction(const event::Action& e) override { void onAction(const event::Action& e) override {


+ 137
- 105
src/core/MIDI_CV.cpp View File

@@ -35,6 +35,7 @@ struct MIDI_CV : Module {


midi::InputQueue midiInput; midi::InputQueue midiInput;


bool smooth;
int channels; int channels;
enum PolyMode { enum PolyMode {
ROTATE_MODE, ROTATE_MODE,
@@ -94,6 +95,7 @@ struct MIDI_CV : Module {
} }


void onReset() override { void onReset() override {
smooth = true;
channels = 1; channels = 1;
polyMode = ROTATE_MODE; polyMode = ROTATE_MODE;
clockDivision = 24; clockDivision = 24;
@@ -143,19 +145,38 @@ struct MIDI_CV : Module {
outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(args.sampleTime) ? 10.f : 0.f, c); outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(args.sampleTime) ? 10.f : 0.f, c);
} }


// Set pitch and mod wheel
auto updatePitch = [&](int c) {
float pitch = ((int) pitches[c] - 8192) / 8191.f;
pitch = clamp(pitch, -1.f, 1.f);
if (smooth)
pitch = pitchFilters[c].process(args.sampleTime, pitch);
else
pitchFilters[c].out = pitch;
outputs[PITCH_OUTPUT].setVoltage(pitchFilters[c].out * 5.f);
};
auto updateMod = [&](int c) {
float mod = mods[c] / 127.f;
mod = clamp(mod, 0.f, 1.f);
if (smooth)
modFilters[c].process(args.sampleTime, mod);
else
modFilters[c].out = mod;
outputs[MOD_OUTPUT].setVoltage(modFilters[c].out * 10.f);
};
if (polyMode == MPE_MODE) { if (polyMode == MPE_MODE) {
for (int c = 0; c < channels; c++) { for (int c = 0; c < channels; c++) {
outputs[PITCH_OUTPUT].setChannels(channels);
outputs[MOD_OUTPUT].setChannels(channels);
outputs[PITCH_OUTPUT].setVoltage(pitchFilters[c].process(args.sampleTime, rescale(pitches[c], 0, 1 << 14, -5.f, 5.f)), c);
outputs[MOD_OUTPUT].setVoltage(modFilters[c].process(args.sampleTime, rescale(mods[c], 0, 127, 0.f, 10.f)), c);
updatePitch(c);
outputs[PITCH_OUTPUT].setChannels(1);
updateMod(c);
outputs[MOD_OUTPUT].setChannels(1);
} }
} }
else { else {
updatePitch(0);
outputs[PITCH_OUTPUT].setChannels(1); outputs[PITCH_OUTPUT].setChannels(1);
updateMod(0);
outputs[MOD_OUTPUT].setChannels(1); outputs[MOD_OUTPUT].setChannels(1);
outputs[PITCH_OUTPUT].setVoltage(pitchFilters[0].process(args.sampleTime, rescale(pitches[0], 0, 1 << 14, -5.f, 5.f)));
outputs[MOD_OUTPUT].setVoltage(modFilters[0].process(args.sampleTime, rescale(mods[0], 0, 127, 0.f, 10.f)));
} }


outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(args.sampleTime) ? 10.f : 0.f); outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(args.sampleTime) ? 10.f : 0.f);
@@ -415,6 +436,7 @@ struct MIDI_CV : Module {


json_t* dataToJson() override { json_t* dataToJson() override {
json_t* rootJ = json_object(); json_t* rootJ = json_object();
json_object_set_new(rootJ, "smooth", json_boolean(smooth));
json_object_set_new(rootJ, "channels", json_integer(channels)); json_object_set_new(rootJ, "channels", json_integer(channels));
json_object_set_new(rootJ, "polyMode", json_integer(polyMode)); json_object_set_new(rootJ, "polyMode", json_integer(polyMode));
json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision)); json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision));
@@ -428,6 +450,10 @@ struct MIDI_CV : Module {
} }


void dataFromJson(json_t* rootJ) override { void dataFromJson(json_t* rootJ) override {
json_t* smoothJ = json_object_get(rootJ, "smooth");
if (smoothJ)
smooth = json_boolean_value(smoothJ);

json_t* channelsJ = json_object_get(rootJ, "channels"); json_t* channelsJ = json_object_get(rootJ, "channels");
if (channelsJ) if (channelsJ)
setChannels(json_integer_value(channelsJ)); setChannels(json_integer_value(channelsJ));
@@ -455,104 +481,6 @@ struct MIDI_CV : Module {
}; };




struct ClockDivisionValueItem : MenuItem {
MIDI_CV* module;
int clockDivision;
void onAction(const event::Action& e) override {
module->clockDivision = clockDivision;
}
};


struct ClockDivisionItem : MenuItem {
MIDI_CV* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
std::vector<int> divisions = {24 * 4, 24 * 2, 24, 24 / 2, 24 / 4, 24 / 8, 2, 1};
std::vector<std::string> divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"};
for (size_t i = 0; i < divisions.size(); i++) {
ClockDivisionValueItem* item = new ClockDivisionValueItem;
item->text = divisionNames[i];
item->rightText = CHECKMARK(module->clockDivision == divisions[i]);
item->module = module;
item->clockDivision = divisions[i];
menu->addChild(item);
}
return menu;
}
};


struct ChannelValueItem : MenuItem {
MIDI_CV* module;
int channels;
void onAction(const event::Action& e) override {
module->setChannels(channels);
}
};


struct ChannelItem : MenuItem {
MIDI_CV* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
for (int channels = 1; channels <= 16; channels++) {
ChannelValueItem* item = new ChannelValueItem;
if (channels == 1)
item->text = "Monophonic";
else
item->text = string::f("%d", channels);
item->rightText = CHECKMARK(module->channels == channels);
item->module = module;
item->channels = channels;
menu->addChild(item);
}
return menu;
}
};


struct PolyModeValueItem : MenuItem {
MIDI_CV* module;
MIDI_CV::PolyMode polyMode;
void onAction(const event::Action& e) override {
module->setPolyMode(polyMode);
}
};


struct PolyModeItem : MenuItem {
MIDI_CV* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
std::vector<std::string> polyModeNames = {
"Rotate",
"Reuse",
"Reset",
"MPE",
};
for (int i = 0; i < MIDI_CV::NUM_POLY_MODES; i++) {
MIDI_CV::PolyMode polyMode = (MIDI_CV::PolyMode) i;
PolyModeValueItem* item = new PolyModeValueItem;
item->text = polyModeNames[i];
item->rightText = CHECKMARK(module->polyMode == polyMode);
item->module = module;
item->polyMode = polyMode;
menu->addChild(item);
}
return menu;
}
};


struct MIDI_CVPanicItem : MenuItem {
MIDI_CV* module;
void onAction(const event::Action& e) override {
module->panic();
}
};


struct MIDI_CVWidget : ModuleWidget { struct MIDI_CVWidget : ModuleWidget {
MIDI_CVWidget(MIDI_CV* module) { MIDI_CVWidget(MIDI_CV* module) {
setModule(module); setModule(module);
@@ -587,25 +515,129 @@ struct MIDI_CVWidget : ModuleWidget {


menu->addChild(new MenuSeparator); menu->addChild(new MenuSeparator);


struct SmoothItem : MenuItem {
MIDI_CV* module;
void onAction(const event::Action& e) override {
module->smooth ^= true;
}
};

SmoothItem* smoothItem = new SmoothItem;
smoothItem->text = "Smooth pitch/mod wheel";
smoothItem->rightText = CHECKMARK(module->smooth);
smoothItem->module = module;
menu->addChild(smoothItem);

struct ClockDivisionValueItem : MenuItem {
MIDI_CV* module;
int clockDivision;
void onAction(const event::Action& e) override {
module->clockDivision = clockDivision;
}
};

struct ClockDivisionItem : MenuItem {
MIDI_CV* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
std::vector<int> divisions = {24 * 4, 24 * 2, 24, 24 / 2, 24 / 4, 24 / 8, 2, 1};
std::vector<std::string> divisionNames = {"Whole", "Half", "Quarter", "8th", "16th", "32nd", "12 PPQN", "24 PPQN"};
for (size_t i = 0; i < divisions.size(); i++) {
ClockDivisionValueItem* item = new ClockDivisionValueItem;
item->text = divisionNames[i];
item->rightText = CHECKMARK(module->clockDivision == divisions[i]);
item->module = module;
item->clockDivision = divisions[i];
menu->addChild(item);
}
return menu;
}
};

ClockDivisionItem* clockDivisionItem = new ClockDivisionItem; ClockDivisionItem* clockDivisionItem = new ClockDivisionItem;
clockDivisionItem->text = "CLK/N divider"; clockDivisionItem->text = "CLK/N divider";
clockDivisionItem->rightText = RIGHT_ARROW; clockDivisionItem->rightText = RIGHT_ARROW;
clockDivisionItem->module = module; clockDivisionItem->module = module;
menu->addChild(clockDivisionItem); menu->addChild(clockDivisionItem);


struct ChannelValueItem : MenuItem {
MIDI_CV* module;
int channels;
void onAction(const event::Action& e) override {
module->setChannels(channels);
}
};

struct ChannelItem : MenuItem {
MIDI_CV* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
for (int channels = 1; channels <= 16; channels++) {
ChannelValueItem* item = new ChannelValueItem;
if (channels == 1)
item->text = "Monophonic";
else
item->text = string::f("%d", channels);
item->rightText = CHECKMARK(module->channels == channels);
item->module = module;
item->channels = channels;
menu->addChild(item);
}
return menu;
}
};

ChannelItem* channelItem = new ChannelItem; ChannelItem* channelItem = new ChannelItem;
channelItem->text = "Polyphony channels"; channelItem->text = "Polyphony channels";
channelItem->rightText = string::f("%d", module->channels) + " " + RIGHT_ARROW; channelItem->rightText = string::f("%d", module->channels) + " " + RIGHT_ARROW;
channelItem->module = module; channelItem->module = module;
menu->addChild(channelItem); menu->addChild(channelItem);


struct PolyModeValueItem : MenuItem {
MIDI_CV* module;
MIDI_CV::PolyMode polyMode;
void onAction(const event::Action& e) override {
module->setPolyMode(polyMode);
}
};

struct PolyModeItem : MenuItem {
MIDI_CV* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
std::vector<std::string> polyModeNames = {
"Rotate",
"Reuse",
"Reset",
"MPE",
};
for (int i = 0; i < MIDI_CV::NUM_POLY_MODES; i++) {
MIDI_CV::PolyMode polyMode = (MIDI_CV::PolyMode) i;
PolyModeValueItem* item = new PolyModeValueItem;
item->text = polyModeNames[i];
item->rightText = CHECKMARK(module->polyMode == polyMode);
item->module = module;
item->polyMode = polyMode;
menu->addChild(item);
}
return menu;
}
};

PolyModeItem* polyModeItem = new PolyModeItem; PolyModeItem* polyModeItem = new PolyModeItem;
polyModeItem->text = "Polyphony mode"; polyModeItem->text = "Polyphony mode";
polyModeItem->rightText = RIGHT_ARROW; polyModeItem->rightText = RIGHT_ARROW;
polyModeItem->module = module; polyModeItem->module = module;
menu->addChild(polyModeItem); menu->addChild(polyModeItem);


MIDI_CVPanicItem* panicItem = new MIDI_CVPanicItem;
struct PanicItem : MenuItem {
MIDI_CV* module;
void onAction(const event::Action& e) override {
module->panic();
}
};

PanicItem* panicItem = new PanicItem;
panicItem->text = "Panic"; panicItem->text = "Panic";
panicItem->module = module; panicItem->module = module;
menu->addChild(panicItem); menu->addChild(panicItem);


+ 34
- 6
src/core/MIDI_Map.cpp View File

@@ -24,6 +24,7 @@ struct MIDI_Map : Module {


midi::InputQueue midiInput; midi::InputQueue midiInput;


bool smooth;
/** Number of maps */ /** Number of maps */
int mapLen = 0; int mapLen = 0;
/** The mapped CC number of each channel */ /** The mapped CC number of each channel */
@@ -65,6 +66,7 @@ struct MIDI_Map : Module {
} }


void onReset() override { void onReset() override {
smooth = true;
learningId = -1; learningId = -1;
learnedCc = false; learnedCc = false;
learnedParam = false; learnedParam = false;
@@ -116,14 +118,14 @@ struct MIDI_Map : Module {
continue; continue;
float value = values[cc] / 127.f; float value = values[cc] / 127.f;
// Detect behavior from MIDI buttons. // Detect behavior from MIDI buttons.
if (std::fabs(valueFilters[id].out - value) >= 1.f) {
// Jump value
valueFilters[id].out = value;
}
else {
if (smooth && std::fabs(valueFilters[id].out - value) < 1.f) {
// Smooth value with filter // Smooth value with filter
valueFilters[id].process(args.sampleTime * divider.getDivision(), value); valueFilters[id].process(args.sampleTime * divider.getDivision(), value);
} }
else {
// Jump value
valueFilters[id].out = value;
}
paramQuantity->setScaledValue(valueFilters[id].out); paramQuantity->setScaledValue(valueFilters[id].out);
} }
} }
@@ -153,6 +155,9 @@ struct MIDI_Map : Module {
updateMapLen(); updateMapLen();
refreshParamHandleText(learningId); refreshParamHandleText(learningId);
} }
// Ignore negative values generated using the nonstandard 8-bit MIDI extension from the gamepad driver
if (values[cc] < 0)
return;
values[cc] = value; values[cc] = value;
} }


@@ -250,13 +255,13 @@ struct MIDI_Map : Module {
} }
json_object_set_new(rootJ, "maps", mapsJ); json_object_set_new(rootJ, "maps", mapsJ);


json_object_set_new(rootJ, "smooth", json_boolean(smooth));
json_object_set_new(rootJ, "midi", midiInput.toJson()); json_object_set_new(rootJ, "midi", midiInput.toJson());
return rootJ; return rootJ;
} }


void dataFromJson(json_t* rootJ) override { void dataFromJson(json_t* rootJ) override {
clearMaps(); clearMaps();

json_t* mapsJ = json_object_get(rootJ, "maps"); json_t* mapsJ = json_object_get(rootJ, "maps");
if (mapsJ) { if (mapsJ) {
json_t* mapJ; json_t* mapJ;
@@ -277,6 +282,10 @@ struct MIDI_Map : Module {


updateMapLen(); updateMapLen();


json_t* smoothJ = json_object_get(rootJ, "smooth");
if (smoothJ)
smooth = json_boolean_value(smoothJ);

json_t* midiJ = json_object_get(rootJ, "midi"); json_t* midiJ = json_object_get(rootJ, "midi");
if (midiJ) if (midiJ)
midiInput.fromJson(midiJ); midiInput.fromJson(midiJ);
@@ -482,6 +491,25 @@ struct MIDI_MapWidget : ModuleWidget {
midiWidget->setModule(module); midiWidget->setModule(module);
addChild(midiWidget); addChild(midiWidget);
} }

void appendContextMenu(Menu* menu) override {
MIDI_Map* module = dynamic_cast<MIDI_Map*>(this->module);

menu->addChild(new MenuSeparator);

struct SmoothItem : MenuItem {
MIDI_Map* module;
void onAction(const event::Action& e) override {
module->smooth ^= true;
}
};

SmoothItem* smoothItem = new SmoothItem;
smoothItem->text = "Smooth CC";
smoothItem->rightText = CHECKMARK(module->smooth);
smoothItem->module = module;
menu->addChild(smoothItem);
}
}; };






Loading…
Cancel
Save