cc-stuff/mess/tmpc.lua

638 lines
22 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 statusText, statusTicks = nil, 0
local function setStatus(txt, ticks)
statusText, statusTicks = txt, ticks or 10
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 > #self.songs - (th - 3) then
self.screens[2].scroll = #self.songs - (th - 3)
end
if self.screens[2].scroll < 0 then
self.screens[2].scroll = 0
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))
term.write(" // TODO")
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
-- 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))
local timeString = string.format("[%s:%s]", time2str(time), time2str(duration))
term.setCursorPos(1, th)
term.clearLine()
if statusTicks > 0 then
term.setTextColor(colors.red)
term.write(statusText)
statusTicks = statusTicks - 1
end
if drive.getState() ~= "STOPPED" then
term.setTextColor(mplayer.colors.status)
if statusTicks <= 0 then
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)
-- 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
end
if statusTicks <= 0 then
term.setTextColor(mplayer.colors.status)
term.setCursorPos(tw - #timeString + 1, th)
term.write(timeString)
end
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
local shiftHeld = mplayer.heldKeys[keys.leftShift] ~= nil or mplayer.heldKeys[keys.rightShift] ~= nil
local ctrlHeld = mplayer.heldKeys[keys.leftCtrl] ~= nil or mplayer.heldKeys[keys.rightCtrl] ~= nil
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.comma or evd[1] == keys.period) and shiftHeld then
setStatus("Not implemented yet!", 20)
elseif ev == "key" and evd[1] == keys.left or evd[1] == keys.right or evd[1] == keys.minus or (evd[1] == keys.equals and shiftHeld) then
setStatus("Not implemented yet!", 20)
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 = {}
mplayer.currentSong = 0
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 = "NOTE: It's just a regular tape",
offset = drive.getSize() - 10,
length = 10
},
{
title = drive.getLabel(),
offset = 0,
length = drive.getSize() - 10
}
}
for t = 1, drive.getSize(), 6000 * 60 * 5 do
table.insert(mplayer.songs, {
title = "Skip to " .. time2str(t / 6000),
offset = t,
length = 6000 * 60 * 5
})
end
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!")