Safe Haskell | Safe-Inferred |
---|
Convert incoming MIDI (and other inputs) to the internal note representation. This has two purposes: note entry, and midi thru.
This module is, in a way, dual to Perform.Midi.Perform. It takes MIDI
input to the internal Input
representation and back again, while Perform
takes the internal representation, in the form of
Event
, to MIDI output.
The overlapping part is that this module maps Control
s to
and from MIDI while Play uses Control
. They
use the same control names, though, so I can reuse code from Control.
One significant difference between Input
and MIDI is that MIDI supports
two levels of control addressing: note and channel, while Input
can
only represent note addressing through NoteId
. MIDI controls almost
all apply at the channel level, but of course these controls all apply
at the note level. The result is that a MIDI control that on a keyboard
affects the whole channel will only affect the last played note here. When
the input is converted back to MIDI it may wind up sharing a channel
anyway, at which point the control will go back to being channel global,
but if the instrument has multiple channels, I try to distribute between
them to keep note_ids on separate channels. This way, multiple channel
emitting controls can be mapped to multiple channel using instruments.
This is basically a simplified version of the channel allocation algorithm in Perform.Midi.Perform. It's hard to reuse that algorithm directly because this one has to operate in realtime and can't see which controls the note is going to use.
Synopsis
- keyboard_velocity :: Signal.Y
- type Input = GenericInput Pitch.Input
- type InputNn = GenericInput Pitch.NoteNumber
- data GenericInput pitch
- input_id :: GenericInput x -> NoteId
- offset_note_id :: Int -> GenericInput x -> GenericInput x
- newtype NoteId = NoteId Int
- key_to_id :: Midi.Key -> NoteId
- id_to_key :: NoteId -> Midi.Key
- type Addr = (Midi.ReadDevice, Midi.Channel)
- newtype ReadDeviceState = ReadDeviceState (Map Midi.ReadDevice ControlState)
- empty_rdev_state :: ReadDeviceState
- next_state :: Midi.ReadDevice -> ControlState -> ReadDeviceState -> ReadDeviceState
- data ControlState = ControlState {}
- empty_control_state :: Control.PbRange -> ControlState
- from_midi :: ReadDeviceState -> Midi.ReadDevice -> Midi.Message -> Maybe (Input, ReadDeviceState)
- update_control_state :: Addr -> Midi.ChannelMessage -> ControlState -> ControlState
- pb_to_input :: Control.PbRange -> Midi.PitchBendValue -> Midi.Key -> Pitch.Input
- nn_to_input :: Pitch.NoteNumber -> Pitch.Input
- input_to_nn :: Pitch.Input -> Pitch.NoteNumber
- cc_to_control :: Midi.Control -> ScoreT.Control
- control_to_cc :: ScoreT.Control -> Maybe Midi.Control
- cc_control :: Map Midi.Control ScoreT.Control
- control_cc :: Map ScoreT.Control Midi.Control
- from_ascii :: Bool -> Pitch.Pitch -> Input
- pitch_to_nn :: Pitch.Pitch -> Int
- to_midi :: Control.PbRange -> Map NoteId Midi.Key -> InputNn -> ([Midi.ChannelMessage], Map NoteId Midi.Key)
Documentation
keyboard_velocity :: Signal.Y Source #
Since the ASCII keyboard isn't pressure sensitive, this is the default velocity. Hopefully it's strong but not so strong as to be hard on the ears.
type Input = GenericInput Pitch.Input Source #
type InputNn = GenericInput Pitch.NoteNumber Source #
An input with a plain NoteNumber pitch instead of a Pitch.Input
.
data GenericInput pitch Source #
NoteOn NoteId pitch Signal.Y | The Input and val (velocity) could be sent separately, but that would make converting this back into midi for thru harder. |
NoteOff NoteId Signal.Y | |
Control NoteId ScoreT.Control Signal.Y | Controls coming from MIDI are mapped to control names, since this is
a superset of MIDI CC numbers, and may include non-MIDI as well. But
for MidiThru to map back to a CC number, I need 1:1 mapping between
ScoreT.Controls and CC numbers. This is what |
PitchChange NoteId pitch | Pitch could also be a Control, but this way the pitch is typed. |
Instances
Show pitch => Show (GenericInput pitch) Source # | |
Defined in Cmd.InputNote showsPrec :: Int -> GenericInput pitch -> ShowS # show :: GenericInput pitch -> String # showList :: [GenericInput pitch] -> ShowS # | |
Eq pitch => Eq (GenericInput pitch) Source # | |
Defined in Cmd.InputNote (==) :: GenericInput pitch -> GenericInput pitch -> Bool # (/=) :: GenericInput pitch -> GenericInput pitch -> Bool # | |
(Show pitch, Pretty pitch) => Pretty (GenericInput pitch) Source # | |
Defined in Cmd.InputNote pretty :: GenericInput pitch -> Text Source # format :: GenericInput pitch -> Doc Source # formatList :: [GenericInput pitch] -> Doc Source # |
input_id :: GenericInput x -> NoteId Source #
offset_note_id :: Int -> GenericInput x -> GenericInput x Source #
Modify the NodeId so that it won't collide with other NodeIds.
NoteIds are supposed to be unique for each Input. However, in practice they
wind up being the MIDI NoteOn Midi.Key
, for reasons described in NoteId
.
So if you want to emit MIDI thru for two notes with the same pitch (e.g.
dispatch a single pitch to two instruments), you need to give them different
NoteIds. This function multiplies them such that they won't collide.
TODO This is a grody hack. A better solution might be to make NoteId into a (Channel, Int) pair.
In theory, NoteId is an arbitrary ID, but in practice it's the same as the initial note on Midi.Key. The reason is that pitch bend needs to know the original key so it knows what the pitch bend is relative to. I could store the original key separately, but it's convenient to put them both into NoteId, and I can't think of any instances where I'd want them to be different.
In addition, when a MIDI NoteOff comes in I have to know what NoteId it applies to. Since MIDI's NoteId is the key number, I have no choice but to use that.
from midi
type Addr = (Midi.ReadDevice, Midi.Channel) Source #
newtype ReadDeviceState Source #
Keep track of the state of each Midi.ReadDevice
.
Instances
Show ReadDeviceState Source # | |
Defined in Cmd.InputNote showsPrec :: Int -> ReadDeviceState -> ShowS # show :: ReadDeviceState -> String # showList :: [ReadDeviceState] -> ShowS # | |
Eq ReadDeviceState Source # | |
Defined in Cmd.InputNote (==) :: ReadDeviceState -> ReadDeviceState -> Bool # (/=) :: ReadDeviceState -> ReadDeviceState -> Bool # |
data ControlState Source #
The state of one Midi.ReadDevice
.
ControlState | |
|
Instances
Show ControlState Source # | |
Defined in Cmd.InputNote showsPrec :: Int -> ControlState -> ShowS # show :: ControlState -> String # showList :: [ControlState] -> ShowS # | |
Eq ControlState Source # | |
Defined in Cmd.InputNote (==) :: ControlState -> ControlState -> Bool # (/=) :: ControlState -> ControlState -> Bool # |
from_midi :: ReadDeviceState -> Midi.ReadDevice -> Midi.Message -> Maybe (Input, ReadDeviceState) Source #
pb_to_input :: Control.PbRange -> Midi.PitchBendValue -> Midi.Key -> Pitch.Input Source #
from ascii
from_ascii :: Bool -> Pitch.Pitch -> Input Source #
Create an Input from an ascii keyboard Pitch.
pitch_to_nn :: Pitch.Pitch -> Int Source #