-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmusicplayercc.lua
485 lines (387 loc) · 14.8 KB
/
musicplayercc.lua
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
local dfpwm = require("cc.audio.dfpwm")
local speaker = peripheral.find("speaker")
-- Initial Boot Setup Script
local basaltExists = fs.exists("basalt.lua")
local musicDir = "/music"
local configFilePath = "/config.txt"
local defaultConfig = {
chunkSize = 1024*6, -- Default chunk size
}
local function downloadAndRunInstaller(url, ...)
local args = {...} -- Capture any additional arguments to pass to the script
-- Use http.get to fetch the script
local response = http.get(url)
if not response then
error("Failed to download the installer script")
end
local scriptContent = response.readAll()
response.close()
-- Use loadstring to load the script content.
-- Note: loadstring is available in Lua 5.1, but in newer versions, load may be used.
local script, errorMessage = loadstring(scriptContent)
if not script then
error("Failed to load the installer script: " .. errorMessage)
end
-- Execute the loaded script, passing any additional arguments
script(table.unpack(args))
end
-- Function to check and install Basalt
local function installBasalt()
if not basaltExists then
print("Basalt not found. Attempting to download...")
downloadAndRunInstaller("https://basalt.madefor.cc/install.lua", "release", "latest.lua")
print("Basalt installed successfully.")
else
print("Basalt is already installed.")
end
end
-- Function to create the music directory if it doesn't exist
local function createMusicDirectory()
if not fs.exists(musicDir) then
fs.makeDir(musicDir)
print("Music directory created.")
else
print("Music directory already exists.")
end
end
-- Function to create or update the configuration file
local function createOrUpdateConfig()
local configData = textutils.serialize(defaultConfig)
local configFile = fs.open(configFilePath, "w")
configFile.write(configData)
configFile.close()
print("Configuration file created/updated.")
end
-- Main function to run the initial setup
local function runInitialSetup()
installBasalt()
createMusicDirectory()
createOrUpdateConfig()
if basaltExists == false then
term.clear()
print("Initial setup completed.")
print("Program is terminating to allow user upload of music.")
print("Use the /music/ folder created for music storage.")
print("Sub folders are supported to allow for user organization")
print("Thank you for using this program and enjoy!")
print()
print("-coppertj")
os.sleep(5)
error()
end
end
-- Execute the initial setup
runInitialSetup()
-- Ensure these are initialized at the start of your script
local basalt = require("basalt")
local isPaused = false
local lastChunkData = nil
local currentPosition = 1
local playbackStarted = false
local trackStartTime = 0
local backPressedOnce = false
local basalt = require("basalt")
local breakit = false
local main = basalt.createFrame()
local screenWidth, screenHeight = main:getSize()
local numBars = 16 -- Number of bars in the visualizer
local barWidth = 1 -- Width of each bar (1 character)
local visualizerHeight = 10 -- Max height of the visualizer
local visualizerStartX = math.floor((screenWidth - (numBars * barWidth)) / 2)
local visualizerStartY = screenHeight - visualizerHeight - 5 -- Position above the bottom buttons
local buttonWidth = screenWidth / 4
local buttonHeight = 3 -- Height of buttons
local spacing = (screenWidth - (buttonWidth * 3)) / 4
local startY = screenHeight - buttonHeight - 1 -- Y position for buttons
local returnToMenu = false
-- Define rootFolder where your music files are located
local rootFolder = "/music/"
local shuffleEnabled = false
-- Function to read the contents of a folder and return the filenames as a table
function readFolderContents(folderPath)
local fileNames = {}
for _, file in ipairs(fs.list(folderPath)) do
table.insert(fileNames, file)
end
return fileNames
end
-- Shuffle State Label
local shuffleStateLabel = main:addLabel()
:setText("Shuffle: Off")
:setPosition(12, 2) -- Adjust to position next to the shuffle button
:setSize(15, 1)
local function updateShuffleStateLabel()
if shuffleEnabled then
shuffleStateLabel:setText("Shuffle: On")
else
shuffleStateLabel:setText("Shuffle: Off")
end
-- Refresh the frame to show the updated label text if necessary
end
local function onShufflePressed()
speaker.playSound("ui.button.click", 0.5)
shuffleEnabled = not shuffleEnabled
updateShuffleStateLabel()
end
-- Assuming you have variables for spacing and startY like before
-- Adjust these values as needed to position your shuffle button
local shuffleButtonX = 10 -- Adjust based on your layout, for example, placing it at the top left
local shuffleButtonY = 20 -- Adjust based on your layout, perhaps just below the top edge
-- Initialize fileList using the readFolderContents function
-- File Explorer
local mainFrame = basalt.createFrame("firstBaseFrame"):show()
local fileList = mainFrame:addList()
:setPosition(2, 2)
:setSize(20, 15) -- Adjust size as needed
local function updateFileList(path)
fileList:clear() -- Clear the list for new directory contents
local files = fs.list(path) -- Use the passed 'path' parameter
for _, file in ipairs(files) do
fileList:addItem(file) -- Add each file or directory to the list
end
end
fileList:onSelect(function(self, event, item)
if item and item.text then
local selectedFileName = item.text
local selectedItemPath = fs.combine(rootFolder, selectedFileName)
if fs.isDir(selectedItemPath) then
-- Navigate into the directory
rootFolder = selectedItemPath .. "/" -- Update the rootFolder to the new path
updateFileList(rootFolder) -- Update the list to show contents of the selected directory
else
-- Handle file selection
basalt.setActiveFrame(main) -- Switch to the playback frame
parallel.waitForAny(
function() mainLoop(selectedItemPath) end, -- Play the selected file
waitForUserInput -- Continue listening for user input
)
end
end
end)
-- Add any additional UI elements like a back button here
local function togglePauseState()
speaker.playSound("ui.button.click", 0.5)
isPaused = not isPaused
end
local function onPlaybackPressed()
speaker.playSound("ui.button.click", 0.5)
local currentTime = os.clock()
if backPressedOnce and (currentTime - trackStartTime) <= 3 then
-- If back button is pressed twice within 3 seconds, go to the previous song
currentPosition = math.max(1, currentPosition - 1)
backPressedOnce = false -- Reset the flag
else
-- If pressed once or after 3 seconds, restart the current song or go to the previous song
if (currentTime - trackStartTime) > 3 then
-- Consider as a request to go back if beyond the threshold
currentPosition = math.max(1, currentPosition - 1)
end
backPressedOnce = true -- Set the flag for the first press
trackStartTime = currentTime -- Update start time to prevent immediate trigger
end
breakit = true
-- Exit to restart the current song or go to the previous song
end
local playedTracks = {}
local fileLister = readFolderContents(rootFolder)
function shuffleTrack()
-- Reset shuffle if all tracks have been played
if tablelength(playedTracks) >= #fileLister then
playedTracks = {} -- Clear played tracks for reshuffle
-- Optional: Return to the menu or start shuffle again
end
local nextTrack
repeat
nextTrack = math.random(1, #fileLister)
until not playedTracks[nextTrack]
playedTracks[nextTrack] = true
currentPosition = nextTrack -- Update current position for shuffle track
return true
end
local function onSkipPressed()
speaker.playSound("ui.button.click", 0.5)
-- If shuffle is not enabled, simply move to the next track in order
if currentPosition > #fileLister then
currentPosition = 1 -- Optionally loop back to the start or stop playback
end
breakit = true
end
local bars = {}
for i = 1, numBars do
local xPosition = visualizerStartX + (i - 1) * barWidth
bars[i] = main:addLabel()
:setPosition(xPosition, visualizerStartY)
:setSize(barWidth, visualizerHeight)
:setText("|")
end
local labelYPosition = visualizerStartY + 7 -- Position the label above the visualizer, adjust as needed
local trackLabel = main:addLabel()
:setPosition(1, labelYPosition)
:setSize(screenWidth, 5)
:setText("")
local function onMainMenuButtonPressed()
speaker.playSound("ui.button.click", 0.5)
breakit = true
returnToMenu = true
rootFolder = "/music/"
updateFileList(rootFolder)
basalt.setActiveFrame(mainFrame)
mainFrame:show()
main:hide()
basalt.update()
-- Initialize the list with contents from the starting directory
end
function waitForUserInput()
breakit = false
returnToMenu = false
basalt.setActiveFrame(main)
main:show()
mainFrame:hide()
basalt.update()
while true do
if returnToMenu == true then
basalt.setActiveFrame(mainFrame)
main:show()
mainFrame:hide()
basalt.update()
-- Initialize the list with contents from the starting directory
break
end
-- Creating buttons
-- Create the main menu button at the top left corner of the screen
local mainMenuButton = main:addButton()
:setText("=")
:setPosition(1, 1) -- Top left corner
:setSize(3, 1) -- Adjust size as needed
:onClick(onMainMenuButtonPressed)
-- Create the shuffle button with a smaller size
local shuffleButton = main:addButton()
:setText("Shuffle")
:setPosition(shuffleButtonX, shuffleButtonY)
:setSize(7, 1) -- Smaller width and standard height for a single character
:onClick(onShufflePressed)
-- Playback Button
local playbackButton = main:addButton()
:setText("<")
:setPosition(spacing * 2, startY)
:setSize(buttonWidth, buttonHeight)
:onClick(onPlaybackPressed)
-- Pause Button
local pauseButton = main:addButton()
:setText("||")
:setPosition(spacing * 3 + buttonWidth, startY)
:setSize(buttonWidth, buttonHeight)
:onClick(togglePauseState)
-- Skip Button
local skipButton = main:addButton()
:setText(">")
:setPosition(spacing * 4 + buttonWidth * 2, startY)
:setSize(buttonWidth, buttonHeight)
:onClick(onSkipPressed)
for i = 1, numBars do
local height
if isPaused then
height = 1 -- Set height to 0 to clear the bars if paused
else
height = math.random(1, visualizerHeight) -- Otherwise, set a random height
end
local barText = string.rep("|", height) .. string.rep(" ", visualizerHeight - height)
bars[i]:setText(barText)
end
local ev = table.pack(os.pullEventRaw())
basalt.update(table.unpack(ev))
end
end
local function updateShuffleStateLabel()
if shuffleEnabled then
shuffleStateLabel:setText("Shuffle: On")
else
shuffleStateLabel:setText("Shuffle: Off")
end
-- Refresh the frame to show the updated label text if necessary
end
-- Updated function to play a DFPWM file, incorporating pause logic
function playSound(filePath)
breakit = false
trackStartTime = os.clock()
local file = fs.open(filePath, "rb")
if not file then
return
end
local decoder = dfpwm.make_decoder()
local chunkData, buffer
local fileName = filePath:match("^.+/(.+)$") or filePath -- Pattern extracts the name after the last slash
trackLabel:setText(fileName) -- Update the label text with the current file name
while true do
if breakit == true then break end
if isPaused then
os.sleep(0.5) -- Simple pause handling
else
chunkData = file.read(1024*16)
if not chunkData then break end -- End of file
buffer = decoder(chunkData)
while not speaker.playAudio(buffer) do
if isPaused then
lastChunkData = chunkData -- Save the chunk data if paused mid-playback
break
end
os.pullEvent("speaker_audio_empty")
end
end
end
file.close()
end
-- Main playback loop
-- Initialize playedTracks outside the main loop to track played files across shuffle iterations
local playedTracks = {}
-- Main playback loop
function mainLoop(initialTrackPath)
fileLister = readFolderContents(rootFolder)
if initialTrackPath then
-- Directly play the provided track
playSound(initialTrackPath)
-- Find and set currentPosition for the next track
for index, fileName in ipairs(fileLister) do
local filePath = fs.combine(rootFolder, fileName)
if filePath == initialTrackPath then
currentPosition = index + 1
break
end
end
end
-- Check if shuffle is enabled and reset playedTracks if necessary
if shuffleEnabled and #playedTracks == #fileLister then
playedTracks = {} -- Reset after all tracks have been played
end
while currentPosition <= #fileLister do
if returnToMenu then break end
local fileName = fileLister[currentPosition]
local filePath = fs.combine(rootFolder, fileName) -- Correctly combine paths
if shuffleEnabled then
local nextTrack
repeat
nextTrack = math.random(1, #fileLister)
until not playedTracks[nextTrack] or #playedTracks == #fileLister
playedTracks[nextTrack] = true
currentPosition = nextTrack
filePath = fs.combine(rootFolder, fileLister[currentPosition]) -- Update filePath for shuffle
playSound(filePath)
else
playSound(filePath)
currentPosition = currentPosition + 1
end
if currentPosition > #fileLister and not shuffleEnabled then
breakit = true
returnToMenu = true
basalt.setActiveFrame(mainFrame)
mainFrame:show()
main:hide()
basalt.update()
-- Initialize the list with contents from the starting directory
updateFileList("/music/")
break
end
end
end
updateFileList(rootFolder)
basalt.autoUpdate()