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:
1 parent
0fc3be79e1
commit
a735c515bf
20 files changed
+1325
-327
No files matched your search
+11
-2
@@ -6,6 +6,10 @@ extends Node
|
|||||||
## Emitted when another theme is used, for code that reads theme colours itself.
|
## Emitted when another theme is used, for code that reads theme colours itself.
|
||||||
signal theme_changed
|
signal theme_changed
|
||||||
|
|
||||||
|
## How the server picks a random song: any soundtrack then any of its songs, preferring
|
||||||
|
## soundtracks played less than average, or every song with the same chance.
|
||||||
|
enum RandomMode { NORMAL, LOW_PLAYED, CLASSIC }
|
||||||
|
|
||||||
const MAX_NUMBER_OF_PLAYERS: int = 7
|
const MAX_NUMBER_OF_PLAYERS: int = 7
|
||||||
## How many letters a search needs before it also finds names with the letters in order
|
## How many letters a search needs before it also finds names with the letters in order
|
||||||
## but apart, e.g. "lttp" for "A Link to the Past". Shorter searches only find names
|
## but apart, e.g. "lttp" for "A Link to the Past". Shorter searches only find names
|
||||||
@@ -26,7 +30,7 @@ const SAVED_SETTINGS: Dictionary[String, Array] = {
|
|||||||
"quiz": [
|
"quiz": [
|
||||||
&"auto_repeat_song", &"stop_after_current", &"hide_next_track", &"hide_beginning",
|
&"auto_repeat_song", &"stop_after_current", &"hide_next_track", &"hide_beginning",
|
||||||
&"hide_for_seconds", &"hide_length", &"hide_ticks", &"winning_score",
|
&"hide_for_seconds", &"hide_length", &"hide_ticks", &"winning_score",
|
||||||
&"inspiration_list_speed",
|
&"inspiration_list_speed", &"short_songs", &"short_song_seconds",
|
||||||
],
|
],
|
||||||
"window": [&"fullscreen"],
|
"window": [&"fullscreen"],
|
||||||
"app": [&"last_seen_version", &"theme_id", &"reduce_animations"],
|
"app": [&"last_seen_version", &"theme_id", &"reduce_animations"],
|
||||||
@@ -77,7 +81,8 @@ var stop_after_current: bool = true
|
|||||||
var auto_repeat_song: bool = true
|
var auto_repeat_song: bool = true
|
||||||
var hide_next_track: bool = true
|
var hide_next_track: bool = true
|
||||||
var add_to_stats: bool = true
|
var add_to_stats: bool = true
|
||||||
var use_low_played_mode: bool = false
|
## How the server picks random songs. Not saved, like statistics counting.
|
||||||
|
var random_mode: RandomMode = RandomMode.NORMAL
|
||||||
var winning_score: int = 20
|
var winning_score: int = 20
|
||||||
var fullscreen: bool = false
|
var fullscreen: bool = false
|
||||||
var inspiration_list_speed: int = 1
|
var inspiration_list_speed: int = 1
|
||||||
@@ -85,6 +90,10 @@ var hide_for_seconds: float = 5.0
|
|||||||
var hide_beginning: bool = false
|
var hide_beginning: bool = false
|
||||||
var hide_length: bool = false
|
var hide_length: bool = false
|
||||||
var hide_ticks: bool = false
|
var hide_ticks: bool = false
|
||||||
|
## Plays only [member short_song_seconds] of each song, from a random point, so more
|
||||||
|
## songs fit in a game.
|
||||||
|
var short_songs: bool = false
|
||||||
|
var short_song_seconds: int = 30
|
||||||
var player_array: Array[PlayerObject]
|
var player_array: Array[PlayerObject]
|
||||||
var edit_players: bool = false
|
var edit_players: bool = false
|
||||||
var currently_syncing: bool = false
|
var currently_syncing: bool = false
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ var is_playing: bool = false
|
|||||||
var song_path: String = ""
|
var song_path: String = ""
|
||||||
## The song itself, only when it could not be saved to a file.
|
## The song itself, only when it could not be saved to a file.
|
||||||
var song: AudioStream
|
var song: AudioStream
|
||||||
|
## Where the part played with Short songs starts, in seconds, or -1 before it is picked.
|
||||||
|
## Picked once, so restarting and repeating play the same part.
|
||||||
|
var short_start: float = -1.0
|
||||||
|
|
||||||
|
|
||||||
func get_soundtrack_title() -> String:
|
func get_soundtrack_title() -> String:
|
||||||
|
|||||||
+81
-36
@@ -5,6 +5,11 @@ extends Control
|
|||||||
##
|
##
|
||||||
## The buttons grow a little under the mouse and the play button pops when it switches
|
## The buttons grow a little under the mouse and the play button pops when it switches
|
||||||
## between play and pause, see [ButtonBounce].
|
## 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 update_song_list
|
||||||
signal play_next_song
|
signal play_next_song
|
||||||
@@ -16,6 +21,15 @@ var playback_position: float
|
|||||||
var stream: AudioStream
|
var stream: AudioStream
|
||||||
var song_finished: bool = false
|
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 play_button: Button = %PlayButton
|
||||||
@onready var restart_button: Button = %RestartButton
|
@onready var restart_button: Button = %RestartButton
|
||||||
@onready var audio_player: AudioStreamPlayer = $AudioStreamPlayer
|
@onready var audio_player: AudioStreamPlayer = $AudioStreamPlayer
|
||||||
@@ -38,14 +52,46 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
if audio_player.has_stream_playback() && !is_changing && !audio_player.stream_paused:
|
if audio_player.has_stream_playback() && !is_changing && !audio_player.stream_paused \
|
||||||
if !Settings.hide_beginning \
|
&& audio_player.playing:
|
||||||
|| audio_player.get_playback_position() > Settings.hide_for_seconds:
|
var position: float = position_in_part()
|
||||||
progress_slider.value = audio_player.get_playback_position()
|
if _clip_length > 0.0 && position >= _clip_length:
|
||||||
if stream != null:
|
# The short part has played, which counts as the song ending
|
||||||
music_time_label.text = format_text(progress_slider.value, stream.get_length())
|
audio_player.stop()
|
||||||
else:
|
progress_slider.value = _clip_length
|
||||||
music_time_label.text = format_text(progress_slider.value, 0.0)
|
_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.
|
## 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)
|
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:
|
func format_time(time: float) -> String:
|
||||||
var total_seconds: int = roundi(time)
|
var total_seconds: int = roundi(time)
|
||||||
@warning_ignore("integer_division")
|
@warning_ignore("integer_division")
|
||||||
@@ -87,11 +141,7 @@ func play_or_pause() -> void:
|
|||||||
song_finished = false
|
song_finished = false
|
||||||
audio_player.seek(playback_position)
|
audio_player.seek(playback_position)
|
||||||
GameLog.debug("continue")
|
GameLog.debug("continue")
|
||||||
progress_slider.max_value = round(stream.get_length())
|
_show_length()
|
||||||
if Settings.hide_ticks:
|
|
||||||
progress_slider.tick_count = 0
|
|
||||||
else:
|
|
||||||
progress_slider.tick_count = round(stream.get_length() / 60)
|
|
||||||
else:
|
else:
|
||||||
pause()
|
pause()
|
||||||
|
|
||||||
@@ -102,15 +152,15 @@ func pause() -> void:
|
|||||||
_show_playing(false)
|
_show_playing(false)
|
||||||
|
|
||||||
|
|
||||||
|
## Plays the song, or its short part, from the beginning.
|
||||||
func restart() -> void:
|
func restart() -> void:
|
||||||
audio_player.stop()
|
audio_player.stop()
|
||||||
audio_player.stream_paused = false
|
audio_player.stream_paused = false
|
||||||
song_finished = false
|
song_finished = false
|
||||||
progress_slider.value = 0
|
progress_slider.value = 0
|
||||||
playback_position = audio_player.get_playback_position()
|
playback_position = _clip_start
|
||||||
audio_player.seek(playback_position)
|
|
||||||
_show_playing(true)
|
_show_playing(true)
|
||||||
audio_player.play()
|
audio_player.play(_clip_start)
|
||||||
|
|
||||||
|
|
||||||
func _on_finished() -> void:
|
func _on_finished() -> void:
|
||||||
@@ -127,16 +177,18 @@ func _on_drag_started() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_drag_ended(_changed: bool) -> void:
|
func _on_drag_ended(_changed: bool) -> void:
|
||||||
audio_player.seek(progress_slider.value)
|
audio_player.seek(_clip_start + progress_slider.value)
|
||||||
playback_position = progress_slider.value
|
playback_position = _clip_start + progress_slider.value
|
||||||
is_changing = false
|
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:
|
func seek(new_position: float) -> void:
|
||||||
progress_slider.value += new_position
|
progress_slider.value += new_position
|
||||||
is_changing = true
|
is_changing = true
|
||||||
audio_player.seek(progress_slider.value)
|
audio_player.seek(_clip_start + progress_slider.value)
|
||||||
playback_position = progress_slider.value
|
playback_position = _clip_start + progress_slider.value
|
||||||
is_changing = false
|
is_changing = false
|
||||||
|
|
||||||
|
|
||||||
@@ -173,6 +225,9 @@ func play_sound(sound_name: AudioStream) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func play_song(song: 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.stream = song
|
||||||
audio_player.play()
|
audio_player.play()
|
||||||
sound_player.stop()
|
sound_player.stop()
|
||||||
@@ -180,12 +235,7 @@ func play_song(song: AudioStream) -> void:
|
|||||||
_show_playing(true)
|
_show_playing(true)
|
||||||
stream = audio_player.stream
|
stream = audio_player.stream
|
||||||
progress_slider.value = 0
|
progress_slider.value = 0
|
||||||
progress_slider.max_value = round(stream.get_length())
|
_show_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())
|
|
||||||
|
|
||||||
|
|
||||||
## Puts song [param song_object_no] in the player without playing it, e.g. the song that
|
## 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
|
return
|
||||||
audio_player.stream = song
|
audio_player.stream = song
|
||||||
stream = song
|
stream = song
|
||||||
|
choose_part(Playlist.song_object_array[song_object_no], song)
|
||||||
song_finished = true
|
song_finished = true
|
||||||
_show_playing(false)
|
_show_playing(false)
|
||||||
progress_slider.value = 0
|
progress_slider.value = 0
|
||||||
progress_slider.max_value = round(stream.get_length())
|
_show_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())
|
|
||||||
|
|
||||||
|
|
||||||
func play_song_object(song_object_no: int) -> void:
|
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()
|
audio_player.stop()
|
||||||
await get_tree().create_timer(0.5).timeout
|
await get_tree().create_timer(0.5).timeout
|
||||||
audio_player.stream = song
|
audio_player.stream = song
|
||||||
|
choose_part(Playlist.song_object_array[song_object_no], song)
|
||||||
sound_player.stop()
|
sound_player.stop()
|
||||||
audio_player.play()
|
audio_player.play(_clip_start)
|
||||||
song_finished = false
|
song_finished = false
|
||||||
_show_playing(true)
|
_show_playing(true)
|
||||||
# A new song makes the play button pop, also when it was already playing
|
# A new song makes the play button pop, also when it was already playing
|
||||||
ButtonBounce.pop(play_button)
|
ButtonBounce.pop(play_button)
|
||||||
stream = audio_player.stream
|
stream = audio_player.stream
|
||||||
progress_slider.value = 0
|
progress_slider.value = 0
|
||||||
progress_slider.max_value = round(stream.get_length())
|
_show_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())
|
|
||||||
Playlist.set_currently_playing_song(song_object_no)
|
Playlist.set_currently_playing_song(song_object_no)
|
||||||
Playlist.unset_is_playing()
|
Playlist.unset_is_playing()
|
||||||
if !Settings.hide_next_track:
|
if !Settings.hide_next_track:
|
||||||
|
|||||||
@@ -205,12 +205,31 @@ action_mode = 0
|
|||||||
theme_type_variation = &"StepButton"
|
theme_type_variation = &"StepButton"
|
||||||
text = "+1"
|
text = "+1"
|
||||||
|
|
||||||
[node name="LowPlayedCheckButton" type="CheckButton" parent="SettingsPanel/VBoxContainer"]
|
[node name="RandomModeRow" type="HBoxContainer" parent="SettingsPanel/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="SettingsPanel/VBoxContainer/RandomModeRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"SettingsRowLabel"
|
||||||
|
text = "Random songs"
|
||||||
|
|
||||||
|
[node name="RandomModeButton" type="OptionButton" parent="SettingsPanel/VBoxContainer/RandomModeRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(180, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Normal: a random soundtrack, then one of its songs.
|
||||||
|
Low played: the same, but soundtracks played less than average come up more often.
|
||||||
|
Classic: every song has the same chance, whatever its soundtrack."
|
||||||
focus_mode = 0
|
focus_mode = 0
|
||||||
action_mode = 0
|
selected = 0
|
||||||
text = "Prefer less played soundtracks"
|
item_count = 3
|
||||||
|
popup/item_0/text = "Normal"
|
||||||
|
popup/item_0/id = 0
|
||||||
|
popup/item_1/text = "Low played"
|
||||||
|
popup/item_1/id = 1
|
||||||
|
popup/item_2/text = "Classic"
|
||||||
|
popup/item_2/id = 2
|
||||||
|
|
||||||
[node name="AddToDatabaseCheckButton" type="CheckButton" parent="SettingsPanel/VBoxContainer"]
|
[node name="AddToDatabaseCheckButton" type="CheckButton" parent="SettingsPanel/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
class_name SettingsPopup
|
class_name SettingsPopup
|
||||||
extends BasePopup
|
extends BasePopup
|
||||||
## The application settings: which MusicServer or music folder to play from, the theme,
|
## The application settings: which MusicServer or music folder to play from, the theme,
|
||||||
## fullscreen, fewer animations, how many songs to download ahead, whether to prefer less played soundtracks
|
## fullscreen, fewer animations, how many songs to download ahead, how random songs are
|
||||||
## and whether plays count in the statistics, and a button that opens the folder the
|
## picked and whether plays count in the statistics, and a button that opens the folder
|
||||||
## settings are kept in. Opened from the Settings button in the top bar.
|
## the settings are kept in. Opened from the Settings button in the top bar.
|
||||||
##
|
##
|
||||||
## A music folder (see [MusicFolder]) is used instead of the server until Use server is
|
## A music folder (see [MusicFolder]) is used instead of the server until Use server is
|
||||||
## pressed or the game is closed. The settings that need the server are off meanwhile.
|
## pressed or the game is closed. The settings that need the server are off meanwhile.
|
||||||
@@ -30,7 +30,7 @@ const PADDING: Vector2 = Vector2(20, 12)
|
|||||||
@onready var cache_label: Label = %CacheLabel
|
@onready var cache_label: Label = %CacheLabel
|
||||||
@onready var lower_cache_button: Button = %LowerCacheButton
|
@onready var lower_cache_button: Button = %LowerCacheButton
|
||||||
@onready var increase_cache_button: Button = %IncreaseCacheButton
|
@onready var increase_cache_button: Button = %IncreaseCacheButton
|
||||||
@onready var low_played_button: CheckButton = %LowPlayedCheckButton
|
@onready var random_mode_button: OptionButton = %RandomModeButton
|
||||||
@onready var add_to_database_button: CheckButton = %AddToDatabaseCheckButton
|
@onready var add_to_database_button: CheckButton = %AddToDatabaseCheckButton
|
||||||
@onready var rows: VBoxContainer = %VBoxContainer
|
@onready var rows: VBoxContainer = %VBoxContainer
|
||||||
@onready var folder_label: Label = %FolderLabel
|
@onready var folder_label: Label = %FolderLabel
|
||||||
@@ -81,8 +81,8 @@ func _ready() -> void:
|
|||||||
lower_cache_button.pressed.connect(lower_cache)
|
lower_cache_button.pressed.connect(lower_cache)
|
||||||
increase_cache_button.pressed.connect(increase_cache)
|
increase_cache_button.pressed.connect(increase_cache)
|
||||||
select_server_button.item_selected.connect(select_server)
|
select_server_button.item_selected.connect(select_server)
|
||||||
low_played_button.toggled.connect(
|
random_mode_button.item_selected.connect(
|
||||||
func(on: bool) -> void: Settings.use_low_played_mode = on)
|
func(mode: int) -> void: Settings.random_mode = mode as Settings.RandomMode)
|
||||||
add_to_database_button.toggled.connect(func(on: bool) -> void: Settings.add_to_stats = on)
|
add_to_database_button.toggled.connect(func(on: bool) -> void: Settings.add_to_stats = on)
|
||||||
visibility_changed.connect(_on_visibility_changed)
|
visibility_changed.connect(_on_visibility_changed)
|
||||||
# Alt+Enter switches fullscreen from the main window, which resizes the window
|
# Alt+Enter switches fullscreen from the main window, which resizes the window
|
||||||
@@ -189,7 +189,7 @@ func _use_server(new_server: int) -> void:
|
|||||||
func _on_visibility_changed() -> void:
|
func _on_visibility_changed() -> void:
|
||||||
if visible:
|
if visible:
|
||||||
_update_fullscreen_button()
|
_update_fullscreen_button()
|
||||||
low_played_button.set_pressed_no_signal(Settings.use_low_played_mode)
|
random_mode_button.select(Settings.random_mode)
|
||||||
add_to_database_button.set_pressed_no_signal(Settings.add_to_stats)
|
add_to_database_button.set_pressed_no_signal(Settings.add_to_stats)
|
||||||
else:
|
else:
|
||||||
folder_error_label.visible = false
|
folder_error_label.visible = false
|
||||||
@@ -224,7 +224,7 @@ func _show_music_source() -> void:
|
|||||||
characters_label.text = "From " + folder.display_name()
|
characters_label.text = "From " + folder.display_name()
|
||||||
else:
|
else:
|
||||||
characters_label.text = "From the server"
|
characters_label.text = "From the server"
|
||||||
for control: BaseButton in [select_server_button, low_played_button, add_to_database_button]:
|
for control: BaseButton in [select_server_button, random_mode_button, add_to_database_button]:
|
||||||
control.disabled = folder != null
|
control.disabled = folder != null
|
||||||
_fit_to_rows()
|
_fit_to_rows()
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,49 @@ focus_mode = 0
|
|||||||
action_mode = 0
|
action_mode = 0
|
||||||
text = "Hide next track"
|
text = "Hide next track"
|
||||||
|
|
||||||
|
[node name="ShortSongsCheckButton" type="CheckButton" parent="CardPanel/VBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
tooltip_text = "Play only a part of each song, from a random point, so more songs fit in a game"
|
||||||
|
focus_mode = 0
|
||||||
|
action_mode = 0
|
||||||
|
text = "Short songs"
|
||||||
|
|
||||||
|
[node name="ShortSongSecondsRow" type="HBoxContainer" parent="CardPanel/VBox"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Title" type="Label" parent="CardPanel/VBox/ShortSongSecondsRow"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
theme_type_variation = &"SettingsRowLabel"
|
||||||
|
text = "Seconds per song"
|
||||||
|
|
||||||
|
[node name="ShortSongSecondsLabel" type="Label" parent="CardPanel/VBox/ShortSongSecondsRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(48, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
text = "30"
|
||||||
|
horizontal_alignment = 1
|
||||||
|
|
||||||
|
[node name="LowerShortSongSecondsButton" type="Button" parent="CardPanel/VBox/ShortSongSecondsRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(30, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 0
|
||||||
|
action_mode = 0
|
||||||
|
theme_type_variation = &"StepButton"
|
||||||
|
text = "-5"
|
||||||
|
|
||||||
|
[node name="IncreaseShortSongSecondsButton" type="Button" parent="CardPanel/VBox/ShortSongSecondsRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(30, 0)
|
||||||
|
layout_mode = 2
|
||||||
|
focus_mode = 0
|
||||||
|
action_mode = 0
|
||||||
|
theme_type_variation = &"StepButton"
|
||||||
|
text = "+5"
|
||||||
|
|
||||||
[node name="HideBeginningCheckButton" type="CheckButton" parent="CardPanel/VBox"]
|
[node name="HideBeginningCheckButton" type="CheckButton" parent="CardPanel/VBox"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ extends BasePopup
|
|||||||
|
|
||||||
## The most seconds of a song's beginning that can be hidden.
|
## The most seconds of a song's beginning that can be hidden.
|
||||||
const MAX_HIDE_SECONDS: float = 10.0
|
const MAX_HIDE_SECONDS: float = 10.0
|
||||||
|
## The shortest and longest part of a song that Short songs plays, and the step between.
|
||||||
|
const MIN_SHORT_SONG_SECONDS: int = 10
|
||||||
|
const MAX_SHORT_SONG_SECONDS: int = 120
|
||||||
|
const SHORT_SONG_STEP: int = 5
|
||||||
## Space between the rows and the card's edges.
|
## Space between the rows and the card's edges.
|
||||||
const PADDING: Vector2 = Vector2(16, 12)
|
const PADDING: Vector2 = Vector2(16, 12)
|
||||||
|
|
||||||
@@ -21,6 +25,11 @@ const PADDING: Vector2 = Vector2(16, 12)
|
|||||||
@onready var auto_repeat_button: CheckButton = %AutoRepeatCheckButton
|
@onready var auto_repeat_button: CheckButton = %AutoRepeatCheckButton
|
||||||
@onready var stop_after_current_button: CheckButton = %StopAfterCurrentCheckButton
|
@onready var stop_after_current_button: CheckButton = %StopAfterCurrentCheckButton
|
||||||
@onready var hide_next_track_button: CheckButton = %HideNextTrackCheckButton
|
@onready var hide_next_track_button: CheckButton = %HideNextTrackCheckButton
|
||||||
|
@onready var short_songs_button: CheckButton = %ShortSongsCheckButton
|
||||||
|
@onready var short_song_seconds_row: HBoxContainer = %ShortSongSecondsRow
|
||||||
|
@onready var short_song_seconds_label: Label = %ShortSongSecondsLabel
|
||||||
|
@onready var lower_short_song_seconds_button: Button = %LowerShortSongSecondsButton
|
||||||
|
@onready var increase_short_song_seconds_button: Button = %IncreaseShortSongSecondsButton
|
||||||
@onready var hide_beginning_button: CheckButton = %HideBeginningCheckButton
|
@onready var hide_beginning_button: CheckButton = %HideBeginningCheckButton
|
||||||
@onready var hide_for_seconds_row: HBoxContainer = %HideForSecondsRow
|
@onready var hide_for_seconds_row: HBoxContainer = %HideForSecondsRow
|
||||||
@onready var hide_for_seconds_label: Label = %HideForSecondsLabel
|
@onready var hide_for_seconds_label: Label = %HideForSecondsLabel
|
||||||
@@ -41,6 +50,11 @@ func _ready() -> void:
|
|||||||
auto_repeat_button.toggled.connect(_save_toggle.bind(&"auto_repeat_song"))
|
auto_repeat_button.toggled.connect(_save_toggle.bind(&"auto_repeat_song"))
|
||||||
stop_after_current_button.toggled.connect(_save_toggle.bind(&"stop_after_current"))
|
stop_after_current_button.toggled.connect(_save_toggle.bind(&"stop_after_current"))
|
||||||
hide_next_track_button.toggled.connect(_save_toggle.bind(&"hide_next_track"))
|
hide_next_track_button.toggled.connect(_save_toggle.bind(&"hide_next_track"))
|
||||||
|
short_songs_button.toggled.connect(_on_short_songs_toggled)
|
||||||
|
lower_short_song_seconds_button.pressed.connect(
|
||||||
|
set_short_song_seconds.bind(-SHORT_SONG_STEP, true))
|
||||||
|
increase_short_song_seconds_button.pressed.connect(
|
||||||
|
set_short_song_seconds.bind(SHORT_SONG_STEP, true))
|
||||||
hide_beginning_button.toggled.connect(_on_hide_beginning_toggled)
|
hide_beginning_button.toggled.connect(_on_hide_beginning_toggled)
|
||||||
hide_length_button.toggled.connect(_save_toggle.bind(&"hide_length"))
|
hide_length_button.toggled.connect(_save_toggle.bind(&"hide_length"))
|
||||||
hide_ticks_button.toggled.connect(_save_toggle.bind(&"hide_ticks"))
|
hide_ticks_button.toggled.connect(_save_toggle.bind(&"hide_ticks"))
|
||||||
@@ -60,6 +74,9 @@ func show_settings() -> void:
|
|||||||
auto_repeat_button.set_pressed_no_signal(Settings.auto_repeat_song)
|
auto_repeat_button.set_pressed_no_signal(Settings.auto_repeat_song)
|
||||||
stop_after_current_button.set_pressed_no_signal(Settings.stop_after_current)
|
stop_after_current_button.set_pressed_no_signal(Settings.stop_after_current)
|
||||||
hide_next_track_button.set_pressed_no_signal(Settings.hide_next_track)
|
hide_next_track_button.set_pressed_no_signal(Settings.hide_next_track)
|
||||||
|
short_songs_button.set_pressed_no_signal(Settings.short_songs)
|
||||||
|
short_song_seconds_row.visible = Settings.short_songs
|
||||||
|
short_song_seconds_label.text = str(Settings.short_song_seconds)
|
||||||
hide_beginning_button.set_pressed_no_signal(Settings.hide_beginning)
|
hide_beginning_button.set_pressed_no_signal(Settings.hide_beginning)
|
||||||
hide_length_button.set_pressed_no_signal(Settings.hide_length)
|
hide_length_button.set_pressed_no_signal(Settings.hide_length)
|
||||||
hide_ticks_button.set_pressed_no_signal(Settings.hide_ticks)
|
hide_ticks_button.set_pressed_no_signal(Settings.hide_ticks)
|
||||||
@@ -117,6 +134,23 @@ func _save_toggle(on: bool, setting: StringName) -> void:
|
|||||||
Settings.save_settings()
|
Settings.save_settings()
|
||||||
|
|
||||||
|
|
||||||
|
## Sets how many seconds of each song Short songs plays, between
|
||||||
|
## [constant MIN_SHORT_SONG_SECONDS] and [constant MAX_SHORT_SONG_SECONDS]. With
|
||||||
|
## [param relative] the seconds are added to the current length.
|
||||||
|
func set_short_song_seconds(seconds: int, relative: bool = false) -> void:
|
||||||
|
var length: int = Settings.short_song_seconds + seconds if relative else seconds
|
||||||
|
Settings.short_song_seconds = clampi(length, MIN_SHORT_SONG_SECONDS,
|
||||||
|
MAX_SHORT_SONG_SECONDS)
|
||||||
|
short_song_seconds_label.text = str(Settings.short_song_seconds)
|
||||||
|
Settings.save_settings()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_short_songs_toggled(on: bool) -> void:
|
||||||
|
_save_toggle(on, &"short_songs")
|
||||||
|
short_song_seconds_row.visible = on
|
||||||
|
_fit_to_rows()
|
||||||
|
|
||||||
|
|
||||||
func _on_hide_beginning_toggled(on: bool) -> void:
|
func _on_hide_beginning_toggled(on: bool) -> void:
|
||||||
_save_toggle(on, &"hide_beginning")
|
_save_toggle(on, &"hide_beginning")
|
||||||
hide_for_seconds_row.visible = on
|
hide_for_seconds_row.visible = on
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@ func _download_random_song(new_song_object: SongObject) -> void:
|
|||||||
return
|
return
|
||||||
Playlist.store_song(new_song_object, body)
|
Playlist.store_song(new_song_object, body)
|
||||||
_add_song_to_queue(new_song_object)
|
_add_song_to_queue(new_song_object)
|
||||||
MusicServerClient.fetch_random_song(Settings.use_low_played_mode, song_downloaded,
|
MusicServerClient.fetch_random_song(Settings.random_mode, song_downloaded,
|
||||||
_retry_later.bind(_download_random_song.bind(new_song_object)))
|
_retry_later.bind(_download_random_song.bind(new_song_object)))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,17 +51,22 @@ func fetch_song_list(on_done: Callable, on_failed: Callable = Callable()) -> voi
|
|||||||
get_json_list("/music/list", SongInfo, on_done, on_failed)
|
get_json_list("/music/list", SongInfo, on_done, on_failed)
|
||||||
|
|
||||||
|
|
||||||
## Downloads a random song as MP3 bytes. With [param low_played] the server prefers
|
## Downloads a random song as MP3 bytes, picked the way [param mode] (a
|
||||||
## soundtracks that have been played the least.
|
## [code]Settings.RandomMode[/code]) says: a random soundtrack and then one of its songs,
|
||||||
func fetch_random_song(
|
## the same but preferring soundtracks played less than average, or every song with the
|
||||||
low_played: bool, on_done: Callable, on_failed: Callable = Callable()) -> void:
|
## same chance.
|
||||||
get_bytes("/music/rand/low" if low_played else "/music/rand", on_done, on_failed)
|
func fetch_random_song(mode: int, on_done: Callable, on_failed: Callable = Callable()) -> void:
|
||||||
|
get_bytes(random_song_path(mode), on_done, on_failed)
|
||||||
|
|
||||||
|
|
||||||
## Downloads a random song as MP3 bytes, picked the way the server did before low played
|
## The endpoint for random songs picked the way [param mode] says.
|
||||||
## mode existed: any soundtrack is as likely as any other.
|
static func random_song_path(mode: int) -> String:
|
||||||
func fetch_random_song_classic(on_done: Callable, on_failed: Callable = Callable()) -> void:
|
match mode:
|
||||||
get_bytes("/music/rand/classic", on_done, on_failed)
|
Settings.RandomMode.LOW_PLAYED:
|
||||||
|
return "/music/rand/low"
|
||||||
|
Settings.RandomMode.CLASSIC:
|
||||||
|
return "/music/rand/classic"
|
||||||
|
return "/music/rand"
|
||||||
|
|
||||||
|
|
||||||
## Adds the most recently fetched random song to the server playlist.
|
## Adds the most recently fetched random song to the server playlist.
|
||||||
|
|||||||
+831
-237
File diff suppressed because it is too large.
Load diff
@@ -26,7 +26,7 @@ func before_test() -> void:
|
|||||||
QuizSave.delete()
|
QuizSave.delete()
|
||||||
Settings.add_to_stats = true
|
Settings.add_to_stats = true
|
||||||
Settings.hide_next_track = true
|
Settings.hide_next_track = true
|
||||||
Settings.use_low_played_mode = false
|
Settings.random_mode = Settings.RandomMode.NORMAL
|
||||||
Settings.winning_score = 20
|
Settings.winning_score = 20
|
||||||
# Otherwise the About popup opens at start, as for a new version
|
# Otherwise the About popup opens at start, as for a new version
|
||||||
Settings.last_seen_version = Settings.version
|
Settings.last_seen_version = Settings.version
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ static func prepare_autoloads() -> void:
|
|||||||
Settings.character_select_open = false
|
Settings.character_select_open = false
|
||||||
Settings.add_to_stats = true
|
Settings.add_to_stats = true
|
||||||
Settings.hide_next_track = true
|
Settings.hide_next_track = true
|
||||||
Settings.use_low_played_mode = false
|
Settings.random_mode = Settings.RandomMode.NORMAL
|
||||||
Settings.winning_score = 20
|
Settings.winning_score = 20
|
||||||
Settings.last_seen_version = Settings.version
|
Settings.last_seen_version = Settings.version
|
||||||
# The same picture every time, without half-finished animations
|
# The same picture every time, without half-finished animations
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func test_startup_loads_soundtracks_and_preloads_a_song() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func test_low_played_mode_fetches_from_low_played_endpoint() -> void:
|
func test_low_played_mode_fetches_from_low_played_endpoint() -> void:
|
||||||
Settings.use_low_played_mode = true
|
Settings.random_mode = Settings.RandomMode.LOW_PLAYED
|
||||||
_server.respond_bytes(
|
_server.respond_bytes(
|
||||||
"/music/rand/low", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
"/music/rand/low", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||||
|
|
||||||
|
|||||||
@@ -177,3 +177,33 @@ func test_changes_are_saved_at_once() -> void:
|
|||||||
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
||||||
assert_bool(saved.get_value("quiz", "hide_next_track")).is_false()
|
assert_bool(saved.get_value("quiz", "hide_next_track")).is_false()
|
||||||
assert_int(saved.get_value("quiz", "winning_score")).is_equal(19)
|
assert_int(saved.get_value("quiz", "winning_score")).is_equal(19)
|
||||||
|
|
||||||
|
|
||||||
|
func test_short_songs_shows_its_seconds_and_is_saved() -> void:
|
||||||
|
Settings.short_songs = false
|
||||||
|
await _open()
|
||||||
|
assert_bool(_card.short_song_seconds_row.visible).is_false()
|
||||||
|
|
||||||
|
_card.short_songs_button.button_pressed = true
|
||||||
|
|
||||||
|
assert_bool(Settings.short_songs).is_true()
|
||||||
|
assert_bool(_card.short_song_seconds_row.visible).is_true()
|
||||||
|
var saved: ConfigFile = ConfigFile.new()
|
||||||
|
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
||||||
|
assert_bool(saved.get_value("quiz", "short_songs")).is_true()
|
||||||
|
|
||||||
|
|
||||||
|
func test_seconds_per_song_go_in_fives_between_ten_and_two_minutes() -> void:
|
||||||
|
Settings.short_song_seconds = 30
|
||||||
|
Settings.short_songs = true
|
||||||
|
await _open()
|
||||||
|
|
||||||
|
_card.increase_short_song_seconds_button.pressed.emit()
|
||||||
|
assert_int(Settings.short_song_seconds).is_equal(35)
|
||||||
|
assert_str(_card.short_song_seconds_label.text).is_equal("35")
|
||||||
|
for i: int in 10:
|
||||||
|
_card.lower_short_song_seconds_button.pressed.emit()
|
||||||
|
assert_int(Settings.short_song_seconds).is_equal(QuizSettingsCard.MIN_SHORT_SONG_SECONDS)
|
||||||
|
for i: int in 40:
|
||||||
|
_card.increase_short_song_seconds_button.pressed.emit()
|
||||||
|
assert_int(Settings.short_song_seconds).is_equal(QuizSettingsCard.MAX_SHORT_SONG_SECONDS)
|
||||||
@@ -9,15 +9,33 @@ func before_test() -> void:
|
|||||||
_popup = _window.get_node("SettingsPopup")
|
_popup = _window.get_node("SettingsPopup")
|
||||||
|
|
||||||
|
|
||||||
func test_less_played_toggle_changes_the_setting() -> void:
|
func test_random_songs_choice_lists_the_three_modes() -> void:
|
||||||
Settings.use_low_played_mode = false
|
var names: Array[String] = []
|
||||||
|
for index: int in _popup.random_mode_button.item_count:
|
||||||
|
names.append(_popup.random_mode_button.get_item_text(index))
|
||||||
|
|
||||||
|
assert_array(names).contains_exactly(["Normal", "Low played", "Classic"])
|
||||||
|
|
||||||
|
|
||||||
|
@warning_ignore("unused_parameter")
|
||||||
|
func test_random_songs_choice_changes_the_setting(mode: Settings.RandomMode,
|
||||||
|
test_parameters: Array = [[Settings.RandomMode.LOW_PLAYED],
|
||||||
|
[Settings.RandomMode.CLASSIC], [Settings.RandomMode.NORMAL]]) -> void:
|
||||||
_popup.open()
|
_popup.open()
|
||||||
await _runner.simulate_frames(1)
|
await _runner.simulate_frames(1)
|
||||||
assert_bool(_popup.low_played_button.button_pressed).is_false()
|
|
||||||
|
|
||||||
await _click_at(_popup.low_played_button.get_global_rect().get_center())
|
_popup.random_mode_button.select(mode)
|
||||||
|
_popup.random_mode_button.item_selected.emit(mode)
|
||||||
|
|
||||||
assert_bool(Settings.use_low_played_mode).is_true()
|
assert_int(Settings.random_mode).is_equal(mode)
|
||||||
|
|
||||||
|
|
||||||
|
func test_random_songs_choice_shows_the_mode_in_use() -> void:
|
||||||
|
Settings.random_mode = Settings.RandomMode.CLASSIC
|
||||||
|
|
||||||
|
_popup.open()
|
||||||
|
|
||||||
|
assert_int(_popup.random_mode_button.selected).is_equal(Settings.RandomMode.CLASSIC)
|
||||||
|
|
||||||
|
|
||||||
func test_statistics_toggle_changes_the_setting() -> void:
|
func test_statistics_toggle_changes_the_setting() -> void:
|
||||||
@@ -33,14 +51,15 @@ func test_statistics_toggle_changes_the_setting() -> void:
|
|||||||
|
|
||||||
func test_statistics_settings_are_not_saved_with_the_quiz_settings() -> void:
|
func test_statistics_settings_are_not_saved_with_the_quiz_settings() -> void:
|
||||||
Settings.add_to_stats = false
|
Settings.add_to_stats = false
|
||||||
Settings.use_low_played_mode = true
|
Settings.random_mode = Settings.RandomMode.LOW_PLAYED
|
||||||
|
|
||||||
Settings.save_settings()
|
Settings.save_settings()
|
||||||
|
|
||||||
var saved: ConfigFile = ConfigFile.new()
|
var saved: ConfigFile = ConfigFile.new()
|
||||||
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
assert_int(saved.load(Settings.saved_settings_path())).is_equal(OK)
|
||||||
assert_bool(saved.has_section_key("quiz", "add_to_stats")).is_false()
|
assert_bool(saved.has_section_key("quiz", "add_to_stats")).is_false()
|
||||||
assert_bool(saved.has_section_key("quiz", "use_low_played_mode")).is_false()
|
assert_bool(saved.has_section_key("quiz", "random_mode")).is_false()
|
||||||
|
assert_bool(saved.has_section_key("app", "random_mode")).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_popup_fits_its_rows_and_is_centred() -> void:
|
func test_popup_fits_its_rows_and_is_centred() -> void:
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
extends GdUnitTestSuite
|
||||||
|
## Short songs: only a part of each quiz song plays, from a random point picked once per
|
||||||
|
## song, and the end of the part counts as the song ending.
|
||||||
|
|
||||||
|
## How long the test song is, in seconds.
|
||||||
|
const SONG_SECONDS: float = 60.0
|
||||||
|
|
||||||
|
var _snapshot: AutoloadSnapshot
|
||||||
|
var _runner: GdUnitSceneRunner
|
||||||
|
var _player: MusicPlayer
|
||||||
|
|
||||||
|
|
||||||
|
func before_test() -> void:
|
||||||
|
_snapshot = AutoloadSnapshot.take([Settings, Playlist])
|
||||||
|
Settings.short_songs = true
|
||||||
|
Settings.short_song_seconds = 20
|
||||||
|
Settings.hide_beginning = false
|
||||||
|
Settings.hide_length = false
|
||||||
|
Settings.auto_repeat_song = false
|
||||||
|
Settings.stop_after_current = true
|
||||||
|
Playlist.song_object_array.clear()
|
||||||
|
_runner = scene_runner("res://player/MusicPlayer.tscn")
|
||||||
|
_player = _runner.scene()
|
||||||
|
|
||||||
|
|
||||||
|
func after_test() -> void:
|
||||||
|
_player.audio_player.stop()
|
||||||
|
_snapshot.restore()
|
||||||
|
|
||||||
|
|
||||||
|
# A silent song of [param seconds], in the playlist
|
||||||
|
func _add_song(seconds: float = SONG_SECONDS) -> SongObject:
|
||||||
|
var song: AudioStreamWAV = AudioStreamWAV.new()
|
||||||
|
song.format = AudioStreamWAV.FORMAT_8_BITS
|
||||||
|
song.mix_rate = 8000
|
||||||
|
var silence: PackedByteArray = []
|
||||||
|
silence.resize(int(seconds * song.mix_rate))
|
||||||
|
song.data = silence
|
||||||
|
var song_object: SongObject = SongObject.new()
|
||||||
|
song_object.song = song
|
||||||
|
song_object.song_number = Playlist.song_object_array.size()
|
||||||
|
Playlist.add_to_playlist(song_object)
|
||||||
|
return song_object
|
||||||
|
|
||||||
|
|
||||||
|
func _play(song_object: SongObject) -> void:
|
||||||
|
await _player.play_song_object(song_object.song_number)
|
||||||
|
await _runner.simulate_frames(2)
|
||||||
|
|
||||||
|
|
||||||
|
func test_part_starts_between_ten_and_sixty_percent_in() -> void:
|
||||||
|
for i: int in 20:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
_player.choose_part(song_object, song_object.song)
|
||||||
|
|
||||||
|
assert_float(song_object.short_start).is_between(SONG_SECONDS * 0.1, SONG_SECONDS * 0.6)
|
||||||
|
assert_float(_player.part_length()).is_equal(20.0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_part_is_picked_once_per_song() -> void:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
_player.choose_part(song_object, song_object.song)
|
||||||
|
var start: float = song_object.short_start
|
||||||
|
|
||||||
|
_player.choose_part(song_object, song_object.song)
|
||||||
|
|
||||||
|
assert_float(song_object.short_start).is_equal(start)
|
||||||
|
|
||||||
|
|
||||||
|
func test_part_always_fits_in_the_song() -> void:
|
||||||
|
Settings.short_song_seconds = 50
|
||||||
|
for i: int in 20:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
_player.choose_part(song_object, song_object.song)
|
||||||
|
|
||||||
|
assert_float(song_object.short_start + 50).is_less_equal(SONG_SECONDS)
|
||||||
|
|
||||||
|
|
||||||
|
func test_song_shorter_than_the_part_plays_whole() -> void:
|
||||||
|
var song_object: SongObject = _add_song(15.0)
|
||||||
|
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
assert_float(_player.part_length()).is_equal_approx(15.0, 0.01)
|
||||||
|
assert_float(song_object.short_start).is_equal(-1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_whole_song_plays_with_short_songs_off() -> void:
|
||||||
|
Settings.short_songs = false
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
assert_float(_player.part_length()).is_equal_approx(SONG_SECONDS, 0.01)
|
||||||
|
assert_float(_player.position_in_part()).is_less(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_song_starts_at_its_part_and_shows_the_part_length() -> void:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
assert_float(_player.audio_player.get_playback_position()) \
|
||||||
|
.is_greater_equal(song_object.short_start - 0.01)
|
||||||
|
assert_float(_player.position_in_part()).is_less(1.0)
|
||||||
|
assert_float(_player.progress_slider.max_value).is_equal(20.0)
|
||||||
|
assert_str(_player.music_time_label.text).ends_with("/ 00:20")
|
||||||
|
|
||||||
|
|
||||||
|
func test_seeking_stays_inside_the_part() -> void:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
_player.seek(500)
|
||||||
|
|
||||||
|
assert_float(_player.progress_slider.value).is_equal(20.0)
|
||||||
|
assert_float(_player.playback_position) \
|
||||||
|
.is_equal_approx(song_object.short_start + 20.0, 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
func test_end_of_the_part_stops_the_song() -> void:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
_player.seek(19.9)
|
||||||
|
await await_millis(300)
|
||||||
|
|
||||||
|
assert_bool(_player.song_finished).is_true()
|
||||||
|
assert_bool(_player.audio_player.playing).is_false()
|
||||||
|
|
||||||
|
|
||||||
|
func test_end_of_the_part_repeats_it_with_auto_repeat() -> void:
|
||||||
|
Settings.auto_repeat_song = true
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
_player.seek(19.9)
|
||||||
|
await await_millis(300)
|
||||||
|
|
||||||
|
assert_bool(_player.audio_player.playing).is_true()
|
||||||
|
assert_float(_player.position_in_part()).is_less(1.0)
|
||||||
|
assert_float(_player.audio_player.get_playback_position()) \
|
||||||
|
.is_greater_equal(song_object.short_start - 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
func test_end_of_the_part_goes_to_the_next_song_without_stop_after_current() -> void:
|
||||||
|
Settings.stop_after_current = false
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
await _play(song_object)
|
||||||
|
var signals: GdUnitSignalAssert = assert_signal(monitor_signals(_player))
|
||||||
|
|
||||||
|
_player.seek(19.9)
|
||||||
|
|
||||||
|
await signals.wait_until(1000).is_emitted("play_next_song")
|
||||||
|
|
||||||
|
|
||||||
|
func test_restart_plays_the_same_part_again() -> void:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
await _play(song_object)
|
||||||
|
_player.seek(10)
|
||||||
|
|
||||||
|
_player.restart()
|
||||||
|
await _runner.simulate_frames(2)
|
||||||
|
|
||||||
|
assert_float(_player.position_in_part()).is_less(1.0)
|
||||||
|
assert_float(_player.audio_player.get_playback_position()) \
|
||||||
|
.is_greater_equal(song_object.short_start - 0.01)
|
||||||
|
|
||||||
|
|
||||||
|
func test_hidden_beginning_counts_inside_the_part() -> void:
|
||||||
|
Settings.hide_beginning = true
|
||||||
|
Settings.hide_for_seconds = 5.0
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
assert_str(_player.music_time_label.text).is_equal("??:?? / 00:20")
|
||||||
|
_player.seek(6)
|
||||||
|
await _runner.simulate_frames(2)
|
||||||
|
assert_str(_player.music_time_label.text).starts_with("00:06")
|
||||||
|
|
||||||
|
|
||||||
|
func test_other_sounds_always_play_whole() -> void:
|
||||||
|
var song_object: SongObject = _add_song()
|
||||||
|
await _play(song_object)
|
||||||
|
|
||||||
|
_player.play_song(_add_song(30.0).song)
|
||||||
|
|
||||||
|
assert_float(_player.part_length()).is_equal_approx(30.0, 0.01)
|
||||||
|
assert_float(_player.position_in_part()).is_less(1.0)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://ov2rqiwl8x6s
|
||||||
@@ -15,7 +15,7 @@ func before_test() -> void:
|
|||||||
Playlist.reset_playlist()
|
Playlist.reset_playlist()
|
||||||
Playlist.is_song_downloading = false
|
Playlist.is_song_downloading = false
|
||||||
Playlist.number_of_tracks_to_preload = 1
|
Playlist.number_of_tracks_to_preload = 1
|
||||||
Settings.use_low_played_mode = false
|
Settings.random_mode = Settings.RandomMode.NORMAL
|
||||||
_server = FakeHttpTransport.new().install()
|
_server = FakeHttpTransport.new().install()
|
||||||
_server.respond_json("/music/list", [])
|
_server.respond_json("/music/list", [])
|
||||||
_server.respond_bytes("/music/rand", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
_server.respond_bytes("/music/rand", FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||||
@@ -79,15 +79,22 @@ func test_fetch_song_keeps_going_until_the_cache_is_full() -> void:
|
|||||||
assert_int(_server.count("/music/rand")).is_equal(3)
|
assert_int(_server.count("/music/rand")).is_equal(3)
|
||||||
|
|
||||||
|
|
||||||
func test_low_played_mode_uses_its_endpoint() -> void:
|
@warning_ignore("unused_parameter")
|
||||||
Settings.use_low_played_mode = true
|
func test_each_random_mode_uses_its_endpoint(mode: Settings.RandomMode, path: String,
|
||||||
_server.respond_bytes("/music/rand/low",
|
test_parameters: Array = [
|
||||||
FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
[Settings.RandomMode.NORMAL, "/music/rand"],
|
||||||
|
[Settings.RandomMode.LOW_PLAYED, "/music/rand/low"],
|
||||||
|
[Settings.RandomMode.CLASSIC, "/music/rand/classic"],
|
||||||
|
]) -> void:
|
||||||
|
Settings.random_mode = mode
|
||||||
|
_server.respond_bytes(path, FileAccess.get_file_as_bytes("res://tests/data/short.mp3"))
|
||||||
|
|
||||||
_loader.fetch_song()
|
_loader.fetch_song()
|
||||||
|
|
||||||
assert_int(_server.count("/music/rand/low")).is_equal(1)
|
assert_int(_server.count(path)).is_equal(1)
|
||||||
assert_int(_server.count("/music/rand")).is_equal(0)
|
for other: String in ["/music/rand", "/music/rand/low", "/music/rand/classic"]:
|
||||||
|
if other != path:
|
||||||
|
assert_int(_server.count(other)).override_failure_message(other).is_equal(0)
|
||||||
|
|
||||||
|
|
||||||
func test_fetch_song_if_idle_does_nothing_while_downloading() -> void:
|
func test_fetch_song_if_idle_does_nothing_while_downloading() -> void:
|
||||||
|
|||||||
@@ -59,16 +59,16 @@ func test_json_list_that_is_not_an_array_calls_on_failed() -> void:
|
|||||||
assert_array(outcome).contains_exactly(["failed"])
|
assert_array(outcome).contains_exactly(["failed"])
|
||||||
|
|
||||||
|
|
||||||
func test_fetch_random_song_uses_normal_endpoint() -> void:
|
@warning_ignore("unused_parameter")
|
||||||
MusicServerClient.fetch_random_song(false, func(_body: PackedByteArray) -> void: pass)
|
func test_fetch_random_song_uses_the_endpoint_of_its_mode(mode: Settings.RandomMode,
|
||||||
|
path: String, test_parameters: Array = [
|
||||||
|
[Settings.RandomMode.NORMAL, "/music/rand"],
|
||||||
|
[Settings.RandomMode.LOW_PLAYED, "/music/rand/low"],
|
||||||
|
[Settings.RandomMode.CLASSIC, "/music/rand/classic"],
|
||||||
|
]) -> void:
|
||||||
|
MusicServerClient.fetch_random_song(mode, func(_body: PackedByteArray) -> void: pass)
|
||||||
|
|
||||||
assert_array(_server.requested_paths).contains_exactly(["/music/rand"])
|
assert_array(_server.requested_paths).contains_exactly([path])
|
||||||
|
|
||||||
|
|
||||||
func test_fetch_random_song_uses_low_played_endpoint() -> void:
|
|
||||||
MusicServerClient.fetch_random_song(true, func(_body: PackedByteArray) -> void: pass)
|
|
||||||
|
|
||||||
assert_array(_server.requested_paths).contains_exactly(["/music/rand/low"])
|
|
||||||
|
|
||||||
|
|
||||||
func test_fetch_song_passes_raw_bytes() -> void:
|
func test_fetch_song_passes_raw_bytes() -> void:
|
||||||
@@ -103,7 +103,7 @@ func test_failed_request_calls_on_failed() -> void:
|
|||||||
_server.respond_bytes("/music/rand", PackedByteArray(), 503)
|
_server.respond_bytes("/music/rand", PackedByteArray(), 503)
|
||||||
var outcome: Array[String] = []
|
var outcome: Array[String] = []
|
||||||
|
|
||||||
MusicServerClient.fetch_random_song(false,
|
MusicServerClient.fetch_random_song(Settings.RandomMode.NORMAL,
|
||||||
func(_body: PackedByteArray) -> void: outcome.append("done"),
|
func(_body: PackedByteArray) -> void: outcome.append("done"),
|
||||||
func() -> void: outcome.append("failed"))
|
func() -> void: outcome.append("failed"))
|
||||||
|
|
||||||
@@ -370,8 +370,6 @@ func test_delete_token_without_token_sends_nothing() -> void:
|
|||||||
func test_endpoint_paths_and_methods() -> void:
|
func test_endpoint_paths_and_methods() -> void:
|
||||||
var noop: Callable = func(_data: Variant = null) -> void: pass
|
var noop: Callable = func(_data: Variant = null) -> void: pass
|
||||||
var calls: Array[Array] = [
|
var calls: Array[Array] = [
|
||||||
[func() -> void: MusicServerClient.fetch_random_song_classic(noop),
|
|
||||||
"/music/rand/classic", HTTPClient.METHOD_GET],
|
|
||||||
[func() -> void: MusicServerClient.fetch_next_song(noop),
|
[func() -> void: MusicServerClient.fetch_next_song(noop),
|
||||||
"/music/next", HTTPClient.METHOD_GET],
|
"/music/next", HTTPClient.METHOD_GET],
|
||||||
[func() -> void: MusicServerClient.fetch_previous_song(noop),
|
[func() -> void: MusicServerClient.fetch_previous_song(noop),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const SAVED_VALUES: Dictionary = {
|
|||||||
&"hide_ticks": true, &"winning_score": 13, &"inspiration_list_speed": 4,
|
&"hide_ticks": true, &"winning_score": 13, &"inspiration_list_speed": 4,
|
||||||
&"fullscreen": true, &"last_seen_version": "1.2.3", &"theme_id": "original_web",
|
&"fullscreen": true, &"last_seen_version": "1.2.3", &"theme_id": "original_web",
|
||||||
&"reduce_animations": true,
|
&"reduce_animations": true,
|
||||||
|
&"short_songs": true, &"short_song_seconds": 45,
|
||||||
}
|
}
|
||||||
|
|
||||||
var _snapshot: AutoloadSnapshot
|
var _snapshot: AutoloadSnapshot
|
||||||
@@ -93,7 +94,7 @@ func test_statistics_settings_are_not_saved() -> void:
|
|||||||
|
|
||||||
var file: ConfigFile = _saved_file()
|
var file: ConfigFile = _saved_file()
|
||||||
assert_bool(file.has_section_key("quiz", "add_to_stats")).is_false()
|
assert_bool(file.has_section_key("quiz", "add_to_stats")).is_false()
|
||||||
assert_bool(file.has_section_key("quiz", "use_low_played_mode")).is_false()
|
assert_bool(file.has_section_key("quiz", "random_mode")).is_false()
|
||||||
|
|
||||||
|
|
||||||
func test_queue_ids_are_saved_and_load_back() -> void:
|
func test_queue_ids_are_saved_and_load_back() -> void:
|
||||||
|
|||||||
Reference in new issue
Block a user