feat(synth): Add advanced frequency management for 8-bit synth MIDI and effects
Browse filesThis commit introduces a suite of new tools specifically for the **8-bit synthesizer**, providing fine-grained control over the frequency spectrum to address issues with both excessive harshness in the high-end and muddiness in the low-end.
These features empower users to create cleaner, more balanced, and professional-sounding mixes directly within the synthesizer.
**1. MIDI Pre-processing (For 8-bit Synth): Low-Pitch Management**
Complements the existing high-pitch taming by adding a new "Low-Pitch Attenuation" rule.
Users can now define a low pitch threshold (e.g., C2) and a velocity scale.
The pre-processor will automatically reduce the velocity of any notes falling below this threshold before they are sent to the **8-bit synthesizer**.
This is a preventative measure to control excessive sub-bass energy, reduce muddiness, and prevent clipping.
**2. Delay/Echo Effect (For 8-bit Synth): Full Frequency Spectrum Control**
The Delay/Echo effect has been upgraded with a comprehensive "Frequency Management" toolkit, allowing users to shape the timbre of the echoes generated by the **8-bit synthesizer**.
**High-Pass Filter:** A configurable high-pass filter can be applied to the echo layer, cleanly removing low frequencies to prevent echoes from adding mud to the mix.
**Low-Pass Filter:** A new, corresponding low-pass filter can be applied to the echo layer, removing high frequencies to make echoes sound darker, warmer, and less harsh.
**Flexible Pitch Shifting (Transposer):** The previous fixed octave checkboxes have been replaced with powerful pitch-shifting sliders for both bass and treble notes, allowing for the creation of complex harmonies in the echo trails.
The audio filtering pipeline in `Render_MIDI` was refactored to support this multi-filter audio processing on the dedicated echo layer.
|
@@ -182,6 +182,10 @@ class AppParameters:
|
|
| 182 |
s8bit_enable_midi_preprocessing: bool = True # Master switch for this feature
|
| 183 |
s8bit_high_pitch_threshold: int = 84 # Pitch (C6) above which velocity is scaled
|
| 184 |
s8bit_high_pitch_velocity_scale: float = 0.8 # Velocity multiplier for high notes (e.g., 80%)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
s8bit_chord_density_threshold: int = 4 # Min number of notes to be considered a dense chord
|
| 186 |
s8bit_chord_velocity_threshold: int = 100 # Min average velocity for a chord to be tamed
|
| 187 |
s8bit_chord_velocity_scale: float = 0.75 # Velocity multiplier for loud, dense chords
|
|
@@ -202,6 +206,12 @@ class AppParameters:
|
|
| 202 |
s8bit_delay_division: str = "Dotted 8th Note"
|
| 203 |
s8bit_delay_feedback: float = 0.5 # Velocity scale for each subsequent echo (50%)
|
| 204 |
s8bit_delay_repeats: int = 3 # Number of echoes to generate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
# =================================================================================================
|
| 207 |
# === Helper Functions ===
|
|
@@ -341,32 +351,42 @@ def format_params_for_metadata(params: AppParameters, transcription_log: dict =
|
|
| 341 |
def preprocess_midi_for_harshness(midi_data: pretty_midi.PrettyMIDI, params: AppParameters):
|
| 342 |
"""
|
| 343 |
Analyzes and modifies a PrettyMIDI object in-place to reduce characteristics
|
| 344 |
-
that can cause harshness in simple synthesizers.
|
|
|
|
| 345 |
|
| 346 |
Args:
|
| 347 |
midi_data: The PrettyMIDI object to process.
|
| 348 |
params: The AppParameters object containing the control thresholds.
|
| 349 |
"""
|
| 350 |
-
print("Running MIDI pre-processing to reduce harshness...")
|
| 351 |
-
|
|
|
|
| 352 |
chords_tamed = 0
|
| 353 |
-
|
| 354 |
-
# Rule 1: High
|
| 355 |
for instrument in midi_data.instruments:
|
| 356 |
for note in instrument.notes:
|
|
|
|
| 357 |
if note.pitch > params.s8bit_high_pitch_threshold:
|
| 358 |
-
original_velocity = note.velocity
|
| 359 |
note.velocity = int(note.velocity * params.s8bit_high_pitch_velocity_scale)
|
| 360 |
if note.velocity < 1: note.velocity = 1
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
|
| 366 |
-
# Rule
|
| 367 |
# This is a simplified approach: group notes by near-simultaneous start times
|
| 368 |
all_notes = sorted([note for instrument in midi_data.instruments for note in instrument.notes], key=lambda x: x.start)
|
| 369 |
-
|
| 370 |
time_window = 0.02 # 20ms window to group notes into a chord
|
| 371 |
i = 0
|
| 372 |
while i < len(all_notes):
|
|
@@ -660,12 +680,21 @@ def create_delay_effect(midi_data: pretty_midi.PrettyMIDI, params: AppParameters
|
|
| 660 |
print(" - No notes found to apply delay to. Skipping.")
|
| 661 |
return processed_midi
|
| 662 |
|
| 663 |
-
# --- Step 3: Generate echo notes using the calculated delay time ---
|
| 664 |
echo_notes = []
|
|
|
|
|
|
|
|
|
|
| 665 |
for i in range(1, params.s8bit_delay_repeats + 1):
|
| 666 |
for original_note in notes_to_echo:
|
| 667 |
# Create a copy of the note for the echo
|
| 668 |
echo_note = copy.copy(original_note)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
|
| 670 |
# Use the tempo-synced time and velocity
|
| 671 |
time_offset = i * delay_time_s
|
|
@@ -696,6 +725,42 @@ def create_delay_effect(midi_data: pretty_midi.PrettyMIDI, params: AppParameters
|
|
| 696 |
return processed_midi
|
| 697 |
|
| 698 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
def one_pole_lowpass(x, cutoff_hz, fs):
|
| 700 |
"""Simple one-pole lowpass filter (causal), stable and cheap."""
|
| 701 |
if cutoff_hz <= 0 or cutoff_hz >= fs/2:
|
|
@@ -1832,18 +1897,83 @@ def Render_MIDI(*, input_midi_path: str, params: AppParameters, progress: gr.Pro
|
|
| 1832 |
arpeggiated_midi = arpeggiate_midi(base_midi, params)
|
| 1833 |
|
| 1834 |
# --- Step 2: Render the main (original) layer ---
|
| 1835 |
-
print(" - Rendering main synthesis layer...")
|
| 1836 |
# Synthesize the waveform, passing new FX parameters to the synthesis function
|
| 1837 |
-
|
| 1838 |
midi_data=base_midi,
|
| 1839 |
fs=srate,
|
| 1840 |
params=params,
|
| 1841 |
progress=progress
|
| 1842 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1843 |
|
| 1844 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1845 |
|
| 1846 |
-
# --- Step
|
| 1847 |
if arpeggiated_midi and arpeggiated_midi.instruments:
|
| 1848 |
print(" - Rendering and mixing arpeggiator layer...")
|
| 1849 |
# Temporarily override panning for the arpeggiator synth call
|
|
@@ -3764,6 +3894,28 @@ if __name__ == "__main__":
|
|
| 3764 |
label="Number of Repeats",
|
| 3765 |
info="The total number of echoes to generate for each note."
|
| 3766 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3767 |
|
| 3768 |
# --- Section 2: MIDI Pre-processing (Corrective Tool) ---
|
| 3769 |
with gr.Accordion("MIDI Pre-processing (Corrective Tool)", open=False):
|
|
@@ -3783,6 +3935,17 @@ if __name__ == "__main__":
|
|
| 3783 |
label="High Pitch Velocity Scale",
|
| 3784 |
info="Multiplier for high notes' velocity (e.g., 0.8 = 80% of original velocity)."
|
| 3785 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3786 |
s8bit_chord_density_threshold = gr.Slider(
|
| 3787 |
2, 10, value=4, step=1,
|
| 3788 |
label="Chord Density Threshold",
|
|
|
|
| 182 |
s8bit_enable_midi_preprocessing: bool = True # Master switch for this feature
|
| 183 |
s8bit_high_pitch_threshold: int = 84 # Pitch (C6) above which velocity is scaled
|
| 184 |
s8bit_high_pitch_velocity_scale: float = 0.8 # Velocity multiplier for high notes (e.g., 80%)
|
| 185 |
+
# --- Low-pitch management parameters ---
|
| 186 |
+
s8bit_low_pitch_threshold: int = 36 # Low pitch threshold (C2)
|
| 187 |
+
s8bit_low_pitch_velocity_scale: float = 0.9 # Low pitch velocity scale
|
| 188 |
+
|
| 189 |
s8bit_chord_density_threshold: int = 4 # Min number of notes to be considered a dense chord
|
| 190 |
s8bit_chord_velocity_threshold: int = 100 # Min average velocity for a chord to be tamed
|
| 191 |
s8bit_chord_velocity_scale: float = 0.75 # Velocity multiplier for loud, dense chords
|
|
|
|
| 206 |
s8bit_delay_division: str = "Dotted 8th Note"
|
| 207 |
s8bit_delay_feedback: float = 0.5 # Velocity scale for each subsequent echo (50%)
|
| 208 |
s8bit_delay_repeats: int = 3 # Number of echoes to generate
|
| 209 |
+
# --- NEW: Low-End Management for Delay ---
|
| 210 |
+
s8bit_delay_highpass_cutoff_hz: int = 100 # High-pass filter frequency for delay echoes (removes low-end rumble from echoes)
|
| 211 |
+
s8bit_delay_bass_pitch_shift: int = 0 # Pitch shift (in semitones) applied to low notes in delay echoes
|
| 212 |
+
# --- High-End Management for Delay ---
|
| 213 |
+
s8bit_delay_lowpass_cutoff_hz: int = 5000 # Lowpass filter frequency for delay echoes (removes harsh high frequencies from echoes)
|
| 214 |
+
s8bit_delay_treble_pitch_shift: int = 0 # Pitch shift (in semitones) applied to high notes in delay echoes
|
| 215 |
|
| 216 |
# =================================================================================================
|
| 217 |
# === Helper Functions ===
|
|
|
|
| 351 |
def preprocess_midi_for_harshness(midi_data: pretty_midi.PrettyMIDI, params: AppParameters):
|
| 352 |
"""
|
| 353 |
Analyzes and modifies a PrettyMIDI object in-place to reduce characteristics
|
| 354 |
+
that can cause harshness or muddiness in simple synthesizers.
|
| 355 |
+
Now includes both high and low pitch attenuation.
|
| 356 |
|
| 357 |
Args:
|
| 358 |
midi_data: The PrettyMIDI object to process.
|
| 359 |
params: The AppParameters object containing the control thresholds.
|
| 360 |
"""
|
| 361 |
+
print("Running MIDI pre-processing to reduce harshness and muddiness...")
|
| 362 |
+
high_notes_tamed = 0
|
| 363 |
+
low_notes_tamed = 0
|
| 364 |
chords_tamed = 0
|
| 365 |
+
|
| 366 |
+
# Rule 1 & 2: High and Low Pitch Attenuation
|
| 367 |
for instrument in midi_data.instruments:
|
| 368 |
for note in instrument.notes:
|
| 369 |
+
# Tame very high notes to reduce harshness/aliasing
|
| 370 |
if note.pitch > params.s8bit_high_pitch_threshold:
|
|
|
|
| 371 |
note.velocity = int(note.velocity * params.s8bit_high_pitch_velocity_scale)
|
| 372 |
if note.velocity < 1: note.velocity = 1
|
| 373 |
+
high_notes_tamed += 1
|
| 374 |
+
|
| 375 |
+
# Tame very low notes to reduce muddiness/rumble
|
| 376 |
+
if note.pitch < params.s8bit_low_pitch_threshold:
|
| 377 |
+
note.velocity = int(note.velocity * params.s8bit_low_pitch_velocity_scale)
|
| 378 |
+
if note.velocity < 1: note.velocity = 1
|
| 379 |
+
low_notes_tamed += 1
|
| 380 |
+
|
| 381 |
+
if high_notes_tamed > 0:
|
| 382 |
+
print(f" - Tamed {high_notes_tamed} individual high-pitched notes.")
|
| 383 |
+
if low_notes_tamed > 0:
|
| 384 |
+
print(f" - Tamed {low_notes_tamed} individual low-pitched notes.")
|
| 385 |
|
| 386 |
+
# Rule 3: Chord Compression
|
| 387 |
# This is a simplified approach: group notes by near-simultaneous start times
|
| 388 |
all_notes = sorted([note for instrument in midi_data.instruments for note in instrument.notes], key=lambda x: x.start)
|
| 389 |
+
|
| 390 |
time_window = 0.02 # 20ms window to group notes into a chord
|
| 391 |
i = 0
|
| 392 |
while i < len(all_notes):
|
|
|
|
| 680 |
print(" - No notes found to apply delay to. Skipping.")
|
| 681 |
return processed_midi
|
| 682 |
|
| 683 |
+
# --- Step 3: Generate echo notes with optional octave shift using the calculated delay time ---
|
| 684 |
echo_notes = []
|
| 685 |
+
bass_note_threshold = 48 # MIDI note for C3
|
| 686 |
+
treble_note_threshold = 84 # MIDI note for C6
|
| 687 |
+
|
| 688 |
for i in range(1, params.s8bit_delay_repeats + 1):
|
| 689 |
for original_note in notes_to_echo:
|
| 690 |
# Create a copy of the note for the echo
|
| 691 |
echo_note = copy.copy(original_note)
|
| 692 |
+
|
| 693 |
+
# --- Octave Shift Logic for both Bass and Treble ---
|
| 694 |
+
if params.s8bit_delay_bass_pitch_shift and original_note.pitch < bass_note_threshold:
|
| 695 |
+
echo_note.pitch += params.s8bit_delay_bass_pitch_shift
|
| 696 |
+
elif params.s8bit_delay_treble_pitch_shift and original_note.pitch > treble_note_threshold:
|
| 697 |
+
echo_note.pitch += params.s8bit_delay_treble_pitch_shift
|
| 698 |
|
| 699 |
# Use the tempo-synced time and velocity
|
| 700 |
time_offset = i * delay_time_s
|
|
|
|
| 725 |
return processed_midi
|
| 726 |
|
| 727 |
|
| 728 |
+
def butter_highpass(cutoff, fs, order=5):
|
| 729 |
+
nyq = 0.5 * fs
|
| 730 |
+
normal_cutoff = cutoff / nyq
|
| 731 |
+
b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
|
| 732 |
+
return b, a
|
| 733 |
+
|
| 734 |
+
def apply_butter_highpass_filter(data, cutoff, fs, order=5):
|
| 735 |
+
"""Applies a Butterworth highpass filter to a stereo audio signal."""
|
| 736 |
+
if cutoff <= 0:
|
| 737 |
+
return data
|
| 738 |
+
b, a = butter_highpass(cutoff, fs, order=order)
|
| 739 |
+
# Apply filter to each channel independently
|
| 740 |
+
filtered_data = np.zeros_like(data)
|
| 741 |
+
for channel in range(data.shape[1]):
|
| 742 |
+
filtered_data[:, channel] = signal.lfilter(b, a, data[:, channel])
|
| 743 |
+
return filtered_data
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
def butter_lowpass(cutoff, fs, order=5):
|
| 747 |
+
nyq = 0.5 * fs
|
| 748 |
+
normal_cutoff = cutoff / nyq
|
| 749 |
+
b, a = signal.butter(order, normal_cutoff, btype='low', analog=False)
|
| 750 |
+
return b, a
|
| 751 |
+
|
| 752 |
+
def apply_butter_lowpass_filter(data, cutoff, fs, order=5):
|
| 753 |
+
"""Applies a Butterworth lowpass filter to a stereo audio signal."""
|
| 754 |
+
# A cutoff at or above Nyquist frequency is pointless
|
| 755 |
+
if cutoff >= fs / 2:
|
| 756 |
+
return data
|
| 757 |
+
b, a = butter_lowpass(cutoff, fs, order=order)
|
| 758 |
+
filtered_data = np.zeros_like(data)
|
| 759 |
+
for channel in range(data.shape[1]):
|
| 760 |
+
filtered_data[:, channel] = signal.lfilter(b, a, data[:, channel])
|
| 761 |
+
return filtered_data
|
| 762 |
+
|
| 763 |
+
|
| 764 |
def one_pole_lowpass(x, cutoff_hz, fs):
|
| 765 |
"""Simple one-pole lowpass filter (causal), stable and cheap."""
|
| 766 |
if cutoff_hz <= 0 or cutoff_hz >= fs/2:
|
|
|
|
| 1897 |
arpeggiated_midi = arpeggiate_midi(base_midi, params)
|
| 1898 |
|
| 1899 |
# --- Step 2: Render the main (original) layer ---
|
| 1900 |
+
print(" - Rendering main synthesis layer (including echoes)...")
|
| 1901 |
# Synthesize the waveform, passing new FX parameters to the synthesis function
|
| 1902 |
+
main_and_echo_waveform = synthesize_8bit_style(
|
| 1903 |
midi_data=base_midi,
|
| 1904 |
fs=srate,
|
| 1905 |
params=params,
|
| 1906 |
progress=progress
|
| 1907 |
)
|
| 1908 |
+
|
| 1909 |
+
# --- Isolate and filter the echo part if it exists ---
|
| 1910 |
+
echo_instrument = None
|
| 1911 |
+
for inst in base_midi.instruments:
|
| 1912 |
+
if inst.name == "Echo Layer":
|
| 1913 |
+
echo_instrument = inst
|
| 1914 |
+
break
|
| 1915 |
|
| 1916 |
+
# --- Step 3: Render the delay layers (if enabled) ---
|
| 1917 |
+
if echo_instrument:
|
| 1918 |
+
print(" - Processing echo layer audio effects...")
|
| 1919 |
+
# Create a temporary MIDI object with ONLY the echo instrument
|
| 1920 |
+
echo_only_midi = pretty_midi.PrettyMIDI()
|
| 1921 |
+
echo_only_midi.instruments.append(echo_instrument)
|
| 1922 |
+
|
| 1923 |
+
# Render ONLY the echo layer to an audio waveform
|
| 1924 |
+
echo_waveform_raw = synthesize_8bit_style(midi_data=echo_only_midi, fs=srate, params=params)
|
| 1925 |
+
|
| 1926 |
+
# --- Start of the Robust Filtering Block ---
|
| 1927 |
+
# Apply both High-Pass and Low-Pass filters
|
| 1928 |
+
unfiltered_echo = echo_waveform_raw
|
| 1929 |
+
filtered_echo = echo_waveform_raw
|
| 1930 |
+
|
| 1931 |
+
# --- Apply Filters if requested ---
|
| 1932 |
+
# Convert to a format filter function expects (samples, channels)
|
| 1933 |
+
# This is inefficient, we should only do it once.
|
| 1934 |
+
# Let's assume the filter functions are adapted to take (channels, samples)
|
| 1935 |
+
# For now, we'll keep the transpose for simplicity.
|
| 1936 |
+
# We will apply filters on a temporary copy to avoid chaining issues.
|
| 1937 |
+
temp_filtered_echo = echo_waveform_raw.T
|
| 1938 |
+
|
| 1939 |
+
should_filter = False
|
| 1940 |
+
# Apply High-Pass Filter
|
| 1941 |
+
if params.s8bit_delay_highpass_cutoff_hz > 0:
|
| 1942 |
+
print(f" - Applying high-pass filter at {params.s8bit_delay_highpass_cutoff_hz} Hz...")
|
| 1943 |
+
temp_filtered_echo = apply_butter_highpass_filter(temp_filtered_echo, params.s8bit_delay_highpass_cutoff_hz, srate)
|
| 1944 |
+
should_filter = True
|
| 1945 |
+
|
| 1946 |
+
# Apply Low-Pass Filter
|
| 1947 |
+
if params.s8bit_delay_lowpass_cutoff_hz < srate / 2:
|
| 1948 |
+
print(f" - Applying low-pass filter at {params.s8bit_delay_lowpass_cutoff_hz} Hz...")
|
| 1949 |
+
temp_filtered_echo = apply_butter_lowpass_filter(temp_filtered_echo, params.s8bit_delay_lowpass_cutoff_hz, srate)
|
| 1950 |
+
should_filter = True
|
| 1951 |
+
|
| 1952 |
+
# Convert back and get the difference
|
| 1953 |
+
if should_filter:
|
| 1954 |
+
filtered_echo = temp_filtered_echo.T
|
| 1955 |
+
|
| 1956 |
+
# To avoid re-rendering, we subtract the unfiltered echo and add the filtered one
|
| 1957 |
+
# Ensure all waveforms have the same length before math ---
|
| 1958 |
+
target_length = main_and_echo_waveform.shape[1]
|
| 1959 |
+
|
| 1960 |
+
# Pad the unfiltered echo if it's shorter
|
| 1961 |
+
len_unfiltered = unfiltered_echo.shape[1]
|
| 1962 |
+
if len_unfiltered < target_length:
|
| 1963 |
+
unfiltered_echo = np.pad(unfiltered_echo, ((0, 0), (0, target_length - len_unfiltered)))
|
| 1964 |
+
|
| 1965 |
+
# Pad the filtered echo if it's shorter
|
| 1966 |
+
len_filtered = filtered_echo.shape[1]
|
| 1967 |
+
if len_filtered < target_length:
|
| 1968 |
+
filtered_echo = np.pad(filtered_echo, ((0, 0), (0, target_length - len_filtered)))
|
| 1969 |
+
|
| 1970 |
+
# Now that all shapes are guaranteed to be identical, perform the operation.
|
| 1971 |
+
main_and_echo_waveform -= unfiltered_echo[:, :target_length]
|
| 1972 |
+
main_and_echo_waveform += filtered_echo[:, :target_length]
|
| 1973 |
+
|
| 1974 |
+
final_waveform = main_and_echo_waveform
|
| 1975 |
|
| 1976 |
+
# --- Step 4: Render the arpeggiator layer (if enabled) ---
|
| 1977 |
if arpeggiated_midi and arpeggiated_midi.instruments:
|
| 1978 |
print(" - Rendering and mixing arpeggiator layer...")
|
| 1979 |
# Temporarily override panning for the arpeggiator synth call
|
|
|
|
| 3894 |
label="Number of Repeats",
|
| 3895 |
info="The total number of echoes to generate for each note."
|
| 3896 |
)
|
| 3897 |
+
# --- UI controls for low-end management ---
|
| 3898 |
+
s8bit_delay_highpass_cutoff_hz = gr.Slider(
|
| 3899 |
+
0, 500, value=100, step=10,
|
| 3900 |
+
label="Echo High-Pass Filter (Hz)",
|
| 3901 |
+
info="Filters out low frequencies from the echoes to prevent muddiness. Set to 0 to disable. 80-120Hz is a good range to clean up bass."
|
| 3902 |
+
)
|
| 3903 |
+
s8bit_delay_bass_pitch_shift = gr.Slider(
|
| 3904 |
+
-12, 24, value=12, step=1,
|
| 3905 |
+
label="Echo Pitch Shift for Low Notes (Semitones)",
|
| 3906 |
+
info="Shifts the pitch of echoes for very low notes (below C3). +12 is one octave up, +7 is a perfect fifth. 0 to disable."
|
| 3907 |
+
)
|
| 3908 |
+
# --- UI controls for high-end management ---
|
| 3909 |
+
s8bit_delay_lowpass_cutoff_hz = gr.Slider(
|
| 3910 |
+
1000, 20000, value=5000, step=500,
|
| 3911 |
+
label="Echo Low-Pass Filter (Hz)",
|
| 3912 |
+
info="Filters out high frequencies from the echoes to reduce harshness. Set to 20000 to disable. 4k-8kHz is a good range to make echoes sound 'darker'."
|
| 3913 |
+
)
|
| 3914 |
+
s8bit_delay_treble_pitch_shift = gr.Slider(
|
| 3915 |
+
-24, 12, value=-12, step=1,
|
| 3916 |
+
label="Echo Pitch Shift for High Notes (Semitones)",
|
| 3917 |
+
info="Shifts the pitch of echoes for very high notes (above C6). -12 is one octave down. 0 to disable."
|
| 3918 |
+
)
|
| 3919 |
|
| 3920 |
# --- Section 2: MIDI Pre-processing (Corrective Tool) ---
|
| 3921 |
with gr.Accordion("MIDI Pre-processing (Corrective Tool)", open=False):
|
|
|
|
| 3935 |
label="High Pitch Velocity Scale",
|
| 3936 |
info="Multiplier for high notes' velocity (e.g., 0.8 = 80% of original velocity)."
|
| 3937 |
)
|
| 3938 |
+
# --- UI controls for low-pitch management ---
|
| 3939 |
+
s8bit_low_pitch_threshold = gr.Slider(
|
| 3940 |
+
21, 60, value=36, step=1,
|
| 3941 |
+
label="Low Pitch Threshold (MIDI Note)",
|
| 3942 |
+
info="Notes below this pitch will have their velocity reduced to prevent muddiness. 36 = C2."
|
| 3943 |
+
)
|
| 3944 |
+
s8bit_low_pitch_velocity_scale = gr.Slider(
|
| 3945 |
+
0.1, 1.0, value=0.9, step=0.05,
|
| 3946 |
+
label="Low Pitch Velocity Scale",
|
| 3947 |
+
info="Multiplier for low notes' velocity. Use this to gently tame excessive sub-bass."
|
| 3948 |
+
)
|
| 3949 |
s8bit_chord_density_threshold = gr.Slider(
|
| 3950 |
2, 10, value=4, step=1,
|
| 3951 |
label="Chord Density Threshold",
|