Skip to content

Instantly share code, notes, and snippets.

@Bentroen
Last active October 7, 2022 09:36
Show Gist options
  • Save Bentroen/979f10f928f1cd9e13736834987ec70f to your computer and use it in GitHub Desktop.
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.
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