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

Safe HaskellNone

Derive.EvalTrack

Description

Derive tracks.

It should also have Deriver utilities that could go in Derive, but are more specific to calls.

It used to be that events were evaluated in "normalized time", which to say each one was shifted and stretched into place so that it always begins at 0t and ends at 1t. While elegant, this was awkward in practice. Some calls take ScoreTimes as arguments, and for those to be in the track's ScoreTime they have to be warped too. Calls that look at the time of the next event on the track must warp that too. The result is that calls have to work in two time references simultaneously, which is confusing. But the main thing is that note calls with subtracks need to slice the relevant events out of the subtracks, and those events are naturally in track time. So the slice times would have to be unwarped, and then the sliced events warped. It was too complicated.

Now events are evaluated in track time. Block calls still warp the call into place, so blocks are still in normalized time, but other calls must keep track of their start and end times.

The way expression evaluation works is a little irregular. The toplevel expression returns a parameterized deriver, so this part of the type is exported to the haskell type system. The values and non-toplevel calls return dynamically typed Vals though. The difference between a generator and a transformer is that the latter takes an extra deriver arg, but since the type of the deriver is statically determined at the haskell level, it isn't passed as a normal arg but is instead hardcoded into the evaluation scheme for the toplevel expression. So only the toplevel calls can take and return derivers.

I experimented with a system that added a VDeriver type, but there were several problems:

  • If I don't parameterize Val I wind up with separate VEventDeriver, VPitchDeriver, etc. constructors. Every call that takes a deriver must validate the type and there is no static guarantee that event deriver calls won't wind up the pitch deriver symbol table. It seems nice that the CallMap and Environ can all be replaced with a single symbol table, but in practice they represent different scopes, so they would need to be separated anyway.
  • If I do parameterize Val, I need some complicated typeclass gymnastics and a lot of redundant Typecheck instances to make the new VDeriver type fit in with the calling scheme. I have to differentiate PassedVals, which include VDeriver, from Vals, which don't, so Environ can remain unparameterized. Otherwise I would need a separate Environ per track, and copy over vals which should be shared, like srate. The implication is that Environ should really have dynamically typed deriver vals.
  • Replacing a | b | c with a (b (c)) is appealing, but if the deriver is the final argument then I have a problem where a required argument wants to follow an optional one. Solutions would be to implement some kind of keyword args that allow the required arg to remain at the end, or simply put it as the first arg, so that a 1 | b 2 | c 3 is sugar for a (b (c 3) 2) 1.
  • But, most importantly, I don't have a clear use for making derivers first class. Examples would be:

    • A call that takes two derivers: do-something (block1) (block2). I can't think of a do-something.
    • Derivers in the environment: default-something = (block1). I can't think of a default-something.

I could move more in the direction of a real language by unifying all symbols into Environ, looking up Symbols in eval, and making a VCall type. That way I could rebind calls with tr = absolute-trill or do argument substitution with d = (block1); transpose 1 | d. However, I don't have any uses in mind for that, and haskell is supposed to be the real language. I should focus more on making it easy to write your own calls in haskell.

Synopsis

Documentation

data TrackInfo d Source #

Per-track parameters, to cut down on the number of arguments taken by derive_track.

type GetLastVal d = [d] -> Maybe.Maybe d Source #

derive_control_track :: Callable d => State -> TrackInfo d -> DeriveResult d Source #

This is the toplevel function to derive control tracks. It's responsible for actually evaluating each event.

derive_note_track :: (TrackTree.EventsTree -> NoteDeriver) -> State -> TrackInfo Score.Event -> DeriveResult Score.Event Source #

This is the note track version of derive_control_track. The main difference is that it evaluates orphans.

Orphans are uncovered events in note tracks in the sub-tracks. They are extracted with Slice.checked_slice_notes and evaluated as-is. The effect is that note parents can be stacked horizontally, and tracks left empty have no effect, except whatever transformers they may have in their titles.

This is all very complicated and unsatisfactory, but it's still less complicated and somewhat more satisfactory than it used to be, if you can imagine that.

