Author: Perette Barella Date: 2015-12-03 Summary: Audio engine state machine and cross-fading songs. Cross Fading ============ A goal of pianod2 is cross-fading audio tracks. This requires starting a second audio player before the first has completed, which requires some planning and attention to detail. Furthermore, the AVFoundation source can return unknown durations and playpoint before it fully initializes. This causes the audio engines to report goofy times in playback status messages. Because we need to announce playback status, but can’t wait for the information, it falls into the same track-playback-state realm. At present, any state is divined from the state of variables: * current_song != nullptr means there is a player. * audio_player->playpoint(), audio_player->playback_complete() and audio_player->remaining() tell the periodic() function what to do to. Work may be done multiple times over during the last seconds of playback. A state machine is a way to address the complexity. States ------ A `Completed track data announced` flag or state must be independently stored, as the state machine would need to advance past the announcing state regardless of the data’s arrival. Let: - Te = time at end of song - Tcrossfade = duration of crossfade - Tx = time to start crossfading = Te - Tcrossfade - Tc = time to cue song = Tx - 5 = Te - Tcrossfade - 5 - Tp = time of purge & prefetch = Tc - 5 The states will follow the sequence as follows, with time Te being the end of song (units are in seconds): Playing : the single audio player is playing, has not reached time Tp and no preparation for end-of-song has occurred. Next periodic() at time Tp. Purged : at time Tp, the queue will be purged and refilled if necessary. Next periodic() at time Tc. Cueing : At time Tc, the new player will be created but playback will not start. This provides a chance to initialize and pre-fetch data. Next periodic() at time Tx. Crossfading : At time Tx, the new player will be put in motion. Next periodic in 0.2. The player will remain in this state until completion. State Details ------------- ### Cueing Entering `Cueing` state, the front item of the queue will be removed and started as a secondary player. Therefore, we will need both current_song and a new cueing_song. Logic: * Pop and retain the front item of the queue into cueing_song. * Start cueing_player with cueing_song. * Note: Requests to view the queue, history, or status should treat the cueing_song as still in the queue. ### Crossfading Each time periodic() is invoked while in this state, the volume of the old player’s volume will be offset down by (Te - Tnow)/Tcrossfade * 20dB, and the new player’s volume will be offset down by (Tnow - Tx)/Tcrossfade * 20dB. *(If Tcrossfade is 0, both volumes are left unadjusted.)* On the first invocation (state change from Cueing), after setting volumes, playback will be initiated. When the old player completes: * Set the cueing_player volume to the full current audio_level. * The player shall be disposed in the usual manner. * The current_song pushed to history and released once by the engine.. * cueing_song will be moved to current_song, and cueing_song cleared. * cueing_player will be moved to audio_player, and cueing_player cleared. * Set state to Playing. * Note: If pausing while there are two audio players, we must pause both. If resuming, we must always resume the old player, but the new only if we have passed Tx.