230 lines
5.8 KiB
Lua
230 lines
5.8 KiB
Lua
local monitor = peripheral.find("monitor")
|
|
local w, h = monitor.getSize()
|
|
|
|
local recent_logs = {
|
|
["misc"] = {}
|
|
}
|
|
|
|
function ui()
|
|
monitor.clear()
|
|
|
|
local x = 1
|
|
local y = 3
|
|
|
|
monitor.setTextScale(0.5)
|
|
monitor.setCursorPos(x,1)
|
|
monitor.write("Log")
|
|
|
|
|
|
for k, v in pairs(recent_logs) do
|
|
if k ~= "misc" then
|
|
monitor.setCursorPos(x, y)
|
|
monitor.write(k .. " -> " .. v.time)
|
|
monitor.setCursorPos(x, y+1)
|
|
monitor.write(v.log:sub(1, (w/2)-1))
|
|
|
|
y = y+3;
|
|
|
|
if y >= h then
|
|
y = 3
|
|
x = (w/2)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
monitor.setCursorPos(x,y)
|
|
monitor.write("misc")
|
|
y = y +1
|
|
|
|
for _, v in pairs(recent_logs.misc) do
|
|
monitor.setCursorPos(x,y)
|
|
monitor.write(v.log .. " " .. v.time)
|
|
y = y + 1
|
|
if y >= h then
|
|
y = 3
|
|
x = (w/2)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
local dcode = require "dcode"
|
|
local make_decoder = dcode.make_dec
|
|
|
|
local ws = http.websocket("ws://vps.sad.ovh:5821/ws")
|
|
|
|
|
|
local raw = ws.receive()
|
|
local meta = textutils.unserializeJSON(raw)
|
|
print(("codec=%s rate=%dHz frame_ms=%d ch_enc=%d frame_bytes/chan=%d")
|
|
:format(meta.codec, meta.sample_rate, meta.frame_ms, meta.channels_encoded, meta.frame_bytes))
|
|
|
|
local namesSpkrsL = {
|
|
"speaker_756",
|
|
"speaker_755",
|
|
"speaker_754",
|
|
}
|
|
local namesSpkrsR = {
|
|
"speaker_751",
|
|
"speaker_752",
|
|
"speaker_753",
|
|
}
|
|
|
|
local spkrsL = {}
|
|
local spkrsR = {}
|
|
|
|
for i=1,#namesSpkrsL do
|
|
spkrsL[i] = peripheral.wrap(namesSpkrsL[i])
|
|
spkrsR[i] = peripheral.wrap(namesSpkrsR[i])
|
|
end
|
|
|
|
|
|
local function make_queue() return { buf = {}, head = 1, tail = 0 } end
|
|
local function q_len(q) return q.tail - q.head + 1 end
|
|
local function q_push(q, v) q.tail = q.tail + 1; q.buf[q.tail] = v end
|
|
local function q_pop(q) if q.head > q.tail then return nil end local v=q.buf[q.head]; q.buf[q.head]=nil; q.head=q.head+1; return v end
|
|
local function q_trim_to(q, n) while q_len(q) > n do q_pop(q) end end
|
|
|
|
|
|
local MIN_BUFFER, TARGET_BUFFER, MAX_BUFFER = 5, 10, 30
|
|
|
|
local frames = make_queue()
|
|
|
|
local function log(event, extra)
|
|
local t = textutils.formatTime(os.time(), true)
|
|
|
|
if extra == nil then
|
|
table.insert(recent_logs.misc, {
|
|
log = event,
|
|
time = os.time()
|
|
})
|
|
if #recent_logs.misc > 5 then
|
|
table.remove(recent_logs.misc, 1)
|
|
end
|
|
else
|
|
recent_logs[event] = {
|
|
log = extra,
|
|
time = os.time()
|
|
}
|
|
end
|
|
--ui()
|
|
print(("[%s] %-12s | %s"):format(t, event, extra or ""))
|
|
end
|
|
|
|
|
|
local function receiver()
|
|
while true do
|
|
local msg = ws.receive((meta.frame_ms / 1000) * 1.5)
|
|
if msg then
|
|
q_push(frames, msg)
|
|
local n = q_len(frames)
|
|
log("recv", "frames=" .. n)
|
|
if n > MAX_BUFFER then
|
|
log("drop", ("queue %d>%d trimming to %d"):format(n, MAX_BUFFER, TARGET_BUFFER))
|
|
q_trim_to(frames, TARGET_BUFFER)
|
|
end
|
|
else
|
|
log("ws underrun")
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function player()
|
|
local buffering = true
|
|
local last_log = 0
|
|
local decL, decR = make_decoder(), make_decoder()
|
|
|
|
while true do
|
|
local queued = q_len(frames)
|
|
|
|
if buffering then
|
|
if queued < MIN_BUFFER then
|
|
if os.clock() - last_log > 0.5 then
|
|
log("buffering", ("waiting %d/%d"):format(queued, MIN_BUFFER))
|
|
last_log = os.clock()
|
|
end
|
|
os.sleep(meta.frame_ms / 1000 / 4)
|
|
goto continue
|
|
else
|
|
log("start", ("starting playback with %d frames buffered"):format(queued))
|
|
buffering = false
|
|
end
|
|
end
|
|
local frame = q_pop(frames)
|
|
if not frame then
|
|
log("underrun", "no frame available — rebuffering")
|
|
buffering = true
|
|
else
|
|
if meta.channels_encoded == 2 and #frame >= meta.frame_bytes * 6 then
|
|
local B = meta.frame_bytes
|
|
|
|
-- Split frame into per-speaker byte chunks (L1,L2,L3,R1,R2,R3)
|
|
local lBytes = {
|
|
frame:sub(1, B),
|
|
frame:sub(B + 1, 2 * B),
|
|
frame:sub(2 * B + 1, 3 * B),
|
|
}
|
|
local rBytes = {
|
|
frame:sub(3 * B + 1, 4 * B),
|
|
frame:sub(4 * B + 1, 5 * B),
|
|
frame:sub(5 * B + 1, 6 * B),
|
|
}
|
|
|
|
-- Decode per speaker
|
|
local lPcm = { decL(lBytes[1]), decL(lBytes[2]), decL(lBytes[3]) }
|
|
local rPcm = { decR(rBytes[1]), decR(rBytes[2]), decR(rBytes[3]) }
|
|
|
|
for i = 1, 3 do spkrsL[i].playAudio(lPcm[i]) end
|
|
for i = 1, 3 do spkrsR[i].playAudio(rPcm[i]) end
|
|
|
|
local done = {}
|
|
for i = 1, 3 do done[namesSpkrsL[i]] = false; done[namesSpkrsR[i]] = false end
|
|
|
|
while true do
|
|
local allDone = true
|
|
for _, v in pairs(done) do if not v then allDone = false break end end
|
|
if allDone then
|
|
print("all speakers are done")
|
|
break end
|
|
|
|
local _, speaker = os.pullEvent("speaker_audio_empty")
|
|
print("speaker: ".. speaker .. " is done")
|
|
done[speaker] = true
|
|
end
|
|
else
|
|
-- mono stream or short packet: decode and play sequentially
|
|
local pcm = decL(frame)
|
|
if pcm then
|
|
local lDone = (not spkL)
|
|
local rDone = (not spkR)
|
|
while not (lDone and rDone) do
|
|
if not lDone then
|
|
lDone = spkL.playAudio(pcm)
|
|
end
|
|
if not rDone then
|
|
rDone = spkR.playAudio(pcm)
|
|
end
|
|
if not (lDone and rDone) then
|
|
os.pullEvent("speaker_audio_empty")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local n = q_len(frames)
|
|
if n < 1 then
|
|
log("underrun", "ran out after frame — rebuffering")
|
|
buffering = true
|
|
elseif n < MIN_BUFFER and os.clock() - last_log > 0.5 then
|
|
log("lowbuf", ("frames=%d < %d"):format(n, MIN_BUFFER))
|
|
last_log = os.clock()
|
|
end
|
|
end
|
|
::continue::
|
|
end
|
|
end
|
|
|
|
log("init", "waiting for frames…")
|
|
parallel.waitForAny(receiver, player)
|