avans06 commited on
Commit
e835c5f
·
1 Parent(s): b114cd4

feat(synth): Enhance 8-bit synthesizer and expand presets

Browse files

This 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.

Files changed (1) hide show
  1. app.py +375 -31
app.py CHANGED
@@ -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, vibrato_rate, vibrato_depth, bass_boost_level, fs=44100):
 
 
 
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: pan_l, pan_r = 1.0, 0.0 # Left
192
- elif i == 1: pan_l, pan_r = 0.0, 1.0 # Right
 
 
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 == 0:
200
  continue
201
 
202
- t = np.linspace(0., note_duration, num_samples, endpoint=False)
203
-
204
  # --- Vibrato LFO ---
205
- vibrato_lfo = vibrato_depth * np.sin(2 * np.pi * vibrato_rate * t)
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
- # --- Waveform Generation (Main Oscillator) ---
208
  if waveform_type == 'Square':
209
- note_waveform = signal.square(2 * np.pi * (freq + vibrato_lfo) * t, duty=pulse_width)
210
  elif waveform_type == 'Sawtooth':
211
- note_waveform = signal.sawtooth(2 * np.pi * (freq + vibrato_lfo) * t)
212
  elif waveform_type == 'Triangle':
213
- note_waveform = signal.sawtooth(2 * np.pi * (freq + vibrato_lfo) * t, width=0.5)
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
- bass_sub_waveform = signal.square(2 * np.pi * bass_freq * t, duty=0.5)
 
 
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)' and num_samples > 0:
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)' and num_samples > 0:
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
- # --- NEW: 8-bit Synthesizer Settings ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.5, value=0.1, step=0.01, label="Decay Time (s)")
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
- # --- Define all input components for the click event ---
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)