143 lines
3.0 KiB
GDScript3
143 lines
3.0 KiB
GDScript3
|
|
## Class for playing music and providing an interface to keep events in sync.
|
||
|
|
class_name Conductor extends Node
|
||
|
|
|
||
|
|
## If true, start immediately when ready.
|
||
|
|
@export var autostart: bool = false
|
||
|
|
|
||
|
|
## Music to play.
|
||
|
|
@export var music: Music
|
||
|
|
|
||
|
|
signal started
|
||
|
|
signal ticked(beat_update: BeatUpdate)
|
||
|
|
signal ticked_forward(new_beat: float)
|
||
|
|
#signal ticked_backward(new_beat: float)
|
||
|
|
signal finished
|
||
|
|
|
||
|
|
|
||
|
|
func start() -> void:
|
||
|
|
assert(music != null)
|
||
|
|
assert(_audio_player != null)
|
||
|
|
|
||
|
|
_stop_playback_and_initialize()
|
||
|
|
_active = true
|
||
|
|
started.emit()
|
||
|
|
|
||
|
|
|
||
|
|
func is_active() -> bool:
|
||
|
|
return _active
|
||
|
|
|
||
|
|
|
||
|
|
func current_time() -> float:
|
||
|
|
return(_audio_player.get_playback_position() +
|
||
|
|
AudioServer.get_time_since_last_mix() +
|
||
|
|
_music_offset - _remaining_silence)
|
||
|
|
|
||
|
|
|
||
|
|
func previous_time() -> float:
|
||
|
|
return _previous_time
|
||
|
|
|
||
|
|
|
||
|
|
func current_beat() -> float:
|
||
|
|
return _sync_track.to_beat(current_time())
|
||
|
|
|
||
|
|
|
||
|
|
func previous_beat() -> float:
|
||
|
|
return _previous_beat
|
||
|
|
|
||
|
|
|
||
|
|
func current_bpm() -> float:
|
||
|
|
return _sync_track.bpm_at_time(current_time())
|
||
|
|
|
||
|
|
|
||
|
|
func add_time(seconds: float) -> void:
|
||
|
|
if _audio_player == null:
|
||
|
|
return
|
||
|
|
_audio_player.seek(_audio_player.get_playback_position() + seconds)
|
||
|
|
_previous_time = current_time()
|
||
|
|
_previous_beat = current_beat()
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
# ====== IMPLEMENTATION ====== #
|
||
|
|
|
||
|
|
var _active: bool = false
|
||
|
|
|
||
|
|
var _previous_time: float = -1.0
|
||
|
|
var _previous_beat: float = -1.0
|
||
|
|
|
||
|
|
var _audio_player: AudioStreamPlayer = null
|
||
|
|
var _sync_track: SyncTrack = SyncTrack.new()
|
||
|
|
|
||
|
|
var _music_offset: float
|
||
|
|
var _music_playing: bool = false
|
||
|
|
var _remaining_silence: float = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _ready() -> void:
|
||
|
|
_audio_player = AudioStreamPlayer.new()
|
||
|
|
add_child(_audio_player)
|
||
|
|
_audio_player.finished.connect(_on_music_finished)
|
||
|
|
|
||
|
|
if autostart:
|
||
|
|
start()
|
||
|
|
|
||
|
|
|
||
|
|
func _on_music_finished() -> void:
|
||
|
|
_music_playing = false
|
||
|
|
_active = false
|
||
|
|
finished.emit()
|
||
|
|
|
||
|
|
|
||
|
|
func _process(delta: float) -> void:
|
||
|
|
if not _active:
|
||
|
|
return
|
||
|
|
|
||
|
|
if _music_playing:
|
||
|
|
_beat_update()
|
||
|
|
else:
|
||
|
|
_update_silence(delta)
|
||
|
|
|
||
|
|
|
||
|
|
func _update_silence(elapsed_time: float) -> void:
|
||
|
|
_remaining_silence -= elapsed_time
|
||
|
|
|
||
|
|
if _remaining_silence < 0:
|
||
|
|
_play_music_from_offset(-_remaining_silence)
|
||
|
|
_remaining_silence = 0.0
|
||
|
|
|
||
|
|
|
||
|
|
func _beat_update() -> void:
|
||
|
|
var _current_time = current_time()
|
||
|
|
|
||
|
|
# _audio_player.get_playback_position() used in current_time()
|
||
|
|
# is not perfect and may return a lesser value than last frame.
|
||
|
|
# This makes sure the beat never updates backward.
|
||
|
|
if _current_time <= _previous_time:
|
||
|
|
return
|
||
|
|
|
||
|
|
var _current_beat = _sync_track.to_beat(_current_time)
|
||
|
|
ticked.emit(BeatUpdate.new(_current_beat, _previous_beat))
|
||
|
|
ticked_forward.emit(_current_beat)
|
||
|
|
|
||
|
|
_previous_beat = _current_beat
|
||
|
|
_previous_time = _current_time
|
||
|
|
|
||
|
|
|
||
|
|
func _stop_playback_and_initialize() -> void:
|
||
|
|
if _audio_player.playing:
|
||
|
|
_audio_player.stop()
|
||
|
|
|
||
|
|
_active = false
|
||
|
|
_music_playing = false
|
||
|
|
_previous_time = -1.0
|
||
|
|
_previous_beat = -1.0
|
||
|
|
_music_offset = music.offset
|
||
|
|
_remaining_silence = _music_offset
|
||
|
|
_audio_player.stream = music.stream
|
||
|
|
_sync_track.initialize(music.tempo)
|
||
|
|
|
||
|
|
|
||
|
|
func _play_music_from_offset(offset: float) -> void:
|
||
|
|
_audio_player.play(offset)
|
||
|
|
_music_playing = true
|