File size: 5,535 Bytes
88f34f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a0132d
 
 
 
 
 
 
 
 
88f34f8
837da5d
88f34f8
 
 
41e3e01
 
88f34f8
41e3e01
837da5d
 
 
 
 
 
 
 
 
8a0132d
 
 
 
837da5d
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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()