-- NoteBlock Song API
-- by MysticT

-- yield to avoid error
local function yield()
	os.queueEvent("fake")
	os.pullEvent("fake")
end

-- read short integer (16-bit) from file
local function readShort(file)
	return file.read() + file.read() * 256
end

-- 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 = ""
	local len = readInt(file)
	for i = 1, len do
		local c = file.read()
		if not c then
			break
		end
		s = s..string.char(c)
	end
	return s
end

-- read nbs file header
local function readNBSHeader(file)
	local header = {}
	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)
	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
		table.insert(tSong, {})
	end
	return jump > 0
end

-- read the notes in a tick
local function readTick(file)
	local t = {}
	local jump = readShort(file)
	while jump > 0 do
		local instrument = file.read() + 1
		if instrument > 5 then
			return nil, "Can't convert custom instruments"
		end
		local note = file.read() - 33
		if note < 0 or note > 24 then
			return nil, "Notes must be in Minecraft's 2 octaves"
		end
		if not t[instrument] then
			t[instrument] = {}
		end
		table.insert(t[instrument], note)
		jump = readShort(file)
	end
	return t
end

-- API functions

-- save a converted song to a file
function 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

-- load and convert an .nbs file and save it
function 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.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
		while nextTick(file, tSong) do
			local tick, err = readTick(file, tSong)
			if tick then
				table.insert(tSong, tick)
    print("a")
			else
				file.close()
				return nil, err
			end
			yield()
		end
		file.close()
		return tSong
	end
	return nil, "Error opening file "..sPath
end

load("/ba.nbs", true)

