Add Short songs and a choice of how random songs are picked (#30, #51)
Build / test (push) Successful in 1m40s
Build / build (push) Successful in 1m28s

Short songs (#30): a quiz settings switch and a Seconds per song row (30 by
default, 10-120 in steps of 5), saved with the quiz settings. Only part of
each quiz song plays, from a random point 10-60% into it, picked once per song
so restart and repeat play the same part. The progress bar, time and seeking
stay within the part, a hidden beginning counts inside it, and its end counts
as the song ending (repeat, stop or go to the next song as set).

Random songs (#51): the low-played switch in Settings is now a Normal / Low
played / Classic choice, stored as Settings.random_mode and not saved.
fetch_random_song(mode) picks /music/rand, /music/rand/low or
/music/rand/classic.

Closes #30
Closes #51

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SansanandClaude Opus 5.5 committed 2026-10-13 21:21:20 +02:00
1 parent 0fc3be79e1
commit a735c515bf
20 files changed
+1325 -327

No files matched your search

+81 -36
View File
@@ -5,6 +5,11 @@ extends Control
##
## The buttons grow a little under the mouse and the play button pops when it switches
## between play and pause, see [ButtonBounce].
##
## With Short songs on ([member Settings.short_songs]) only a part of each quiz song plays:
## [member Settings.short_song_seconds] from a random point between
## [constant SHORT_START_MIN] and [constant SHORT_START_MAX] of the song. The progress bar,
## the time and seeking all stay within that part, and its end counts as the song ending.
signal update_song_list
signal play_next_song
@@ -16,6 +21,15 @@ var playback_position: float
var stream: AudioStream
var song_finished: bool = false
## Where in the song the part played with Short songs may start, as a share of its length.
## Many songs have a quiet intro, so the start is never right at the beginning.
const SHORT_START_MIN: float = 0.1
const SHORT_START_MAX: float = 0.6
# The part of the song that plays: its start in seconds, and its length (0: to the end)
var _clip_start: float = 0.0
var _clip_length: float = 0.0
@onready var play_button: Button = %PlayButton
@onready var restart_button: Button = %RestartButton
@onready var audio_player: AudioStreamPlayer = $AudioStreamPlayer
@@ -38,14 +52,46 @@ func _ready() -> void:
func _process(_delta: float) -> void:
if audio_player.has_stream_playback() && !is_changing && !audio_player.stream_paused:
if !Settings.hide_beginning \
|| audio_player.get_playback_position() > Settings.hide_for_seconds:
progress_slider.value = audio_player.get_playback_position()
if stream != null:
music_time_label.text = format_text(progress_slider.value, stream.get_length())
else:
music_time_label.text = format_text(progress_slider.value, 0.0)
if audio_player.has_stream_playback() && !is_changing && !audio_player.stream_paused \
&& audio_player.playing:
var position: float = position_in_part()
if _clip_length > 0.0 && position >= _clip_length:
# The short part has played, which counts as the song ending
audio_player.stop()
progress_slider.value = _clip_length
_on_finished()
return
if !Settings.hide_beginning || position > Settings.hide_for_seconds:
progress_slider.value = position
music_time_label.text = format_text(progress_slider.value, part_length())
## How long the part of the song that plays is: the short part, or the whole song.
func part_length() -> float:
if _clip_length > 0.0:
return _clip_length
return stream.get_length() if stream != null else 0.0
## How far into the part that plays the song is, in seconds.
func position_in_part() -> float:
return audio_player.get_playback_position() - _clip_start
## Plays only part of [param song_object]'s song when Short songs is on and the song is
## longer than the part. The part's start is picked once per song.
func choose_part(song_object: SongObject, song: AudioStream) -> void:
_clip_start = 0.0
_clip_length = 0.0
var length: float = song.get_length()
if !Settings.short_songs || song_object == null || length <= Settings.short_song_seconds:
return
if song_object.short_start < 0.0:
var latest: float = minf(length * SHORT_START_MAX, length - Settings.short_song_seconds)
var earliest: float = minf(length * SHORT_START_MIN, latest)
song_object.short_start = randf_range(earliest, latest)
_clip_start = minf(song_object.short_start, length - Settings.short_song_seconds)
_clip_length = Settings.short_song_seconds
## Whether the play button shows pause, because a song is playing.
@@ -61,6 +107,14 @@ func _show_playing(playing: bool) -> void:
ButtonBounce.pop(play_button)
# The progress bar and time show the length of the part that plays
func _show_length() -> void:
var length: float = part_length()
progress_slider.max_value = round(length)
progress_slider.tick_count = 0 if Settings.hide_ticks else round(length / 60)
music_time_label.text = format_text(progress_slider.value, length)
func format_time(time: float) -> String:
var total_seconds: int = roundi(time)
@warning_ignore("integer_division")
@@ -87,11 +141,7 @@ func play_or_pause() -> void:
song_finished = false
audio_player.seek(playback_position)
GameLog.debug("continue")
progress_slider.max_value = round(stream.get_length())
if Settings.hide_ticks:
progress_slider.tick_count = 0
else:
progress_slider.tick_count = round(stream.get_length() / 60)
_show_length()
else:
pause()
@@ -102,15 +152,15 @@ func pause() -> void:
_show_playing(false)
## Plays the song, or its short part, from the beginning.
func restart() -> void:
audio_player.stop()
audio_player.stream_paused = false
song_finished = false
progress_slider.value = 0
playback_position = audio_player.get_playback_position()
audio_player.seek(playback_position)
playback_position = _clip_start
_show_playing(true)
audio_player.play()
audio_player.play(_clip_start)
func _on_finished() -> void:
@@ -127,16 +177,18 @@ func _on_drag_started() -> void:
func _on_drag_ended(_changed: bool) -> void:
audio_player.seek(progress_slider.value)
playback_position = progress_slider.value
audio_player.seek(_clip_start + progress_slider.value)
playback_position = _clip_start + progress_slider.value
is_changing = false
## Jumps [param new_position] seconds forward, or back when negative, staying within the
## part that plays.
func seek(new_position: float) -> void:
progress_slider.value += new_position
is_changing = true
audio_player.seek(progress_slider.value)
playback_position = progress_slider.value
audio_player.seek(_clip_start + progress_slider.value)
playback_position = _clip_start + progress_slider.value
is_changing = false
@@ -173,6 +225,9 @@ func play_sound(sound_name: AudioStream) -> void:
func play_song(song: AudioStream) -> void:
# Sounds such as the winning song always play whole
_clip_start = 0.0
_clip_length = 0.0
audio_player.stream = song
audio_player.play()
sound_player.stop()
@@ -180,12 +235,7 @@ func play_song(song: AudioStream) -> void:
_show_playing(true)
stream = audio_player.stream
progress_slider.value = 0
progress_slider.max_value = round(stream.get_length())
if Settings.hide_ticks:
progress_slider.tick_count = 0
else:
progress_slider.tick_count = round(stream.get_length() / 60)
music_time_label.text = format_text(progress_slider.value, stream.get_length())
_show_length()
## Puts song [param song_object_no] in the player without playing it, e.g. the song that
@@ -196,12 +246,11 @@ func load_song_object(song_object_no: int) -> void:
return
audio_player.stream = song
stream = song
choose_part(Playlist.song_object_array[song_object_no], song)
song_finished = true
_show_playing(false)
progress_slider.value = 0
progress_slider.max_value = round(stream.get_length())
progress_slider.tick_count = 0 if Settings.hide_ticks else round(stream.get_length() / 60)
music_time_label.text = format_text(progress_slider.value, stream.get_length())
_show_length()
func play_song_object(song_object_no: int) -> void:
@@ -214,20 +263,16 @@ func play_song_object(song_object_no: int) -> void:
audio_player.stop()
await get_tree().create_timer(0.5).timeout
audio_player.stream = song
choose_part(Playlist.song_object_array[song_object_no], song)
sound_player.stop()
audio_player.play()
audio_player.play(_clip_start)
song_finished = false
_show_playing(true)
# A new song makes the play button pop, also when it was already playing
ButtonBounce.pop(play_button)
stream = audio_player.stream
progress_slider.value = 0
progress_slider.max_value = round(stream.get_length())
if Settings.hide_ticks:
progress_slider.tick_count = 0
else:
progress_slider.tick_count = round(stream.get_length() / 60)
music_time_label.text = format_text(progress_slider.value, stream.get_length())
_show_length()
Playlist.set_currently_playing_song(song_object_no)
Playlist.unset_is_playing()
if !Settings.hide_next_track: