Safe Haskell | Safe-Inferred |
---|
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
- cmd_midi_thru :: Msg.Msg -> Cmd.CmdId Cmd.Status
- for_instrument :: ScoreT.Instrument -> Cmd.ThruFunction
- convert_input :: Cmd.M m => ScoreT.Instrument -> Scale -> InputNote.Input -> m InputNote.InputNn
- channel_messages :: Cmd.M m => Maybe ScoreT.Instrument -> Bool -> [Midi.ChannelMessage] -> m ()
Documentation
cmd_midi_thru :: Msg.Msg -> Cmd.CmdId Cmd.Status Source #
Send midi thru, addressing it to the given Instrument.
Actually, this handles Cmd.ImThru
as well, since it relies on the
instrument itself providing the thru function in Cmd.inst_thru
.
convert_input :: Cmd.M m => ScoreT.Instrument -> Scale -> InputNote.Input -> m InputNote.InputNn Source #
Convert a keyboard input into the NoteNumber desired by the scale.
util
:: Cmd.M m | |
=> Maybe ScoreT.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.