Safe Haskell | Safe-Inferred |
---|
Support for high level score modifications. This is companion to Cmd.ModifyEvents, which is for low level transformations.
The main interface to this is Cmd.Repl.LNote.
The score language is code to to be interpreted, not data to be manipulated. This is good for flexibility, but bad for direct transformation. Therefore, all the functions in here rely on a certain amount of conventional structure to tame the flexibility.
The lowest level, represented by a list of Note
s, assumes that each
note track has a single "branch" of control tracks underneath it, and
each note event has control values within its extent, so each note can be
sliced out and treated as a unit. So it doesn't support note tracks with
multiple parallel children, and it doesn't support order-dependent control
tracks, which means that relative controls are out too (TODO actually
a relative control track is fine as long as there's only one). Also,
since notes only carry along the controls directly underneath them,
they can wind up with different control values when they are placed on
a different track (TODO it would be possible to deal with this too, by
copying the events forward).
I initially attempted to support trees of control tracks in full
generality, or even just an ordered list of controls, but there's a problem
when different Notes have different controls: where do the control tracks
get merged into a tree, relative to each other? Not only do I have to
invent an order, but it has to be linear, since there's also no information
to merge into a branching skeleton. Since I can't create one with Note
s,
I felt Notes shouldn't be able to parse them either.
The Note
s can be annotated with additional data, such as pitch, but
of course will make it more specialized and reliant on convention. For
instance, the pitches have to be extracted from the pitch events, which
will fail unless there's an easily parseable pitch in there.
TODO it should be possible to get the pitch out of the derivation by finding the corresponding Score.Event by looking for its stack.
Synopsis
- data Note = Note {
- note_start :: !TrackTime
- note_duration :: !TrackTime
- note_text :: !Text
- note_controls :: !Controls
- note_index :: !Index
- note_control_track_ids :: ![TrackId]
- note_end :: Note -> TrackTime
- start :: Note :-> TrackTime
- duration :: Note :-> TrackTime
- text :: Note :-> Text
- controls :: Note :-> Controls
- index :: Note :-> Index
- end :: Note :-> TrackTime
- note_min :: Note -> TrackTime
- note_max :: Note -> TrackTime
- note_orientation :: Note -> Types.Orientation
- type Index = Int
- notes_overlap :: Note -> Note -> Bool
- type Controls = Map Control Events.Events
- data Control
- control_to_title :: Control -> Text
- type Error = Text
- title_to_control :: Text -> Either Error Control
- sorted_controls :: Controls -> [(Control, Events.Events)]
- type ModifyNotes m = BlockId -> [(Note, TrackId)] -> m [Note]
- notes :: Monad m => ([Note] -> [Note]) -> ModifyNotes m
- note :: Monad m => (Note -> Note) -> ModifyNotes m
- selection :: Cmd.M m => ModifyNotes m -> m ()
- remove_ranges :: [(Note, TrackId)] -> [(TrackId, Events.Range)]
- selected_notes :: Cmd.M m => m [(Note, TrackId)]
- type Annotated a m = [(Note, a)] -> m [Note]
- annotate_nns :: Cmd.M m => Annotated (Maybe Pitch.NoteNumber) m -> ModifyNotes m
- annotate_controls :: Cmd.M m => Annotated (Maybe Transposed, ScoreT.ControlValMap) m -> ModifyNotes m
- find_controls :: [(Note, TrackId)] -> Vector.Vector Score.Event -> [(Note, (Maybe Transposed, ScoreT.ControlValMap))]
- find_event :: TrackId -> Note -> Vector.Vector Score.Event -> Maybe Score.Event
- stack_matches :: TrackId -> TrackTime -> TrackTime -> Stack.Stack -> Bool
- slice_tracks :: Ui.M m => TrackTree.TrackTree -> [(TrackId, [Event.Event])] -> m [(Note, TrackId)]
- slice_note :: Index -> [Tree.Tree (Ui.TrackInfo, Events.Events)] -> Event.Event -> Either Error Note
- extract_controls :: (TrackTime, TrackTime) -> [Tree.Tree (Ui.TrackInfo, Events.Events)] -> Either Error [(Control, Events.Events)]
- annotate :: Text -> Either Error a -> Either Error a
- data NoteTrack = NoteTrack Events.Events Controls
- merge_notes :: [Note] -> [NoteTrack]
- write_tracks :: Ui.M m => BlockId -> [TrackId] -> [NoteTrack] -> m ()
- extract_note_trees :: Ui.M m => BlockId -> [TrackId] -> m TrackTree.TrackTree
- merge_controls :: Ui.M m => BlockId -> TrackId -> TrackTree.TrackTree -> [(Control, Events.Events)] -> m ()
- tracknum_after :: Ui.M m => BlockId -> [TrackId] -> m Types.TrackNum
- bottom_track :: Ui.M m => BlockId -> TrackId -> m (Maybe Ui.TrackInfo)
- parent_of :: Ui.M m => BlockId -> TrackId -> m (Maybe Ui.TrackInfo)
Documentation
This represents a single event on a note track.
Note | |
|
Each note has an Index, which indicates which of the selected note tracks it came from, or should be written to.
controls
A simplified version of ParseTitle.ControlType
, since Notes don't
support all the forms of control tracks. Put Pitch first so it sorts first,
to support the convention of putting the pitch track right after the note
track.
control_to_title :: Control -> Text Source #
sorted_controls :: Controls -> [(Control, Events.Events)] Source #
Put the pitch tracks next to the note, the rest go in alphabetical order.
selection
selection :: Cmd.M m => ModifyNotes m -> m () Source #
Modify notes on the selected tracks. Only the top level note tracks are affected, so you can select an entire block and not worry about mangling parent controls.
This may add new tracks, but will not delete tracks that are made empty. It could, but it seems easy enough to delete the tracks by hand once I verify that the transformation worked. TODO revisit this if it's annoying
remove_ranges :: [(Note, TrackId)] -> [(TrackId, Events.Range)] Source #
selected_notes :: Cmd.M m => m [(Note, TrackId)] Source #
Find the top-level note tracks in the selection, and reduce them down to Notes.
annotated transformations
annotate_nns :: Cmd.M m => Annotated (Maybe Pitch.NoteNumber) m -> ModifyNotes m Source #
annotate_controls :: Cmd.M m => Annotated (Maybe Transposed, ScoreT.ControlValMap) m -> ModifyNotes m Source #
find_controls :: [(Note, TrackId)] -> Vector.Vector Score.Event -> [(Note, (Maybe Transposed, ScoreT.ControlValMap))] Source #
This finds the controls of each note by looking for its corresponding event in the performance. TODO matching by stack seems like it could be inaccurate, and inefficient too. Shouldn't I look up the signal directly from the performance?
find_event :: TrackId -> Note -> Vector.Vector Score.Event -> Maybe Score.Event Source #
stack_matches :: TrackId -> TrackTime -> TrackTime -> Stack.Stack -> Bool Source #
read
slice_tracks :: Ui.M m => TrackTree.TrackTree -> [(TrackId, [Event.Event])] -> m [(Note, TrackId)] Source #
slice_note :: Index -> [Tree.Tree (Ui.TrackInfo, Events.Events)] -> Event.Event -> Either Error Note Source #
The whole thing fails if a title is unparseable or the control tracks have a fork in the skeleton.
This is similar to slice
and I initially spent some time
trying to reuse it, but it's different enough that most of the work that
slice does doesn't apply here.
extract_controls :: (TrackTime, TrackTime) -> [Tree.Tree (Ui.TrackInfo, Events.Events)] -> Either Error [(Control, Events.Events)] Source #
write
merge_notes :: [Note] -> [NoteTrack] Source #
:: Ui.M m | |
=> BlockId | |
-> [TrackId] | The TrackIds are expected to line up with NoteTracks. If there are more NoteTracks than TrackIds, new tracks will be created. |
-> [NoteTrack] | |
-> m () |
Write NoteTracks to the given block. It may create new tracks, but won't delete ones that are made empty.
extract_note_trees :: Ui.M m => BlockId -> [TrackId] -> m TrackTree.TrackTree Source #
merge_controls :: Ui.M m => BlockId -> TrackId -> TrackTree.TrackTree -> [(Control, Events.Events)] -> m () Source #
tracknum_after :: Ui.M m => BlockId -> [TrackId] -> m Types.TrackNum Source #
Get the tracknum after the given tracks.
bottom_track :: Ui.M m => BlockId -> TrackId -> m (Maybe Ui.TrackInfo) Source #
Get the bottom track below the given TrackId. If there are more than one, pick the one with the highest TrackNum.