Browse Source

Add code logic go MIDI-Gate

Signed-off-by: falkTX <falktx@falktx.com>
tags/22.02
falkTX 3 years ago
parent
commit
d68c303627
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
2 changed files with 391 additions and 4 deletions
  1. +389
    -3
      plugins/Cardinal/src/HostMIDI-Gate.cpp
  2. +2
    -1
      plugins/Cardinal/src/HostMIDI.cpp

+ 389
- 3
plugins/Cardinal/src/HostMIDI-Gate.cpp View File

@@ -16,7 +16,7 @@
*/

/**
* This file contains a substantial amount of code from VCVRack's core/....cpp and core/....cpp
* This file contains a substantial amount of code from VCVRack's core/Gate_MIDI.cpp and core/MIDI_Gate.cpp
* Copyright (C) 2016-2021 VCV.
*
* This program is free software: you can redistribute it and/or
@@ -38,9 +38,11 @@ struct HostMIDIGate : Module {
NUM_PARAMS
};
enum InputIds {
ENUMS(GATE_INPUTS, 16),
NUM_INPUTS
};
enum OutputIds {
ENUMS(GATE_OUTPUTS, 16),
NUM_OUTPUTS
};
enum LightIds {
@@ -49,21 +51,309 @@ struct HostMIDIGate : Module {

CardinalPluginContext* const pcontext;

struct MidiInput {
// Cardinal specific
CardinalPluginContext* const pcontext;
midi::Message converterMsg;
const MidiEvent* midiEvents;
uint32_t midiEventsLeft;
uint32_t midiEventFrame;
int64_t lastBlockFrame;
uint8_t channel;

// stuff from Rack
/** [cell][channel] */
bool gates[16][16];
/** [cell][channel] */
float gateTimes[16][16];
/** [cell][channel] */
uint8_t velocities[16][16];
/** Cell ID in learn mode, or -1 if none. */
int learningId;

bool mpeMode;

MidiInput(CardinalPluginContext* const pc)
: pcontext(pc)
{
converterMsg.bytes.resize(0xff);
reset();
}

void reset()
{
midiEvents = nullptr;
midiEventsLeft = 0;
midiEventFrame = 0;
lastBlockFrame = -1;
channel = 0;
learningId = -1;
mpeMode = false;
panic();
}

void panic() {
for (int i = 0; i < 16; ++i)
{
for (int c = 0; c < 16; ++c)
{
gates[i][c] = false;
gateTimes[i][c] = 0.f;
}
}
}

bool process(const ProcessArgs& args, std::vector<rack::engine::Output>& outputs,
const bool velocityMode, uint8_t learnedNotes[16])
{
// Cardinal specific
const int64_t blockFrame = pcontext->engine->getBlockFrame();
const bool blockFrameChanged = lastBlockFrame != blockFrame;

if (blockFrameChanged)
{
lastBlockFrame = blockFrame;

midiEvents = pcontext->midiEvents;
midiEventsLeft = pcontext->midiEventCount;
midiEventFrame = 0;
}

while (midiEventsLeft != 0)
{
const MidiEvent& midiEvent(*midiEvents);

if (midiEvent.frame > midiEventFrame)
break;

++midiEvents;
--midiEventsLeft;

const uint8_t* data;

if (midiEvent.size > MidiEvent::kDataSize)
{
data = midiEvent.dataExt;
converterMsg.bytes.resize(midiEvent.size);
}
else
{
data = midiEvent.data;
}

if (channel != 0 && data[0] < 0xF0)
{
if ((data[0] & 0x0F) != (channel - 1))
continue;
}

// adapted from Rack
switch (data[0] & 0xF0)
{
// note on
case 0x90:
if (data[2] > 0)
{
const int c = mpeMode ? (data[0] & 0x0F) : 0;
// Learn
if (learningId >= 0) {
learnedNotes[learningId] = data[1];
learningId = -1;
}
// Find id
for (int i = 0; i < 16; i++) {
if (learnedNotes[i] == data[1]) {
gates[i][c] = true;
gateTimes[i][c] = 1e-3f;
velocities[i][c] = data[2];
}
}
break;
}
// fall-through
// note off
case 0x80:
const int c = mpeMode ? (data[0] & 0x0F) : 0;
// Find id
for (int i = 0; i < 16; i++) {
if (learnedNotes[i] == data[1]) {
gates[i][c] = false;
}
}
break;
}
}

++midiEventFrame;

// Rack stuff
const int channels = mpeMode ? 16 : 1;

for (int i = 0; i < 16; i++) {
outputs[GATE_OUTPUTS + i].setChannels(channels);
for (int c = 0; c < channels; c++) {
// Make sure all pulses last longer than 1ms
if (gates[i][c] || gateTimes[i][c] > 0.f)
{
float velocity = velocityMode ? (velocities[i][c] / 127.f) : 1.f;
outputs[GATE_OUTPUTS + i].setVoltage(velocity * 10.f, c);
gateTimes[i][c] -= args.sampleTime;
}
else
{
outputs[GATE_OUTPUTS + i].setVoltage(0.f, c);
}
}
}

return blockFrameChanged;
}

} midiInput;

