mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-07-05 03:22:53 +00:00
Add cc.audio.wav module
This commit is contained in:
parent
764e1aa332
commit
b04914065a
@ -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`].
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
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"
|
||||
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
|
||||
|
@ -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}
|
@ -22,14 +22,6 @@ local function get_speakers(name)
|
||||
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)
|
||||
printError(("speaker cannot play %s files."):format(format))
|
||||
local pp = require "cc.pretty"
|
||||
@ -63,50 +55,21 @@ elseif cmd == "play" then
|
||||
end
|
||||
|
||||
local start = handle.read(4)
|
||||
local pcm = false
|
||||
local wav = false
|
||||
local size = 16 * 1024 - 4
|
||||
if start == "RIFF" then
|
||||
handle.read(4)
|
||||
if handle.read(8) ~= "WAVEfmt " then
|
||||
handle.close()
|
||||
error("Could not play audio: Unsupported WAV file", 0)
|
||||
local data = start .. handle.readAll()
|
||||
handle.close()
|
||||
local ok
|
||||
ok, handle = pcall(require("cc.audio.wav").readWAV, data)
|
||||
if not ok then
|
||||
printError("Could not play audio:")
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
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)
|
||||
wav = true
|
||||
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.
|
||||
elseif start == "OggS" then return report_invalid_format("Ogg")
|
||||
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")
|
||||
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
|
||||
local chunk = handle.read(size)
|
||||
if not chunk then break end
|
||||
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user