├── .gitignore
├── example_songs
├── Peanut's Theme.nbs
├── Tetris A Theme.nbs
├── carelesswhisper.nbs
├── Never Gonna Give You Up.nbs
├── Perry The Platypus Theme (Squished).nbs
├── Megalovania - Super Smash Bros. Ultimate.nbs
├── Pirates of the Caribbean - He's a Pirate.nbs
├── Perry The Platypus Theme (Extended).nbs
└── september.nbs
├── Silent_Discs
├── pack.mcmeta
└── assets
│ └── minecraft
│ └── sounds
│ └── records
│ ├── 11.ogg
│ ├── 13.ogg
│ ├── 5.ogg
│ ├── cat.ogg
│ ├── far.ogg
│ ├── chirp.ogg
│ ├── mall.ogg
│ ├── stal.ogg
│ ├── strad.ogg
│ ├── wait.ogg
│ ├── ward.ogg
│ ├── blocks.ogg
│ ├── mellohi.ogg
│ ├── pigstep.ogg
│ └── otherside.ogg
├── constants.py
├── nbs_format_song.py
├── README.md
└── nbs_generate_schematic.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 | .idea
3 | __pycache__
4 | *.nbs
5 | !example_songs/*.nbs
6 | *.schem
7 | *.txt
8 |
--------------------------------------------------------------------------------
/example_songs/Peanut's Theme.nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/Peanut's Theme.nbs
--------------------------------------------------------------------------------
/example_songs/Tetris A Theme.nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/Tetris A Theme.nbs
--------------------------------------------------------------------------------
/example_songs/carelesswhisper.nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/carelesswhisper.nbs
--------------------------------------------------------------------------------
/Silent_Discs/pack.mcmeta:
--------------------------------------------------------------------------------
1 | {
2 | "pack": {
3 | "pack_format": 15,
4 | "description": "Silent Music Discs"
5 | }
6 | }
--------------------------------------------------------------------------------
/example_songs/Never Gonna Give You Up.nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/Never Gonna Give You Up.nbs
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/11.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/11.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/13.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/13.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/5.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/5.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/cat.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/cat.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/far.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/far.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/chirp.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/chirp.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/mall.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/mall.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/stal.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/stal.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/strad.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/strad.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/wait.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/wait.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/ward.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/ward.ogg
--------------------------------------------------------------------------------
/example_songs/Perry The Platypus Theme (Squished).nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/Perry The Platypus Theme (Squished).nbs
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/blocks.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/blocks.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/mellohi.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/mellohi.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/pigstep.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/pigstep.ogg
--------------------------------------------------------------------------------
/Silent_Discs/assets/minecraft/sounds/records/otherside.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/Silent_Discs/assets/minecraft/sounds/records/otherside.ogg
--------------------------------------------------------------------------------
/example_songs/Megalovania - Super Smash Bros. Ultimate.nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/Megalovania - Super Smash Bros. Ultimate.nbs
--------------------------------------------------------------------------------
/example_songs/Pirates of the Caribbean - He's a Pirate.nbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazziiRed/nbs-converter/HEAD/example_songs/Pirates of the Caribbean - He's a Pirate.nbs
--------------------------------------------------------------------------------
/constants.py:
--------------------------------------------------------------------------------
1 | # EDITABLE SETTING
2 | # Choose whether or not to use named discs
3 | # Using unnamed discs will reduce schematic size and
4 | # Possibly reduce lag due to less NBT data
5 | # Options: True, False (it's case-sensitive,
6 | # so make sure it's written with a capital first letter and
7 | # all other letters lowercase)
8 | NAME_DISCS = False
9 |
10 | # EDITABLE SETTING
11 | # The number of full modules (2 half-modules) of each instrument
12 | # in the physical machine. Can be set to 0 if the module does not exist.
13 | # NOTE: These settings are configured for the machine in the world download.
14 | CHORD_MAX_SIZES = {
15 | 'piano': 3,
16 | 'double bass': 1,
17 | 'bass drum': 1,
18 | 'snare drum': 1,
19 | 'click': 1,
20 | 'guitar': 3,
21 | 'flute': 3,
22 | 'bell': 1,
23 | 'chime': 1,
24 | 'xylophone': 1,
25 | 'iron xylophone': 1,
26 | 'cow bell': 1,
27 | 'digeridoo': 1,
28 | 'bit': 1,
29 | 'banjo': 1,
30 | 'pling': 3,
31 | }
32 |
33 | # EDITABLE SETTING
34 | # Whether you want to keep the lowest or highest notes in chords that are too large
35 | # 'l' means keep the low notes; 'h' means keep the high notes.
36 | KEEP_NOTES_BY_INSTRUMENT = {
37 | 'piano': 'h',
38 | 'double bass': 'l',
39 | 'bass drum': 'l',
40 | 'snare drum': 'l',
41 | 'click': 'l',
42 | 'guitar': 'l',
43 | 'flute': 'h',
44 | 'bell': 'l',
45 | 'chime': 'l',
46 | 'xylophone': 'l',
47 | 'iron xylophone': 'l',
48 | 'cow bell': 'l',
49 | 'digeridoo': 'l',
50 | 'bit': 'l',
51 | 'banjo': 'l',
52 | 'pling': 'h',
53 | }
54 |
55 | # DO NOT EDIT
56 | # The maximum length of a song, dictated by the capacity of a double chest
57 | MAX_SONG_LENGTH = 1457
58 |
59 | # DO NOT EDIT
60 | # The minimum fill of the last shulker box so that the machine doesn't break
61 | CHEST_MIN_FILL = 4
62 |
63 | # DO NOT EDIT
64 | # The instrument names in order
65 | INSTRUMENTS = [
66 | 'piano',
67 | 'double bass',
68 | 'bass drum',
69 | 'snare drum',
70 | 'click',
71 | 'guitar',
72 | 'flute',
73 | 'bell',
74 | 'chime',
75 | 'xylophone',
76 | 'iron xylophone',
77 | 'cow bell',
78 | 'digeridoo',
79 | 'bit',
80 | 'banjo',
81 | 'pling',
82 | ]
83 |
84 | # DO NOT EDIT
85 | # The range of .nbs notes that a vanilla note block can play
86 | INSTRUMENT_RANGE = (33, 57)
87 |
88 | # DO NOT EDIT
89 | # List of music discs in order
90 | NOTES_TO_DISCS_UNNAMED = [
91 | '"minecraft:music_disc_13"',
92 | '"minecraft:music_disc_cat"',
93 | '"minecraft:music_disc_blocks"',
94 | '"minecraft:music_disc_chirp"',
95 | '"minecraft:music_disc_far"',
96 | '"minecraft:music_disc_mall"',
97 | '"minecraft:music_disc_mellohi"',
98 | '"minecraft:music_disc_stal"',
99 | '"minecraft:music_disc_strad"',
100 | '"minecraft:music_disc_ward"',
101 | '"minecraft:music_disc_11"',
102 | '"minecraft:music_disc_wait"',
103 | '"minecraft:music_disc_pigstep"',
104 | ]
105 |
106 | # DO NOT EDIT
107 | # List of music discs in order, but named
108 | NOTES_TO_DISCS_NAMED = [
109 | '"minecraft:music_disc_13",tag:{display:{Name:\'{"text":"F#"}\'}}',
110 | '"minecraft:music_disc_cat",tag:{display:{Name:\'{"text":"G"}\'}}',
111 | '"minecraft:music_disc_blocks",tag:{display:{Name:\'{"text":"G#"}\'}}',
112 | '"minecraft:music_disc_chirp",tag:{display:{Name:\'{"text":"A"}\'}}',
113 | '"minecraft:music_disc_far",tag:{display:{Name:\'{"text":"A#"}\'}}',
114 | '"minecraft:music_disc_mall",tag:{display:{Name:\'{"text":"B"}\'}}',
115 | '"minecraft:music_disc_mellohi",tag:{display:{Name:\'{"text":"C"}\'}}',
116 | '"minecraft:music_disc_stal",tag:{display:{Name:\'{"text":"C#"}\'}}',
117 | '"minecraft:music_disc_strad",tag:{display:{Name:\'{"text":"D"}\'}}',
118 | '"minecraft:music_disc_ward",tag:{display:{Name:\'{"text":"D#"}\'}}',
119 | '"minecraft:music_disc_11",tag:{display:{Name:\'{"text":"E"}\'}}',
120 | '"minecraft:music_disc_wait",tag:{display:{Name:\'{"text":"F"}\'}}',
121 | '"minecraft:music_disc_pigstep",tag:{display:{Name:\'{"text":"F#"}\'}}',
122 | ]
123 |
--------------------------------------------------------------------------------
/nbs_format_song.py:
--------------------------------------------------------------------------------
1 | import pynbs
2 | import sys
3 | from constants import *
4 |
5 |
6 | def getValidInput(validInputs, prompt):
7 | while True:
8 | userInput = input(prompt)
9 | if userInput in validInputs:
10 | return userInput
11 | else:
12 | print(f'"{userInput}" is not a valid input. Please try again.')
13 |
14 |
15 | def removeCustomNotes(chord):
16 | return [note for note in chord if note.instrument <= 15]
17 |
18 |
19 | def fixIllegalNotes(chord):
20 | instrumentMin, instrumentMax = INSTRUMENT_RANGE
21 | newChord = []
22 |
23 | for note in chord:
24 | while note.key < instrumentMin:
25 | note.key += 12
26 | while note.key > instrumentMax:
27 | note.key -= 12
28 | newChord.append(note)
29 |
30 | return newChord
31 |
32 |
33 | def removeHighestHelper(chord, chordMaxSize):
34 | if len(chord) <= chordMaxSize:
35 | return chord
36 |
37 | highestNote = max(chord, key=lambda note: note.key)
38 | chord.remove(highestNote)
39 |
40 | return removeHighestHelper(chord, chordMaxSize)
41 |
42 |
43 | def removeHighestNotes(chord, chordMaxSize):
44 | lowerOctaveNotes = [note for note in chord if note.key < INSTRUMENT_RANGE[0] + 12]
45 | upperOctaveNotes = [note for note in chord if note.key >= INSTRUMENT_RANGE[0] + 12]
46 |
47 | lowerOctaveNotes = removeHighestHelper(lowerOctaveNotes, chordMaxSize)
48 | upperOctaveNotes = removeHighestHelper(upperOctaveNotes, chordMaxSize)
49 |
50 | return lowerOctaveNotes + upperOctaveNotes
51 |
52 |
53 | def removeLowestHelper(chord, chordMaxSize):
54 | if len(chord) <= chordMaxSize:
55 | return chord
56 |
57 | lowestNote = min(chord, key=lambda note: note.key)
58 | chord.remove(lowestNote)
59 |
60 | return removeLowestHelper(chord, chordMaxSize)
61 |
62 |
63 | def removeLowestNotes(chord, chord_max_size):
64 | lowerOctaveNotes = [note for note in chord if note.key < INSTRUMENT_RANGE[0] + 12]
65 | upperOctaveNotes = [note for note in chord if note.key >= INSTRUMENT_RANGE[0] + 12]
66 |
67 | lowerOctaveNotes = removeLowestHelper(lowerOctaveNotes, chord_max_size)
68 | upperOctaveNotes = removeLowestHelper(upperOctaveNotes, chord_max_size)
69 |
70 | return lowerOctaveNotes + upperOctaveNotes
71 |
72 |
73 | def removeChordViolations(chord):
74 | listOfChords = {}
75 | for note in chord:
76 | instrument = note.instrument
77 | if instrument in listOfChords:
78 | listOfChords[instrument].append(note)
79 | else:
80 | listOfChords[instrument] = [note]
81 |
82 | newChord = []
83 | for instrument, singleChord in listOfChords.items():
84 | maxSize = CHORD_MAX_SIZES[INSTRUMENTS[instrument]]
85 |
86 | if KEEP_NOTES_BY_INSTRUMENT[INSTRUMENTS[instrument]] == 'h':
87 | newSingleChord = removeLowestNotes(singleChord, maxSize)
88 | else:
89 | newSingleChord = removeHighestNotes(singleChord, maxSize)
90 |
91 | newChord.extend(newSingleChord)
92 |
93 | # We need to preserve the original note order, because sometimes
94 | # saving has issues when notes are reordered
95 | preservedOrderChord = [note for note in chord if note in newChord]
96 |
97 | return preservedOrderChord, len(preservedOrderChord) < len(chord)
98 |
99 |
100 | def main():
101 | # get song file from user
102 | songFile = input('Please enter the file name of your song (include the .nbs): ')
103 | if not songFile.endswith('.nbs'):
104 | sys.exit('Your song file must end with ".nbs".')
105 |
106 | try:
107 | song = pynbs.read(songFile)
108 | songName = songFile[:-4]
109 | except Exception as e:
110 | sys.exit(f'An error occurred while reading the song file "{songFile}".\nError name: {e.__class__.__name__}\nExact error (search this up for help): {e}')
111 |
112 | # give user option to compress song
113 | originalSongLength = song.header.song_length
114 | if originalSongLength > MAX_SONG_LENGTH:
115 | print(f"Your song's length is {originalSongLength}, and the max length of a song is {MAX_SONG_LENGTH}.")
116 |
117 | print('You might want to compress your song if it is too slow or too long.')
118 | print('Compressing your song would remove every other tick and make it half as long. This may or may not make your song sound much worse.')
119 | settingCompress = True if getValidInput(['y', 'n'], 'Would you like to compress your song? (y/n): ') == 'y' else False
120 |
121 | # warn user if there are notes out of range
122 | for note in song.notes:
123 | if note.key < INSTRUMENT_RANGE[0] or note.key > INSTRUMENT_RANGE[1]:
124 | print('Your song contains notes that are outside the normal range. They will be transposed to be playable.')
125 | input('Press Enter to Continue')
126 | break
127 |
128 | # warn user if there are custom instruments
129 | if len(song.instruments) > 0:
130 | print('Your song contains custom instruments. All notes using custom instruments will be removed.')
131 | input('Press Enter to Continue')
132 |
133 | newSong = pynbs.new_file()
134 | newSong.header = song.header
135 | newSong.layers = song.layers
136 | newSong.header.tempo = 5
137 |
138 | hasMaxChordViolation = False
139 |
140 | # iterate through the whole song by chords
141 | for tick, chord in song:
142 | newTick = tick if not settingCompress else tick // 2
143 |
144 | if newTick > MAX_SONG_LENGTH:
145 | print('Notice: Your song was too long, so some had to be cut off the end.')
146 | break
147 |
148 | if (tick % 2 != 0 and not settingCompress) or (tick % 2 == 0):
149 | chord = removeCustomNotes(chord)
150 | chord = fixIllegalNotes(chord)
151 | chord, chordViolation = removeChordViolations(chord)
152 |
153 | if chordViolation:
154 | hasMaxChordViolation = True
155 |
156 | for note in chord:
157 | note.tick = newTick
158 | note.panning = 0
159 | note.pitch = 0
160 | newSong.notes.append(note)
161 |
162 | if hasMaxChordViolation:
163 | print('Notice: Your song contained chords that were larger than allowed. Some notes were removed from these chords.')
164 |
165 | # save the new song
166 | newFileName = songName + ' (Formatted).nbs'
167 |
168 | newSong.save(newFileName)
169 | print(f'Your formatted song was saved under "{newFileName}"')
170 |
171 |
172 | if __name__ == '__main__':
173 | main()
174 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 🎶 NBS Converter for Music Machine 🎶
3 |
4 |
5 | This repo contains some Python scripts to convert `.nbs` files into a schematic that can be pasted into your Minecraft world. This is specifically designed to work with [jazziiRed](https://www.youtube.com/@jazziiRed) and Mooncatcher's Music Machine, featured in [this YouTube video](https://youtu.be/V6X2BHpeLww) and downloadable [here](https://www.mediafire.com/file/tt3gpsz4jc6ak1m/Music_Machine_Final.zip/file). Basically, it can take any `.nbs` song and translate it into several sequences of music discs stored inside chests, so you an enjoy any song you want in our machine!
6 |
7 | An `.nbs` song is a note block song file that is generated by [Open Note Block Studio](https://opennbs.org/), a program commonly used by Minecraft musicians to compose note block songs.
8 |
9 | ***Disclaimer**: This is not the most professional code in the world, as most of it was written by me on a plane. You will need a basic understanding of how to run Python scripts using the command line, but don't worry, I will provide you with resources in the below sections.*
10 |
11 | ***Note: I will not be proactively updating this code unless I feel the need to. I will also not provide any tech support to those of you who are having technical troubles with Python. This is nothing against you, I just don't have the bandwidth. Hopefully the resources I provide will be enough to guide you through any technical issues you have.***
12 |
13 | ## Getting Started
14 |
15 | ### Installing Open Note Block Studio
16 |
17 | Let's start off with installing [Open Note Block Studio](https://opennbs.org/), the program used to create, edit, and listen to `.nbs` songs. This is optional, but it'll help a lot when it comes to previewing your songs before and after formatting to make sure that they'll sound good in the machine.
18 |
19 | ***Note**: This program does not currently work with MacOS. Sorry Mac users... The good news is that the scripts will still work on your Mac - you just won't be able to preview your songs before importing them into the game.*
20 |
21 | ### Installing Python
22 |
23 | Now for the technical stuff. First off, you need to have Python installed on your computer in order to run these scripts. If you've never done this, simply go to the [Python download page](https://www.python.org/downloads/) and install the latest version of Python for your operating system. If you are on macOS, you may also use [Homebrew](https://brew.sh/).
24 |
25 | When installing Python, make sure to check the box that says "Add Python to PATH" (or something similar). This will allow you to run Python scripts from the command line.
26 |
27 | Whatever operating system you have and whatever method you choose, just make sure that Python installed properly by running **one** of the following in your Command Prompt (Windows) or Terminal (MacOS or Linux):
28 |
29 | ```bash
30 | py --version
31 | python --version
32 | python3 --version
33 | ```
34 |
35 | This should spit out the version of Python that was installed on your system.
36 |
37 | ***Note**: `python3` can be replaced with whatever major version of Python you just installed, i.e. `python2` or `python4`, if you're from the future.*
38 |
39 | If none of these commands work, Google your error message. There are plenty of online forums such as stackoverflow that may contain the answer to your issues. Failing everything, simply uninstall and reinstall Python. Sometimes that works.
40 |
41 | Once one of these commands work, take note of which one (`py`, `python`, or `python3`) worked on your system. This will be the command used to run Python scripts moving forward.
42 |
43 | ### Installing Packages
44 |
45 | The two Python scripts in this repo make use of two existing Python libraries: [pynbs](https://github.com/OpenNBS/pynbs) and [mcschematic](https://github.com/Sloimayyy/mcschematic). The first is used to parse, edit, and create `.nbs` files, and the second is used to create and edit Minecraft schematics. Run the following two commands to install them. You can also go to their respective repos for more details.
46 |
47 | ```bash
48 | pip install pynbs
49 | pip install mcschematic
50 | ```
51 |
52 | Both of these libraries can be installed using `pip`, which should be automatically installed for you alongside Python. If you are on Windows, this should have been a checkbox during installation.
53 |
54 | `pip` is a package manager used to install Python libraries that other people have written. These libraries contain code that you can use to easily do things that might take a long time to do from scratch, like parsing an `.nbs` file.
55 |
56 | If you have issues with `pip`, please look up your error message on Google. You might need to use `pip3` (or whatever version). Just like with Python, you can check your version of `pip` using either of the following commands:
57 |
58 | ```bash
59 | pip --version
60 | pip3 --version
61 | ```
62 |
63 | If `pip` doesn't work, you can also try the following (make sure to replace `python` with whatever command worked for you in [Installing Python](#installing-python)):
64 |
65 | ```bash
66 | python -m pip install pynbs
67 | python -m pip install mcschematic
68 | ```
69 |
70 | Once you have these installed, the hard part is over! 🎉
71 |
72 | Now for the fun part...
73 |
74 | ## Running the Scripts
75 |
76 | So in order to run the scripts, you need to have them on your computer. You can either clone this repository using git, or you can simply download the following three files and put them all in the same folder.
77 |
78 | - `constants.py`
79 | - `nbs_format_song.py`
80 | - `nbs_generate_schematic.py`
81 |
82 | Once you've done this, you're ready to convert any `.nbs` song you wish! Download or compose any song you like (I have some good ones in the `example_songs` folder.), and drop them into the same folder as the three `.py` files.
83 |
84 | ### Formatting the Song
85 |
86 | The first thing that you will want to do is format your song. However you decide to build the final configuration of the music machine, it will not be able to play every `.nbs` song as is, due to chord size limitations, length, illegal notes, etc. So this first script is designed to format the `.nbs` song so that our machine can actually play it.
87 |
88 | But before you run the script, you might want to tweak some settings. To do so, open `constants.py` in a text editor of your choice (even notepad is fine) and take a look at any of the variables marked with `EDITABLE SETTING`. **DO NOT** edit any of the variables marked with `DO NOT EDIT` (unless you want to, I'm not your mom).
89 |
90 | FYI - If you're just using this script for the machine in the world download, all the default settings will work fine. In fact, I would advise against editing this file if this is the case.
91 |
92 | Once you've made the edits that you want, make sure to save the file. Now it's time to actually run the script!
93 |
94 | #### Running the Formatting Script
95 |
96 | First, navigate to the folder that your scripts are stored in using either Command Prompt (Windows) or Terminal (macOS or Linux). To do so, use the `cd` command (This works in Windows, MacOS, and Linux.). So if, for example, the path to your folder is `C:\Users\username\Documents\nbs-converter`, you would need to type
97 |
98 | ```bash
99 | cd C:\Users\username\Documents\nbs-converter
100 | ```
101 |
102 | Once there, run the following command:
103 |
104 | ```bash
105 | py nbs_format_song.py
106 | ```
107 |
108 | or whatever Python command worked for you in [Installing Python](#installing-python).
109 |
110 | Follow the instructions that the script gives you, and at the end it should spit out a brand new `.nbs` file that's correctly formatted! Take a listen to this to see if it still sounds good, and once you're satisfied, it's time to move on to the next step!
111 |
112 | ### Generating the Schematic
113 |
114 | Now that you have a formatted `.nbs` song that sounds great, it's time to transform it into a form that can be pasted into Minecraft!
115 |
116 | This part's really easy. Again using either Command Prompt or Terminal, run the following:
117 |
118 | ```bash
119 | py nbs_generate_schematic.py
120 | ```
121 |
122 | or whatever Python command worked for you in [Installing Python](#installing-python).
123 |
124 | Follow the instructions that the script gives you, and at the end it should spit out a brand new `.schem` file that you can paste into your world!
125 |
126 | ## Pasting the Song into Minecraft
127 |
128 | Ok, full transparency - this part requires you to have worldedit or some other mod that can handle `.schem` files in Minecraft. I'm not going to get into how to install this, as there are several great tutorials out there for installing and using worldedit. I'll link a couple here, but feel free to look around to find one that makes sense to you.
129 |
130 | - [Installing Fabric](https://shockbyte.com/billing/knowledgebase/213/How-to-Install-Fabric-Mods-on-Your-PC.html)
131 | - [Basic worldedit tutorial by BodhiMC](https://youtu.be/5PoHeqvxeos?si=Qo_AvVJ9Ppb2OLve)
132 | - [Schematic tutorial by Avomance](https://youtu.be/P_mR4Y0R0uc?si=j1jXinxHueqxKS9W)
133 |
134 | Once you're ready, simply load up the world. If you're using the world download, follow the instructions on the signs for exactly where to paste the song. The song will paste into your world as a straight row of chests labelled with signs.
135 |
136 | ***Note**: You will probably also want to download and use the `Silent_Discs` resource pack in this repo. This replaces all the music disc songs with silence, so you can only hear the note blocks while the machine is running. It's pretty obnoxious without it.*
137 |
138 | Then, all you have to do is start it up and enjoy the music! 🎧🎶
139 |
--------------------------------------------------------------------------------
/nbs_generate_schematic.py:
--------------------------------------------------------------------------------
1 | import pynbs
2 | import sys
3 | import numpy
4 | import mcschematic
5 | from constants import *
6 |
7 |
8 | def verifyFormat(song, songName):
9 | print('Verifying your song...')
10 | isValid = True
11 | # check song length
12 | print('Checking song length...')
13 | if song.header.song_length > MAX_SONG_LENGTH:
14 | print('Warning: Your song is too long.')
15 | isValid = False
16 |
17 | # check custom instruments
18 | print('Checking for custom instruments...')
19 | if len(song.instruments) > 0:
20 | print('Warning: Your song contains custom instruments.')
21 | isValid = False
22 |
23 | # check range
24 | print('Checking note ranges...')
25 | for note in song.notes:
26 | if note.key < INSTRUMENT_RANGE[0] or note.key > INSTRUMENT_RANGE[1]:
27 | print('Warning: Your song contains notes that are outside the normal range.')
28 | isValid = False
29 | break
30 |
31 | # check chord lengths
32 | print('Checking chord lengths...')
33 | for tick, chord in song:
34 | listOfChords = {}
35 | for note in chord:
36 | instrument = note.instrument
37 | if instrument in listOfChords:
38 | listOfChords[instrument].append(note)
39 | else:
40 | listOfChords[instrument] = [note]
41 |
42 | for instrument, singleChord in listOfChords.items():
43 | lowerOctaveNotes = [note for note in singleChord if note.key < INSTRUMENT_RANGE[0] + 12]
44 | upperOctaveNotes = [note for note in singleChord if note.key >= INSTRUMENT_RANGE[0] + 12]
45 |
46 | if len(lowerOctaveNotes) > CHORD_MAX_SIZES[INSTRUMENTS[instrument]] or len(upperOctaveNotes) > CHORD_MAX_SIZES[INSTRUMENTS[instrument]]:
47 | print('Warning: Your song contains chords that are larger than allowed.')
48 | isValid = False
49 | break
50 |
51 | if not isValid:
52 | break
53 |
54 | if not isValid:
55 | sys.exit(f'We found some issues with your song "{songName}". Please make sure to format it using the "nbs_format_song" script.')
56 | else:
57 | print('Song verified. Everything looks good!')
58 |
59 |
60 | def removeEmptyChests(chestContents):
61 | newChestContents = {}
62 |
63 | for instrument, contents in chestContents.items():
64 | newChestContents[instrument] = []
65 |
66 | for octaves in contents:
67 | newOctaves = [[], []]
68 |
69 | isLowerOctaveNotEmpty = any(note != -1 for note in octaves[0])
70 | isUpperOctaveNotEmpty = any(note != -1 for note in octaves[1])
71 |
72 | if isLowerOctaveNotEmpty:
73 | newOctaves[0] = octaves[0]
74 |
75 | if isUpperOctaveNotEmpty:
76 | newOctaves[1] = octaves[1]
77 |
78 | newChestContents[instrument].append(newOctaves)
79 |
80 | return newChestContents
81 |
82 |
83 | def newDisc(slot, note):
84 | if note == -1:
85 | return '{Count:1b,Slot:' + str(slot) + 'b,id:"minecraft:wooden_shovel"}'
86 |
87 | if note >= 12:
88 | note -= 12
89 |
90 | disc = NOTES_TO_DISCS_NAMED[note] if NAME_DISCS else NOTES_TO_DISCS_UNNAMED[note]
91 | return '{Count:1b,Slot:' + str(slot) + 'b,id:' + disc + '}'
92 |
93 |
94 | def createShulker(currentShulker, contents):
95 | slot = (currentShulker - 1) % 27
96 |
97 | # remove trailing comma
98 | contents = contents[:len(contents) - 1]
99 | return '{Count:1b,Slot:' + str(
100 | slot) + 'b,id:"minecraft:shulker_box",tag:{BlockEntityTag:{CustomName:\'{"text":"' + str(
101 | currentShulker) + '"}\',Items:[' + contents + '],id:"minecraft:shulker_box"},display:{Name:\'{"text":"' + str(
102 | currentShulker) + '"}\'}}}'
103 |
104 |
105 | def createChest(type_, contents):
106 | # remove trailing comma
107 | if len(contents) > 0:
108 | contents = contents[:len(contents) - 1]
109 |
110 | return 'minecraft:chest[facing=south,type=' + type_ + ']{Items:[' + contents + ']}'
111 |
112 |
113 | def createSign(instrument, currentModule, octave):
114 | octaveMessage = 'lower octave' if octave == 0 else 'upper octave'
115 | return 'minecraft:oak_wall_sign[facing=south,waterlogged=false]{front_text:{color:"black",has_glowing_text:0b,messages:[\'{"text":"' + instrument + ' ' + str(
116 | currentModule) + '"}\',\'{"text":"' + octaveMessage + '"}\',\'{"text":""}\',\'{"text":""}\']},is_waxed:0b}'
117 |
118 |
119 | def main():
120 | # get song file from user
121 | songFile = input('Please enter the file name of your song (include the .nbs): ')
122 | if not songFile.endswith('.nbs'):
123 | sys.exit('Your song file must end with ".nbs".')
124 |
125 | try:
126 | song = pynbs.read(songFile)
127 | songName = songFile[:-4]
128 | except Exception as e:
129 | sys.exit(f'An error occurred while reading the song file "{songFile}".\nError name: {e.__class__.__name__}\nExact error (search this up for help): {e}')
130 |
131 | verifyFormat(song, songName)
132 |
133 | # fix the length of the song for min fill of last chest
134 | lastChestFill = (song.header.song_length + 1) % 27
135 | songLengthAdjusted = song.header.song_length + 1
136 | if 1 <= lastChestFill < CHEST_MIN_FILL:
137 | songLengthAdjusted += CHEST_MIN_FILL - lastChestFill
138 |
139 | # initialize data structure
140 | allChestContents = {}
141 | emptyChest = numpy.full(songLengthAdjusted, -1)
142 | for instrument in INSTRUMENTS:
143 | allChestContents[instrument] = []
144 | for i in range(CHORD_MAX_SIZES[instrument]):
145 | allChestContents[instrument].append([emptyChest.copy(), emptyChest.copy()])
146 |
147 | # iterate through the whole song by chords
148 | keyModifier = INSTRUMENT_RANGE[0]
149 | currentIndices = {}
150 | for tick, chord in song:
151 | # reset current indices
152 | for instrument in INSTRUMENTS:
153 | currentIndices[instrument] = [0, 0]
154 |
155 | for note in chord:
156 | instrument = INSTRUMENTS[note.instrument]
157 | adjustedKey = note.key - keyModifier
158 | octave = 0 if adjustedKey <= 11 else 1
159 | allChestContents[instrument][currentIndices[instrument][octave]][octave][tick] = adjustedKey
160 | currentIndices[instrument][octave] += 1
161 |
162 | minimalChestContents = removeEmptyChests(allChestContents)
163 |
164 | # turn minimalChestContents into a schematic
165 | schem = mcschematic.MCSchematic()
166 | offset = 0
167 | print('Generating Schematic...')
168 | for instrument, contents in minimalChestContents.items():
169 | currentModule = 1
170 |
171 | for module in contents:
172 | lowerChest1 = ''
173 | upperChest1 = ''
174 | lowerChest2 = ''
175 | upperChest2 = ''
176 | lowerShulker = ''
177 | upperShulker = ''
178 | currentShulker = 1
179 | lowerOctaveEmpty = len(module[0]) == 0
180 | upperOctaveEmpty = len(module[1]) == 0
181 |
182 | for currentTick in range(songLengthAdjusted):
183 | currentSlot = currentTick % 27
184 |
185 | if not lowerOctaveEmpty:
186 | lowerShulker += newDisc(currentSlot, module[0][currentTick]) + ','
187 |
188 | if not upperOctaveEmpty:
189 | upperShulker += newDisc(currentSlot, module[1][currentTick]) + ','
190 |
191 | # if we are on the last slot of a shulker box, or the song has ended
192 | if (currentTick + 1) % 27 == 0 or currentTick == songLengthAdjusted - 1:
193 | # turn the shulker contents into actual shulker
194 | if not lowerOctaveEmpty:
195 | lowerShulker = createShulker(currentShulker, lowerShulker)
196 |
197 | if not upperOctaveEmpty:
198 | upperShulker = createShulker(currentShulker, upperShulker)
199 |
200 | # if the current shulker should go in the first chests
201 | if currentShulker <= 27:
202 | if not lowerOctaveEmpty:
203 | lowerChest1 += lowerShulker + ','
204 |
205 | if not upperOctaveEmpty:
206 | upperChest1 += upperShulker + ','
207 |
208 | else:
209 | if not lowerOctaveEmpty:
210 | lowerChest2 += lowerShulker + ','
211 |
212 | if not upperOctaveEmpty:
213 | upperChest2 += upperShulker + ','
214 |
215 | # reset the shulkers and increment the current shulker
216 | lowerShulker = ''
217 | upperShulker = ''
218 | currentShulker += 1
219 |
220 | if not lowerOctaveEmpty:
221 | lowerChest1 = createChest('right', lowerChest1)
222 | lowerChest2 = createChest('left', lowerChest2)
223 | schem.setBlock((offset, 0, -1), lowerChest1)
224 | schem.setBlock((offset + 1, 0, -1), lowerChest2)
225 | schem.setBlock((offset, 0, 0), createSign(instrument, currentModule, 0))
226 | else:
227 | schem.setBlock((offset, 0, -1), "minecraft:air")
228 | schem.setBlock((offset + 1, 0, -1), "minecraft:air")
229 | schem.setBlock((offset, 0, 0), "minecraft:air")
230 |
231 | if not upperOctaveEmpty:
232 | upperChest1 = createChest('right', upperChest1)
233 | upperChest2 = createChest('left', upperChest2)
234 | schem.setBlock((offset, 1, -1), upperChest1)
235 | schem.setBlock((offset + 1, 1, -1), upperChest2)
236 | schem.setBlock((offset, 1, 0), createSign(instrument, currentModule, 1))
237 | else:
238 | schem.setBlock((offset, 1, -1), "minecraft:air")
239 | schem.setBlock((offset + 1, 1, -1), "minecraft:air")
240 | schem.setBlock((offset, 1, 0), "minecraft:air")
241 |
242 | currentModule += 1
243 | offset += 2
244 |
245 | saveName = songName.lower().replace('(', '').replace(')', '').replace(' ', '_')
246 | schem.save('', saveName, mcschematic.Version.JE_1_20)
247 | print('Your schematic was successfully generated and saved under "' + saveName + '.schem"')
248 |
249 |
250 | if __name__ == '__main__':
251 | main()
252 |
--------------------------------------------------------------------------------
/example_songs/Perry The Platypus Theme (Extended).nbs:
--------------------------------------------------------------------------------
1 | @ L
2 | Perry the Platypus Theme.mid 'dd 0dd 5dd 8dd 8dd 8dd 5dd 0dd
3 | "dd "dd 'dd "dd
4 | "dd "dd "dd
5 | $dd $dd 'dd %dd $dd
6 | "dd "dd "dd
7 | %dd %dd 'dd %dd
8 | 'dd 'dd 'dd %dd 'dd
9 | "dd "dd "dd 'dd
10 | "dd "dd "dd
11 | $dd $dd 'dd %dd $dd
12 | "dd "dd "dd
13 | %dd %dd 'dd %dd
14 | 'dd 'dd 'dd %dd 'dd
15 | "dd "dd "dd 'dd
16 | "dd "dd "dd
17 | $dd $dd 'dd %dd $dd
18 | "dd "dd "dd
19 | %dd %dd 'dd %dd
20 | 'dd 'dd 'dd %dd 'dd
21 | "dd "dd "dd 'dd
22 | "dd "dd "dd
23 | $dd $dd 'dd %dd $dd
24 | "dd "dd "dd .dd %dd %dd 'dd %dd 1dd 5dd 8dd .dd 'dd 'dd 'dd %dd 'dd 8dd 5dd "dd "dd "dd 1dd 5dd 'dd
25 | "dd "dd "dd
26 | $dd $dd 'dd %dd $dd
27 | "dd "dd "dd 8dd %dd %dd 'dd %dd
28 | 'dd 'dd 'dd %dd 'dd
29 | "dd "dd "dd 7dd 'dd
30 | "dd "dd "dd
31 | $dd $dd 'dd %dd $dd
32 | "dd "dd "dd 6dd %dd %dd 'dd %dd
33 | 'dd 'dd 'dd %dd 'dd
34 | "dd "dd "dd 5dd 'dd
35 | "dd "dd "dd
36 | $dd $dd 'dd %dd $dd
37 | "dd "dd "dd 8dd %dd %dd 'dd %dd
38 | 'dd 'dd 'dd %dd 'dd
39 | "dd "dd "dd 7dd 'dd
40 | "dd "dd "dd
41 | $dd $dd 'dd %dd $dd
42 | "dd "dd "dd 6dd %dd %dd 'dd %dd
43 | 'dd 'dd 'dd %dd 'dd 5dd 5dd -dd 5dd )dd 3dd 'dd %dd 4dd 4dd ,dd 4dd 2dd 5dd 5dd -dd 5dd )dd 3dd 'dd %dd
44 | $dd .dd 5dd $dd )dd 5dd 0dd 3dd 9dd 3dd )dd 5dd $dd )dd 5dd 0dd 7dd 9dd 3dd )dd .dd 'dd )dd 3dd ,dd 7dd .dd "dd %dd "dd 7dd 'dd .dd 7dd .dd )dd 'dd %dd 1dd "dd 7dd 7dd 'dd 5dd )dd 7dd
45 | "dd 'dd %dd 3dd 7dd .dd 1dd *dd "dd 'dd 7dd 'dd 3dd 7dd
46 | "dd 'dd %dd 1dd 'dd 7dd 3dd 7dd 'dd 3dd "dd 7dd 1dd 'dd 'dd %dd 5dd 7dd "dd .dd 1dd +dd "dd "dd "dd 7dd 'dd "dd "dd .dd "dd "dd "dd 7dd "dd $dd $dd $dd $dd 'dd %dd $dd "dd "dd "dd "dd 7dd "dd %dd %dd %dd %dd 7dd 'dd %dd .dd .dd .dd 7dd 1dd 'dd 1dd 'dd 'dd 'dd 1dd 'dd %dd 'dd .dd "dd .dd "dd "dd "dd .dd 7dd "dd 8dd .dd 1dd 8dd *dd "dd 8dd 7dd 'dd 7dd "dd 7dd "dd "dd "dd 7dd 7dd "dd $dd $dd $dd $dd 'dd %dd $dd "dd "dd "dd "dd 7dd "dd 3dd %dd 3dd %dd %dd %dd 3dd 7dd 'dd %dd 5dd .dd 5dd 5dd 7dd 'dd 'dd 'dd 'dd 'dd %dd 'dd ,dd 7dd .dd 1dd .dd )dd "dd "dd "dd 7dd 'dd .dd 7dd .dd )dd 'dd %dd .dd "dd 7dd 1dd 7dd 'dd 3dd )dd 7dd
47 | "dd 'dd %dd 1dd 7dd .dd 1dd 5dd *dd "dd )dd 'dd 7dd 'dd 5dd 7dd
48 | "dd 'dd %dd 5dd 'dd 7dd 7dd 'dd 3dd "dd 7dd 1dd 'dd 'dd %dd 5dd 7dd -dd 0dd )dd !dd )dd )dd 7dd 'dd 7dd 8dd $dd 'dd %dd 5dd )dd 7dd 7dd 'dd
49 | $dd 7dd 8dd )dd 'dd %dd 5dd 7dd
50 | )dd 7dd 'dd 7dd
51 | $dd 'dd %dd
52 | )dd 7dd 7dd 'dd 3dd $dd 7dd 5dd )dd 'dd %dd 7dd .dd *dd .dd 'dd "dd 'dd 'dd 7dd 'dd 7dd 8dd "dd 'dd %dd 5dd 'dd 7dd 7dd 'dd 3dd "dd 7dd
53 | 'dd 'dd %dd 5dd 7dd
54 | 'dd 7dd 'dd 7dd
55 | "dd 'dd %dd
56 | 'dd 7dd 5dd 5dd 1dd 7dd 'dd .dd )dd 5dd "dd 7dd 3dd 3dd 'dd 3dd 0dd 'dd %dd ,dd 'dd 1dd 7dd .dd .dd )dd 5dd %dd "dd "dd "dd .dd 1dd 7dd 'dd 5dd )dd .dd 7dd
57 | )dd 'dd %dd .dd "dd 7dd 7dd 'dd .dd )dd 7dd ,dd "dd 'dd %dd .dd 7dd
58 | "dd 7dd 'dd 5dd 5dd 3dd 7dd 1dd .dd "dd
59 | )dd 'dd %dd
60 | "dd 7dd 5dd 5dd 3dd 7dd 'dd 1dd .dd "dd .dd )dd 7dd
61 | "dd 'dd %dd ,dd 7dd .dd *dd .dd 'dd "dd 'dd 'dd 7dd 'dd .dd 7dd
62 | "dd 'dd %dd 1dd 'dd 7dd 7dd 'dd
63 | "dd 7dd
64 | 'dd 'dd %dd 3dd 7dd 5dd 'dd 7dd 'dd 5dd 7dd 5dd "dd 'dd %dd 8dd 'dd 7dd 7dd 'dd 5dd "dd 7dd
65 | 'dd 'dd %dd 5dd 7dd !dd $dd )dd 7dd 'dd 7dd
66 | $dd 'dd %dd
67 | )dd 7dd 7dd 'dd
68 | $dd 7dd
69 | )dd 'dd %dd 7dd 5dd 5dd 9dd 7dd 'dd 5dd -dd #dd .dd "dd .dd "dd )dd "dd %dd .dd "dd "dd "dd "dd .dd 5dd 7dd 2dd 1dd "dd "dd "dd 7dd .dd $dd $dd 8dd ,dd $dd .dd 5dd 'dd %dd 1dd "dd "dd .dd "dd "dd 7dd %dd %dd %dd 7dd 'dd 7dd 'dd 'dd 'dd 'dd %dd 1dd "dd "dd "dd %dd *dd "dd 1dd 6dd 7dd 1dd "dd %dd 7dd 'dd "dd "dd "dd 7dd $dd $dd $dd 'dd %dd "dd "dd "dd 7dd %dd %dd .dd "dd %dd 7dd 'dd .dd "dd 7dd 'dd 'dd 'dd 'dd %dd 8dd ,dd 7dd .dd "dd +dd "dd %dd .dd "dd 3dd "dd "dd .dd 7dd 7dd 'dd 1dd "dd "dd "dd 7dd .dd $dd $dd 1dd %dd 1dd $dd .dd 7dd 'dd %dd 1dd "dd "dd .dd "dd "dd 7dd %dd %dd 3dd %dd 7dd 'dd .dd .dd .dd 7dd "dd "dd 1dd 'dd 'dd 5dd 'dd 1dd 1dd 'dd %dd %dd %dd .dd "dd "dd "dd %dd *dd "dd .dd .dd 7dd "dd "dd 8dd "dd %dd 8dd 8dd 7dd 'dd ,dd ,dd 7dd "dd "dd "dd 7dd 7dd 7dd +dd +dd $dd $dd $dd 'dd %dd "dd "dd "dd 7dd 3dd %dd %dd %dd 3dd 3dd 7dd 'dd 'dd 'dd 5dd 5dd 5dd 7dd )dd )dd 'dd 'dd 'dd 'dd %dd 7dd .dd "dd )dd "dd %dd .dd "dd "dd "dd "dd .dd 5dd 7dd 'dd 1dd "dd "dd "dd 7dd .dd $dd $dd 8dd ,dd $dd .dd 5dd 'dd %dd 1dd "dd "dd .dd "dd "dd 7dd %dd %dd %dd 7dd 'dd 7dd 'dd 'dd 'dd 'dd %dd 1dd "dd "dd "dd %dd *dd "dd 1dd 6dd 7dd 1dd "dd %dd 7dd 'dd "dd "dd "dd 7dd $dd $dd $dd 'dd %dd "dd "dd "dd 7dd %dd %dd .dd "dd %dd 7dd 'dd .dd "dd 7dd 'dd 'dd 'dd 'dd %dd 8dd ,dd 7dd .dd %dd +dd %dd %dd .dd "dd 3dd "dd .dd 7dd 7dd 'dd 1dd 7dd .dd 1dd %dd 1dd .dd 7dd 'dd %dd 1dd .dd "dd 7dd %dd %dd 3dd $dd 5dd )dd 5dd -dd 5dd 5dd )dd )dd 5dd 5dd 9dd 5dd )dd )dd 5dd 5dd 9dd 8dd 5dd 0dd .dd .dd "dd 'dd 7dd .dd .dd
70 | "dd 7dd 7dd 1dd 1dd
71 | 8dd 7dd .dd .dd
72 | 7dd 7dd 1dd 1dd
73 | 7dd 5dd "dd 7dd 3dd 3dd
74 | 7dd .dd .dd )dd 5dd "dd 7dd 7dd .dd .dd
75 | "dd 7dd 7dd 1dd 1dd
76 | 8dd 7dd .dd .dd
77 | 7dd 7dd 1dd 1dd
78 | 7dd
79 | %dd 5dd "dd 7dd 3dd 3dd )dd 7dd
80 | (dd "dd 7dd 7dd .dd .dd
81 | 'dd 7dd .dd .dd
82 | "dd 7dd 7dd 1dd 1dd
83 | 8dd 7dd .dd .dd
84 | 7dd 7dd 1dd 1dd
85 | 7dd 5dd "dd 7dd 3dd 3dd
86 | 7dd .dd .dd
87 | 5dd "dd 7dd
88 | 'dd 7dd .dd .dd
89 | "dd 7dd 7dd 1dd 1dd
90 | 8dd 7dd .dd .dd %dd 7dd 7dd 1dd 1dd
91 | 7dd 5dd "dd 7dd 3dd 3dd $dd 7dd "dd 7dd 7dd .dd 3dd .dd "dd 'dd 7dd .dd 3dd .dd
92 | "dd 7dd 7dd 1dd 5dd 1dd
93 | 8dd 7dd .dd 1dd .dd
94 | 7dd 7dd 1dd 5dd 1dd
95 | 7dd 5dd "dd 7dd 3dd 7dd 3dd
96 | 7dd .dd 3dd .dd )dd 5dd "dd 7dd 7dd .dd 3dd .dd
97 | "dd 7dd 7dd 1dd 5dd 1dd
98 | 8dd 7dd .dd 1dd .dd
99 | 7dd 7dd 1dd 5dd 1dd
100 | 7dd
101 | %dd 5dd "dd 7dd 3dd 7dd 3dd )dd 7dd
102 | ,dd "dd 7dd 7dd .dd 3dd .dd
103 | 'dd 7dd .dd 3dd .dd
104 | "dd 7dd 7dd 1dd 5dd 1dd
105 | 8dd 7dd .dd 1dd .dd
106 | 7dd 7dd 1dd 5dd 1dd
107 | 7dd 5dd "dd 7dd 3dd 7dd 3dd
108 | 7dd .dd 3dd .dd
109 | 5dd "dd 7dd
110 | 'dd 7dd .dd 3dd .dd
111 | "dd 7dd 7dd 1dd 5dd 1dd
112 | 8dd 7dd .dd 1dd .dd %dd 7dd 7dd 1dd 5dd 1dd
113 | 7dd 5dd "dd 7dd 3dd 7dd 3dd $dd 7dd "dd 7dd 7dd 'dd "dd "dd %dd 'dd 'dd 7dd 'dd +dd
114 | 'dd 'dd 7dd 'dd %dd
115 | 'dd 'dd 7dd 7dd 1dd +dd
116 | 7dd 'dd .dd 5dd .dd $dd $dd 7dd 7dd 1dd +dd "dd "dd 'dd %dd .dd 5dd .dd $dd $dd 7dd
117 | 'dd 'dd 7dd 'dd "dd +dd 'dd 'dd .dd 7dd .dd 'dd 7dd 1dd +dd
118 | 'dd %dd .dd 5dd .dd 'dd 'dd 7dd "dd +dd .dd 7dd 'dd .dd 'dd
119 | $dd $dd 7dd 7dd 1dd +dd "dd "dd 7dd 'dd %dd +dd
120 | $dd $dd .dd 7dd .dd "dd %dd "dd "dd 1dd 7dd 'dd 1dd 0dd 7dd 0dd
121 | )dd )dd .dd 'dd %dd .dd
122 | .dd "dd 7dd 7dd +dd 5dd 1dd )dd .dd 7dd 'dd .dd .dd 5dd .dd )dd )dd 7dd 5dd 1dd )dd %dd %dd 7dd 'dd %dd +dd .dd 5dd .dd
123 | 7dd
124 | "dd "dd 7dd 'dd 1dd 7dd 1dd 5dd 1dd )dd )dd )dd 'dd %dd .dd 5dd .dd .dd "dd 7dd 1dd 7dd 'dd 1dd
125 | )dd )dd 0dd 7dd 0dd 5dd 1dd )dd %dd %dd 1dd 'dd %dd 1dd .dd 7dd .dd 'dd "dd "dd %dd .dd 0dd 7dd 'dd +dd 0dd .dd 0dd 7dd .dd 0dd 0dd .dd 'dd %dd 0dd .dd 0dd 7dd 0dd .dd 7dd 1dd +dd 0dd 7dd 'dd .dd 0dd .dd 5dd .dd 0dd .dd 7dd 0dd 7dd 1dd +dd
126 | 'dd %dd .dd 5dd .dd
127 | 7dd 7dd 'dd 7dd 7dd 1dd +dd
128 | 'dd %dd .dd 5dd .dd
129 | 7dd 7dd 'dd 7dd 7dd 1dd +dd
130 | 'dd %dd 7dd 5dd 5dd )dd 0dd 5dd 'dd 2dd 0dd 6dd 6dd *dd 1dd 6dd 'dd 2dd 1dd )dd )dd )dd )dd 5dd 5dd )dd 0dd 5dd 'dd 2dd 0dd 6dd 6dd *dd 1dd 6dd 'dd 2dd 1dd )dd )dd )dd )dd 5dd $dd 5dd 5dd 0dd 3dd 9dd 3dd )dd 5dd $dd 5dd 5dd 0dd 7dd 9dd 3dd )dd "dd .dd
"dd ,dd ,dd
,dd "dd .dd "dd %dd "dd 7dd 'dd "dd "dd .dd 7dd "dd "dd .dd )dd 'dd %dd "dd %dd 1dd "dd 7dd %dd 7dd 'dd )dd 5dd )dd 7dd )dd )dd 5dd "dd 'dd %dd )dd 'dd 3dd 7dd 'dd .dd 1dd *dd "dd 'dd 7dd 'dd 'dd 3dd 7dd 'dd
131 | "dd 'dd %dd 'dd 3dd 'dd 7dd 'dd 7dd 'dd 'dd 3dd "dd 7dd 'dd %dd 1dd 'dd 'dd %dd %dd )dd 5dd 7dd )dd .dd "dd 1dd +dd "dd "dd "dd 7dd 'dd "dd "dd .dd "dd "dd "dd 7dd "dd $dd $dd $dd $dd 'dd %dd "dd "dd "dd "dd 7dd %dd %dd %dd %dd 7dd 'dd .dd .dd .dd 7dd 1dd 'dd 1dd 'dd 'dd 'dd 1dd 'dd %dd .dd "dd .dd "dd "dd "dd .dd 7dd 8dd .dd 1dd 8dd *dd "dd 8dd 7dd 'dd 7dd "dd 7dd "dd "dd "dd 7dd 7dd $dd $dd $dd $dd 'dd %dd "dd "dd "dd "dd 7dd 3dd %dd 3dd %dd %dd %dd 3dd 7dd 'dd 5dd "dd .dd 5dd 5dd 7dd "dd "dd 'dd .dd 'dd 'dd 'dd 'dd %dd "dd ,dd ,dd 7dd ,dd "dd .dd 1dd .dd )dd "dd "dd "dd 7dd 'dd "dd "dd .dd 7dd "dd
132 | )dd 'dd %dd %dd 1dd "dd 7dd %dd 7dd 'dd
133 | )dd 7dd
134 | "dd 'dd %dd 7dd 'dd .dd 1dd 3dd *dd "dd )dd 'dd 7dd 'dd 'dd 7dd %dd 1dd "dd 'dd %dd %dd 'dd 3dd 'dd 7dd 'dd 7dd 'dd
135 | "dd 7dd
136 | 'dd 'dd %dd 'dd 3dd 7dd 'dd )dd -dd 0dd 5dd )dd !dd )dd )dd 7dd 'dd )dd )dd 5dd 7dd )dd )dd 5dd $dd 'dd %dd )dd )dd 5dd )dd 7dd )dd ,dd 8dd 7dd 'dd ,dd ,dd 8dd $dd 7dd ,dd )dd 5dd )dd 'dd %dd )dd 7dd )dd 5dd )dd 7dd 'dd )dd 7dd 'dd 3dd $dd 'dd %dd 'dd )dd 5dd )dd 7dd )dd 7dd 'dd
137 | $dd 7dd )dd 5dd )dd 'dd %dd )dd 7dd "dd .dd *dd .dd 'dd "dd 'dd 'dd 7dd 'dd .dd 7dd ,dd 8dd "dd 'dd %dd ,dd )dd 5dd 'dd 7dd )dd 7dd 'dd 'dd 3dd "dd 7dd 'dd
138 | 'dd 'dd %dd )dd 5dd 7dd )dd
139 | 'dd 7dd 'dd 7dd
140 | "dd 'dd %dd
141 | 'dd 7dd 5dd 5dd 1dd 7dd 'dd .dd )dd )dd 5dd "dd 7dd )dd 3dd 'dd 3dd 'dd 3dd 0dd 'dd %dd ,dd 'dd 'dd %dd 1dd 7dd %dd .dd )dd .dd )dd 5dd %dd "dd "dd "dd .dd 1dd 7dd 'dd 5dd )dd )dd "dd .dd 7dd "dd
142 | )dd 'dd %dd "dd .dd "dd 7dd "dd 7dd 'dd "dd .dd )dd 7dd "dd ,dd ,dd "dd 'dd %dd ,dd "dd .dd 7dd "dd
143 | "dd 7dd 'dd 5dd 5dd 3dd 7dd 1dd .dd
144 | )dd 'dd %dd
145 | "dd 7dd 5dd 5dd 3dd 7dd 'dd 1dd .dd "dd .dd )dd 7dd "dd
146 | "dd 'dd %dd ,dd ,dd 7dd ,dd "dd .dd *dd .dd 'dd "dd 'dd 'dd 7dd 'dd "dd "dd .dd 7dd "dd
147 | "dd 'dd %dd %dd 1dd 'dd 7dd %dd 7dd 'dd
148 | "dd 7dd
149 | 'dd 'dd %dd 'dd 3dd 7dd 'dd )dd 5dd 'dd .dd 7dd 'dd )dd )dd 5dd 7dd )dd )dd 5dd "dd 'dd %dd )dd ,dd 8dd 'dd 7dd ,dd 1dd 7dd 'dd )dd 5dd "dd 7dd )dd
150 | 'dd 'dd %dd )dd 5dd 7dd )dd !dd $dd )dd 0dd 7dd 'dd 7dd
151 | $dd 'dd %dd
152 | )dd 7dd 7dd 'dd
153 | $dd 7dd
154 | )dd 'dd %dd 7dd 5dd )dd 5dd )dd 5dd 5dd 5dd 8dd #dd 9dd 8dd 5dd 5dd .dd .dd 8dd 8dd 5dd 5dd .dd .dd .dd "dd 8dd 8dd .dd .dd .dd "dd )dd "dd %dd .dd "dd "dd "dd "dd .dd 5dd 7dd 2dd 1dd "dd "dd "dd 7dd .dd $dd $dd 8dd ,dd $dd .dd 5dd 'dd %dd 1dd "dd "dd .dd "dd "dd 7dd %dd %dd %dd 7dd 'dd 7dd 'dd 'dd 'dd 'dd %dd 1dd "dd "dd "dd %dd *dd "dd 1dd 6dd 7dd 1dd "dd %dd 7dd 'dd "dd "dd "dd 7dd $dd $dd $dd 'dd %dd "dd "dd "dd 7dd %dd %dd .dd "dd %dd 7dd 'dd .dd "dd 7dd 'dd 'dd 'dd 'dd %dd 8dd ,dd 7dd .dd "dd +dd "dd %dd .dd "dd 3dd "dd "dd .dd 7dd 7dd 'dd 1dd "dd "dd "dd 7dd .dd $dd $dd 1dd %dd 1dd $dd .dd 7dd 'dd %dd 1dd "dd "dd .dd "dd "dd 7dd %dd %dd 3dd %dd 7dd 'dd .dd .dd .dd 7dd "dd "dd 1dd 'dd 'dd 5dd 'dd 1dd 1dd 'dd %dd %dd %dd .dd "dd "dd "dd %dd *dd "dd .dd .dd 7dd "dd "dd 8dd "dd %dd 8dd 8dd 7dd 'dd ,dd ,dd 7dd "dd "dd "dd 7dd 7dd 7dd +dd +dd $dd $dd $dd 'dd %dd "dd "dd "dd 7dd 3dd %dd %dd %dd 3dd 3dd 7dd 'dd 'dd 'dd 5dd 5dd 5dd 7dd )dd )dd 'dd 'dd 'dd 'dd %dd 7dd .dd "dd )dd "dd %dd .dd "dd "dd "dd "dd .dd 5dd 7dd 'dd 1dd "dd "dd "dd 7dd .dd $dd $dd 8dd ,dd $dd .dd 5dd 'dd %dd 1dd "dd "dd .dd "dd "dd 7dd %dd %dd %dd 7dd 'dd 7dd 'dd 'dd 'dd 'dd %dd 1dd "dd "dd "dd %dd *dd "dd 1dd 6dd 7dd 1dd "dd %dd 7dd 'dd "dd "dd "dd 7dd $dd $dd $dd 'dd %dd "dd "dd "dd 7dd %dd %dd .dd "dd %dd 7dd 'dd .dd "dd 7dd 'dd 'dd 'dd 'dd %dd 8dd ,dd 7dd .dd "dd +dd "dd %dd .dd "dd 3dd .dd "dd .dd 7dd 7dd 'dd 1dd "dd "dd .dd "dd 7dd .dd $dd $dd 1dd %dd 1dd 0dd $dd .dd 7dd 'dd %dd 1dd "dd "dd .dd "dd .dd "dd 7dd %dd %dd 3dd 1dd %dd 7dd 'dd 5dd "dd "dd .dd "dd .dd .dd 7dd 'dd 'dd 5dd 3dd 'dd 1dd 1dd 'dd %dd "dd "dd .dd "dd .dd .dd 7dd 4dd (dd 4dd ,dd 4dd (dd 4dd 8dd 8dd 2dd 8dd 5dd 5dd .dd .dd 4dd (dd 4dd +dd 4dd (dd 4dd 7dd 8dd 2dd 8dd 5dd 5dd .dd .dd 4dd (dd 4dd *dd 4dd (dd 4dd 6dd 8dd 2dd 8dd .dd .dd )dd )dd 5dd 5dd
155 | $dd
156 | ,dd
157 | #dd ,dd 3dd /dd 2dd /dd 6dd /dd 5dd &dd