607 lines
21 KiB
Lua
607 lines
21 KiB
Lua
settings.define("mplayer.colors.bg", {
|
|
description = "Background color in media player",
|
|
default = 0x131313, -- #131313
|
|
type = number
|
|
})
|
|
|
|
settings.define("mplayer.colors.fg", {
|
|
description = "Text color in media player",
|
|
default = 0xEFEFEF, -- #EFEFEF
|
|
type = number
|
|
})
|
|
|
|
settings.define("mplayer.colors.cursor", {
|
|
description = "Color of the cursor",
|
|
default = 0x8080EF, -- #8080EF
|
|
type = number
|
|
})
|
|
|
|
settings.define("mplayer.colors.current", {
|
|
description = "Color of the currently playing song",
|
|
default = 0xEFEF80, -- #EFEF80
|
|
type = number
|
|
})
|
|
|
|
settings.define("mplayer.colors.status", {
|
|
description = "Color of the statusbar",
|
|
default = 0x80EF80, -- #80EF80
|
|
type = number
|
|
})
|
|
|
|
local tw, th = term.getSize()
|
|
|
|
os.queueEvent("dummy")
|
|
while tw < 25 or th < 10 do
|
|
local ev = { os.pullEvent() }
|
|
term.clear()
|
|
term.setCursorPos(1, 1)
|
|
printError("Too small: " .. tw .. "x" .. th .. " < 25x10")
|
|
printError("Q to exit")
|
|
printError("Y to ignore")
|
|
|
|
local setTextScale = term.current().setTextScale
|
|
if setTextScale ~= nil then
|
|
printError("S to try rescaling")
|
|
end
|
|
printError("I'll wait while you're adding more")
|
|
|
|
if ev[1] == "term_resize" then
|
|
tw, th = term.getSize()
|
|
elseif ev[1] == "key" and ev[2] == keys.s and setTextScale ~= nil then
|
|
setTextScale(0.5)
|
|
elseif ev[1] == "key" and ev[2] == keys.y then
|
|
break
|
|
elseif ev[1] == "key" and ev[2] == keys.q then
|
|
return
|
|
end
|
|
end
|
|
|
|
|
|
local drive = peripheral.find("tape_drive")
|
|
if not drive then
|
|
printError("No drive found, starting in dummy mode")
|
|
local fp = io.open("noita.dfpwm", "rb")
|
|
if fp == nil then
|
|
printError("No sample file found, are you running it on a real computer without a tape drive?")
|
|
return
|
|
end
|
|
local size = fp:seek("end", 0)
|
|
fp:seek("set", 0)
|
|
drive = {
|
|
_pos = 0,
|
|
_fp = fp,
|
|
_state = "STOPPED",
|
|
isDummy = true,
|
|
seek = function(howMuch)
|
|
drive._pos = math.min(drive.getSize(), math.max(0, drive._pos + howMuch))
|
|
end,
|
|
getPosition = function()
|
|
return drive._pos
|
|
end,
|
|
read = function(n)
|
|
local out = { drive._fp:read(n) }
|
|
drive.seek(n or 1)
|
|
return table.unpack(out)
|
|
end,
|
|
getSize = function()
|
|
return size
|
|
end,
|
|
getState = function()
|
|
return drive._state
|
|
end,
|
|
play = function()
|
|
drive._state = "PLAYING"
|
|
end,
|
|
stop = function()
|
|
drive._state = "STOPPED"
|
|
end,
|
|
isReady = function()
|
|
return true
|
|
end,
|
|
getLabel = function()
|
|
return "Dummy drive tape"
|
|
end,
|
|
_tick = function()
|
|
if drive._state == "PLAYING" then
|
|
drive.read(600)
|
|
end
|
|
end,
|
|
}
|
|
os.sleep(1)
|
|
end
|
|
|
|
local function time2str(ti)
|
|
ti = math.floor(ti)
|
|
local m, s = math.floor(ti / 60), ti % 60
|
|
return string.format("%02d:%02d", m, s)
|
|
end
|
|
|
|
local function read32()
|
|
local v = 0
|
|
for i = 1, 4 do
|
|
local b = string.byte(drive.read(1), 1)
|
|
v = bit32.bor(bit32.lshift(v, 8), b)
|
|
end
|
|
return v
|
|
end
|
|
|
|
local help = {}
|
|
|
|
for line in ([[# Movement:
|
|
--
|
|
Up k : Move cursor up
|
|
Dn j : Move cursor down
|
|
H : Jump to top
|
|
L : Jump down
|
|
PgUp : Page up
|
|
PgDn : Page down
|
|
|
|
Tab : Next screen
|
|
S+Tab : Prev. screen
|
|
|
|
1 F1 : Help screen
|
|
2 F2 : Songs list
|
|
3 F3 : Settings
|
|
|
|
# Global:
|
|
--
|
|
s : Stop, jump to start
|
|
p : Pause/resume
|
|
< : Next track
|
|
> : Previous track
|
|
f : Seek forward
|
|
b : Seek backward
|
|
\x1b - : Lower volume
|
|
\x1a + : Higher volume
|
|
|
|
# List screen:
|
|
--
|
|
Enter : Play
|
|
Ctrl+l : Center
|
|
l : Jump to current
|
|
|
|
]]):gsub("\\x(..)", function(m)
|
|
return string.char(tonumber(m, 16))
|
|
end):gmatch("[^\n]+") do
|
|
table.insert(help, line)
|
|
end
|
|
|
|
|
|
local mplayer = {
|
|
colors = {
|
|
bg = colors.black,
|
|
fg = colors.white,
|
|
cursor = colors.blue,
|
|
current = colors.yellow,
|
|
status = colors.lime
|
|
},
|
|
heldKeys = {},
|
|
screens = {
|
|
{
|
|
title = "Help",
|
|
scroll = 0,
|
|
render = function(self)
|
|
for i = 1, th - 3 do
|
|
local line = help[i + self.screens[1].scroll] or "~"
|
|
term.setCursorPos(1, i + 1)
|
|
term.clearLine()
|
|
if line:sub(1, 1) == "~" then
|
|
term.setTextColor(self.colors.cursor)
|
|
elseif line:sub(1, 1) == "#" then
|
|
term.setTextColor(self.colors.current)
|
|
elseif line:sub(1, 2) == "--" then
|
|
term.setTextColor(self.colors.status)
|
|
term.write(("-"):rep(tw - 2))
|
|
else
|
|
term.setTextColor(self.colors.fg)
|
|
end
|
|
term.write(line)
|
|
end
|
|
end,
|
|
handleKey = function(self, key, repeating)
|
|
if key == keys.down or key == keys.j then
|
|
self.screens[1].handleScroll(self, 1)
|
|
elseif key == keys.up or key == keys.k then
|
|
self.screens[1].handleScroll(self, -1)
|
|
elseif key == keys.pageDown then
|
|
self.screens[1].handleScroll(self, th - 3)
|
|
elseif key == keys.pageUp then
|
|
self.screens[1].handleScroll(self, -(th - 3))
|
|
end
|
|
end,
|
|
handleScroll = function(self, direction, x, y)
|
|
self.screens[1].scroll = math.max(0, math.min(th - 1, self.screens[1].scroll + direction))
|
|
end,
|
|
},
|
|
{
|
|
title = "List",
|
|
scroll = 0,
|
|
cursor = 1,
|
|
textScroll = 0,
|
|
render = function(self)
|
|
for i = 1, th - 3 do
|
|
local song = self.songs[i + self.screens[2].scroll]
|
|
local isCurrent = (i + self.screens[2].scroll) == self.currentSong
|
|
local isHovered = (i + self.screens[2].scroll) == self.screens[2].cursor
|
|
term.setCursorPos(1, i + 1)
|
|
local bg, fg = self.colors.bg, (isCurrent and self.colors.current or self.colors.fg)
|
|
if isHovered then bg, fg = fg, bg end
|
|
term.setBackgroundColor(bg)
|
|
term.setTextColor(fg)
|
|
term.clearLine()
|
|
if song then
|
|
local timeString = " ["..time2str(song.length / 6000) .. "]"
|
|
local w = tw - #timeString
|
|
if #song.title <= w then
|
|
term.write(song.title)
|
|
else
|
|
local off = isHovered and ((self.screens[2].textScroll % (#song.title + 5)) + 1) or 1
|
|
local txt = song.title .. " ::: " .. song.title
|
|
term.write(txt:sub(off, off + w - 1))
|
|
end
|
|
term.setCursorPos(tw - #timeString + 1, i + 1)
|
|
term.write(timeString)
|
|
end
|
|
end
|
|
end,
|
|
handleKey = function(self, key, repeating)
|
|
local shiftHeld = self.heldKeys[keys.leftShift] ~= nil or self.heldKeys[keys.rightShift] ~= nil
|
|
local ctrlHeld = self.heldKeys[keys.leftCtrl] ~= nil or self.heldKeys[keys.rightCtrl] ~= nil
|
|
|
|
if key == keys.down or key == keys.j then
|
|
self.screens[2].handleScroll(self, 1, 1, 1)
|
|
elseif key == keys.up or key == keys.k then
|
|
self.screens[2].handleScroll(self, -1, 1, 1)
|
|
elseif key == keys.pageDown then
|
|
self.screens[2].handleScroll(self, th - 3, 1, 1)
|
|
elseif key == keys.pageUp then
|
|
self.screens[2].handleScroll(self, -(th - 3), 1, 1)
|
|
elseif key == keys.h and shiftHeld then
|
|
self.screens[2].handleScroll(self, -#self.songs, 1, 1)
|
|
elseif key == keys.l and shiftHeld then
|
|
self.screens[2].handleScroll(self, #self.songs, 1, 1)
|
|
elseif key == keys.l then
|
|
self.screens[2].cursor = self.currentSong or 1
|
|
if ctrlHeld then
|
|
self.screens[2].scroll = self.screens[2].scroll - math.floor((th - 3) / 2)
|
|
end
|
|
self.screens[2].handleScroll(self, 0, 1, 1)
|
|
elseif key == keys.enter then
|
|
drive.seek(-drive.getSize())
|
|
drive.seek(self.songs[self.screens[2].cursor].offset)
|
|
drive.play()
|
|
self.currentSong = self.screens[2].cursor
|
|
end
|
|
end,
|
|
handleScroll = function(self, direction, x, y)
|
|
self.screens[2].cursor = math.max(1, math.min(#self.songs, self.screens[2].cursor + direction))
|
|
if self.screens[2].scroll + 1 > self.screens[2].cursor then
|
|
self.screens[2].scroll = self.screens[2].cursor - 1
|
|
end
|
|
local maxi = self.screens[2].scroll + th - 3
|
|
if self.screens[2].cursor > maxi then
|
|
self.screens[2].scroll = self.screens[2].cursor - (th - 3)
|
|
end
|
|
if self.screens[2].scroll < 0 then
|
|
self.screens[2].scroll = 0
|
|
end
|
|
if self.screens[2].scroll > #self.songs - (th - 3) then
|
|
self.screens[2].scroll = #self.songs - (th - 3)
|
|
end
|
|
self.screens[2].textScroll = 0
|
|
end,
|
|
handleClick = function(self, x, y)
|
|
local i = self.screens[2].scroll + y - 1
|
|
if i == self.screens[2].cursor then
|
|
self.screens[2].handleKey(self, keys.enter, false)
|
|
elseif i <= #self.songs then
|
|
self.screens[2].cursor = i
|
|
end
|
|
end,
|
|
},
|
|
{
|
|
title = "Settings",
|
|
scroll = 0,
|
|
cursor = 1,
|
|
render = function(self)
|
|
term.clear()
|
|
term.write(string.format("opt = %d, scroll = %d", self.screens[3].cursor, self.screens[3].scroll))
|
|
end,
|
|
handleKey = function(self, key, repeating)
|
|
if key == keys.down or key == keys.j then
|
|
self.screens[3].handleScroll(self, 1)
|
|
elseif key == keys.up or key == keys.k then
|
|
self.screens[3].handleScroll(self, -1)
|
|
elseif key == keys.pageDown then
|
|
self.screens[3].handleScroll(self, th - 3)
|
|
elseif key == keys.pageUp then
|
|
self.screens[3].handleScroll(self, -(th - 3))
|
|
end
|
|
end,
|
|
handleScroll = function(self, direction, x, y)
|
|
self.screens[3].cursor = math.max(1, math.min(20, self.screens[3].cursor + direction))
|
|
if self.screens[3].scroll + 1 > self.screens[3].cursor then
|
|
self.screens[3].scroll = self.screens[3].cursor - 1
|
|
end
|
|
local maxi = self.screens[3].scroll + th - 3
|
|
if self.screens[3].cursor > maxi then
|
|
self.screens[3].scroll = self.screens[3].cursor - (th - 3)
|
|
end
|
|
end
|
|
}
|
|
},
|
|
songs = {},
|
|
currentSong = 0,
|
|
statusLineScroll = 0,
|
|
currentScreen = 2,
|
|
}
|
|
|
|
for k, v in pairs(mplayer.colors) do
|
|
term.setPaletteColor(v, settings.get("mplayer.colors." .. k))
|
|
end
|
|
term.setCursorPos(1, 1)
|
|
term.setBackgroundColor(mplayer.colors.bg)
|
|
term.setTextColor(mplayer.colors.fg)
|
|
term.clear()
|
|
|
|
|
|
local spinner = {
|
|
"\x81", "\x83", "\x82",
|
|
"\x8a", "\x88", "\x8c",
|
|
"\x84", "\x85"
|
|
}
|
|
|
|
parallel.waitForAny(
|
|
function()
|
|
while true do
|
|
-- Current screen
|
|
term.setCursorPos(1, 2)
|
|
mplayer.screens[mplayer.currentScreen].render(mplayer)
|
|
|
|
-- Top bar
|
|
term.setCursorPos(1, 1)
|
|
for i, screen in ipairs(mplayer.screens) do
|
|
term.setTextColor(mplayer.colors[i == mplayer.currentScreen and "bg" or "fg"])
|
|
term.setBackgroundColor(mplayer.colors[i == mplayer.currentScreen and "fg" or "bg"])
|
|
term.write(" "..i..":"..screen.title.." ")
|
|
end
|
|
|
|
term.setBackgroundColor(mplayer.colors.bg)
|
|
|
|
local title, time, duration = "Whatever is on the tape", drive.getPosition() / 6000, drive.getSize() / 6000
|
|
if mplayer.currentSong ~= 0 then
|
|
local song = mplayer.songs[mplayer.currentSong]
|
|
time = (drive.getPosition() - song.offset) / 6000
|
|
duration = song.length / 6000
|
|
title = song.title
|
|
end
|
|
|
|
-- Statusline
|
|
term.setCursorPos(1, th)
|
|
term.clearLine()
|
|
|
|
local timeString = string.format("[%s:%s]", time2str(time), time2str(duration))
|
|
if drive.getState() ~= "STOPPED" then
|
|
term.setTextColor(mplayer.colors.status)
|
|
local speen = spinner[(math.floor(drive.getPosition() / 3000) % #spinner) + 1]
|
|
local action = ""
|
|
if drive.getState() == "PLAYING" then action = "Playing:"
|
|
elseif drive.getState() == "REWINDING" then action = "Rewinding"
|
|
elseif drive.getState() == "FORWARDING" then action = "Forwarding"
|
|
else
|
|
printError("Unknown drive state: "..tostring(drive.getState()))
|
|
return
|
|
end
|
|
action = speen .. " " .. action
|
|
term.write(action)
|
|
|
|
-- Progressbar
|
|
local lw = math.floor(tw * time / duration)
|
|
term.setCursorPos(1, th - 1)
|
|
term.setTextColor(mplayer.colors.current)
|
|
term.clearLine()
|
|
term.write(string.rep("=", lw))
|
|
term.write("\x10")
|
|
term.setTextColor(mplayer.colors.cursor)
|
|
term.write(string.rep("\xad", tw - lw - 1))
|
|
|
|
-- Statusline text
|
|
term.setCursorPos(#action + 2, th)
|
|
local w = tw - #timeString - #action - 2 -- "Playing: ", spinner plus spacing
|
|
|
|
term.setTextColor(mplayer.colors.current)
|
|
if #title <= w then
|
|
term.write(title)
|
|
else
|
|
local off = (mplayer.statusLineScroll % (#title + 5)) + 1
|
|
local txt = title .. " ::: " .. title
|
|
term.write(txt:sub(off, off + w - 1))
|
|
end
|
|
end
|
|
term.setTextColor(mplayer.colors.status)
|
|
term.setCursorPos(tw - #timeString + 1, th)
|
|
term.write(timeString)
|
|
os.sleep(0.1)
|
|
end
|
|
end,
|
|
function()
|
|
local pretty = require("cc.pretty")
|
|
while true do
|
|
local _evd = { os.pullEvent() }
|
|
local ev, evd = table.remove(_evd, 1), _evd
|
|
if ev == "key" then
|
|
mplayer.heldKeys[evd[1]] = evd[2]
|
|
elseif ev == "key_up" then
|
|
mplayer.heldKeys[evd[1]] = nil
|
|
end
|
|
|
|
if ev == "key_up" and evd[1] == keys.q then
|
|
break
|
|
elseif ev == "key" and (evd[1] == keys.one or evd[1] == keys.f1) then
|
|
mplayer.currentScreen = 1
|
|
elseif ev == "key" and (evd[1] == keys.two or evd[1] == keys.f2) then
|
|
mplayer.currentScreen = 2
|
|
elseif ev == "key" and (evd[1] == keys.three or evd[1] == keys.f3) then
|
|
mplayer.currentScreen = 3
|
|
elseif ev == "key" and evd[1] == keys.f then
|
|
drive.seek(3000)
|
|
elseif ev == "key" and evd[1] == keys.b then
|
|
drive.seek(-3000)
|
|
elseif ev == "key" and evd[1] == keys.s then
|
|
drive.stop()
|
|
drive.seek(-drive.getSize())
|
|
drive.seek(6000)
|
|
elseif ev == "key" and (evd[1] == keys.p or evd[1] == keys.space) then
|
|
if drive.getState() ~= "STOPPED" then
|
|
drive.stop()
|
|
else
|
|
drive.play()
|
|
end
|
|
elseif ev == "key" and evd[1] == keys.tab then
|
|
local dir = ((mplayer.heldKeys[keys.leftShift] ~= nil) or (mplayer.heldKeys[keys.rightShift] ~= nil)) and -1 or 1
|
|
mplayer.currentScreen = ((mplayer.currentScreen - 1 + #mplayer.screens + dir)) % #mplayer.screens + 1
|
|
elseif ev == "key" then
|
|
mplayer.screens[mplayer.currentScreen].handleKey(mplayer, evd[1], evd[2])
|
|
elseif ev == "mouse_scroll" then
|
|
local dir, x, y = table.unpack(evd)
|
|
mplayer.screens[mplayer.currentScreen].handleScroll(mplayer, dir, x, y)
|
|
elseif ev == "song_change" then
|
|
mplayer.statusLineScroll = 0
|
|
elseif (ev == "mouse_click" or ev == "mouse_drag") and evd[3] == th - 1 then
|
|
local p = (evd[2] - 1) / tw
|
|
local song = mplayer.songs[mplayer.currentSong]
|
|
drive.seek(-drive.getSize())
|
|
if song ~= nil then
|
|
drive.seek(song.offset + math.floor(song.length * p))
|
|
else
|
|
drive.seek(math.floor(p * drive.getSize()))
|
|
end
|
|
elseif (ev == "mouse_click" or ev == "mouse_drag") and evd[3] == 1 then
|
|
local cx, x = 1, evd[2]
|
|
for i, screen in ipairs(mplayer.screens) do
|
|
local caption = " "..i..""..screen.title.." "
|
|
if x >= cx and x <= (cx + #caption) then
|
|
mplayer.currentScreen = i
|
|
break
|
|
end
|
|
cx = cx + #caption
|
|
end
|
|
elseif ev == "mouse_click" or ev == "mouse_drag" then
|
|
if mplayer.screens[mplayer.currentScreen].handleClick then
|
|
local butt, x, y = table.unpack(evd)
|
|
mplayer.screens[mplayer.currentScreen].handleClick(mplayer, x, y)
|
|
end
|
|
elseif ev == "term_resize" then
|
|
tw, th = term.getSize()
|
|
elseif ev == "tape_removed" then
|
|
mplayer.songs = {}
|
|
elseif ev == "tape_inserted" then
|
|
local seekToAndPlay = nil
|
|
if drive.getState() == "PLAYING" then
|
|
seekToAndPlay = drive.getPosition()
|
|
end
|
|
|
|
drive.stop()
|
|
drive.seek(-drive.getSize())
|
|
for i = 1, 48 do
|
|
local offset = read32()
|
|
local length = read32()
|
|
local title = drive.read(117)
|
|
if offset > drive.getSize() or length > drive.getSize() then
|
|
mplayer.songs = {
|
|
{
|
|
title = drive.getLabel(),
|
|
offset = 0,
|
|
length = drive.getSize() - 10
|
|
},
|
|
{
|
|
title = "[ !!! ] It's just a regular tape",
|
|
offset = drive.getSize() - 10,
|
|
length = 10
|
|
}
|
|
}
|
|
break
|
|
end
|
|
title = title:sub(1, title:find("\x00"))
|
|
if length > 0 and offset > 0 then
|
|
mplayer.songs[i] = {
|
|
title = title,
|
|
offset = offset,
|
|
length = length
|
|
}
|
|
end
|
|
end
|
|
|
|
if seekToAndPlay ~= nil then
|
|
drive.seek(-drive.getSize())
|
|
drive.seek(seekToAndPlay)
|
|
drive.play()
|
|
end
|
|
elseif ev ~= "timer" then
|
|
if drive.isDummy then
|
|
local m = term.redirect(peripheral.wrap("right"))
|
|
io.write(ev .. " ")
|
|
pretty.print(pretty.pretty(evd))
|
|
term.redirect(m)
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
function()
|
|
while true do
|
|
mplayer.statusLineScroll = mplayer.statusLineScroll + 1
|
|
mplayer.screens[2].textScroll = mplayer.screens[2].textScroll + 1
|
|
os.sleep(0.25)
|
|
end
|
|
end,
|
|
function()
|
|
local oldSong = nil
|
|
local oldDriveState = nil
|
|
local oldTapeState = nil
|
|
while true do
|
|
mplayer.currentSong = 0
|
|
if drive._tick then drive._tick() end -- dummy mode
|
|
|
|
local tapeState = drive.isReady()
|
|
if tapeState ~= oldTapeState then
|
|
os.queueEvent(tapeState and "tape_inserted" or "tape_removed")
|
|
oldTapeState = tapeState
|
|
end
|
|
|
|
local driveState = drive.getState()
|
|
if driveState ~= oldDriveState then
|
|
os.queueEvent("drive_state", driveState)
|
|
oldDriveState = driveState
|
|
end
|
|
|
|
local pos = drive.getPosition()
|
|
for i, song in ipairs(mplayer.songs) do
|
|
if pos >= song.offset and pos < (song.offset + song.length) then
|
|
mplayer.currentSong = i
|
|
end
|
|
end
|
|
|
|
if oldSong ~= mplayer.currentSong then
|
|
os.queueEvent("song_change")
|
|
oldSong = mplayer.currentSong
|
|
end
|
|
|
|
os.sleep(0.1)
|
|
end
|
|
end
|
|
)
|
|
|
|
-- cleanup
|
|
term.clear()
|
|
term.setCursorPos(1, 1)
|
|
for i = 1, 16 do
|
|
local c = math.pow(2, i - 1)
|
|
term.setPaletteColor(c, term.nativePaletteColor(c))
|
|
end
|
|
term.setBackgroundColor(colors.black)
|
|
term.setTextColor(colors.white)
|
|
if term.current().setTextScale then
|
|
term.current().setTextScale(1.0)
|
|
end
|
|
print("<tmpc> Goodbye!")
|