struct MidiOutput {
// cardinal specific
CardinalPluginContext* const pcontext;
uint8_t channel = 0;

// base class vars
int vels[128];
bool lastGates[128];
int64_t frame = 0;

MidiOutput(CardinalPluginContext* const pc)
: pcontext(pc)
{
reset();
}

void reset()
{
// base class vars
for (int note = 0; note < 128; ++note)
{
vels[note] = 100;
lastGates[note] = false;
}

// cardinal specific
channel = 0;
}

void panic()
{
// TODO send all notes off CC

// Send all note off commands
for (int note = 0; note < 128; note++)
{
// Note off
midi::Message m;
m.setStatus(0x8);
m.setNote(note);
m.setValue(0);
m.setFrame(frame);
sendMessage(m);
lastGates[note] = false;
}
}

void setVelocity(int vel, int note)
{
vels[note] = vel;
}

void setGate(bool gate, int note)
{
if (gate && !lastGates[note])
{
// Note on
midi::Message m;
m.setStatus(0x9);
m.setNote(note);
m.setValue(vels[note]);
m.setFrame(frame);
sendMessage(m);
}
else if (!gate && lastGates[note])
{
// Note off
midi::Message m;
m.setStatus(0x8);
m.setNote(note);
m.setValue(vels[note]);
m.setFrame(frame);
sendMessage(m);
}
lastGates[note] = gate;
}

void sendMessage(const midi::Message& message)
{
pcontext->writeMidiMessage(message, channel);
}

} midiOutput;

bool velocityMode = false;
uint8_t learnedNotes[16] = {};

HostMIDIGate()
: pcontext(static_cast<CardinalPluginContext*>(APP))
: pcontext(static_cast<CardinalPluginContext*>(APP)),
midiInput(pcontext),
midiOutput(pcontext)
{
if (pcontext == nullptr)
throw rack::Exception("Plugin context is null");

config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);

for (int i = 0; i < 16; i++)
configInput(GATE_INPUTS + i, string::f("Cell %d", i + 1));

for (int i = 0; i < 16; i++)
configOutput(GATE_OUTPUTS + i, string::f("Gate %d", i + 1));

onReset();
}

void onReset() override
{
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x)
learnedNotes[4 * y + x] = 36 + 4 * (3 - y) + x;

velocityMode = false;

midiInput.reset();
midiOutput.reset();
}

void process(const ProcessArgs& args) override
{
if (midiInput.process(args, outputs, velocityMode, learnedNotes))
midiOutput.frame = 0;
else
++midiOutput.frame;

for (int i = 0; i < 16; i++)
{
const int note = learnedNotes[i];

if (velocityMode)
{
int vel = (int) std::round(inputs[GATE_INPUTS + i].getVoltage() / 10.f * 127);
vel = clamp(vel, 0, 127);
midiOutput.setVelocity(vel, note);
midiOutput.setGate(vel > 0, note);
}
else
{
const bool gate = inputs[GATE_INPUTS + i].getVoltage() >= 1.f;
midiOutput.setVelocity(100, note);
midiOutput.setGate(gate, note);
}
}
}

json_t* dataToJson() override
@@ -71,11 +361,52 @@ struct HostMIDIGate : Module {
json_t* const rootJ = json_object();
DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr);

