Advertisement
arismoko

wa

Sep 29th, 2021
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.22 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. local wave = dofile("wave/wave.lua")
  25.  
  26. local cmdHelp = [[
  27. -l lists all outputs connected to the computer.
  28. -c <config file> loads the parameters from a file.
  29. parameters are separated by newlines.
  30. -t <theme file> loads the theme from a file.
  31. -f <filter[:second]> sets the note filter for the outputs.
  32. examples:
  33. -f 10111 sets the filter for all outputs to remove the bass instrument.
  34. -f 10011:01100 sets the filter so the bass and basedrum instruments only come out of the second output
  35. -v <volume[:second]> sets the volume for the outputs.
  36. --nrm --stp --rep --shf sets the play mode.
  37. --noui --noinput disables the ui/keyboard input]]
  38.  
  39.  
  40. local trackMode = 1
  41. -- 1 = normal (go to next song on finish)
  42. -- 2 = stop (stop on finish)
  43. -- 3 = repeat (restart song on finish)
  44. -- 4 = shuffle (go to random song on finish)
  45.  
  46. local files = { }
  47. local tracks = { }
  48. local context, track, instance
  49.  
  50. -- ui stuff
  51. local noUI = false
  52. local noInput = false
  53. local screenWidth, screenHeight = term.getSize()
  54. local trackScroll = 0
  55. local currentTrack = 1
  56. local vsEasings = {0, 0, 0, 0, 0}
  57. local vsStep = 5
  58. local vsDecline = 0.25
  59.  
  60. -- theme
  61. local theme = term.isColor() and
  62. {
  63. topBar = colors.lime,
  64. topBarTitle = colors.white,
  65. topBarOption = colors.white,
  66. topBarOptionSelected = colors.lightGray,
  67. topBarClose = colors.white,
  68. song = colors.black,
  69. songBackground = colors.white,
  70. songSelected = colors.black,
  71. songSelectedBackground = colors.lightGray,
  72. scrollBackground = colors.lightGray,
  73. scrollBar = colors.gray,
  74. scrollButton = colors.black,
  75. visualiserBar = colors.lime,
  76. visualiserBackground = colors.green,
  77. progressTime = colors.white,
  78. progressBackground = colors.lightGray,
  79. progressLine = colors.gray,
  80. progressNub = colors.gray,
  81. progressNubBackground = colors.gray,
  82. progressNubChar = "=",
  83. progressButton = colors.white
  84. }
  85. or
  86. {
  87. topBar = colors.lightGray,
  88. topBarTitle = colors.white,
  89. topBarOption = colors.white,
  90. topBarOptionSelected = colors.gray,
  91. topBarClose = colors.white,
  92. song = colors.black,
  93. songBackground = colors.white,
  94. songSelected = colors.black,
  95. songSelectedBackground = colors.lightGray,
  96. scrollBackground = colors.lightGray,
  97. scrollBar = colors.gray,
  98. scrollButton = colors.black,
  99. visualiserBar = colors.black,
  100. visualiserBackground = colors.gray,
  101. progressTime = colors.white,
  102. progressBackground = colors.lightGray,
  103. progressLine = colors.gray,
  104. progressNub = colors.gray,
  105. progressNubBackground = colors.gray,
  106. progressNubChar = "=",
  107. progressButton = colors.white
  108. }
  109.  
  110. local running = true
  111.  
  112.  
  113.  
  114. local function addFiles(path)
  115. local dirstack = {path}
  116. while #dirstack > 0 do
  117. local dir = dirstack[1]
  118. table.remove(dirstack, 1)
  119. if dir ~= "rom" then
  120. for _, v in pairs(fs.list(dir)) do
  121. local path = (dir == "") and v or dir.."/"..v
  122. if fs.isDir(path) then
  123. dirstack[#dirstack + 1] = path
  124. elseif path:sub(#path - 3, #path) == ".nbs" then
  125. files[#files + 1] = path
  126. end
  127. end
  128. end
  129. end
  130. end
  131.  
  132. local function init(args)
  133. local volumes = { }
  134. local filters = { }
  135. local outputs = wave.scanOutputs()
  136. local timestamp = 0
  137.  
  138. if #outputs == 0 then
  139. error("no outputs found")
  140. end
  141.  
  142. local i, argtype = 1
  143. while i <= #args do
  144. if not argtype then
  145. if args[i] == "-h" then
  146. print(cmdHelp)
  147. noUI = true
  148. running = false
  149. return
  150. elseif args[i] == "-c" or args[i] == "-v" or args[i] == "-f" or args[i] == "-t" then
  151. argtype = args[i]
  152. elseif args[i] == "-l" then
  153. print(#outputs.." outputs detected:")
  154. for i = 1, #outputs do
  155. print(i..":", outputs[i].type, type(outputs[i].native) == "string" and outputs[i].native or "")
  156. end
  157. noUI = true
  158. running = false
  159. return
  160. elseif args[i] == "--noui" then
  161. noUI = true
  162. elseif args[i] == "--noinput" then
  163. noInput = true
  164. elseif args[i] == "--nrm" then
  165. trackMode = 1
  166. elseif args[i] == "--stp" then
  167. trackMode = 2
  168. elseif args[i] == "--rep" then
  169. trackMode = 3
  170. elseif args[i] == "--shf" then
  171. trackMode = 4
  172. else
  173. local path = shell.resolve(args[i])
  174. if fs.isDir(path) then
  175. addFiles(path)
  176. elseif fs.exists(path) then
  177. files[#files + 1] = path
  178. end
  179. end
  180. else
  181. if argtype == "-c" then
  182. local path = shell.resolve(args[i])
  183. local handle = fs.open(path, "r")
  184. if not handle then
  185. error("config file does not exist: "..path)
  186. end
  187. local line = handle.readLine()
  188. while line do
  189. args[#args + 1] = line
  190. line = handle.readLine()
  191. end
  192. handle.close()
  193. elseif argtype == "-t" then
  194. local path = shell.resolve(args[i])
  195. local handle = fs.open(path, "r")
  196. if not handle then
  197. error("theme file does not exist: "..path)
  198. end
  199. local data = handle.readAll()
  200. handle.close()
  201. for k, v in pairs(colors) do
  202. data = data:gsub("colors."..k, tostring(v))
  203. end
  204. for k, v in pairs(colours) do
  205. data = data:gsub("colours."..k, tostring(v))
  206. end
  207. local newtheme = textutils.unserialize(data)
  208. for k, v in pairs(newtheme) do
  209. theme[k] = v
  210. end
  211. elseif argtype == "-v" then
  212. for str in args[i]:gmatch("([^:]+)") do
  213. local vol = tonumber(str)
  214. if vol then
  215. if vol >= 0 and vol <= 1 then
  216. volumes[#volumes + 1] = vol
  217. else
  218. error("invalid volume value: "..str)
  219. end
  220. else
  221. error("invalid volume value: "..str)
  222. end
  223. end
  224. elseif argtype == "-f" then
  225. for str in args[i]:gmatch("([^:]+)") do
  226. if #str == 5 then
  227. local filter = { }
  228. for i = 1, 5 do
  229. if str:sub(i, i) == "1" then
  230. filter[i] = true
  231. elseif str:sub(i, i) == "0" then
  232. filter[i] = false
  233. else
  234. error("invalid filter value: "..str)
  235. end
  236. end
  237. filters[#filters + 1] = filter
  238. else
  239. error("invalid filter value: "..str)
  240. end
  241. end
  242. end
  243. argtype = nil
  244. end
  245. i = i + 1
  246. end
  247.  
  248. if #files == 0 then
  249. addFiles("")
  250. end
  251.  
  252. i = 1
  253. print("loading tracks...")
  254. while i <= #files do
  255. local track
  256. pcall(function () track = wave.loadTrack(files[i]) end)
  257. if not track then
  258. print("failed to load "..files[i])
  259. os.sleep(0.2)
  260. table.remove(files, i)
  261. else
  262. tracks[i] = track
  263. print("loaded "..files[i])
  264. i = i + 1
  265. end
  266. if i % 10 == 0 then
  267. os.sleep(0)
  268. end
  269. end
  270. if #files == 0 then
  271. error("no tracks found")
  272. end
  273.  
  274. if #volumes == 0 then
  275. volumes[1] = 1
  276. end
  277. if #filters == 0 then
  278. filters[1] = {true, true, true, true, true}
  279. end
  280. if #volumes == 1 then
  281. for i = 2, #outputs do
  282. volumes[i] = volumes[1]
  283. end
  284. end
  285. if #filters == 1 then
  286. for i = 2, #outputs do
  287. filters[i] = filters[1]
  288. end
  289. end
  290. if #volumes ~= #outputs then
  291. error("invalid amount of volume values: "..#volumes.." (must be 1 or "..#outputs..")")
  292. end
  293. if #filters ~= #outputs then
  294. error("invalid amount of filter values: "..#filters.." (must be 1 or "..#outputs..")")
  295. end
  296.  
  297. for i = 1, #outputs do
  298. outputs[i].volume = volumes[i]
  299. outputs[i].filter = filters[i]
  300. end
  301.  
  302. context = wave.createContext()
  303. context:addOutputs(outputs)
  304. end
  305.  
  306.  
  307.  
  308.  
  309. local function formatTime(secs)
  310. local mins = math.floor(secs / 60)
  311. secs = secs - mins * 60
  312. return string.format("%01d:%02d", mins, secs)
  313. end
  314.  
  315. local function drawStatic()
  316. if noUI then return end
  317. term.setCursorPos(1, 1)
  318. term.setBackgroundColor(theme.topBar)
  319. term.setTextColor(theme.topBarTitle)
  320. term.write("wave-amp")
  321. term.write((" "):rep(screenWidth - 25))
  322. term.setTextColor(trackMode == 1 and theme.topBarOptionSelected or theme.topBarOption)
  323. term.write("nrm ")
  324. term.setTextColor(trackMode == 2 and theme.topBarOptionSelected or theme.topBarOption)
  325. term.write("stp ")
  326. term.setTextColor(trackMode == 3 and theme.topBarOptionSelected or theme.topBarOption)
  327. term.write("rep ")
  328. term.setTextColor(trackMode == 4 and theme.topBarOptionSelected or theme.topBarOption)
  329. term.write("shf ")
  330. term.setTextColor(theme.topBarClose)
  331. term.write("X")
  332.  
  333. local scrollnub = math.floor(trackScroll / (#tracks - screenHeight + 7) * (screenHeight - 10) + 0.5)
  334.  
  335. term.setTextColor(theme.song)
  336. term.setBackgroundColor(theme.songBackground)
  337. for i = 1, screenHeight - 7 do
  338. local index = i + trackScroll
  339. term.setCursorPos(1, i + 1)
  340. term.setTextColor(index == currentTrack and theme.songSelected or theme.song)
  341. term.setBackgroundColor(index == currentTrack and theme.songSelectedBackground or theme.songBackground)
  342. local str = ""
  343. if tracks[index] then
  344. local track = tracks[index]
  345. str = formatTime(track.length / track.tempo).." "
  346. if #track.name > 0 then
  347. str = str..(#track.originalAuthor == 0 and track.author or track.originalAuthor).." - "..track.name
  348. else
  349. local name = fs.getName(files[index])
  350. str = str..name:sub(1, #name - 4)
  351. end
  352. end
  353. if #str > screenWidth - 1 then
  354. str = str:sub(1, screenWidth - 3)..".."
  355. end
  356. term.write(str)
  357. term.write((" "):rep(screenWidth - 1 - #str))
  358. term.setBackgroundColor((i >= scrollnub + 1 and i <= scrollnub + 3) and theme.scrollBar or theme.scrollBackground)
  359. if i == 1 then
  360. term.setTextColor(theme.scrollButton)
  361. term.write(_HOST and "\30" or "^")
  362. elseif i == screenHeight - 7 then
  363. term.setTextColor(theme.scrollButton)
  364. term.write(_HOST and "\31" or "v")
  365. else
  366. term.write(" ")
  367. end
  368. end
  369. end
  370.  
  371. local function drawDynamic()
  372. if noUI then return end
  373. for i = 1, 5 do
  374. vsEasings[i] = vsEasings[i] - vsDecline
  375. if vsEasings[i] < 0 then
  376. vsEasings[i] = 0
  377. end
  378. local part = context.vs[i] > vsStep and vsStep or context.vs[i]
  379. if vsEasings[i] < part then
  380. vsEasings[i] = part
  381. end
  382. local full = math.floor(part / vsStep * screenWidth + 0.5)
  383. local easing = math.floor(vsEasings[i] / vsStep * screenWidth + 0.5)
  384. term.setCursorPos(1, screenHeight - 6 + i)
  385. term.setBackgroundColor(theme.visualiserBar)
  386. term.setTextColor(theme.visualiserBackground)
  387. term.write((" "):rep(full))
  388. term.write((_HOST and "\127" or "#"):rep(math.floor((easing - full) / 2)))
  389. term.setBackgroundColor(theme.visualiserBackground)
  390. term.setTextColor(theme.visualiserBar)
  391. term.write((_HOST and "\127" or "#"):rep(math.ceil((easing - full) / 2)))
  392. term.write((" "):rep(screenWidth - easing))
  393. end
  394.  
  395. local progressnub = math.floor((instance.tick / track.length) * (screenWidth - 14) + 0.5)
  396.  
  397. term.setCursorPos(1, screenHeight)
  398. term.setTextColor(theme.progressTime)
  399. term.setBackgroundColor(theme.progressBackground)
  400. term.write(formatTime(instance.tick / track.tempo))
  401.  
  402. term.setTextColor(theme.progressLine)
  403. term.write("\136")
  404. term.write(("\140"):rep(progressnub))
  405. term.setTextColor(theme.progressNub)
  406. term.setBackgroundColor(theme.progressNubBackground)
  407. term.write(theme.progressNubChar)
  408. term.setTextColor(theme.progressLine)
  409. term.setBackgroundColor(theme.progressBackground)
  410. term.write(("\140"):rep(screenWidth - 14 - progressnub))
  411. term.write("\132")
  412.  
  413. term.setTextColor(theme.progressTime)
  414. term.write(formatTime(track.length / track.tempo).." ")
  415. term.setTextColor(theme.progressButton)
  416. term.write(instance.playing and (_HOST and "|\016" or "|>") or "||")
  417. end
  418.  
  419. local function playSong(index)
  420. if index >= 1 and index <= #tracks then
  421. currentTrack = index
  422. track = tracks[currentTrack]
  423. context:removeInstance(1)
  424. instance = context:addInstance(track, 1, trackMode ~= 2, trackMode == 3)
  425. if currentTrack <= trackScroll then
  426. trackScroll = currentTrack - 1
  427. end
  428. if currentTrack > trackScroll + screenHeight - 7 then
  429. trackScroll = currentTrack - screenHeight + 7
  430. end
  431. drawStatic()
  432. end
  433. end
  434.  
  435. local function nextSong()
  436. if trackMode == 1 then
  437. playSong(currentTrack + 1)
  438. elseif trackMode == 4 then
  439. playSong(math.random(#tracks))
  440. end
  441. end
  442.  
  443. local function setScroll(scroll)
  444. trackScroll = scroll
  445. if trackScroll > #tracks - screenHeight + 7 then
  446. trackScroll = #tracks - screenHeight + 7
  447. end
  448. if trackScroll < 0 then
  449. trackScroll = 0
  450. end
  451. drawStatic()
  452. end
  453.  
  454. local function handleClick(x, y)
  455. if noUI then return end
  456. if y == 1 then
  457. if x == screenWidth then
  458. running = false
  459. elseif x >= screenWidth - 16 and x <= screenWidth - 2 and (x - screenWidth + 1) % 4 ~= 0 then
  460. trackMode = math.floor((x - screenWidth + 16) / 4) + 1
  461. instance.loop = trackMode == 3
  462. drawStatic()
  463. end
  464. elseif x < screenWidth and y >= 2 and y <= screenHeight - 6 then
  465. playSong(y - 1 + trackScroll)
  466. elseif x == screenWidth and y == 2 then
  467. setScroll(trackScroll - 2)
  468. elseif x == screenWidth and y == screenHeight - 6 then
  469. setScroll(trackScroll + 2)
  470. elseif x == screenWidth and y >= 3 and y <= screenHeight - 7 then
  471. setScroll(math.floor((y - 3) / (screenHeight - 10) * (#tracks - screenHeight + 7 ) + 0.5))
  472. elseif y == screenHeight then
  473. if x >= screenWidth - 1 and x <= screenWidth then
  474. instance.playing = not instance.playing
  475. elseif x >= 6 and x <= screenWidth - 8 then
  476. instance.tick = ((x - 6) / (screenWidth - 14)) * track.length
  477. end
  478. end
  479. end
  480.  
  481. local function handleScroll(x, y, scroll)
  482. if noUI then return end
  483. if y >= 2 and y <= screenHeight - 6 then
  484. setScroll(trackScroll + scroll * 2)
  485. end
  486. end
  487.  
  488. local function handleKey(key)
  489. if noInput then return end
  490. if key == keys.space then
  491. instance.playing = not instance.playing
  492. elseif key == keys.n then
  493. nextSong()
  494. elseif key == keys.p then
  495. playSong(currentTrack - 1)
  496. elseif key == keys.m then
  497. context.volume = (context.volume == 0) and 1 or 0
  498. elseif key == keys.left then
  499. instance.tick = instance.tick - track.tempo * 10
  500. if instance.tick < 1 then
  501. instance.tick = 1
  502. end
  503. elseif key == keys.right then
  504. instance.tick = instance.tick + track.tempo * 10
  505. elseif key == keys.up then
  506. context.volume = (context.volume == 1) and 1 or context.volume + 0.1
  507. elseif key == keys.down then
  508. context.volume = (context.volume == 0) and 0 or context.volume - 0.1
  509. elseif key == keys.j then
  510. setScroll(trackScroll + 2)
  511. elseif key == keys.k then
  512. setScroll(trackScroll - 2)
  513. elseif key == keys.pageUp then
  514. setScroll(trackScroll - 5)
  515. elseif key == keys.pageDown then
  516. setScroll(trackScroll + 5)
  517. elseif key == keys.leftShift then
  518. trackMode = trackMode % 4 + 1
  519. drawStatic()
  520. elseif key == keys.backspace then
  521. running = false
  522. end
  523. end
  524.  
  525. local function run()
  526. playSong(1)
  527. drawStatic()
  528. drawDynamic()
  529. local timer = os.startTimer(0.05)
  530. while running do
  531. local e = {os.pullEventRaw()}
  532. if e[1] == "timer" and e[2] == timer then
  533. timer = os.startTimer(0)
  534. local prevtick = instance.tick
  535. context:update()
  536. if prevtick > 1 and instance.tick == 1 then
  537. nextSong()
  538. end
  539. drawDynamic()
  540. elseif e[1] == "terminate" then
  541. running = false
  542. elseif e[1] == "term_resize" then
  543. screenWidth, screenHeight = term.getSize()
  544. elseif e[1] == "mouse_click" then
  545. handleClick(e[3], e[4])
  546. elseif e[1] == "mouse_scroll" then
  547. handleScroll(e[3], e[4], e[2])
  548. elseif e[1] == "key" then
  549. handleKey(e[2])
  550. end
  551. end
  552. end
  553.  
  554. local function exit()
  555. if noUI then return end
  556. term.setBackgroundColor(colors.black)
  557. term.setTextColor(colors.white)
  558. term.setCursorPos(1, 1)
  559. term.clear()
  560. end
  561.  
  562. init({...})
  563. run()
  564. exit()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement