Skip to content

Commit

Permalink
Hemisphere: Experimental auto-MIDI-output
Browse files Browse the repository at this point in the history
Defaults to Channel 1 on Left / Channel 2 on Right
A/C == Note value
B/D == Gate (NoteOn vs. NoteOff)
MIDI-Out applet will modify MIDI Channel.

TODO:
* Use PitchBend for microtonal accuracy
* Implement Velocity and Aftertouch
* More configuration for CC#, or extra Notes
  • Loading branch information
djphazer committed May 20, 2024
1 parent 61879ba commit b48d0e4
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 11 deletions.
87 changes: 80 additions & 7 deletions software/src/HSIOFrame.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,42 @@ typedef struct IOFrame {
int function_cc[ADC_CHANNEL_LAST]; // CC# for each channel
uint16_t semitone_mask[ADC_CHANNEL_LAST]; // which notes are currently on

// Output values and ClockOut triggers, handled by MIDIIn applet
int outputs[DAC_CHANNEL_LAST];
bool trigout_q[DAC_CHANNEL_LAST];
bool clock_run = 0;

// MIDI input stuff handled by MIDIIn applet
int8_t notes_on[16]; // attempts to track how many notes are on, per MIDI channel
int last_msg_tick; // Tick of last received message
uint8_t clock_count; // MIDI clock counter (24ppqn)
int outputs[DAC_CHANNEL_LAST]; // translated CV values
bool trigout_q[DAC_CHANNEL_LAST];

// Clock/Start/Stop are handled by ClockSetup applet
bool clock_run = 0;
bool clock_q;
bool start_q;
bool stop_q;
uint8_t clock_count; // MIDI clock counter (24ppqn)
int last_msg_tick; // Tick of last received message

// MIDI output stuff
int outchan[DAC_CHANNEL_LAST] = {
0, 0, 1, 1,
#ifdef ARDUINO_TEENSY41
2, 2, 3, 3,
#endif
};
int outfn[DAC_CHANNEL_LAST] = {
HEM_MIDI_NOTE_OUT, HEM_MIDI_GATE_OUT,
HEM_MIDI_NOTE_OUT, HEM_MIDI_GATE_OUT,
#ifdef ARDUINO_TEENSY41
HEM_MIDI_NOTE_OUT, HEM_MIDI_GATE_OUT,
HEM_MIDI_NOTE_OUT, HEM_MIDI_GATE_OUT,
#endif
};
int outchan_last[DAC_CHANNEL_LAST];
uint8_t current_note[16]; // note number, per MIDI channel
int note_countdown[DAC_CHANNEL_LAST];
int inputs[DAC_CHANNEL_LAST]; // CV to be translated
int last_cv[DAC_CHANNEL_LAST];
bool clocked[DAC_CHANNEL_LAST];
bool gate_high[DAC_CHANNEL_LAST];
bool changed_cv[DAC_CHANNEL_LAST];

// Logging
MIDILogEntry log[7];
Expand Down Expand Up @@ -199,6 +222,55 @@ typedef struct IOFrame {
if (log_this) UpdateLog(message, data1, data2);
}
}
void Send(int *outvals) {

// first pass - calculate things and turn off notes
for (int i = 0; i < DAC_CHANNEL_LAST; ++i) {
inputs[i] = outvals[i];
gate_high[i] = inputs[i] > (12 << 7);
clocked[i] = (gate_high[i] && last_cv[i] < (12 << 7));
if (abs(inputs[i] - last_cv[i]) > HEMISPHERE_CHANGE_THRESHOLD) {
changed_cv[i] = 1;
last_cv[i] = inputs[i];
} else changed_cv[i] = 0;

if (outfn[i] == HEM_MIDI_NOTE_OUT) {
if (changed_cv[i]) {
// a note has changed, turn the last one off first
SendNoteOff(i);
current_note[outchan[i]] = MIDIQuantizer::NoteNumber( inputs[i] );
}
}
if (outfn[i] == HEM_MIDI_GATE_OUT) {
if (!gate_high[i] && changed_cv[i]) SendNoteOff(i);
}

// Handle clock pulse timing
if (note_countdown[i] > 0) {
if (--note_countdown[i] == 0) SendNoteOff(i);
}
}

// 2nd pass - send eligible notes
for (int i = 0; i < 2; ++i) {
if (outfn[i*2 + 1] == HEM_MIDI_GATE_OUT) {
if (clocked[i*2 + 1]) SendNoteOn(i*2 + 1, 0);
} else if (outfn[i*2] == HEM_MIDI_NOTE_OUT) {
if (changed_cv[i*2]) SendNoteOn(i*2);
}
}

usbMIDI.send_now();
}
void SendNoteOn(int ch, int timeout = HEMISPHERE_CLOCK_TICKS * HS::trig_length) {
usbMIDI.sendNoteOn(current_note[ outchan[ch] ], 100, outchan[ch] + 1);

outchan_last[ch] = outchan[ch];
note_countdown[ch] = timeout;
}
void SendNoteOff(int ch) {
usbMIDI.sendNoteOff(current_note[ outchan_last[ch] ], 0, outchan_last[ch] + 1);
}

} MIDIState;

Expand Down Expand Up @@ -248,6 +320,7 @@ typedef struct IOFrame {
for (int i = 0; i < DAC_CHANNEL_LAST; ++i) {
OC::DAC::set_pitch(DAC_CHANNEL(i), outputs[i], 0);
}
MIDIState.Send(outputs);

#ifdef ARDUINO_TEENSY41
// HACK - modulate filter with first output from RIGHT side...
Expand Down
24 changes: 20 additions & 4 deletions software/src/applets/hMIDIOut.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class hMIDIOut : public HemisphereApplet {
legato = 1;
log_index = 0;

// defaults for auto MIDI out
HS::frame.MIDIState.outfn[io_offset + 0] = HEM_MIDI_NOTE_OUT;
HS::frame.MIDIState.outfn[io_offset + 1] = HEM_MIDI_GATE_OUT;
}

void Controller() {
Expand Down Expand Up @@ -148,10 +151,23 @@ class hMIDIOut : public HemisphereApplet {
MoveCursor(cursor, direction, 4);
return;
}
if (cursor == 0) channel = constrain(channel + direction, 0, 15);
if (cursor == 1) transpose = constrain(transpose + direction, -24, 24);
if (cursor == 2) function = constrain(function + direction, 0, 3);
if (cursor == 3) legato = direction > 0 ? 1 : 0;

switch (cursor) {
case 0:
channel = constrain(channel + direction, 0, 15);
HS::frame.MIDIState.outchan[io_offset + 0] = channel;
HS::frame.MIDIState.outchan[io_offset + 1] = channel;
break;
case 1: transpose = constrain(transpose + direction, -24, 24);
break;
case 2:
function = constrain(function + direction, 0, 3);
HS::frame.MIDIState.outfn[io_offset + 0] = HEM_MIDI_NOTE_OUT;
HS::frame.MIDIState.outfn[io_offset + 1] = HEM_MIDI_GATE_OUT;
break;
case 3: legato = direction > 0 ? 1 : 0;
break;
}
ResetCursor();
}

Expand Down

0 comments on commit b48d0e4

Please sign in to comment.