Karya, built on 2023-08-29T07:47:28 (patch 7a412d5d6ba4968ca4155ef276a062ccdeb9109a)
Safe HaskellSafe-Inferred

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_note_track.

Instances

Instances details
Pretty.Pretty (TrackInfo d) Source # 
Instance details

Defined in Derive.EvalTrack

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

derive_control_track :: CallableExpr 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