Advertisement
18107

18107's Jukebox for Computer Craft

Jul 1st, 2015
451
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.14 KB | None | 0 0
  1. --18107's Jukebox
  2. --version 1.1.2
  3. --last updated 14 July 2015
  4. --requires 'project red' or 'computronics'
  5. --Noteblock player at http://pastebin.com/aX8quTFy
  6.  
  7. local monitor = peripheral.find("monitor")
  8. if monitor == nil then
  9.   term.setTextColor(colors.red)
  10.   print("Error: No monitor detected")
  11.   return
  12. end
  13. local modem = peripheral.find("modem")
  14. local musicList = {}
  15. local monitorWidth, monitorHeight = monitor.getSize()
  16. local song = nil
  17. local playing = nil
  18. local songLength, songName, songAuth, origSongAuth, songDesc
  19. local tempo = 1000
  20. local page = 1
  21. local screen = 1
  22. local noteblock = peripheral.find("iron_noteblock")
  23. local instrum = {0, 0, 0, 0, 0}
  24. local repeatMode = "off" -- off, single, all ordered, all random
  25. local finished = false
  26. local refreshCounter = 1
  27. local ticksPlayed = 0
  28. local updateNeeded = false
  29. local paused = false
  30. math.randomseed(os.time())
  31. math.random()
  32.  
  33. function newLine()
  34.   local x, y = monitor.getCursorPos()
  35.   monitor.setCursorPos(1, y + 1)
  36. end --newline()
  37.  
  38. function readNumber(file, size) --reads a binary number
  39.   local number = 0
  40.   for siz = 1, size do
  41.     number = number + bit.blshift(file.read(), 8*(siz-1))
  42.   end --for size
  43.   return number
  44. end --readNumber()
  45.  
  46. function readString(file, length)
  47.   if length == 0 then return end
  48.   local text = string.char(file.read())
  49.   for char = 2, length do
  50.     text = text..string.char(file.read())
  51.   end --for char
  52.   return text
  53. end --readString()
  54.  
  55. function refreshMusicList()
  56.   local length = #musicList
  57.   local thisSong
  58.   if playing ~= 0 and playing ~= nil then
  59.     thisSong = musicList[playing]
  60.   end --if playing
  61.  
  62.   local list = fs.list("")
  63.   local disks = {}
  64.   musicList = {}
  65.   local counter = 1
  66.   local counter2 = 1
  67.   for i = 1, #list do
  68.     if string.sub(list[i],-4)==".nbs" then
  69.       musicList[counter] = list[i]
  70.       counter = counter + 1
  71.     elseif string.sub(list[i],0,4) == "disk" and fs.isDir(list[i]) then
  72.       disks[counter2] = list[i]
  73.       counter2 = counter2 + 1
  74.     end --if ".nbs"
  75.   end --for #list
  76.  
  77.   for a = 1, #disks do
  78.     list = fs.list(disks[a])
  79.     for b = 1, #list do
  80.       if string.sub(list[b],-4)==".nbs" then
  81.         musicList[counter] = disks[a].."/"..list[b]
  82.         counter = counter + 1
  83.       end --if ".nbs"
  84.     end --for #list
  85.   end --for #disks
  86.  
  87.   if playing ~= 0 and playing ~= nil then
  88.     if thisSong ~= musicList[playing] then
  89.       playing = playing - length + #musicList --FIXME check if this worked
  90.       updateNeeded = true
  91.     end --if thisSong
  92.   end --if playing
  93.  
  94.   if page > #musicList/(monitorHeight-3) and #musicList > 0 then
  95.     page = math.ceil(#musicList/(monitorHeight-3))
  96.   end --if page
  97. end --refreshMusicList()
  98.  
  99. function nextSong()
  100.   if playing == 0 or playing == nil then return end
  101.  
  102.   if repeatMode == "all random" then
  103.     playing = math.random(#musicList)
  104.   else
  105.     if playing >= #musicList then
  106.     playing = 0
  107.   end --if playing == #musicList
  108.   playing = playing + 1
  109.   end --if repeatMode
  110.   page = math.floor((playing-1)/(monitorHeight-3)) + 1
  111.   updateNeeded = true
  112. end --skip()
  113.  
  114. function displayMusicList(page, playing)
  115.   refreshMusicList()
  116.   monitor.clear()
  117.   monitor.setCursorPos(monitorWidth-4-20, 1)
  118.   monitor.setTextColor(colors.yellow)
  119.   monitor.write("Repeat: "..repeatMode)
  120.   monitor.setCursorPos(1, 1)
  121.   monitor.setTextColor(colors.cyan)
  122.   monitor.write("18107's Juke Box")
  123.   monitor.setCursorPos(monitorWidth-3, 1)
  124.   monitor.setTextColor(colors.red)
  125.   monitor.write("Stop")
  126.   monitor.setTextColor(colors.white)
  127.   newLine()
  128.   if #musicList > 0 then
  129.     for i = (monitorHeight-3)*(page-1)+1, (monitorHeight-3)*(page-1)+monitorHeight-3 do
  130.       if playing == i then monitor.setTextColor(colors.blue) end
  131.       if i <= #musicList then
  132.         if string.sub(musicList[i],0,4) == "disk" then
  133.           if string.sub(musicList[i],5,5) == "/" then
  134.             monitor.write("Disk : "..string.sub(musicList[i],6,-5))
  135.           else
  136.             monitor.write("Disk "..string.sub(musicList[i],5,5).." : "..string.sub(musicList[i],7,-5))
  137.           end --if "/"
  138.         else
  139.           monitor.write(string.sub(musicList[i],0,-5))
  140.         end --if disk/
  141.       end --if #musicList
  142.       if playing == i then monitor.setTextColor(colors.white) end
  143.       newLine()
  144.     end --for 1, 16
  145.   end --if #musicList
  146.   monitor.setCursorPos(1, monitorHeight-1)
  147.   if playing == 0 or playing == nil then
  148.     monitor.setTextColor(colors.lightGray)
  149.   else
  150.     monitor.setTextColor(colors.cyan)
  151.   end --if playing
  152.   monitor.write("Currently Playing")
  153.   monitor.setCursorPos(monitorWidth-9, monitorHeight-1)
  154.   if paused then
  155.     monitor.write("Play  ")
  156.   else
  157.     monitor.write("Pause ")
  158.   end --if paused
  159.   if playing ~= 0 and playing ~= nil then
  160.     monitor.setTextColor(colors.blue)
  161.   end --if playing
  162.   monitor.write("Skip")
  163.   newLine()
  164.   monitor.setTextColor(colors.green)
  165.   monitor.write("Previous")
  166.   monitor.setCursorPos(monitorWidth-3, monitorHeight)
  167.   monitor.write("Next")
  168.   monitor.setCursorPos(monitorWidth/2+1, monitorHeight)
  169.   monitor.setTextColor(colors.yellow)
  170.   monitor.write(tostring(page))
  171.   monitor.setTextColor(colors.white)
  172. end --displayMusicList()
  173.  
  174. function displayCurrentlyPlaying(playing, full)
  175.   if full then
  176.     monitor.clear()
  177.     monitor.setCursorPos(monitorWidth-4-20, 1)
  178.     monitor.setTextColor(colors.yellow)
  179.     monitor.write("Repeat: "..repeatMode)
  180.     monitor.setCursorPos(1, 1)
  181.     monitor.setTextColor(colors.cyan)
  182.     monitor.write("18107's Juke Box")
  183.     monitor.setCursorPos(monitorWidth-3, 1)
  184.     monitor.setTextColor(colors.red)
  185.     monitor.write("Stop")
  186.     monitor.setTextColor(colors.white)
  187.     newLine()
  188.     newLine()
  189.     if string.sub(musicList[playing], 0, 4) == "disk" then
  190.       if string.sub(musicList[playing], 5, 5) == "/" then
  191.         monitor.write("Disk : "..string.sub(musicList[playing], 6, -5))
  192.       else
  193.         monitor.write("Disk "..string.sub(musicList[playing],5,5).." : "..string.sub(musicList[playing],7,-5))
  194.       end --if "/"
  195.     else
  196.       monitor.write(string.sub(musicList[playing], 0, -5))
  197.     end --if disk/
  198.     newLine()
  199.     newLine()
  200.     if type(songAuth) == "string" then
  201.       monitor.write("Author: "..songAuth)
  202.     end --if string
  203.     newLine()
  204.     newLine()
  205.     if type(origSongAuth) == "string" then
  206.       monitor.write("Original author: "..origSongAuth)
  207.     end --if string
  208.     newLine()
  209.     newLine()
  210.     if type(songDesc) == "string" then
  211.       for desc = 0, #songDesc/monitorWidth+1 do
  212.         monitor.write(string.sub(songDesc, desc*monitorWidth+1, (desc+1)*monitorWidth))
  213.         newLine()
  214.       end --for #songDesc
  215.     end --if string
  216.     monitor.setCursorPos(1, monitorHeight-1)
  217.     monitor.setTextColor(colors.cyan)
  218.     monitor.write("Song list")
  219.     monitor.setCursorPos(monitorWidth-9, monitorHeight-1)
  220.     if paused then
  221.       monitor.write("Play  ")
  222.     else
  223.       monitor.write("Pause ")
  224.     end --if paused
  225.     monitor.setTextColor(colors.blue)
  226.     monitor.write("Skip")
  227.   end --if full
  228.  
  229.   monitor.setCursorPos(1, monitorHeight)
  230.   monitor.setTextColor(colors.lightGray)
  231.   local minutes = math.floor(ticksPlayed*100/tempo/60)
  232.   local seconds = math.floor(ticksPlayed*100/tempo) % 60
  233.   if minutes < 10 then monitor.write(" ") end
  234.   monitor.write(minutes..":")
  235.   if seconds < 10 then monitor.write("0") end
  236.   monitor.write(seconds.."")
  237.  
  238.   minutes = math.floor(songLength*100/tempo/60)
  239.   seconds = math.floor(songLength*100/tempo) % 60
  240.   monitor.setCursorPos(monitorWidth-4, monitorHeight)
  241.   if minutes < 10 then monitor.write(" ") end
  242.   monitor.write(minutes..":")
  243.   if seconds < 10 then monitor.write("0") end
  244.   monitor.write(seconds.."")
  245.  
  246.   monitor.setCursorPos(7, monitorHeight)
  247.   monitor.write("<")
  248.   for i = 8, monitorWidth-7 do
  249.     monitor.write("=")
  250.   end --for monitorWidth
  251.   monitor.write(">")
  252.  
  253.   monitor.setTextColor(colors.white)
  254. end --displayCurrentlyPlaying()
  255.  
  256. function header(song)
  257.   local strLen = 0
  258.   songLength = readNumber(song, 2)
  259.   readNumber(song, 2) --song height
  260.   strLen = readNumber(song, 4)
  261.   songName = readString(song, strLen)
  262.   strLen = readNumber(song, 4)
  263.   songAuth = readString(song, strLen)
  264.   strLen = readNumber(song, 4)
  265.   origSongAuth = readString(song, strLen)
  266.   strLen = readNumber(song, 4)
  267.   songDesc = readString(song, strLen)
  268.   local tempo = readNumber(song, 2)
  269.   readNumber(song, 1) --auto saving
  270.   readNumber(song, 1) --auto saving duration
  271.   readNumber(song, 1) --time signature
  272.   readNumber(song, 4) --minutes spent
  273.   readNumber(song, 4) --left clicks
  274.   readNumber(song, 4) --right clicks
  275.   readNumber(song, 4) --blocks added
  276.   readNumber(song, 4) --blocks removed
  277.   strLen = readNumber(song, 4)
  278.   readString(song, strLen) --MIDI/schematic file name
  279.   return tempo
  280. end --header()
  281.  
  282. function playSong(number)
  283.   while true do --coroutine loop
  284.     if song ~= nil then song.close() end
  285.     if number == 0 then number = nil end
  286.     while number == nil do
  287.       number = coroutine.yield()
  288.       if number == 0 then number = nil end
  289.     end -- while number == nil
  290.     song = fs.open(musicList[number], "rb")
  291.     if song == nil then
  292.       print("File not found")
  293.       break
  294.     end --if nil
  295.    
  296.     tempo = header(song)
  297.    
  298.     ticksPlayed = -1
  299.     local nextTick = 0
  300.     local instrument = 0
  301.     local key = 0
  302.     local running = true
  303.     while running do --song loop
  304.       nextTick = readNumber(song, 2)
  305.       -- if end of file
  306.       if nextTick == 0 or nextTick == nil then
  307.         instrum = {0, 0, 0, 0, 0}
  308.         transmitNote()
  309.         finished = true
  310.         break
  311.       end --if nextTick
  312.      
  313.       for tick = 1, nextTick do --wait for next note
  314.         ticksPlayed = ticksPlayed + 1
  315.         number = coroutine.yield()
  316.         if number ~= nil then running = false break end
  317.       end --for tick
  318.      
  319.       if number == nil then
  320.         while readNumber(song, 2) ~= 0 do --play chord
  321.           instrument = readNumber(song, 1)
  322.           key = readNumber(song, 1)
  323.           instrum[instrument+1] = bit.bor(instrum[instrument+1], bit.blshift(1, key-33))
  324.           playNote(instrument, key-33)
  325.         end --while chord
  326.         transmitNote()
  327.         instrum = {0, 0, 0, 0, 0}
  328.       elseif number == 0 then --stop
  329.         instrum = {0, 0, 0, 0, 0}
  330.         transmitNote()
  331.       end --if number
  332.     end --while song loop
  333.     --song = nil FIXME why was this here? - removed
  334.   end --while coroutine loop
  335. end --playSong()
  336.  
  337. function transmitNote()
  338.   if modem == nil then return end
  339.   for instrument = 1, 5 do
  340.     modem.transmit(instrument, 0, instrum[instrument])
  341.   end --for instrument
  342. end --transmitNote()
  343.  
  344. function playNote(type, key)
  345.   if noteblock == nil then return end
  346.   if type == 0 then noteblock.playNote(0, key)
  347.   elseif type == 1 then noteblock.playNote(4, key)
  348.   elseif type == 2 then noteblock.playNote(1, key)
  349.   elseif type == 3 then noteblock.playNote(2, key)
  350.   else noteblock.playNote(3, key) end
  351. end
  352.  
  353. displayMusicList(page)
  354. local play = coroutine.create(playSong)
  355. local arg = nil
  356. local event, side, x, y
  357. os.startTimer(0.1)
  358. while coroutine.status(play) ~= "dead" do
  359.   event, side, x, y = os.pullEventRaw()
  360.  
  361.   if updateNeeded then
  362.     updateNeeded = false
  363.     if screen == 1 then
  364.       displayMusicList(page, playing)
  365.     elseif screen == 2 then
  366.       displayCurrentlyPlaying(playing, true)
  367.     end --if screen
  368.   end --if update
  369.  
  370.   if event == "terminate" then
  371.     monitor.clear()
  372.     return
  373.   end --if terminate
  374.  
  375.   if event == "timer" then
  376.     os.startTimer(100/tempo)
  377.     if finished then
  378.       finished = false
  379.       if repeatMode == "single" then
  380.         arg = playing
  381.       elseif repeatMode == "off" then
  382.         screen = 1
  383.         playing = nil
  384.         tempo = 100 -- slow timer
  385.         refreshCounter = 0
  386.       else
  387.         nextSong()
  388.     arg = playing
  389.       end -- if repeatMode
  390.     if screen == 1 then
  391.         displayMusicList(page, playing)
  392.       elseif screen == 2 then
  393.         displayCurrentlyPlaying(playing, true)
  394.       end --if screen
  395.     end -- if finished
  396.  
  397.     if refreshCounter <= 0 then
  398.       refreshCounter = 100
  399.       if screen == 1 then
  400.         displayMusicList(page, playing)
  401.       elseif screen == 2 then
  402.         displayCurrentlyPlaying(playing, true)
  403.       end -- if screen
  404.     end -- if refreshCounter
  405.     refreshCounter = refreshCounter - 1
  406.     if not paused then
  407.       if screen == 2 then
  408.         displayCurrentlyPlaying(playing, false)
  409.       end --if screen
  410.       coroutine.resume(play, arg)
  411.     elseif arg ~= nil then
  412.       coroutine.resume(play, arg)
  413.     end --if not paused
  414.   end
  415.   arg = nil
  416.  
  417.   if event == "monitor_touch" then
  418.     if screen == 1 then
  419.       if y > 1 and y < monitorHeight-1 then
  420.         -- select song
  421.         arg = (y-1)+(page-1)*(monitorHeight-3)
  422.         playing = arg
  423.         displayMusicList(page, playing)
  424.       elseif y == monitorHeight-1 then
  425.         if x >= monitorWidth-9 and x < monitorWidth-4 then
  426.           paused = not paused
  427.           updateNeeded = true
  428.         elseif x >= monitorWidth-3 then
  429.           nextSong()
  430.           arg = playing
  431.           displayMusicList(page, playing)
  432.           coroutine.resume(play, arg)
  433.         elseif x <= 17 then
  434.           -- currently playing
  435.           if playing ~= 0 and playing ~= nil then
  436.             screen = 2
  437.             displayCurrentlyPlaying(playing, true)
  438.           end --if playing
  439.         end --if monitorHeight-1
  440.       elseif y == monitorHeight then
  441.         -- previous, next
  442.         if x < 9 then
  443.           page = page - 1
  444.           if page <= 0 then page = 1 end
  445.           displayMusicList(page, playing)
  446.         elseif x > monitorWidth-4 then
  447.           if page*(monitorHeight-3) < #musicList then
  448.             page = page + 1
  449.           end --if page
  450.           displayMusicList(page, playing)
  451.         end --if x
  452.       else --if y
  453.         -- top line
  454.         if x > monitorWidth-4 then
  455.           -- stop
  456.           arg = 0
  457.           playing = arg
  458.           displayMusicList(page, playing)
  459.         elseif x > 16 and x <= monitorWidth-6 then
  460.           if repeatMode == "off" then
  461.             repeatMode = "single"
  462.           elseif repeatMode == "single" then
  463.             repeatMode = "all ordered"
  464.           elseif repeatMode == "all ordered" then
  465.             repeatMode = "all random"
  466.           else -- if repeatMode == "all random"
  467.             repeatMode = "off"
  468.           end -- if repeatMode
  469.           displayMusicList(page, playing)
  470.         end --if x
  471.       end --if y
  472.  
  473.     elseif screen == 2 then
  474.       if y == monitorHeight-1 then
  475.         if x >= monitorWidth-3 then
  476.           nextSong()
  477.           arg = playing
  478.           displayCurrentlyPlaying(playing, true)
  479.           coroutine.resume(play, arg)
  480.         elseif x >= monitorWidth-9 and x < monitorWidth-4 then
  481.           paused = not paused
  482.           updateNeeded = true
  483.         elseif x <= 9 then
  484.           -- song list
  485.           screen = 1
  486.           displayMusicList(page, playing)
  487.         end --if x
  488.       elseif y == 1 then
  489.         if x > monitorWidth-4 then
  490.         -- stop
  491.         arg = 0
  492.         playing = arg
  493.         screen = 1
  494.         displayMusicList(page, playing)
  495.         elseif x > 16 and x <= monitorWidth-6 then
  496.           if repeatMode == "off" then
  497.             repeatMode = "single"
  498.           elseif repeatMode == "single" then
  499.             repeatMode = "all ordered"
  500.           elseif repeatMode == "all ordered" then
  501.             repeatMode = "all random"
  502.           else -- if repeatMode == "all random"
  503.             repeatMode = "off"
  504.           end -- if repeatMode
  505.           displayCurrentlyPlaying(playing, true)
  506.         end
  507.       end --if y
  508.     end --if screen == 1
  509.   end --monitor touch
  510.  
  511.   if event == "peripheral" or event == "peripheral_detach" or event == "monitor_resize" then
  512.     modem = peripheral.find("modem")
  513.     monitor = peripheral.find("monitor")
  514.     if monitor == nil then
  515.       term.setTextColor(colors.red)
  516.       print("Error: monitor detached")
  517.       return
  518.     end
  519.     noteblock = peripheral.find("iron_noteblock")
  520.     monitorWidth, monitorHeight = monitor.getSize()
  521.     if screen == 1 then
  522.       displayMusicList(page, playing)
  523.     elseif screen == 2 then
  524.       displayCurrentlyPlaying(playing, true)
  525.     end
  526.   end --if peripheral
  527.  
  528.   if event == "disk" then
  529.     refreshMusicList()
  530.     updateNeeded = true
  531.   end --if disk
  532.  
  533.   if event == "disk_eject" then
  534.     if playing ~= 0 and playing ~= nil then
  535.       local length = #musicList
  536.       local thisSong = musicList[playing]
  537.       if string.sub(thisSong, 0, 4) == "disk" then
  538.         if string.sub(thisSong, 5, 5) == "/" then
  539.           if not fs.isDir("disk") then
  540.             arg = 0
  541.           end --if isDir
  542.         else
  543.           if not fs.isDir("disk"..string.sub(thisSong, 5, 5)) then
  544.             arg = 0
  545.           end --if isDir
  546.         end --if "/"
  547.       end --if playing disk
  548.      
  549.       refreshMusicList()
  550.       updateNeeded = true
  551.      
  552.       if arg == 0 then
  553.         playing = arg
  554.         if screen == 2 then
  555.           screen = 1
  556.         end
  557.         coroutine.resume(play, arg)
  558.       end --if arg
  559.     else --if playing
  560.       refreshMusicList()
  561.       updateNeeded = true
  562.     end --if playing
  563.   end -- if disk_eject
  564. end --while coroutine
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement