Tuesday, April 17, 2018

Input device and MIDI Thru

I got MIDI Thru working! No MIDI sequencer is complete without it. The input side was easy, I pretty much ripped it straight out of ChordEase.
The output side was tricky however, because you can't write events directly to a MIDI device that's been opened for synchronized streaming (as opposed to immediate output, as in ChordEase). Instead your callback function (which is being called at regular intervals by Windows) has to add the events to its next buffer, which it subsequently queues to the output device. My solution was to add a special input queue for "live" events. At the start of each callback, I check the live input queue, and if there are events in it, I dequeue them and add them to the start of my next MIDI buffer, before any events that may get added from the song's tracks. This is quite safe (provided a thread-safe queue is used) and it works pretty well. It does have some limitations though.
  1. MIDI thru is only operational while the sequencer is playing, not while it's stopped or paused. This is potentially annoying, and the only solution is to keep the MIDI output device open all the time, not only during playback. Doing so would have the additional benefit of reducing the lag between pressing "Play" and playback actually starting. Most of that lag is caused by opening the device, though some devices are worse than others. It's a cool idea and it's on the list, but I'm not going to deal with it right away.
  2. The delay can be longer than one might like, depending on how the sequencer's latency is set. At the default latency of 10ms, it's probably fine for controlling parameters, but Chopin is out of the question. The sequencer's latency can be set lower, all the way down to 1ms, but the lower it is, the greater the risk of timing glitches in song playback, due to the callback taking too long and not being able to keep up. This also depends on other factors such as tempo and the density of the sequence.
  3. The delay isn't constant. This is because I grab all the live input events that have occurred since the last callback and write them all to the start of the buffer. They probably didn't all happen at the same time, but they're all output at the same time. In other words, the live input can get "bunched up" into discrete packets.
Whether "bunching" is a problem depends on how fast and dense the input is. If the input is sparse, there might only be one input event per callback. But if there are multiple events per callback, the callback could try to approximate their original timing. The events have timestamps, which can presumably be used to space out the events in time. Note however that doing so makes the events even LATER than they already are. A nasty trade-off! This is a hard problem and I intend to ignore it for now. Possible bunching aside, the current scheme is straightforward and minimizes delay.
The live input queue also makes it possible to do live patch changes, along with volume and panning. This is already implemented in the Channels bar. The addition of a MIDI input device is a prerequisite to other useful things, such as MIDI mapping of all parameters, which is also on the list.

No comments:

Post a Comment

Heptatonic scales with a minor third

Which heptatonic scales consist entirely of semitones, whole tones, and a single minor third, without having two semitones in a row? The he...