Safe Haskell | Safe-Inferred |
---|
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
witha (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 thata 1 | b 2 | c 3
is sugar fora (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 ado-something
. - Derivers in the environment:
default-something = (block1)
. I can't think of adefault-something
.
- A call that takes two derivers:
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
- data TrackInfo d = TrackInfo {}
- type GetLastVal d = [d] -> Maybe.Maybe d
- derive_control_track :: CallableExpr d => State -> TrackInfo d -> DeriveResult d
- derive_note_track :: (TrackTree.EventsTree -> NoteDeriver) -> State -> TrackInfo Score.Event -> DeriveResult Score.Event
- defragment_track_signals :: Warp.Warp -> Collect -> Collect
- unwarp :: Warp.Warp -> Signal.Control -> Track.TrackSignal
- derive_event :: CallableExpr d => Context d -> Event.Event -> Deriver (Stream.Stream d)
- context :: TrackInfo a -> Maybe.Maybe val -> [Event.Event] -> Event.Event -> [Event.Event] -> Context val
Documentation
Per-track parameters, to cut down on the number of arguments taken by
derive_note_track
.
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.
unwarp :: Warp.Warp -> Signal.Control -> Track.TrackSignal Source #
derive_event :: CallableExpr d => Context d -> Event.Event -> Deriver (Stream.Stream d) Source #
:: TrackInfo a | |
-> Maybe.Maybe val | |
-> [Event.Event] | previous events, in reverse order |
-> Event.Event | cur event |
-> [Event.Event] | following events |
-> Context val |