Karya, built on 2022-03-21T01:30:44 (patch 89d1651424c35e564138d93424a157ff87457245)
Safe HaskellSafe-Inferred



Master control for playing blocks.

Here's how it works:

  • Find the relevant block to play.
  • Deriver generates a performable score and an inverse tempo map.
  • The score is preprocessed by adding the current absolute time to it and skipping notes based on the start offset.
  • Create a Transport.PlayControl to tell players when to stop, and a Transport.ActivePlayers to track the number of players still running. Start players for each kind of backend: player_thread for MIDI and im-via-MIDI, one for SC if appropriate, and an audio streaming thread if play_im_direct is enabled.
  • The ActivePlayers and tempo map are passed to play_monitor_thread, which uses the tempo map to display the play position in the various blocks. It stops when it runs out of tempo map (which corresponds with running off the end of the score), or when the PlayControl goes to Stop. It's not synchronized to the play threads in any way, but of course they are both working from the same score.
  • A stop from the user sets Transport.stop_player. All the players and the play_monitor_thread are polling it and will quit.
  • Another thread is watching the ActivePlayers. It sent Transport.Playing to the responder when they started, and will send Transport.Stopped when they go to zero. This control's the UI's idea of whether it's playing or not. If it's playing, it still has the PlayControl, so it won't accept another play, but will accept a stop. So player threads should only exit when there's nothing left for them to cancel, so MIDI and OSC, being scheduled in advance, will hang around even after they scheduled their last message. If a player never sends Transport.player_stopped, ActivePlayers will never go to 0, and we get stuck. So they should do the stop in a finally block.

The im backend complicates things a bit. See NOTE [play-im].

repeat_at, the play speed multiplier, and the negative start adjustment also complicate things.




cmd_context_stop :: Cmd.CmdT IO Bool Source #

Context sensitive stop that stops whatever is going on. First it stops realtime play, then step play, and then it just sends all notes off. If it does the last one, it returns False in case you want to go stop something else.

stop_im :: Cmd.CmdT IO () Source #

Stop im stream, if playing. See NOTE [play-im].


local_block :: Cmd.M m => m Cmd.PlayArgs Source #

Play the local block from its beginning.

local_selection :: Cmd.M m => m Cmd.PlayArgs Source #

Start playing from the point selection on the local block. If the selection is a range, loop that range forever.

local_previous :: Cmd.M m => m Cmd.PlayArgs Source #

Play the current block's performance from the previous Cmd.state_play_step.

local_top :: Cmd.M m => m Cmd.PlayArgs Source #

Play the current block's performance from the top of the window.

root_block :: Cmd.M m => m Cmd.PlayArgs Source #

Play the root block from its beginning.

root_from_root_selection :: Cmd.M m => m Cmd.PlayArgs Source #

Play the root performance from the selection on the root block. This is useful to manually set a point to start playing.

root_selection :: Cmd.M m => m Cmd.PlayArgs Source #

The same as local_selection, but use the root performance.

root_previous :: Cmd.M m => m Cmd.PlayArgs Source #

Find the previous step on the focused block, get its RealTime, and play from the root at that RealTime. If this block isn't linked from the root, then fall back on local_previous.

root_top :: Cmd.M m => m Cmd.PlayArgs Source #

Like root_previous, but play from the top of the selected block.

from_score Source #


:: Cmd.M m 
=> Id.BlockId 
-> Maybe.Maybe Id.TrackId

Track to play from. Since different tracks can have different tempos, a track is needed to convert to RealTime. If not given, use the first track that has tempo information.

-> ScoreTime

Convert to RealTime and start playing from this time.

-> Maybe.Maybe ScoreTime 
-> m Cmd.PlayArgs 

get_realtime Source #


:: Cmd.M m 
=> Id.BlockId

Lookup realtime according to the performance of this block.

-> Id.BlockId

Lookup realtime at the position (TrackId, ScoreTime) within this block.

-> Maybe.Maybe Id.TrackId 
-> ScoreTime 
-> m RealTime.RealTime 

record_cache_stats :: Cmd.M m => [Log.Msg] -> m () Source #

Summarize the cache stats and emit them as global status msgs.

The output looks like

~C: [34 / 6742] bid bid bid... || ~X control damage: [104] bid bid ... ||
~X trock block damage: [1] bid

This means that 34 blocks were cached, totally 6742 events. 104 blocks were not cached due to control damage, and 1 more due to track block damage. The reasons are from find_generator_cache. They keys are prefixed with a tilde to make them sort last in the logview status line.

cache_stats gives a more complete summary.

extract_cache_stats Source #


:: (Log.Msg -> Maybe.Maybe k) 
-> [Log.Msg] 
-> ([(Text, [k])], [(k, Int)])

(cache misses, cache hits): ([(because, [key])], [(key, cached_vals)])

get_block_id :: Log.Msg -> Maybe.Maybe Id.BlockId Source #

Get block cache stats.

from_realtime :: Cmd.M m => Id.BlockId -> Maybe.Maybe RealTime.RealTime -> RealTime.RealTime -> m Cmd.PlayArgs Source #

Play the performance of the given block starting from the given time.

merge_midi :: [LEvent.LEvent Midi.WriteMessage] -> [LEvent.LEvent Midi.WriteMessage] -> [LEvent.LEvent Midi.WriteMessage] Source #

Merge a finite list of notes with an infinite list of MTC.

merge_until :: Ord k => (a -> k) -> [a] -> [a] -> [a] Source #

Merge until the leftmost list runs out.


gets :: Cmd.M m => (Cmd.PlayState -> a) -> m a Source #