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 @dataclass(frozen=True) 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()