1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-07-05 11:32:53 +00:00

Add cc.audio.wav module

This commit is contained in:
MCJack123 2024-08-08 00:20:52 -04:00
parent 764e1aa332
commit b04914065a
No known key found for this signature in database
GPG Key ID: 1D99413F734AA894
4 changed files with 314 additions and 53 deletions

View File

@ -13,7 +13,7 @@ Typically DFPWM audio is read from [the filesystem][`fs.ReadHandle`] or a [a web
and converted a format suitable for [`speaker.playAudio`]. and converted a format suitable for [`speaker.playAudio`].
## Encoding and decoding files ## Encoding and decoding files
This modules exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder. This module exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
The returned encoder/decoder is itself a function, which converts between the two kinds of data. The returned encoder/decoder is itself a function, which converts between the two kinds of data.
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
@ -21,9 +21,9 @@ a specific audio stream. Typically you will want to create a decoder for each st
for each one you write. for each one you write.
## Converting audio to DFPWM ## Converting audio to DFPWM
DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it. DFPWM is not a popular file format and so standard audio processing tools may not have an option to export to it.
Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java
application or development builds of [FFmpeg]. application or [FFmpeg] 5.1 or later.
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked" [music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter " [LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "

View File

@ -0,0 +1,224 @@
-- SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
--
-- SPDX-License-Identifier: MPL-2.0
--[[-
Read WAV audio files into a table, including audio data.
WAV is a common file format used to store audio with metadata, including
information about the type of audio stored inside. WAV can store many different
types of codecs inside, including PCM and [DFPWM][`cc.audio.dfpwm`].
This module exposes a function to parse a WAV file into a table, [`readWAV`].
This function takes in the binary data from a WAV file, and outputs a more
usable table format with all the metadata and file audio inside. It also has a
[`readWAVFile`] function to simplify reading from a single file.
@see cc.audio.play To play the chunk decoded by this module.
@since 1.113.0
@usage Reads "data/example.wav" into a table, prints its codec, sample rate,
and length in seconds, and plays the audio on a speaker.
```lua
local wav = require("cc.audio.wav")
local speaker = peripheral.find("speaker")
local audio = wav.readWAVFile("data/example.wav")
print("Codec type:", audio.codec)
print("Sample rate:", audio.sampleRate, "Hz")
-- audio.length is the length in samples; divide by sample rate to get seconds
print("Length:", audio.length / audio.sampleRate, "s")
for chunk in audio.read, 131072 do
while not speaker.playAudio(chunk) do
os.pullEvent("speaker_audio_empty")
end
end
```
]]
local expect = require "cc.expect".expect
local dfpwm = require "cc.audio.dfpwm"
local str_unpack, str_sub, math_floor = string.unpack, string.sub, math.floor
local dfpwmUUID = "3ac1fa38-811d-4361-a40d-ce53ca607cd1" -- UUID for DFPWM in WAV files
local function uuidBytes(uuid) return uuid:gsub("-", ""):gsub("%x%x", function(c) return string.char(tonumber(c, 16)) end) end
local wavExtensible = {
dfpwm = uuidBytes(dfpwmUUID),
pcm = uuidBytes "01000000-0000-1000-8000-00aa00389b71",
msadpcm = uuidBytes "02000000-0000-1000-8000-00aa00389b71",
alaw = uuidBytes "06000000-0000-1000-8000-00aa00389b71",
ulaw = uuidBytes "07000000-0000-1000-8000-00aa00389b71",
adpcm = uuidBytes "11000000-0000-1000-8000-00aa00389b71",
pcm_float = uuidBytes "03000000-0000-1000-8000-00aa00389b71"
}
local wavMetadata = {
IPRD = "album",
INAM = "title",
IART = "artist",
IWRI = "author",
IMUS = "composer",
IPRO = "producer",
IPRT = "trackNumber",
ITRK = "trackNumber",
IFRM = "trackCount",
PRT1 = "partNumber",
PRT2 = "partCount",
TLEN = "length",
IRTD = "rating",
ICRD = "date",
ITCH = "encodedBy",
ISFT = "encoder",
ISRF = "media",
IGNR = "genre",
ICMT = "comment",
ICOP = "copyright",
ILNG = "language"
}
--[[- Read WAV data into a table.
The returned table contains the following fields:
- `codec`: A string with information about the codec used in the file (one of `u8`, `s16`, `s24`, `s32`, `f32`, `dfpwm`)
- `sampleRate`: The sample rate of the audio in Hz. If this is not 48000, the file will need to be resampled to play correctly.
- `channels`: The number of channels in the file (1 = mono, 2 = stereo).
- `length`: The number of samples in the file. Divide by sample rate to get seconds.
- `metadata`: If the WAV file contains `INFO` metadata, this table contains the metadata.
Known keys are converted to friendly names like `artist`, `album`, and `track`, while unknown keys are kept the same.
Otherwise, this table is empty.
- `read(length: number): number[]...`: This is a function that reads the audio data in chunks.
It takes the number of samples to read, and returns each channel chunk as multiple return values.
Channel data is in the same format as `speaker.playAudio` takes: 8-bit signed numbers.
@tparam string data The WAV data to read.
@treturn table The decoded WAV file data table.
]]
local function readWAV(data)
expect(1, data, "string")
local bitDepth, length, dataType, blockAlign, coefficients
local temp, pos = str_unpack("c4", data)
if temp ~= "RIFF" then error("bad argument #1 (not a WAV file)", 2) end
pos = pos + 4
temp, pos = str_unpack("c4", data, pos)
if temp ~= "WAVE" then error("bad argument #1 (not a WAV file)", 2) end
local retval = {metadata = {}}
while pos <= #data do
local size
temp, pos = str_unpack("c4", data, pos)
size, pos = str_unpack("<I", data, pos)
if temp == "fmt " then
local chunk = str_sub(data, pos, pos + size - 1)
pos = pos + size
local format
format, retval.channels, retval.sampleRate, blockAlign, bitDepth = str_unpack("<HHIxxxxHH", chunk)
if format == 1 then
dataType = bitDepth == 8 and "unsigned" or "signed"
retval.codec = (bitDepth == 8 and "u" or "s") .. bitDepth
elseif format == 3 then
dataType = "float"
retval.codec = "f32"
elseif format == 0xFFFE then
bitDepth = str_unpack("<H", chunk, 19)
local uuid = str_sub(chunk, 25, 40)
if uuid == wavExtensible.pcm then
dataType = bitDepth == 8 and "unsigned" or "signed"
retval.codec = (bitDepth == 8 and "u" or "s") .. bitDepth
elseif uuid == wavExtensible.dfpwm then
dataType = "dfpwm"
retval.codec = "dfpwm"
elseif uuid == wavExtensible.pcm_float then
dataType = "float"
retval.codec = "f32"
else error("unsupported WAV file", 2) end
else error("unsupported WAV file", 2) end
elseif temp == "data" then
local data = str_sub(data, pos, pos + size - 1)
if #data < size then error("invalid WAV file", 2) end
if not retval.length then retval.length = size / blockAlign end
pos = pos + size
local pos = 1
local channels = retval.channels
if dataType == "dfpwm" then
local decoder = dfpwm.make_decoder()
function retval.read(samples)
if pos > #data then return nil end
local chunk = decoder(str_sub(data, pos, pos + math.ceil(samples * channels / 8) - 1))
pos = pos + math.ceil(samples * channels / 8)
local res = {}
for i = 1, channels do
local c = {}
res[i] = c
for j = 1, samples do
c[j] = chunk[(j - 1) * channels + i]
end
end
return table.unpack(res)
end
else
local format = (dataType == "unsigned" and "I" .. (bitDepth / 8) or (dataType == "signed" and "i" .. (bitDepth / 8) or "f"))
local transform
if dataType == "unsigned" then
function transform(n) return n - 128 end
elseif dataType == "signed" then
if bitDepth == 16 then function transform(n) return math_floor(n / 0x100) end
elseif bitDepth == 24 then function transform(n) return math_floor(n / 0x10000) end
elseif bitDepth == 32 then function transform(n) return math_floor(n / 0x1000000) end end
elseif dataType == "float" then
function transform(n) return math_floor(n * (n < 0 and 128 or 127)) end
end
function retval.read(samples)
if pos > #data then return nil end
local chunk = {("<" .. format:rep(math.min(samples * channels, (#data - pos + 1) / (bitDepth / 8)))):unpack(data, pos)}
pos = table.remove(chunk)
local res = {}
for i = 1, channels do
local c = {}
res[i] = c
for j = 1, samples do
c[j] = transform(chunk[(j - 1) * channels + i])
end
end
return table.unpack(res)
end
end
elseif temp == "fact" then
retval.length, pos = str_unpack("<I4", data, pos)
elseif temp == "LIST" then
local type = str_unpack("c4", data, pos)
if type == "INFO" then
local e = pos + size
pos = pos + 4
while pos < e do
local str
type, str, pos = str_unpack("!2<c4s4Xh", data, pos)
str = str:gsub("\0+$", "")
if wavMetadata[type] then retval.metadata[wavMetadata[type]] = tonumber(str) or str
else retval.metadata[type] = tonumber(str) or str end
end
else pos = pos + size end
else pos = pos + size end
end
if not retval.read then error("invalid WAV file", 2) end
return retval
end
--- Reads a WAV file from a path.
--
-- This functions identically to [`readWAV`], but reads from a file instead.
--
-- @tparam string path The (absolute) path to read from.
-- @treturn table The decoded WAV file table.
-- @see readWAV To read WAV data from a string.
local function readWAVFile(path)
expect(1, path, "string")
local file = assert(fs.open(path, "rb"))
local data = file.readAll()
file.close()
return readWAV(data)
end
return {readWAV = readWAV, readWAVFile = readWAVFile}

View File

@ -22,14 +22,6 @@ local function get_speakers(name)
end end
end end
local function pcm_decoder(chunk)
local buffer = {}
for i = 1, #chunk do
buffer[i] = chunk:byte(i) - 128
end
return buffer
end
local function report_invalid_format(format) local function report_invalid_format(format)
printError(("speaker cannot play %s files."):format(format)) printError(("speaker cannot play %s files."):format(format))
local pp = require "cc.pretty" local pp = require "cc.pretty"
@ -63,50 +55,21 @@ elseif cmd == "play" then
end end
local start = handle.read(4) local start = handle.read(4)
local pcm = false local wav = false
local size = 16 * 1024 - 4 local size = 16 * 1024 - 4
if start == "RIFF" then if start == "RIFF" then
handle.read(4) local data = start .. handle.readAll()
if handle.read(8) ~= "WAVEfmt " then handle.close()
handle.close() local ok
error("Could not play audio: Unsupported WAV file", 0) ok, handle = pcall(require("cc.audio.wav").readWAV, data)
if not ok then
printError("Could not play audio:")
error(err, 0)
end end
wav = true
local fmtsize = ("<I4"):unpack(handle.read(4))
local fmt = handle.read(fmtsize)
local format, channels, rate, _, _, bits = ("<I2I2I4I4I2I2"):unpack(fmt)
if not ((format == 1 and bits == 8) or (format == 0xFFFE and bits == 1)) then
handle.close()
error("Could not play audio: Unsupported WAV file", 0)
end
if channels ~= 1 or rate ~= 48000 then
print("Warning: Only 48 kHz mono WAV files are supported. This file may not play correctly.")
end
if format == 0xFFFE then
local guid = fmt:sub(25)
if guid ~= "\x3A\xC1\xFA\x38\x81\x1D\x43\x61\xA4\x0D\xCE\x53\xCA\x60\x7C\xD1" then -- DFPWM format GUID
handle.close()
error("Could not play audio: Unsupported WAV file", 0)
end
size = size + 4
else
pcm = true
size = 16 * 1024 * 8
end
repeat
local chunk = handle.read(4)
if chunk == nil then
handle.close()
error("Could not play audio: Invalid WAV file", 0)
elseif chunk ~= "data" then -- Ignore extra chunks
local size = ("<I4"):unpack(handle.read(4))
handle.read(size)
end
until chunk == "data"
handle.read(4)
start = nil start = nil
if handle.sampleRate ~= 48000 then error("Could not play audio: Unsupported sample rate") end
if handle.channels ~= 1 then printError("This audio file has more than one channel. It may not play correctly.") end
-- Detect several other common audio files. -- Detect several other common audio files.
elseif start == "OggS" then return report_invalid_format("Ogg") elseif start == "OggS" then return report_invalid_format("Ogg")
elseif start == "fLaC" then return report_invalid_format("FLAC") elseif start == "fLaC" then return report_invalid_format("FLAC")
@ -114,9 +77,13 @@ elseif cmd == "play" then
elseif start == "<!DO" --[[<!DOCTYPE]] then return report_invalid_format("HTML") elseif start == "<!DO" --[[<!DOCTYPE]] then return report_invalid_format("HTML")
end end
print("Playing " .. file) if handle.metadata and handle.metadata.title and handle.metadata.artist then
print("Playing " .. handle.metadata.artist .. " - " .. handle.metadata.title)
else
print("Playing " .. file)
end
local decoder = pcm and pcm_decoder or require "cc.audio.dfpwm".make_decoder() local decoder = wav and function(c) return c end or require "cc.audio.dfpwm".make_decoder()
while true do while true do
local chunk = handle.read(size) local chunk = handle.read(size)
if not chunk then break end if not chunk then break end

View File

@ -0,0 +1,70 @@
-- SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
--
-- SPDX-License-Identifier: MPL-2.0
describe("cc.audio.wav", function()
local wav = require "cc.audio.wav"
describe("readWAV", function()
it("parses an 8-bit unsigned file", function()
local input = "RIFF\44\0\0\0WAVEfmt \16\0\0\0\1\0\1\0\x44\xAC\0\0\x44\xAC\0\0\1\0\8\0data\8\0\0\0\x7C\x7D\x7E\x7F\x80\x81\x82\x83"
local decoded = wav.readWAV(input)
expect(decoded.codec):describe("The codec matches"):eq("u8")
expect(decoded.length):describe("The length matches"):eq(8)
expect(decoded.channels):describe("The channels match"):eq(1)
expect(decoded.sampleRate):describe("The sample rate matches"):eq(44100)
local chunk = decoded.read(8)
expect(chunk):describe("The chunk is read successfully"):type("table")
expect(#chunk):describe("The chunk length is correct"):eq(8)
expect(decoded.read(1)):describe("The reader is finished"):eq(nil)
for i = 1, 8 do expect(chunk[i]):describe("Item at #" .. i):eq(i - 5) end
end)
it("parses a stereo 16-bit signed file", function()
local input = "RIFF\68\0\0\0WAVEfmt \16\0\0\0\1\0\2\0\x80\xBB\0\0\0\xEE\2\0\4\0\16\0data\32\0\0\0\0\xFC\0\3\0\xFD\0\2\0\xFE\0\1\0\xFF\0\0\0\0\0\xFF\0\1\0\xFE\0\2\0\xFD\0\3\0\xFC"
local decoded = wav.readWAV(input)
expect(decoded.codec):describe("The codec matches"):eq("s16")
expect(decoded.length):describe("The length matches"):eq(8)
expect(decoded.channels):describe("The channels match"):eq(2)
expect(decoded.sampleRate):describe("The sample rate matches"):eq(48000)
local chunkL, chunkR = decoded.read(8)
expect(chunkL):describe("The left chunk is read successfully"):type("table")
expect(chunkR):describe("The right chunk is read successfully"):type("table")
expect(#chunkL):describe("The left chunk length is correct"):eq(8)
expect(#chunkR):describe("The right chunk length is correct"):eq(8)
expect(decoded.read(1)):describe("The reader is finished"):eq(nil)
for i = 1, 8 do
expect(chunkL[i]):describe("Left item at #" .. i):eq(i - 5)
expect(chunkR[i]):describe("Right item at #" .. i):eq(4 - i)
end
end)
it("parses a DFPWM-WAV file with metadata", function()
local input = "RIFF\27\1\0\0WAVEfmt \x28\x00\x00\x00\xFE\xFF\x01\x00\x80\xBB\x00\x00\x70\x17\x00\x00\x01\x00\x01\x00\x16\x00\x01\x00\x01\x00\x00\x00\x3A\xC1\xFA\x38\x81\x1D\x43\x61\xA4\x0D\xCE\x53\xCA\x60\x7C\xD1\x66\x61\x63\x74\x04\x00\x00\x00\x00\x04\x00\x00\x4C\x49\x53\x54\x4A\x00\x00\x00\x49\x4E\x46\x4F\x49\x41\x52\x54\x05\x00\x00\x00\x74\x65\x73\x74\x00\x00\x49\x43\x52\x44\x05\x00\x00\x00\x32\x30\x32\x34\x00\x00\x49\x50\x52\x44\x0C\x00\x00\x00\x43\x43\x3A\x20\x54\x77\x65\x61\x6B\x65\x64\x00\x49\x53\x46\x54\x0E\x00\x00\x00\x4C\x61\x76\x66\x35\x39\x2E\x31\x38\x2E\x31\x30\x30\x00data\128\0\0\0\43\225\33\44\30\240\171\23\253\201\46\186\68\189\74\160\188\16\94\169\251\87\11\240\19\92\85\185\126\5\172\64\17\250\85\245\255\169\244\1\85\200\33\176\82\104\163\17\126\23\91\226\37\224\117\184\198\11\180\19\148\86\191\246\255\188\231\10\210\85\124\202\15\232\43\162\117\63\220\15\250\88\87\230\173\106\41\13\228\143\246\190\119\169\143\68\201\40\149\62\20\72\3\160\114\169\254\39\152\30\20\42\84\24\47\64\43\61\221\95\191\42\61\42\206\4\247\81"
local output = { 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, -1, -2, -2, -1, 0, 1, 0, -1, -3, -5, -5, -5, -7, -9, -11, -11, -9, -9, -9, -9, -10, -12, -12, -10, -8, -6, -6, -8, -10, -12, -14, -16, -18, -17, -15, -12, -9, -6, -3, -2, -2, -2, -2, -2, -2, 0, 3, 6, 7, 7, 7, 4, 1, 1, 1, 1, 3, 5, 7, 9, 12, 15, 15, 12, 12, 12, 9, 9, 11, 12, 12, 14, 16, 17, 17, 17, 14, 11, 11, 11, 10, 12, 14, 14, 13, 13, 10, 9, 9, 7, 5, 4, 4, 4, 4, 4, 6, 8, 10, 10, 10, 10, 10, 10, 10, 9, 8, 8, 8, 7, 6, 4, 2, 0, 0, 0, 0, 0, -1, -1, 0, 1, 3, 3, 3, 3, 2, 0, -2, -2, -2, -3, -5, -7, -7, -5, -3, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -1, -1, 0, 1, 1, 1, 2, 3, 4, 5, 6, 7, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 9, 8, 7, 6, 4, 2, 0, 0, 2, 4, 6, 8, 10, 10, 8, 7, 7, 5, 3, 1, -1, 0, 2, 4, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 4, 5, 5, 5, 5, 5, 6, 7, 8, 9, 10, 9, 9, 9, 9, 9, 8, 7, 6, 5, 3, 1, 1, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -3, -3, -3, -3, -2, -3, -4, -4, -3, -4, -5, -6, -6, -5, -5, -4, -3, -2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 18, 20, 20, 17, 16, 16, 15, 15, 15, 15, 13, 13, 13, 13, 14, 15, 16, 18, 18, 16, 14, 12, 10, 8, 5, 5, 5, 4, 4, 4, 4, 4, 4, 2, 0, -2, -2, -2, -4, -4, -2, 0, 0, -2, -4, -6, -6, -6, -8, -10, -12, -14, -16, -15, -13, -12, -11, -11, -11, -11, -13, -13, -13, -13, -13, -14, -16, -18, -18, -18, -18, -16, -16, -16, -14, -13, -14, -15, -15, -14, -14, -12, -11, -12, -13, -13, -12, -13, -14, -15, -15, -13, -11, -9, -7, -5, -5, -5, -3, -1, -1, -1, -1, -3, -5, -5, -3, -3, -3, -1, -1, -1, -1, -3, -3, -3, -4, -6, -6, -4, -2, 0, 0, 0, 0, -2, -2, -2, -3, -5, -7, -9, -11, -13, -13, -11, -9, -7, -6, -6, -6, -6, -4, -2, -2, -4, -6, -8, -7, -5, -3, -2, -2, -2, -2, 0, 0, -2, -4, -4, -2, 0, 2, 2, 1, 1, -1, -3, -5, -7, -10, -10, -10, -10, -8, -7, -7, -5, -3, -2, -4, -4, -4, -6, -8, -10, -12, -12, -12, -12, -12, -14, -13, -13, -13, -11, -11, -11, -11, -11, -11, -11, -9, -7, -5, -3, -1, -1, -1, -1, -1, 1, 1, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 22, 19, 18, 20, 22, 24, 23, 22, 24, 26, 28, 27, 24, 23, 25, 28, 28, 28, 27, 26, 26, 23, 20, 17, 14, 14, 14, 11, 11, 11, 11, 13, 15, 16, 16, 16, 15, 15, 14, 14, 12, 10, 9, 11, 13, 15, 17, 17, 14, 13, 13, 12, 12, 10, 9, 11, 13, 15, 17, 19, 19, 16, 13, 10, 7, 4, 1, 1, 2, 2, 4, 7, 10, 13, 13, 13, 12, 12, 12, 9, 6, 6, 6, 3, 0, 0, 0, 0, 2, 3, 3, 3, 3, 5, 7, 7, 7, 9, 11, 13, 15, 18, 18, 15, 12, 9, 8, 10, 13, 13, 13, 15, 18, 21, 24, 27, 27, 23, 19, 15, 11, 10, 9, 9, 12, 16, 19, 22, 23, 19, 14, 13, 16, 16, 15, 15, 14, 17, 20, 20, 19, 19, 18, 17, 14, 13, 15, 15, 12, 11, 13, 16, 19, 19, 18, 20, 20, 19, 18, 18, 17, 17, 16, 16, 16, 15, 17, 17, 16, 16, 13, 12, 12, 11, 11, 9, 9, 9, 9, 11, 11, 9, 7, 5, 3, 1, 1, 1, -1, -1, 1, 3, 5, 7, 9, 11, 12, 9, 6, 6, 6, 6, 8, 8, 7, 9, 11, 13, 13, 12, 14, 16, 18, 20, 20, 20, 22, 24, 26, 25, 25, 27, 29, 28, 27, 26, 23, 22, 22, 21, 21, 20, 22, 24, 26, 28, 27, 24, 21, 21, 21, 18, 17, 17, 14, 11, 11, 11, 10, 10, 7, 6, 6, 4, 3, 5, 5, 3, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 0, -1, -1, 0, 0, 1, 2, 3, 4, 3, 1, -1, -3, -3, -3, -3, -2, -3, -4, -6, -8, -10, -10, -10, -12, -12, -12, -12, -10, -10, -11, -12, -14, -16, -18, -20, -22, -24, -26, -28, -27, -27, -26, -26, -25, -25, -27, -26, -24, -22, -22, -22, -22, -24, -24, -24, -24, -23, -23, -22, -22, -21, -20, -19, -17, -15, -13, -11, -9, -7, -7, -9, -9, -9, -11, -13, -15, -17, -16, -14, -13, -15, -14, -14, -14, -12, -10, -8, -7, -9, -11, -13, -15, -14, -14, -13, -13, -15, -17, -19, -18, -18, -17, -17, -16, -16, -18, -20, -22, -21, -21, -21, -21, -21, -20, -21, -22, -24, -24, -22, -22, -24, -26, -25, -23, -21, -19, -18, -17, -17, -19, -21, -23, -25, -27, -29, -31, -30, -29, -28, -26, -25, -24, -24, -23, -23, -25, -24, -24, -24, -22, -20, -18, -18, -20, -20, -20, -20, -18, -16, -16, -16, -14, -12, -10, -8, -6, -4, -4, -4, -4, -4, -2, 0, 2, 4, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 3, 3, 3, 3, 4, 5, 6, 5, 3, 1, 1, 1, 1, 1, 1, 1, 0, -1, -1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, -1, -2, -3, -4, -4, -2, 0, 0, 0, 1, 3, 5, 7, 7, 5, 3, 3, 3, 3, 3 }
local decoded = wav.readWAV(input)
expect(decoded.codec):describe("The codec matches"):eq("dfpwm")
expect(decoded.length):describe("The length matches"):eq(1024)
expect(decoded.channels):describe("The channels match"):eq(1)
expect(decoded.sampleRate):describe("The sample rate matches"):eq(48000)
expect(decoded.metadata.artist):describe("The artist matches"):eq("test")
expect(decoded.metadata.date):describe("The date matches"):eq(2024)
expect(decoded.metadata.album):describe("The album matches"):eq("CC: Tweaked")
expect(decoded.metadata.encoder):describe("The encoder matches"):eq("Lavf59.18.100")
local chunk = decoded.read(1024)
expect(chunk):describe("The chunk is read successfully"):type("table")
expect(#chunk):describe("The chunk length is correct"):eq(1024)
expect(decoded.read(1)):describe("The reader is finished"):eq(nil)
for i = 1, #chunk do expect(chunk[i]):describe("Item at #" .. i):eq(output[i]) end
end)
end)
end)