-- Copyright (C) 2018-2021 by KittenOS NEO contributors -- -- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. -- -- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF -- THIS SOFTWARE. -- knbs.lua : Partial .nbs (Note Block Studio) R/W library -- Does not support custom instruments! -- Authors: 20kdc local function dsu16(str) return str:byte(1) + (str:byte(2) * 256), str:sub(3) end local function dsu32(str) local a, str = dsu16(str) local b, str = dsu16(str) return a + (b * 0x10000), str end local function dsstr(str) local a, str = dsu32(str) return str:sub(1, a), str:sub(a + 1) end local function su16(i, wr) wr(string.char(i % 0x100, math.floor(i / 0x100))) end local function su32(i, wr) su16(i % 0x10000, wr) su16(math.floor(i / 0x10000), wr) end local function sstr(str, wr) su32(#str, wr) wr(str) end return { new = function () return { length = 1, height = 1, name = "New Song", transcriptor = "Mr.Anderson", songwriter = "Morpheus", description = "A blank song.", tempo = 200, autosave = 0, autosaveMin = 60, timeSignature = 4, usageMin = 0, usageLeft = 0, usageRight = 0, usageAdd = 0, usageRm = 0, importName = "", ci = "", ticks = { [0] = { [0] = {0, 33} } }, layers = { [0] = {"L0", 100} } } end, deserialize = function (str) local nbs = {} nbs.length, str = dsu16(str) nbs.length = nbs.length + 1 -- hmph! nbs.height, str = dsu16(str) nbs.name, str = dsstr(str) nbs.transcriptor, str = dsstr(str) nbs.songwriter, str = dsstr(str) nbs.description, str = dsstr(str) nbs.tempo, str = dsu16(str) nbs.autosave, str = str:byte(), str:sub(2) nbs.autosaveMin, str = str:byte(), str:sub(2) nbs.timeSignature, str = str:byte(), str:sub(2) nbs.usageMin, str = dsu32(str) nbs.usageLeft, str = dsu32(str) nbs.usageRight, str = dsu32(str) nbs.usageAdd, str = dsu32(str) nbs.usageRm, str = dsu32(str) nbs.importName, str = dsstr(str) -- ticks[tick][layer] = key nbs.ticks = {} local tick = -1 while true do local ntJ ntJ, str = dsu16(str) if ntJ == 0 then break end tick = tick + ntJ local tickData = {} nbs.ticks[tick] = tickData local layer = -1 while true do local lJ lJ, str = dsu16(str) if lJ == 0 then break end layer = layer + lJ local ins = str:byte(1) local key = str:byte(2) str = str:sub(3) local layerData = {ins, key} if layer < nbs.height then tickData[layer] = layerData -- else: drop the invalid note end end end -- nbs.layers[layer] = {name, volume} nbs.layers = {} if str ~= "" then for i = 0, nbs.height - 1 do nbs.layers[i] = {} nbs.layers[i][1], str = dsstr(str) nbs.layers[i][2], str = str:byte(), str:sub(2) end else for i = 0, nbs.height - 1 do nbs.layers[i] = {"L" .. i, 100} end end nbs.ci = str return nbs end, resizeLayers = function (nbs, layers) -- make all layers after target layer go away for i = layers, nbs.height - 1 do nbs.layers[i] = nil end -- add layers up to target for i = nbs.height, layers - 1 do nbs.layers[i] = {"L" .. i, 100} end -- clean up song for k, v in pairs(nbs.ticks) do for lk, lv in pairs(v) do if lk >= layers then v[lk] = nil end end end nbs.height = layers end, -- Corrects length, height (should not be necessary in correct applications!), and clears out unused tick columns. -- Returns the actual effective height, which can be passed to resizeLayers to remove dead weight. correctSongLH = function (nbs) nbs.length = 1 nbs.height = 0 for k, v in pairs(nbs.layers) do nbs.height = math.max(nbs.height, k + 1) end local eH = 0 for k, v in pairs(nbs.ticks) do local ok = false for lk, lv in pairs(v) do ok = true eH = math.max(eH, lk + 1) end if not ok then nbs.ticks[k] = nil else nbs.length = math.max(nbs.length, k + 1) end end return eH end, serialize = function (nbs, wr) su16(math.max(0, nbs.length - 1), wr) su16(nbs.height, wr) sstr(nbs.name, wr) sstr(nbs.transcriptor, wr) sstr(nbs.songwriter, wr) sstr(nbs.description, wr) su16(nbs.tempo, wr) wr(string.char(nbs.autosave, nbs.autosaveMin, nbs.timeSignature)) su32(nbs.usageMin, wr) su32(nbs.usageLeft, wr) su32(nbs.usageRight, wr) su32(nbs.usageAdd, wr) su32(nbs.usageRm, wr) sstr(nbs.importName, wr) local ptr = -1 for i = 0, nbs.length - 1 do if nbs.ticks[i] then su16(i - ptr, wr) ptr = i local lp = -1 for j = 0, nbs.height - 1 do local id = nbs.ticks[i][j] if id then su16(j - lp, wr) lp = j wr(string.char(id[1], id[2])) end end su16(0, wr) end end su16(0, wr) for i = 0, nbs.height - 1 do sstr(nbs.layers[i][1], wr) wr(string.char(nbs.layers[i][2])) end wr(nbs.ci) end }