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)