Spaces:
Running
Running
import gradio as gr | |
import json | |
import logging | |
import random | |
from dataclasses import dataclass | |
from typing import Union | |
from do_not_delete_or_move_this import midi3 as mid3, midi2 as mid2 | |
# Define constants | |
DISABLE_PROMPTS = True | |
SWAP_BF_EN = False | |
class Preferences: | |
jack_mode: int | |
note_tolerance: int | |
def process_notes(channel_data, spb: float, prefs: Preferences): | |
chart_notes = [] | |
prev_note = [0, 60, 100, 0.0] | |
prev_pitch = prev_note[1] | |
cur_arrow = random.randint(0, 3) | |
for note in channel_data: | |
cur_pitch = note[1] | |
diff = cur_pitch - prev_pitch | |
if cur_pitch < prev_pitch: | |
cur_arrow = (cur_arrow - one_or_two_seed(diff)) % 4 | |
elif cur_pitch > prev_pitch: | |
cur_arrow = (cur_arrow + one_or_two_seed(diff)) % 4 | |
elif random.randint(1, 3) <= prefs.jack_mode: | |
cur_arrow = (cur_arrow + random.randint(-2, 2)) % 4 | |
sus_length = note[3] * 0.85 * 1000 if note[2] < 60 or note[3] > (spb / 2) + 0.0001 else 0 | |
chart_notes.append([note[0] * 1000, cur_arrow, sus_length]) | |
prev_pitch = cur_pitch | |
return chart_notes | |
def one_or_two_seed(seed: int) -> int: | |
seed = abs(seed) | |
seed_map = {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 3} | |
chance = seed_map.get(seed, 3) | |
rand = random.randint(0, 6) | |
return 2 if chance >= rand else 1 | |
def split_into_sections(notes: list, spb: float): | |
section_length = spb * 4 | |
full_note_data = {} | |
for note in notes: | |
note_section = ((note[0] / 1000) + 0.0001) // section_length | |
full_note_data.setdefault(note_section, []).append(note) | |
highest_channel = max(int(mm) for mm in full_note_data.keys()) + 1 | |
isolated_section_list = [[] for _ in range(0, highest_channel)] | |
for key, item in full_note_data.items(): | |
isolated_section_list[int(key)] = item | |
return isolated_section_list | |
def compare_sections(en_section: list, bf_section: list, prev_musthit: bool, prefs: Preferences): | |
percent_bf = (len(bf_section) / len(en_section)) * 100 if len(en_section) != 0 else (100 if len(bf_section) else 50) | |
must_hit = percent_bf >= prefs.note_tolerance or (prefs.note_tolerance >= percent_bf >= (100 - prefs.note_tolerance) and not prev_musthit) | |
unsorted_combined_sections = [] | |
if must_hit: | |
unsorted_combined_sections.extend([[en_note[0], en_note[1] + 4, en_note[2]] for en_note in en_section]) | |
unsorted_combined_sections.extend([[bf_note[0], bf_note[1], bf_note[2]] for bf_note in bf_section]) | |
else: | |
unsorted_combined_sections.extend([[en_note[0], en_note[1], en_note[2]] for en_note in en_section]) | |
unsorted_combined_sections.extend([[bf_note[0], bf_note[1] + 4, bf_note[2]] for bf_note in bf_section]) | |
sorted_combined_sections = sorted(unsorted_combined_sections, key=lambda x: (x[0], x[1])) | |
return sorted_combined_sections, must_hit | |
def main(path_to: str, prefs: Preferences): | |
pm = mid2.process_midi(path_to) | |
spb = mid2.obtain_spb(path_to) | |
bpm = round(60 / spb, 3) | |
full_mid_data = mid3.main(pm) | |
full_note_list_en = process_notes(full_mid_data[0], spb, prefs) | |
full_note_list_bf = process_notes(full_mid_data[1], spb, prefs) | |
sectioned_en_list = split_into_sections(full_note_list_en, spb) | |
sectioned_bf_list = split_into_sections(full_note_list_bf, spb) | |
sectioned_bf_list, sectioned_en_list = sectioned_en_list, sectioned_bf_list if SWAP_BF_EN else (sectioned_bf_list, sectioned_en_list) | |
len_diff = abs(len(sectioned_bf_list) - len(sectioned_en_list)) | |
if len(sectioned_en_list) < len(sectioned_bf_list): | |
sectioned_en_list.extend([[] for _ in range(0, len_diff)]) | |
if len(sectioned_en_list) > len(sectioned_bf_list): | |
sectioned_bf_list.extend([[] for _ in range(0, len_diff)]) | |
musthit = True | |
json_notes = [] | |
for en_section, bf_section in zip(sectioned_en_list, sectioned_bf_list): | |
sec_notes, musthit = compare_sections(en_section, bf_section, musthit, prefs) | |
json_notes.append({"sectionNotes": sec_notes, "lengthInSteps": 16, "mustHitSection": musthit}) | |
return json.dumps({"song": {"player1": "bf", "player2": "dad", "gfVersion": "gf", | |
"notes": json_notes, "stage": "", "needsVoices": True, | |
"validScore": True, "bpm": bpm, "speed": 2.4, "song": "tempSong"}}, indent=4) | |
def gradio_interface(midi_file, jack_mode, note_tolerance): | |
prefs = Preferences(jack_mode=jack_mode, note_tolerance=note_tolerance) | |
return main(midi_file.name, prefs) | |
theme = gr.themes.Soft( | |
primary_hue="sky", | |
secondary_hue="violet", | |
neutral_hue="gray", | |
font=[gr.themes.GoogleFont('orbitron')] | |
) | |
# Create Gradio interface | |
mainweb = gr.Interface( | |
fn=gradio_interface, | |
inputs=[ | |
gr.File(file_count="single", label="MIDI File"), | |
gr.Slider(minimum=0, maximum=3, step=1, value=0, label="Jack Mode"), | |
gr.Slider(minimum=0, maximum=100, step=5, value=75, label="Note Tolerance") | |
], | |
outputs=gr.Textbox(label="JSON Output"), | |
) | |
with gr.Blocks() as information: | |
with open("anotherday.md", "r", encoding="utf8") as f: | |
info = f.read() | |
gr.Markdown(value=info) | |
with gr.Blocks(theme=theme) as demo: | |
gr.Markdown("# Funkin Chart Generator") | |
gr.Markdown("Automatically generate an FnF chart only using MIDI files") | |
gr.TabbedInterface([mainweb, information], ["main settings", "information"]) | |
demo.launch() | |