-- Copyright (C) 2018-2021 by KittenOS NEO contributors -- -- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. -- -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -- THIS SOFTWARE. -- app-tapedeck.lua : Computronics Tape interface. -- Added note: Computerized record discs aren't available, so it can't be called vinylscratch. -- Authors: 20kdc local tapeRate = 4096 local tapeAccess = neo.requireAccess("c.tape_drive", "tapedrives") local event = require("event")(neo) local neoux = require("neoux")(event, neo) -- There's no way to get these, so they get reset local pcvals = {vol = 100, spd = 100} local function pcbox(x, y, low, high, id, fun) return neoux.tcfield(x, y, 5, function (tx) if tx then pcvals[id] = math.min(math.max(0, math.floor(tonumber(tx) or 0)), high) fun(math.max(pcvals[id], low) / 100) end return tostring(pcvals[id]) end) end local window local running = true local focused = true local updateTick local downloadCancelled = false local genPlayer, genList -- used to return to player local function genDownloading(downloadText, inst) downloadCancelled = false local lclLabelText = {downloadText} local lclLabel = neoux.tcrawview(1, 1, lclLabelText) local thr = { "/", "-", "\\", "|" } local thri = 0 updateTick = function () lclLabelText[1] = downloadText .. " " .. (inst.getPosition() / (1024 * 1024)) .. "MB " .. thr[(thri % #thr) + 1] thri = thri + 1 lclLabel.update(window) end return 40, 1, nil, neoux.tcwindow(40, 1, { lclLabel }, function (w) downloadCancelled = true end, 0xFFFFFF, 0) end local function maybeSleep() if math.random() > 0.98 then event.sleepTo(os.uptime() + 0.05) end end local function doINetThing(inet, url, inst) inet = inet.list()() assert(inet, "No available card") inst.stop() inst.seek(-inst.getSize()) window.reset(genDownloading("downloading...", inst)) local req = assert(inet.request(url)) req.finishConnect() local tapePos = 0 local tapeSize = inst.getSize() while (not downloadCancelled) and tapePos < tapeSize do local n, n2 = req.read(neo.readBufSize) if not n then if n2 then req.close() error(n2) end break elseif n == "" then event.sleepTo(os.uptime() + 0.05) else inst.write(n) tapePos = tapePos + #n end end req.close() inst.seek(-inst.getSize()) end local function genWeb(inst) updateTick = nil local url = "" local lockout = false return 40, 3, nil, neoux.tcwindow(40, 3, { neoux.tcrawview(1, 1, {"URL to write to tape?"}), neoux.tcfield(1, 2, 40, function (t) url = t or url return url end), neoux.tcbutton(1, 3, "Download & Write", function (w) lockout = true local inet = neo.requestAccess("c.internet") lockout = false if inet then local ok, err = pcall(doINetThing, inet, url, inst) if not ok then neoux.startDialog("Couldn't download: " .. tostring(err), "error") end end w.reset(genPlayer(inst)) end) }, function (w) w.reset(genPlayer(inst)) end, 0xFFFFFF, 0) end -- The actual main UI -- genPlayer = function (inst) local cachedLabel = inst.getLabel() or "" local cachedState = inst.getState() local function pausePlay() if inst.getState() == "PLAYING" then inst.stop() else inst.play() end window.reset(genPlayer(inst)) end -- Common code for reading/writing tapes. local function rwButton(mode) local fh = neoux.fileDialog(mode) if not fh then return end inst.stop() local tapeSize = inst.getSize() inst.seek(-tapeSize) local tapePos = 0 window.reset(genDownloading("working...", inst)) while tapePos < tapeSize and not downloadCancelled do if mode then local data = inst.read(neo.readBufSize) if not data then break end tapePos = tapePos + #data local res, ifo = fh.write(data) if not res then neoux.startDialog(tostring(ifo), "issue") break end else local data = fh.read(neo.readBufSize) if not data then break end tapePos = tapePos + #data inst.write(data) end maybeSleep() end inst.seek(-tapeSize) fh.close() window.reset(genPlayer(inst)) end local elems = { neoux.tcrawview(1, 1, { "Label:" }), neoux.tcfield(7, 1, 34, function (tx) if tx then inst.setLabel(tx) cachedLabel = tx end return cachedLabel end), { x = 1, y = 5, w = 40, h = 1, selectable = true, line = function (w, x, y, lined, bg, fg, selected) local lx = "" local pos = inst.getPosition() local sz = inst.getSize() if inst.isReady() then -- Show a bar local tick = sz / 23 for i = 1, 23 do local alpos = (tick * i) - (tick / 2) if pos > alpos then lx = lx .. "=" else lx = lx .. "-" end end else lx = "NO TAPE HERE." end local sec = pos / tapeRate local secz = sz / tapeRate lx = lx .. string.format(" %03i:%02i / %03i:%02i ", math.floor(sec / 60), math.floor(sec) % 60, math.floor(secz / 60), math.floor(secz) % 60) if selected then bg, fg = fg, bg end window.span(x, y, lx, bg, fg) end, key = function (w, update, a, b, c, kf) local amount = tapeRate * 10 if kf.shift or kf.rshift then amount = amount * 24 end if c then if a == 32 then pausePlay() elseif b == 203 then inst.seek(-amount) update() return true elseif b == 205 then inst.seek(amount) update() return true end end end }, neoux.tcrawview(33, 3, { "% Volume" }), neoux.tcrawview(20, 3, { "% Speed" }), pcbox(15, 3, 25, 200, "spd", inst.setSpeed), pcbox(28, 3, 0, 100, "vol", inst.setVolume), neoux.tcrawview(1, 4, { "Seeker: use ◃/▹ (shift goes faster)" }), neoux.tcbutton(1, 3, "«", function (w) inst.seek(-inst.getSize()) end), neoux.tcbutton(11, 3, "»", function (w) inst.seek(inst.getSize()) end), neoux.tcbutton(4, 3, ((inst.getState() == "PLAYING") and "Pause") or "Play", function (w) pausePlay() end), -- R/W buttons neoux.tcbutton(1, 2, "Read", function (w) rwButton(true) end), neoux.tcbutton(7, 2, "Write", function (w) rwButton(false) end), neoux.tcbutton(14, 2, "Write Web", function (w) w.reset(genWeb(inst)) end), neoux.tcbutton(25, 2, "Copy", function (w) w.reset(genList(function (inst2) local ts1 = inst.getSize() inst.stop() inst.seek(-ts1) local ts2 = inst2.getSize() inst2.stop() inst2.seek(-ts2) if ts1 < ts2 then w.reset(genDownloading("copying...", inst)) else w.reset(genDownloading("copying...", inst2)) end local pos = 0 while pos < ts1 and pos < ts2 and not downloadCancelled do local dat = inst.read(neo.readBufSize) inst2.write(dat) pos = pos + #dat maybeSleep() end inst.seek(-ts1) inst2.seek(-ts2) inst2.setLabel((inst.getLabel() or "") .. " Copy") w.reset(genPlayer(inst)) end)) end), neoux.tcbutton(31, 2, "Erase", function (w) local ts1 = inst.getSize() inst.stop() inst.seek(-ts1) w.reset(genDownloading("erasing...", inst)) local blank = ("\x00"):rep(neo.readBufSize) local pos = 0 while pos < ts1 and not downloadCancelled do inst.write(blank) pos = pos + #blank maybeSleep() end inst.seek(-ts1) w.reset(genPlayer(inst)) end) } updateTick = function () local lcl = cachedLabel cachedLabel = inst.getLabel() or "" elems[3].update(window) if inst.getState() ~= cachedState then window.reset(genPlayer(inst)) elseif lcl ~= cachedLabel then elems[2].update(window) end end local n = neoux.tcwindow(40, 5, elems, function (w) updateTick = nil running = false w.close() end, 0xFFFFFF, 0) return 40, 5, inst.address, function (a, ...) if a == "focus" then focused = (...) or true end return n(a, ...) end end genList = function(callback) updateTick = nil local elems = {} local tapes = {} for v in tapeAccess.list() do table.insert(tapes, v) end for k, v in ipairs(tapes) do -- There's 38 chars available... local desc1 = neoux.pad(v.address, 13, false, true) if v.isReady() then desc1 = desc1 .. ": " .. neoux.pad(v.getLabel() or "", 23, false, true) else desc1 = desc1 .. " (no tape)" end elems[k] = neoux.tcbutton(1, k, desc1, function (w) callback(v) end) end return 40, #elems, nil, neoux.tcwindow(40, #elems, elems, function (w) running = false w.close() end, 0xFFFFFF, 0) end window = neoux.create(genList(function (v) window.reset(genPlayer(v)) end)) -- Timer for time update local function tick() if updateTick then updateTick() end event.runAt(os.uptime() + ((focused and 1) or 10), tick) end event.runAt(0, tick) while running do event.pull() end