WIP: implementing a music mixer

This commit is contained in:
betalars 2025-09-21 00:25:23 +02:00
parent 48b4596f50
commit 8a5f05cd77
10 changed files with 285 additions and 0 deletions

View File

@ -0,0 +1,21 @@
@tool
class_name AudioTrack extends Resource
@export var audio: AudioStream
@export var bpm: int = 120
@export var jump_interval: int
@export var offset: float = 0
@export_placeholder("\"LOOP\" or track name.") var advance_to: String
@export_group("Transition Settings")
@export var smoothing: float = 1
@export var transition_in_type: MixedAudioStream.TransIn = MixedAudioStream.TransIn.HARD
@export var transition_out_type: MixedAudioStream.TransOut = MixedAudioStream.TransOut.HARD
@export var loop_type: MixedAudioStream.Loop = MixedAudioStream.Loop.HARD_IN_OUT
@export var jump_type: MixedAudioStream.Jump = MixedAudioStream.Jump.SOFT
@export_group("Beat Markers")
@export var intro_end: int = -1
@export var loop_in: int = -1
@export var loop_out: int = -1
@export var outro_start: int = -1

View File

@ -0,0 +1 @@
uid://bso71xkb3xqnb

107
src/dev-util/bpm_timer.gd Normal file
View File

@ -0,0 +1,107 @@
@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

View File

@ -0,0 +1 @@
uid://7v24sdisnbia

View File

@ -0,0 +1,21 @@
class_name FillerTrack extends Resource
@export var audio: AudioStream
@export var bpm: int
@export_flags_3d_render var blend_from
@export_flags_3d_render var blend_to
@export var offset: float = 0
@export_group("Transition Settings")
@export var smoothing: float = 1
@export var transition_in_type: MixedAudioStream.TransIn
@export var transition_out_type: MixedAudioStream.TransOut
@export var previous_out_override: MixedAudioStream.TransOut
@export var next_in_override: MixedAudioStream.TransIn
@export_group("Beat Markers")
@export var fill_in: int
@export var fill_out: int

View File

@ -0,0 +1 @@
uid://qgitde4nibng

View File

@ -0,0 +1,82 @@
@tool
class_name MixedAudioHelper extends AudioStreamPlayer
enum TriggerPoint {
IMMEDIATE,
INTRO_END,
LOOP_IN,
LOOP_OUT,
OUTRO
}
#FIXME: I should consider adding a neutral scene to the ids enum for each room. Maybe we can work something out based on the currently selected room here, but as it stands now it is impossible to select the main theme other than intentionally selecting something unintended.
@export var play_next: Scenes.id:
set(next):
if next == 0:
play_next = 0
return
play_next = next
func get_playback_id_from_scene(scene: int) -> int:
match scene:
Scenes.id.ADULT_DND: return 1
Scenes.id.ADULD_VOLUNTARY: return 2
Scenes.id.ADULD_CHRISTMAS: return 3
Scenes.id.ADULT_EATING: return 4
Scenes.id.ADULT_UNI: return 5
Scenes.id.ADULT_THERAPY: return 6
Scenes.id.ADULT_BURNOUT: return 7
_: return 0
@export var actually_playing: bool = false:
set(play):
actually_playing = play
if actually_playing:
self.play()
else:
stop()
@export var actual_stream: MixedAudioStream
var _playback: AudioStreamPlaybackPolyphonic
var main_stream_id: int = -1:
set(stream_id):
main_stream_id = stream_id
if main_stream_id == -1:
stop()
else:
main_stream_playback_id = _playback.play_stream(actual_stream.get_stream_by_id(main_stream_id))
var main_stream_playback_id:int = -1
var transition_stream_id: int
var transition_stream_playback_id:int = -1
var next_stream_id: int
var next_stream_playback_id:int = -1
func _ready() -> void:
stream = AudioStreamPolyphonic.new()
func play(from_position: float = 0.0):
print("got called")
super.play(from_position)
self._play(from_position)
func queue_trigger(stream_id: int, point: TriggerPoint, trigger: Callable):
pass
func loop_back():
pass
func transition_trigger():
pass
func transition_start():
pass
func transition_end():
#make new
pass
func _play(from_position: float = 0.0):
_playback = get_stream_playback()
main_stream_id = get_playback_id_from_scene(play_next)

View File

@ -0,0 +1 @@
uid://cmp8dth4cvo28

View File

@ -0,0 +1,49 @@
@tool
class_name MixedAudioStream extends AudioStream
enum TransIn {
HARD,
FROM_START,
SOFT
}
enum TransOut {
HARD,
TO_END,
SOFT
}
enum Loop {
HARD_IN_OUT,
HARD_IN_TO_END,
HARD_IN_SOFT_END,
HARD_OUT
}
enum Jump {
HARD,
SOFT
}
@export var tracks: Array[AudioTrack]:
set(dict):
tracks = dict
@export var transitions: Array[FillerTrack]
var _playback: AudioStreamPlaybackPolyphonic
func is_meta_stream():
return true
func is_monophonic():
return false
func initialize_mix(from_position: float, playback: AudioStreamPlaybackPolyphonic):
_playback = playback
func get_stream_by_id(id: int) -> AudioStream:
return tracks[id].audio
func fadeout(stream_id: int, delay: float):
var tween: Tween
tween = _playback.pla

View File

@ -0,0 +1 @@
uid://c05ftj6kxhpil