ldd-CC/nbs5.lua

304 lines
6.3 KiB
Lua

--[[
NoteBlock Song API
by MysticT
Twekaed by Soni
Modified for NBS v5 by LDDestroier (WIP)
--]]
local nbs5 = {}
-- yield to avoid error
local function yield()
os.queueEvent("yield")
os.pullEvent("yield")
end
-- read short integer (16-bit) from file
local function readShort(file)
return file.read() + file.read() * 256
end
_G.readShort = readShort
-- read integer (32-bit) from file
local function readInt(file)
return file.read() + file.read() * 256 + file.read() * 65536 + file.read() * 16777216
end
-- read string from file
local function readString(file)
local s, c = ""
local len = readInt(file)
for i = 1, len do
c = file.read()
if not c then
break
end
s = s .. string.char(c)
end
return s
end
local function makeBitNum(num, bytes)
local output = ""
for i = 0, bytes - 1 do
output = output .. string.char(bit32.band(bit32.rshift(num, i * 8), 0xFF))
end
return output
end
-- write short integer (16-bit) to file
local function writeShort(file, int)
file.write(makeBitNum(int, 2))
end
-- write integer (32-bit) to file
local function writeInt(file, int)
file.write(makeBitNum(int, 4))
end
-- write string to file
local function writeString(file, str)
writeInt(file, #str)
file.write(str)
end
-- read nbs file header
local function readNBSHeader(file)
local header = {}
header.lenght = readShort(file)
if header.lenght > 0 then
-- old NBS
header.nbs_version = 0
header.height = readShort(file)
header.name = readString(file)
if header.name == "" then
header.name = "Untitled"
end
header.author = readString(file)
if header.author == "" then
header.author = "Unknown"
end
header.original_author = readString(file)
if header.original_author == "" then
header.original_author = "Unknown"
end
header.description = readString(file)
header.tempo = readShort(file) / 100
header.autosave = file.read()
header.autosave_duration = file.read()
header.time_signature = file.read()
header.minutes_spent = readInt(file)
header.left_clicks = readInt(file)
header.right_clicks = readInt(file)
header.blocks_added = readInt(file)
header.blocks_removed = readInt(file)
header.filename = readString(file)
else
-- NBS v5
header.nbs_version = file.read()
header.vanilla_instruments = file.read()
header.lenght = readShort(file)
header.height = readShort(file)
header.name = readString(file)
if header.name == "" then
header.name = "Untitled"
end
header.author = readString(file)
if header.author == "" then
header.author = "Unknown"
end
header.original_author = readString(file)
if header.original_author == "" then
header.original_author = "Unknown"
end
header.description = readString(file)
header.tempo = readShort(file) / 100
header.autosave = file.read()
header.autosave_duration = file.read()
header.time_signature = file.read()
header.minutes_spent = readInt(file)
header.left_clicks = readInt(file)
header.right_clicks = readInt(file)
header.blocks_added = readInt(file)
header.blocks_removed = readInt(file)
header.filename = readString(file)
header.looping = file.read()
header.max_looping = file.read()
header.loop_start = readShort(file)
end
return header
end
-- jump to the next tick in the file
local function nextTick(file, tSong)
local jump = readShort(file)
for i = 1, jump - 1 do
tSong[#tSong + 1] = {}
end
return jump > 0
end
-- read the notes in a tick
-- TODO cleanup, move checks to player:nbs.lua
local function readTick(file)
local t = {}
local n = 0
local jump = readShort(file)
while jump > 0 do
n = n + jump
write(".")
local instrument = file.read() + 1
if instrument > 16 then
return nil, "(v5) Can't convert custom instruments"
end
local note = file.read() - 33
local velocity = file.read()
local pan = file.read()
local pitch_mod = readShort(file)
if note < 0 or note > 24 then
return nil, "(v5) Notes must be in Minecraft's 2 octaves (" .. note .. ")"
end
if not t[instrument] then
t[instrument] = {}
end
t[instrument][n] = note
jump = readShort(file)
end
return t
end
-- API functions
-- save a converted song to a file
function nbs5.saveSong(tSong, sPath)
local file = fs.open(sPath, "w")
if file then
file.write(textutils.serialize(tSong))
file.close()
return true
end
return false, "Error opening file "..sPath
end
-- save a song as an NBS v5 file
function nbs5.saveSongNBS(tSong, sPath)
end
-- load and convert an .nbs file and save it
function nbs5.load(sPath, bVerbose)
local file = fs.open(sPath, "rb")
if file then
if bVerbose then
print("Reading header...")
end
local tSong = {}
local header = readNBSHeader(file)
tSong.header = header
tSong.name = header.name
tSong.author = header.author
tSong.original_author = header.original_author
tSong.lenght = header.lenght / header.tempo
tSong.delay = 1 / header.tempo
if bVerbose then
print("Reading ticks...")
end
if header.nbs_version == 0 then
while nextTick(file, tSong) do
local tick, err = readTick(file, tSong)
if tick then
table.insert(tSong, tick)
else
file.close()
return nil, err
end
yield()
end
pcall(function()
local layers = {}
for i=1, header.height do
table.insert(layers, {
name=readString(file),
volume=file.read() + 0
})
end
tSong.layers = layers
local insts = {}
for i=1, file.read() + 0 do
insts[#insts + 1] = {
name = readString(file),
file = readString(file),
pitch = file.read() + 0,
key = (file.read() + 0) ~= 0
}
end
tSong.instruments = insts
end)
else
while nextTick(file, tSong) do
local tick, err = readTick(file, tSong)
if tick then
table.insert(tSong, tick)
else
file.close()
return nil, err
end
yield()
end
pcall(function()
local layers = {}
for i=1, header.height do
table.insert(layers, {
name = readString(file),
lock = file.read(),
volume = file.read(),
pan = file.read()
})
end
tSong.layers = layers
local insts = {}
for i=1, file.read() + 0 do
insts[#insts + 1] = {
name = readString(file),
file = readString(file),
pitch = file.read() + 0,
key = (file.read() + 0) ~= 0
}
end
tSong.instruments = insts
end)
end
file.close()
return tSong
end
return nil, "Error opening file "..sPath
end
return nbs5