feat(synth): Add auto-recommendation for 8-bit synthesizer parameters
Browse filesThis commit introduces a new "Auto-Recommend" feature for the 8-bit synthesizer, designed to help users quickly generate a suitable sound profile for their MIDI files without manual tweaking.
- A new `"Auto-Recommend (Analyze MIDI)"` option has been added to the "Style Preset" dropdown.
- When selected, the main processing function (`process_and_render_file`) now triggers the analysis and recommendation engine.
- The recommended parameters are then used for rendering and are also reflected back onto the UI sliders, allowing users to see and further adjust the generated settings.
app.py
CHANGED
@@ -166,14 +166,16 @@ def prepare_soundfonts():
|
|
166 |
# =================================================================================================
|
167 |
def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s, pulse_width,
|
168 |
vibrato_rate, vibrato_depth, bass_boost_level, fs=44100,
|
169 |
-
|
170 |
-
|
|
|
171 |
"""
|
172 |
Synthesizes an 8-bit style audio waveform from a PrettyMIDI object.
|
173 |
This function generates waveforms manually instead of using a synthesizer like FluidSynth.
|
174 |
Includes an optional sub-octave bass booster with adjustable level.
|
175 |
Instruments are panned based on their order in the MIDI file.
|
176 |
Instrument 1 -> Left, Instrument 2 -> Right.
|
|
|
177 |
"""
|
178 |
total_duration = midi_data.get_end_time()
|
179 |
# Initialize a stereo waveform buffer (2 channels: Left, Right)
|
@@ -213,26 +215,39 @@ def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s,
|
|
213 |
|
214 |
t = np.arange(num_samples) / fs
|
215 |
|
216 |
-
# --- Vibrato
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
226 |
# --- Waveform Generation (Main Oscillator with phase continuity) ---
|
227 |
-
phase_inc = 2 * np.pi * (
|
228 |
phase = osc_phase[i] + np.cumsum(phase_inc)
|
229 |
-
|
|
|
230 |
|
231 |
if waveform_type == 'Square':
|
232 |
note_waveform = signal.square(phase, duty=pulse_width)
|
233 |
elif waveform_type == 'Sawtooth':
|
234 |
note_waveform = signal.sawtooth(phase)
|
235 |
-
|
236 |
note_waveform = signal.sawtooth(phase, width=0.5)
|
237 |
|
238 |
# --- Bass Boost (Sub-Octave Oscillator) ---
|
@@ -249,49 +264,37 @@ def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s,
|
|
249 |
main_level = 1.0 - (0.5 * bass_boost_level)
|
250 |
note_waveform = (note_waveform * main_level) + (bass_sub_waveform * bass_boost_level)
|
251 |
|
252 |
-
# --- Noise
|
253 |
if noise_level > 0:
|
254 |
-
|
255 |
-
note_waveform += noise_waveform * noise_level
|
256 |
|
257 |
# --- Distortion (Wave Shaping) ---
|
258 |
if distortion_level > 0:
|
259 |
-
|
260 |
-
|
261 |
-
# --- Frequency Modulation (FM) ---
|
262 |
-
if fm_modulation_depth > 0:
|
263 |
-
modulated_freq = freq * (1 + fm_modulation_depth * np.sin(2 * np.pi * fm_modulation_rate * t))
|
264 |
-
phase_inc = 2 * np.pi * modulated_freq / fs
|
265 |
-
phase = osc_phase[i] + np.cumsum(phase_inc)
|
266 |
-
osc_phase[i] = phase[-1] % (2 * np.pi) # Store last phase
|
267 |
-
if waveform_type == 'Square':
|
268 |
-
note_waveform = signal.square(phase, duty=pulse_width)
|
269 |
-
elif waveform_type == 'Sawtooth':
|
270 |
-
note_waveform = signal.sawtooth(phase)
|
271 |
-
elif waveform_type == 'Triangle':
|
272 |
-
note_waveform = signal.sawtooth(phase, width=0.5)
|
273 |
|
274 |
# --- ADSR Envelope ---
|
275 |
start_amp = note.velocity / 127.0
|
276 |
envelope = np.zeros(num_samples)
|
277 |
|
278 |
if envelope_type == 'Plucky (AD Envelope)':
|
279 |
-
|
280 |
-
attack_samples = min(int(attack_time_s * fs), num_samples)
|
281 |
decay_samples = min(int(decay_time_s * fs), num_samples - attack_samples)
|
282 |
|
283 |
envelope[:attack_samples] = np.linspace(0, start_amp, attack_samples)
|
284 |
if decay_samples > 0:
|
285 |
envelope[attack_samples:attack_samples+decay_samples] = np.linspace(start_amp, 0, decay_samples)
|
286 |
-
|
287 |
envelope = np.linspace(start_amp, 0, num_samples)
|
288 |
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
|
|
|
|
295 |
|
296 |
# Apply envelope to the (potentially combined) waveform
|
297 |
note_waveform *= envelope
|
@@ -572,7 +575,7 @@ def Render_MIDI(input_midi_path,
|
|
572 |
# --- 8-bit synth params ---
|
573 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
574 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
575 |
-
s8bit_bass_boost_level,
|
576 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
577 |
):
|
578 |
"""
|
@@ -799,8 +802,8 @@ def Render_MIDI(input_midi_path,
|
|
799 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
800 |
s8bit_bass_boost_level,
|
801 |
fs=srate,
|
802 |
-
|
803 |
-
|
804 |
noise_level=s8bit_noise_level,
|
805 |
distortion_level=s8bit_distortion_level,
|
806 |
fm_modulation_depth=s8bit_fm_modulation_depth,
|
@@ -855,11 +858,190 @@ def Render_MIDI(input_midi_path,
|
|
855 |
|
856 |
return new_md5_hash, fn1, output_midi_summary, midi_to_render_path, (srate, audio_out), output_plot, song_description
|
857 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
858 |
# =================================================================================================
|
859 |
# === Main Application Logic ===
|
860 |
# =================================================================================================
|
861 |
|
862 |
-
def process_and_render_file(input_file,
|
|
|
|
|
863 |
# --- Transcription params ---
|
864 |
enable_stereo_processing,
|
865 |
transcription_method,
|
@@ -871,7 +1053,7 @@ def process_and_render_file(input_file,
|
|
871 |
# --- 8-bit synth params ---
|
872 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
873 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
874 |
-
s8bit_bass_boost_level,
|
875 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
876 |
):
|
877 |
"""
|
@@ -880,8 +1062,8 @@ def process_and_render_file(input_file,
|
|
880 |
"""
|
881 |
start_time = reqtime.time()
|
882 |
if input_file is None:
|
883 |
-
# Return a list of updates to clear all output fields
|
884 |
-
return [gr.update(value=None)] * 7
|
885 |
|
886 |
# The input_file from gr.Audio(type="filepath") is now the direct path (a string),
|
887 |
# not a temporary file object. We no longer need to access the .name attribute.
|
@@ -890,9 +1072,13 @@ def process_and_render_file(input_file,
|
|
890 |
print(f"Processing new file: {filename}")
|
891 |
|
892 |
try:
|
|
|
893 |
audio_data, native_sample_rate = librosa.load(input_file_path, sr=None, mono=False)
|
894 |
except Exception as e:
|
895 |
-
|
|
|
|
|
|
|
896 |
|
897 |
# --- Step 1: Check file type and transcribe if necessary ---
|
898 |
if filename.lower().endswith(('.mid', '.midi', '.kar')):
|
@@ -907,7 +1093,7 @@ def process_and_render_file(input_file,
|
|
907 |
|
908 |
# === STEREO PROCESSING LOGIC ===
|
909 |
if enable_stereo_processing:
|
910 |
-
if audio_data.ndim != 2 or audio_data.shape[0] != 2:
|
911 |
print("Warning: Audio is not stereo or could not be loaded as stereo. Falling back to mono transcription.")
|
912 |
enable_stereo_processing = False # Disable stereo processing if audio is not stereo
|
913 |
|
@@ -929,16 +1115,12 @@ def process_and_render_file(input_file,
|
|
929 |
print(f"Saved left channel to: {temp_left_wav_path}")
|
930 |
print(f"Saved right channel to: {temp_right_wav_path}")
|
931 |
|
932 |
-
print("Transcribing left channel...")
|
933 |
if transcription_method == "General Purpose":
|
934 |
midi_path_left = TranscribeGeneralAudio(temp_left_wav_path, onset_thresh, frame_thresh, min_note_len, min_freq, max_freq, infer_onsets_bool, melodia_trick_bool, multiple_bends_bool)
|
935 |
-
else:
|
936 |
-
midi_path_left = TranscribePianoAudio(temp_left_wav_path)
|
937 |
-
|
938 |
-
print("Transcribing right channel...")
|
939 |
-
if transcription_method == "General Purpose":
|
940 |
midi_path_right = TranscribeGeneralAudio(temp_right_wav_path, onset_thresh, frame_thresh, min_note_len, min_freq, max_freq, infer_onsets_bool, melodia_trick_bool, multiple_bends_bool)
|
941 |
else:
|
|
|
942 |
midi_path_right = TranscribePianoAudio(temp_right_wav_path)
|
943 |
|
944 |
if midi_path_left and midi_path_right:
|
@@ -956,48 +1138,111 @@ def process_and_render_file(input_file,
|
|
956 |
except Exception as e:
|
957 |
print(f"An error occurred during stereo processing: {e}")
|
958 |
raise gr.Error(f"Stereo Processing Failed: {e}")
|
959 |
-
else:
|
960 |
print("Stereo processing disabled. Using standard mono transcription.")
|
961 |
-
if audio_data
|
962 |
-
|
963 |
-
|
964 |
-
|
965 |
-
|
966 |
-
normalized_mono = normalize_loudness(mono_signal, native_sample_rate)
|
967 |
|
968 |
-
|
969 |
-
|
|
|
|
|
|
|
970 |
|
971 |
try:
|
972 |
if transcription_method == "General Purpose":
|
973 |
-
midi_path_for_rendering = TranscribeGeneralAudio(
|
974 |
-
temp_mono_wav_path, onset_thresh, frame_thresh, min_note_len,
|
975 |
-
min_freq, max_freq, infer_onsets_bool, melodia_trick_bool, multiple_bends_bool
|
976 |
-
)
|
977 |
else: # Piano-Specific
|
978 |
-
midi_path_for_rendering = TranscribePianoAudio(
|
979 |
-
analyze_midi_velocity(midi_path_for_rendering)
|
980 |
except Exception as e:
|
981 |
print(f"An error occurred during transcription: {e}")
|
982 |
raise gr.Error(f"Transcription Failed: {e}")
|
983 |
|
984 |
# --- Step 2: Render the MIDI file with selected options ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
985 |
print(f"Proceeding to render MIDI file: {os.path.basename(midi_path_for_rendering)}")
|
986 |
-
|
|
|
|
|
|
|
987 |
results = Render_MIDI(midi_path_for_rendering,
|
988 |
render_type, soundfont_bank, render_sample_rate,
|
989 |
render_with_sustains, merge_misaligned_notes, custom_render_patch, render_align,
|
990 |
render_transpose_value, render_transpose_to_C4, render_output_as_solo_piano, render_remove_drums,
|
991 |
-
|
992 |
-
|
993 |
-
|
994 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
995 |
)
|
996 |
|
997 |
print(f'Total processing time: {(reqtime.time() - start_time):.2f} sec')
|
998 |
print('*' * 70)
|
999 |
|
1000 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1001 |
|
1002 |
# =================================================================================================
|
1003 |
# === Gradio UI Setup ===
|
@@ -1016,40 +1261,38 @@ def update_ui_visibility(transcription_method, soundfont_choice):
|
|
1016 |
}
|
1017 |
|
1018 |
# --- Function to apply 8-bit synthesizer presets ---
|
|
|
1019 |
def apply_8bit_preset(preset_name):
|
1020 |
"""
|
1021 |
Takes the name of a preset and returns a dictionary of gr.update objects
|
1022 |
-
to set the values of the 8-bit synthesizer's UI components.
|
1023 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1024 |
# If the user selects "Custom" or the preset is not found, do not change the values.
|
1025 |
if preset_name == "Custom" or preset_name not in S8BIT_PRESETS:
|
1026 |
-
return
|
1027 |
-
|
1028 |
-
s8bit_pulse_width: gr.update(),
|
1029 |
-
s8bit_envelope_type: gr.update(),
|
1030 |
-
s8bit_decay_time_s: gr.update(),
|
1031 |
-
s8bit_vibrato_rate: gr.update(),
|
1032 |
-
s8bit_vibrato_depth: gr.update(),
|
1033 |
-
s8bit_smooth_notes: gr.update(),
|
1034 |
-
s8bit_continuous_vibrato: gr.update(),
|
1035 |
-
s8bit_bass_boost_level: gr.update()
|
1036 |
-
}
|
1037 |
|
1038 |
# Get the settings dictionary for the chosen preset.
|
1039 |
settings = S8BIT_PRESETS[preset_name]
|
1040 |
|
1041 |
-
#
|
1042 |
-
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
}
|
1053 |
|
1054 |
if __name__ == "__main__":
|
1055 |
# Initialize the app: download model (if needed) and apply patches
|
@@ -1066,146 +1309,268 @@ if __name__ == "__main__":
|
|
1066 |
print("\nWARNING: No SoundFonts were found or could be downloaded.")
|
1067 |
print("Rendering with SoundFonts will fail. Only the 8-bit synthesizer will be available.")
|
1068 |
|
|
|
|
|
|
|
|
|
1069 |
# --- Data structure for 8-bit synthesizer presets ---
|
1070 |
# Comprehensive preset dictionary with new FX parameters for all presets
|
1071 |
# Comprehensive preset dictionary including new JRPG and Handheld classics
|
1072 |
# Note: Vibrato depth is mapped to a representative value on the 0-50 Hz slider.
|
1073 |
S8BIT_PRESETS = {
|
1074 |
# --- Rhythmic & Action ---
|
1075 |
-
"Rhythm Pop Lead": {
|
1076 |
# Description: A clean, round square wave perfect for the snappy, catchy feel of rhythm games.
|
1077 |
-
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.18,
|
1078 |
-
'
|
|
|
|
|
|
|
|
|
1079 |
},
|
1080 |
-
"Arcade Brawler Lead": {
|
1081 |
# Description: A gritty sawtooth lead with a hard attack, capturing the high-energy feel of classic fighting games.
|
1082 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.15,
|
1083 |
-
'
|
|
|
|
|
|
|
|
|
1084 |
},
|
1085 |
-
"Mega Man (Rockman)": {
|
1086 |
# Description: A thin, sharp square wave lead with fast vibrato, iconic for its driving, heroic melodies.
|
1087 |
-
'waveform_type': 'Square', 'pulse_width': 0.2, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.15,
|
1088 |
-
'
|
|
|
|
|
|
|
|
|
1089 |
},
|
1090 |
-
"Kirby's Bubbly Melody": {
|
1091 |
# Description: A soft, round square wave with a bouncy vibrato, creating a cheerful and adorable sound.
|
1092 |
-
'waveform_type': 'Square', 'pulse_width': 0.4, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.2,
|
1093 |
-
'
|
|
|
|
|
|
|
|
|
1094 |
},
|
1095 |
-
"Mario (Super Mario Bros)": {
|
1096 |
# Description: A bright square wave with a per-note vibrato, producing the classic bouncy platformer sound.
|
1097 |
-
'waveform_type': 'Square', 'pulse_width': 0.3, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.25,
|
1098 |
-
'
|
|
|
|
|
|
|
|
|
1099 |
},
|
1100 |
# --- Epic & Atmospheric ---
|
1101 |
-
"Mecha & Tactics Brass": {
|
1102 |
# Description: A powerful, sustained sawtooth emulating the bold, heroic synth-brass of strategy and mecha anime themes.
|
1103 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1104 |
-
'
|
|
|
|
|
|
|
|
|
1105 |
},
|
1106 |
-
"Mystic Mana Pad": {
|
1107 |
-
|
1108 |
-
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.5,
|
1109 |
-
'
|
|
|
|
|
|
|
|
|
1110 |
},
|
1111 |
-
"Dragon Quest (
|
1112 |
# Description: A pure triangle wave with a long decay, mimicking the grand, orchestral feel of a classical flute or string section.
|
1113 |
-
|
1114 |
-
'
|
|
|
|
|
|
|
|
|
1115 |
},
|
1116 |
-
"ONI V (Wafu Mystic)": {
|
1117 |
# Description: A solemn triangle wave with a slow, expressive vibrato, evoking the mysterious atmosphere of Japanese folklore.
|
1118 |
-
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1119 |
-
'
|
|
|
|
|
|
|
|
|
1120 |
},
|
1121 |
-
"Zelda (
|
1122 |
# Description: The classic pure triangle wave lead, perfect for heroic and adventurous overworld themes.
|
1123 |
-
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.3,
|
1124 |
-
'
|
|
|
|
|
|
|
|
|
1125 |
},
|
1126 |
# --- JRPG & System Classics ---
|
1127 |
-
"Falcom Ys (
|
1128 |
# Description: A powerful sawtooth with slight distortion, emulating the driving rock organ and guitar leads of action JRPGs.
|
1129 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.15,
|
1130 |
-
'
|
|
|
|
|
|
|
|
|
1131 |
},
|
1132 |
-
"Final Fantasy (
|
1133 |
# Description: A perfect, clean square wave with zero vibrato, creating the iconic, crystal-clear arpeggio sound.
|
1134 |
-
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.22,
|
1135 |
-
'
|
|
|
|
|
|
|
|
|
1136 |
},
|
1137 |
-
"Castlevania (Akumajō Dracula)": {
|
1138 |
# Description: A sharp square wave with dramatic vibrato, ideal for fast, gothic, and baroque-inspired melodies.
|
1139 |
-
'waveform_type': 'Square', 'pulse_width': 0.25, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.18,
|
1140 |
-
'
|
|
|
|
|
|
|
|
|
1141 |
},
|
1142 |
-
"Pokémon (Game Boy Classics)": {
|
1143 |
# Description: A full, friendly square wave sound, capturing the cheerful and adventurous spirit of early handheld RPGs.
|
1144 |
-
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.22,
|
1145 |
-
'
|
|
|
|
|
|
|
|
|
1146 |
},
|
1147 |
# --- Advanced System Impressions ---
|
1148 |
"Commodore 64 (SID Feel)": {
|
1149 |
# Description: (Impression) Uses high-speed, shallow vibrato to mimic the characteristic "buzzy" texture of the SID chip's PWM.
|
1150 |
-
'waveform_type': 'Square', 'pulse_width': 0.25, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.25,
|
1151 |
-
'
|
|
|
|
|
|
|
|
|
1152 |
},
|
1153 |
"Megadrive/Genesis (FM Grit)": {
|
1154 |
# Description: (Impression) Uses FM, distortion, and noise to capture the gritty, metallic, and aggressive tone of the YM2612 chip.
|
1155 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.18,
|
1156 |
-
'
|
|
|
|
|
|
|
|
|
1157 |
},
|
1158 |
-
"PC-98 (Touhou Feel)": {
|
1159 |
# Description: (Impression) A very sharp square wave with fast FM, emulating the bright, high-energy leads of Japanese PC games.
|
1160 |
-
'waveform_type': 'Square', 'pulse_width': 0.15, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.12,
|
1161 |
-
'
|
|
|
|
|
|
|
|
|
1162 |
},
|
1163 |
"Roland SC-88 (GM Vibe)": {
|
1164 |
# Description: (Impression) A clean, stable triangle wave with no effects, mimicking the polished, sample-based sounds of General MIDI.
|
1165 |
-
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.35,
|
1166 |
-
'
|
|
|
|
|
|
|
|
|
1167 |
},
|
1168 |
# --- Experimental & Sound FX ---
|
1169 |
"Sci-Fi Energy Field": {
|
1170 |
# Description: (SFX) High-speed vibrato and noise create a constant, shimmering hum suitable for energy shields or force fields.
|
1171 |
-
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1172 |
-
'
|
|
|
|
|
|
|
|
|
1173 |
},
|
1174 |
"Industrial Alarm": {
|
1175 |
# Description: (SFX) Extreme vibrato rate on a sawtooth wave produces a harsh, metallic, dissonant alarm sound.
|
1176 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.2,
|
1177 |
-
'
|
|
|
|
|
|
|
|
|
1178 |
},
|
1179 |
"Laser Charge-Up": {
|
1180 |
# Description: (SFX) Extreme vibrato depth creates a dramatic, rising pitch effect, perfect for sci-fi weapon sounds.
|
1181 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.3,
|
1182 |
-
'
|
|
|
|
|
|
|
|
|
1183 |
},
|
1184 |
"Unstable Machine Core": {
|
1185 |
# Description: (SFX) Maximum depth and distortion create a chaotic, atonal noise, simulating a machine on the verge of exploding.
|
1186 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.5,
|
1187 |
-
'
|
|
|
|
|
|
|
|
|
1188 |
},
|
1189 |
"Hardcore Gabber Kick": {
|
1190 |
# Description: (Experimental) Maximum bass boost and distortion create an overwhelmingly powerful, clipped kick drum sound.
|
1191 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.1,
|
1192 |
-
'
|
|
|
|
|
|
|
|
|
1193 |
},
|
1194 |
# --- Utility ---
|
1195 |
"Generic Chiptune Loop": {
|
1196 |
# Description: A well-balanced, pleasant square wave lead that serves as a great starting point for custom sounds.
|
1197 |
-
'waveform_type': 'Square', 'pulse_width': 0.25, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.2,
|
1198 |
-
'
|
|
|
|
|
|
|
|
|
1199 |
},
|
1200 |
-
"Dark/Boss Atmosphere": {
|
1201 |
-
# Description: An aggressive sawtooth
|
1202 |
-
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.35,
|
1203 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1204 |
}
|
1205 |
}
|
1206 |
|
1207 |
app = gr.Blocks(theme=gr.themes.Base())
|
1208 |
-
|
1209 |
with app:
|
1210 |
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Audio-to-MIDI & Advanced Renderer</h1>")
|
1211 |
gr.Markdown(
|
@@ -1348,10 +1713,12 @@ if __name__ == "__main__":
|
|
1348 |
# - High: Creates fast modulation, resulting in bright, complex, often metallic harmonics and sidebands.
|
1349 |
# =================================================================================
|
1350 |
#
|
|
|
|
|
1351 |
with gr.Accordion("8-bit Synthesizer Settings", open=False, visible=False) as synth_8bit_settings:
|
1352 |
-
# ---
|
1353 |
s8bit_preset_selector = gr.Dropdown(
|
1354 |
-
choices=["Custom"] + list(S8BIT_PRESETS.keys()),
|
1355 |
value="Custom",
|
1356 |
label="Style Preset",
|
1357 |
info="Select a preset to auto-fill the settings below. Choose 'Custom' for manual control.\nFor reference and entertainment only. These presets are not guaranteed to be perfectly accurate."
|
@@ -1360,20 +1727,20 @@ if __name__ == "__main__":
|
|
1360 |
s8bit_waveform_type = gr.Dropdown(['Square', 'Sawtooth', 'Triangle'], value='Square', label="Waveform Type")
|
1361 |
s8bit_pulse_width = gr.Slider(0.01, 0.99, value=0.5, step=0.01, label="Pulse Width (Square Wave Only)")
|
1362 |
s8bit_envelope_type = gr.Dropdown(['Plucky (AD Envelope)', 'Sustained (Full Decay)'], value='Plucky (AD Envelope)', label="Envelope Type")
|
1363 |
-
s8bit_decay_time_s = gr.Slider(0.01, 0.6, value=0.1, step=0.01, label="Decay Time (s)")
|
1364 |
s8bit_vibrato_rate = gr.Slider(0, 20, value=5, label="Vibrato Rate (Hz)")
|
1365 |
s8bit_vibrato_depth = gr.Slider(0, 50, value=0, label="Vibrato Depth (Hz)")
|
1366 |
-
s8bit_bass_boost_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.
|
1367 |
-
|
1368 |
-
|
1369 |
-
|
1370 |
# --- New accordion for advanced effects ---
|
1371 |
with gr.Accordion("Advanced Synthesis & FX", open=False):
|
1372 |
s8bit_noise_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="Noise Level", info="Mixes in white noise. Great for percussion or adding 'air'.")
|
1373 |
s8bit_distortion_level = gr.Slider(minimum=0.0, maximum=0.9, value=0.0, step=0.05, label="Distortion Level", info="Applies wave-shaping distortion for a grittier, harsher sound.")
|
1374 |
s8bit_fm_modulation_depth = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="FM Depth", info="Depth of Frequency Modulation. Creates complex, metallic, or bell-like tones.")
|
1375 |
s8bit_fm_modulation_rate = gr.Slider(minimum=0.0, maximum=500.0, value=0.0, step=1.0, label="FM Rate", info="Rate of Frequency Modulation. Higher values create brighter, more complex harmonics.")
|
1376 |
-
|
1377 |
# --- Original Advanced Options (Now tied to Piano-Specific) ---
|
1378 |
with gr.Accordion("Advanced MIDI Rendering Options", open=False) as advanced_rendering_options:
|
1379 |
render_with_sustains = gr.Checkbox(label="Apply sustain pedal effects (if present)", value=True)
|
@@ -1404,38 +1771,44 @@ if __name__ == "__main__":
|
|
1404 |
output_midi_summary = gr.Textbox(label="MIDI metadata summary", lines=4)
|
1405 |
|
1406 |
# Define all input components for the click event, excluding the preset selector which is not a direct input to the final processing.
|
|
|
|
|
1407 |
all_inputs = [
|
1408 |
-
input_file,
|
1409 |
-
|
1410 |
-
|
1411 |
-
|
1412 |
-
|
1413 |
-
|
1414 |
-
render_with_sustains, merge_misaligned_notes, custom_render_patch, render_align,
|
1415 |
-
render_transpose_value, render_transpose_to_C4, render_output_as_solo_piano, render_remove_drums,
|
1416 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
1417 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth, s8bit_bass_boost_level,
|
1418 |
-
|
1419 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
1420 |
]
|
1421 |
-
|
|
|
|
|
1422 |
output_midi_md5, output_midi_title, output_midi_summary,
|
1423 |
output_midi, output_audio, output_plot, output_song_description
|
1424 |
]
|
1425 |
-
|
1426 |
-
#
|
1427 |
-
|
1428 |
-
|
1429 |
-
s8bit_decay_time_s, s8bit_vibrato_rate,
|
1430 |
-
|
|
|
1431 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
1432 |
]
|
1433 |
|
1434 |
-
#
|
|
|
|
|
|
|
1435 |
submit_btn.click(
|
1436 |
process_and_render_file,
|
1437 |
inputs=all_inputs,
|
1438 |
-
outputs=all_outputs
|
1439 |
)
|
1440 |
|
1441 |
# --- Listeners for dynamic UI updates ---
|
@@ -1450,14 +1823,16 @@ if __name__ == "__main__":
|
|
1450 |
outputs=[general_transcription_settings, synth_8bit_settings]
|
1451 |
)
|
1452 |
|
|
|
1453 |
# --- Event listener for the preset selector ---
|
1454 |
# When the preset dropdown changes, it calls the `apply_8bit_preset` function.
|
1455 |
# The input to the function is the selected preset name.
|
1456 |
# The outputs are all the individual 8-bit setting components that need to be updated.
|
|
|
1457 |
s8bit_preset_selector.change(
|
1458 |
fn=apply_8bit_preset,
|
1459 |
inputs=[s8bit_preset_selector],
|
1460 |
-
outputs=
|
1461 |
)
|
1462 |
|
1463 |
|
|
|
166 |
# =================================================================================================
|
167 |
def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s, pulse_width,
|
168 |
vibrato_rate, vibrato_depth, bass_boost_level, fs=44100,
|
169 |
+
smooth_notes_level=0.0, continuous_vibrato_level=0.0,
|
170 |
+
noise_level=0.0, distortion_level=0.0,
|
171 |
+
fm_modulation_depth=0.0, fm_modulation_rate=0.0):
|
172 |
"""
|
173 |
Synthesizes an 8-bit style audio waveform from a PrettyMIDI object.
|
174 |
This function generates waveforms manually instead of using a synthesizer like FluidSynth.
|
175 |
Includes an optional sub-octave bass booster with adjustable level.
|
176 |
Instruments are panned based on their order in the MIDI file.
|
177 |
Instrument 1 -> Left, Instrument 2 -> Right.
|
178 |
+
Now supports graded levels for smoothing and vibrato continuity.
|
179 |
"""
|
180 |
total_duration = midi_data.get_end_time()
|
181 |
# Initialize a stereo waveform buffer (2 channels: Left, Right)
|
|
|
215 |
|
216 |
t = np.arange(num_samples) / fs
|
217 |
|
218 |
+
# --- Graded Continuous Vibrato ---
|
219 |
+
# This now interpolates between a fully reset vibrato and a fully continuous one.
|
220 |
+
# Use accumulated phase to avoid vibrato reset per note
|
221 |
+
vib_phase_inc = 2 * np.pi * vibrato_rate / fs
|
222 |
+
per_note_vib_phase = 2 * np.pi * vibrato_rate * t
|
223 |
+
continuous_vib_phase = vibrato_phase + np.arange(num_samples) * vib_phase_inc
|
224 |
+
|
225 |
+
# Weighted average of the two phase types
|
226 |
+
final_vib_phase = (
|
227 |
+
per_note_vib_phase * (1 - continuous_vibrato_level) +
|
228 |
+
continuous_vib_phase * continuous_vibrato_level
|
229 |
+
)
|
230 |
+
vibrato_lfo = vibrato_depth * np.sin(final_vib_phase)
|
231 |
+
|
232 |
+
# Update the global vibrato phase for the next note
|
233 |
+
if num_samples > 0:
|
234 |
+
vibrato_phase = (continuous_vib_phase[-1] + vib_phase_inc) % (2 * np.pi)
|
235 |
+
|
236 |
+
# --- Waveform Generation with FM ---
|
237 |
+
fm_lfo = fm_modulation_depth * np.sin(2 * np.pi * fm_modulation_rate * t)
|
238 |
+
modulated_freq = freq * (1 + fm_lfo)
|
239 |
+
|
240 |
# --- Waveform Generation (Main Oscillator with phase continuity) ---
|
241 |
+
phase_inc = 2 * np.pi * (modulated_freq + vibrato_lfo) / fs
|
242 |
phase = osc_phase[i] + np.cumsum(phase_inc)
|
243 |
+
if num_samples > 0:
|
244 |
+
osc_phase[i] = phase[-1] % (2 * np.pi) # Store last phase
|
245 |
|
246 |
if waveform_type == 'Square':
|
247 |
note_waveform = signal.square(phase, duty=pulse_width)
|
248 |
elif waveform_type == 'Sawtooth':
|
249 |
note_waveform = signal.sawtooth(phase)
|
250 |
+
else: # Triangle
|
251 |
note_waveform = signal.sawtooth(phase, width=0.5)
|
252 |
|
253 |
# --- Bass Boost (Sub-Octave Oscillator) ---
|
|
|
264 |
main_level = 1.0 - (0.5 * bass_boost_level)
|
265 |
note_waveform = (note_waveform * main_level) + (bass_sub_waveform * bass_boost_level)
|
266 |
|
267 |
+
# --- Noise & Distortion Simulation (White Noise) ---
|
268 |
if noise_level > 0:
|
269 |
+
note_waveform += np.random.uniform(-1, 1, num_samples) * noise_level
|
|
|
270 |
|
271 |
# --- Distortion (Wave Shaping) ---
|
272 |
if distortion_level > 0:
|
273 |
+
# Using a tanh function for a smoother, "warmer" distortion
|
274 |
+
note_waveform = np.tanh(note_waveform * (1 + distortion_level * 5))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
|
276 |
# --- ADSR Envelope ---
|
277 |
start_amp = note.velocity / 127.0
|
278 |
envelope = np.zeros(num_samples)
|
279 |
|
280 |
if envelope_type == 'Plucky (AD Envelope)':
|
281 |
+
attack_samples = min(int(0.005 * fs), num_samples)
|
|
|
282 |
decay_samples = min(int(decay_time_s * fs), num_samples - attack_samples)
|
283 |
|
284 |
envelope[:attack_samples] = np.linspace(0, start_amp, attack_samples)
|
285 |
if decay_samples > 0:
|
286 |
envelope[attack_samples:attack_samples+decay_samples] = np.linspace(start_amp, 0, decay_samples)
|
287 |
+
else: # Sustained
|
288 |
envelope = np.linspace(start_amp, 0, num_samples)
|
289 |
|
290 |
+
# --- Graded Note Smoothing ---
|
291 |
+
# The level controls the length of the fade in/out. Max fade is 10ms.
|
292 |
+
if smooth_notes_level > 0 and num_samples > 10:
|
293 |
+
fade_length = int(fs * 0.01 * smooth_notes_level)
|
294 |
+
fade_samples = min(fade_length, num_samples // 2)
|
295 |
+
if fade_samples > 0:
|
296 |
+
envelope[:fade_samples] *= np.linspace(0.5, 1.0, fade_samples)
|
297 |
+
envelope[-fade_samples:] *= np.linspace(1.0, 0.0, fade_samples)
|
298 |
|
299 |
# Apply envelope to the (potentially combined) waveform
|
300 |
note_waveform *= envelope
|
|
|
575 |
# --- 8-bit synth params ---
|
576 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
577 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
578 |
+
s8bit_bass_boost_level, s8bit_smooth_notes_level, s8bit_continuous_vibrato_level,
|
579 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
580 |
):
|
581 |
"""
|
|
|
802 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
803 |
s8bit_bass_boost_level,
|
804 |
fs=srate,
|
805 |
+
smooth_notes_level=s8bit_smooth_notes_level,
|
806 |
+
continuous_vibrato_level=s8bit_continuous_vibrato_level,
|
807 |
noise_level=s8bit_noise_level,
|
808 |
distortion_level=s8bit_distortion_level,
|
809 |
fm_modulation_depth=s8bit_fm_modulation_depth,
|
|
|
858 |
|
859 |
return new_md5_hash, fn1, output_midi_summary, midi_to_render_path, (srate, audio_out), output_plot, song_description
|
860 |
|
861 |
+
|
862 |
+
def analyze_midi_features(midi_data):
|
863 |
+
"""
|
864 |
+
Analyzes a PrettyMIDI object to extract musical features for parameter recommendation.
|
865 |
+
|
866 |
+
Args:
|
867 |
+
midi_data (pretty_midi.PrettyMIDI): The MIDI data to analyze.
|
868 |
+
|
869 |
+
Returns:
|
870 |
+
dict or None: A dictionary containing features, or None if the MIDI is empty.
|
871 |
+
Features: 'note_count', 'instruments_count', 'duration',
|
872 |
+
'note_density', 'avg_velocity', 'pitch_range'.
|
873 |
+
"""
|
874 |
+
all_notes = [note for instrument in midi_data.instruments for note in instrument.notes]
|
875 |
+
note_count = len(all_notes)
|
876 |
+
|
877 |
+
# Return None if the MIDI file has no notes to analyze.
|
878 |
+
if note_count == 0:
|
879 |
+
return None
|
880 |
+
|
881 |
+
duration = midi_data.get_end_time()
|
882 |
+
# Avoid division by zero for empty-duration MIDI files.
|
883 |
+
if duration == 0:
|
884 |
+
note_density = 0
|
885 |
+
else:
|
886 |
+
note_density = note_count / duration
|
887 |
+
|
888 |
+
# --- Calculate new required features ---
|
889 |
+
avg_velocity = sum(note.velocity for note in all_notes) / note_count
|
890 |
+
avg_pitch = sum(note.pitch for note in all_notes) / note_count
|
891 |
+
avg_note_length = sum(note.end - note.start for note in all_notes) / note_count
|
892 |
+
|
893 |
+
# Calculate pitch range
|
894 |
+
if note_count > 1:
|
895 |
+
min_pitch = min(note.pitch for note in all_notes)
|
896 |
+
max_pitch = max(note.pitch for note in all_notes)
|
897 |
+
pitch_range = max_pitch - min_pitch
|
898 |
+
else:
|
899 |
+
pitch_range = 0
|
900 |
+
|
901 |
+
return {
|
902 |
+
'note_count': note_count,
|
903 |
+
'instruments_count': len(midi_data.instruments),
|
904 |
+
'duration': duration,
|
905 |
+
'note_density': note_density, # Notes per second
|
906 |
+
'avg_velocity': avg_velocity,
|
907 |
+
'pitch_range': pitch_range, # In semitones
|
908 |
+
'avg_pitch': avg_pitch,
|
909 |
+
'avg_note_length': avg_note_length,
|
910 |
+
}
|
911 |
+
|
912 |
+
def determine_waveform_type(features):
|
913 |
+
"""
|
914 |
+
Determines the best waveform type based on analyzed MIDI features.
|
915 |
+
- Square: Best for most general-purpose, bright melodies.
|
916 |
+
- Sawtooth: Best for intense, heavy, or powerful leads and basses.
|
917 |
+
- Triangle: Best for soft, gentle basses or flute-like sounds.
|
918 |
+
|
919 |
+
Args:
|
920 |
+
features (dict): The dictionary of features from analyze_midi_features.
|
921 |
+
|
922 |
+
Returns:
|
923 |
+
str: The recommended waveform type ('Square', 'Sawtooth', or 'Triangle').
|
924 |
+
"""
|
925 |
+
# 1. Check for conditions that strongly suggest a Triangle wave (soft bassline)
|
926 |
+
# MIDI Pitch 52 is ~G#3. If the average pitch is below this, it's likely a bass part.
|
927 |
+
# If notes are long and the pitch range is narrow, it confirms a simple, melodic bassline.
|
928 |
+
if features['avg_pitch'] <= 52 and features['avg_note_length'] >= 0.3 and features['pitch_range'] < 12:
|
929 |
+
return "Triangle"
|
930 |
+
|
931 |
+
# 2. Check for conditions that suggest a Sawtooth wave (intense/complex part)
|
932 |
+
# High note density or a very wide pitch range often indicates an aggressive lead or a complex solo.
|
933 |
+
# The sawtooth's rich harmonics are perfect for this.
|
934 |
+
if features['note_density'] >= 6 or features['pitch_range'] >= 18:
|
935 |
+
return "Sawtooth"
|
936 |
+
|
937 |
+
# 3. Default to the most versatile waveform: Square
|
938 |
+
return "Square"
|
939 |
+
|
940 |
+
def recommend_8bit_params(midi_data, default_preset):
|
941 |
+
"""
|
942 |
+
Recommends 8-bit synthesizer parameters using a unified, factor-based model.
|
943 |
+
This "AI" generates a sound profile based on normalized musical features.
|
944 |
+
|
945 |
+
Args:
|
946 |
+
midi_data (pretty_midi.PrettyMIDI): The MIDI data to analyze.
|
947 |
+
default_preset (dict): A fallback preset if analysis fails.
|
948 |
+
|
949 |
+
Returns:
|
950 |
+
dict: A dictionary of recommended synthesizer parameters.
|
951 |
+
"""
|
952 |
+
features = analyze_midi_features(midi_data)
|
953 |
+
if features is None:
|
954 |
+
# Return a default preset if MIDI is empty or cannot be analyzed
|
955 |
+
return default_preset
|
956 |
+
|
957 |
+
# --- Rule-based Parameter Recommendation ---
|
958 |
+
params = {}
|
959 |
+
|
960 |
+
# --- 1. Core Timbre Selection ---
|
961 |
+
# Intelligent Waveform Selection
|
962 |
+
params['waveform_type'] = determine_waveform_type(features)
|
963 |
+
# Determine pulse width *after* knowing the waveform.
|
964 |
+
# This only applies if the waveform is Square.
|
965 |
+
if params['waveform_type'] == 'Square':
|
966 |
+
# For Square waves, use pitch complexity to decide pulse width.
|
967 |
+
# Complex melodies get a thinner sound (0.3) for clarity.
|
968 |
+
# Simpler melodies get a fuller sound (0.5).
|
969 |
+
params['pulse_width'] = 0.3 if features['pitch_range'] > 30 else 0.5
|
970 |
+
else:
|
971 |
+
# For Sawtooth or Triangle, pulse width is not applicable. Set a default.
|
972 |
+
params['pulse_width'] = 0.5
|
973 |
+
|
974 |
+
# --- 2. Envelope and Rhythm ---
|
975 |
+
# Determine envelope type based on note density
|
976 |
+
is_plucky = features['note_density'] > 10
|
977 |
+
params['envelope_type'] = 'Plucky (AD Envelope)' if is_plucky else 'Sustained (Full Decay)'
|
978 |
+
params['decay_time_s'] = 0.15 if is_plucky else 0.4
|
979 |
+
|
980 |
+
# --- 3. Modulation (Vibrato) ---
|
981 |
+
# Vibrato depth and rate based on velocity and density
|
982 |
+
params['vibrato_depth'] = min(max((features['avg_velocity'] - 60) / 20, 0), 10) # More velocity = more depth
|
983 |
+
if features['note_density'] > 12:
|
984 |
+
params['vibrato_rate'] = 7.0 # Very fast music -> frantic vibrato
|
985 |
+
elif features['note_density'] > 6:
|
986 |
+
params['vibrato_rate'] = 5.0 # Moderately fast music -> standard vibrato
|
987 |
+
else:
|
988 |
+
params['vibrato_rate'] = 3.0 # Slow music -> gentle vibrato
|
989 |
+
|
990 |
+
# --- 4. Progressive/Graded Parameters using Normalization ---
|
991 |
+
|
992 |
+
# Smooth notes level (0.0 to 1.0): More smoothing for denser passages.
|
993 |
+
# Effective range: 3 to 8 notes/sec.
|
994 |
+
params['smooth_notes_level'] = min(max((features['note_density'] - 3) / 5.0, 0.0), 1.0) # Smoothen notes in denser passages
|
995 |
+
|
996 |
+
# Continuous vibrato level (0.0 to 1.0): Less dense passages get more lyrical, continuous vibrato.
|
997 |
+
# Effective range: 5 to 10 notes/sec. (Inverted)
|
998 |
+
params['continuous_vibrato_level'] = 1.0 - min(max((features['note_density'] - 5) / 5.0, 0.0), 1.0) # Lyrical (less dense) music gets connected vibrato
|
999 |
+
|
1000 |
+
# Noise level (0.0 to 0.1): Higher velocity passages get more "air" or "grit".
|
1001 |
+
# Effective range: velocity 50 to 90.
|
1002 |
+
params['noise_level'] = min(max((features['avg_velocity'] - 50) / 40.0, 0.0), 1.0) * 0.1
|
1003 |
+
|
1004 |
+
# Distortion level (0.0 to 0.1): Shorter notes get more distortion for punch.
|
1005 |
+
# Effective range: note length 0.5s down to 0.25s. (Inverted)
|
1006 |
+
if features['avg_note_length'] < 0.25: # Short, staccato notes
|
1007 |
+
params['distortion_level'] = 0.1
|
1008 |
+
elif features['avg_note_length'] < 0.5: # Medium length notes
|
1009 |
+
params['distortion_level'] = 0.05
|
1010 |
+
else: # Long, sustained notes
|
1011 |
+
params['distortion_level'] = 0.0
|
1012 |
+
|
1013 |
+
# Progressive FM modulation based on a combined complexity factor.
|
1014 |
+
# Normalizes note density and pitch range to a 0-1 scale.
|
1015 |
+
density_factor = min(max((features['note_density'] - 5) / 15, 0), 1) # Effective range 5-20 notes/sec
|
1016 |
+
range_factor = min(max((features['pitch_range'] - 15) / 30, 0), 1) # Effective range 15-45 semitones
|
1017 |
+
|
1018 |
+
# The overall complexity is the average of these two factors.
|
1019 |
+
complexity_factor = (density_factor + range_factor) / 2
|
1020 |
+
params['fm_modulation_depth'] = round(0.3 * complexity_factor, 3)
|
1021 |
+
params['fm_modulation_rate'] = round(200 * complexity_factor, 1)
|
1022 |
+
|
1023 |
+
# Non-linear bass boost
|
1024 |
+
# REFINED LOGIC: Non-linear bass boost based on instrument count.
|
1025 |
+
# More instruments lead to less bass boost to avoid a muddy mix,
|
1026 |
+
# while solo or duo arrangements get a significant boost to sound fuller.
|
1027 |
+
# The boost level has a floor of 0.2 and a ceiling of 1.0.
|
1028 |
+
params['bass_boost_level'] = max(0.2, 1.0 - (features['instruments_count'] - 1) * 0.15)
|
1029 |
+
|
1030 |
+
# Round all float values for cleaner output
|
1031 |
+
for key, value in params.items():
|
1032 |
+
if isinstance(value, float):
|
1033 |
+
params[key] = round(value, 3)
|
1034 |
+
|
1035 |
+
return params
|
1036 |
+
|
1037 |
+
|
1038 |
# =================================================================================================
|
1039 |
# === Main Application Logic ===
|
1040 |
# =================================================================================================
|
1041 |
|
1042 |
+
def process_and_render_file(input_file,
|
1043 |
+
# --- Pass the preset selector value ---
|
1044 |
+
s8bit_preset_selector,
|
1045 |
# --- Transcription params ---
|
1046 |
enable_stereo_processing,
|
1047 |
transcription_method,
|
|
|
1053 |
# --- 8-bit synth params ---
|
1054 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
1055 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
1056 |
+
s8bit_bass_boost_level, s8bit_smooth_notes_level, s8bit_continuous_vibrato_level,
|
1057 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
1058 |
):
|
1059 |
"""
|
|
|
1062 |
"""
|
1063 |
start_time = reqtime.time()
|
1064 |
if input_file is None:
|
1065 |
+
# Return a list of updates to clear all output fields and UI controls
|
1066 |
+
return [gr.update(value=None)] * (7 + 13) # 7 results + 13 synth controls
|
1067 |
|
1068 |
# The input_file from gr.Audio(type="filepath") is now the direct path (a string),
|
1069 |
# not a temporary file object. We no longer need to access the .name attribute.
|
|
|
1072 |
print(f"Processing new file: {filename}")
|
1073 |
|
1074 |
try:
|
1075 |
+
# Mono=False is required to correctly detect stereo channels
|
1076 |
audio_data, native_sample_rate = librosa.load(input_file_path, sr=None, mono=False)
|
1077 |
except Exception as e:
|
1078 |
+
# If loading fails, it might be a MIDI file, which librosa cannot handle.
|
1079 |
+
# We will proceed, assuming it's a MIDI, and let pretty_midi handle it later.
|
1080 |
+
print(f"Could not load as audio: {e}. Assuming it is a MIDI file.")
|
1081 |
+
pass
|
1082 |
|
1083 |
# --- Step 1: Check file type and transcribe if necessary ---
|
1084 |
if filename.lower().endswith(('.mid', '.midi', '.kar')):
|
|
|
1093 |
|
1094 |
# === STEREO PROCESSING LOGIC ===
|
1095 |
if enable_stereo_processing:
|
1096 |
+
if 'audio_data' not in locals() or audio_data.ndim != 2 or audio_data.shape[0] != 2:
|
1097 |
print("Warning: Audio is not stereo or could not be loaded as stereo. Falling back to mono transcription.")
|
1098 |
enable_stereo_processing = False # Disable stereo processing if audio is not stereo
|
1099 |
|
|
|
1115 |
print(f"Saved left channel to: {temp_left_wav_path}")
|
1116 |
print(f"Saved right channel to: {temp_right_wav_path}")
|
1117 |
|
1118 |
+
print("Transcribing left and right channel...")
|
1119 |
if transcription_method == "General Purpose":
|
1120 |
midi_path_left = TranscribeGeneralAudio(temp_left_wav_path, onset_thresh, frame_thresh, min_note_len, min_freq, max_freq, infer_onsets_bool, melodia_trick_bool, multiple_bends_bool)
|
|
|
|
|
|
|
|
|
|
|
1121 |
midi_path_right = TranscribeGeneralAudio(temp_right_wav_path, onset_thresh, frame_thresh, min_note_len, min_freq, max_freq, infer_onsets_bool, melodia_trick_bool, multiple_bends_bool)
|
1122 |
else:
|
1123 |
+
midi_path_left = TranscribePianoAudio(temp_left_wav_path)
|
1124 |
midi_path_right = TranscribePianoAudio(temp_right_wav_path)
|
1125 |
|
1126 |
if midi_path_left and midi_path_right:
|
|
|
1138 |
except Exception as e:
|
1139 |
print(f"An error occurred during stereo processing: {e}")
|
1140 |
raise gr.Error(f"Stereo Processing Failed: {e}")
|
1141 |
+
else: # Standard mono transcription
|
1142 |
print("Stereo processing disabled. Using standard mono transcription.")
|
1143 |
+
if 'audio_data' in locals():
|
1144 |
+
if audio_data.ndim == 1:
|
1145 |
+
mono_signal = audio_data
|
1146 |
+
else:
|
1147 |
+
mono_signal = np.mean(audio_data, axis=0)
|
|
|
1148 |
|
1149 |
+
normalized_mono = normalize_loudness(mono_signal, native_sample_rate)
|
1150 |
+
|
1151 |
+
temp_mono_wav_path = os.path.join(temp_dir, f"{base_name}_mono.wav")
|
1152 |
+
sf.write(temp_mono_wav_path, normalized_mono, native_sample_rate)
|
1153 |
+
input_file_path = temp_mono_wav_path # Use the normalized mono file for transcription
|
1154 |
|
1155 |
try:
|
1156 |
if transcription_method == "General Purpose":
|
1157 |
+
midi_path_for_rendering = TranscribeGeneralAudio(input_file_path, onset_thresh, frame_thresh, min_note_len, min_freq, max_freq, infer_onsets_bool, melodia_trick_bool, multiple_bends_bool)
|
|
|
|
|
|
|
1158 |
else: # Piano-Specific
|
1159 |
+
midi_path_for_rendering = TranscribePianoAudio(input_file_path)
|
|
|
1160 |
except Exception as e:
|
1161 |
print(f"An error occurred during transcription: {e}")
|
1162 |
raise gr.Error(f"Transcription Failed: {e}")
|
1163 |
|
1164 |
# --- Step 2: Render the MIDI file with selected options ---
|
1165 |
+
|
1166 |
+
# --- Auto-Recommendation Logic ---
|
1167 |
+
# Store the original parameters from the UI sliders into a dictionary.
|
1168 |
+
# The keys in this dictionary match the keys returned by recommend_8bit_params.
|
1169 |
+
synth_params = {
|
1170 |
+
'waveform_type': s8bit_waveform_type, 'pulse_width': s8bit_pulse_width, 'envelope_type': s8bit_envelope_type,
|
1171 |
+
'decay_time_s': s8bit_decay_time_s, 'vibrato_rate': s8bit_vibrato_rate, 'vibrato_depth': s8bit_vibrato_depth,
|
1172 |
+
'bass_boost_level': s8bit_bass_boost_level, 'smooth_notes_level': s8bit_smooth_notes_level, 'continuous_vibrato_level': s8bit_continuous_vibrato_level,
|
1173 |
+
'noise_level': s8bit_noise_level, 'distortion_level': s8bit_distortion_level,
|
1174 |
+
'fm_modulation_depth': s8bit_fm_modulation_depth, 'fm_modulation_rate': s8bit_fm_modulation_rate,
|
1175 |
+
}
|
1176 |
+
|
1177 |
+
# This variable will hold the values to update the UI sliders
|
1178 |
+
ui_updates = {}
|
1179 |
+
|
1180 |
+
# If the user selected the auto-recommend option, override the parameters
|
1181 |
+
if s8bit_preset_selector == "Auto-Recommend (Analyze MIDI)":
|
1182 |
+
print("Auto-Recommendation is enabled. Analyzing MIDI features...")
|
1183 |
+
try:
|
1184 |
+
midi_to_analyze = pretty_midi.PrettyMIDI(midi_path_for_rendering)
|
1185 |
+
default_params = S8BIT_PRESETS[FALLBACK_PRESET_NAME]
|
1186 |
+
recommended_params = recommend_8bit_params(midi_to_analyze, default_params)
|
1187 |
+
|
1188 |
+
print("Recommended parameters:", recommended_params)
|
1189 |
+
# Both the synthesis parameters and the UI update values are set to the recommendations
|
1190 |
+
synth_params.update(recommended_params)
|
1191 |
+
ui_updates = recommended_params.copy() # Use a copy for UI updates
|
1192 |
+
except Exception as e:
|
1193 |
+
print(f"Could not auto-recommend parameters: {e}. Using default values from UI.")
|
1194 |
+
|
1195 |
print(f"Proceeding to render MIDI file: {os.path.basename(midi_path_for_rendering)}")
|
1196 |
+
|
1197 |
+
# --- Correctly pass parameters to Render_MIDI ---
|
1198 |
+
# The Render_MIDI function expects positional arguments, not keyword arguments.
|
1199 |
+
# We must unpack the values from our synth_params dictionary in the correct order.
|
1200 |
results = Render_MIDI(midi_path_for_rendering,
|
1201 |
render_type, soundfont_bank, render_sample_rate,
|
1202 |
render_with_sustains, merge_misaligned_notes, custom_render_patch, render_align,
|
1203 |
render_transpose_value, render_transpose_to_C4, render_output_as_solo_piano, render_remove_drums,
|
1204 |
+
# Unpack the values from the dictionary as positional arguments
|
1205 |
+
synth_params['waveform_type'],
|
1206 |
+
synth_params['envelope_type'],
|
1207 |
+
synth_params['decay_time_s'],
|
1208 |
+
synth_params['pulse_width'],
|
1209 |
+
synth_params['vibrato_rate'],
|
1210 |
+
synth_params['vibrato_depth'],
|
1211 |
+
synth_params['bass_boost_level'],
|
1212 |
+
synth_params['smooth_notes_level'],
|
1213 |
+
synth_params['continuous_vibrato_level'],
|
1214 |
+
synth_params['noise_level'],
|
1215 |
+
synth_params['distortion_level'],
|
1216 |
+
synth_params['fm_modulation_depth'],
|
1217 |
+
synth_params['fm_modulation_rate']
|
1218 |
)
|
1219 |
|
1220 |
print(f'Total processing time: {(reqtime.time() - start_time):.2f} sec')
|
1221 |
print('*' * 70)
|
1222 |
|
1223 |
+
# --- Prepare the final return value for Gradio ---
|
1224 |
+
|
1225 |
+
# This list defines the order of UI components to be updated.
|
1226 |
+
# IT MUST MATCH THE ORDER IN `s8bit_updater_outputs` IN THE MAIN BLOCK.
|
1227 |
+
param_order = [
|
1228 |
+
'waveform_type', 'pulse_width', 'envelope_type', 'decay_time_s', 'vibrato_rate',
|
1229 |
+
'vibrato_depth', 'bass_boost_level', 'smooth_notes_level', 'continuous_vibrato_level',
|
1230 |
+
'noise_level', 'distortion_level', 'fm_modulation_depth', 'fm_modulation_rate'
|
1231 |
+
]
|
1232 |
+
|
1233 |
+
final_ui_updates = []
|
1234 |
+
if ui_updates: # If auto-recommendation was successful
|
1235 |
+
# We have new values, so we create a list of these values in the correct order.
|
1236 |
+
for param in param_order:
|
1237 |
+
final_ui_updates.append(ui_updates.get(param))
|
1238 |
+
else:
|
1239 |
+
# No auto-recommendation, so we tell Gradio not to change the UI.
|
1240 |
+
# We send a gr.update() for each UI component.
|
1241 |
+
for _ in param_order:
|
1242 |
+
final_ui_updates.append(gr.update())
|
1243 |
+
|
1244 |
+
# The final return is a combination of the result values and the UI update values.
|
1245 |
+
return list(results) + final_ui_updates
|
1246 |
|
1247 |
# =================================================================================================
|
1248 |
# === Gradio UI Setup ===
|
|
|
1261 |
}
|
1262 |
|
1263 |
# --- Function to apply 8-bit synthesizer presets ---
|
1264 |
+
# --- This function must be defined before the UI components that use it ---
|
1265 |
def apply_8bit_preset(preset_name):
|
1266 |
"""
|
1267 |
Takes the name of a preset and returns a dictionary of gr.update objects
|
1268 |
+
to set the values of all 13 of the 8-bit synthesizer's UI components.
|
1269 |
"""
|
1270 |
+
# --- Use a list of keys for consistent updates ---
|
1271 |
+
param_keys = [
|
1272 |
+
'waveform_type', 'pulse_width', 'envelope_type', 'decay_time_s', 'vibrato_rate',
|
1273 |
+
'vibrato_depth', 'bass_boost_level', 'smooth_notes_level', 'continuous_vibrato_level',
|
1274 |
+
'noise_level', 'distortion_level', 'fm_modulation_depth', 'fm_modulation_rate'
|
1275 |
+
]
|
1276 |
+
|
1277 |
# If the user selects "Custom" or the preset is not found, do not change the values.
|
1278 |
if preset_name == "Custom" or preset_name not in S8BIT_PRESETS:
|
1279 |
+
# When switching to custom, don't change any values, just return empty updates.
|
1280 |
+
return {comp: gr.update() for comp in s8bit_ui_components}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1281 |
|
1282 |
# Get the settings dictionary for the chosen preset.
|
1283 |
settings = S8BIT_PRESETS[preset_name]
|
1284 |
|
1285 |
+
# Create a dictionary mapping UI components to their new values from the preset.
|
1286 |
+
update_dict = {}
|
1287 |
+
for i, key in enumerate(param_keys):
|
1288 |
+
component = s8bit_ui_components[i]
|
1289 |
+
value = settings.get(key)
|
1290 |
+
if value is not None:
|
1291 |
+
update_dict[component] = gr.update(value=value)
|
1292 |
+
else:
|
1293 |
+
update_dict[component] = gr.update()
|
1294 |
+
return update_dict
|
1295 |
+
|
|
|
1296 |
|
1297 |
if __name__ == "__main__":
|
1298 |
# Initialize the app: download model (if needed) and apply patches
|
|
|
1309 |
print("\nWARNING: No SoundFonts were found or could be downloaded.")
|
1310 |
print("Rendering with SoundFonts will fail. Only the 8-bit synthesizer will be available.")
|
1311 |
|
1312 |
+
# --- Define a constant for the fallback preset name ---
|
1313 |
+
# This prevents errors if the preset name is changed in the dictionary.
|
1314 |
+
FALLBACK_PRESET_NAME = "Generic Chiptune Loop"
|
1315 |
+
|
1316 |
# --- Data structure for 8-bit synthesizer presets ---
|
1317 |
# Comprehensive preset dictionary with new FX parameters for all presets
|
1318 |
# Comprehensive preset dictionary including new JRPG and Handheld classics
|
1319 |
# Note: Vibrato depth is mapped to a representative value on the 0-50 Hz slider.
|
1320 |
S8BIT_PRESETS = {
|
1321 |
# --- Rhythmic & Action ---
|
1322 |
+
"Rhythm Pop Lead (Rhythm Tengoku / リズム天国)": {
|
1323 |
# Description: A clean, round square wave perfect for the snappy, catchy feel of rhythm games.
|
1324 |
+
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.18,
|
1325 |
+
'vibrato_rate': 4.5, 'vibrato_depth': 4,
|
1326 |
+
'smooth_notes_level': 0.9, # Formerly True -> 1.0; slightly reduced for a bit more attack.
|
1327 |
+
'continuous_vibrato_level': 0.8, # Formerly True -> 1.0; slightly weakened for more defined note transitions.
|
1328 |
+
'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1329 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1330 |
},
|
1331 |
+
"Arcade Brawler Lead (Street Fighter / ストリートファイター)": {
|
1332 |
# Description: A gritty sawtooth lead with a hard attack, capturing the high-energy feel of classic fighting games.
|
1333 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.15,
|
1334 |
+
'vibrato_rate': 5.0, 'vibrato_depth': 6,
|
1335 |
+
'smooth_notes_level': 0.8,
|
1336 |
+
'continuous_vibrato_level': 0.7,
|
1337 |
+
'bass_boost_level': 0.4, 'noise_level': 0.05, 'distortion_level': 0.1,
|
1338 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1339 |
},
|
1340 |
+
"Mega Man (Rockman / ロックマン)": {
|
1341 |
# Description: A thin, sharp square wave lead with fast vibrato, iconic for its driving, heroic melodies.
|
1342 |
+
'waveform_type': 'Square', 'pulse_width': 0.2, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.15,
|
1343 |
+
'vibrato_rate': 6.0, 'vibrato_depth': 8,
|
1344 |
+
'smooth_notes_level': 0.9,
|
1345 |
+
'continuous_vibrato_level': 0.85,
|
1346 |
+
'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.05,
|
1347 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1348 |
},
|
1349 |
+
"Kirby's Bubbly Melody (Hoshi no Kirby / 星のカービィ)": {
|
1350 |
# Description: A soft, round square wave with a bouncy vibrato, creating a cheerful and adorable sound.
|
1351 |
+
'waveform_type': 'Square', 'pulse_width': 0.4, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.2,
|
1352 |
+
'vibrato_rate': 6.0, 'vibrato_depth': 4,
|
1353 |
+
'smooth_notes_level': 0.85,
|
1354 |
+
'continuous_vibrato_level': 0.3, # Formerly False (0.0); adds a hint of continuity for more liveliness.
|
1355 |
+
'bass_boost_level': 0.1, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1356 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1357 |
},
|
1358 |
+
"Mario (Super Mario Bros / スーパーマリオブラザーズ)": {
|
1359 |
# Description: A bright square wave with a per-note vibrato, producing the classic bouncy platformer sound.
|
1360 |
+
'waveform_type': 'Square', 'pulse_width': 0.3, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.25,
|
1361 |
+
'vibrato_rate': 5.0, 'vibrato_depth': 5,
|
1362 |
+
'smooth_notes_level': 0.8,
|
1363 |
+
'continuous_vibrato_level': 0.25,
|
1364 |
+
'bass_boost_level': 0.2, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1365 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1366 |
},
|
1367 |
# --- Epic & Atmospheric ---
|
1368 |
+
"Mecha & Tactics Brass (Super Robot Wars / スーパーロボット大戦)": {
|
1369 |
# Description: A powerful, sustained sawtooth emulating the bold, heroic synth-brass of strategy and mecha anime themes.
|
1370 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1371 |
+
'vibrato_rate': 3.5, 'vibrato_depth': 5,
|
1372 |
+
'smooth_notes_level': 0.95,
|
1373 |
+
'continuous_vibrato_level': 0.9,
|
1374 |
+
'bass_boost_level': 0.5, 'noise_level': 0.1, 'distortion_level': 0.15,
|
1375 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1376 |
},
|
1377 |
+
"Mystic Mana Pad (Secret of Mana / 聖剣伝説2)": {
|
1378 |
+
# Description: A warm, ethereal square wave pad with slow vibrato, capturing a feeling of fantasy and wonder.
|
1379 |
+
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.5,
|
1380 |
+
'vibrato_rate': 2.5, 'vibrato_depth': 4,
|
1381 |
+
'smooth_notes_level': 1.0,
|
1382 |
+
'continuous_vibrato_level': 0.95,
|
1383 |
+
'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1384 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1385 |
},
|
1386 |
+
"Dragon Quest (ドラゴンクエスト)": {
|
1387 |
# Description: A pure triangle wave with a long decay, mimicking the grand, orchestral feel of a classical flute or string section.
|
1388 |
+
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.6,
|
1389 |
+
'vibrato_rate': 3.0, 'vibrato_depth': 4,
|
1390 |
+
'smooth_notes_level': 0.9,
|
1391 |
+
'continuous_vibrato_level': 0.9,
|
1392 |
+
'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1393 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1394 |
},
|
1395 |
+
"ONI V (Wafu Mystic / ONI V 隠忍を継ぐ者)": {
|
1396 |
# Description: A solemn triangle wave with a slow, expressive vibrato, evoking the mysterious atmosphere of Japanese folklore.
|
1397 |
+
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1398 |
+
'vibrato_rate': 3.5, 'vibrato_depth': 3,
|
1399 |
+
'smooth_notes_level': 0.9,
|
1400 |
+
'continuous_vibrato_level': 0.85,
|
1401 |
+
'bass_boost_level': 0.4, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1402 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1403 |
},
|
1404 |
+
"Zelda (The Legend of Zelda / ゼルダの伝説)": {
|
1405 |
# Description: The classic pure triangle wave lead, perfect for heroic and adventurous overworld themes.
|
1406 |
+
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.3,
|
1407 |
+
'vibrato_rate': 4.5, 'vibrato_depth': 4,
|
1408 |
+
'smooth_notes_level': 0.9,
|
1409 |
+
'continuous_vibrato_level': 0.9,
|
1410 |
+
'bass_boost_level': 0.15, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1411 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1412 |
},
|
1413 |
# --- JRPG & System Classics ---
|
1414 |
+
"Falcom Ys (Ys / イース)": {
|
1415 |
# Description: A powerful sawtooth with slight distortion, emulating the driving rock organ and guitar leads of action JRPGs.
|
1416 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.15,
|
1417 |
+
'vibrato_rate': 5.5, 'vibrato_depth': 6,
|
1418 |
+
'smooth_notes_level': 0.85,
|
1419 |
+
'continuous_vibrato_level': 0.8,
|
1420 |
+
'bass_boost_level': 0.4, 'noise_level': 0.05, 'distortion_level': 0.15,
|
1421 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1422 |
},
|
1423 |
+
"Final Fantasy (ファイナルファンタジー)": {
|
1424 |
# Description: A perfect, clean square wave with zero vibrato, creating the iconic, crystal-clear arpeggio sound.
|
1425 |
+
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.22,
|
1426 |
+
'vibrato_rate': 5.0, 'vibrato_depth': 0,
|
1427 |
+
'smooth_notes_level': 0.9,
|
1428 |
+
'continuous_vibrato_level': 0.2,
|
1429 |
+
'bass_boost_level': 0.2, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1430 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1431 |
},
|
1432 |
+
"Castlevania (Akumajō Dracula / 悪魔城ドラキュラ)": {
|
1433 |
# Description: A sharp square wave with dramatic vibrato, ideal for fast, gothic, and baroque-inspired melodies.
|
1434 |
+
'waveform_type': 'Square', 'pulse_width': 0.25, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.18,
|
1435 |
+
'vibrato_rate': 6.5, 'vibrato_depth': 6,
|
1436 |
+
'smooth_notes_level': 0.85,
|
1437 |
+
'continuous_vibrato_level': 0.85,
|
1438 |
+
'bass_boost_level': 0.35, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1439 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1440 |
},
|
1441 |
+
"Pokémon (Game Boy Classics / ポケットモンスター)": {
|
1442 |
# Description: A full, friendly square wave sound, capturing the cheerful and adventurous spirit of early handheld RPGs.
|
1443 |
+
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.22,
|
1444 |
+
'vibrato_rate': 5.0, 'vibrato_depth': 5,
|
1445 |
+
'smooth_notes_level': 0.9,
|
1446 |
+
'continuous_vibrato_level': 0.9,
|
1447 |
+
'bass_boost_level': 0.25, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1448 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1449 |
},
|
1450 |
# --- Advanced System Impressions ---
|
1451 |
"Commodore 64 (SID Feel)": {
|
1452 |
# Description: (Impression) Uses high-speed, shallow vibrato to mimic the characteristic "buzzy" texture of the SID chip's PWM.
|
1453 |
+
'waveform_type': 'Square', 'pulse_width': 0.25, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.25,
|
1454 |
+
'vibrato_rate': 8.0, 'vibrato_depth': 4,
|
1455 |
+
'smooth_notes_level': 0.9,
|
1456 |
+
'continuous_vibrato_level': 0.3,
|
1457 |
+
'bass_boost_level': 0.2, 'noise_level': 0.05, 'distortion_level': 0.1,
|
1458 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1459 |
},
|
1460 |
"Megadrive/Genesis (FM Grit)": {
|
1461 |
# Description: (Impression) Uses FM, distortion, and noise to capture the gritty, metallic, and aggressive tone of the YM2612 chip.
|
1462 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.18,
|
1463 |
+
'vibrato_rate': 0.0, 'vibrato_depth': 0,
|
1464 |
+
'smooth_notes_level': 0.0,
|
1465 |
+
'continuous_vibrato_level': 0.9,
|
1466 |
+
'bass_boost_level': 0.4, 'noise_level': 0.1, 'distortion_level': 0.2,
|
1467 |
+
'fm_modulation_depth': 0.2, 'fm_modulation_rate': 150
|
1468 |
},
|
1469 |
+
"PC-98 (Touhou Feel / 東方Project)": {
|
1470 |
# Description: (Impression) A very sharp square wave with fast FM, emulating the bright, high-energy leads of Japanese PC games.
|
1471 |
+
'waveform_type': 'Square', 'pulse_width': 0.15, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.12,
|
1472 |
+
'vibrato_rate': 7.5, 'vibrato_depth': 7,
|
1473 |
+
'smooth_notes_level': 0.95,
|
1474 |
+
'continuous_vibrato_level': 0.85,
|
1475 |
+
'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1476 |
+
'fm_modulation_depth': 0.1, 'fm_modulation_rate': 200
|
1477 |
},
|
1478 |
"Roland SC-88 (GM Vibe)": {
|
1479 |
# Description: (Impression) A clean, stable triangle wave with no effects, mimicking the polished, sample-based sounds of General MIDI.
|
1480 |
+
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.35,
|
1481 |
+
'vibrato_rate': 0, 'vibrato_depth': 0,
|
1482 |
+
'smooth_notes_level': 1.0,
|
1483 |
+
'continuous_vibrato_level': 0.0,
|
1484 |
+
'bass_boost_level': 0.1, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1485 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1486 |
},
|
1487 |
# --- Experimental & Sound FX ---
|
1488 |
"Sci-Fi Energy Field": {
|
1489 |
# Description: (SFX) High-speed vibrato and noise create a constant, shimmering hum suitable for energy shields or force fields.
|
1490 |
+
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1491 |
+
'vibrato_rate': 10.0, 'vibrato_depth': 3,
|
1492 |
+
'smooth_notes_level': 0.85,
|
1493 |
+
'continuous_vibrato_level': 0.9,
|
1494 |
+
'bass_boost_level': 0.1, 'noise_level': 0.1, 'distortion_level': 0.0,
|
1495 |
+
'fm_modulation_depth': 0.05, 'fm_modulation_rate': 50
|
1496 |
},
|
1497 |
"Industrial Alarm": {
|
1498 |
# Description: (SFX) Extreme vibrato rate on a sawtooth wave produces a harsh, metallic, dissonant alarm sound.
|
1499 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.2,
|
1500 |
+
'vibrato_rate': 15.0, 'vibrato_depth': 8,
|
1501 |
+
'smooth_notes_level': 0.0,
|
1502 |
+
'continuous_vibrato_level': 0.0,
|
1503 |
+
'bass_boost_level': 0.3, 'noise_level': 0.2, 'distortion_level': 0.3,
|
1504 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1505 |
},
|
1506 |
"Laser Charge-Up": {
|
1507 |
# Description: (SFX) Extreme vibrato depth creates a dramatic, rising pitch effect, perfect for sci-fi weapon sounds.
|
1508 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.3,
|
1509 |
+
'vibrato_rate': 4.0, 'vibrato_depth': 25,
|
1510 |
+
'smooth_notes_level': 0.9,
|
1511 |
+
'continuous_vibrato_level': 0.95,
|
1512 |
+
'bass_boost_level': 0.2, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1513 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1514 |
},
|
1515 |
"Unstable Machine Core": {
|
1516 |
# Description: (SFX) Maximum depth and distortion create a chaotic, atonal noise, simulating a machine on the verge of exploding.
|
1517 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.5,
|
1518 |
+
'vibrato_rate': 1.0, 'vibrato_depth': 50,
|
1519 |
+
'smooth_notes_level': 0.0,
|
1520 |
+
'continuous_vibrato_level': 0.9,
|
1521 |
+
'bass_boost_level': 0.5, 'noise_level': 0.3, 'distortion_level': 0.4,
|
1522 |
+
'fm_modulation_depth': 0.5, 'fm_modulation_rate': 10
|
1523 |
},
|
1524 |
"Hardcore Gabber Kick": {
|
1525 |
# Description: (Experimental) Maximum bass boost and distortion create an overwhelmingly powerful, clipped kick drum sound.
|
1526 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.1,
|
1527 |
+
'vibrato_rate': 0, 'vibrato_depth': 0,
|
1528 |
+
'smooth_notes_level': 0.0,
|
1529 |
+
'continuous_vibrato_level': 0.0,
|
1530 |
+
'bass_boost_level': 0.8, 'noise_level': 0.2, 'distortion_level': 0.5,
|
1531 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1532 |
},
|
1533 |
# --- Utility ---
|
1534 |
"Generic Chiptune Loop": {
|
1535 |
# Description: A well-balanced, pleasant square wave lead that serves as a great starting point for custom sounds.
|
1536 |
+
'waveform_type': 'Square', 'pulse_width': 0.25, 'envelope_type': 'Plucky (AD Envelope)', 'decay_time_s': 0.2,
|
1537 |
+
'vibrato_rate': 5.5, 'vibrato_depth': 4,
|
1538 |
+
'smooth_notes_level': 0.9,
|
1539 |
+
'continuous_vibrato_level': 0.85,
|
1540 |
+
'bass_boost_level': 0.25, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1541 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1542 |
},
|
1543 |
+
"Dark/Boss Atmosphere (Shin Megami Tensei / 真・女神転生)": {
|
1544 |
+
# Description: An aggressive sawtooth, inspired by the dark, rock-infused themes of SMT.
|
1545 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.35,
|
1546 |
+
'vibrato_rate': 7.0, 'vibrato_depth': 12,
|
1547 |
+
'smooth_notes_level': 0.1,
|
1548 |
+
'continuous_vibrato_level': 0.0,
|
1549 |
+
'bass_boost_level': 0.4, 'noise_level': 0.15, 'distortion_level': 0.25,
|
1550 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1551 |
+
},
|
1552 |
+
"Modern JRPG Pad (Persona / ペルソナ)": {
|
1553 |
+
# Description: A warm, stylish square wave pad, capturing the modern, pop/jazz-infused feel of the Persona series.
|
1554 |
+
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.5,
|
1555 |
+
'vibrato_rate': 2.5, 'vibrato_depth': 4,
|
1556 |
+
'smooth_notes_level': 1.0,
|
1557 |
+
'continuous_vibrato_level': 0.95,
|
1558 |
+
'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0,
|
1559 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1560 |
+
},
|
1561 |
+
"Tactical Brass (Fire Emblem / ファイアーエムブレム)": {
|
1562 |
+
# Description: A powerful, sustained sawtooth emulating the bold, heroic synth-brass of Fire Emblem's tactical themes.
|
1563 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.4,
|
1564 |
+
'vibrato_rate': 3.5, 'vibrato_depth': 5,
|
1565 |
+
'smooth_notes_level': 0.95,
|
1566 |
+
'continuous_vibrato_level': 0.9,
|
1567 |
+
'bass_boost_level': 0.5, 'noise_level': 0.1, 'distortion_level': 0.15,
|
1568 |
+
'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
1569 |
}
|
1570 |
}
|
1571 |
|
1572 |
app = gr.Blocks(theme=gr.themes.Base())
|
1573 |
+
|
1574 |
with app:
|
1575 |
gr.Markdown("<h1 style='text-align: center; margin-bottom: 1rem'>Audio-to-MIDI & Advanced Renderer</h1>")
|
1576 |
gr.Markdown(
|
|
|
1713 |
# - High: Creates fast modulation, resulting in bright, complex, often metallic harmonics and sidebands.
|
1714 |
# =================================================================================
|
1715 |
#
|
1716 |
+
# --- New option for auto-recommendation ---
|
1717 |
+
# Define the 8-bit UI components in one place for easy reference
|
1718 |
with gr.Accordion("8-bit Synthesizer Settings", open=False, visible=False) as synth_8bit_settings:
|
1719 |
+
# --- Preset selector dropdown ---
|
1720 |
s8bit_preset_selector = gr.Dropdown(
|
1721 |
+
choices=["Custom", "Auto-Recommend (Analyze MIDI)"] + list(S8BIT_PRESETS.keys()),
|
1722 |
value="Custom",
|
1723 |
label="Style Preset",
|
1724 |
info="Select a preset to auto-fill the settings below. Choose 'Custom' for manual control.\nFor reference and entertainment only. These presets are not guaranteed to be perfectly accurate."
|
|
|
1727 |
s8bit_waveform_type = gr.Dropdown(['Square', 'Sawtooth', 'Triangle'], value='Square', label="Waveform Type")
|
1728 |
s8bit_pulse_width = gr.Slider(0.01, 0.99, value=0.5, step=0.01, label="Pulse Width (Square Wave Only)")
|
1729 |
s8bit_envelope_type = gr.Dropdown(['Plucky (AD Envelope)', 'Sustained (Full Decay)'], value='Plucky (AD Envelope)', label="Envelope Type")
|
1730 |
+
s8bit_decay_time_s = gr.Slider(0.01, 0.6, value=0.1, step=0.01, label="Decay Time (s)") # Increased max to 0.6 for DQ style
|
1731 |
s8bit_vibrato_rate = gr.Slider(0, 20, value=5, label="Vibrato Rate (Hz)")
|
1732 |
s8bit_vibrato_depth = gr.Slider(0, 50, value=0, label="Vibrato Depth (Hz)")
|
1733 |
+
s8bit_bass_boost_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="Bass Boost Level", info="Adjusts the volume of the sub-octave. 0 is off.")
|
1734 |
+
s8bit_smooth_notes_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="Smooth Notes Level", info="Level of fade-in/out to reduce clicks. 0=off, 1=max.")
|
1735 |
+
s8bit_continuous_vibrato_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="Continuous Vibrato Level", info="Controls vibrato continuity. 0=resets per note, 1=fully continuous.")
|
1736 |
+
|
1737 |
# --- New accordion for advanced effects ---
|
1738 |
with gr.Accordion("Advanced Synthesis & FX", open=False):
|
1739 |
s8bit_noise_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="Noise Level", info="Mixes in white noise. Great for percussion or adding 'air'.")
|
1740 |
s8bit_distortion_level = gr.Slider(minimum=0.0, maximum=0.9, value=0.0, step=0.05, label="Distortion Level", info="Applies wave-shaping distortion for a grittier, harsher sound.")
|
1741 |
s8bit_fm_modulation_depth = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.05, label="FM Depth", info="Depth of Frequency Modulation. Creates complex, metallic, or bell-like tones.")
|
1742 |
s8bit_fm_modulation_rate = gr.Slider(minimum=0.0, maximum=500.0, value=0.0, step=1.0, label="FM Rate", info="Rate of Frequency Modulation. Higher values create brighter, more complex harmonics.")
|
1743 |
+
|
1744 |
# --- Original Advanced Options (Now tied to Piano-Specific) ---
|
1745 |
with gr.Accordion("Advanced MIDI Rendering Options", open=False) as advanced_rendering_options:
|
1746 |
render_with_sustains = gr.Checkbox(label="Apply sustain pedal effects (if present)", value=True)
|
|
|
1771 |
output_midi_summary = gr.Textbox(label="MIDI metadata summary", lines=4)
|
1772 |
|
1773 |
# Define all input components for the click event, excluding the preset selector which is not a direct input to the final processing.
|
1774 |
+
# all_inputs now includes the preset selector itself
|
1775 |
+
# Inputs for the main processing function
|
1776 |
all_inputs = [
|
1777 |
+
input_file, s8bit_preset_selector, enable_stereo_processing,
|
1778 |
+
transcription_method, onset_threshold, frame_threshold, minimum_note_length,
|
1779 |
+
minimum_frequency, maximum_frequency, infer_onsets, melodia_trick, multiple_pitch_bends,
|
1780 |
+
render_type, soundfont_bank, render_sample_rate, render_with_sustains,
|
1781 |
+
merge_misaligned_notes, custom_render_patch, render_align, render_transpose_value,
|
1782 |
+
render_transpose_to_C4, render_output_as_solo_piano, render_remove_drums,
|
|
|
|
|
1783 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
1784 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth, s8bit_bass_boost_level,
|
1785 |
+
s8bit_smooth_notes_level, s8bit_continuous_vibrato_level,
|
1786 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
1787 |
]
|
1788 |
+
|
1789 |
+
# Outputs for the main results
|
1790 |
+
result_outputs = [
|
1791 |
output_midi_md5, output_midi_title, output_midi_summary,
|
1792 |
output_midi, output_audio, output_plot, output_song_description
|
1793 |
]
|
1794 |
+
|
1795 |
+
# The list of 8-bit UI components that can be updated
|
1796 |
+
# This MUST be defined after the components themselves are created in the UI.
|
1797 |
+
s8bit_ui_components = [
|
1798 |
+
s8bit_waveform_type, s8bit_pulse_width, s8bit_envelope_type, s8bit_decay_time_s, s8bit_vibrato_rate,
|
1799 |
+
s8bit_vibrato_depth, s8bit_bass_boost_level,
|
1800 |
+
s8bit_smooth_notes_level, s8bit_continuous_vibrato_level,
|
1801 |
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
1802 |
]
|
1803 |
|
1804 |
+
# all_outputs now includes both results AND the UI controls to be updated
|
1805 |
+
all_outputs = result_outputs + s8bit_ui_components
|
1806 |
+
|
1807 |
+
# Event Handling
|
1808 |
submit_btn.click(
|
1809 |
process_and_render_file,
|
1810 |
inputs=all_inputs,
|
1811 |
+
outputs=all_outputs # Pass the combined list
|
1812 |
)
|
1813 |
|
1814 |
# --- Listeners for dynamic UI updates ---
|
|
|
1823 |
outputs=[general_transcription_settings, synth_8bit_settings]
|
1824 |
)
|
1825 |
|
1826 |
+
# This listener now correctly handles only the named presets, ignoring "Auto-Recommend"
|
1827 |
# --- Event listener for the preset selector ---
|
1828 |
# When the preset dropdown changes, it calls the `apply_8bit_preset` function.
|
1829 |
# The input to the function is the selected preset name.
|
1830 |
# The outputs are all the individual 8-bit setting components that need to be updated.
|
1831 |
+
# This listener is for manual preset selection (e.g., choosing "Mega Man")
|
1832 |
s8bit_preset_selector.change(
|
1833 |
fn=apply_8bit_preset,
|
1834 |
inputs=[s8bit_preset_selector],
|
1835 |
+
outputs=s8bit_ui_components # This now correctly targets the new sliders
|
1836 |
)
|
1837 |
|
1838 |
|