This apis and programs allows you to play music from your computer using noteblocks. Using RP bundled cables and just 3 computers, you can play all the notes and instruments available in noteblocks.
You need to setup some players (2 should be enough) that send the redstone pulses to the noteblocks, and a controller that sends the data to them.
NoteBlock API
The api has the functions needed to send the data to the players.
It splits the notes and sends them to the corresponding computer.
Functions:
Spoiler
nb.playNote(instrument, note)
-- plays a note in an instrument.
-- instrument: number (1-5)
-- note: number (0-24)
nb.playChord(instrument, notes)
-- plays some notes in an instrument.
-- instrument: number (1-5)
-- notes: table/array of notes
nb.play(t)
-- play some notes on some instruments
-- t: table (see the api file for the format)
nb.playSong(t)
-- play a song
-- t: table (see the api file for the format)
Download: pastebin (code: U0r5Hj38)
Code:
Spoiler
-- NoteBlock Controller API
-- by MysticT
-- Config
-- id of the players
local tIds = {}
-- number of notes per player
local nNotes = 0
-- Instruments
piano = 1
bass = 2
bass_drum = 3
snare_drum = 4
click = 5
local function send(id, t)
rednet.send(id, "<PLAY> "..textutils.serialize(t))
end
--[[
i: instrument (1-5)
n: note (0-24)
--]]
function playNote(i, n)
local t = {}
t[i] = { n }
local id = tIds[math.floor(n / nNotes) + 1]
send(id, t)
end
--[[
i: instrument (1-5)
notes: { n1, n2, n3, ..., nN}
n: note (0-24)
--]]
function playChord(i, notes)
local ts = {}
for _,n in ipairs(notes) do
local id = tIds[math.floor(n / nNotes) + 1]
if not ts[id] then
ts[id] = {}
end
if not ts[id][i] then
ts[id][i] = {}
end
table.insert(ts[id][i], n)
end
for id, v in pairs(ts) do
send(id, v)
end
end
--[[
Table format:
t = {
[i1] = { n1, n2, n3, ..., nN },
[i2] = { n1, n2, n3, ..., nN },
...
[iN] = { n1, n2, n3, ..., nN }
}
i: instrument (1-5)
n: note (0-24)
--]]
function play(t)
local ts = {}
for i, notes in pairs(t) do
for _,n in ipairs(notes) do
local id = tIds[math.floor(n / nNotes) + 1]
if not ts[id] then
ts[id] = {}
end
if not ts[id][i] then
ts[id][i] = {}
end
table.insert(ts[id][i], n)
end
end
for id, v in pairs(ts) do
send(id, v)
end
end
--[[
Table format:
t = {
[1] = n,
[2] = n,
[3] = n,
...
[N] = n,
["delay"] = d,
}
d: delay (in seconds), default 0.5
n: instrument-notes table (play() format, see above)
--]]
function playSong(t)
local nDelay = t.delay or 0.5
for _,n in ipairs(t) do
play(n)
sleep(nDelay)
end
end
NoteBlock Player
This is what actually plays the noteblocks, by sending redstone pulses to the corresponding noteblock.
It receives messages over rednet from the controller, then decodes the message and plays the corresponding notes and instruments.
Download: pastebin (code: PdG4imtY)
Code:
Spoiler
-- NoteBlock Player
-- by MysticT
-- Config
-- id of the controller computer
local nControllerID = 0
-- sides corresponding to each instrument
-- order: piano, bass, bass drum, snare drum, click
local tSides = {}
-- colors corresponding to each note
local tColors = {}
-- pulse sleep time
local nPulse = 0.1
-- Functions
-- clear the screen
local function clear()
term.clear()
term.setCursorPos(1, 1)
end
-- detect modem and connect
local function connect()
for _,s in ipairs(rs.getSides()) do
if peripheral.isPresent(s) and peripheral.getType(s) == "modem" then
rednet.open(s)
return true
end
end
return false
end
-- send a pulse
--[[
table format:
t = {
[side1] = colors,
[side2] = colors,
...
[sideN] = colors,
}
--]]
local function pulse(t)
for side, c in pairs(t) do
rs.setBundledOutput(side, c)
end
sleep(nPulse)
for side,_ in pairs(t) do
rs.setBundledOutput(side, 0)
end
sleep(nPulse)
end
-- play the given notes on the given instruments
--[[
table format:
t = {
[i1] = { n1, n2, ..., nN },
[i2] = { n1, n2, ..., nN },
...
[iN] = { n1, n2, ..., nN }
}
i: instrument number (range defined by the sides table)
n: notes (range defined by the colors table)
--]]
local function play(t)
print("Playing...")
local tPulse = {}
for i, notes in pairs(t) do
print("Instrument: ", i)
local side = tSides[i]
if side then
if type(notes) == "table" then
print("Notes: ", unpack(notes))
local c = 0
for _,n in ipairs(notes) do
local k = tColors[n]
if k then
c = colors.combine(c, k)
else
print("Unknown note: ", n)
end
end
tPulse[side] = c
else
print("Wrong format. table expected, got ", type(notes))
end
else
print("Unknown Instrument")
end
end
pulse(tPulse)
end
-- Start Program
-- try to connect to rednet
if not connect() then
print("No modem found")
return
end
clear()
print("Waiting for messages...")
-- start receiving
while true do
local id, msg = rednet.receive()
if id == nControllerID then
local t = string.match(msg, "<PLAY> (.+)")
if t then
local notes = textutils.unserialize(t)
if notes then
play(notes)
end
end
end
end
Setup
Spoiler
Ok, this is the hardest part. You need to setup the noteblocks, players and the controller.Noteblock setup:
This is just placing the noteblocks in the different types of terrain to have all the instruments and then setting each of them to the correct note (by right-clicking them). It can be time consuming, but it should be easy to do.
Players setup:
Now you need to put the computers and wire them to the noteblocks. You will need to connect the computer to each noteblock with a different cable color, that's why you need at least 2 computers (16 colors, 25 notes, 5 instruments). Each computer can have 5 bundled cables attached (the other side is used by the modem), and each cable can have 16 different color cables. So the best way is to use a bundled cable for each instrument, and use one computer for the first 16 notes and another one for the last 9.
Once you'r done with that, copy the NoteBlock Player program to the computers startup. Now you have to set the config for each computer, it's at the top of the player program (wich should be in the startup file now):
Controller ID:
local nControllerID = 0
If you already know wich will be the controller computer, write it's id here.
Cable sides:
local tSides = {}
Add the sides you use for the bundled cables, ordered by instrument: piano, bass, bass drum, snare drum, click.
Colors:
local tColors = {}
Set the colors corresponding to each note. It has to be like: [note number] = color
Pulse time:
local nPulse = 0.1
This is the time used for the pulses (turn on, sleep, turn off, sleep). 0.1 is a good value, you can change it, but it might cause troubles like skiping notes if it's too low or too high.
Controller setup:
Just place a computer with a modem, and copy the NoteBlock API to the root of the computer (save it as "nb", or it won't load it). You need to configure the api to work for your setup:
Id of the players:
local tIds = {}
This is the list of player's ids, ordered by the notes they play (the first one in the list plays the first n notes, the second the next n notes, etc.).
Notes per player:
local nNotes = 0
This is the amount of notes for each player. The last player can have less than this value, but the rest should have that value, or it won't work.
Here's an example of a setup (this is the setup I used to test):
Spoiler
Pictures:Spoiler
-- id of the controller computer
local nControllerID = 19
-- sides corresponding to each instrument
-- order: piano, bass, bass drum, snare drum, click
local tSides = { "top", "left", "back", "right", "bottom" }
-- colors corresponding to each note
local tColors = {}
-- from 0 to 15, the first 16 notes, the first 16 colors
for i = 0, 15 do
tColors[i] = 2 ^ i
end
-- pulse sleep time
local nPulse = 0.1
Player 2 (left computer) Configuration (id 17):
-- id of the controller computer
local nControllerID = 19
-- sides corresponding to each instrument
-- order: piano, bass, bass drum, snare drum, click
local tSides = { "bottom", "right", "back", "left", "top" }
-- colors corresponding to each note
local tColors = {}
-- from 16 to 24, the last 9 notes, the first 9 colors
for i = 0, 8 do
tColors[i + 16] = 2 ^ i
end
-- pulse sleep time
local nPulse = 0.1
API Configuration (id 19):
-- id of the players
local tIds = { 18, 17 }
-- number of notes per player
local nNotes = 16
NBPlayer
With this program you can play songs stored in files. The format of the files is a serialized table, wich is sent to the nb api to decode and send to the players.
Usage:
NBPlayer <song file>
<song file>: the file to play. It must be a path to a file with the player format.
Download: pastebin (code: WZWVS5Fz)
Code:
Spoiler
-- NBPlayer
-- by MysticT
-- load the nb api
if not nb then
os.loadAPI("nb")
if not nb then
print("Couldn't load nb api. Check if you installed it correctly.")
return
end
end
-- detect a modem and connect
local function connect()
for _,s in ipairs(rs.getSides()) do
if peripheral.isPresent(s) and peripheral.getType(s) == "modem" then
rednet.open(s)
return true
end
end
return false
end
-- try to connect to rednet
if not connect() then
print("No modem found")
return
end
-- play a song file
local function playFile(sPath)
local file = fs.open(sPath, "r")
if file then
local s = file.readAll()
file.close()
local tSong = textutils.unserialize(s)
if tSong and type(tSong) == "table" then
local title = tSong.name
if not title or title == "" then
title = "Untitled"
end
local author = tSong.author
if not author or author == "" then
author = "Unknown"
end
print("Playing: ", title, " - by ", author)
if tSong.original_author and tSong.original_author ~= "" then
print("Original Author: ", tSong.original_author)
end
if tSong.lenght then
print("Lenght: ", tSong.lenght)
end
nb.playSong(tSong)
return true
end
return false, "Unknown file format."
end
return false, "Error opening file "..sPath
end
-- Start Program
-- get arguments
local tArgs = { ... }
if #tArgs ~= 1 then
print("Usage: nbplay <file>")
return
end
-- load and play the file
local ok, err = playFile(tArgs[1])
if not ok then
return false, err
end
And if that's not enough, you can grab your favourite song in nbs (NoteBlock Song, from NoteBlock Studio) format and convert it or play it directly using this programs. So you can take a midi file, use NoteBlock Studio to convert it to nbs and then play it with this, no need to export to schematic files.
NBS API
This api provides functions to load nbs files and save them in the format used by the NBPlayer.
Functions:
Spoiler
nbs.load(path, verbose)
-- load an nbs file
-- path: the path to the nbs file
-- verbose: boolean, true to print information during load process
-- return value: the loaded song in a table format.
nbs.saveSong(song, path)
-- save a converted song to a file
-- song: a song table loaded using the load function
-- path: the path to save the song
Installation: save it to the root of the computer (or to /rom/apis) as "nbs".
Download: pastebin (code: 0SN5Tftz)
Code:
Spoiler
-- 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
-- empty table, for empty ticks
local tEmpty = {}
-- 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, tEmpty)
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) do
local tick, err = readTick(file, tSong)
if tick then
table.insert(tSong, tick)
else
file.close()
return nil, err
end
yield()
end
file.close()
return tSong
end
return nil, "Error opening file "..sPath
end
NBS Player
Loads an nbs file and then it plays it. It's recommended to use the NBSConverter and NBPlayer, since the loading can take a while for long songs.
Usage:
NBSPlayer <nbs file>
<nbs file>: the nbs file to play
Example:
NBSPlayer MySong.nbs
Download: pastebin (code: aBWwUsWe)
Code:
Spoiler
-- NoteBlock Song Player
-- by MysticT
-- load the nb and nbs apis
if not nb then
os.loadAPI("nb")
if not nb then
print("Couldn't load nb api. Check if you installed it correctly.")
return
end
end
if not nbs then
os.loadAPI("nbs")
if not nbs then
print("Error loading nbs api. Check you installed it correctly.")
return
end
end
-- detect a modem and connect
local function connect()
for _,s in ipairs(rs.getSides()) do
if peripheral.isPresent(s) and peripheral.getType(s) == "modem" then
rednet.open(s)
return true
end
end
return false
end
-- try to connect to rednet
if not connect() then
print("No modem found")
return
end
-- load and play an nbs file
local function playFile(sPath)
local tSong, err = nbs.load(sPath)
if tSong then
print("Playing: ", tSong.name, " - by ", tSong.author)
if tSong.original_author ~= "" then
print("Original Author: ", tSong.original_author)
end
print("Lenght: ", tSong.lenght)
nb.playSong(tSong)
return true
end
return false, err
end
-- Start Program
-- get arguments
local tArgs = { ... }
if #tArgs ~= 1 then
print("Usage: nbsplay <file>")
return
end
-- play the file
local ok, err = playFile(tArgs[1])
if not ok then
return false, err
end
NBSConverter
Converts a nbs file to the format used by NBPlayer. This format loads faster than nbs, so it's recommended to use it, specially for long songs.
Usage:
NBSConverter <nbs file> <output name>
<nbs file>: the path to the nbs file to convert.
<output name>: the name to use when saving the converted song.
Example:
NBSConverter MySong.nbs MySong
It will convert the file MySong.nbs and save it as MySong
Download: pastebin (code: 9cCAJtgm)
Code:
Spoiler
-- NBS File Converter
-- by MysticT
-- load nbs api
if not nbs then
os.loadAPI("nbs")
if not nbs then
print("Error loading nbs api. Check you installed it correctly.")
return
end
end
-- Start program
-- get arguments
local tArgs = { ... }
if #tArgs ~= 2 then
print("Usage: nbsconvert <file path> <save path>")
return
end
-- load the song from the nbs file
print("Converting...")
local tSong, err = nbs.load(tArgs[1], true)
if tSong then
print("File loaded")
print("Title: ", tSong.name)
print("Author: ", tSong.author)
print("Original Author: ", tSong.original_author)
print("Lenght: ", tSong.lenght)
else
print("Error: ", err)
return
end
-- save the converted song to a file
local ok, err = nbs.save(tSong, tArgs[2])
if ok then
print("nFile saved to", tArgs[2])
else
print("Error: ", err)
end
Ok, hope you enjoy it :P/>/>
Any feedback is welcome.