avans06 commited on
Commit
5cb75be
·
1 Parent(s): 7339550

This commit delivers three key improvements focused on user experience and bug fixing.

Browse files

1. **Add Rich Info Text to 8-bit Synthesizer UI**

2. **Detect and Log Stereo MIDI Information**

3. **Fix Stereo Loss in "Convert to Solo Piano":**
- Resolved a critical bug where enabling "Convert to Solo Piano" would collapse stereo MIDI files into mono.

Files changed (2) hide show
  1. app.py +115 -15
  2. src/TMIDIX.py +26 -12
app.py CHANGED
@@ -529,6 +529,44 @@ def merge_midis(midi_path_left, midi_path_right, output_path):
529
  return None
530
 
531
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  # =================================================================================================
533
  # === Stage 1: Audio to MIDI Transcription Functions ===
534
  # =================================================================================================
@@ -1194,6 +1232,10 @@ def run_single_file_pipeline(input_file_path: str, timestamp: str, params: AppPa
1194
  # For MIDI files, we start at 0% and directly proceed to the rendering steps.
1195
  update_progress(0, "MIDI file detected, skipping transcription...")
1196
  print("MIDI file detected. Skipping transcription. Proceeding directly to rendering.")
 
 
 
 
1197
  midi_path_for_rendering = input_file_path
1198
  else:
1199
  temp_dir = "output/temp_transcribe" # Define temp_dir early for the fallback
@@ -2349,23 +2391,81 @@ if __name__ == "__main__":
2349
  # Define the 8-bit UI components in one place for easy reference
2350
  gr.Markdown("### 8-bit Synthesizer Settings")
2351
  with gr.Accordion("8-bit Synthesizer Settings", open=True, visible=False) as synth_8bit_settings:
2352
- s8bit_preset_selector = gr.Dropdown(choices=["Custom", "Auto-Recommend (Analyze MIDI)"] + list(S8BIT_PRESETS.keys()), value="Custom", label="Style Preset",
2353
- 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.")
2354
- s8bit_waveform_type = gr.Dropdown(['Square', 'Sawtooth', 'Triangle'], value='Square', label="Waveform Type")
2355
- s8bit_pulse_width = gr.Slider(0.01, 0.99, value=0.5, step=0.01, label="Pulse Width (Square Wave Only)")
2356
- s8bit_envelope_type = gr.Dropdown(['Plucky (AD Envelope)', 'Sustained (Full Decay)'], value='Plucky (AD Envelope)', label="Envelope Type")
2357
- s8bit_decay_time_s = gr.Slider(0.01, 1.0, value=0.1, step=0.01, label="Decay Time (s)")
2358
- s8bit_vibrato_rate = gr.Slider(0, 20, value=5, label="Vibrato Rate (Hz)")
2359
- s8bit_vibrato_depth = gr.Slider(0, 50, value=0, label="Vibrato Depth (Hz)")
2360
- s8bit_bass_boost_level = gr.Slider(0.0, 1.0, value=0.0, step=0.05, label="Bass Boost Level", info="Adjusts the volume of the sub-octave. 0 is off.")
2361
- s8bit_smooth_notes_level = gr.Slider(0.0, 1.0, value=0.0, step=0.05, label="Smooth Notes Level", info="Level of fade-in/out to reduce clicks. 0=off, 1=max.")
2362
- s8bit_continuous_vibrato_level = gr.Slider(0.0, 1.0, value=0.0, step=0.05, label="Continuous Vibrato Level", info="Controls vibrato continuity. 0=resets per note, 1=fully continuous.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2363
  # --- New accordion for advanced effects ---
2364
  with gr.Accordion("Advanced Synthesis & FX", open=False):
2365
- s8bit_noise_level = gr.Slider(0.0, 1.0, value=0.0, step=0.05, label="Noise Level", info="Mixes in white noise. Great for percussion or adding 'air'.")
2366
- s8bit_distortion_level = gr.Slider(0.0, 0.9, value=0.0, step=0.05, label="Distortion Level", info="Applies wave-shaping distortion for a grittier, harsher sound.")
2367
- s8bit_fm_modulation_depth = gr.Slider(0.0, 1.0, value=0.0, step=0.05, label="FM Depth", info="Depth of Frequency Modulation. Creates complex, metallic, or bell-like tones.")
2368
- s8bit_fm_modulation_rate = gr.Slider(0.0, 500.0, value=0.0, step=1.0, label="FM Rate", info="Rate of Frequency Modulation. Higher values create brighter, more complex harmonics.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2369
 
2370
  # Create a dictionary mapping key names to the actual Gradio components
2371
  ui_component_map = locals()
 
529
  return None
530
 
531
 
532
+ def is_stereo_midi(midi_path: str) -> bool:
533
+ """
534
+ Checks if a MIDI file contains the specific stereo panning control changes
535
+ (hard left and hard right) created by the merge_midis function.
536
+
537
+ Args:
538
+ midi_path (str): The file path to the MIDI file.
539
+
540
+ Returns:
541
+ bool: True if both hard-left (0) and hard-right (127) pan controls are found, False otherwise.
542
+ """
543
+ try:
544
+ midi_data = pretty_midi.PrettyMIDI(midi_path)
545
+
546
+ found_left_pan = False
547
+ found_right_pan = False
548
+
549
+ for instrument in midi_data.instruments:
550
+ for control_change in instrument.control_changes:
551
+ # MIDI Controller Number 10 is for Panning.
552
+ if control_change.number == 10:
553
+ if control_change.value == 0:
554
+ found_left_pan = True
555
+ elif control_change.value == 127:
556
+ found_right_pan = True
557
+
558
+ # Optimization: If we've already found both, no need to check further.
559
+ if found_left_pan and found_right_pan:
560
+ return True
561
+
562
+ return found_left_pan and found_right_pan
563
+
564
+ except Exception as e:
565
+ # If the MIDI file is invalid or another error occurs, assume it's not our special stereo format.
566
+ print(f"Could not analyze MIDI for stereo info: {e}")
567
+ return False
568
+
569
+
570
  # =================================================================================================
571
  # === Stage 1: Audio to MIDI Transcription Functions ===
572
  # =================================================================================================
 
1232
  # For MIDI files, we start at 0% and directly proceed to the rendering steps.
1233
  update_progress(0, "MIDI file detected, skipping transcription...")
1234
  print("MIDI file detected. Skipping transcription. Proceeding directly to rendering.")
1235
+
1236
+ if is_stereo_midi(input_file_path):
1237
+ print("\nINFO: Stereo pan information (Left/Right) detected in the input MIDI. It will be rendered in stereo.\n")
1238
+
1239
  midi_path_for_rendering = input_file_path
1240
  else:
1241
  temp_dir = "output/temp_transcribe" # Define temp_dir early for the fallback
 
2391
  # Define the 8-bit UI components in one place for easy reference
2392
  gr.Markdown("### 8-bit Synthesizer Settings")
2393
  with gr.Accordion("8-bit Synthesizer Settings", open=True, visible=False) as synth_8bit_settings:
2394
+ s8bit_preset_selector = gr.Dropdown(
2395
+ choices=["Custom", "Auto-Recommend (Analyze MIDI)"] + list(S8BIT_PRESETS.keys()),
2396
+ value="Custom",
2397
+ label="Style Preset",
2398
+ info="Select a preset to auto-fill the settings below. Choose 'Custom' for manual control or 'Auto-Recommend' to analyze the MIDI.\nFor reference and entertainment only. These presets are not guaranteed to be perfectly accurate."
2399
+ )
2400
+ s8bit_waveform_type = gr.Dropdown(
2401
+ ['Square', 'Sawtooth', 'Triangle'],
2402
+ value='Square',
2403
+ label="Waveform Type",
2404
+ info="The fundamental timbre of the sound. Square is bright and hollow (classic NES), Sawtooth is aggressive and buzzy, Triangle is soft and flute-like."
2405
+ )
2406
+ s8bit_pulse_width = gr.Slider(
2407
+ 0.01, 0.99, value=0.5, step=0.01,
2408
+ label="Pulse Width (Square Wave Only)",
2409
+ info="Changes the character of the Square wave. Low values (~0.1) are thin and nasal, while mid values (~0.5) are full and round."
2410
+ )
2411
+ s8bit_envelope_type = gr.Dropdown(
2412
+ ['Plucky (AD Envelope)', 'Sustained (Full Decay)'],
2413
+ value='Plucky (AD Envelope)',
2414
+ label="Envelope Type",
2415
+ info="Shapes the volume of each note. 'Plucky' is a short, percussive sound. 'Sustained' holds the note for its full duration."
2416
+ )
2417
+ s8bit_decay_time_s = gr.Slider(
2418
+ 0.01, 1.0, value=0.1, step=0.01,
2419
+ label="Decay Time (s)",
2420
+ info="For the 'Plucky' envelope, this is the time it takes for a note to fade to silence. Low values are short and staccato; high values are longer and more resonant."
2421
+ )
2422
+ s8bit_vibrato_rate = gr.Slider(
2423
+ 0, 20, value=5,
2424
+ label="Vibrato Rate (Hz)",
2425
+ info="The SPEED of the pitch wobble. Low values create a slow, gentle waver. High values create a fast, frantic buzz."
2426
+ )
2427
+ s8bit_vibrato_depth = gr.Slider(
2428
+ 0, 50, value=0,
2429
+ label="Vibrato Depth (Hz)",
2430
+ info="The INTENSITY of the pitch wobble. Low values are subtle or off. High values create a dramatic, siren-like pitch bend."
2431
+ )
2432
+ s8bit_bass_boost_level = gr.Slider(
2433
+ 0.0, 1.0, value=0.0, step=0.05,
2434
+ label="Bass Boost Level",
2435
+ info="Mixes in a sub-octave (a square wave one octave lower). Low values have no effect; high values add significant weight and power."
2436
+ )
2437
+ s8bit_smooth_notes_level = gr.Slider(
2438
+ 0.0, 1.0, value=0.0, step=0.05,
2439
+ label="Smooth Notes Level",
2440
+ info="Applies a tiny fade-in/out to reduce clicking. Low values (or 0) give a hard, abrupt attack. High values give a softer, cleaner onset."
2441
+ )
2442
+ s8bit_continuous_vibrato_level = gr.Slider(
2443
+ 0.0, 1.0, value=0.0, step=0.05,
2444
+ label="Continuous Vibrato Level",
2445
+ info="Controls vibrato continuity across notes. Low values (0) reset vibrato on each note (bouncy). High values (1) create a smooth, connected 'singing' vibrato."
2446
+ )
2447
  # --- New accordion for advanced effects ---
2448
  with gr.Accordion("Advanced Synthesis & FX", open=False):
2449
+ s8bit_noise_level = gr.Slider(
2450
+ 0.0, 1.0, value=0.0, step=0.05,
2451
+ label="Noise Level",
2452
+ info="Mixes in white noise with the main waveform. Low values are clean; high values add 'grit', 'air', or a hissing quality, useful for percussion."
2453
+ )
2454
+ s8bit_distortion_level = gr.Slider(
2455
+ 0.0, 0.9, value=0.0, step=0.05,
2456
+ label="Distortion Level",
2457
+ info="Applies wave-shaping to make the sound harsher. Low values are clean; high values create a crushed, 'fuzzy', and aggressive tone."
2458
+ )
2459
+ s8bit_fm_modulation_depth = gr.Slider(
2460
+ 0.0, 1.0, value=0.0, step=0.05,
2461
+ label="FM Depth",
2462
+ info="Frequency Modulation intensity. At low values, there is no effect. At high values, it creates complex, metallic, or bell-like tones."
2463
+ )
2464
+ s8bit_fm_modulation_rate = gr.Slider(
2465
+ 0.0, 500.0, value=0.0, step=1.0,
2466
+ label="FM Rate",
2467
+ info="Frequency Modulation speed. Low values create a slow 'wobble'. High values create fast modulation, resulting in bright, dissonant harmonics."
2468
+ )
2469
 
2470
  # Create a dictionary mapping key names to the actual Gradio components
2471
  ui_component_map = locals()
src/TMIDIX.py CHANGED
@@ -6048,32 +6048,46 @@ def solo_piano_escore_notes(escore_notes,
6048
  patches_index=6,
6049
  keep_drums=False,
6050
  ):
6051
-
 
 
 
 
 
6052
  cscore = chordify_score([1000, escore_notes])
6053
 
6054
  sp_escore_notes = []
6055
 
6056
  for c in cscore:
6057
-
6058
- seen = []
6059
  chord = []
6060
 
6061
  for cc in c:
6062
 
6063
- if cc[channels_index] != 9:
6064
- if cc[pitches_index] not in seen:
6065
-
6066
- cc[channels_index] = 0
6067
- cc[patches_index] = 0
 
 
 
 
 
 
 
6068
 
6069
  chord.append(cc)
6070
- seen.append(cc[pitches_index])
6071
 
6072
- else:
6073
  if keep_drums:
6074
- if cc[pitches_index]+128 not in seen:
 
 
6075
  chord.append(cc)
6076
- seen.append(cc[pitches_index]+128)
6077
 
6078
  sp_escore_notes.append(chord)
6079
 
 
6048
  patches_index=6,
6049
  keep_drums=False,
6050
  ):
6051
+ """
6052
+ A modified version of TMIDIX.solo_piano_escore_notes that preserves the
6053
+ original MIDI channel of each note. This allows stereo panning information,
6054
+ which is often channel-dependent, to be maintained during the conversion
6055
+ to a solo piano performance.
6056
+ """
6057
  cscore = chordify_score([1000, escore_notes])
6058
 
6059
  sp_escore_notes = []
6060
 
6061
  for c in cscore:
6062
+ # --- Use a set to store (pitch, channel) tuples for uniqueness ---
6063
+ seen_notes = set()
6064
  chord = []
6065
 
6066
  for cc in c:
6067
 
6068
+ if cc[channels_index] != 9: # If not a drum channel
6069
+ # Create a unique identifier for each note using both pitch and channel
6070
+ note_id = (cc[pitches_index], cc[channels_index])
6071
+
6072
+ # Check if this specific pitch-channel combination has been seen
6073
+ if note_id not in seen_notes:
6074
+ # The original function forced the channel to 0, destroying stereo separation.
6075
+ # We comment out that line and ONLY change the instrument patch.
6076
+ # cc[channels_index] = 0 <-- THIS LINE IS REMOVED
6077
+
6078
+ # Force the instrument patch to 0 (Acoustic Grand Piano)
6079
+ cc[patches_index] = 0 # Set patch to Grand Piano
6080
 
6081
  chord.append(cc)
6082
+ seen_notes.add(note_id) # Add the unique ID to the set
6083
 
6084
+ else: # If it is a drum channel
6085
  if keep_drums:
6086
+ # Apply the same logic for drums to be safe
6087
+ drum_id = (cc[pitches_index] + 128, cc[channels_index])
6088
+ if drum_id not in seen_notes:
6089
  chord.append(cc)
6090
+ seen_notes.add(drum_id)
6091
 
6092
  sp_escore_notes.append(chord)
6093