feat(synth): Enhance 8-bit synthesizer and expand presets
Browse filesThis major update significantly enhances the capabilities of the 8-bit style synthesizer and expands the preset library with a wide range of iconic and experimental sounds.
**Synthesizer Engine Enhancements:**
The `synthesize_8bit_style` function has been upgraded with four new parameters to allow for more complex and authentic sound design, moving beyond basic subtractive synthesis:
- **Noise Channel (`noise_level`):** Adds a layer of white noise, essential for creating percussive sounds, atmospheric textures, and adding grit.
- **Waveform Distortion (`distortion_level`):** Introduces a wave-shaping algorithm to simulate the hardware clipping and saturation found in vintage consoles, enabling harsher and more aggressive tones.
- **Frequency Modulation (`fm_modulation_depth`, `fm_modulation_rate`):** Implements a basic FM synthesis capability, allowing for the creation of complex, metallic, and bell-like timbres reminiscent of sound chips like the Yamaha YM2612.
___
The `S8BIT_PRESETS` dictionary has been massively expanded and reorganized to showcase the new engine's capabilities. This includes:
1. **Rhythm Pop Lead:** A clean, round square wave perfect for the snappy, catchy feel of rhythm games.
2. **Arcade Brawler Lead:** A gritty sawtooth lead with a hard attack, capturing the high-energy feel of classic fighting games.
3. **Mega Man (Rockman):** A thin, sharp square wave lead with fast vibrato, iconic for its driving, heroic melodies.
4. **Kirby's Bubbly Melody:** A soft, round square wave with a bouncy vibrato, creating a cheerful and adorable sound.
5. **Mario (Super Mario Bros):** A bright square wave with a per-note vibrato, producing the classic bouncy platformer sound.
6. **Mecha & Tactics Brass:** A powerful, sustained sawtooth emulating the bold, heroic synth-brass of strategy and mecha anime themes.
7. **Mystic Mana Pad:** A warm, ethereal square wave pad with slow vibrato, capturing a feeling of fantasy and wonder.
8. **Dragon Quest (Orchestral Feel):** A pure triangle wave with a long decay, mimicking the grand, orchestral feel of a classical flute or string section.
9. **ONI V (Wafu Mystic):** A solemn triangle wave with a slow, expressive vibrato, evoking the mysterious atmosphere of Japanese folklore.
10. **Zelda (NES):** The classic pure triangle wave lead, perfect for heroic and adventurous overworld themes.
11. **Falcom Ys (Rock Lead):** A powerful sawtooth with slight distortion, emulating the driving rock organ and guitar leads of action JRPGs.
12. **Final Fantasy (Arpeggio):** A perfect, clean square wave with zero vibrato, creating the iconic, crystal-clear arpeggio sound.
13. **Castlevania (Akumajō Dracula):** A sharp square wave with dramatic vibrato, ideal for fast, gothic, and baroque-inspired melodies.
14. **Pokémon (Game Boy Classics):** A full, friendly square wave sound, capturing the cheerful and adventurous spirit of early handheld RPGs.
15. **Commodore 64 (SID Feel):** (Impression) Uses high-speed, shallow vibrato to mimic the characteristic "buzzy" texture of the SID chip's PWM.
16. **Megadrive/Genesis (FM Grit):** (Impression) Uses FM, distortion, and noise to capture the gritty, metallic, and aggressive tone of the YM2612 chip.
17. **PC-98 (Touhou Feel):** (Impression) A very sharp square wave with fast FM, emulating the bright, high-energy leads of Japanese PC games.
18. **Roland SC-88 (GM Vibe):** (Impression) A clean, stable triangle wave with no effects, mimicking the polished, sample-based sounds of General MIDI.
19. **Sci-Fi Energy Field:** (SFX) High-speed vibrato and noise create a constant, shimmering hum suitable for energy shields or force fields.
20. **Industrial Alarm:** (SFX) Extreme vibrato rate on a sawtooth wave produces a harsh, metallic, dissonant alarm sound.
21. **Laser Charge-Up:** (SFX) Extreme vibrato depth creates a dramatic, rising pitch effect, perfect for sci-fi weapon sounds.
22. **Unstable Machine Core:** (SFX) Maximum depth and distortion create a chaotic, atonal noise, simulating a machine on the verge of exploding.
23. **Hardcore Gabber Kick:** (Experimental) Maximum bass boost and distortion create an overwhelmingly powerful, clipped kick drum sound.
24. **Generic Chiptune Loop:** A well-balanced, pleasant square wave lead that serves as a great starting point for custom sounds.
25. **Dark/Boss Atmosphere:** An aggressive sawtooth with heavy bass and distortion, perfect for tense or menacing background music.
|
@@ -164,7 +164,10 @@ def prepare_soundfonts():
|
|
| 164 |
# =================================================================================================
|
| 165 |
# === 8-bit Style Synthesizer (Stereo Enabled) ===
|
| 166 |
# =================================================================================================
|
| 167 |
-
def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s, pulse_width,
|
|
|
|
|
|
|
|
|
|
| 168 |
"""
|
| 169 |
Synthesizes an 8-bit style audio waveform from a PrettyMIDI object.
|
| 170 |
This function generates waveforms manually instead of using a synthesizer like FluidSynth.
|
|
@@ -175,9 +178,14 @@ def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s,
|
|
| 175 |
total_duration = midi_data.get_end_time()
|
| 176 |
# Initialize a stereo waveform buffer (2 channels: Left, Right)
|
| 177 |
waveform = np.zeros((2, int(total_duration * fs) + fs))
|
| 178 |
-
|
| 179 |
num_instruments = len(midi_data.instruments)
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
for i, instrument in enumerate(midi_data.instruments):
|
| 182 |
# --- Panning Logic ---
|
| 183 |
# Default to center-panned mono
|
|
@@ -188,57 +196,103 @@ def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s,
|
|
| 188 |
elif i == 1: # Second instrument panned right
|
| 189 |
pan_l, pan_r = 0.0, 1.0
|
| 190 |
elif num_instruments > 2:
|
| 191 |
-
if i == 0:
|
| 192 |
-
|
|
|
|
|
|
|
| 193 |
# Other instruments remain centered
|
| 194 |
|
|
|
|
|
|
|
| 195 |
for note in instrument.notes:
|
| 196 |
freq = pretty_midi.note_number_to_hz(note.pitch)
|
| 197 |
note_duration = note.end - note.start
|
| 198 |
num_samples = int(note_duration * fs)
|
| 199 |
-
if num_samples
|
| 200 |
continue
|
| 201 |
|
| 202 |
-
t = np.
|
| 203 |
-
|
| 204 |
# --- Vibrato LFO ---
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
-
# --- Waveform Generation (Main Oscillator) ---
|
| 208 |
if waveform_type == 'Square':
|
| 209 |
-
note_waveform = signal.square(
|
| 210 |
elif waveform_type == 'Sawtooth':
|
| 211 |
-
note_waveform = signal.sawtooth(
|
| 212 |
elif waveform_type == 'Triangle':
|
| 213 |
-
note_waveform = signal.sawtooth(
|
| 214 |
-
|
| 215 |
# --- Bass Boost (Sub-Octave Oscillator) ---
|
| 216 |
if bass_boost_level > 0:
|
| 217 |
bass_freq = freq / 2.0
|
| 218 |
# Only add bass if the frequency is reasonably audible
|
| 219 |
if bass_freq > 20:
|
| 220 |
# Bass uses a simple square wave, no vibrato, for stability
|
| 221 |
-
|
|
|
|
|
|
|
| 222 |
# Mix the main and bass waveforms.
|
| 223 |
# As bass level increases, slightly decrease main waveform volume to prevent clipping.
|
| 224 |
main_level = 1.0 - (0.5 * bass_boost_level)
|
| 225 |
note_waveform = (note_waveform * main_level) + (bass_sub_waveform * bass_boost_level)
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
# --- ADSR Envelope ---
|
| 228 |
start_amp = note.velocity / 127.0
|
| 229 |
envelope = np.zeros(num_samples)
|
| 230 |
-
|
| 231 |
-
if envelope_type == 'Plucky (AD Envelope)'
|
| 232 |
attack_time_s = 0.005
|
| 233 |
attack_samples = min(int(attack_time_s * fs), num_samples)
|
| 234 |
decay_samples = min(int(decay_time_s * fs), num_samples - attack_samples)
|
| 235 |
-
|
| 236 |
envelope[:attack_samples] = np.linspace(0, start_amp, attack_samples)
|
| 237 |
if decay_samples > 0:
|
| 238 |
envelope[attack_samples:attack_samples+decay_samples] = np.linspace(start_amp, 0, decay_samples)
|
| 239 |
-
elif envelope_type == 'Sustained (Full Decay)'
|
| 240 |
envelope = np.linspace(start_amp, 0, num_samples)
|
| 241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
# Apply envelope to the (potentially combined) waveform
|
| 243 |
note_waveform *= envelope
|
| 244 |
|
|
@@ -247,11 +301,11 @@ def synthesize_8bit_style(midi_data, waveform_type, envelope_type, decay_time_s,
|
|
| 247 |
if end_sample > waveform.shape[1]:
|
| 248 |
end_sample = waveform.shape[1]
|
| 249 |
note_waveform = note_waveform[:end_sample-start_sample]
|
| 250 |
-
|
| 251 |
# Add the mono note waveform to the stereo buffer with panning
|
| 252 |
waveform[0, start_sample:end_sample] += note_waveform * pan_l
|
| 253 |
waveform[1, start_sample:end_sample] += note_waveform * pan_r
|
| 254 |
-
|
| 255 |
return waveform # Returns a (2, N) numpy array
|
| 256 |
|
| 257 |
|
|
@@ -518,7 +572,8 @@ def Render_MIDI(input_midi_path,
|
|
| 518 |
# --- 8-bit synth params ---
|
| 519 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 520 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
| 521 |
-
s8bit_bass_boost_level
|
|
|
|
| 522 |
):
|
| 523 |
"""
|
| 524 |
Processes and renders a MIDI file according to user-defined settings.
|
|
@@ -737,12 +792,19 @@ def Render_MIDI(input_midi_path,
|
|
| 737 |
# Load the MIDI file with pretty_midi for manual synthesis
|
| 738 |
midi_data_for_synth = pretty_midi.PrettyMIDI(midi_to_render_path)
|
| 739 |
# Synthesize the waveform
|
|
|
|
| 740 |
audio = synthesize_8bit_style(
|
| 741 |
midi_data_for_synth,
|
| 742 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 743 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
| 744 |
s8bit_bass_boost_level,
|
| 745 |
-
fs=srate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
)
|
| 747 |
# Normalize and prepare for Gradio
|
| 748 |
peak_val = np.max(np.abs(audio))
|
|
@@ -809,7 +871,8 @@ def process_and_render_file(input_file,
|
|
| 809 |
# --- 8-bit synth params ---
|
| 810 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 811 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
| 812 |
-
s8bit_bass_boost_level
|
|
|
|
| 813 |
):
|
| 814 |
"""
|
| 815 |
Main function to handle file processing. It determines the file type and calls the
|
|
@@ -920,12 +983,16 @@ def process_and_render_file(input_file,
|
|
| 920 |
|
| 921 |
# --- Step 2: Render the MIDI file with selected options ---
|
| 922 |
print(f"Proceeding to render MIDI file: {os.path.basename(midi_path_for_rendering)}")
|
|
|
|
| 923 |
results = Render_MIDI(midi_path_for_rendering,
|
| 924 |
render_type, soundfont_bank, render_sample_rate,
|
| 925 |
render_with_sustains, merge_misaligned_notes, custom_render_patch, render_align,
|
| 926 |
render_transpose_value, render_transpose_to_C4, render_output_as_solo_piano, render_remove_drums,
|
| 927 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 928 |
-
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth, s8bit_bass_boost_level
|
|
|
|
|
|
|
|
|
|
| 929 |
|
| 930 |
print(f'Total processing time: {(reqtime.time() - start_time):.2f} sec')
|
| 931 |
print('*' * 70)
|
|
@@ -948,6 +1015,42 @@ def update_ui_visibility(transcription_method, soundfont_choice):
|
|
| 948 |
synth_8bit_settings: gr.update(visible=is_8bit),
|
| 949 |
}
|
| 950 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 951 |
if __name__ == "__main__":
|
| 952 |
# Initialize the app: download model (if needed) and apply patches
|
| 953 |
# Set to False if you don't have 'requests' or 'tqdm' installed
|
|
@@ -962,6 +1065,144 @@ if __name__ == "__main__":
|
|
| 962 |
if not soundfonts_dict:
|
| 963 |
print("\nWARNING: No SoundFonts were found or could be downloaded.")
|
| 964 |
print("Rendering with SoundFonts will fail. Only the 8-bit synthesizer will be available.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 965 |
|
| 966 |
app = gr.Blocks(theme=gr.themes.Base())
|
| 967 |
|
|
@@ -1041,16 +1282,98 @@ if __name__ == "__main__":
|
|
| 1041 |
value="44100"
|
| 1042 |
)
|
| 1043 |
|
| 1044 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1045 |
with gr.Accordion("8-bit Synthesizer Settings", open=False, visible=False) as synth_8bit_settings:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1046 |
s8bit_waveform_type = gr.Dropdown(['Square', 'Sawtooth', 'Triangle'], value='Square', label="Waveform Type")
|
|
|
|
| 1047 |
s8bit_envelope_type = gr.Dropdown(['Plucky (AD Envelope)', 'Sustained (Full Decay)'], value='Plucky (AD Envelope)', label="Envelope Type")
|
| 1048 |
-
s8bit_decay_time_s = gr.Slider(0.01, 0.
|
| 1049 |
-
s8bit_pulse_width = gr.Slider(0.01, 0.99, value=0.5, step=0.01, label="Pulse Width")
|
| 1050 |
s8bit_vibrato_rate = gr.Slider(0, 20, value=5, label="Vibrato Rate (Hz)")
|
| 1051 |
s8bit_vibrato_depth = gr.Slider(0, 50, value=0, label="Vibrato Depth (Hz)")
|
| 1052 |
s8bit_bass_boost_level = gr.Slider(minimum=0.0, maximum=1.0, value=0.0, step=0.1, label="Bass Boost Level", info="Adjusts the volume of the sub-octave. 0 is off.")
|
| 1053 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
# --- Original Advanced Options (Now tied to Piano-Specific) ---
|
| 1055 |
with gr.Accordion("Advanced MIDI Rendering Options", open=False) as advanced_rendering_options:
|
| 1056 |
render_with_sustains = gr.Checkbox(label="Apply sustain pedal effects (if present)", value=True)
|
|
@@ -1079,8 +1402,8 @@ if __name__ == "__main__":
|
|
| 1079 |
output_midi = gr.File(label="Download Processed MIDI File", file_types=[".mid"])
|
| 1080 |
output_midi_md5 = gr.Textbox(label="Output MIDI MD5 Hash")
|
| 1081 |
output_midi_summary = gr.Textbox(label="MIDI metadata summary", lines=4)
|
| 1082 |
-
|
| 1083 |
-
#
|
| 1084 |
all_inputs = [
|
| 1085 |
input_file,
|
| 1086 |
enable_stereo_processing,
|
|
@@ -1091,13 +1414,23 @@ if __name__ == "__main__":
|
|
| 1091 |
render_with_sustains, merge_misaligned_notes, custom_render_patch, render_align,
|
| 1092 |
render_transpose_value, render_transpose_to_C4, render_output_as_solo_piano, render_remove_drums,
|
| 1093 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 1094 |
-
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth, s8bit_bass_boost_level
|
|
|
|
|
|
|
| 1095 |
]
|
| 1096 |
all_outputs = [
|
| 1097 |
output_midi_md5, output_midi_title, output_midi_summary,
|
| 1098 |
output_midi, output_audio, output_plot, output_song_description
|
| 1099 |
]
|
| 1100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1101 |
# --- Event Handling ---
|
| 1102 |
submit_btn.click(
|
| 1103 |
process_and_render_file,
|
|
@@ -1116,6 +1449,17 @@ if __name__ == "__main__":
|
|
| 1116 |
inputs=[transcription_method, soundfont_bank],
|
| 1117 |
outputs=[general_transcription_settings, synth_8bit_settings]
|
| 1118 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1119 |
|
| 1120 |
# Launch the Gradio app
|
| 1121 |
app.queue().launch(inbrowser=True, debug=True)
|
|
|
|
| 164 |
# =================================================================================================
|
| 165 |
# === 8-bit Style Synthesizer (Stereo Enabled) ===
|
| 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=False, continuous_vibrato=False, noise_level=0.0,
|
| 170 |
+
distortion_level=0.0, fm_modulation_depth=0.0, fm_modulation_rate=0.0):
|
| 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.
|
|
|
|
| 178 |
total_duration = midi_data.get_end_time()
|
| 179 |
# Initialize a stereo waveform buffer (2 channels: Left, Right)
|
| 180 |
waveform = np.zeros((2, int(total_duration * fs) + fs))
|
| 181 |
+
|
| 182 |
num_instruments = len(midi_data.instruments)
|
| 183 |
|
| 184 |
+
# Phase tracking: main oscillator phase for each instrument
|
| 185 |
+
osc_phase = {}
|
| 186 |
+
# Vibrato phase tracking
|
| 187 |
+
vibrato_phase = 0.0
|
| 188 |
+
|
| 189 |
for i, instrument in enumerate(midi_data.instruments):
|
| 190 |
# --- Panning Logic ---
|
| 191 |
# Default to center-panned mono
|
|
|
|
| 196 |
elif i == 1: # Second instrument panned right
|
| 197 |
pan_l, pan_r = 0.0, 1.0
|
| 198 |
elif num_instruments > 2:
|
| 199 |
+
if i == 0: # Left
|
| 200 |
+
pan_l, pan_r = 1.0, 0.0
|
| 201 |
+
elif i == 1: # Right
|
| 202 |
+
pan_l, pan_r = 0.0, 1.0
|
| 203 |
# Other instruments remain centered
|
| 204 |
|
| 205 |
+
osc_phase[i] = 0.0 # Independent phase tracking for each instrument
|
| 206 |
+
|
| 207 |
for note in instrument.notes:
|
| 208 |
freq = pretty_midi.note_number_to_hz(note.pitch)
|
| 209 |
note_duration = note.end - note.start
|
| 210 |
num_samples = int(note_duration * fs)
|
| 211 |
+
if num_samples <= 0:
|
| 212 |
continue
|
| 213 |
|
| 214 |
+
t = np.arange(num_samples) / fs
|
| 215 |
+
|
| 216 |
# --- Vibrato LFO ---
|
| 217 |
+
if continuous_vibrato:
|
| 218 |
+
# Use accumulated phase to avoid vibrato reset per note
|
| 219 |
+
vib_phase_inc = 2 * np.pi * vibrato_rate / fs
|
| 220 |
+
vib_phase_array = vibrato_phase + np.arange(num_samples) * vib_phase_inc
|
| 221 |
+
vibrato_phase = (vib_phase_array[-1] + vib_phase_inc) % (2 * np.pi)
|
| 222 |
+
vibrato_lfo = vibrato_depth * np.sin(vib_phase_array)
|
| 223 |
+
else:
|
| 224 |
+
vibrato_lfo = vibrato_depth * np.sin(2 * np.pi * vibrato_rate * t)
|
| 225 |
+
|
| 226 |
+
# --- Waveform Generation (Main Oscillator with phase continuity) ---
|
| 227 |
+
phase_inc = 2 * np.pi * (freq + vibrato_lfo) / fs
|
| 228 |
+
phase = osc_phase[i] + np.cumsum(phase_inc)
|
| 229 |
+
osc_phase[i] = phase[-1] % (2 * np.pi) # Store last phase
|
| 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 |
elif waveform_type == 'Triangle':
|
| 236 |
+
note_waveform = signal.sawtooth(phase, width=0.5)
|
| 237 |
+
|
| 238 |
# --- Bass Boost (Sub-Octave Oscillator) ---
|
| 239 |
if bass_boost_level > 0:
|
| 240 |
bass_freq = freq / 2.0
|
| 241 |
# Only add bass if the frequency is reasonably audible
|
| 242 |
if bass_freq > 20:
|
| 243 |
# Bass uses a simple square wave, no vibrato, for stability
|
| 244 |
+
bass_phase_inc = 2 * np.pi * bass_freq / fs
|
| 245 |
+
bass_phase = np.cumsum(np.full(num_samples, bass_phase_inc))
|
| 246 |
+
bass_sub_waveform = signal.square(bass_phase, duty=0.5)
|
| 247 |
# Mix the main and bass waveforms.
|
| 248 |
# As bass level increases, slightly decrease main waveform volume to prevent clipping.
|
| 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 Channel Simulation (White Noise) ---
|
| 253 |
+
if noise_level > 0:
|
| 254 |
+
noise_waveform = np.random.uniform(-1, 1, num_samples)
|
| 255 |
+
note_waveform += noise_waveform * noise_level
|
| 256 |
+
|
| 257 |
+
# --- Distortion (Wave Shaping) ---
|
| 258 |
+
if distortion_level > 0:
|
| 259 |
+
note_waveform = np.sign(note_waveform) * np.abs(note_waveform) ** (1.0 - distortion_level)
|
| 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 |
attack_time_s = 0.005
|
| 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 |
+
elif envelope_type == 'Sustained (Full Decay)':
|
| 287 |
envelope = np.linspace(start_amp, 0, num_samples)
|
| 288 |
|
| 289 |
+
if smooth_notes:
|
| 290 |
+
# Add short release
|
| 291 |
+
release_samples = min(int(0.005 * fs), num_samples)
|
| 292 |
+
envelope[-release_samples:] *= np.linspace(1, 0, release_samples)
|
| 293 |
+
# Small crossfade (to avoid clicks)
|
| 294 |
+
envelope[:min(10, num_samples)] *= np.linspace(0.5, 1, min(10, num_samples))
|
| 295 |
+
|
| 296 |
# Apply envelope to the (potentially combined) waveform
|
| 297 |
note_waveform *= envelope
|
| 298 |
|
|
|
|
| 301 |
if end_sample > waveform.shape[1]:
|
| 302 |
end_sample = waveform.shape[1]
|
| 303 |
note_waveform = note_waveform[:end_sample-start_sample]
|
| 304 |
+
|
| 305 |
# Add the mono note waveform to the stereo buffer with panning
|
| 306 |
waveform[0, start_sample:end_sample] += note_waveform * pan_l
|
| 307 |
waveform[1, start_sample:end_sample] += note_waveform * pan_r
|
| 308 |
+
|
| 309 |
return waveform # Returns a (2, N) numpy array
|
| 310 |
|
| 311 |
|
|
|
|
| 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, s8bit_smooth_notes, s8bit_continuous_vibrato,
|
| 576 |
+
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
| 577 |
):
|
| 578 |
"""
|
| 579 |
Processes and renders a MIDI file according to user-defined settings.
|
|
|
|
| 792 |
# Load the MIDI file with pretty_midi for manual synthesis
|
| 793 |
midi_data_for_synth = pretty_midi.PrettyMIDI(midi_to_render_path)
|
| 794 |
# Synthesize the waveform
|
| 795 |
+
# --- Passing new FX parameters to the synthesis function ---
|
| 796 |
audio = synthesize_8bit_style(
|
| 797 |
midi_data_for_synth,
|
| 798 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 799 |
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
| 800 |
s8bit_bass_boost_level,
|
| 801 |
+
fs=srate,
|
| 802 |
+
smooth_notes=s8bit_smooth_notes,
|
| 803 |
+
continuous_vibrato=s8bit_continuous_vibrato,
|
| 804 |
+
noise_level=s8bit_noise_level,
|
| 805 |
+
distortion_level=s8bit_distortion_level,
|
| 806 |
+
fm_modulation_depth=s8bit_fm_modulation_depth,
|
| 807 |
+
fm_modulation_rate=s8bit_fm_modulation_rate
|
| 808 |
)
|
| 809 |
# Normalize and prepare for Gradio
|
| 810 |
peak_val = np.max(np.abs(audio))
|
|
|
|
| 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, s8bit_smooth_notes, s8bit_continuous_vibrato,
|
| 875 |
+
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
| 876 |
):
|
| 877 |
"""
|
| 878 |
Main function to handle file processing. It determines the file type and calls the
|
|
|
|
| 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 |
+
# --- Passing new FX parameters to the Render_MIDI function ---
|
| 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 |
s8bit_waveform_type, s8bit_envelope_type, s8bit_decay_time_s,
|
| 992 |
+
s8bit_pulse_width, s8bit_vibrato_rate, s8bit_vibrato_depth, s8bit_bass_boost_level,
|
| 993 |
+
s8bit_smooth_notes, s8bit_continuous_vibrato,
|
| 994 |
+
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
| 995 |
+
)
|
| 996 |
|
| 997 |
print(f'Total processing time: {(reqtime.time() - start_time):.2f} sec')
|
| 998 |
print('*' * 70)
|
|
|
|
| 1015 |
synth_8bit_settings: gr.update(visible=is_8bit),
|
| 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 |
+
s8bit_waveform_type: gr.update(),
|
| 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 |
+
# Return a dictionary that maps each UI component to a gr.update call with the new value.
|
| 1042 |
+
return {
|
| 1043 |
+
s8bit_waveform_type: gr.update(value=settings['waveform_type']),
|
| 1044 |
+
s8bit_pulse_width: gr.update(value=settings['pulse_width']),
|
| 1045 |
+
s8bit_envelope_type: gr.update(value=settings['envelope_type']),
|
| 1046 |
+
s8bit_decay_time_s: gr.update(value=settings['decay_time_s']),
|
| 1047 |
+
s8bit_vibrato_rate: gr.update(value=settings['vibrato_rate']),
|
| 1048 |
+
s8bit_vibrato_depth: gr.update(value=settings['vibrato_depth']),
|
| 1049 |
+
s8bit_smooth_notes: gr.update(value=settings['smooth_notes']),
|
| 1050 |
+
s8bit_continuous_vibrato: gr.update(value=settings['continuous_vibrato']),
|
| 1051 |
+
s8bit_bass_boost_level: gr.update(value=settings['bass_boost_level'])
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
if __name__ == "__main__":
|
| 1055 |
# Initialize the app: download model (if needed) and apply patches
|
| 1056 |
# Set to False if you don't have 'requests' or 'tqdm' installed
|
|
|
|
| 1065 |
if not soundfonts_dict:
|
| 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, 'vibrato_rate': 4.5, 'vibrato_depth': 4,
|
| 1078 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 5.0, 'vibrato_depth': 6,
|
| 1083 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.4, 'noise_level': 0.05, 'distortion_level': 0.1, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 6.0, 'vibrato_depth': 8,
|
| 1088 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.05, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 6.0, 'vibrato_depth': 4,
|
| 1093 |
+
'smooth_notes': True, 'continuous_vibrato': False, 'bass_boost_level': 0.1, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 5.0, 'vibrato_depth': 5,
|
| 1098 |
+
'smooth_notes': True, 'continuous_vibrato': False, 'bass_boost_level': 0.2, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 3.5, 'vibrato_depth': 5,
|
| 1104 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.5, 'noise_level': 0.1, 'distortion_level': 0.15, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1105 |
+
},
|
| 1106 |
+
"Mystic Mana Pad": {
|
| 1107 |
+
# Description: A warm, ethereal square wave pad with slow vibrato, capturing a feeling of fantasy and wonder.
|
| 1108 |
+
'waveform_type': 'Square', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.5, 'vibrato_rate': 2.5, 'vibrato_depth': 4,
|
| 1109 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1110 |
+
},
|
| 1111 |
+
"Dragon Quest (Orchestral Feel)": {
|
| 1112 |
+
# Description: A pure triangle wave with a long decay, mimicking the grand, orchestral feel of a classical flute or string section.
|
| 1113 |
+
'waveform_type': 'Triangle', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.6, 'vibrato_rate': 3.0, 'vibrato_depth': 4,
|
| 1114 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 3.5, 'vibrato_depth': 3,
|
| 1119 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.4, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1120 |
+
},
|
| 1121 |
+
"Zelda (NES)": {
|
| 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, 'vibrato_rate': 4.5, 'vibrato_depth': 4,
|
| 1124 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.15, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1125 |
+
},
|
| 1126 |
+
# --- JRPG & System Classics ---
|
| 1127 |
+
"Falcom Ys (Rock Lead)": {
|
| 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, 'vibrato_rate': 5.5, 'vibrato_depth': 6,
|
| 1130 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.4, 'noise_level': 0.05, 'distortion_level': 0.15, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1131 |
+
},
|
| 1132 |
+
"Final Fantasy (Arpeggio)": {
|
| 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, 'vibrato_rate': 5.0, 'vibrato_depth': 0,
|
| 1135 |
+
'smooth_notes': True, 'continuous_vibrato': False, 'bass_boost_level': 0.2, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 6.5, 'vibrato_depth': 6,
|
| 1140 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.35, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 5.0, 'vibrato_depth': 5,
|
| 1145 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.25, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 8.0, 'vibrato_depth': 4,
|
| 1151 |
+
'smooth_notes': True, 'continuous_vibrato': False, 'bass_boost_level': 0.2, 'noise_level': 0.05, 'distortion_level': 0.1, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 0.0, 'vibrato_depth': 0,
|
| 1156 |
+
'smooth_notes': False, 'continuous_vibrato': True, 'bass_boost_level': 0.4, 'noise_level': 0.1, 'distortion_level': 0.2, 'fm_modulation_depth': 0.2, 'fm_modulation_rate': 150
|
| 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, 'vibrato_rate': 7.5, 'vibrato_depth': 7,
|
| 1161 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.3, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.1, 'fm_modulation_rate': 200
|
| 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, 'vibrato_rate': 0, 'vibrato_depth': 0,
|
| 1166 |
+
'smooth_notes': True, 'continuous_vibrato': False, 'bass_boost_level': 0.1, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 10.0, 'vibrato_depth': 3,
|
| 1172 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.1, 'noise_level': 0.1, 'distortion_level': 0.0, 'fm_modulation_depth': 0.05, 'fm_modulation_rate': 50
|
| 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, 'vibrato_rate': 15.0, 'vibrato_depth': 8,
|
| 1177 |
+
'smooth_notes': False, 'continuous_vibrato': False, 'bass_boost_level': 0.3, 'noise_level': 0.2, 'distortion_level': 0.3, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 4.0, 'vibrato_depth': 25,
|
| 1182 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.2, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 1.0, 'vibrato_depth': 50,
|
| 1187 |
+
'smooth_notes': False, 'continuous_vibrato': True, 'bass_boost_level': 0.5, 'noise_level': 0.3, 'distortion_level': 0.4, 'fm_modulation_depth': 0.5, 'fm_modulation_rate': 10
|
| 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, 'vibrato_rate': 0, 'vibrato_depth': 0,
|
| 1192 |
+
'smooth_notes': False, 'continuous_vibrato': False, 'bass_boost_level': 0.8, 'noise_level': 0.2, 'distortion_level': 0.5, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 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, 'vibrato_rate': 5.5, 'vibrato_depth': 4,
|
| 1198 |
+
'smooth_notes': True, 'continuous_vibrato': True, 'bass_boost_level': 0.25, 'noise_level': 0.0, 'distortion_level': 0.0, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1199 |
+
},
|
| 1200 |
+
"Dark/Boss Atmosphere": {
|
| 1201 |
+
# Description: An aggressive sawtooth with heavy bass and distortion, perfect for tense or menacing background music.
|
| 1202 |
+
'waveform_type': 'Sawtooth', 'pulse_width': 0.5, 'envelope_type': 'Sustained (Full Decay)', 'decay_time_s': 0.35, 'vibrato_rate': 7.0, 'vibrato_depth': 12,
|
| 1203 |
+
'smooth_notes': False, 'continuous_vibrato': False, 'bass_boost_level': 0.4, 'noise_level': 0.15, 'distortion_level': 0.25, 'fm_modulation_depth': 0.0, 'fm_modulation_rate': 0.0
|
| 1204 |
+
}
|
| 1205 |
+
}
|
| 1206 |
|
| 1207 |
app = gr.Blocks(theme=gr.themes.Base())
|
| 1208 |
|
|
|
|
| 1282 |
value="44100"
|
| 1283 |
)
|
| 1284 |
|
| 1285 |
+
# --- 8-bit Synthesizer Settings ---
|
| 1286 |
+
#
|
| 1287 |
+
# =================================================================================
|
| 1288 |
+
# === 8-Bit Synthesizer Parameter Guide ===
|
| 1289 |
+
# =================================================================================
|
| 1290 |
+
#
|
| 1291 |
+
# --- Basic Tone Shaping ---
|
| 1292 |
+
#
|
| 1293 |
+
# Waveform Type: The fundamental timbre of the sound.
|
| 1294 |
+
# - Square: The classic, bright, somewhat hollow sound of the NES. Its tone is heavily modified by Pulse Width.
|
| 1295 |
+
# - Sawtooth: Aggressive, buzzy, and rich. Great for intense leads or gritty basslines.
|
| 1296 |
+
# - Triangle: Soft, pure, and flute-like. Often used for basslines or gentler melodies.
|
| 1297 |
+
#
|
| 1298 |
+
# Pulse Width (Square Wave Only): Modifies the character of the Square wave.
|
| 1299 |
+
# - Low (near 0.1) or High (near 0.9): Creates a thin, sharp, or nasal sound. A common choice for classic leads.
|
| 1300 |
+
# - Mid (near 0.5): A "perfect" square wave. The sound is full, round, and most robust.
|
| 1301 |
+
#
|
| 1302 |
+
# Envelope Type: Shapes the volume of each note over its duration.
|
| 1303 |
+
# - Plucky (AD): Creates a percussive, short sound that attacks instantly and then fades. Ideal for fast melodies and arpeggios.
|
| 1304 |
+
# - Sustained (Full Decay): Creates a held-out sound that lasts for the note's full duration. Ideal for pads and atmospheric sounds.
|
| 1305 |
+
#
|
| 1306 |
+
# Decay Time (s): Controls how long a note's sound lasts (in the Plucky envelope).
|
| 1307 |
+
# - Low: Very short, staccato notes.
|
| 1308 |
+
# - High: Longer, more resonant notes that can bleed into each other.
|
| 1309 |
+
#
|
| 1310 |
+
# Bass Boost Level: Mixes in a sub-octave (a square wave one octave lower).
|
| 1311 |
+
# - Low (or 0): The pure, original waveform.
|
| 1312 |
+
# - High: Adds significant weight, thickness, and power to the sound.
|
| 1313 |
+
#
|
| 1314 |
+
# --- Modulation & Performance ---
|
| 1315 |
+
#
|
| 1316 |
+
# Vibrato Rate (Hz): The SPEED of the pitch wobble.
|
| 1317 |
+
# - Low: A slow, gentle wavering effect.
|
| 1318 |
+
# - High (8Hz+): A fast, frantic buzzing or trembling effect. Can create "ring-mod" style sounds at extreme values.
|
| 1319 |
+
#
|
| 1320 |
+
# Vibrato Depth (Hz): The INTENSITY of the pitch wobble.
|
| 1321 |
+
# - Low (or 0): A very subtle effect, or no vibrato at all.
|
| 1322 |
+
# - High: An extreme, dramatic pitch bend. Can sound chaotic or like a siren at extreme values.
|
| 1323 |
+
#
|
| 1324 |
+
# Smooth Notes (Checkbox):
|
| 1325 |
+
# - Enabled: Applies a tiny fade-in/out to reduce clicking artifacts. Makes the sound slightly softer but cleaner.
|
| 1326 |
+
# - Disabled: More abrupt, harsh note onsets. Can be desirable for an aggressive sound.
|
| 1327 |
+
#
|
| 1328 |
+
# Continuous Vibrato (Checkbox):
|
| 1329 |
+
# - Enabled: The vibrato is smooth and connected across a musical phrase, creating a "singing" or legato effect.
|
| 1330 |
+
# - Disabled: The vibrato resets on each new note, creating a bouncy, per-note, staccato effect (key for the "Mario" style).
|
| 1331 |
+
#
|
| 1332 |
+
# --- FX & Advanced Synthesis ---
|
| 1333 |
+
#
|
| 1334 |
+
# Noise Level: Mixes in white noise with the main waveform.
|
| 1335 |
+
# - Low (or 0): No noise.
|
| 1336 |
+
# - High: Adds "air," "grit," or a "hissing" quality. Essential for simulating percussion or creating wind-like sound effects.
|
| 1337 |
+
#
|
| 1338 |
+
# Distortion Level: Applies a wave-shaping algorithm to make the sound harsher.
|
| 1339 |
+
# - Low (or 0): The clean, original sound.
|
| 1340 |
+
# - High: Progressively crushes and saturates the waveform, creating a very aggressive, "fuzzy" or "broken" tone.
|
| 1341 |
+
#
|
| 1342 |
+
# FM Depth (Frequency Modulation): Controls the intensity of the frequency modulation.
|
| 1343 |
+
# - Low (or 0): No FM effect.
|
| 1344 |
+
# - High: The main frequency is more heavily altered by the FM Rate, creating complex, bell-like, metallic, or dissonant tones.
|
| 1345 |
+
#
|
| 1346 |
+
# FM Rate (Frequency Modulation): Controls the speed of the modulating oscillator.
|
| 1347 |
+
# - Low: Creates a slow, vibrato-like or "wobbling" FM effect.
|
| 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 |
+
# --- ADDED: Preset selector dropdown ---
|
| 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."
|
| 1358 |
+
)
|
| 1359 |
+
|
| 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.1, label="Bass Boost Level", info="Adjusts the volume of the sub-octave. 0 is off.")
|
| 1367 |
+
s8bit_smooth_notes = gr.Checkbox(value=True, label="Smooth Notes", info="Applies a tiny fade-in/out to notes to reduce clicking.")
|
| 1368 |
+
s8bit_continuous_vibrato = gr.Checkbox(value=True, label="Continuous Vibrato", info="Prevents vibrato from resetting on each note.")
|
| 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)
|
|
|
|
| 1402 |
output_midi = gr.File(label="Download Processed MIDI File", file_types=[".mid"])
|
| 1403 |
output_midi_md5 = gr.Textbox(label="Output MIDI MD5 Hash")
|
| 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 |
enable_stereo_processing,
|
|
|
|
| 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 |
+
s8bit_smooth_notes, s8bit_continuous_vibrato,
|
| 1419 |
+
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
| 1420 |
]
|
| 1421 |
all_outputs = [
|
| 1422 |
output_midi_md5, output_midi_title, output_midi_summary,
|
| 1423 |
output_midi, output_audio, output_plot, output_song_description
|
| 1424 |
]
|
| 1425 |
|
| 1426 |
+
# Define the output components for the preset updater function.
|
| 1427 |
+
s8bit_updater_outputs = [
|
| 1428 |
+
s8bit_waveform_type, s8bit_pulse_width, s8bit_envelope_type,
|
| 1429 |
+
s8bit_decay_time_s, s8bit_vibrato_rate, s8bit_vibrato_depth,
|
| 1430 |
+
s8bit_smooth_notes, s8bit_continuous_vibrato, s8bit_bass_boost_level,
|
| 1431 |
+
s8bit_noise_level, s8bit_distortion_level, s8bit_fm_modulation_depth, s8bit_fm_modulation_rate
|
| 1432 |
+
]
|
| 1433 |
+
|
| 1434 |
# --- Event Handling ---
|
| 1435 |
submit_btn.click(
|
| 1436 |
process_and_render_file,
|
|
|
|
| 1449 |
inputs=[transcription_method, soundfont_bank],
|
| 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=s8bit_updater_outputs
|
| 1461 |
+
)
|
| 1462 |
+
|
| 1463 |
|
| 1464 |
# Launch the Gradio app
|
| 1465 |
app.queue().launch(inbrowser=True, debug=True)
|