Last active
October 7, 2022 09:36
-
-
Save Bentroen/979f10f928f1cd9e13736834987ec70f to your computer and use it in GitHub Desktop.
Script used to merge two very large .nbs files for the first Note Block Megacollab, totaling over 250,000 notes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import copy | |
import pynbs | |
import shutil | |
import os | |
file1 = "megacollab parts 1-28.nbs" | |
file2 = "megacollab parts 29-50.nbs" | |
output_file = "megacollab_merged.nbs" | |
song1 = pynbs.read(file1) | |
song2 = pynbs.read(file2) | |
INS_COUNT = song1.header.default_instruments | |
# Offset instrument IDs from the second song (so the IDs don't overlap) | |
offset = 1000 | |
for id, ins in enumerate(song2.instruments): | |
ins.id += offset | |
for note in song2.notes: | |
if note.instrument >= INS_COUNT: | |
note.instrument += offset | |
# Clean up, merge and remove unused instruments | |
all_instruments = song1.instruments + song2.instruments | |
used_instruments = set(note.instrument - INS_COUNT for note in song1.notes + song2.notes if note.instrument >= INS_COUNT) | |
merged_instruments_lookup = {} # Maps old instrument IDs to new instrument IDs | |
merged_instruments = [] # List of merged instruments | |
seen = [] | |
for ins in all_instruments: | |
if ins.id not in used_instruments: | |
continue | |
if ins.file in seen: | |
merged_instruments_lookup[ins.id] = seen.index(ins.file) | |
else: | |
id = len(merged_instruments) | |
merged_instruments_lookup[ins.id] = id | |
seen.append(ins.file) | |
new_ins = pynbs.Instrument(id, ins.name, ins.file, ins.pitch) | |
merged_instruments.append(new_ins) | |
# Rename and normalize instrument paths | |
seen = [] | |
for ins in merged_instruments: | |
if ins.file == "": | |
continue | |
old_path = os.path.join("instruments", ins.file.replace("/", os.sep)) | |
filename, ext = os.path.splitext(ins.file.split("/")[-1]) | |
if filename in seen: # avoid duplicate filenames | |
suffix = f"_{seen.count(filename) + 1}" | |
else: | |
suffix = "" | |
new_path = f"Megacollab/{filename}{suffix}{ext}" | |
shutil.copy(old_path, new_path) | |
ins.file = new_path | |
seen.append(filename) | |
# Zip up the instrument files | |
shutil.make_archive("megacollab_merged - Instruments", "zip", "Megacollab") | |
# Merge notes | |
song1_notes = song1.notes | |
song2_notes = [] | |
tick_offset = max(note.tick for note in song1.notes) + 1 | |
for note in song2.notes: | |
note.tick += tick_offset | |
song2_notes.append(note) | |
merged_notes: list[pynbs.Note] = song1_notes + song2_notes | |
# Fix note instruments | |
for note in merged_notes: | |
if note.instrument >= INS_COUNT: | |
note.instrument = merged_instruments_lookup[note.instrument - INS_COUNT] + INS_COUNT | |
# Merge stats | |
merged_header = song1.header | |
merged_header.minutes_spent += song2.header.minutes_spent | |
merged_header.left_clicks += song2.header.left_clicks | |
merged_header.right_clicks += song2.header.right_clicks | |
merged_header.blocks_added += song2.header.blocks_added | |
merged_header.blocks_removed += song2.header.blocks_removed | |
# Add song info | |
merged_header.song_name = "Note Block Megacollab" | |
# Populate layers | |
last_layer_id = max(note.layer for note in merged_notes) | |
merged_layers = [pynbs.Layer(id) for id in range(last_layer_id + 1)] | |
# Stats! | |
print("Ticks:", max(note.tick for note in merged_notes) + 1) | |
print("Layers:", max(note.layer for note in merged_notes) + 1) | |
print("Instruments:", max(note.instrument for note in merged_notes) + 1) | |
# Add starting tempo changers | |
tempo_changer_id = [ins for ins in merged_instruments if ins.name == "Tempo Changer"][0].id | |
note1 = pynbs.Note( | |
tick=0, | |
layer=110, | |
instrument=tempo_changer_id + INS_COUNT, | |
key=39, | |
pitch=int(song1.header.tempo * 15) | |
) | |
merged_notes.append(note1) | |
note2 = copy.copy(note1) | |
note2.tick += 1 | |
merged_notes.append(note2) | |
# Create final song file | |
final_song = pynbs.File( | |
merged_header, | |
merged_notes, | |
merged_layers, | |
merged_instruments, | |
) | |
final_song.save(output_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment