A simple python library to read and write .nbs files from Open Note Block Studio.
pynbs
makes it possible to easily iterate over Note Block Studio songs.
import pynbs
for tick, chord in pynbs.read('demo_song.nbs'):
print(tick, [note.key for note in chord])
You can also use pynbs
to generate new songs programmatically.
import pynbs
new_file = pynbs.new_file(song_name='Hello world')
new_file.notes.extend([
pynbs.Note(tick=i, layer=0, instrument=0, key=i + 35) for i in range(10)
])
new_file.save('new_file.nbs')
The package can be installed with pip
.
$ pip install pynbs
The latest release follows the latest version of the NBS file format specification (version 5). However, it also allows you to load and save files in any of the older versions.
You can use the read()
function to read and parse a specific NBS file.
demo_song = pynbs.read('demo_song.nbs')
The read()
function returns a pynbs
file object. These objects have several
attributes that mirror the binary structure of NBS files.
The first attribute is header
, the file header. It contains information about
the file.
header = demo_song.header
Attribute | Type | Details |
---|---|---|
header.version |
int |
The NBS version this file was saved on. |
header.default_instruments |
int |
The amount of instruments from vanilla Minecraft in the song. |
header.song_length |
int |
The length of the song, measured in ticks. |
header.song_layers |
int |
The ID of the last layer with at least one note block in it. |
header.song_name |
str |
The name of the song. |
header.song_author |
str |
The author of the song. |
header.original_author |
str |
The original song author of the song. |
header.description |
str |
The description of the song. |
header.tempo |
float |
The tempo of the song. |
header.auto_save |
bool |
Whether auto-saving has been enabled. |
header.auto_save_duration |
int |
The amount of minutes between each auto-save. |
header.time_signature |
int |
The time signature of the song. |
header.minutes_spent |
int |
The amount of minutes spent on the project. |
header.left_clicks |
int |
The amount of times the user has left-clicked. |
header.right_clicks |
int |
The amount of times the user has right-clicked. |
header.blocks_added |
int |
The amount of times the user has added a block. |
header.blocks_removed |
int |
The amount of times the user has removed a block. |
header.song_origin |
str |
The file name of the original MIDI or schematic. |
header.loop |
bool |
Whether the song should loop back to the start after ending. |
header.max_loop_count |
int |
The amount of times to loop. 0 = infinite. |
header.loop_start |
int |
The tick the song will loop back to at the end of playback. |
For more information about all these fields, check out the official specification.
The notes
attribute holds a list of all the notes of the song in order.
first_note = demo_song.notes[0]
Attribute | Type | Details |
---|---|---|
note.tick |
int |
The tick at which the note plays. |
note.layer |
int |
The ID of the layer in which the note is placed. |
note.instrument |
int |
The ID of the instrument. |
note.key |
int |
The key of the note. (between 0 and 87) |
note.velocity |
int |
The velocity of the note. (between 0 and 100) |
note.panning |
int |
The stereo panning of the note. (between -100 and 100) |
note.pitch |
int |
The detune of the note, in cents. (between -1200 and 1200) |
The layers
attribute holds a list of all the layers of the song in order.
first_layer = demo_song.layers[0]
Attribute | Type | Details |
---|---|---|
layer.id |
int |
The ID of the layer. |
layer.name |
str |
The name of the layer. |
layer.lock |
bool |
Whether the layer is locked. |
layer.volume |
int |
The volume of the layer. |
layer.panning |
int |
The stereo panning of the layer. |
The instruments
attribute holds a list of all the custom instruments of the
song in order.
first_custom_instrument = demo_song.instruments[0]
Attribute | Type | Details |
---|---|---|
instrument.id |
int |
The ID of the instrument. |
instrument.name |
str |
The name of the instrument. |
instrument.file |
str |
The name of the sound file of the instrument. |
instrument.pitch |
int |
The pitch of the instrument. (between 0 and 87) |
instrument.press_key |
bool |
Whether the piano should automatically press keys with the instrument when the marker passes them. |
Iterating over a pynbs
file object yields consecutively all the chords of the song with
the associated tick.
for tick, chord in demo_song:
...
chord
is a list of all the notes that play during the tick tick
.
You can create new files using the new_file()
function. The function lets
you specify header attributes with keyword arguments.
new_file = pynbs.new_file(song_name='Hello world')
The function returns a new pynbs
file object that you can now edit
programmatically.
You can use the save()
method to encode and write the file to a specified
location.
new_file.save('new_file.nbs')
By default, the file will be saved in the latest NBS version available.
To save the file in an older version, you can use the version
parameter:
# This will save the song in the classic format.
new_file.save('new_file.nbs', version=0)
(Keep in mind some of the song properties may be lost when saving in older versions.)
While pynbs
is up-to-date with the latest version of the Open Note Block Studio
specification, all previous versions — including the original file format — are still
supported by the read()
function, making it possible to bulk upgrade songs to the
most recent version:
import glob
import pynbs
for old_file in glob.glob('*.nbs'):
pynbs.read(old_file).save(old_file)
Contributions are welcome. Make sure to first open an issue discussing the problem or the new feature before creating a pull request. The project uses poetry
.
$ poetry install
You can run the tests with poetry run pytest
.
$ poetry run pytest
The code follows the black
code style. Import statements are sorted with isort
.
$ poetry run isort pynbs tests
$ poetry run black pynbs tests
$ poetry run black --check pynbs tests
License - MIT