Karya, built on 2018-02-23T20:23:55 (patch cf8565b7ac832266878af99a942555d139065f12)

Cmd.MidiThru

Contents

Description

Implement midi thru by mapping InputNotes to MIDI messages.

This is effectively a recreation of the deriver and MIDI performer, but geared to producing a single note immediately rather than deriving and performing an entire score. But since derivation and performance are both very complicated, it's doomed to be complicated and inaccurate.

The rationale is that the performer is oriented around a stream of events when their durations are known, while this must derive a single key, and in real time. However, it's also due to history (derivation used to be much simpler), and concerns about efficiency, so in the future I'll probably move towards reusing as much of the deriver and performer as possible.

Note that actually much of the deriver is already reused, courtesy of Perf.derive_at. Also, scale_input_to_nn may have a shortcut implementation, but for complicated scales falls back on derivation.

An implementation that fully reuses deriver and performer is in Cmd.Instrument.CUtil.insert_expr.

This is a very complicated thru and might be too slow. It has to deal with:

• Remap input pitch according to scale and control pitch bend range (done by NoteEntry) and instrument pb range. This means keeping track of previous note id and pb val.
• Remap addr based on addrs assign to instrument, assigning round-robin. This means keeping track of note ids assigned to addrs and serial numbers for each addr.

It's different from the usual simple thru in that it attempts to assign control messages to a single note. So if the instrument is multiplexed, control changes (including pitch bend) will go only to the last sounding key. This also means that controls will not go through at all unless there is a note sounding.

It should be possible to reduce latency by bypassing the responder loop and running this in its own thread. It does mean the InputNote work is duplicated and synchronization of state, such as current instrument info, gets more complicated because it has to go through an mvar or something.

I should find out what makes the responder so slow. Profile it!

• The sync afterwards: Some mechanism to find out if no Ui.State changes have happened and skip sync.
• Marshalling the cmd list: cache the expensive parts. The only changing bit is the focus cmds, so keep those until focus changes.
• Duplicate NoteInput conversions.
• Instrument is looked up on every msg just for pb_range, so cache that. Effectively, the short-circuit thread is another way to cache this.

Synopsis

# Documentation

Send midi thru, addressing it to the given Instrument.

# util

Arguments

 :: Cmd.M m => Maybe Score.Instrument use this inst, or the one on the selected track if Nothing. -> Bool -> [Midi.ChannelMessage] -> m ()

Send ChannelMessages to the addrs (or just the lowest addr) of the current instrument. This bypasses all of the WriteDeviceState stuff so it won't cooperate with addr allocation, but hopefully this won't cause problems for simple uses like keymapped instruments.

Send midi thru, addressing it to the given Instrument.

I used to keep track of the previous PitchBend to avoid sending extra ones. But it turns out I don't actually know the state of the MIDI channel, so now I always send PitchBend. I'm not sure why I ever thought it could work. I could still do this by tracking channel state at the Midi.Interface level. I actually already do that a bit tracking with note_tracker, but it's simpler to just always send PitchBend, unless it becomes a problem.

The keyswitch winds up being simultaneous with the note on. Especially stupid VSTs like kontakt will sometimes miss a keyswitch if it doesn't have enough lead time. There's not much I can do about that, but to avoid making the keyswitch too short I hold it down along with the note.

Realize the Input as a pitch in the given scale.

Remove transposers because otherwise the thru pitch doesn't match the entered pitch and it's very confusing. However, I retain Controls.octave and Controls.hz because those are used to configure a scale, e.g. via config_controls, and the pitch is nominally the same.

Arguments

 :: Patch.AttributeMap -> Maybe Patch.Scale -> Attrs.Attributes -> Pitch.NoteNumber -> (Maybe (Pitch.NoteNumber, [Patch.Keyswitch]), Bool) The Bool is True if the attrs were non-empty but not found.

This is a midi thru version of convert_midi_pitch. It's different because it works with a scalar NoteNumber instead of a Score.Event with a pitch signal, which makes it hard to share code.

Arguments