Skip to content

Instantly share code, notes, and snippets.

@NAEL2XD
Last active May 16, 2025 20:53
Show Gist options
  • Select an option

  • Save NAEL2XD/c5a590137903f3f781c2bba013474c2f to your computer and use it in GitHub Desktop.

Select an option

Save NAEL2XD/c5a590137903f3f781c2bba013474c2f to your computer and use it in GitHub Desktop.
FNF: MIDI to FNF Psych Engine Compatible chart.
"""
Converts a single midi to a fnf chart for psych engine <0.7.3
Odd Instruments are DAD section and Even Instruments are BF notes.
REQUIRES pretty_midi and tqdm: pip install pretty_midi, tqdm
You need "import.mid" next to the py file or else you'll get an error!
"""
import pretty_midi
from tqdm import tqdm
import os.path
def checkExist(path):
if not os.path.exists(path): os.mkdir(path)
def checkDelete(path):
if os.path.exists(path): os.remove(path)
pretty_midi.pretty_midi.MAX_TICK = 1e10
midi = pretty_midi.PrettyMIDI("import.mid")
tempo_times, tempos = midi.get_tempo_changes()
if int(tempos[0]) == 120:
tempos[0] = float(input("Set the BPM: "))
bpmSpeed = tempos[0]/120
else:
bpmSpeed = float(input("BPM Mult? 1 for 1x, 2 for 2x, etc.\n> "))
songName = input("Song name: ")
speed = float(input("Speed: "))
isDad = input("Start as Must HIT (dad will sing first if it's y)? [y/n] ") == 'n'
instCount = 0
storage = []
notes = []
coolJSON = '{"song":{"player1":"bf","events":[],"gfVersion":"gf","notes":['
for instrument in tqdm(midi.instruments, desc="Reading..."):
isDad = not isDad
instCount += 1
count = 0
coolJSON += '{"sectionNotes":['
for note in tqdm(instrument.notes):
count += 1
coolJSON += f'[{(note.start*1000)/bpmSpeed},{note.pitch % 4 + (4 if isDad else 0)},{((note.end*1000) - (note.start*1000))/bpmSpeed}]'
if len(instrument.notes) != count: coolJSON += ","
if len(coolJSON) > 1000000:
storage.append(coolJSON)
coolJSON = ""
coolJSON += '],"lengthInSteps":16,"mustHitSection":false}' + ("," if len(midi.instruments) != instCount else "")
storage.append(coolJSON)
storage.append('],"player2":"dad","song":"' + songName + '","needsVoices":true,"validScore":true,"bpm":' + str(tempos[0]) + ',"speed":' + str(speed) + ',"generatedBy":"Nael\'s MIDI to FNF chart"}}')
coolJSON = ""
for i in tqdm(range(len(storage)), desc="Saving..."):
coolJSON += storage[i]
checkExist('out')
save = open(f'out/{songName}.json', 'w')
save.write(coolJSON)
save.close()
makeAsMod = input("Done saving! Do you want to make it as a mod? [y/n] ") == "y"
if makeAsMod:
convMod = songName.lower().replace(' ', '-').replace('.', '')
checkDelete(f'mods/weeks/{convMod}.json')
checkDelete(f'mods/songs/{convMod}/place your inst here')
checkDelete(f'mods/data/{convMod}/{convMod}.json')
checkExist("mods")
checkExist("mods/data")
checkExist("mods/songs")
checkExist("mods/weeks")
checkExist(f"mods/data/{convMod}")
checkExist(f"mods/songs/{convMod}")
week = '{"songs":[["' + songName + '","bf",[12,181,0]]],"hideFreeplay":false,"weekBackground":"","difficulties":"Normal","weekCharacters":["","",""],"storyName":"Converted Using Nael\'s MIDI to FNF Script","weekName":"","freeplayColor":[146,113,253],"hideStoryMode":false,"weekBefore":"","startUnlocked":false}'
save = open(f'mods/weeks/{convMod}.json', 'w')
save.write(week)
save.close()
save = open(f'mods/songs/{convMod}/place your inst here', 'w')
save.close()
os.rename(f'out/{songName}.json', f'mods/data/{convMod}/{convMod}.json')
print("Done")
os.system("pause")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment