2018-06-09 04:43:19 +10:00
|
|
|
-- This is released into the public domain.
|
|
|
|
-- No warranty is provided, implied or otherwise.
|
|
|
|
|
|
|
|
-- 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
|
2018-06-10 07:55:35 +10:00
|
|
|
for i = nbs.height, layers - 1 do
|
2018-06-09 04:43:19 +10:00
|
|
|
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
|
|
|
|
}
|