#pragma once #include #include namespace rack { namespace dsp { /** Converts gates and CV to MIDI messages. CHANNELS is the number of polyphony channels. Use 1 for monophonic. */ template struct MidiGenerator { int8_t vels[CHANNELS]; int8_t notes[CHANNELS]; bool gates[CHANNELS]; int8_t keyPressures[CHANNELS]; int8_t channelPressure; int8_t ccs[128]; int16_t pw; bool clk; bool start; bool stop; bool cont; int64_t frame = -1; MidiGenerator() { reset(); } void reset() { for (int c = 0; c < CHANNELS; c++) { vels[c] = 100; notes[c] = 60; gates[c] = false; keyPressures[c] = -1; } channelPressure = -1; for (int i = 0; i < 128; i++) { ccs[i] = -1; } pw = 0x2000; clk = false; start = false; stop = false; cont = false; } void panic() { reset(); // Send all note off commands for (int note = 0; note <= 127; note++) { // Note off midi::Message m; m.setStatus(0x8); m.setNote(note); m.setValue(0); m.setFrame(frame); onMessage(m); } } /** Must be called before setNoteGate(). */ void setVelocity(int8_t vel, int c) { vels[c] = vel; } void setNoteGate(int8_t note, bool gate, int c) { bool changedNote = gate && gates[c] && (note != notes[c]); bool enabledGate = gate && !gates[c]; bool disabledGate = !gate && gates[c]; if (changedNote || disabledGate) { // Note off midi::Message m; m.setStatus(0x8); m.setNote(notes[c]); m.setValue(vels[c]); m.setFrame(frame); onMessage(m); } if (changedNote || enabledGate) { // Note on midi::Message m; m.setStatus(0x9); m.setNote(note); m.setValue(vels[c]); m.setFrame(frame); onMessage(m); } notes[c] = note; gates[c] = gate; } void setKeyPressure(int8_t val, int c) { if (keyPressures[c] == val) return; keyPressures[c] = val; // Polyphonic key pressure midi::Message m; m.setStatus(0xa); m.setNote(notes[c]); m.setValue(val); m.setFrame(frame); onMessage(m); } void setChannelPressure(int8_t val) { if (channelPressure == val) return; channelPressure = val; // Channel pressure midi::Message m; m.setSize(2); m.setStatus(0xd); m.setNote(val); m.setFrame(frame); onMessage(m); } void setCc(int8_t cc, int id) { if (ccs[id] == cc) return; ccs[id] = cc; // Continuous controller midi::Message m; m.setStatus(0xb); m.setNote(id); m.setValue(cc); m.setFrame(frame); onMessage(m); } void setModWheel(int8_t cc) { setCc(cc, 0x01); } void setVolume(int8_t cc) { setCc(cc, 0x07); } void setBalance(int8_t cc) { setCc(cc, 0x08); } void setPan(int8_t cc) { setCc(cc, 0x0a); } void setSustainPedal(int8_t cc) { setCc(cc, 0x40); } void setPitchWheel(int16_t pw) { if (this->pw == pw) return; this->pw = pw; // Pitch wheel midi::Message m; m.setStatus(0xe); m.setNote(pw & 0x7f); m.setValue((pw >> 7) & 0x7f); m.setFrame(frame); onMessage(m); } void setClock(bool clk) { if (this->clk == clk) return; this->clk = clk; if (clk) { // Timing clock midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0x8); m.setFrame(frame); onMessage(m); } } void setStart(bool start) { if (this->start == start) return; this->start = start; if (start) { // Start midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0xa); m.setFrame(frame); onMessage(m); } } void setContinue(bool cont) { if (this->cont == cont) return; this->cont = cont; if (cont) { // Continue midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0xb); m.setFrame(frame); onMessage(m); } } void setStop(bool stop) { if (this->stop == stop) return; this->stop = stop; if (stop) { // Stop midi::Message m; m.setSize(1); m.setStatus(0xf); m.setChannel(0xc); m.setFrame(frame); onMessage(m); } } void setFrame(int64_t frame) { this->frame = frame; } virtual void onMessage(const midi::Message& message) {} }; } // namespace dsp } // namespace rack