Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
330 changes: 330 additions & 0 deletions src/main/resources/assets/audio_perf/loot/tape/usr/bin/tape.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
--[[ tape program, provides basic tape modification and access tools
Authors: Bizzycola and Vexatos
]]
local component = require("component")
local fs = require("filesystem")
local shell = require("shell")
local term = require("term")

local args, options = shell.parse(...)

if not component.isAvailable("tape_drive") then
io.stderr:write("This program requires a tape drive to run.")
return
end

local function printUsage()
print("Usage:")
print(" - 'tape play' to start playing a tape")
print(" - 'tape pause' to pause playing the tape")
print(" - 'tape stop' to stop playing and rewind the tape")
print(" - 'tape rewind' to rewind the tape")
print(" - 'tape wipe' to wipe any data on the tape and erase it completely")
print(" - 'tape label [name]' to label the tape, leave 'name' empty to get current label")
print(" - 'tape speed <speed>' to set the playback speed. Needs to be between 0.25 and 2.0")
print(" - 'tape volume <volume>' to set the volume of the tape. Needs to be between 0.0 and 1.0")
print(" - 'tape write <path/of/audio/file>' to write to the tape from a file")
print(" - 'tape write <URL>' to write from a URL")
print("Other options:")
print(" '--address=<address>' to use a specific tape drive")
print(" '--b=<bytes>' to specify the size of the chunks the program will write to a tape")
print(" '--t=<timeout>' to specify a custom maximum timeout in seconds when writing from a URL")
print(" '-y' to not ask for confirmation before starting to write")
return
end

local function getTapeDrive()
--Credits to gamax92 for this
local tape
if options.address then
if type(options.address) ~= "string" then
io.stderr:write("'address' may only be a string.")
return
end
local fulladdr = component.get(options.address)
if fulladdr == nil then
io.stderr:write("No component at this address.")
return
end
if component.type(fulladdr) ~= "tape_drive" then
io.stderr:write("No tape drive at this address.")
return
end
tape = component.proxy(fulladdr)
else
tape = component.tape_drive
end
return tape
--End of gamax92's part
end

local tape = getTapeDrive()

if not tape.isReady() then
io.stderr:write("The tape drive does not contain a tape.")
return
end

local function label(name)
if not name then
if tape.getLabel() == "" then
print("Tape is currently not labeled.")
return
end
print("Tape is currently labeled: " .. tape.getLabel())
return
end
tape.setLabel(name)
print("Tape label set to " .. name)
end

local function rewind()
print("Rewound tape")
tape.seek(-tape.getSize())
end

local function play()
if tape.getState() == "PLAYING" then
print("Tape is already playing")
else
tape.play()
print("Tape started")
end
end

local function stop()
if tape.getState() == "STOPPED" then
print("Tape is already stopped")
else
tape.stop()
tape.seek(-tape.getSize())
print("Tape stopped")
end
end

local function pause()
if tape.getState() == "STOPPED" then
print("Tape is already paused")
else
tape.stop()
print("Tape paused")
end
end

local function speed(sp)
local s = tonumber(sp)
if not s or s < 0.25 or s > 2 then
io.stderr:write("Speed needs to be a number between 0.25 and 2.0")
return
end
tape.setSpeed(s)
print("Playback speed set to " .. sp)
end

local function volume(vol)
local v = tonumber(vol)
if not v or v < 0 or v > 1 then
io.stderr:write("Volume needs to be a number between 0.0 and 1.0")
return
end
tape.setVolume(v)
print("Volume set to " .. vol)
end

local function confirm(msg)
if not options.y then
print(msg)
print("Type `y` to confirm, `n` to cancel.")
repeat
local response = io.read()
if response and response:lower():sub(1, 1) == "n" then
print("Canceled.")
return false
end
until response and response:lower():sub(1, 1) == "y"
end
return true
end

