1
- local music = {}
1
+ local music = {
2
+ PLAYLIST = {},
3
+ PLAYLIST_ID = - 1 ,
4
+ PLAYING = nil ,
5
+ FINISHED = true ,
6
+ LOOP = false ,
7
+ USE_WEIGHTS = false ,
8
+ TRACK_NUMBER = {},
9
+ }
2
10
3
11
local log = require (" log" )
4
12
local melee = require (" melee" )
@@ -8,30 +16,25 @@ local wav = require("wav")
8
16
9
17
require (" extensions.math" )
10
18
11
- local STAGE_TRACKS = {}
12
- local STAGE_ID = - 1
13
- local PLAYING_SONG = nil
14
- local SOURCE_SONG_LOOPS = {}
15
- local SONG_FINISHED_PLAYING = true
16
- local SONG_SHOULD_LOOP = false
17
-
18
19
-- Given a list of {element, weight} pairs, do a weighted random sample
19
20
-- of the elements (returns an index, not the element itself)
20
21
local function weightedRandomChoice (list )
21
22
if not list or # list == 0 then return nil end
22
23
if # list == 1 then return 1 end
23
- local weight_sum = 0
24
- for idx = 1 ,# list do
25
- weight_sum = weight_sum + list [idx ].weight
24
+
25
+ local sum = 0
26
+ for i = 1 , # list do
27
+ sum = sum + list [i ].WEIGHT
26
28
end
27
- local choice = math.random (1 , weight_sum )
29
+
30
+ local choice = math.random (1 , sum )
28
31
for idx = 1 ,# list do
29
- choice = choice - list [idx ].weight
32
+ choice = choice - list [idx ].WEIGHT
30
33
if choice <= 0 then
31
34
return idx
32
35
end
33
36
end
34
- return choice_idx
37
+ return choice
35
38
end
36
39
37
40
local function moveFolderContentsTo (from , to )
@@ -51,37 +54,37 @@ local function moveFolderContentsTo(from, to)
51
54
end
52
55
53
56
function music .update ()
54
- if not PLAYING_SONG or SONG_FINISHED_PLAYING then return end
57
+ if not music . PLAYING or music . FINISHED then return end
55
58
56
- local duration = PLAYING_SONG :getDuration (" samples" )
57
- local position = PLAYING_SONG :tell (" samples" )
59
+ local duration = music . PLAYING . STREAM :getDuration (" samples" )
60
+ local position = music . PLAYING . STREAM :tell (" samples" )
58
61
59
- if SONG_SHOULD_LOOP then
60
- local finished = not PLAYING_SONG :isPlaying ()
62
+ if music . LOOP then
63
+ local finished = not music . PLAYING . STREAM :isPlaying ()
61
64
62
65
if finished then
63
66
-- If the song is no longer playing, that means it reached the end
64
67
-- Immediately play it again
65
68
log .debug (" [MUSIC] End of song reached, replaying" )
66
- PLAYING_SONG :play ()
69
+ music . PLAYING . STREAM :play ()
67
70
end
68
71
69
- local info = SOURCE_SONG_LOOPS [ PLAYING_SONG ]
72
+ local info = music . PLAYING . WAV
70
73
71
74
if info then
72
75
for k , loop in pairs (info .loops ) do
73
76
if finished or position >= loop .sample_end then
74
77
log .debug (" [MUSIC] Loop point reached, seeking to %d" , loop .sample_start )
75
78
-- If the song finished playing or reached the looping point, loop back to the start?
76
- PLAYING_SONG :seek (loop .sample_start , " samples" )
79
+ music . PLAYING . STREAM :seek (loop .sample_start , " samples" )
77
80
break -- Only handle the first loop for now..
78
81
end
79
82
end
80
83
end
81
84
else
82
- if not PLAYING_SONG :isPlaying () or position >= duration then
85
+ if not music . PLAYING . STREAM :isPlaying () or position >= duration then
83
86
-- We mark that the song has completed, allowing the next game frame hook to play the next song in the playlist
84
- SONG_FINISHED_PLAYING = true
87
+ music . FINISHED = true
85
88
end
86
89
end
87
90
end
@@ -137,9 +140,9 @@ function music.init()
137
140
end
138
141
139
142
function music .kill ()
140
- if PLAYING_SONG and PLAYING_SONG :isPlaying () then
141
- PLAYING_SONG :stop ()
142
- SONG_FINISHED_PLAYING = true
143
+ if music . PLAYING and music . PLAYING . STREAM :isPlaying () then
144
+ music . PLAYING . STREAM :stop ()
145
+ music . FINISHED = true
143
146
end
144
147
end
145
148
@@ -156,7 +159,7 @@ function music.shouldPlayMusic()
156
159
end
157
160
158
161
function music .onLoopChange (mode )
159
- if PLAYING_SONG and PLAYING_SONG :isPlaying () then
162
+ if music . PLAYING and music . PLAYING . STREAM :isPlaying () then
160
163
local loop = false
161
164
-- Handle the different loop settings properly
162
165
if mode == LOOPING_MENU and melee .isInMenus () then
@@ -166,8 +169,8 @@ function music.onLoopChange(mode)
166
169
elseif mode == LOOPING_ALL then
167
170
loop = true
168
171
end
169
- -- PLAYING_SONG :setLooping(loop)
170
- SONG_SHOULD_LOOP = loop
172
+ -- music.PLAYING.STREAM :setLooping(loop)
173
+ music . LOOP = loop
171
174
end
172
175
end
173
176
@@ -211,43 +214,58 @@ function music.setVolume(vol)
211
214
memory .writeByte (0x804D3887 , (vol / 100 ) * 127 )
212
215
end
213
216
214
- if PLAYING_SONG and PLAYING_SONG :isPlaying () then
215
- PLAYING_SONG :setVolume ((vol / 100 ) * (memory .match .paused and 0.35 or 1 ))
217
+ if music . PLAYING and music . PLAYING . STREAM :isPlaying () then
218
+ music . PLAYING . STREAM :setVolume ((vol / 100 ) * (memory .match .paused and 0.35 or 1 ))
216
219
end
217
220
end
218
221
219
222
function music .playNextTrack ()
220
223
if not memory .isMelee () or not PANEL_SETTINGS :PlayStageMusic () then return end
221
- if PLAYING_SONG ~= nil and not SONG_FINISHED_PLAYING then return end
222
- if not STAGE_ID then return end
224
+ if music . PLAYING ~= nil and not music . FINISHED then return end
225
+ if not music . PLAYLIST_ID then return end
223
226
if not music .shouldPlayMusic () then return end
224
227
225
- local songs = STAGE_TRACKS
228
+ local songs = music . PLAYLIST
226
229
227
- if songs and # songs > 0 then
228
- local track = weightedRandomChoice (songs )
229
- if not track or not songs [track ] then return end
230
+ if # music .PLAYLIST > 0 then
231
+ local track
230
232
231
- PLAYING_SONG = songs [track ].source
233
+ if music .USE_WEIGHTS then
234
+ track = weightedRandomChoice (songs )
235
+ if not track or not songs [track ] then return end
236
+ music .PLAYING = songs [track ]
232
237
233
- if PLAYING_SONG then
234
- if STAGE_ID == 0x0 then
238
+ else
239
+ music .TRACK_NUMBER [music .PLAYLIST_ID ] = ((music .TRACK_NUMBER [music .PLAYLIST_ID ] or - 1 ) + 1 ) % # songs
240
+ track = music .TRACK_NUMBER [music .PLAYLIST_ID ] + 1
241
+ music .PLAYING = songs [track ]
242
+
243
+ -- Every time we play a song, we randomly place it towards the start of the playlist
244
+ local newpos = math.random (1 , track )
245
+
246
+ table.remove (songs , track )
247
+ table.insert (songs , newpos , music .PLAYING )
248
+ end
249
+
250
+
251
+ if music .PLAYING then
252
+ if music .PLAYLIST_ID == 0x0 then
235
253
log .info (" [MUSIC] Playing track #%d for menu" , track )
236
254
else
237
- log .info (" [MUSIC] Playing track #%d for stage %q" , track , melee .getStageName (STAGE_ID ))
255
+ log .info (" [MUSIC] Playing track #%d for stage %q" , track , melee .getStageName (music . PLAYLIST_ID ))
238
256
end
239
257
240
258
local loop = PANEL_SETTINGS :GetMusicLoopMode ()
241
259
242
- if STAGE_ID == 0 then
243
- SONG_SHOULD_LOOP = loop == LOOPING_MENU or loop == LOOPING_ALL
260
+ if music . PLAYLIST_ID == 0 then
261
+ music . LOOP = loop == LOOPING_MENU or loop == LOOPING_ALL
244
262
else
245
- SONG_SHOULD_LOOP = loop == LOOPING_STAGE or loop == LOOPING_ALL
263
+ music . LOOP = loop == LOOPING_STAGE or loop == LOOPING_ALL
246
264
end
247
265
248
- PLAYING_SONG :setVolume ((PANEL_SETTINGS :GetVolume ()/ 100 ) * (memory .match .paused and 0.35 or 1 ))
249
- PLAYING_SONG :play ()
250
- SONG_FINISHED_PLAYING = false
266
+ music . PLAYING . STREAM :setVolume ((PANEL_SETTINGS :GetVolume ()/ 100 ) * (memory .match .paused and 0.35 or 1 ))
267
+ music . PLAYING . STREAM :play ()
268
+ music . FINISHED = false
251
269
end
252
270
end
253
271
end
325
343
memory .hook (" controller.*.buttons.pressed" , " Melee - Music skipper" , function (port , pressed )
326
344
if PANEL_SETTINGS :IsBinding () or PANEL_SETTINGS :IsSlippiReplay () then return end -- Don't skip when the user is setting a button combination or when watching a replay
327
345
local mask = PANEL_SETTINGS :GetMusicSkipMask ()
328
- if mask ~= 0x0 and port == love .getPort () and bit . band ( pressed , mask ) == mask and # STAGE_TRACKS > 1 then
346
+ if mask ~= 0x0 and port == love .getPort () and pressed == mask and # music . PLAYLIST > 1 then
329
347
log .debug (" [MUSIC] [MASK = 0x%X] Button combo pressed, stopping music." , mask )
330
348
music .kill ()
331
349
end
@@ -353,15 +371,18 @@ function music.loadStageMusicInDir(stageid, name)
353
371
loaded = loaded + 1
354
372
355
373
-- Insert the newly loaded track into a random position in the playlist
356
- local pos = math.random (1 , # STAGE_TRACKS )
374
+ local pos = math.random (1 , # music . PLAYLIST )
357
375
local prob = tonumber (string.match (filepath , " [^%._\n ]+_(%d+)%.%w+$" )) or 1
358
376
359
- table.insert (STAGE_TRACKS , pos , {source = source , weight = prob })
377
+ if prob > 1 then
378
+ music .USE_WEIGHTS = true
379
+ end
360
380
381
+ local wavinfo
361
382
if ext == " wav" then
362
- SOURCE_SONG_LOOPS [source ] = wav .parse (filepath )
363
- log .debug (" [MUSIC] Parsed %q for loop points: %d found" , file , # SOURCE_SONG_LOOPS [source ].loops )
383
+ wavinfo = wav .parse (filepath )
364
384
end
385
+ table.insert (music .PLAYLIST , pos , {STREAM = source , WEIGHT = prob , WAV = wavinfo })
365
386
else
366
387
local err = (" invalid music file \" %s/%s\" " ):format (name , file )
367
388
log .error (" [MUSIC] %s" , err )
@@ -376,19 +397,20 @@ function music.loadStageMusicInDir(stageid, name)
376
397
end
377
398
378
399
function music .loadForStage (stageid )
379
- if STAGE_ID == stageid then return end
400
+ if music . PLAYLIST_ID == stageid then return end
380
401
381
402
music .kill ()
382
403
if not memory .isMelee () or not PANEL_SETTINGS :PlayStageMusic () then return end
383
404
384
- STAGE_ID = stageid
405
+ music . PLAYLIST_ID = stageid
385
406
386
- for k ,v in pairs (STAGE_TRACKS ) do
387
- v .source :release ()
407
+ for k ,v in pairs (music . PLAYLIST ) do
408
+ v .STREAM :release ()
388
409
end
389
410
390
- PLAYING_SONG = nil
391
- STAGE_TRACKS = {}
411
+ music .PLAYING = nil
412
+ music .PLAYLIST = {}
413
+ music .USE_WEIGHTS = false
392
414
393
415
music .loadStageMusicInDir (stageid , " Melee" )
394
416
@@ -405,7 +427,7 @@ function music.loadForStage(stageid)
405
427
local sp = melee .isSinglePlayerStage (stageid )
406
428
local aka = melee .isAkaneiaStage (stageid )
407
429
408
- if not name then STAGE_ID = nil return end
430
+ if not name then music . PLAYLIST_ID = nil return end
409
431
410
432
if sp then
411
433
music .loadStageMusicInDir (stageid , (" Melee/Single Player Music/%s" ):format (name )) -- Load everything in the stage specific folder
0 commit comments