Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local runMainLoop = true
- function byte_lsb(handle)
- return handle.read()
- end
- local function int16_lsb(handle)
- return bit32.bor(bit32.lshift(byte_lsb(handle),8), byte_lsb(handle))
- end
- function int16_msb(handle)
- local x = int16_lsb(handle)
- --# for some reason computercraft doesn't like little endian so we convert to big endian
- local y = 0
- y = y + bit32.lshift(bit32.band(x,0x00FF),8)
- y = y + bit32.rshift(bit32.band(x,0xFF00),8)
- return y
- end
- local function int32_lsb(handle)
- return bit32.bor(bit32.lshift(int16_lsb(handle),16),int16_lsb(handle))
- end
- function int32_msb(handle)
- local x = int32_lsb(handle)
- --# again, convert little-endian to big-endian
- local y = 0
- y = y + bit32.lshift(bit32.band(x, 0x000000FF), 24)
- y = y + bit32.rshift(bit32.band(x, 0xFF000000), 24)
- y = y + bit32.lshift(bit32.band(x, 0x0000FF00), 8)
- y = y + bit32.rshift(bit32.band(x, 0x00FF0000), 8)
- return y
- end
- function bit_string(handle)
- local len = int32_msb(handle)
- local str = ""
- for i=1, len do
- str = str .. string.char(byte_lsb(handle))
- end
- return str
- end
- function stringSelector(boolean, var1, var2)
- if boolean then
- if type(var1) == "table" then
- local str = ""
- for i=1, #var1 do
- str = str .. var1[i]
- end
- var1 = str
- end
- return var1
- else
- if type(var2) == "table" then
- local str = ""
- for i=1, #var2 do
- str = str .. var2[i]
- end
- var2 = str
- end
- return var2
- end
- end
- function songDetails(handle)
- local song = {}
- local new = int16_msb(handle)
- if new == 0 then
- song.version = byte_lsb(handle)
- song.fci = byte_lsb(handle)
- if song.version >= 3 then
- song.length = int16_msb(handle)
- else
- song.length = nil
- end
- else
- song.version = 0
- song.length = new
- end
- song.height = int16_msb(handle)
- song.name = bit_string(handle)
- song.author = bit_string(handle)
- song.ogAuthor = bit_string(handle)
- song.description = bit_string(handle)
- song.tempo = int16_msb(handle)/100
- song.autoSave = byte_lsb(handle)
- song.autoSaveDuration = byte_lsb(handle)
- song.timeSignature = byte_lsb(handle)
- song.minutesSpent = int32_msb(handle)
- song.leftClicks = int32_msb(handle)
- song.rightClicks = int32_msb(handle)
- song.blocksAdded = int32_msb(handle)
- song.blocksRemoved = int32_msb(handle)
- song.ogFileName = bit_string(handle)
- song.duration = song.length/song.tempo
- if song.version >= 4 then
- song.loop = byte_lsb(handle)
- song.loopmax = byte_lsb(handle)
- song.loopstart = int16_lsb(handle)
- end
- return song
- end
- function loadSong(handle)
- local song = songDetails(handle)
- song.ticks = {}
- local curTick = -1
- local running1 = true
- while running1 do
- local tickJump = int16_msb(handle)
- curTick = curTick + tickJump
- if tickJump > 0 then
- local tick = {}
- tick.tick = curTick
- tick.layers = {}
- local curLayer = 0
- local running2 = true
- while running2 do
- local layerJump = int16_msb(handle)
- curLayer = curLayer + layerJump
- if layerJump > 0 then
- local layer = {}
- layer.instrument = byte_lsb(handle)
- layer.note = byte_lsb(handle)-33
- layer.layer = curLayer
- if song.version >= 4 then
- layer.vel = byte_lsb(handle)
- layer.pan = byte_lsb(handle)
- layer.pit = int16_msb(handle)
- else
- layer.vel = 100
- layer.pan = 100
- layer.pit = 0
- end
- table.insert(tick.layers,layer)
- else
- running2 = false
- end
- end
- table.insert(song.ticks,tick)
- else
- running1 = false
- end
- end
- if song.length == nil then
- song.length = song.ticks[#song.ticks].tick
- end
- return song
- end
- local speakers = {}
- function loadSpeakers()
- speakers = table.pack(peripheral.find("speaker"))
- end
- local instruments = {}
- instruments[0] = "harp"
- instruments[1] = "bass"
- instruments[2] = "basedrum"
- instruments[3] = "snare"
- instruments[4] = "hat"
- instruments[5] = "guitar"
- instruments[6] = "flute"
- instruments[7] = "bell"
- instruments[8] = "chime"
- instruments[9] = "xylophone"
- local progress = 0
- volume = 3
- function playSong(fileName)
- local handle, err = fs.open(fileName,"rb")
- if not handle then error(err) end
- if #speakers == 0 then loadSpeakers() end
- local song = loadSong(handle)
- handle.close()
- local ticks = song.ticks
- local waitTime = 1/song.tempo
- local startTime = os.clock()
- for i=1, #ticks do
- progress = ticks[i].tick/song.length
- layers = ticks[i].layers
- for j=1, #layers do
- l = layers[j]
- speakers[j%#speakers+1].playNote(instruments[l.instrument],volume,l.note)
- end
- if i ~= #ticks then
- sleep((startTime+waitTime*ticks[i+1].tick)-os.clock())
- end
- end
- end
- local function round(num)
- if num%1 >= .5 then
- return math.ceil(num)
- else
- return math.floor(num)
- end
- end
- local songs = {}
- local ongoingSong = {}
- local nextSong = {}
- local paused = false
- local shuffle = false
- local autoPlay = false
- local songProgress = 0
- function chooseNextSong()
- if #songs > 0 then
- if autoPlay and ongoingSong.pos ~= nil and nextSong.pos == nil then
- if shuffle then
- local num = round(#songs*math.random())
- nextSong = songs[num]
- nextSong.pos = num
- else
- local num = (ongoingSong.pos)%#songs+1
- nextSong = songs[num]
- nextSong.pos = num
- end
- end
- end
- end
- function songPlayer()
- local event, command, arg1 = nil, nil, nil
- local runStartingStatement = true
- while true do
- if runStartingStatement then
- if autoPlay and nextSong.pos ~= nil then
- ongoingSong = nextSong
- nextSong = {}
- else
- ongoingSong = {}
- event, command, arg1 = os.pullEvent("song")
- end
- else
- runStartingStatement = true
- end
- if ongoingSong.pos ~= nil or (command == "play" and tonumber(arg1) and arg1 > 0 and arg1 < #songs) then
- if paused then
- paused = false
- os.queueEvent("screen","update","bottomBar")
- end
- if arg1 then
- ongoingSong = songs[arg1]
- ongoingSong.pos = arg1
- nextSong = {}
- end
- chooseNextSong()
- local songPath = ongoingSong.path
- local handle, err = fs.open(songPath,"rb")
- event, command, arg1 = nil, nil, nil
- if not handle then error(err) end
- if #speakers == 0 then loadSpeakers() end
- local song = loadSong(handle)
- handle.close()
- if song.length == nil then
- song.length = song.ticks[#song.ticks].ticks
- end
- local ticks = song.ticks
- local waitTime = 1/song.tempo
- local startTime = os.clock()
- local i = 1
- while i <= #ticks do
- songProgress = ticks[i].tick/song.length
- layers = ticks[i].layers
- for j=1, #layers do
- l = layers[j]
- speakers[j%#speakers+1].playNote(instruments[l.instrument],volume,l.note)
- end
- local timerID = nil
- local pauseStart = nil
- if paused then
- pauseStart = os.clock()
- elseif i ~= #ticks then
- timerID = os.startTimer(startTime+waitTime*ticks[i+1].tick-os.clock())
- --sleep((startTime+waitTime*ticks[i+1].tick)-os.clock())
- end
- local moveOn = false
- while i ~= #ticks and not moveOn do
- event, command, arg1 = os.pullEvent()
- moveOn = true
- if event == "timer" and command == timerID then
- elseif event == "song" then
- if command == "resume" and pauseStart ~= nil then
- startTime = startTime + (os.clock()-pauseStart)
- elseif command == "play" and arg1 ~= nil and songPath ~= nil and songs ~= nil and songs[arg1] ~= nil then
- if songs[arg1].path ~= songPath then
- i = #ticks+1
- runStartingStatement = false
- else
- i = 0
- startTime = os.clock()
- end
- paused = false
- os.queueEvent("screen", "update", "bottomBar")
- elseif command == "jump" then
- if arg1 > 0 and arg1 <= song.length*arg1 then
- local pos = math.floor(arg1*song.length)
- local closestTick = -1
- local closestTickPos = 0
- for b=1, #ticks do
- if closestTick == -1 or math.abs(pos-ticks[b].tick) <= math.abs(pos-closestTick) then
- closestTick = ticks[b].tick
- closestTickPos = b
- else
- break
- end
- if b%100 == 0 then
- sleep(0.00001)
- end
- end
- i=closestTickPos
- startTime = os.clock()-(waitTime*closestTick)
- end
- paused = false
- os.queueEvent("screen", "update", "bottomBar")
- end
- else
- moveOn = false
- end
- end
- i = i+1
- end
- end
- end
- end
- function recursiveFind(dir, key)
- local files = {}
- local directories = {dir}
- local iterations = 0
- while #directories > 0 do
- iterations = iterations + 1
- if iterations%100 == 0 then
- sleep(0.0001)
- end
- local path = table.remove(directories,1)
- local dirFiles = fs.list(path)
- for i, v in pairs(dirFiles) do
- if fs.isDir(path .. "/" .. v) then
- table.insert(directories, path .. v .. "/")
- elseif string.find(v,key) then
- table.insert(files, path .. v)
- end
- end
- end
- return files
- end
- function retrieveSongs()
- local files = recursiveFind("", ".nbs")
- songs = {}
- for i=1, #files do
- local file, err = fs.open(files[i],"rb")
- if not file then error(err) end
- local tmpSong = songDetails(file)
- file.close()
- local song = {}
- song.author = tmpSong.author
- song.name = tmpSong.name
- if song.name == "" and tmpSong.ogFileName ~= "" then
- song.name = tmpSong.ogFileName
- elseif song.name == "" then
- song.name = files[i]
- end
- song.path = files[i]
- song.duration = math.floor(tmpSong.duration*100)/100
- table.insert(songs,song)
- end
- end
- screenPrototype = {screen="og"}
- function screenPrototype.__init__(baseClass, data)
- self = {}
- for i,v in pairs(data) do
- self[i] = v
- end
- setmetatable(self, {__index=screenPrototype})
- return self
- end
- setmetatable(screenPrototype, {__call=screenPrototype.__init__})
- function screenPrototype:bWrite(str, posX, posY, backColor, frontColor)
- local screen = self
- prevBackColor = screen.getBackgroundColor()
- prevFrontColor = screen.getTextColor()
- if backColor then
- screen.setBackgroundColor(backColor)
- end
- if frontColor then
- screen.setTextColor(frontColor)
- end
- screen.setCursorPos(posX,posY)
- screen.write(str)
- screen.setTextColor(prevFrontColor)
- screen.setBackgroundColor(prevBackColor)
- end
- function xCenText(str, space)
- return string.rep(" ", math.floor((space-#str)/2)) .. str .. string.rep(" ", math.ceil((space-#str)/2))
- end
- local songSelectionSpace = 6
- local songScreen = {}
- function songScreen.drawSongSelector(screen)
- if not screen.name then
- screen = screenPrototype(screen)
- end
- if #songs == 0 then retrieveSongs() end
- local topSong = screen.topSong
- local selectedSong = screen.selectedSong
- local x,y = screen.getSize()
- screen.setBackgroundColor(colors.yellow)
- local lines = y-songSelectionSpace
- screen.setCursorPos(1,1)
- local space = (x-11)/2
- local nameSpace = space + space%1
- local authorSpace = space - space%1
- local durationSpace = 10
- if authorSpace%2 == nameSpace%2 then
- nameSpace = nameSpace + nameSpace%2
- authorSpace = authorSpace - authorSpace%2
- else
- nameSpace = nameSpace - nameSpace%2
- authorSpace = authorSpace - authorSpace%2
- durationSpace = durationSpace+1
- end
- nameStart = 1
- nameEnd = nameSpace+1
- authorStart = nameSpace+1
- authorEnd = authorStart+authorSpace
- durationStart = authorEnd+1
- durationEnd = durationStart+durationSpace
- screen:bWrite(xCenText("Name", nameSpace), nameStart,1)
- screen:bWrite("|", nameSpace,1, colors.red)
- screen:bWrite(xCenText("Author", authorSpace), authorStart,1)
- screen:bWrite("|", nameSpace+1+authorSpace,1, colors.red)
- screen:bWrite(string.rep(" ", x-nameSpace-authorSpace-1-9) .. "Length(s)", durationStart,1)
- screen:bWrite(string.rep("-", nameSpace-1) .. "+" .. string.rep("-",authorSpace) .. "+" .. string.rep("-", durationSpace), 1,2, colors.red)
- local iter = lines-3
- if #songs < iter then iter = #songs end
- for i=topSong, iter+topSong-1 do
- local song = songs[i]
- local yLevel = i-topSong+3
- local backColor = colors.yellow
- local frontColor = colors.white
- if i == selectedSong then
- backColor = colors.white
- frontColor = colors.black
- end
- if #song.name > nameSpace then
- screen:bWrite(string.sub(song.name,1,nameSpace), 1, yLevel, backColor, frontColor)
- else
- screen:bWrite(song.name .. string.rep(" ", nameSpace-#song.name), 1, yLevel, backColor, frontColor)
- end
- screen:bWrite("|", nameSpace,yLevel, colors.red)
- if #song.author > authorSpace then
- screen:bWrite(string.sub(song.author,1,authorSpace), authorStart, yLevel, backColor, frontColor)
- else
- screen:bWrite(song.author .. string.rep(" ", authorSpace-#song.author), authorStart, yLevel, backColor, frontColor)
- end
- screen:bWrite("|", authorEnd,yLevel, colors.red)
- if #tostring(song.duration) > durationSpace then
- screen:bWrite(string.sub(tostring(song.duration),1,durationSpace), durationStart, yLevel, backColor, frontColor)
- else
- screen:bWrite(tostring(song.duration) .. string.rep(" ", durationSpace-#tostring(song.duration)), durationStart, yLevel, backColor, frontColor)
- end
- end
- if #songs < lines then
- for i=#songs+3, lines-1 do
- screen:bWrite(string.rep(" ", nameSpace-1), 1, i)
- screen:bWrite("|", nameSpace,i, colors.red)
- screen:bWrite(string.rep(" ", authorSpace), authorStart,i)
- screen:bWrite("|", nameSpace+1+authorSpace,i, colors.red)
- screen:bWrite(string.rep(" ", durationSpace), durationStart, i)
- end
- end
- screen:bWrite(string.rep("-", nameSpace-1) .. "+" .. string.rep("-",authorSpace) .. "+" .. string.rep("-", durationSpace), 1,lines, colors.red)
- end
- function songScreen.drawBottomBar(screen)
- if not screen.name then
- screen = screenPrototype(screen)
- end
- local selectedSong = screen.selectedSong
- if #songs == 0 then retrieveSongs() end
- local x,y = screen.getSize()
- local stLine = y-songSelectionSpace+1
- local playingStr = stringSelector(ongoingSong.pos ~= nil, table.pack("Playing: ",ongoingSong.name, " by ",ongoingSong.author), "Playing: ")
- screen:bWrite(string.sub(playingStr,1, x-3) .. string.rep(" ", x-#playingStr-3), 1, stLine, colors.black)
- local nextStr = stringSelector(nextSong.pos ~= nil, table.pack("Next: ",nextSong.name," by ",nextSong.author), "Next: ")
- screen:bWrite(string.sub(nextStr, 1, x-3) .. string.rep(" ", x-#nextStr-3), 1, stLine+1, colors.black)
- if selectedSong > 1 then
- screen:bWrite("/\\", x-1,stLine, colors.blue)
- else
- screen:bWrite(" ", x-1,stLine, colors.blue)
- end
- if selectedSong ~= #songs then
- screen:bWrite("\\/", x-1, stLine+1, colors.blue)
- else
- screen:bWrite(" ", x-1,stLine+1, colors.blue)
- end
- screen:bWrite(">", x-2,stLine,colors.green)
- screen:bWrite(" ", x-2,stLine+1,colors.red)
- local button1Space = math.floor((x-1)/2)
- local button2Space = math.ceil((x-1)/2)
- screen:bWrite(xCenText("Pause",button1Space),1,stLine+2, stringSelector(paused,colors.white,colors.gray), colors.black)
- screen:bWrite(xCenText("shuffle",button2Space),button1Space+2,stLine+2, stringSelector(shuffle,colors.white,colors.gray), colors.black)
- screen:bWrite(xCenText("Auto Play",button1Space),1,stLine+3, stringSelector(autoPlay,colors.white,colors.gray), colors.black)
- screen:bWrite(xCenText("Refresh Songs",button2Space),button1Space+2,stLine+3, colors.blue, colors.white)
- screen:bWrite(xCenText("Random",button1Space),1,stLine+4, colors.blue, colors.white)
- screen:bWrite(xCenText("Configure",button2Space),button1Space+2,stLine+4, colors.blue, colors.white)
- bars = math.floor(x*songProgress)
- screen:bWrite(string.rep(" ", bars), 1, y, colors.white)
- screen:bWrite(string.rep(" ", x-bars) .. ">", bars+2, y, colors.black)
- end
- function songScreen.load(screen)
- screen.setBackgroundColor(colors.black)
- screen.clear()
- songScreen.drawSongSelector(screen)
- songScreen.drawBottomBar(screen)
- end
- local timerEventID = nil
- function songScreen.event(screen, event)
- local update = true
- if event[1] == "timer" and event[2] == timerEventID then
- update = false
- songScreen.drawBottomBar(screen)
- elseif (event[1] == "monitor_touch" and event[2] == screen.name) or (event[1] == "mouse_click" and screen.name == "term") then
- local x,y = screen.getSize()
- local stLine = y-songSelectionSpace+1
- local button1Space = math.floor((x-1)/2)
- local button2Space = math.ceil((x-1)/2)
- local tx = event[3]
- local ty = event[4]
- if ty > 2 and ty < y-songSelectionSpace then
- if ty-2 <= #songs then
- if ty-2+screen.topSong-1 == screen.selectedSong then
- os.queueEvent("song","play",screen.selectedSong)
- else
- screen.selectedSong = ty-2+screen.topSong-1
- end
- end
- elseif screen.selectedSong ~= 1 and ty == stLine and tx >= x-1 then
- screen.selectedSong = screen.selectedSong - 1
- if screen.selectedSong < screen.topSong then
- screen.topSong = screen.selectedSong
- end
- elseif screen.selectedSong < #songs and ty == stLine+1 and tx >= x-1 then
- screen.selectedSong = screen.selectedSong + 1
- if screen.selectedSong >= screen.topSong+(y-songSelectionSpace-3) then
- screen.topSong = screen.topSong + 1
- end
- elseif screen.selectedSong >= 1 and screen.selectedSong <= #songs and ty == stLine and tx == x-2 then
- os.queueEvent("song","play",screen.selectedSong)
- elseif ty == stLine+2 and tx >= 1 and tx <= button1Space then
- paused = not paused
- os.queueEvent("song","resume")
- elseif ty == stLine+2 and tx >= button1Space+2 and tx <= x then
- shuffle = not shuffle
- if autoPlay then
- nextSong = {}
- chooseNextSong()
- end
- elseif ty == stLine+3 and tx >= 1 and tx <= button1Space then
- autoPlay = not autoPlay
- if autoPlay then
- nextSong = {}
- chooseNextSong()
- else
- nextSong = {}
- end
- elseif ty == stLine+3 and tx >= button1Space+2 and tx <= x then
- retrieveSongs()
- os.queueEvent("screen","update")
- elseif ty == stLine+4 and tx >= 1 and tx <= button1Space and (ongoingSong.pos == nil or #songs > 1) then
- nextSong = {}
- local songNum = round(#songs*math.random())
- if ongoingSong.pos ~= nil and #songs > 1 then
- iterations = 0
- while songNum == ongoingSong.pos do
- songNum = round(#songs*math.random())
- iterations = iterations+1
- if iterations > 10 then
- songNum = (orginalSong.pos)%#songs+1
- end
- end
- end
- os.queueEvent("song","play", songNum)
- elseif ty == stLine+4 and tx >= button1Space+2 and tx <= x then
- --configuration
- elseif ty == y then
- update = false
- os.queueEvent("song", "jump",tx/x)
- end
- elseif event[1] == "key" and screen.name == "term" then
- local x,y = screen.getSize()
- if event[2] == keys.up then
- if screen.selectedSong ~= 1 then
- screen.selectedSong = screen.selectedSong - 1
- if screen.selectedSong < screen.topSong then
- screen.topSong = screen.selectedSong
- end
- end
- elseif event[2] == keys.down then
- if screen.selectedSong ~= #songs then
- screen.selectedSong = screen.selectedSong + 1
- if screen.selectedSong > screen.topSong+(y-songSelectionSpace-4) then
- screen.topSong = screen.topSong + 1
- end
- end
- elseif event[2] == keys.enter and screen.selectedSong >= 1 and screen.selectedSong then
- os.queueEvent("song","play",screen.selectedSong)
- elseif event[2] == keys.tab then
- runMainLoop = false
- update = false
- end
- elseif event[1] == "screen" and event[2] == "update" then
- update = false
- if event[3] == "bottomBar" then
- songScreen.drawBottomBar(screen)
- elseif event[3] == "songSelector" then
- songScreen.drawSongSelector(screen)
- else
- songScreen.load(screen)
- end
- else
- update = false
- end
- if update then
- songScreen.load(screen)
- end
- end
- local screens = {}
- for i,v in pairs(peripheral.getNames()) do
- if (peripheral.getType(v) == "monitor") then
- local tScreen = screenPrototype(peripheral.wrap(v))
- tScreen.selectedSong = 1
- tScreen.topSong = 1
- tScreen.name = v
- songScreen.load(tScreen)
- table.insert(screens,tScreen)
- end
- end
- local tScreen = screenPrototype(term)
- tScreen.selectedSong = 1
- tScreen.topSong = 1
- tScreen.name = "term"
- songScreen.load(tScreen)
- table.insert(screens,tScreen)
- function mainLoop()
- timerEventID = os.startTimer(1)
- while runMainLoop do
- local event = table.pack(os.pullEvent())
- for i=1, #screens do
- songScreen.event(screens[i],event)
- end
- if event[1] == "timer" and event[2] == timerEventID then
- timerEventID = os.startTimer(1)
- end
- end
- end
- parallel.waitForAny(mainLoop,songPlayer)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement