forked from hkc/cc-stuff
Added multi-track tape player
This commit is contained in:
parent
a316da89b9
commit
e8dcb586a3
|
@ -0,0 +1,88 @@
|
|||
local drive = peripheral.find("tape_drive")
|
||||
if not tape then
|
||||
printError("no tape drive found")
|
||||
printError("it's kinda required to play tapes, you know?")
|
||||
return
|
||||
end
|
||||
|
||||
local running = true
|
||||
|
||||
local table_of_contents = {}
|
||||
|
||||
local function read32()
|
||||
local v = 0
|
||||
for i = 1, 4 do
|
||||
local b = drive.read()
|
||||
v = bit32.bor(bit32.lshift(v), b)
|
||||
end
|
||||
return v
|
||||
end
|
||||
|
||||
local function readstr(len)
|
||||
local out = ""
|
||||
for i = 1, len do
|
||||
out = out .. drive.read()
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function bytes2time(b)
|
||||
local s = math.floor(b / 6000)
|
||||
if s < 60 then return string.format("%ds", s) end
|
||||
return string.format("%dm, %ds", math.floor(s / 60), s % 60)
|
||||
end
|
||||
|
||||
|
||||
parallel.waitForAll(
|
||||
function()
|
||||
while running do
|
||||
for i, track in ipairs(table_of_contents) do
|
||||
term.setCursorPos(1, i)
|
||||
term.write(string.format("%s (%s)", track.title, bytes2time(track.length)))
|
||||
end
|
||||
os.sleep(0.1)
|
||||
end
|
||||
end,
|
||||
function()
|
||||
while running do
|
||||
local evd = { os.pullEvent() }
|
||||
local ev, evd = table.remove(evd, 1), evd
|
||||
|
||||
if ev == "mouse_click" then
|
||||
local x, y = table.unpack(evd, 1)
|
||||
elseif ev == "tape_present" then
|
||||
table_of_contents = {}
|
||||
if evd[1] then
|
||||
drive.seek(-drive.getSize())
|
||||
for i = 1, 48 do
|
||||
local offset = read32()
|
||||
local length = read32()
|
||||
local title = readstr(117)
|
||||
table.insert(table_of_contents, {
|
||||
title = title,
|
||||
offset = offset,
|
||||
length = length,
|
||||
ending = offset + length
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
function()
|
||||
local tape_was_present = nil
|
||||
local drive_old_state = nil
|
||||
while running do
|
||||
local tape_present = drive.isReady()
|
||||
if tape_present ~= tape_was_present then
|
||||
os.queueEvent("tape_present", tape_present)
|
||||
end
|
||||
|
||||
local drive_state = drive.getState()
|
||||
if drive_old_state ~= drive_state then
|
||||
os.queueEvent("drive_state", drive_state)
|
||||
end
|
||||
|
||||
os.sleep(0.25)
|
||||
end
|
||||
end)
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python3
|
||||
# x-run: python3 % /mnt/windows/Users/user/Music/vgm/night-in-the-woods/2014-lost-constellation /tmp/lost-constellation.dfpwm
|
||||
|
||||
from tempfile import TemporaryDirectory
|
||||
from subprocess import Popen, PIPE
|
||||
from sys import argv
|
||||
from pathlib import Path
|
||||
from os.path import splitext
|
||||
from math import ceil
|
||||
|
||||
DFPWM_ENCODER_EXECUTABLE = "aucmp"
|
||||
FFMPEG_EXECUTABLE = "ffmpeg"
|
||||
|
||||
DFPWM_SAMPLE_RATE = 6000
|
||||
|
||||
TAPE_SIZES = [ 2, 4, 8, 16, 32, 64, 128 ]
|
||||
|
||||
input_folder = Path(argv[1])
|
||||
output_file = Path(argv[2])
|
||||
|
||||
with TemporaryDirectory(prefix="tapedrive") as tmpdir_str:
|
||||
tmpdir = Path(tmpdir_str)
|
||||
|
||||
filelist: list[Path] = []
|
||||
for i, name in enumerate(input_folder.rglob("*.mp3")):
|
||||
if i >= 48:
|
||||
print(f"more than 48 tracks, skipping {name}")
|
||||
continue
|
||||
encoded_file = tmpdir / (splitext(name.name)[0] + ".dfpwm")
|
||||
filelist.append(encoded_file)
|
||||
with encoded_file.open("wb") as fout:
|
||||
with Popen([ DFPWM_ENCODER_EXECUTABLE ], stdout=fout, stdin=PIPE) as dfpwm:
|
||||
with Popen([ FFMPEG_EXECUTABLE, "-i", name, "-f", "s8", "-ac", "1",
|
||||
"-ar", "48k", "-" ], stdout=dfpwm.stdin) as ffmpeg:
|
||||
ffmpeg.wait()
|
||||
|
||||
offset: int = 6000
|
||||
titles: list[bytes] = []
|
||||
positions: list[tuple[int, int]] = []
|
||||
for file in filelist:
|
||||
titles.append(file.name.removesuffix(".dfpwm").encode("ascii", errors="replace"))
|
||||
size = ceil(file.stat().st_size / DFPWM_SAMPLE_RATE) * DFPWM_SAMPLE_RATE
|
||||
positions.append((offset, size))
|
||||
offset += size
|
||||
|
||||
with output_file.open("wb") as fout:
|
||||
print("Writing header...")
|
||||
written_bytes: int = 0
|
||||
for i in range(48):
|
||||
name = (titles[i] if i < len(titles) else b"")[:117]
|
||||
pos = positions[i] if i < len(titles) else (0, 0)
|
||||
written_bytes += fout.write(pos[0].to_bytes(4, "little"))
|
||||
written_bytes += fout.write(pos[0].to_bytes(4, "little"))
|
||||
written_bytes += fout.write(name)
|
||||
written_bytes += fout.write(b"\x00" * (117 - len(name)))
|
||||
if written_bytes != 6000:
|
||||
print(f"!!! expected 6000 bytes to be written in header, but it's {written_bytes}")
|
||||
|
||||
print("Writing files...")
|
||||
for i, (pos, file) in enumerate(zip(positions, filelist)):
|
||||
print(f"Writing {file.name}")
|
||||
if written_bytes != pos[0]:
|
||||
print(f"!!! expected to be at {pos[0]}, rn at {written_bytes}")
|
||||
with file.open("rb") as fin:
|
||||
file_size: int = 0
|
||||
while (chunk := fin.read(DFPWM_SAMPLE_RATE)):
|
||||
file_size += fout.write(chunk)
|
||||
written_bytes += file_size
|
||||
padding_size = DFPWM_SAMPLE_RATE - (written_bytes % DFPWM_SAMPLE_RATE)
|
||||
written_bytes += fout.write(b"\x00" * padding_size)
|
||||
print("Use any of those tapes to store properly:")
|
||||
for size in filter(lambda sz: sz * DFPWM_SAMPLE_RATE * 60 >= written_bytes, TAPE_SIZES):
|
||||
print(f"{size} minutes ({size * DFPWM_SAMPLE_RATE * 60} bytes, {written_bytes * 100 / (size * DFPWM_SAMPLE_RATE * 60):7.3f}% used)")
|
Loading…
Reference in New Issue