diff --git a/lib/sanjuuni/32vid-player-mini.lua b/lib/sanjuuni/32vid-player-mini.lua deleted file mode 100644 index 0f6a4c0..0000000 --- a/lib/sanjuuni/32vid-player-mini.lua +++ /dev/null @@ -1,258 +0,0 @@ -local bit32_band, bit32_lshift, bit32_rshift, math_frexp = bit32.band, bit32.lshift, bit32.rshift, math.frexp -local function log2(n) local _, r = math_frexp(n) return r-1 end -local dfpwm = require "cc.audio.dfpwm" - -local speaker = peripheral.find "speaker" -local file -local path = ... -if path:match "^https?://" then - file = assert(http.get(path, nil, true)) -else - file = assert(fs.open(shell.resolve(path), "rb")) -end - -if file.read(4) ~= "32VD" then file.close() error("Not a 32Vid file") end -local width, height, fps, nstreams, flags = ("= 16 then - local l = 2^(t.s - 15) - for n = 0, l-1 do retval[i+n] = last end - i = i + l - else retval[i], last, i = t.s, t.s, i + 1 end - X = t.X + readbits(t.n) - end - --print(X) - return retval - end -else - error("Unimplemented!") -end - -local blitColors = {[0] = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"} -local start = os.epoch "utc" -local lastyield = start -local vframe = 0 -local subs = {} -term.clear() -for _ = 1, nframes do - local size, ftype = (" 3000 then sleep(0) lastyield = os.epoch "utc" end - local dcstart = os.epoch "utc" - --print("init screen", vframe, file.seek()) - init(false) - --print("read screen", vframe, file.seek()) - local screen = read(width * height) - --print("init colors", vframe, file.seek()) - init(true) - --print("read bg colors", vframe) - local bg = read(width * height) - --print("read fg colors", vframe) - local fg = read(width * height) - local dctime = os.epoch "utc" - dcstart - while os.epoch "utc" < start + vframe * 1000 / fps do end - local texta, fga, bga = {}, {}, {} - for y = 0, height - 1 do - local text, fgs, bgs = "", "", "" - for x = 1, width do - text = text .. string.char(128 + screen[y*width+x]) - fgs = fgs .. blitColors[fg[y*width+x]] - bgs = bgs .. blitColors[bg[y*width+x]] - end - texta[y+1], fga[y+1], bga[y+1] = text, fgs, bgs - end - for i = 0, 15 do term.setPaletteColor(2^i, file.read() / 255, file.read() / 255, file.read() / 255) end - for y = 1, height do - term.setCursorPos(1, y) - term.blit(texta[y], fga[y], bga[y]) - end - local delete = {} - for i, v in ipairs(subs) do - if vframe <= v.frame + v.length then - term.setCursorPos(v.x, v.y) - term.setBackgroundColor(v.bgColor) - term.setTextColor(v.fgColor) - term.write(v.text) - else delete[#delete+1] = i end - end - for i, v in ipairs(delete) do table.remove(subs, v - i + 1) end - --term.setCursorPos(1, height + 1) - --term.clearLine() - --print("Frame decode time:", dctime, "ms") - vframe = vframe + 1 - elseif ftype == 1 then - local audio = file.read(size) - if speaker then - if bit32_band(flags, 12) == 0 then - local chunk = {audio:byte(1, -1)} - for i = 1, #chunk do chunk[i] = chunk[i] - 128 end - speaker.playAudio(chunk) - else - speaker.playAudio(dfpwm.decode(audio)) - end - end - elseif ftype == 8 then - local data = file.read(size) - local sub = {} - sub.frame, sub.length, sub.x, sub.y, sub.color, sub.flags, sub.text = ("= 0x40 and ftype < 0x80 then - if ftype == 64 then vframe = vframe + 1 end - local mx, my = bit32_band(bit32_rshift(ftype, 3), 7) + 1, bit32_band(ftype, 7) + 1 - --print("(" .. mx .. ", " .. my .. ")") - local term = monitors[my][mx] - if os.epoch "utc" - lastyield > 3000 then sleep(0) lastyield = os.epoch "utc" end - local width, height = ("= 8*#raw - end - - function stream:pos() - return curr - end - - return stream - end - - -- inflate - - local CL_LENS_ORDER = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15} - local MAX_BITS = 15 - local PT_WIDTH = 8 - - local function cl_code_lens(stream, hclen) - local code_lens = {} - for i = 1, hclen do - code_lens[#code_lens + 1] = bint(stream:read(3)) - end - return code_lens - end - - local function code_tree(lens, alphabet) - alphabet = alphabet or range(#lens) - local using = filter(function(x, i) return lens[i] and lens[i] > 0 end, alphabet) - lens = filter(function(x) return x > 0 end, lens) - local tree = zip(lens, using) - table.sort(tree, function(a, b) - if a[1] == b[1] then - return a[2] < b[2] - else - return a[1] < b[1] - end - end) - return unzip(tree) - end - - local function codes(lens) - local codes = {} - local code = 0 - for i = 1, #lens do - codes[#codes + 1] = bits(code, lens[i]) - if i < #lens then - code = (code + 1)*2^(lens[i + 1] - lens[i]) - end - end - return codes - end - - local prefix_table - local function handle_long_codes(codes, alphabet, pt) - local i = find(function(x) return #x > PT_WIDTH end, codes) - local long = slice(zip(codes, alphabet), i) - i = 0 - repeat - local prefix = long[i + 1][1]:sub(1, PT_WIDTH) - local same = filter(function(x) return x[1]:sub(1, PT_WIDTH) == prefix end, long) - same = map(function(x) return {x[1]:sub(PT_WIDTH + 1), x[2]} end, same) - pt[prefix] = {rest = prefix_table(unzip(same)), unused = 0} - i = i + #same - until i == #long - end - - function prefix_table(codes, alphabet) - local pt = {} - if #codes[#codes] > PT_WIDTH then - handle_long_codes(codes, alphabet, pt) - end - for i = 1, #codes do - local code = codes[i] - if #code > PT_WIDTH then - break - end - local entry = {value = alphabet[i], unused = PT_WIDTH - #code} - if entry.unused == 0 then - pt[code] = entry - else - for i = 0, 2^entry.unused - 1 do - pt[code .. bits(i, entry.unused)] = entry - end - end - end - return pt - end - - local function huffman_decoder(lens, alphabet) - local base_codes = prefix_table(codes(lens), alphabet) - return function(stream) - local codes = base_codes - local entry - repeat - entry = codes[stream:read(PT_WIDTH, true)] - stream:seek(-entry.unused) - codes = entry.rest - until not codes - return entry.value - end - end - - local function code_lens(stream, decode, n) - local lens = {} - repeat - local value = decode(stream) - if value < 16 then - lens[#lens + 1] = value - elseif value == 16 then - for i = 1, bint(stream:read(2)) + 3 do - lens[#lens + 1] = lens[#lens] - end - elseif value == 17 then - for i = 1, bint(stream:read(3)) + 3 do - lens[#lens + 1] = 0 - end - elseif value == 18 then - for i = 1, bint(stream:read(7)) + 11 do - lens[#lens + 1] = 0 - end - end - until #lens == n - return lens - end - - local function code_trees(stream) - local hlit = bint(stream:read(5)) + 257 - local hdist = bint(stream:read(5)) + 1 - local hclen = bint(stream:read(4)) + 4 - local cl_decode = huffman_decoder(code_tree(cl_code_lens(stream, hclen), CL_LENS_ORDER)) - local ll_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hlit))) - local d_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hdist))) - return ll_decode, d_decode - end - - local function extra_bits(value) - if value >= 4 and value <= 29 then - return floor(value/2) - 1 - elseif value >= 265 and value <= 284 then - return ceil(value/4) - 66 - else - return 0 - end - end - extra_bits = memoize(extra_bits) - - local function decode_len(value, bits) - assert(value >= 257 and value <= 285, "value out of range") - assert(#bits == extra_bits(value), "wrong number of extra bits") - if value <= 264 then - return value - 254 - elseif value == 285 then - return 258 - end - local len = 11 - for i = 1, #bits - 1 do - len = len + 2^(i+2) - end - return floor(bint(bits) + len + ((value - 1) % 4)*2^#bits) - end - decode_len = memoize(decode_len) - - local function a(n) - if n <= 3 then - return n + 2 - else - return a(n-1) + 2*a(n-2) - 2*a(n-3) - end - end - a = memoize(a) - - local function decode_dist(value, bits) - assert(value >= 0 and value <= 29, "value out of range") - assert(#bits == extra_bits(value), "wrong number of extra bits)") - return bint(bits) + a(value - 1) - end - decode_dist = memoize(decode_dist) - - function inflate(data) - local stream = bit_stream(data) - local ostream = output_stream() - repeat - local bfinal, btype = bint(stream:read(1)), bint(stream:read(2)) - assert(btype == 2, "compression method not supported") - local ll_decode, d_decode = code_trees(stream) - while true do - local value = ll_decode(stream) - if value < 256 then - ostream:write(string.char(value)) - elseif value == 256 then - break - else - local len = decode_len(value, stream:read(extra_bits(value))) - value = d_decode(stream) - local dist = decode_dist(value, stream:read(extra_bits(value))) - ostream:back_copy(dist, len) - end - end - until bfinal == 1 - return ostream:raw() - end - -end -local dfpwm = require "cc.audio.dfpwm" - -local hexstr = "0123456789abcdef" -local file, err = fs.open(shell.resolve(...), "rb") -if not file then error(err) end -if file.read(4) ~= "32VD" then file.close() error("Not a 32vid file") end -local width, height, fps, nStreams, flags = ("= nFrames - 10 then print(i, os.epoch "utc" - start) start = os.epoch "utc" sleep(0) end - local frame = {palette = {}} - local tmp, tmppos = 0, 0 - local use5bit, customcompress = bit32.btest(flags, 16), bit32.band(flags, 3) == 3 - local codetree = {} - local solidchar, runlen - local function readField(isColor) - if customcompress then - if runlen then - local c = solidchar - runlen = runlen - 1 - if runlen == 0 then runlen = nil end - return c - end - if not isColor and solidchar then return solidchar end - -- MARK: Huffman decoding - local node = codetree - while true do - local n = bit32.extract(tmp, tmppos, 1) - tmppos = tmppos - 1 - if tmppos < 0 then tmp, pos, tmppos = data:byte(pos), pos + 1, 7 end - if type(node) ~= "table" then error(("Invalid tree state: position %X, frame %d"):format(pos+file.seek()-size-1, i)) end - if type(node[n]) == "number" then - local c = node[n] - if isColor then - if c > 15 then runlen = 2^(c-15)-1 return assert(solidchar) - else solidchar = c end - end - return c - else node = node[n] end - end - else - local n - if tmppos * 5 + 5 > 32 then n = bit32.extract(math.floor(tmp / 0x1000000), tmppos * 5 - 24, 5) - else n = bit32.extract(tmp, tmppos * 5, 5) end - tmppos = tmppos - 1 - if tmppos < 0 then tmp, pos = (">I5"):unpack(data, pos) tmppos = 7 end - return n - end - end - if customcompress then - -- MARK: Huffman tree reconstruction - -- read bit lengths - local bitlen = {} - if use5bit then - for j = 0, 15 do - bitlen[j*2+1], bitlen[j*2+2] = {s = j*2, l = bit32.rshift(data:byte(pos+j), 4)}, {s = j*2+1, l = bit32.band(data:byte(pos+j), 0x0F)} - end - pos = pos + 16 - else - for j = 0, 7 do - tmp, pos = (">I5"):unpack(data, pos) - bitlen[j*8+1] = {s = j*8+1, l = math.floor(tmp / 0x800000000)} - bitlen[j*8+2] = {s = j*8+2, l = math.floor(tmp / 0x40000000) % 32} - for k = 3, 8 do bitlen[j*8+k] = {s = j*8+k, l = bit32.extract(tmp, (8-k)*5, 5)} end - end - end - do - local j = 1 - while j <= #bitlen do - if bitlen[j].l == 0 then table.remove(bitlen, j) - else j = j + 1 end - end - end - if #bitlen == 0 then - -- screen is solid character - solidchar = data:byte(pos) - pos = pos + 1 - else - -- reconstruct codes from bit lengths - table.sort(bitlen, function(a, b) if a.l == b.l then return a.s < b.s else return a.l < b.l end end) - bitlen[1].c = 0 - for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end - -- create tree from codes - for j = 1, #bitlen do - local c = bitlen[j].c - local node = codetree - for k = bitlen[j].l - 1, 1, -1 do - local n = bit32.extract(c, k, 1) - if not node[n] then node[n] = {} end - node = node[n] - if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end - end - local n = bit32.extract(c, 0, 1) - node[n] = bitlen[j].s - end - -- read first byte - tmp, tmppos, pos = data:byte(pos), 7, pos + 1 - end - else readField() end - for y = 1, height do - local line = {"", "", "", {}} - for x = 1, width do - if pos + 5 + 1 >= #data then print(i, pos, x, y) error() end - local n = readField() - line[1] = line[1] .. string.char(128 + (n % 0x20)) - line[4][x] = bit32.btest(n, 0x20) - end - frame[y] = line - end - if customcompress then - if tmppos == 7 then pos = pos - 1 end - codetree = {} - -- MARK: Huffman tree reconstruction - -- read bit lengths - local bitlen = {} - for j = 0, 11 do - bitlen[j*2+1], bitlen[j*2+2] = {s = j*2, l = bit32.rshift(data:byte(pos+j), 4)}, {s = j*2+1, l = bit32.band(data:byte(pos+j), 0x0F)} - end - pos = pos + 12 - do - local j = 1 - while j <= #bitlen do - if bitlen[j].l == 0 then table.remove(bitlen, j) - else j = j + 1 end - end - end - if #bitlen == 0 then - -- screen is solid color - solidchar = data:byte(pos) - pos = pos + 1 - runlen = math.huge - else - -- reconstruct codes from bit lengths - table.sort(bitlen, function(a, b) if a.l == b.l then return a.s < b.s else return a.l < b.l end end) - bitlen[1].c = 0 - for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end - -- create tree from codes - for j = 1, #bitlen do - local c = bitlen[j].c - local node = codetree - for k = bitlen[j].l - 1, 1, -1 do - local n = bit32.extract(c, k, 1) - if not node[n] then node[n] = {} end - node = node[n] - if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end - end - local n = bit32.extract(c, 0, 1) - node[n] = bitlen[j].s - end - -- read first byte - tmp, tmppos, pos = data:byte(pos), 7, pos + 1 - end - for y = 1, height do - local line = frame[y] - for x = 1, width do - local c = readField(true) - line[2] = line[2] .. hexstr:sub(c+1, c+1) - end - end - runlen = nil - for y = 1, height do - local line = frame[y] - for x = 1, width do - local c = readField(true) - line[3] = line[3] .. hexstr:sub(c+1, c+1) - end - end - if tmppos == 7 then pos = pos - 1 end - else - for y = 1, height do - local line = frame[y] - for x = 1, width do - local c = data:byte(pos) - pos = pos + 1 - if line[4][x] then c = bit32.bor(bit32.band(bit32.lshift(c, 4), 0xF0), bit32.rshift(c, 4)) end - line[2] = line[2] .. hexstr:sub(bit32.band(c, 15)+1, bit32.band(c, 15)+1) - line[3] = line[3] .. hexstr:sub(bit32.rshift(c, 4)+1, bit32.rshift(c, 4)+1) - end - line[4] = nil - end - end - for n = 1, 16 do frame.palette[n], pos = {data:byte(pos) / 255, data:byte(pos+1) / 255, data:byte(pos+2) / 255}, pos + 3 end - video[i] = frame - end - elseif frameType == 1 and not audio then - audio = {} - if bit32.band(flags, 12) == 0 then - for i = 1, math.ceil(size / 48000) do - local data - if jit then - data = {} - for j = 0, 5 do - local t = {file.read(math.min(size - (i-1) * 48000 - j * 8000, 8000)):byte(1, -1)} - if #t > 0 then for k = 1, #t do data[j*8000+k] = t[k] end end - end - else data = {file.read(math.min(size - (i-1) * 48000, 48000)):byte(1, -1)} end - for j = 1, #data do data[j] = data[j] - 128 end - audio[i] = data - end - elseif bit32.band(flags, 12) == 4 then - local decode = dfpwm.make_decoder() - for i = 1, math.ceil(size / 6000) do - local data = file.read(math.min(size - (i-1)*6000, 6000)) - if not data then break end - audio[i] = decode(data) - end - end - elseif frameType == 8 and not subtitles then - subtitles = {} - for _ = 1, nFrames do - local start, length, x, y, color, sz = ("") end -local file, err = fs.open(shell.resolve(path), "rb") -if not file then error("Could not open file: " .. err) end -local img = textutils.unserialize(file.readAll()) -file.close() -local function drawFrame(frame, term) - for y, row in ipairs(frame) do - term.setCursorPos(1, y) - term.blit(table.unpack(row)) - end - if frame.palette then for i = 0, #frame.palette do - local c = frame.palette[i] - if type(c) == "table" then term.setPaletteColor(2^i, table.unpack(c)) - else term.setPaletteColor(2^i, c) end - end end - if img.multiMonitor then term.setTextScale(img.multiMonitor.scale or 0.5) end -end -if img.multiMonitor then - local width, height = img.multiMonitor.width, img.multiMonitor.height - local monitors = settings.get('sanjuuni.multimonitor') - if not monitors or #monitors < height or #monitors[1] < width then - term.clear() - term.setCursorPos(1, 1) - print('This image needs monitors to be calibrated before being displayed. Please right-click each monitor in order, from the top left corner to the bottom right corner, going right first, then down.') - monitors = {} - local names = {} - for y = 1, height do - monitors[y] = {} - for x = 1, width do - local _, oy = term.getCursorPos() - for ly = 1, height do - term.setCursorPos(3, oy + ly - 1) - term.clearLine() - for lx = 1, width do term.blit('\x8F ', (lx == x and ly == y) and '00' or '77', 'ff') end - end - term.setCursorPos(3, oy + height) - term.write('(' .. x .. ', ' .. y .. ')') - term.setCursorPos(1, oy) - repeat - local _, name = os.pullEvent('monitor_touch') - monitors[y][x] = name - until not names[name] - names[monitors[y][x]] = true - sleep(0.25) - end - end - settings.set('sanjuuni.multimonitor', monitors) - settings.save() - print('Calibration complete. Settings have been saved for future use.') - end - for i = 1, #img, width * height do - for y = 1, height do - for x = 1, width do - drawFrame(img[i + (y-1) * width + x-1], peripheral.wrap(monitors[y][x])) - end - end - if img.animation then sleep(img[i].duration or img.secondsPerFrame or 0.05) - else read() break end - end -else - term.clear() - for _, frame in ipairs(img) do - drawFrame(frame, term) - if img.animation then sleep(frame.duration or img.secondsPerFrame or 0.05) - else read() break end - end - term.setBackgroundColor(colors.black) - term.setTextColor(colors.white) - term.clear() - term.setCursorPos(1, 1) - for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end -end diff --git a/lib/sanjuuni/raw-player.lua b/lib/sanjuuni/raw-player.lua deleted file mode 100644 index 8ad915d..0000000 --- a/lib/sanjuuni/raw-player.lua +++ /dev/null @@ -1,80 +0,0 @@ -local b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -local function base64decode(str) - local retval = "" - for s in str:gmatch "...." do - if s:sub(3, 4) == '==' then - retval = retval .. string.char(bit32.bor(bit32.lshift(b64str:find(s:sub(1, 1)) - 1, 2), bit32.rshift(b64str:find(s:sub(2, 2)) - 1, 4))) - elseif s:sub(4, 4) == '=' then - local n = (b64str:find(s:sub(1, 1))-1) * 4096 + (b64str:find(s:sub(2, 2))-1) * 64 + (b64str:find(s:sub(3, 3))-1) - retval = retval .. string.char(bit32.extract(n, 10, 8)) .. string.char(bit32.extract(n, 2, 8)) - else - local n = (b64str:find(s:sub(1, 1))-1) * 262144 + (b64str:find(s:sub(2, 2))-1) * 4096 + (b64str:find(s:sub(3, 3))-1) * 64 + (b64str:find(s:sub(4, 4))-1) - retval = retval .. string.char(bit32.extract(n, 16, 8)) .. string.char(bit32.extract(n, 8, 8)) .. string.char(bit32.extract(n, 0, 8)) - end - end - return retval -end - -local file, err = fs.open(shell.resolve(...), "r") -if not file then error(err) end -if file.readLine() ~= "32Vid 1.1" then error("Unsupported file") end -local fps = tonumber(file.readLine()) -local first, second = file.readLine(), file.readLine() -if second == "" or second == nil then fps = 0 end -term.clear() -while true do - local frame - if first then frame, first = first, nil - elseif second then frame, second = second, nil - else frame = file.readLine() end - if frame == "" or frame == nil then break end - local mode = frame:match("^!CP([CD])") - if not mode then error("Invalid file") end - local b64data - if mode == "C" then - local len = tonumber(frame:sub(5, 8), 16) - b64data = frame:sub(9, len + 8) - else - local len = tonumber(frame:sub(5, 16), 16) - b64data = frame:sub(17, len + 16) - end - local data = base64decode(b64data) - -- TODO: maybe verify checksums? - assert(data:sub(1, 4) == "\0\0\0\0" and data:sub(9, 16) == "\0\0\0\0\0\0\0\0", "Invalid file") - local width, height = ("HH"):unpack(data, 5) - local c, n, pos = string.unpack("c1B", data, 17) - local text = {} - for y = 1, height do - text[y] = "" - for x = 1, width do - text[y] = text[y] .. c - n = n - 1 - if n == 0 then c, n, pos = string.unpack("c1B", data, pos) end - end - end - c = c:byte() - for y = 1, height do - local fg, bg = "", "" - for x = 1, width do - fg, bg = fg .. ("%x"):format(bit32.band(c, 0x0F)), bg .. ("%x"):format(bit32.rshift(c, 4)) - n = n - 1 - if n == 0 then c, n, pos = string.unpack("BB", data, pos) end - end - term.setCursorPos(1, y) - term.blit(text[y], fg, bg) - end - pos = pos - 2 - local r, g, b - for i = 0, 15 do - r, g, b, pos = string.unpack("BBB", data, pos) - term.setPaletteColor(2^i, r / 255, g / 255, b / 255) - end - if fps == 0 then read() break - else sleep(1 / fps) end -end -file.close() -for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end -term.setBackgroundColor(colors.black) -term.setTextColor(colors.white) -term.clear() -term.setCursorPos(1, 1) \ No newline at end of file diff --git a/lib/sanjuuni/websocket-player.lua b/lib/sanjuuni/websocket-player.lua deleted file mode 100644 index 27791a5..0000000 --- a/lib/sanjuuni/websocket-player.lua +++ /dev/null @@ -1,51 +0,0 @@ -local ws, err = http.websocket(...) -if not ws then error("Could not connect to WebSocket server: " .. err) end -ws.send("n") -local nFrames = tonumber(ws.receive(), 10) -ws.send("f") -local fps = tonumber(ws.receive(), 10) -local speaker = peripheral.find "speaker" -term.clear() -local lock = false -parallel.waitForAll(function() - local start = os.epoch "utc" - for f = 0, nFrames - 1 do - while lock do os.pullEvent() end - lock = true - ws.send("v" .. f) - local frame, ok = ws.receive() - while #frame % 65535 == 0 do frame = frame .. ws.receive() end - lock = false - --if not ok then break end - if load(frame) then - local image, palette = assert(load(frame, "=frame", "t", {}))() - for i = 0, #palette do term.setPaletteColor(2^i, table.unpack(palette[i])) end - for y, r in ipairs(image) do - term.setCursorPos(1, y) - term.blit(table.unpack(r)) - end - end - while os.epoch "utc" < start + (f + 1) / fps * 1000 do sleep(1 / fps) end - end -end, function() - if not speaker or not speaker.playAudio then return end - local pos = 0 - while pos < nFrames / fps * 48000 do - while lock do os.pullEvent() end - lock = true - ws.send("a" .. pos) - local audio, ok = ws.receive(1) - lock = false - if not ok then break end - audio = {audio:byte(1, -1)} - for i = 1, #audio do audio[i] = audio[i] - 128 end - pos = pos + #audio - while not speaker.playAudio(audio) do repeat local ev, sp = os.pullEvent("speaker_audio_empty") until sp == peripheral.getName(speaker) end - end -end) -ws.close() -for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end -term.setBackgroundColor(colors.black) -term.setTextColor(colors.white) -term.setCursorPos(1, 1) -term.clear()