Karya, built on 2020-11-26T21:03:17 (patch 23b5be2d53a9e8e7d6136cda5aae2849abe5cded)

Cmd.Play

Contents

Description

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.
• The score is sent to the Performer, which starts player_thread and returns a Transport.PlayControl and a Transport.PlayMonitorControl. The PlayControl is just to stop the player_thread, and the PlayMonitorControl is so the player_thread can signal that it's done.
• The PlayMonitorControl 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 the player_thread sends a stop. It's not synchronized to the playback in any way, but of course they are both working from the same score.
• A stop from the user sets Transport.stop_player, which causes the player_thread to quit, which in turn tells the monitor thread.

So, there are three threads involved: the player_thread is scheduling MIDI msgs, the play_monitor_thread sweeps the play position along, and the app event handler is waiting for events in the responder.

The player_thread also gets the event loopback channel in Transport.info_send_status, so it can send Transport msgs through the event loop. They're picked up by cmd_play_msg, which can use them to set UI state like changing the play box color and logging.

The play_monitor_thread is kicked off simultaneously with the player_thread, and advances the play selection in its own loop, using the tempo map from the deriver. It will keep running until it gets a stop msg from the control or the tempo map tells it there is no more score to "play". While the monitor doesn't actually play anything, it's the one that sends Playing and Stopped transport msgs to indicate player status. If all goes well, the monitor and the player will run to completion, the monitor will send Stopped, and the player will exit on its own.

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

For example:

In a normal situation, the player will do its thing and the monitor will eventually run out of InverseTempoMap, which will return Nothing. The monitor will send Stopped, which will clear the player control from the responder Cmd.State, which is how the UI knows whether playing is in progress. The player is assumed to have completed and exited on its own, probably even before the playback audio is completed, since it schedules in advance.

If the player dies on an error, it sends a Died to the responder chan. As mentioned above, it will also tell the monitor to stop. The monitor will notice this, and may stop itself, emitting a Stopped msg. The Stopped msg will then cause the responder to clear the player control out of its state, which lets it know that play has stopped and it's ok to start another play thread.

If the user requests a stop, the responder sets the player control to Stop. The player stops, telling the monitor to stop, which emits Stopped, which clears the PlayMonitorControl.

Synopsis

# stop

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 stream, if playing. See NOTE [play-im].

# play

Play the local block from its beginning.

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

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

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

Play the root block from its beginning.

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

The same as local_selection, but use the root performance.

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.

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

Arguments

 :: 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.PlayMidiArgs

Arguments

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

Arguments

 :: (Log.Msg -> Maybe.Maybe k) -> [Log.Msg] -> ([(Text, [k])], [(k, Int)]) (cache misses, cache hits): ([(because, [key])], [(key, cached_vals)])

Get block cache stats.

Get track cache stats.

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

If there are im instruments, find the Im.Play.play_cache_synth allocation.

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.

# implementation

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