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)
|