mirror of
https://github.com/LDDestroier/CC/
synced 2024-10-31 23:26:19 +00:00
304 lines
6.3 KiB
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
|