| 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 |
|
|
| |
| 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')] |
| ) |
|
|
|
|
| |
| 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() |
|
|