XEXModz

Music for Minecraft CC:Tweaked computer.

Nov 13th, 2025 (edited)
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.05 KB | Gaming | 0 0
  1. local dfpwm = require("cc.audio.dfpwm")
  2. local speakers = { peripheral.find("speaker") }
  3. if #speakers == 0 then error("No speakers attached") end
  4.  
  5. -- Terminal setup
  6. local mon = peripheral.find("monitor")
  7. if mon and not pocket then
  8. term.redirect(mon)
  9. if mon.setTextScale then mon.setTextScale(1) end
  10. mon.clear()
  11. end
  12.  
  13. term.setBackgroundColor(colors.black)
  14. term.setTextColor(colors.white)
  15. term.clear()
  16.  
  17. -- ===== Songs setup =====
  18. local songIndexUrl = "https://raw.githubusercontent.com/XEXModz/CC-Tweaked_Music_Player/refs/heads/main/index.txt"
  19. local songNames = textutils.unserialize(http.get(songIndexUrl).readAll())
  20. local songs = {}
  21. for i, name in ipairs(songNames) do
  22. table.insert(songs, {
  23. name = name,
  24. url = "https://raw.githubusercontent.com/XEXModz/CC-Tweaked_Music_Player/refs/heads/main/" .. name:gsub(" ", "%%20") .. ".dfpwm"
  25. })
  26. end
  27.  
  28. -- ===== Playback state =====
  29. local currentSong = nil
  30. local playing = false
  31. local stopFlag = false
  32. local shuffle = false
  33. local loopMode = 0
  34. local volume = 1.5
  35. local decoder = dfpwm.make_decoder()
  36. local currentPage = 1
  37. local width, height = term.getSize()
  38. local topRows = 2
  39. local bottomRows = 5
  40. local songsPerPage = height - topRows - bottomRows
  41.  
  42. local buttons = {}
  43.  
  44. -- ===== UI =====
  45. local function totalPages()
  46. return math.max(1, math.ceil(#songs / songsPerPage))
  47. end
  48.  
  49. local function drawUI()
  50. term.setBackgroundColor(colors.black)
  51. term.setTextColor(colors.white)
  52. term.clear()
  53. term.setCursorPos(2,1)
  54. term.write("Now Playing: " .. (currentSong and currentSong.name or "(none)"))
  55.  
  56. local startIdx = (currentPage-1)*songsPerPage + 1
  57. local y = 3
  58. for i=startIdx, math.min(startIdx+songsPerPage-1, #songs) do
  59. term.setCursorPos(2,y)
  60. term.setTextColor((currentSong==songs[i]) and colors.yellow or colors.white)
  61. term.write(songs[i].name)
  62. y = y + 1
  63. end
  64.  
  65. buttons = {}
  66. local btnLines = {
  67. {"Shuffle: "..(shuffle and "On" or "Off"), "Loop: "..({[0]="Off",[1]="All",[2]="One"})[loopMode]},
  68. {"Page "..currentPage.."/"..totalPages(), "Prev","Next"},
  69. {(playing and "Playing" or "Stopped"), "Skip"},
  70. {"-","Volume: "..math.floor(volume/3*100).."%","+",""}
  71. }
  72.  
  73. local startY = height - bottomRows + 1
  74. for lineIdx, line in ipairs(btnLines) do
  75. local x = 2
  76. local lineY = startY + lineIdx - 1
  77. for _, btn in ipairs(line) do
  78. if #btn>0 then
  79. term.setCursorPos(x, lineY)
  80. term.setBackgroundColor(colors.gray)
  81. term.setTextColor(colors.white)
  82. term.write(" "..btn.." ")
  83. table.insert(buttons,{line=lineIdx, text=btn, x1=x, x2=x+#btn+1})
  84. x = x + #btn + 3
  85. end
  86. end
  87. end
  88. end
  89.  
  90. -- ===== Playback loop =====
  91. -- Per docs: speaker buffers ONE playAudio call at a time. Track which speakers
  92. -- have accepted the current buffer so we don't re-call playAudio on them.
  93. local function playerLoop()
  94. while true do
  95. if currentSong and playing then
  96. -- Fetch song data BEFORE entering the audio loop so HTTP latency
  97. -- doesn't block playback timing.
  98. local response = http.get(currentSong.url)
  99. if not response then
  100. playing = false
  101. drawUI()
  102. else
  103. local songData = response.readAll()
  104. response.close()
  105. local dataLen = #songData
  106.  
  107. for i = 1, dataLen, 16*1024 do
  108. if stopFlag then break end
  109. local chunk = songData:sub(i, math.min(i+16*1024-1, dataLen))
  110. local buffer = decoder(chunk)
  111.  
  112. -- Track acceptance per-speaker so we don't waste calls
  113. local accepted = {}
  114. local pending = #speakers
  115.  
  116. while pending > 0 and not stopFlag do
  117. for idx, spk in ipairs(speakers) do
  118. if not accepted[idx] then
  119. if spk.playAudio(buffer, volume) then
  120. accepted[idx] = true
  121. pending = pending - 1
  122. end
  123. end
  124. end
  125. if pending > 0 then
  126. os.pullEvent("speaker_audio_empty")
  127. end
  128. end
  129. end
  130.  
  131. if stopFlag then
  132. for _, spk in ipairs(speakers) do spk.stop() end
  133. stopFlag = false
  134. else
  135. if loopMode == 2 then
  136. -- loop current
  137. elseif shuffle then
  138. currentSong = songs[math.random(#songs)]
  139. elseif loopMode == 1 then
  140. local idx = 1
  141. for i,s in ipairs(songs) do if s==currentSong then idx=i end end
  142. currentSong = songs[idx % #songs + 1]
  143. else
  144. local idx = 1
  145. for i,s in ipairs(songs) do if s==currentSong then idx=i end end
  146. if idx<#songs then currentSong = songs[idx+1] else currentSong = nil playing=false end
  147. end
  148. end
  149. drawUI()
  150. end
  151. else
  152. os.sleep(0.05)
  153. end
  154. end
  155. end
  156.  
  157. -- ===== Input loop =====
  158. local function inputLoop()
  159. drawUI()
  160. while true do
  161. local e, p2, x, y = os.pullEvent()
  162. if e == "mouse_click" or e == "monitor_touch" then
  163. local startIdx = (currentPage-1)*songsPerPage + 1
  164. for i=startIdx, math.min(startIdx+songsPerPage-1, #songs) do
  165. local row = 3 + (i-startIdx)
  166. if y==row then
  167. currentSong = songs[i]
  168. stopFlag = true
  169. playing = true
  170. drawUI()
  171. end
  172. end
  173.  
  174. for _, btn in ipairs(buttons) do
  175. local btnY = (height - bottomRows + btn.line)
  176. if y == btnY and x >= btn.x1 and x <= btn.x2 then
  177. if btn.text:find("Shuffle") then shuffle = not shuffle
  178. elseif btn.text:find("Loop") then loopMode = (loopMode+1)%3
  179. elseif btn.text:find("Prev") and currentPage>1 then currentPage=currentPage-1
  180. elseif btn.text:find("Next") and currentPage<totalPages() then currentPage=currentPage+1
  181. elseif btn.text:find("Stopped") or btn.text:find("Playing") then
  182. if playing then
  183. stopFlag = true
  184. playing = false
  185. else
  186. if currentSong then playing = true end
  187. end
  188. elseif btn.text:find("Skip") then
  189. if currentSong then
  190. local idx = 1
  191. for i,s in ipairs(songs) do if s==currentSong then idx=i end end
  192. if shuffle then
  193. currentSong = songs[math.random(#songs)]
  194. else
  195. if idx < #songs then
  196. currentSong = songs[idx+1]
  197. else
  198. currentSong = songs[1]
  199. end
  200. end
  201. stopFlag = true
  202. playing = true
  203. end
  204. elseif btn.text=="-" then volume = math.max(0,volume-0.3)
  205. elseif btn.text=="+" then volume = math.min(3,volume+0.3)
  206. end
  207. drawUI()
  208. end
  209. end
  210. end
  211. end
  212. end
  213.  
  214. parallel.waitForAny(playerLoop, inputLoop)
Advertisement
Add Comment
Please, Sign In to add comment