context Source #

Arguments

:: TrackInfo a 
-> Maybe.Maybe val 
-> [Event.Event]

previous events, in reverse order

-> Event.Event

cur event

-> [Event.Event]

following events

-> Context val 

data TrackInfo d Source #

Per-track parameters, to cut down on the number of arguments taken by derive_track.

type GetLastVal d = [d] -> Maybe.Maybe d Source #

derive_control_track :: Callable d => State -> TrackInfo d -> DeriveResult d Source #

This is the toplevel function to derive control tracks. It's responsible for actually evaluating each event.

derive_note_track :: (TrackTree.EventsTree -> NoteDeriver) -> State -> TrackInfo Score.Event -> DeriveResult Score.Event Source #

This is the note track version of derive_control_track. The main difference is that it evaluates orphans.

Orphans are uncovered events in note tracks in the sub-tracks. They are extracted with Slice.checked_slice_notes and evaluated as-is. The effect is that note parents can be stacked horizontally, and tracks left empty have no effect, except whatever transformers they may have in their titles.

This is all very complicated and unsatisfactory, but it's still less complicated and somewhat more satisfactory than it used to be, if you can imagine that.

with_inverted :: TrackInfo d -> Deriver a -> Deriver a Source #

Update Dynamic before evaluating the inverted generator.

post_track :: Taggable d => TrackTree.Track -> ((State, Maybe.Maybe d), a) -> (a, Threaded, Collect) Source #

Extract the final state at the end of a track derivation.

event_prev_nexts :: TrackInfo d -> [([Event.Event], [Event.Event])] Source #

Get all event prefixes and suffixes.

derive_control_track_stream :: Callable d => TrackInfo d -> (State, Maybe.Maybe d, Maybe.Maybe d) -> ([Event.Event], [Event.Event]) -> ((State, Maybe.Maybe d, Maybe.Maybe d), Stream.Stream d) Source #

Derive one event on a control track. Carrying previous values forward on a control track is a bit more complicated, because there is a separate next_val and save_val. The next_val should be the next event's prev_val, and the save_val should be saved as the final next_val at the end of the track. The reason is that I only save a prev val if the event won't be derived again, e.g. there's a future event <= the start of the next slice. Otherwise, a sliced event will see its own output as its previous val.

derive_note_track_stream :: (TrackTree.EventsTree -> NoteDeriver) -> TrackInfo Score.Event -> (State, Maybe.Maybe Score.Event) -> ([Event.Event], [Event.Event]) -> ((State, Maybe.Maybe Score.Event), Stream.Stream Score.Event) Source #

Derive one event on a note track. This also derives orphan events before the event, or after the last event.

reset_event_serial :: State -> State Source #

See state_event_serial for what this is doing.

run_derive :: State -> Deriver (Stream.Stream d) -> (Stream.Stream d, State) Source #

Run a derivation. If the deriver throws an exception, it will be caught and turned into a log msg, and any state changes rolled back. Internal.local relies on this, since it doesn't revert the state after an exception.

derive_orphans Source #

Arguments

:: (TrackTree.EventsTree -> NoteDeriver) 
-> Maybe.Maybe Event.Event 
-> TrackTime 
-> TrackTree.EventsTree 
-> Maybe.Maybe NoteDeriver

The Maybe is a micro-optimization to avoid returning mempty. This is because d_merge doesn't know that one of its operands is empty, and does all the splitting of and restoring collect bother. I expect lots of empties here so maybe it makes a difference.

is_linear_warp :: Warp -> Maybe.Maybe (RealTime.RealTime, RealTime.RealTime) Source #

Return (shift, stretch) if the tempo is linear. This relies on an optimization in with_tempo to notice when the tempo is constant and give it id_warp_signal.

context Source #

Arguments

:: TrackInfo a 
-> Maybe.Maybe val 
-> [Event.Event]

previous events, in reverse order

-> Event.Event

cur event

-> [Event.Event]

following events

-> Context val