I just wanted to follow up this thread. As a proof of concept, I patched addToSequence
in tracktion_MidiList.cpp
to insert MIDI Tuning SysEx messages. I also added a member to MidiNote
called pitchDeviation
(being a float value describing the deviation from 12 EDO in semitones).
In tracktion_MidiList.cpp
:
static void addToSequence (juce::MidiMessageSequence& seq, const MidiClip& clip,
const MidiNote& note, int channelNumber, bool addNoteUp,
const GrooveTemplate* grooveTemplate)
// ...
float pitchDev = note.getPitchDeviation();
if (addNoteUp)
{
// nudge the note-up backwards just a bit to make sure the ordering is correct
double upTime = note.getPlaybackTime (MidiNote::endEdge, clip, grooveTemplate);
if (upTime > downTime && upTime > 0.0)
{
// Tune the note if necessary
if (pitchDev != 0) {
seq.addEvent(createSingleNoteTuningMessage(noteNumber, pitchDev, channelNumber), std::max (0.0, downTime));
}
seq.addEvent (juce::MidiMessage::noteOn (channelNumber, noteNumber, velocity), std::max (0.0, downTime));
// Tune back
if (pitchDev != 0) {
seq.addEvent(createSingleNoteTuningMessage(noteNumber, 0.0, channelNumber), std::max (0.0, downTime));
}
seq.addEvent (juce::MidiMessage::noteOff (channelNumber, noteNumber), upTime);
}
}
// ... etc
}
static juce::MidiMessage createSingleNoteTuningMessage(int noteNumber, float pitchDev, int ch)
{
float tuned = noteNumber + pitchDev;
juce::uint8 data1 = tuned;
juce::uint16 micro = (tuned - data1) * 0x3fff;
juce::uint8 data2 = micro / 0x80;
juce::uint8 data3 = micro % 0x80;
juce::uint8 data[11] = {
0x7e, // non-real time
0x7f, // target device ID (7F = all devices)
0x08, // sub-id #1 "Midi Tuning Standard"
0x07, // sub-id #2 "Single note tuning change (non real-time) (bank)"
0x01, // bank (always use bank 1 for now)
(juce::uint8) ch-1, // tuning preset ("program"), use channel number for that
0x01, // number of changes (1 in this case)
(juce::uint8) noteNumber, // MIDI key
data1, // change 1 - freq byte 1
data2, // change 1 - freq byte 2
data3 // change 1 - freq byte 3
};
return juce::MidiMessage::createSysExMessage (data, sizeof(data));
}
So far, this seems to work very well: the noteNumber
is still an integer so existing code would not have to be changed and the pitchDeviation
value can be safely ignored by code not interested in microtonality.
So the question now would be if and how this could be achieved without the need of monkey-patching the tracktion engine source code. From my perspective of course it would be most convenient if the tracktion engine added support for this out of the box, but another possible way would be to add a more general hook into the conversion from clip to MidiMessageSequence (or indeed the possibility to override the appropriate functions such as addToSequence
).