ComputerCraft Archive

nbs5

computer utility LDDestroier github

Description

A collection of all my ComputerCraft programs and the APIs they use. This is mostly just to get them the fuck off of pastebin, and also to ensure that API owners don't change things to break my precious programs...!

Installation

Copy one of these commands into your ComputerCraft terminal:

wget:wget https://raw.githubusercontent.com/LDDestroier/CC/master/nbs5.lua nbs5
Archive:wget https://cc.shobie.xyz/cc/get/gh-LDDestroier-CC-nbs5 nbs5
Quick Install: wget https://cc.shobie.xyz/cc/get/gh-LDDestroier-CC-nbs5 nbs5

Usage

Run: nbs5

Tags

none

Source

View Original Source

Code Preview

--[[
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