Advertisement
MCFunRide

Untitled

Oct 31st, 2015
102
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 34.63 KB | None | 0 0
  1. -- +---------------------+------------+---------------------+
  2. -- | | | |
  3. -- | | Note API | |
  4. -- | | | |
  5. -- +---------------------+------------+---------------------+
  6.  
  7. -- Note Block Song format + conversion tools: David Norgren
  8. -- Iron Note Block + NBS loading & playback: TheOriginalBIT
  9. -- Music player interface & API structure: Bomb Bloke
  10.  
  11. -- ----------------------------------------------------------
  12.  
  13. -- Place Note Block Studio NBS files on your ComputerCraft computer,
  14. -- then play them back via a MoarPeripheral's Iron Note Block!
  15.  
  16. -- http://www.computercraft.info/forums2/index.php?/topic/19357-moarperipherals
  17. -- http://www.minecraftforum.net/topic/136749-minecraft-note-block-studio
  18.  
  19. -- This script can be ran as any other, but it can *also* be loaded as an API!
  20. -- Doing so exposes the following functions:
  21.  
  22. -- < note.playSong(fileName) >
  23. -- Simply plays the specified NBS file.
  24.  
  25. -- < note.songEngine([fileName]) >
  26. -- Plays the optionally specified NBS file, but whether one is specified or not, does
  27. -- not return when complete - instead this is intended to run continuously as a background
  28. -- process. Launch it via the parallel API and run it alongside your own script!
  29.  
  30. -- While the song engine function is active, it can be manipulated by queuing the
  31. -- following events:
  32.  
  33. -- * musicPlay
  34. -- Add a filename in as a parameter to start playback, eg:
  35. -- os.queueEvent("musicPlay","mySong.nbs")
  36.  
  37. -- * musicPause
  38. -- Halts playback.
  39.  
  40. -- * musicResume
  41. -- Resumes playback.
  42.  
  43. -- * musicSkipTo
  44. -- Add a song position in as a parameter to skip to that segment. Specify the time
  45. -- in tenths of a second; for example, to skip a minute in use 600.
  46.  
  47. -- Additionally, whenever the song engine finishes a track, it will automatically
  48. -- throw a "musicFinished" event, or a "newTrack" event whenever a new song is loaded.
  49.  
  50. -- **Remember!** The API cannot respond to these events until YOUR code yields!
  51. -- Telling it to load a new song or jump to a different position won't take effect
  52. -- until you pull an event or something!
  53.  
  54. -- < note.setPeripheral(targetDevice1, [targetDevice2,] ...) >
  55. -- By default, upon loading the API attaches itself to any Iron Note Blocks it detects.
  56. -- Use this if you have specific note block(s) in mind, or wish to use different blocks
  57. -- at different times - perhaps mid-song! Returns true if at least one of the specified
  58. -- devices was valid, or false if none were.
  59.  
  60. -- **Note!** The Iron Note Block peripheral can currently play up to five instruments
  61. -- at any given moment. Providing multiple blocks to the API will cause it to
  62. -- automatically spread the load for those songs that need the extra notes.
  63. -- If you provide insufficient blocks, expect some notes to be skipped from
  64. -- certain songs.
  65.  
  66. -- Very few songs (if any) require more than two Iron Note Blocks.
  67.  
  68. -- < note.isPlaying() >
  69. -- Returns whether the API is currently mid-tune (ignoring whether it's paused or not).
  70.  
  71. -- < note.isPaused() >
  72. -- Returns whether playback is paused.
  73.  
  74. -- < note.getSongLength() >
  75. -- Returns the song length in "redstone updates". There are ten updates per second, or
  76. -- one per two game ticks.
  77.  
  78. -- < note.getSongPosition() >
  79. -- Returns the song position in "redstone updates". Time in game ticks is 2 * this. Time
  80. -- in seconds is this / 10.
  81.  
  82. -- < note.getSongSeconds() >
  83. -- Returns the song length in seconds.
  84.  
  85. -- < note.getSongPositionSeconds() >
  86. -- Returns the song position in seconds.
  87.  
  88. -- < note.getSongTempo() >
  89. -- Returns the song tempo, representing the "beats per second".
  90. -- Eg: 2.5 = one beat per 0.4 seconds.
  91. -- 5 = one beat per 0.2 seconds.
  92. -- 10 = one beat per 0.1 seconds.
  93.  
  94. -- Must be a factor of ten.
  95.  
  96. -- < note.getRealSongTempo() >
  97. -- Returns the tempo the song's NBS file specified it should be played at.
  98.  
  99. -- Should be a power of ten, but for whatever reason a lot of NBS files have invalid tempos.
  100.  
  101. -- < note.getSongName() >
  102. -- Returns the name of the song.
  103.  
  104. -- < note.getSongAuthor() >
  105. -- Returns the name of the NBS file author.
  106.  
  107. -- < note.getSongArtist() >
  108. -- Returns the name of the song artist.
  109.  
  110. -- < note.getSongDescription() >
  111. -- Returns the song's description.
  112.  
  113. -- ----------------------------------------------------------
  114.  
  115. if not shell then
  116. -- -----------------------
  117. -- Load as API:
  118. -- -----------------------
  119.  
  120. -- Cranking this value too high will cause crashes:
  121. local MAX_INSTRUMENTS_PER_NOTE_BLOCK = 5
  122.  
  123. local ironnote, paused, cTick, song = {peripheral.find("iron_note")}
  124.  
  125. local translate = {[0]=0,4,1,2,3}
  126.  
  127. local function assert(cdn, msg, lvl)
  128. if not cdn then
  129. error(msg or "assertion failed!", (lvl == 0 and 0 or lvl and (lvl + 1) or 2))
  130. end
  131. return cdn
  132. end
  133.  
  134. -- Returns the song length.
  135. function getSongLength()
  136. if type(song) == "table" then return song.length end
  137. end
  138.  
  139. -- Returns the song position.
  140. function getSongPosition()
  141. return cTick
  142. end
  143.  
  144. -- Returns the song length in seconds.
  145. function getSongSeconds()
  146. if type(song) == "table" then return song.length / song.tempo end
  147. end
  148.  
  149. -- Returns the song position in seconds.
  150. function getSongPositionSeconds()
  151. if type(song) == "table" then return cTick / song.tempo end
  152. end
  153.  
  154. -- Returns the tempo the song will be played at.
  155. function getSongTempo()
  156. if type(song) == "table" then return song.tempo end
  157. end
  158.  
  159. -- Returns the tempo the song's NBS file specified it should be played at.
  160. function getRealSongTempo()
  161. if type(song) == "table" then return song.realTempo end
  162. end
  163.  
  164. -- Switches to a different playback device.
  165. function setPeripheral(...)
  166. local newironnote = {}
  167.  
  168. for i=1,#arg do if type(arg[i]) == "string" and peripheral.getType(arg[i]) == "iron_note" then
  169. newironnote[#newironnote+1] = peripheral.wrap(arg[i])
  170. elseif type(arg[i]) == "table" and arg[i].playNote then
  171. newironnote[#newironnote+1] = arg[i]
  172. end end
  173.  
  174. if #newironnote > 0 then
  175. ironnote = newironnote
  176. return true
  177. else return false end
  178. end
  179.  
  180. -- Returns whether music is loaded for playback.
  181. function isPlaying()
  182. return type(song) == "table"
  183. end
  184.  
  185. -- Returns whether playback is paused.
  186. function isPaused()
  187. return paused
  188. end
  189.  
  190. -- Returns the name of the song.
  191. function getSongName()
  192. if type(song) == "table" then return song.name end
  193. end
  194.  
  195. -- Returns the name of NBS file author.
  196. function getSongAuthor()
  197. if type(song) == "table" then return song.author end
  198. end
  199.  
  200. -- Returns the name of song artist.
  201. function getSongArtist()
  202. if type(song) == "table" then return song.originalauthor end
  203. end
  204.  
  205. -- Returns the song's description.
  206. function getSongDescription()
  207. if type(song) == "table" then return song.description end
  208. end
  209.  
  210. local function byte_lsb(handle)
  211. return assert(handle.read(), "Note NBS loading error: Unexpected EOF (end of file).", 2)
  212. end
  213.  
  214. local function byte_msb(handle)
  215. local x = byte_lsb(handle)
  216. --# convert little-endian to big-endian
  217. local y = 0
  218. y = y + bit.blshift(bit.band(x, 0x00), 7)
  219. y = y + bit.blshift(bit.band(x, 0x02), 6)
  220. y = y + bit.blshift(bit.band(x, 0x04), 5)
  221. y = y + bit.blshift(bit.band(x, 0x08), 4)
  222. y = y + bit.brshift(bit.band(x, 0x10), 4)
  223. y = y + bit.brshift(bit.band(x, 0x20), 5)
  224. y = y + bit.brshift(bit.band(x, 0x40), 6)
  225. y = y + bit.brshift(bit.band(x, 0x80), 7)
  226. return y
  227. end
  228.  
  229. local function int16_lsb(handle)
  230. return bit.bor(bit.blshift(byte_lsb(handle), 8), byte_lsb(handle))
  231. end
  232.  
  233. local function int16_msb(handle)
  234. local x = int16_lsb(handle)
  235. --# convert little-endian to big-endian
  236. local y = 0
  237. y = y + bit.blshift(bit.band(x, 0x00FF), 8)
  238. y = y + bit.brshift(bit.band(x, 0xFF00), 8)
  239. return y
  240. end
  241.  
  242. local function int32_lsb(handle)
  243. return bit.bor(bit.blshift(int16_lsb(handle), 16), int16_lsb(handle))
  244. end
  245.  
  246. local function int32_msb(handle)
  247. local x = int32_lsb(handle)
  248. --# convert little-endian to big-endian
  249. local y = 0
  250. y = y + bit.blshift(bit.band(x, 0x000000FF), 24)
  251. y = y + bit.brshift(bit.band(x, 0xFF000000), 24)
  252. y = y + bit.blshift(bit.band(x, 0x0000FF00), 8)
  253. y = y + bit.brshift(bit.band(x, 0x00FF0000), 8)
  254. return y
  255. end
  256.  
  257. local function nbs_string(handle)
  258. local str = ""
  259. for i = 1, int32_msb(handle) do
  260. str = str..string.char(byte_lsb(handle))
  261. end
  262. return str
  263. end
  264.  
  265. local function readNbs(path)
  266. assert(fs.exists(path), "Note NBS loading error: File \""..path.."\" not found. Did you forget to specify the containing folder?", 0)
  267. assert(not fs.isDir(path), "Note NBS loading error: Specified file \""..path.."\" is actually a folder.", 0)
  268. local handle = fs.open(path, "rb")
  269.  
  270. song = { notes = {}; }
  271.  
  272. --# NBS format found on http://www.stuffbydavid.com/nbs
  273. --# Part 1: Header
  274. song.length = int16_msb(handle)
  275. int16_msb(handle) --# layers, this is Note Block Studio meta-data
  276. song.name = nbs_string(handle)
  277. song.author = nbs_string(handle)
  278. song.originalauthor = nbs_string(handle)
  279. song.description = nbs_string(handle)
  280. song.realTempo = int16_msb(handle)/100
  281. song.tempo = song.realTempo < 20 and (20%song.realTempo == 0 and song.realTempo or math.floor(2000/math.floor(20/song.realTempo+0.5))/100) or 20
  282.  
  283. byte_lsb(handle) --# auto-saving has been enabled (0 or 1)
  284. byte_lsb(handle) --# The amount of minutes between each auto-save (if it has been enabled) (1-60)
  285. byte_lsb(handle) --# The time signature of the song. If this is 3, then the signature is 3/4. Default is 4. This value ranges from 2-8
  286. int32_msb(handle) --# The amount of minutes spent on the project
  287. int32_msb(handle) --# The amount of times the user has left clicked
  288. int32_msb(handle) --# The amount of times the user has right clicked
  289. int32_msb(handle) --# The amount of times the user have added a block
  290. int32_msb(handle) --# The amount of times the user have removed a block
  291. nbs_string(handle) --# If the song has been imported from a .mid or .schematic file, that file name is stored here (Only the name of the file, not the path)
  292.  
  293. --# Part 2: Note Blocks
  294. local maxPitch = 24
  295. local notes = song.notes
  296. local tick = -1
  297. local jumps = 0
  298. while true do
  299. jumps = int16_msb(handle)
  300. if jumps == 0 then
  301. break
  302. end
  303. tick = tick + jumps
  304. local layer = -1
  305. while true do
  306. jumps = int16_msb(handle)
  307. if jumps == 0 then
  308. break
  309. end
  310. layer = layer + jumps
  311. local inst = byte_lsb(handle)
  312. local key = byte_lsb(handle)
  313. --
  314. notes[tick] = notes[tick] or {}
  315. table.insert(notes[tick], {inst = translate[inst]; pitch = math.max((key-33)%maxPitch,0)})
  316. end
  317. end
  318.  
  319. --[[
  320. the rest of the file is useless to us
  321. it's specific to Note Block Studio and
  322. even so is still optional
  323. --]]
  324.  
  325. handle.close()
  326. end
  327.  
  328. function songEngine(targetSong)
  329. assert(ironnote[1], "Note songEngine failure: No Iron Note Blocks assigned.", 0)
  330.  
  331. local tTick, curPeripheral, delay, notes = os.startTimer(0.1), 1
  332.  
  333. if targetSong then os.queueEvent("musicPlay",targetSong) end
  334.  
  335. while true do
  336. local e, someTime = { os.pullEvent() }, os.clock()
  337.  
  338. if e[1] == "timer" and e[2] == tTick and song and not paused then
  339. if notes[cTick] then
  340. local curMaxNotes, nowPlaying = (song.tempo == 20 and math.floor(MAX_INSTRUMENTS_PER_NOTE_BLOCK/2) or MAX_INSTRUMENTS_PER_NOTE_BLOCK) * #ironnote, 0
  341. for _,note in pairs(notes[cTick]) do
  342. ironnote[curPeripheral].playNote(note.inst, note.pitch)
  343. curPeripheral = (curPeripheral == #ironnote) and 1 or (curPeripheral + 1)
  344. nowPlaying = nowPlaying + 1
  345. if nowPlaying == curMaxNotes then break end
  346. end
  347. end
  348.  
  349. cTick = cTick + 1
  350.  
  351. if cTick > song.length then
  352. song = nil
  353. notes = nil
  354. cTick = nil
  355. os.queueEvent("musicFinished")
  356. end
  357.  
  358. tTick = os.startTimer(delay + (someTime - os.clock())) -- The redundant brackets shouldn't be needed, but it doesn't work without them??
  359.  
  360. elseif e[1] == "musicPause" then
  361. paused = true
  362. elseif e[1] == "musicResume" then
  363. paused = false
  364. tTick = os.startTimer(0.1)
  365. elseif e[1] == "musicSkipTo" then
  366. cTick = e[2]
  367. elseif e[1] == "musicPlay" then
  368. readNbs(e[2])
  369. notes = song.notes
  370. cTick = 0
  371. tTick = os.startTimer(0.1)
  372. paused = false
  373. delay = math.floor(100 / song.tempo) / 100
  374. os.queueEvent("newTrack")
  375. end
  376. end
  377. end
  378.  
  379. function playSong(targetSong)
  380. parallel.waitForAny(function () songEngine(targetSong) end, function () os.pullEvent("musicFinished") end)
  381. end
  382. else
  383. -- -----------------------
  384. -- Run as jukebox:
  385. -- ------------------------------------------------------------
  386.  
  387. -- Ignore everything below this point if you're only interested
  388. -- in the API, unless you want to see example usage.
  389.  
  390. -- ------------------------------------------------------------
  391.  
  392. sleep(0) -- 'cause ComputerCraft is buggy.
  393.  
  394. local xSize, ySize = term.getSize()
  395.  
  396. if xSize < 50 then
  397. print("Wider display required!\n")
  398. error()
  399. end
  400.  
  401. if ySize < 7 then
  402. print("Taller display required!\n")
  403. error()
  404. end
  405.  
  406. local startDir = shell.resolve(".")
  407.  
  408. os.loadAPI(shell.getRunningProgram())
  409.  
  410. local playmode, lastSong, marqueePos, myEvent, bump, marquee = 0, {}, 1
  411. local cursor = {{">> "," <<"},{"> > "," < <"},{" >> "," << "},{"> > "," < <"}}
  412. local logo = {{"| |","|\\|","| |"},{"+-+","| |","+-+"},{"---"," | "," | "},{"+--","|- ","+--"}}
  413. local buttons = {{"| /|","|< |","| \\|"},{"|\\ |","| >|","|/ |"},{"| |","| |","| |"},{"|\\ ","| >","|/ "}}
  414.  
  415. -- Returns whether a click was performed at a given location.
  416. -- If one parameter is passed, it checks to see if y is [1].
  417. -- If two parameters are passed, it checks to see if x is [1] and y is [2].
  418. -- If three parameters are passed, it checks to see if x is between [1]/[2] (non-inclusive) and y is [3].
  419. -- If four paramaters are passed, it checks to see if x is between [1]/[2] and y is between [3]/[4] (non-inclusive).
  420. local function clickedAt(...)
  421. if myEvent[1] ~= "mouse_click" then return false end
  422. if #arg == 1 then return (arg[1] == myEvent[4])
  423. elseif #arg == 2 then return (myEvent[3] == arg[1] and myEvent[4] == arg[2])
  424. elseif #arg == 3 then return (myEvent[3] > arg[1] and myEvent[3] < arg[2] and myEvent[4] == arg[3])
  425. else return (myEvent[3] > arg[1] and myEvent[3] < arg[2] and myEvent[4] > arg[3] and myEvent[4] < arg[4]) end
  426. end
  427.  
  428. -- Returns whether one of a given set of keys was pressed.
  429. local function pressedKey(...)
  430. if myEvent[1] ~= "key" then return false end
  431. for i=1,#arg do if arg[i] == myEvent[2] then return true end end
  432. return false
  433. end
  434.  
  435. local function drawPlaymode()
  436. term.setBackgroundColour(term.isColour() and colours.lightGrey or colours.black)
  437. term.setTextColour(term.isColour() and colours.black or colours.white)
  438.  
  439. term.setCursorPos(bump+34, 1)
  440. term.write("[R]epeat ( )")
  441. term.setCursorPos(bump+34, 2)
  442. term.write("Auto-[N]ext ( )")
  443. term.setCursorPos(bump+34, 3)
  444. term.write("[M]ix ( )")
  445.  
  446. if playmode ~= 0 then
  447. term.setTextColour(term.isColour() and colours.blue or colours.white)
  448. term.setCursorPos(bump+47, playmode)
  449. term.write("O")
  450. end
  451. end
  452.  
  453. local function drawInterface()
  454. if term.isColour() then
  455. -- Header / footer.
  456. term.setBackgroundColour(colours.grey)
  457. for i = 1, 3 do
  458. term.setCursorPos(1,i)
  459. term.clearLine()
  460. term.setCursorPos(1,ySize-i+1)
  461. term.clearLine()
  462. end
  463.  
  464. -- Quit button.
  465. term.setTextColour(colours.white)
  466. term.setBackgroundColour(colours.red)
  467. term.setCursorPos(xSize,1)
  468. term.write("X")
  469. end
  470.  
  471. -- Note logo.
  472. term.setTextColour(term.isColour() and colours.blue or colours.white)
  473. term.setBackgroundColour(term.isColour() and colours.lightGrey or colours.black)
  474. for i = 1, 4 do for j = 1, 3 do
  475. term.setCursorPos(bump + (i - 1) * 4, j)
  476. term.write(logo[i][j])
  477. end end
  478.  
  479. -- Skip back / forward buttons.
  480. term.setTextColour(term.isColour() and colours.lightBlue or colours.white)
  481. term.setBackgroundColour(term.isColour() and colours.grey or colours.black)
  482. for j = 0, 1 do for i = 1, 3 do
  483. term.setCursorPos(bump + 17 + j * 11, i)
  484. term.write(buttons[j+1][i])
  485. end end
  486.  
  487. -- Progress bar.
  488. term.setCursorPos(2,ySize-1)
  489. term.setTextColour(term.isColour() and colours.black or colours.white)
  490. term.setBackgroundColour(term.isColour() and colours.lightGrey or colours.black)
  491. term.write("|"..string.rep("=",xSize-4).."|")
  492.  
  493. drawPlaymode()
  494. end
  495.  
  496. local function startSong(newSong)
  497. if #lastSong == 32 then lastSong[32] = nil end
  498. table.insert(lastSong,1,newSong)
  499. os.queueEvent("musicPlay",newSong)
  500. marquee = nil
  501. marqueePos = 1
  502. end
  503.  
  504. local function noteMenu()
  505. local lastPauseState = "maybe"
  506. bump = math.floor((xSize - 49) / 2) + 1
  507. drawInterface()
  508.  
  509. while true do
  510. local displayList, position, lastPosition, animationTimer, curCount, gapTimer, lastProgress = {}, 1, 0, os.startTimer(0), 1
  511. if #shell.resolve(".") > 0 then displayList[1] = ".." end
  512.  
  513. do
  514. local fullList = fs.list(shell.resolve("."))
  515. table.sort(fullList, function (a, b) return string.lower(a) < string.lower(b) end)
  516. for i = 1, #fullList do if fs.isDir(shell.resolve(fullList[i])) then displayList[#displayList + 1] = fullList[i] end end
  517. for i = 1, #fullList do if fullList[i]:sub(#fullList[i]-3):lower() == ".nbs" then displayList[#displayList + 1] = fs.getName(fullList[i]) end end
  518. end
  519.  
  520. while true do
  521. myEvent = {os.pullEvent()}
  522.  
  523. -- Track animations (bouncing, function (a, b) return string.lower(a) < string.lower(b) end cursor + scrolling marquee).
  524. if myEvent[1] == "timer" and myEvent[2] == animationTimer then
  525. if marquee then marqueePos = marqueePos == #marquee and 1 or (marqueePos + 1) end
  526. curCount = curCount == 4 and 1 or (curCount + 1)
  527. animationTimer = os.startTimer(0.5)
  528. myEvent[1] = "cabbage"
  529.  
  530. -- Queue a new song to start playing, based on the playmode toggles (or if the user clicked the skip-ahead button).
  531. elseif (myEvent[1] == "timer" and myEvent[2] == gapTimer and not note.isPlaying()) or (pressedKey(keys.d,keys.right) or clickedAt(bump+27,bump+32,0,4)) then
  532. if playmode == 1 then
  533. os.queueEvent("musicPlay",lastSong[1])
  534. elseif (playmode == 2 or (playmode == 0 and myEvent[1] ~= "timer")) and not fs.isDir(shell.resolve(displayList[#displayList])) then
  535. if shell.resolve(displayList[position]) == lastSong[1] or fs.isDir(shell.resolve(displayList[position])) then repeat
  536. position = position + 1
  537. if position > #displayList then position = 1 end
  538. until not fs.isDir(shell.resolve(displayList[position])) end
  539.  
  540. startSong(shell.resolve(displayList[position]))
  541. elseif playmode == 3 and not fs.isDir(shell.resolve(displayList[#displayList])) then
  542. repeat position = math.random(#displayList) until not fs.isDir(shell.resolve(displayList[position]))
  543. startSong(shell.resolve(displayList[position]))
  544. end
  545.  
  546. gapTimer = nil
  547. myEvent[1] = "cabbage"
  548.  
  549. elseif myEvent[1] ~= "timer" then -- Special consideration, bearing in mind that the songEngine is spamming ten such events a second...
  550. -- Move down the list.
  551. if pressedKey(keys.down,keys.s) or (myEvent[1] == "mouse_scroll" and myEvent[2] == 1) then
  552. position = position == #displayList and 1 or (position + 1)
  553.  
  554. -- Move up the list.
  555. elseif pressedKey(keys.up,keys.w) or (myEvent[1] == "mouse_scroll" and myEvent[2] == -1) then
  556. position = position == 1 and #displayList or (position - 1)
  557.  
  558. -- Start a new song.
  559. elseif pressedKey(keys.enter, keys.space) or (clickedAt(bump+22,bump+26,0,4) and not note.isPlaying()) or clickedAt(math.floor(ySize / 2) + 1) then
  560. if fs.isDir(shell.resolve(displayList[position])) then
  561. shell.setDir(shell.resolve(displayList[position]))
  562. break
  563. else startSong(shell.resolve(displayList[position])) end
  564.  
  565. -- User clicked somewhere on the file list; move that entry to the currently-selected position.
  566. elseif clickedAt(0, xSize + 1, 3, ySize - 2) then
  567. position = position + myEvent[4] - math.floor(ySize / 2) - 1
  568. position = position > #displayList and #displayList or position
  569. position = position < 1 and 1 or position
  570.  
  571. -- Respond to a screen-resize; triggers a full display redraw.
  572. elseif myEvent[1] == "term_resize" then
  573. xSize, ySize = term.getSize()
  574. bump = math.floor((xSize - 49) / 2) + 1
  575. lastPosition = 0
  576. drawInterface()
  577. animationTimer = os.startTimer(0)
  578. lastPauseState = "maybe"
  579.  
  580. -- Quit.
  581. elseif pressedKey(keys.q, keys.x, keys.t) or clickedAt(xSize, 1) then
  582. if myEvent[1] == "key" then os.pullEvent("char") end
  583. os.unloadAPI("note")
  584. term.setTextColour(colours.white)
  585. term.setBackgroundColour(colours.black)
  586. term.clear()
  587. term.setCursorPos(1,1)
  588. print("Thanks for using the Note NBS player!\n")
  589. shell.setDir(startDir)
  590. error()
  591.  
  592. -- Toggle repeat mode.
  593. elseif pressedKey(keys.r) or clickedAt(bump + 33, bump + 49, 1) then
  594. playmode = playmode == 1 and 0 or 1
  595. drawPlaymode()
  596.  
  597. -- Toggle auto-next mode.
  598. elseif pressedKey(keys.n) or clickedAt(bump + 33, bump + 49, 2) then
  599. playmode = playmode == 2 and 0 or 2
  600. drawPlaymode()
  601.  
  602. -- Toggle mix (shuffle) mode.
  603. elseif pressedKey(keys.m) or clickedAt(bump + 33, bump + 49, 3) then
  604. playmode = playmode == 3 and 0 or 3
  605. drawPlaymode()
  606.  
  607. -- Music finished; wait a second or two before responding.
  608. elseif myEvent[1] == "musicFinished" then
  609. gapTimer = os.startTimer(2)
  610. lastPauseState = "maybe"
  611. marquee = ""
  612.  
  613. -- Skip back to start of the song (or to the previous song, if the current song just started).
  614. elseif pressedKey(keys.a,keys.left) or clickedAt(bump+16,bump+21,0,4) then
  615. if note.isPlaying() and note.getSongPositionSeconds() > 3 then
  616. os.queueEvent("musicSkipTo",0)
  617. os.queueEvent("musicResume")
  618. elseif #lastSong > 1 then
  619. table.remove(lastSong,1)
  620. startSong(table.remove(lastSong,1))
  621. end
  622.  
  623. -- Toggle pause/resume.
  624. elseif note.isPlaying() and (pressedKey(keys.p) or clickedAt(bump+22,bump+26,0,4)) then
  625. if note.isPaused() then os.queueEvent("musicResume") else os.queueEvent("musicPause") end
  626.  
  627. -- Tracking bar clicked.
  628. elseif note.isPlaying() and clickedAt(1, xSize, ySize - 1) then
  629. os.queueEvent("musicSkipTo",math.floor(note.getSongLength()*(myEvent[3]-1)/(xSize-2)))
  630.  
  631. -- Song engine just initiated a new track.
  632. elseif myEvent[1] == "newTrack" then
  633. marquee = " [Title: "
  634. if note.getSongName() ~= "" then marquee = marquee..note.getSongName().."]" else marquee = marquee..fs.getName(lastSong[1]).."]" end
  635. if note.getSongArtist() ~= "" then marquee = marquee.." [Artist: "..note.getSongArtist().."]" end
  636. if note.getSongAuthor() ~= "" then marquee = marquee.." [NBS Author: "..note.getSongAuthor().."]" end
  637. marquee = marquee.." [Tempo: "..note.getSongTempo()
  638. marquee = marquee..(note.getSongTempo() == note.getRealSongTempo() and "]" or (" (NBS: "..note.getRealSongTempo()..")]"))
  639. if note.getSongDescription() ~= "" then marquee = marquee.." [Description: "..note.getSongDescription().."]" end
  640. lastPauseState = "maybe"
  641. end
  642. end
  643.  
  644. -- Play / pause button.
  645. if lastPauseState ~= note.isPaused() then
  646. term.setTextColour(term.isColour() and colours.lightBlue or colours.white)
  647. term.setBackgroundColour(term.isColour() and colours.grey or colours.black)
  648. for i=1,3 do
  649. term.setCursorPos(bump + 23,i)
  650. term.write(buttons[(note.isPlaying() and not note.isPaused()) and 3 or 4][i])
  651. end
  652. lastPauseState = note.isPaused()
  653. end
  654.  
  655. -- Update other screen stuff.
  656. if myEvent[1] ~= "timer" then
  657. term.setTextColour(term.isColour() and colours.black or colours.white)
  658. term.setBackgroundColour(term.isColour() and colours.lightGrey or colours.black)
  659.  
  660. -- Clear old progress bar position.
  661. if lastProgress then
  662. term.setCursorPos(lastProgress,ySize-1)
  663. term.write((lastProgress == 2 or lastProgress == xSize - 1) and "|" or "=")
  664. lastProgress = nil
  665. end
  666.  
  667. -- Song timers.
  668. if note.isPlaying() then
  669. term.setCursorPos(xSize-5,ySize-2)
  670. if term.isColour() and note.getSongTempo() ~= note.getRealSongTempo() then term.setTextColour(colours.red) end
  671.  
  672. local mins = tostring(math.min(99,math.floor(note.getSongSeconds()/60)))
  673. local secs = tostring(math.floor(note.getSongSeconds()%60))
  674. term.write((#mins > 1 and "" or "0")..mins..":"..(#secs > 1 and "" or "0")..secs)
  675.  
  676. term.setCursorPos(2,ySize-2)
  677. if note.isPaused() and bit.band(curCount,1) == 1 then
  678. term.write(" ")
  679. else
  680. mins = tostring(math.min(99,math.floor(note.getSongPositionSeconds()/60)))
  681. secs = tostring(math.floor(note.getSongPositionSeconds()%60))
  682. term.write((#mins > 1 and "" or "0")..mins..":"..(#secs > 1 and "" or "0")..secs)
  683. end
  684.  
  685. -- Progress bar position.
  686. term.setTextColour(term.isColour() and colours.blue or colours.white)
  687. term.setBackgroundColour(colours.black)
  688. lastProgress = 2+math.floor(((xSize-3) * note.getSongPosition() / note.getSongLength()))
  689. term.setCursorPos(lastProgress,ySize-1)
  690. term.write("O")
  691. else
  692. term.setCursorPos(2,ySize-2)
  693. term.write("00:00")
  694. term.setCursorPos(xSize-5,ySize-2)
  695. term.write("00:00")
  696. end
  697.  
  698. -- Scrolling marquee.
  699. if marquee then
  700. term.setTextColour(term.isColour() and colours.black or colours.white)
  701. term.setBackgroundColour(term.isColour() and colours.grey or colours.black)
  702. term.setCursorPos(1,ySize)
  703.  
  704. if marquee == "" then
  705. term.clearLine()
  706. marquee = nil
  707. else
  708. local thisLine = marquee:sub(marqueePos,marqueePos+xSize-1)
  709. while #thisLine < xSize do thisLine = thisLine..marquee:sub(1,xSize-#thisLine) end
  710. term.write(thisLine)
  711. end
  712. end
  713.  
  714. -- File list.
  715. term.setBackgroundColour(colours.black)
  716. for y = position == lastPosition and (math.floor(ySize / 2)+1) or 4, position == lastPosition and (math.floor(ySize / 2)+1) or (ySize - 3) do
  717. local thisLine = y + position - math.floor(ySize / 2) - 1
  718.  
  719. if displayList[thisLine] then
  720. local thisString = displayList[thisLine]
  721. thisString = fs.isDir(shell.resolve(thisString)) and "["..thisString.."]" or thisString:sub(1,#thisString-4)
  722.  
  723. if thisLine == position then
  724. term.setCursorPos(math.floor((xSize - #thisString - 8) / 2)+1, y)
  725. term.clearLine()
  726. term.setTextColour(term.isColour() and colours.cyan or colours.black)
  727. term.write(cursor[curCount][1])
  728. term.setTextColour(term.isColour() and colours.blue or colours.white)
  729. term.write(thisString)
  730. term.setTextColour(term.isColour() and colours.cyan or colours.black)
  731. term.write(cursor[curCount][2])
  732. else
  733. term.setCursorPos(math.floor((xSize - #thisString) / 2)+1, y)
  734. term.clearLine()
  735.  
  736. if y == 4 or y == ySize - 3 then
  737. term.setTextColour(colours.black)
  738. elseif y == 5 or y == ySize - 4 then
  739. term.setTextColour(term.isColour() and colours.grey or colours.black)
  740. elseif y == 6 or y == ySize - 5 then
  741. term.setTextColour(term.isColour() and colours.lightGrey or colours.white)
  742. else term.setTextColour(colours.white) end
  743.  
  744. term.write(thisString)
  745. end
  746. else
  747. term.setCursorPos(1,y)
  748. term.clearLine()
  749. end
  750. end
  751.  
  752. lastPosition = position
  753. end
  754. end
  755. end
  756. end
  757.  
  758. do local args = {...}
  759. for i=1,#args do if args[i]:lower() == "-r" then
  760. playmode = 1
  761. elseif args[i]:lower() == "-n" then
  762. playmode = 2
  763. elseif args[i]:lower() == "-m" then
  764. playmode = 3
  765. elseif fs.isDir(shell.resolve(args[i])) then
  766. shell.setDir(shell.resolve(args[i]))
  767. elseif fs.isDir(args[i]) then
  768. shell.setDir(args[i])
  769. elseif fs.exists(shell.resolve(args[i])) then
  770. local filePath = shell.resolve(args[i])
  771. shell.setDir(fs.getDir(filePath))
  772. startSong(filePath)
  773. elseif fs.exists(shell.resolve(args[i]..".nbs")) then
  774. local filePath = shell.resolve(args[i]..".nbs")
  775. shell.setDir(fs.getDir(filePath))
  776. startSong(filePath)
  777. elseif fs.exists(args[i]) then
  778. shell.setDir(fs.getDir(args[i]))
  779. startSong(args[i])
  780. elseif fs.exists(args[i]..".nbs") then
  781. shell.setDir(fs.getDir(args[i]))
  782. startSong(args[i]..".nbs")
  783. end end end
  784.  
  785. if playmode > 1 then os.queueEvent("musicFinished") end
  786.  
  787. term.setBackgroundColour(colours.black)
  788. term.clear()
  789.  
  790. parallel.waitForAny(note.songEngine, noteMenu)
  791. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement