frame-of-mind/src/dev-util/bpm_timer.gd

108 lines
3.3 KiB
GDScript

@tool
class_name BPMTimer extends Timer
@export var bpm: float = 120:
set(beats):
bpm = beats
beat_length = bpm / 60.0
var beat_length: float = (120.0/60.0)
@export var beats_per_bar: int = 4
@export var grid_offset: float = 0
var sync_to: AudioStreamPlayback
var current_beat: int
var current_bar: int
signal on_beat(beat_count: int)
signal on_bar(bar_count: int)
@warning_ignore("shadowed_variable")
func _init(bpm:float = 120, beats_per_bar:int = 4, grid_offset:float = 0, sync_to_playback:AudioStreamPlayback = null) -> void:
self.bpm = bpm
self.beats_per_bar = beats_per_bar
self.grid_offset = grid_offset
self.sync_to = sync_to_playback
func _ready() -> void:
start(0.5)
func _process(delta: float) -> void:
if not is_stopped():
if sync_to != null:
if not sync_to.is_playing():
stop()
if fmod(sync_to.get_playback_position()-grid_offset + delta, beat_length) < delta * 2.0:
_on_beat()
_last_beat_timing = sync_to.get_playback_position()
else:
if not Engine.is_editor_hint(): print(time_left)
func start(time_sec: float = 0, starting_beat: int = 0, starting_bar: int = 0, sync_to_playback:AudioStreamPlayback = null) -> void:
if starting_bar == 0 and starting_beat > 0: starting_bar = starting_beat / beats_per_bar
current_beat = starting_beat - 1
current_bar = starting_bar - 1
_last_bar = starting_beat - ( starting_beat % beats_per_bar )
wait_time = beat_length
grid_offset = time_sec
if not sync_to_playback == null: sync_to = sync_to_playback
if sync_to != null:
await get_tree().process_frame
if sync_to.is_playing():
on_beat.connect(_on_beat)
return
super.start()
else:
await get_tree().create_timer(time_sec).timeout
_on_beat()
super.start()
func stop():
if timeout.get_connections().has(_on_beat):
timeout.disconnect(_on_beat)
super.stop()
var _last_bar = 0
func _on_beat():
current_beat += 1
on_beat.emit(current_beat)
if current_beat - _last_bar > beats_per_bar:
_last_bar = current_bar
current_bar += 1
on_bar.emit(current_bar)
var _last_beat_timing
func get_beat_progress(from_beat:int = -1, to_beat:int = -1, countdown:bool = false) -> float:
if is_stopped(): return 0 if not countdown else 1
if from_beat == -1: from_beat == current_beat
if to_beat == -1: to_beat == current_beat +1
assert(to_beat > from_beat)
if current_beat <= from_beat: return 0 if not countdown else 1
if current_beat >= to_beat: return 1 if not countdown else 0
var beat_range: float = from_beat - to_beat
var beat_progress: float = (current_beat - to_beat) / beat_range
if sync_to == null:
var result: float = beat_progress + (time_left / bpm) / beat_range
return result if not countdown else 1 - result
else:
var result: float = beat_progress + (sync_to.get_playback_position() - _last_beat_timing) / bpm
return result if not countdown else 1 - result
return 0
func get_bar_progress(from_bar:int = 0, to_bar:int = -1, countdown:bool = false) -> float:
if from_bar == -1: from_bar == current_beat
if to_bar == -1: to_bar == current_beat +1
return get_beat_progress(from_bar * beats_per_bar, to_bar * beats_per_bar)
func get_time_to_beat(to_beat:int = -1) -> float:
if to_beat == -1: to_beat == current_beat +1
var result
if sync_to == null:
result = time_left
else:
result = sync_to.get_playback_position() - _last_beat_timing
return result + (to_beat - current_bar + 1) / bpm