// input and output
if (json_t* const notesJ = json_array())
{
for (int i = 0; i < 16; i++)
json_array_append_new(notesJ, json_integer(learnedNotes[i]));
json_object_set_new(rootJ, "notes", notesJ);
}
json_object_set_new(rootJ, "velocity", json_boolean(velocityMode));

// input only
json_object_set_new(rootJ, "mpeMode", json_boolean(midiInput.mpeMode));

// separate
json_object_set_new(rootJ, "inputChannel", json_integer(midiInput.channel));
json_object_set_new(rootJ, "outputChannel", json_integer(midiOutput.channel));

return rootJ;
}

void dataFromJson(json_t* rootJ) override
void dataFromJson(json_t* const rootJ) override
{
// input and output
if (json_t* const notesJ = json_object_get(rootJ, "notes"))
{
for (int i = 0; i < 16; i++)
{
if (json_t* const noteJ = json_array_get(notesJ, i))
learnedNotes[i] = json_integer_value(noteJ);
else
learnedNotes[i] = -1;
}
}

if (json_t* const velocityJ = json_object_get(rootJ, "velocity"))
velocityMode = json_boolean_value(velocityJ);

// input only
if (json_t* const mpeModeJ = json_object_get(rootJ, "mpeMode"))
midiInput.mpeMode = json_boolean_value(mpeModeJ);

// separate
if (json_t* const inputChannelJ = json_object_get(rootJ, "inputChannel"))
midiInput.channel = json_integer_value(inputChannelJ);

if (json_t* const outputChannelJ = json_object_get(rootJ, "outputChannel"))
midiOutput.channel = json_integer_value(outputChannelJ) & 0x0F;
}
};

@@ -115,6 +446,61 @@ struct HostMIDIGateWidget : ModuleWidget {

void appendContextMenu(Menu* const menu) override
{
menu->addChild(new MenuSeparator);
menu->addChild(createMenuLabel("MIDI Input"));

menu->addChild(createBoolPtrMenuItem("MPE mode", "", &module->midiInput.mpeMode));

struct InputChannelItem : MenuItem {
HostMIDIGate* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
for (int c = 0; c <= 16; c++) {
menu->addChild(createCheckMenuItem((c == 0) ? "All" : string::f("%d", c), "",
[=]() {return module->midiInput.channel == c;},
[=]() {module->midiInput.channel = c;}
));
}
return menu;
}
};
InputChannelItem* const inputChannelItem = new InputChannelItem;
inputChannelItem->text = "MIDI channel";
inputChannelItem->rightText = (module->midiInput.channel ? string::f("%d", module->midiInput.channel) : "All")
+ " " + RIGHT_ARROW;
inputChannelItem->module = module;
menu->addChild(inputChannelItem);

menu->addChild(new MenuSeparator);
menu->addChild(createMenuLabel("MIDI Output"));

struct OutputChannelItem : MenuItem {
HostMIDIGate* module;
Menu* createChildMenu() override {
Menu* menu = new Menu;
for (uint8_t c = 0; c < 16; c++) {
menu->addChild(createCheckMenuItem(string::f("%d", c+1), "",
[=]() {return module->midiOutput.channel == c;},
[=]() {module->midiOutput.channel = c;}
));
}
return menu;
}
};
OutputChannelItem* const outputChannelItem = new OutputChannelItem;
outputChannelItem->text = "MIDI channel";
outputChannelItem->rightText = string::f("%d", module->midiOutput.channel+1) + " " + RIGHT_ARROW;
outputChannelItem->module = module;
menu->addChild(outputChannelItem);

menu->addChild(new MenuSeparator);
menu->addChild(createMenuLabel("MIDI Input & Output"));

menu->addChild(createBoolPtrMenuItem("Velocity mode", "", &module->velocityMode));

menu->addChild(createMenuItem("Panic", "",
[=]() { module->midiInput.panic(); module->midiOutput.panic(); }
));
}
};



+ 2
- 1
plugins/Cardinal/src/HostMIDI.cpp View File

@@ -274,7 +274,8 @@ struct HostMIDI : Module {
return blockFrameChanged;
}

void processMessage(const midi::Message& msg) {
void processMessage(const midi::Message& msg)
{
// DEBUG("MIDI: %ld %s", msg.getFrame(), msg.toString().c_str());

switch (msg.getStatus()) {


Loading…
Cancel
Save