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)