local function wipe()
if not confirm("Are you sure you want to wipe this tape?") then return end
local k = tape.getSize()
tape.stop()
tape.seek(-k)
tape.stop() --Just making sure
tape.seek(-90000)
local s = string.rep("\xAA", 8192)
for i = 1, k + 8191, 8192 do
tape.write(s)
end
tape.seek(-k)
tape.seek(-90000)
print("Done.")
end

local function writeTape(path)
local file, msg, _, y
local block = 2048 --How much to read at a time
if options.b then
local nBlock = tonumber(options.b)
if nBlock then
print("Setting chunk size to " .. options.b)
block = nBlock
else
io.stderr:write("option --b is not a number.\n")
return
end
end
if not confirm("Are you sure you want to write to this tape?") then return end
tape.stop()
tape.seek(-tape.getSize())
tape.stop() --Just making sure

local bytery = 0 --For the progress indicator
local filesize = tape.getSize()

if string.match(path, "https?://.+") then

if not component.isAvailable("internet") then
io.stderr:write("This command requires an internet card to run.")
return false
end

local internet = component.internet

local function setupConnection(url)

local file, reason = internet.request(url)

if not file then
io.stderr:write("error requesting data from URL: " .. reason .. "\n")
return false
end

local connected, reason = false, ""
local timeout = 50
if options.t then
local nTimeout = tonumber(options.t)
if nTimeout then
print("Max timeout: " .. options.t)
timeout = nTimeout * 10
else
io.stderr:write("option --t is not a number. Defaulting to 5 seconds.\n")
end
end
for i = 1, timeout do
connected, reason = file.finishConnect()
os.sleep(.1)
if connected or connected == nil then
break
end
end

if connected == nil then
io.stderr:write("Could not connect to server: " .. reason)
return false
end

local status, message, header = file.response()

if status then
status = string.format("%d", status)
print("Status: " .. status .. " " .. message)
if status:sub(1,1) == "2" then
return true, {
close = function(self, ...) return file.close(...) end,
read = function(self, ...) return file.read(...) end,
}, header
end
return false
end
io.stderr:write("no valid HTTP response - no response")
return false
end

local success, header
success, file, header = setupConnection(path)
if not success then
if file then
file:close()
end
return
end

print("Writing...")

_, y = term.getCursor()

if header and header["Content-Length"] and header["Content-Length"][1] then
filesize = tonumber(header["Content-Length"][1])
end
else
local path = shell.resolve(path)
filesize = fs.size(path)
print("Path: " .. path)
file, msg = io.open(path, "rb")
if not file then
io.stderr:write("Error: " .. msg)
return
end

print("Writing...")

_, y = term.getCursor()
end

if filesize > tape.getSize() then
term.setCursor(1, y)
io.stderr:write("Warning: File is too large for tape, shortening file\n")
_, y = term.getCursor()
filesize = tape.getSize()
end

--Displays long numbers with commas
local function fancyNumber(n)
return tostring(math.floor(n)):reverse():gsub("(%d%d%d)", "%1,"):gsub("%D$", ""):reverse()
end

repeat
local bytes = file:read(block)
if bytes and #bytes > 0 then
if not tape.isReady() then
io.stderr:write("\nError: Tape was removed during writing.\n")
file:close()
return
end
term.setCursor(1, y)
bytery = bytery + #bytes
local displaySize = math.min(bytery, filesize)
term.write(string.format("Read %s of %s bytes... (%.2f %%)", fancyNumber(displaySize), fancyNumber(filesize), 100 * displaySize / filesize))
tape.write(bytes)
end
until not bytes or bytery > filesize
file:close()
tape.stop()
tape.seek(-tape.getSize())
tape.stop() --Just making sure
print("\nDone.")
end

if args[1] == "play" then
play()
elseif args[1] == "stop" then
stop()
elseif args[1] == "pause" then
pause()
elseif args[1] == "rewind" then
rewind()
elseif args[1] == "label" then
label(args[2])
elseif args[1] == "speed" then
speed(args[2])
elseif args[1] == "volume" then
volume(args[2])
elseif args[1] == "write" then
writeTape(args[2])
elseif args[1] == "wipe" then
wipe()
else
printUsage()
end