Karya, built on Mon Jul 24 11:39:07 PDT 2017 (patch 33511aca01257b76b88de7c7a2763b7a965c084e)

Safe HaskellNone

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

cmd_midi_thru :: Msg.Msg -> Cmd.CmdId Cmd.Status Source #

Send midi thru, addressing it to the given Instrument.

util

channel_messages Source #

Arguments

:: Cmd.M m 
=> Maybe 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.

cmd_midi_thru :: Msg.Msg -> Cmd.CmdId Cmd.Status Source #

Send midi thru, addressing it to the given Instrument.

keyswitch_to_midi :: [(Midi.WriteDevice, Midi.Message)] -> Patch.Keyswitch -> [(Midi.WriteDevice, Midi.Message)] Source #

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.

input_to_nn :: Cmd.M m => Instrument -> Patch.AttributeMap -> Maybe Patch.Scale -> Scale -> Attrs.Attributes -> InputNote.Input -> m (Maybe (InputNote.InputNn, [Patch.Keyswitch])) Source #

Realize the Input as a pitch in the given scale.

filter_transposers :: Scale -> Deriver a -> Deriver a Source #

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.

convert_pitch Source #

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.

alloc_addr Source #

If the note_id is already playing in an addr, return that one. Otherwise, if it's not NoteOn or NoteOff, abort. If it is, pick a free addr, and if there is no free one, pick the oldest one. Update the wdev state and assign the note id to the addr.

util

channel_messages Source #

Arguments

:: Cmd.M m 
=> Maybe 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.