local gp3 = (function() local gp3 = {} local dfpwmSecond = 6000 local pcmSecond = dfpwmSecond * 8 local padChar = "\0" local pcmFormat = string.rep("b", pcmSecond) local packedPcmFormat = "c48000" local firstFormat = "> c48000 c4" local secondFormat = "> s2 H H B s1" local checksumFormat = "> I4" local firstFormatSize = string.packsize(firstFormat) local POLY = 0xEDB88320 local crc32_table = {} for i = 0, 255 do local crc = i for _ = 1, 8 do if bit32.band(crc, 1) ~= 0 then crc = bit32.bnot(bit32.rshift(crc, 1), POLY) else crc = bit32.rshift(crc, 1) end end crc32_table[i] = bit32.band(crc, 0xFFFFFFFF) end local function crc32(data) local sbyte = string.byte local band, xor, rshift, bnot = bit32.band, bit32.bxor, bit32.rshift, bit32.bnot local crc = 0xFFFFFFFF for i = 1, #data do local byte = sbyte(data, i) local idx = band(xor(crc, byte), 0xFF) crc = xor(rshift(crc, 8), crc32_table[idx]) end return bnot(crc) % 0x100000000 end local function unpackPcm(pcm) if #pcm < pcmSecond then local pad = pcmSecond - #pcm pcm = pcm .. string.rep(padChar, pad) end return { string.unpack(pcmFormat, pcm) } end local function splitLayout(str) local layout = {} for ch in str:gmatch("[^|]+") do table.insert(layout, ch) end return layout end function gp3.decode(packet) local output = { channels = {}, layout = {} } if #packet < firstFormatSize then local track = packet:sub(1, pcmSecond) return nil, unpackPcm(track) end local track, checksum, pos = string.unpack(firstFormat, packet) local calcChecksum = crc32(track) local firstPcm = unpackPcm(track) if calcChecksum ~= string.unpack(checksumFormat, checksum) then return nil, firstPcm end local details, total, current, extraChannels, layout, pos = string.unpack(secondFormat, packet:sub(pos)) output.details = details output.totalSeconds = total output.currentSeconds = current output.channelCount = extraChannels + 1 output.layout = splitLayout(layout) table.insert(output.channels, firstPcm) local pos, track = pos, nil for i = 1, extraChannels do track, pos = string.unpack(packedPcmFormat, packet, pos) table.insert(output.channels, unpackPcm(track)) end return output end return gp3 end)() local args = {...} local radio = peripheral.find("radio_tower") local frequency = tonumber(args[1]) or 5555 radio.setFrequency(frequency) local pcmSecond = 48000 local formats = {} formats.pcm = string.rep("b", pcmSecond) formats.track = "c" .. pcmSecond -- 1 second of PCM -- big endian, track, crc32, track details, total seconds, current seconds (from 0), extra channels, channels layout formats.packet = "> " .. formats.track .. " c4 s2 H H B s1" local speaker = peripheral.find("speaker") speaker.stop() local buffer = {} local tryPlay = true local function decode(packet) local ok, track, details = pcall(function() local track, checksum, details, total, current, extraChannels, layout, pos = string.unpack(formats.packet, packet) local buffer = { string.unpack(formats.pcm, track) } return buffer, details end) if not ok then return nil, track end return track, details end local function submitNext() local chunk = table.remove(buffer, 1) if chunk then speaker.playAudio(chunk) tryPlay = false end if not chunk then tryPlay = true end end local function queue(chunk) table.insert(buffer, chunk) print(os.clock(), "Buffer Size:", #buffer) if tryPlay then print("Buffering...") if #buffer > 2 then submitNext() end end end parallel.waitForAny(function() while true do local _, _, data = os.pullEvent("radio_message") local track, fallback = gp3.decode(data) --local buffer, details = decode(data) if track then queue(track.channels[1]) print(track.details) else printError("Could not decode. fallback") queue(fallback) end end end, function() while true do os.pullEvent("speaker_audio_empty") submitNext() end end)