108 lines
3.3 KiB
GDScript
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
|