· 5 years ago · Jan 19, 2021, 05:44 PM
1-- VARIABLES --
2
3local indexURL = "https://raw.githubusercontent.com/RubenHetKonijn/computronics-songs/main/index.json?cb=" .. os.epoch("utc")
4
5local applicationName = "Musicify"
6local version = 0.2
7
8local backGroundColor = colors.black
9
10local headerTextColor = colors.green
11local headerOffset = 0
12
13local tableTextColor = colors.yellow
14local musicTextColor = colors.white
15
16local selectedColor = colors.blue
17local playingColor = colors.green
18
19local footerBackGroundColor = colors.white
20local footerTextColor = colors.black
21
22local parentRowPosition = 2
23
24local trackRowPosition = parentRowPosition + 1
25local songRowPosition = parentRowPosition + 8
26local authorRowPosition = parentRowPosition + 25
27local timeRowPosition = parentRowPosition + 40
28
29local args = {...}
30local musicify = {}
31
32local tape = peripheral.find("tape_drive")
33local screenWidth, screenHeight = term.getSize()
34local halfScreen = screenWidth / 2
35
36local currentSong = 0
37local selection = 0
38local textScroll = 0
39local maxTextScroll = 0
40local scroll = 0
41
42-- BUSINESS LAYER --
43
44if not tape then
45 print("ERROR: Tapedrive not found")
46end
47
48local handle = http.get(indexURL)
49local indexJSON = handle.readAll()
50handle.close()
51local index = textutils.unserialiseJSON(indexJSON)
52if not index then
53 print("ERROR: The index is malformed.")
54 return
55end
56
57local function getSongID(songname)
58for i in pairs(index.songs) do
59 if index.songs[i].name == songname then
60 return i
61 end
62 end
63end
64
65local function wipe()
66 local k = tape.getSize()
67 tape.stop()
68 tape.seek(-k)
69 tape.stop()
70 tape.seek(-90000)
71 local s = string.rep("\xAA", 8192)
72 for i = 1, k + 8191, 8192 do
73 tape.write(s)
74 end
75 tape.seek(-k)
76 tape.seek(-90000)
77end
78
79local function play(songID)
80 print("Playing " .. getSongID(songID.name) .. " | " .. songID.author .. " - " .. songID.name)
81 wipe()
82 tape.stop()
83 tape.seek(-tape.getSize()) -- go back to the start
84
85 local h = http.get(songID.file, nil, true) -- write in binary mode
86 tape.write(h.readAll()) -- that's it
87 h.close()
88
89 tape.seek(-tape.getSize()) -- back to start again
90
91 tape.setSpeed(songID.speed)
92 while tape.getState() ~= "STOPPED" do
93 sleep(1)
94 end
95 tape.play()
96end
97
98local function update()
99 local s = shell.getRunningProgram()
100 handle = http.get("https://raw.githubusercontent.com/RubenHetKonijn/musicify/main/musicify.lua")
101 if not handle then
102 print("Could not download new version, Please update manually.")
103 else
104 data = handle.readAll()
105 local f = fs.open(s, "w")
106 handle.close()
107 f.write(data)
108 f.close()
109 shell.run(s)
110 return
111 end
112end
113
114if version < index.latestVersion then
115 print("Client outdated, Updating Musicify.") -- Update check
116 update()
117 return
118end
119
120musicify.help = function (arguments)
121 print([[
122Usage: <action> [arguments]
123Actions:
124musicify
125 help -- Displays this message
126 list -- Displays a list of song you can play
127 play <id> -- Plays the specified song by it's ID
128 shuffle [from] [to] -- Starts shuffle mode in the specified range
129 stop -- Stops playback
130 Update -- Updates musicify
131]])
132end
133
134musicify.update = function (arguments)
135 print("Updating musicify, please hold on.")
136 update()
137end
138
139musicify.stop = function (arguments)
140 print("Stopping.")
141 tape.stop()
142end
143
144musicify.list = function (arguments)
145 print("Format: `ID | Author - Name")
146 for i in pairs(index.songs) do
147 print(i .. " | " .. index.songs[i].author .. " - " .. index.songs[i].name)
148 end
149 print("(Use Mildly Better Shell if you want to scroll through the list!)")
150end
151
152musicify.shuffle = function (arguments)
153 local from = arguments[1] or 1
154 local to = arguments[2] or #index.songs
155 if tostring(arguments[1]) and not tonumber(arguments[1]) and arguments[1] then
156 print("Please specify arguments like `musicify shuffle 1 5`")
157 return
158 end
159 while true do
160 print("Currently in shuffle mode, press <CTRL>+T to exit. Use <Enter> to skip songs")
161 local ranNum = math.random(from, to)
162 play(index.songs[ranNum])
163
164 -- Wait till the end of the song
165
166 local function songLengthWait()
167 sleep(index.songs[ranNum].time)
168 end
169
170 local function keyboardWait()
171 while true do
172 local event, key = os.pullEvent("key")
173 if key == keys.enter then
174 print("Skipping!")
175 break
176 end
177 end
178 end
179
180 parallel.waitForAny(songLengthWait,keyboardWait)
181 end
182end
183
184musicify.volume = function (arguments)
185 if not arguments[1] or not tonumber(arguments[1]) or tonumber(arguments[1])>100 or tonumber(arguments[1]) < 1 then
186 print("Please specify a valid volume level between 0-100")
187 return
188 end
189 tape.setVolume(arguments[1] / 100)
190end
191
192musicify.play = function (arguments)
193 if not arguments then
194 print("Resuming playback...")
195 return
196 end
197 if not tonumber(arguments[1]) or not index.songs[tonumber(arguments[1])] then
198 print("Please provide a valid track ID. Use `list` to see all valid track numbers.")
199 return
200 end
201 if not tape.isReady() then
202 print("ERROR: You need to have a tape in the tape drive")
203 return
204 end
205 play(index.songs[tonumber(arguments[1])])
206 tape.play()
207end
208
209musicify.info = function (arguments)
210 print("Current version: " .. version)
211 print("Latest version: " .. index.latestVersion)
212end
213
214musicify.loop = function (arguments)
215 if tostring(arguments[1]) and not tonumber(arguments[1]) then
216 print("ERROR: Please specify a song ID")
217 return
218 end
219 while true do
220 play(index.songs[tonumber(arguments[1])])
221 sleep(index.songs[tonumber(arguments[1])].time)
222 end
223end
224
225command = table.remove(args, 1)
226
227if command == "musicify" then
228 drawGUI()
229elseif not command then
230 print("Please provide a valid command. For usage, use `musicify help`.")
231else
232 musicify[command](args)
233end
234
235-- VISUAL LAYER --
236
237local function secondsToClock(seconds)
238 if seconds <= 0 then
239 return "00:00:00";
240 else
241 mins = string.format("%02.f", math.floor(seconds/60));
242 secs = string.format("%02.f", math.floor(seconds - mins *60));
243 return mins..":"..secs
244 end
245end
246
247local function checkInput()
248 while true do
249 local event, key = os.pullEvent()
250
251 if event == "key" then
252 if key == 208 and selection < #index.songs then
253 if selection - scroll >= screenHeight -3 then
254 scroll = scroll +1
255 end
256
257 selection = selection +1
258 maxTextScroll = string.len(index.songs[selection].name) -13
259 textScroll = 0
260
261 elseif key == 200 and selection > 1 then
262 if selection - scroll <= 1 and scroll > 0 then
263 scroll = scroll -1
264 end
265
266 selection = selection -1
267 maxTextScroll = string.len(index.songs[selection].name) -13
268 textScroll = 0
269
270 elseif key == 28 then
271 play(index.songs[selection])
272 currentSong = selection
273 end
274 end
275 end
276end
277
278local function drawHeader()
279 term.setBackgroundColor(backGroundColor)
280 term.setTextColor(headerTextColor)
281 term.clear()
282
283 term.setCursorPos(halfScreen - (string.len(applicationName) / 2) + headerOffset, 1)
284
285 print(applicationName)
286end
287
288local function drawMusicList()
289 term.setBackgroundColor(backGroundColor)
290 term.setTextColor(tableTextColor)
291
292 term.setCursorPos(trackRowPosition, 2)
293 term.write("Track")
294 term.setCursorPos(timeRowPosition, 2)
295 term.write("Duration")
296 term.setCursorPos(songRowPosition, 2)
297 term.write("Name")
298 term.setCursorPos(authorRowPosition, 2)
299 term.write("Author")
300
301 term.setTextColor(musicTextColor)
302
303 for i in pairs(index.songs) do
304 if i < screenHeight -2 then
305 local track = i + scroll
306
307 term.setCursorPos(1, i +2)
308
309 -- Change the color of the selectoins
310 if selection - scroll == i then
311 term.setBackgroundColor(selectedColor)
312 elseif track == currentSong then
313 term.setBackgroundColor(playingColor)
314 else
315 term.setBackgroundColor(backGroundColor)
316 end
317
318 term.setCursorPos(trackRowPosition, i + 2)
319 term.write(track)
320
321 term.setCursorPos(timeRowPosition, i + 2)
322 term.write(secondsToClock(index.songs[track].time))
323
324 term.setCursorPos(songRowPosition, i + 2)
325 if string.len(index.songs[track].name) < 15 then
326 term.write(index.songs[track].name)
327 elseif selection - scroll == i or track == currentSong then
328 -- if string.len(string.sub(index.songs[track].name, textScroll, textScroll + 12)) < 12 then
329 term.write(string.sub(index.songs[track].name, textScroll +1, textScroll + 12) .. '...')
330 -- end
331 else
332 term.write(string.sub(index.songs[track].name, 0, 12) .. '...')
333 end
334
335 term.setCursorPos(authorRowPosition, i + 2)
336 if string.len(index.songs[track].author) < 12 then
337 term.write(index.songs[track].author)
338 elseif selection - scroll == i or track == currentSong then
339 -- if string.len(string.sub(index.songs[track].author, textScroll, textScroll + 9)) > 9 then
340 term.write(string.sub(index.songs[track].author, textScroll +1, textScroll + 9) .. '...')
341 -- end
342 else
343 term.write(string.sub(index.songs[track].author, 0, 9) .. '...')
344 end
345 else
346 break
347 end
348 end
349end
350
351local function drawFooter()
352 term.setBackgroundColor(footerBackGroundColor)
353 term.setTextColor(footerTextColor)
354
355 term.setCursorPos(1, screenHeight)
356
357 -- If this is somehow possible with the tape mod api
358 if currentSong == 0 then
359 term.write("Play")
360 else
361 term.write("Stop")
362 end
363
364 term.setCursorPos(halfScreen - 4, screenHeight)
365
366 term.write("Shuffle")
367end
368
369local function tick()
370 while true do
371 textScroll = textScroll +1
372
373 if textScroll > maxTextScroll then
374 sleep(1)
375 textScroll = 0
376 end
377 sleep(0.4)
378 end
379end
380
381local function drawGUI()
382 while true do
383 drawHeader()
384 drawMusicList()
385 drawFooter()
386 sleep()
387 end
388end
389
390parallel.waitForAny(drawGUI,checkInput,tick)