Advertisement
Guest User

Wave-Amp Modified

a guest
Jun 18th, 2018
98
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.58 KB | None | 0 0
  1. --[[
  2. wave-amp version 1.0.0
  3.  
  4. The MIT License (MIT)
  5. Copyright (c) 2016 CrazedProgrammer
  6.  
  7. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  8. associated documentation files (the "Software"), to deal in the Software without restriction,
  9. including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
  11. so, subject to the following conditions:
  12.  
  13. The above copyright notice and this permission notice shall be included in all copies or
  14. substantial portions of the Software.
  15.  
  16. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  17. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  18. PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  19. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  20. AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. ]]
  23.  
  24. --[[
  25. wave version 0.1.4
  26.  
  27. The MIT License (MIT)
  28. Copyright (c) 2016 CrazedProgrammer
  29.  
  30. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
  31. associated documentation files (the "Software"), to deal in the Software without restriction,
  32. including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
  33. and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
  34. so, subject to the following conditions:
  35.  
  36. The above copyright notice and this permission notice shall be included in all copies or
  37. substantial portions of the Software.
  38.  
  39. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  40. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  41. PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  42. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
  43. AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  44. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  45. ]]
  46.  
  47. term.redirect( term.native() )
  48.  
  49. local wave = { }
  50. wave.version = "0.1.4"
  51.  
  52. wave._oldSoundMap = {"harp", "bassattack", "bd", "snare", "hat"}
  53. wave._newSoundMap = {"harp", "bass", "basedrum", "snare", "hat"}
  54. wave._defaultThrottle = 99
  55. wave._defaultClipMode = 1
  56. wave._maxInterval = 1
  57. wave._isNewSystem = false
  58. if _HOST then
  59. wave._isNewSystem = _HOST:sub(15, #_HOST) >= "1.80"
  60. end
  61.  
  62. wave.context = { }
  63. wave.output = { }
  64. wave.track = { }
  65. wave.instance = { }
  66.  
  67. function wave.createContext(clock, volume)
  68. clock = clock or os.clock()
  69. volume = volume or 1.0
  70.  
  71. local context = setmetatable({ }, {__index = wave.context})
  72. context.outputs = { }
  73. context.instances = { }
  74. context.vs = {0, 0, 0, 0, 0}
  75. context.prevClock = clock
  76. context.volume = volume
  77. return context
  78. end
  79.  
  80. function wave.context:addOutput(...)
  81. local output = wave.createOutput(...)
  82. self.outputs[#self.outputs + 1] = output
  83. return output
  84. end
  85.  
  86. function wave.context:addOutputs(...)
  87. local outs = {...}
  88. if #outs == 1 then
  89. if not getmetatable(outs) then
  90. outs = outs[1]
  91. else
  92. if getmetatable(outs).__index ~= wave.outputs then
  93. outs = outs[1]
  94. end
  95. end
  96. end
  97. for i = 1, #outs do
  98. self:addOutput(outs[i])
  99. end
  100. end
  101.  
  102. function wave.context:removeOutput(out)
  103. if type(out) == "number" then
  104. table.remove(self.outputs, out)
  105. return
  106. elseif type(out) == "table" then
  107. if getmetatable(out).__index == wave.output then
  108. for i = 1, #self.outputs do
  109. if out == self.outputs[i] then
  110. table.remove(self.outputs, i)
  111. return
  112. end
  113. end
  114. return
  115. end
  116. end
  117. for i = 1, #self.outputs do
  118. if out == self.outputs[i].native then
  119. table.remove(self.outputs, i)
  120. return
  121. end
  122. end
  123. end
  124.  
  125. function wave.context:addInstance(...)
  126. local instance = wave.createInstance(...)
  127. self.instances[#self.instances + 1] = instance
  128. return instance
  129. end
  130.  
  131. function wave.context:removeInstance(instance)
  132. if type(instance) == "number" then
  133. table.remove(self.instances, instance)
  134. else
  135. for i = 1, #self.instances do
  136. if self.instances == instance then
  137. table.remove(self.instances, i)
  138. return
  139. end
  140. end
  141. end
  142. end
  143.  
  144. function wave.context:playNote(note, pitch, volume)
  145. volume = volume or 1.0
  146.  
  147. self.vs[note] = self.vs[note] + volume
  148. for i = 1, #self.outputs do
  149. self.outputs[i]:playNote(note, pitch, volume * self.volume)
  150. end
  151. end
  152.  
  153. function wave.context:update(interval)
  154. local clock = os.clock()
  155. interval = interval or (clock - self.prevClock)
  156.  
  157. self.prevClock = clock
  158. if interval > wave._maxInterval then
  159. interval = wave._maxInterval
  160. end
  161. for i = 1, #self.outputs do
  162. self.outputs[i].notes = 0
  163. end
  164. for i = 1, 5 do
  165. self.vs[i] = 0
  166. end
  167. if interval > 0 then
  168. for i = 1, #self.instances do
  169. local notes = self.instances[i]:update(interval)
  170. for j = 1, #notes / 3 do
  171. self:playNote(notes[j * 3 - 2], notes[j * 3 - 1], notes[j * 3])
  172. end
  173. end
  174. end
  175. end
  176.  
  177.  
  178.  
  179. function wave.createOutput(out, volume, filter, throttle, clipMode)
  180. volume = volume or 1.0
  181. filter = filter or {true, true, true, true, true}
  182. throttle = throttle or wave._defaultThrottle
  183. clipMode = clipMode or wave._defaultClipMode
  184.  
  185. local output = setmetatable({ }, {__index = wave.output})
  186. output.native = out
  187. output.volume = volume
  188. output.filter = filter
  189. output.notes = 0
  190. output.throttle = throttle
  191. output.clipMode = clipMode
  192. if type(out) == "function" then
  193. output.nativePlayNote = out
  194. output.type = "custom"
  195. return output
  196. elseif type(out) == "string" then
  197. if peripheral.getType(out) == "iron_noteblock" then
  198. if wave._isNewSystem then
  199. local nb = peripheral.wrap(out)
  200. output.type = "iron_noteblock"
  201. function output.nativePlayNote(note, pitch, volume)
  202. if output.volume * volume > 0 then
  203. nb.playSound("minecraft:block.note."..wave._newSoundMap[note], volume, math.pow(2, (pitch - 12) / 12))
  204. end
  205. end
  206. return output
  207. end
  208. end
  209. elseif type(out) == "table" then
  210. if out.execAsync then
  211. output.type = "commands"
  212. if wave._isNewSystem then
  213. function output.nativePlayNote(note, pitch, volume)
  214. out.execAsync("playsound minecraft:block.note."..wave._newSoundMap[note].." record @a ~ ~ ~ "..tostring(volume).." "..tostring(math.pow(2, (pitch - 12) / 12)))
  215. end
  216. else
  217. function output.nativePlayNote(note, pitch, volume)
  218. out.execAsync("playsound note."..wave._oldSoundMap[note].." @a ~ ~ ~ "..tostring(volume).." "..tostring(math.pow(2, (pitch - 12) / 12)))
  219. end
  220. end
  221. return output
  222. elseif getmetatable(out) then
  223. if getmetatable(out).__index == wave.output then
  224. return out
  225. end
  226. end
  227. end
  228. end
  229.  
  230. function wave.scanOutputs()
  231. local outs = { }
  232. if commands then
  233. outs[#outs + 1] = wave.createOutput(commands)
  234. end
  235. local sides = peripheral.getNames()
  236. for i = 1, #sides do
  237. if peripheral.getType(sides[i]) == "iron_noteblock" then
  238. outs[#outs + 1] = wave.createOutput(sides[i])
  239. end
  240. end
  241. return outs
  242. end
  243.  
  244. function wave.output:playNote(note, pitch, volume)
  245. volume = volume or 1.0
  246.  
  247. if self.clipMode == 1 then
  248. if pitch < 0 then
  249. pitch = 0
  250. elseif pitch > 24 then
  251. pitch = 24
  252. end
  253. elseif self.clipMode == 2 then
  254. if pitch < 0 then
  255. while pitch < 0 do
  256. pitch = pitch + 12
  257. end
  258. elseif pitch > 24 then
  259. while pitch > 24 do
  260. pitch = pitch - 12
  261. end
  262. end
  263. end
  264. if self.filter[note] and self.notes < self.throttle then
  265. self.nativePlayNote(note, pitch, volume * self.volume)
  266. self.notes = self.notes + 1
  267. end
  268. end
  269.  
  270.  
  271.  
  272. function wave.loadTrack(path)
  273. local track = setmetatable({ }, {__index = wave.track})
  274. local handle = fs.open(path, "rb")
  275. if not handle then return end
  276.  
  277. local function readInt(size)
  278. local num = 0
  279. for i = 0, size - 1 do
  280. local byte = handle.read()
  281. if not byte then -- dont leave open file handles no matter what
  282. handle.close()
  283. return
  284. end
  285. num = num + byte * (256 ^ i)
  286. end
  287. return num
  288. end
  289. local function readStr()
  290. local length = readInt(4)
  291. if not length then return end
  292. local data = { }
  293. for i = 1, length do
  294. data[i] = string.char(handle.read())
  295. end
  296. return table.concat(data)
  297. end
  298.  
  299. -- Part #1: Metadata
  300. track.length = readInt(2) -- song length (ticks)
  301. track.height = readInt(2) -- song height
  302. track.name = readStr() -- song name
  303. track.author = readStr() -- song author
  304. track.originalAuthor = readStr() -- original song author
  305. track.description = readStr() -- song description
  306. track.tempo = readInt(2) / 100 -- tempo (ticks per second)
  307. track.autoSaving = readInt(1) == 0 and true or false -- auto-saving
  308. track.autoSavingDuration = readInt(1) -- auto-saving duration
  309. track.timeSignature = readInt(1) -- time signature (3 = 3/4)
  310. track.minutesSpent = readInt(4) -- minutes spent
  311. track.leftClicks = readInt(4) -- left clicks
  312. track.rightClicks = readInt(4) -- right clicks
  313. track.blocksAdded = readInt(4) -- blocks added
  314. track.blocksRemoved = readInt(4) -- blocks removed
  315. track.schematicFileName = readStr() -- midi/schematic file name
  316.  
  317. -- Part #2: Notes
  318. track.layers = { }
  319. for i = 1, track.height do
  320. track.layers[i] = {name = "Layer "..i, volume = 1.0}
  321. track.layers[i].notes = { }
  322. end
  323.  
  324. local tick = 0
  325. while true do
  326. local tickJumps = readInt(2)
  327. if tickJumps == 0 then break end
  328. tick = tick + tickJumps
  329. local layer = 0
  330. while true do
  331. local layerJumps = readInt(2)
  332. if layerJumps == 0 then break end
  333. layer = layer + layerJumps
  334. if layer > track.height then -- nbs can be buggy
  335. for i = track.height + 1, layer do
  336. track.layers[i] = {name = "Layer "..i, volume = 1.0}
  337. track.layers[i].notes = { }
  338. end
  339. track.height = layer
  340. end
  341. local instrument = readInt(1)
  342. local key = readInt(1)
  343. if instrument <= 4 then -- nbs can be buggy
  344. track.layers[layer].notes[tick * 2 - 1] = instrument + 1
  345. track.layers[layer].notes[tick * 2] = key - 33
  346. end
  347. end
  348. end
  349.  
  350. -- Part #3: Layers
  351. for i = 1, track.height do
  352. local name = readStr()
  353. if not name then break end -- if layer data doesnt exist, abort
  354. track.layers[i].name = name
  355. track.layers[i].volume = readInt(1) / 100
  356. end
  357.  
  358. handle.close()
  359. return track
  360. end
  361.  
  362.  
  363.  
  364. function wave.createInstance(track, volume, playing, loop)
  365. volume = volume or 1.0
  366. playing = (playing == nil) or playing
  367. loop = (loop ~= nil) and loop
  368.  
  369. if getmetatable(track).__index == wave.instance then
  370. return track
  371. end
  372. local instance = setmetatable({ }, {__index = wave.instance})
  373. instance.track = track
  374. instance.volume = volume or 1.0
  375. instance.playing = playing
  376. instance.loop = loop
  377. instance.tick = 1
  378. return instance
  379. end
  380.  
  381. function wave.instance:update(interval)
  382. local notes = { }
  383. if self.playing then
  384. local dticks = interval * self.track.tempo
  385. local starttick = self.tick
  386. local endtick = starttick + dticks
  387. local istarttick = math.ceil(starttick)
  388. local iendtick = math.ceil(endtick) - 1
  389. for i = istarttick, iendtick do
  390. for j = 1, self.track.height do
  391. if self.track.layers[j].notes[i * 2 - 1] then
  392. notes[#notes + 1] = self.track.layers[j].notes[i * 2 - 1]
  393. notes[#notes + 1] = self.track.layers[j].notes[i * 2]
  394. notes[#notes + 1] = self.track.layers[j].volume
  395. end
  396. end
  397. end
  398. self.tick = self.tick + dticks
  399.  
  400. if endtick > self.track.length then
  401. self.tick = 1
  402. self.playing = self.loop
  403. end
  404. end
  405. return notes
  406. end
  407.  
  408.  
  409.  
  410. local cmdHelp = [[
  411. -l lists all outputs connected to the computer.
  412. -c <config file> loads the parameters from a file.
  413. parameters are separated by newlines.
  414. -t <theme file> loads the theme from a file.
  415. -f <filter[:second]> sets the note filter for the outputs.
  416. examples:
  417. -f 10111 sets the filter for all outputs to remove the bass instrument.
  418. -f 10011:01100 sets the filter so the bass and basedrum instruments only come out of the second output
  419. -v <volume[:second]> sets the volume for the outputs.
  420. --nrm --stp --rep --shf sets the play mode.
  421. --noui --noinput disables the ui/keyboard input]]
  422.  
  423.  
  424. local trackMode = 1
  425. -- 1 = normal (go to next song on finish)
  426. -- 2 = stop (stop on finish)
  427. -- 3 = repeat (restart song on finish)
  428. -- 4 = shuffle (go to random song on finish)
  429.  
  430. local files = { }
  431. local tracks = { }
  432. local context, track, instance
  433.  
  434. -- ui stuff
  435. local noUI = false
  436. local noInput = false
  437. local screenWidth, screenHeight = term.getSize()
  438. local trackScroll = 0
  439. local currentTrack = 1
  440. local vsEasings = {0, 0, 0, 0, 0}
  441. local vsStep = 5
  442. local vsDecline = 0.25
  443.  
  444. -- theme
  445. local theme = term.isColor() and
  446. {
  447. topBar = colors.lime,
  448. topBarTitle = colors.white,
  449. topBarOption = colors.white,
  450. topBarOptionSelected = colors.lightGray,
  451. topBarClose = colors.white,
  452. song = colors.black,
  453. songBackground = colors.white,
  454. songSelected = colors.black,
  455. songSelectedBackground = colors.lightGray,
  456. scrollBackground = colors.lightGray,
  457. scrollBar = colors.gray,
  458. scrollButton = colors.black,
  459. visualiserBar = colors.lime,
  460. visualiserBackground = colors.green,
  461. progressTime = colors.white,
  462. progressBackground = colors.lightGray,
  463. progressLine = colors.gray,
  464. progressNub = colors.gray,
  465. progressNubBackground = colors.gray,
  466. progressNubChar = "=",
  467. progressButton = colors.white
  468. }
  469. or
  470. {
  471. topBar = colors.lightGray,
  472. topBarTitle = colors.white,
  473. topBarOption = colors.white,
  474. topBarOptionSelected = colors.gray,
  475. topBarClose = colors.white,
  476. song = colors.black,
  477. songBackground = colors.white,
  478. songSelected = colors.black,
  479. songSelectedBackground = colors.lightGray,
  480. scrollBackground = colors.lightGray,
  481. scrollBar = colors.gray,
  482. scrollButton = colors.black,
  483. visualiserBar = colors.black,
  484. visualiserBackground = colors.gray,
  485. progressTime = colors.white,
  486. progressBackground = colors.lightGray,
  487. progressLine = colors.gray,
  488. progressNub = colors.gray,
  489. progressNubBackground = colors.gray,
  490. progressNubChar = "=",
  491. progressButton = colors.white
  492. }
  493.  
  494. local running = true
  495.  
  496.  
  497.  
  498. local function addFiles(path)
  499. local dirstack = {path}
  500. while #dirstack > 0 do
  501. local dir = dirstack[1]
  502. table.remove(dirstack, 1)
  503. if dir ~= "rom" then
  504. for _, v in pairs(fs.list(dir)) do
  505. local path = (dir == "") and v or dir.."/"..v
  506. if fs.isDir(path) then
  507. dirstack[#dirstack + 1] = path
  508. elseif path:sub(#path - 3, #path) == ".nbs" then
  509. files[#files + 1] = path
  510. end
  511. end
  512. end
  513. end
  514. end
  515.  
  516. local function init(args)
  517. local volumes = { }
  518. local filters = { }
  519. local outputs = wave.scanOutputs()
  520. local timestamp = 0
  521.  
  522. if #outputs == 0 then
  523. error("no outputs found")
  524. end
  525.  
  526. local i, argtype = 1
  527. while i <= #args do
  528. if not argtype then
  529. if args[i] == "-h" then
  530. print(cmdHelp)
  531. noUI = true
  532. running = false
  533. return
  534. elseif args[i] == "-c" or args[i] == "-v" or args[i] == "-f" or args[i] == "-t" then
  535. argtype = args[i]
  536. elseif args[i] == "-l" then
  537. print(#outputs.." outputs detected:")
  538. for i = 1, #outputs do
  539. print(i..":", outputs[i].type, type(outputs[i].native) == "string" and outputs[i].native or "")
  540. end
  541. noUI = true
  542. running = false
  543. return
  544. elseif args[i] == "--noui" then
  545. noUI = true
  546. elseif args[i] == "--noinput" then
  547. noInput = true
  548. elseif args[i] == "--nrm" then
  549. trackMode = 1
  550. elseif args[i] == "--stp" then
  551. trackMode = 2
  552. elseif args[i] == "--rep" then
  553. trackMode = 3
  554. elseif args[i] == "--shf" then
  555. trackMode = 4
  556. else
  557. local path = shell.resolve(args[i])
  558. if fs.isDir(path) then
  559. addFiles(path)
  560. elseif fs.exists(path) then
  561. files[#files + 1] = path
  562. end
  563. end
  564. else
  565. if argtype == "-c" then
  566. local path = shell.resolve(args[i])
  567. local handle = fs.open(path, "r")
  568. if not handle then
  569. error("config file does not exist: "..path)
  570. end
  571. local line = handle.readLine()
  572. while line do
  573. args[#args + 1] = line
  574. line = handle.readLine()
  575. end
  576. handle.close()
  577. elseif argtype == "-t" then
  578. local path = shell.resolve(args[i])
  579. local handle = fs.open(path, "r")
  580. if not handle then
  581. error("theme file does not exist: "..path)
  582. end
  583. local data = handle.readAll()
  584. handle.close()
  585. for k, v in pairs(colors) do
  586. data = data:gsub("colors."..k, tostring(v))
  587. end
  588. for k, v in pairs(colours) do
  589. data = data:gsub("colours."..k, tostring(v))
  590. end
  591. local newtheme = textutils.unserialize(data)
  592. for k, v in pairs(newtheme) do
  593. theme[k] = v
  594. end
  595. elseif argtype == "-v" then
  596. for str in args[i]:gmatch("([^:]+)") do
  597. local vol = tonumber(str)
  598. if vol then
  599. if vol >= 0 and vol <= 1 then
  600. volumes[#volumes + 1] = vol
  601. else
  602. error("invalid volume value: "..str)
  603. end
  604. else
  605. error("invalid volume value: "..str)
  606. end
  607. end
  608. elseif argtype == "-f" then
  609. for str in args[i]:gmatch("([^:]+)") do
  610. if #str == 5 then
  611. local filter = { }
  612. for i = 1, 5 do
  613. if str:sub(i, i) == "1" then
  614. filter[i] = true
  615. elseif str:sub(i, i) == "0" then
  616. filter[i] = false
  617. else
  618. error("invalid filter value: "..str)
  619. end
  620. end
  621. filters[#filters + 1] = filter
  622. else
  623. error("invalid filter value: "..str)
  624. end
  625. end
  626. end
  627. argtype = nil
  628. end
  629. i = i + 1
  630. end
  631.  
  632. if #files == 0 then
  633. addFiles("")
  634. end
  635.  
  636. i = 1
  637. print("loading tracks...")
  638. while i <= #files do
  639. local track
  640. pcall(function () track = wave.loadTrack(files[i]) end)
  641. if not track then
  642. print("failed to load "..files[i])
  643. os.sleep(0.2)
  644. table.remove(files, i)
  645. else
  646. tracks[i] = track
  647. print("loaded "..files[i])
  648. i = i + 1
  649. end
  650. if i % 10 == 0 then
  651. os.sleep(0)
  652. end
  653. end
  654. if #files == 0 then
  655. error("no tracks found")
  656. end
  657.  
  658. if #volumes == 0 then
  659. volumes[1] = 1
  660. end
  661. if #filters == 0 then
  662. filters[1] = {true, true, true, true, true}
  663. end
  664. if #volumes == 1 then
  665. for i = 2, #outputs do
  666. volumes[i] = volumes[1]
  667. end
  668. end
  669. if #filters == 1 then
  670. for i = 2, #outputs do
  671. filters[i] = filters[1]
  672. end
  673. end
  674. if #volumes ~= #outputs then
  675. error("invalid amount of volume values: "..#volumes.." (must be 1 or "..#outputs..")")
  676. end
  677. if #filters ~= #outputs then
  678. error("invalid amount of filter values: "..#filters.." (must be 1 or "..#outputs..")")
  679. end
  680.  
  681. for i = 1, #outputs do
  682. outputs[i].volume = volumes[i]
  683. outputs[i].filter = filters[i]
  684. end
  685.  
  686. context = wave.createContext()
  687. context:addOutputs(outputs)
  688. end
  689.  
  690.  
  691.  
  692.  
  693. local function formatTime(secs)
  694. local mins = math.floor(secs / 60)
  695. secs = secs - mins * 60
  696. return string.format("%01d:%02d", mins, secs)
  697. end
  698.  
  699. local function drawStatic()
  700. if noUI then return end
  701. term.setCursorPos(1, 1)
  702. term.setBackgroundColor(theme.topBar)
  703. term.setTextColor(theme.topBarTitle)
  704. term.write("wave-amp")
  705. term.write((" "):rep(screenWidth - 25))
  706. term.setTextColor(trackMode == 1 and theme.topBarOptionSelected or theme.topBarOption)
  707. term.write("nrm ")
  708. term.setTextColor(trackMode == 2 and theme.topBarOptionSelected or theme.topBarOption)
  709. term.write("stp ")
  710. term.setTextColor(trackMode == 3 and theme.topBarOptionSelected or theme.topBarOption)
  711. term.write("rep ")
  712. term.setTextColor(trackMode == 4 and theme.topBarOptionSelected or theme.topBarOption)
  713. term.write("shf ")
  714. term.setTextColor(theme.topBarClose)
  715. term.write("X")
  716.  
  717. local scrollnub = math.floor(trackScroll / (#tracks - screenHeight + 7) * (screenHeight - 10) + 0.5)
  718.  
  719. term.setTextColor(theme.song)
  720. term.setBackgroundColor(theme.songBackground)
  721. for i = 1, screenHeight - 7 do
  722. local index = i + trackScroll
  723. term.setCursorPos(1, i + 1)
  724. term.setTextColor(index == currentTrack and theme.songSelected or theme.song)
  725. term.setBackgroundColor(index == currentTrack and theme.songSelectedBackground or theme.songBackground)
  726. local str = ""
  727. if tracks[index] then
  728. local track = tracks[index]
  729. str = formatTime(track.length / track.tempo).." "
  730. if #track.name > 0 then
  731. str = str..(#track.originalAuthor == 0 and track.author or track.originalAuthor).." - "..track.name
  732. else
  733. local name = fs.getName(files[index])
  734. str = str..name:sub(1, #name - 4)
  735. end
  736. end
  737. if #str > screenWidth - 1 then
  738. str = str:sub(1, screenWidth - 3)..".."
  739. end
  740. term.write(str)
  741. term.write((" "):rep(screenWidth - 1 - #str))
  742. term.setBackgroundColor((i >= scrollnub + 1 and i <= scrollnub + 3) and theme.scrollBar or theme.scrollBackground)
  743. if i == 1 then
  744. term.setTextColor(theme.scrollButton)
  745. term.write(_HOST and "-" or "^")
  746. elseif i == screenHeight - 7 then
  747. term.setTextColor(theme.scrollButton)
  748. term.write(_HOST and "-" or "v")
  749. else
  750. term.write(" ")
  751. end
  752. end
  753. end
  754.  
  755. local function drawDynamic()
  756. if noUI then return end
  757. for i = 1, 5 do
  758. vsEasings[i] = vsEasings[i] - vsDecline
  759. if vsEasings[i] < 0 then
  760. vsEasings[i] = 0
  761. end
  762. local part = context.vs[i] > vsStep and vsStep or context.vs[i]
  763. if vsEasings[i] < part then
  764. vsEasings[i] = part
  765. end
  766. local full = math.floor(part / vsStep * screenWidth + 0.5)
  767. local easing = math.floor(vsEasings[i] / vsStep * screenWidth + 0.5)
  768. term.setCursorPos(1, screenHeight - 6 + i)
  769. term.setBackgroundColor(theme.visualiserBar)
  770. term.setTextColor(theme.visualiserBackground)
  771. term.write((" "):rep(full))
  772. term.write((_HOST and "-" or "#"):rep(math.floor((easing - full) / 2)))
  773. term.setBackgroundColor(theme.visualiserBackground)
  774. term.setTextColor(theme.visualiserBar)
  775. term.write((_HOST and "-" or "#"):rep(math.ceil((easing - full) / 2)))
  776. term.write((" "):rep(screenWidth - easing))
  777. end
  778.  
  779. local progressnub = math.floor((instance.tick / track.length) * (screenWidth - 14) + 0.5)
  780.  
  781. term.setCursorPos(1, screenHeight)
  782. term.setTextColor(theme.progressTime)
  783. term.setBackgroundColor(theme.progressBackground)
  784. term.write(formatTime(instance.tick / track.tempo))
  785.  
  786. term.setTextColor(theme.progressLine)
  787. term.write("-")
  788. term.write(("-"):rep(progressnub))
  789. term.setTextColor(theme.progressNub)
  790. term.setBackgroundColor(theme.progressNubBackground)
  791. term.write(theme.progressNubChar)
  792. term.setTextColor(theme.progressLine)
  793. term.setBackgroundColor(theme.progressBackground)
  794. term.write(("-"):rep(screenWidth - 14 - progressnub))
  795. term.write("-")
  796.  
  797. term.setTextColor(theme.progressTime)
  798. term.write(formatTime(track.length / track.tempo).." ")
  799. term.setTextColor(theme.progressButton)
  800. term.write(instance.playing and (_HOST and "|-" or "|>") or "||")
  801. end
  802.  
  803. local function playSong(index)
  804. if index >= 1 and index <= #tracks then
  805. currentTrack = index
  806. track = tracks[currentTrack]
  807. context:removeInstance(1)
  808. instance = context:addInstance(track, 1, trackMode ~= 2, trackMode == 3)
  809. if currentTrack <= trackScroll then
  810. trackScroll = currentTrack - 1
  811. end
  812. if currentTrack > trackScroll + screenHeight - 7 then
  813. trackScroll = currentTrack - screenHeight + 7
  814. end
  815. drawStatic()
  816. end
  817. end
  818.  
  819. local function nextSong()
  820. if trackMode == 1 then
  821. playSong(currentTrack + 1)
  822. elseif trackMode == 4 then
  823. playSong(math.random(#tracks))
  824. end
  825. end
  826.  
  827. local function setScroll(scroll)
  828. trackScroll = scroll
  829. if trackScroll > #tracks - screenHeight + 7 then
  830. trackScroll = #tracks - screenHeight + 7
  831. end
  832. if trackScroll < 0 then
  833. trackScroll = 0
  834. end
  835. drawStatic()
  836. end
  837.  
  838. local function handleClick(x, y)
  839. if noUI then return end
  840. if y == 1 then
  841. if x == screenWidth then
  842. running = false
  843. elseif x >= screenWidth - 16 and x <= screenWidth - 2 and (x - screenWidth + 1) % 4 ~= 0 then
  844. trackMode = math.floor((x - screenWidth + 16) / 4) + 1
  845. instance.loop = trackMode == 3
  846. drawStatic()
  847. end
  848. elseif x < screenWidth and y >= 2 and y <= screenHeight - 6 then
  849. playSong(y - 1 + trackScroll)
  850. elseif x == screenWidth and y == 2 then
  851. setScroll(trackScroll - 2)
  852. elseif x == screenWidth and y == screenHeight - 6 then
  853. setScroll(trackScroll + 2)
  854. elseif x == screenWidth and y >= 3 and y <= screenHeight - 7 then
  855. setScroll(math.floor((y - 3) / (screenHeight - 10) * (#tracks - screenHeight + 7 ) + 0.5))
  856. elseif y == screenHeight then
  857. if x >= screenWidth - 1 and x <= screenWidth then
  858. instance.playing = not instance.playing
  859. elseif x >= 6 and x <= screenWidth - 8 then
  860. instance.tick = ((x - 6) / (screenWidth - 14)) * track.length
  861. end
  862. end
  863. end
  864.  
  865. local function handleScroll(x, y, scroll)
  866. if noUI then return end
  867. if y >= 2 and y <= screenHeight - 6 then
  868. setScroll(trackScroll + scroll * 2)
  869. end
  870. end
  871.  
  872. local function handleKey(key)
  873. if noInput then return end
  874. if key == keys.space then
  875. instance.playing = not instance.playing
  876. elseif key == keys.n then
  877. nextSong()
  878. elseif key == keys.p then
  879. playSong(currentTrack - 1)
  880. elseif key == keys.m then
  881. context.volume = (context.volume == 0) and 1 or 0
  882. elseif key == keys.left then
  883. instance.tick = instance.tick - track.tempo * 10
  884. if instance.tick < 1 then
  885. instance.tick = 1
  886. end
  887. elseif key == keys.right then
  888. instance.tick = instance.tick + track.tempo * 10
  889. elseif key == keys.up then
  890. context.volume = (context.volume == 1) and 1 or context.volume + 0.1
  891. elseif key == keys.down then
  892. context.volume = (context.volume == 0) and 0 or context.volume - 0.1
  893. elseif key == keys.j then
  894. setScroll(trackScroll + 2)
  895. elseif key == keys.k then
  896. setScroll(trackScroll - 2)
  897. elseif key == keys.pageUp then
  898. setScroll(trackScroll - 5)
  899. elseif key == keys.pageDown then
  900. setScroll(trackScroll + 5)
  901. elseif key == keys.leftShift then
  902. trackMode = trackMode % 4 + 1
  903. drawStatic()
  904. elseif key == keys.backspace then
  905. running = false
  906. end
  907. end
  908.  
  909. local function run()
  910. playSong(1)
  911. drawStatic()
  912. drawDynamic()
  913. local timer = os.startTimer(0.05)
  914. while running do
  915. local e = {os.pullEventRaw()}
  916. if e[1] == "timer" and e[2] == timer then
  917. timer = os.startTimer(0)
  918. local prevtick = instance.tick
  919. context:update()
  920. if prevtick > 1 and instance.tick == 1 then
  921. nextSong()
  922. end
  923. drawDynamic()
  924. elseif e[1] == "terminate" then
  925. running = false
  926. elseif e[1] == "term_resize" then
  927. screenWidth, screenHeight = term.getSize()
  928. elseif e[1] == "mouse_click" then
  929. handleClick(e[3], e[4])
  930. elseif e[1] == "mouse_scroll" then
  931. handleScroll(e[3], e[4], e[2])
  932. elseif e[1] == "key" then
  933. handleKey(e[2])
  934. end
  935. end
  936. end
  937.  
  938. local function exit()
  939. if noUI then return end
  940. term.setBackgroundColor(colors.black)
  941. term.setTextColor(colors.white)
  942. term.setCursorPos(1, 1)
  943. term.clear()
  944. end
  945.  
  946. init({...})
  947. run()
  948. exit()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement