aboutsummaryrefslogtreecommitdiff
path: root/game/src/Game/MusicConductor/MusicConductor.gd
blob: 7103aff1e08e98ad8dc29d768837ea212361eb02 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
extends Node

signal song_paused(paused : bool)
signal song_started(track_id : int)
## Only triggers when song naturally ends
signal song_finished(track_id : int)
signal song_scrubbed(percentage : float, seconds : float)

# REQUIREMENTS
# * SS-67
@export_dir var music_directory : String
@export var first_song_name : String

@export var _audio_stream_player : AudioStreamPlayer

var _selected_track := 0
var _available_songs : Array[SongInfo] = []
var _auto_play_next_song : bool = true

var playlist: Array[int] = []
var playlist_index:int = 0
var preferred_playlist_len: int = 7
var last_played: int = -1


## True if music player should be visible.
## Used to keep keep consistency between scene changes
var is_music_player_visible : bool = true

var _has_startup_happened : bool = false

func get_all_song_names() -> PackedStringArray:
   var songNames : PackedStringArray = []
   for si : SongInfo in _available_songs:
      songNames.append(si.song_name)
   return songNames
   
func get_all_song_paths() -> PackedStringArray:
   var songPaths : PackedStringArray = []
   for si : SongInfo in _available_songs:
      songPaths.append(si.song_path)
   return songPaths

func get_current_song_index() -> int:
   return _selected_track

func get_current_song_name() -> String:
   return _available_songs[_selected_track].song_name

func scrub_song_by_percentage(percentage: float) -> void:
   var percentInSeconds : float = (percentage / 100.0) * _audio_stream_player.stream.get_length()
   _audio_stream_player.play(percentInSeconds)
   song_scrubbed.emit(percentage, percentInSeconds)

func get_current_song_progress_percentage() -> float:
   return 100 * (_audio_stream_player.get_playback_position() / _audio_stream_player.stream.get_length())

func is_paused() -> bool:
   return _audio_stream_player.stream_paused

func set_paused(paused : bool) -> void:
   _audio_stream_player.stream_paused = paused
   song_paused.emit(paused)

func toggle_play_pause() -> void:
   set_paused(not is_paused())

func start_current_song() -> void:
   _audio_stream_player.stream = _available_songs[_selected_track].song_stream
   _audio_stream_player.play()
   song_started.emit(_selected_track)

# REQUIREMENTS
# * SS-70
func start_song_by_index(id: int) -> void:
   _selected_track = id
   start_current_song()

# REQUIREMENTS
# * SS-69
func select_next_song() -> void:
   #_selected_track = (_selected_track + 1) % len(_available_songs)
   if playlist_index >= preferred_playlist_len or playlist_index >= len(playlist):
      generate_playlist()
      playlist_index = 0
   _selected_track = playlist[playlist_index]
   playlist_index += 1
   last_played = playlist_index
   start_current_song()

func select_previous_song() -> void:
   _selected_track = (len(_available_songs) - 1) if (_selected_track == 0) else (_selected_track - 1)
   start_current_song()

func setup_compat_song(file_name) -> void:
   var song = SongInfo.new()
   var stream = SoundSingleton.get_song(file_name)

   var metadata = MusicMetadata.new()
   metadata.set_from_stream(stream)
   var title = metadata.title
   if title == "":
      #use the file name without the extension if there's no metadata
      title = file_name.split(".")[0]
   song.init_stream(file_name,title,stream)
   _available_songs.append(song)
   
func add_compat_songs() -> void:
   for file_name : String in SoundSingleton.song_list:
      setup_compat_song(file_name)

func add_ootb_music() -> void:
   var dir := DirAccess.open(music_directory)
   for fname : String in dir.get_files():
      if fname.ends_with(".import"):
         fname = fname.get_basename()
         if fname.get_basename() == first_song_name:
            _selected_track = _available_songs.size()
         var song = SongInfo.new()
         song.init_file_path(music_directory, fname)
         _available_songs.append(song)

func generate_playlist() -> void:
   var song_names = MusicConductor.get_all_song_paths()
   var possible_indices = range(len(song_names)-1)
   
   var title_index = song_names.find(SoundSingleton.title_theme)
   possible_indices.remove_at(title_index)
   
   var actual_playlist_len = min(preferred_playlist_len,len(possible_indices))
   
   #if the playlist size is too large or small, make it the same size as what we 
   #need to support
   if len(playlist) != actual_playlist_len:
      playlist.resize(actual_playlist_len)
      playlist.fill(0)

   #The song we just played can be in the playlist, just not the first one
   if last_played != -1:
      possible_indices.remove_at(last_played)
   
   #essentially shuffle-bag randomness, picking from a list of song indices
   for i in range(actual_playlist_len):
      var ind = randi_range(0,len(possible_indices)-1)
      #add back the last song we just played as an option
      if i==2:
         possible_indices.append(last_played)
      
      playlist[i] = possible_indices[ind]
      possible_indices.remove_at(ind)

# REQUIREMENTS
# * SND-2, SND-3
func _ready() -> void:
   add_ootb_music()
   #don't start the current song for compat mode, do that from
   #GameStart so we can wait until the music is loaded

func set_startup_music(play : bool) -> void:
   if not _has_startup_happened:
      _has_startup_happened = true
      set_paused(not play)

func _on_audio_stream_player_finished() -> void:
   song_finished.emit(_selected_track)
   if _auto_play_next_song:
      select_next_song()
      start_current_song()