avans06 commited on
Commit
8adc333
·
1 Parent(s): 22c681c

feat(synth): Upgrade Delay/Echo effect to be tempo-synced

Browse files

This commit refactors the MIDI Delay/Echo effect, transforming it from a static, time-based processor into a dynamic, musical tool that synchronizes with the song's rhythm.

Files changed (1) hide show
  1. app.py +59 -18
app.py CHANGED
@@ -199,7 +199,7 @@ class AppParameters:
199
  # --- MIDI Delay/Echo Effect Parameters ---
200
  s8bit_enable_delay: bool = False # Master switch for the delay effect
201
  s8bit_delay_on_melody_only: bool = True # Apply delay only to the lead melody
202
- s8bit_delay_time_s: float = 0.15 # Time in seconds between each echo
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
 
@@ -482,6 +482,7 @@ def arpeggiate_midi(midi_data: pretty_midi.PrettyMIDI, params: AppParameters):
482
  "Pulsing 4ths": [(0.0, 0.5)],
483
  "Galloping": [(0.0, 0.75), (0.75, 0.25)],
484
  "Simple Quarter Notes": [(0.0, 1.0)],
 
485
  }
486
  selected_rhythm = rhythm_patterns.get(params.s8bit_arpeggio_rhythm, rhythm_patterns["Classic Upbeat (8th)"])
487
 
@@ -604,12 +605,34 @@ def create_delay_effect(midi_data: pretty_midi.PrettyMIDI, params: AppParameters
604
  """
605
  Creates a delay/echo effect by duplicating notes with delayed start times
606
  and scaled velocities. Can be configured to apply only to the lead melody.
 
607
  """
608
- print("Applying MIDI delay/echo effect...")
609
  # Work on a deep copy to ensure the original MIDI object is not mutated.
610
  processed_midi = copy.deepcopy(midi_data)
611
-
612
- # --- Step 1: Identify the notes that should receive the echo effect ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
  notes_to_echo = []
614
 
615
  if params.s8bit_delay_on_melody_only:
@@ -637,15 +660,15 @@ def create_delay_effect(midi_data: pretty_midi.PrettyMIDI, params: AppParameters
637
  print(" - No notes found to apply delay to. Skipping.")
638
  return processed_midi
639
 
640
- # --- Step 2: Generate the echo notes ---
641
  echo_notes = []
642
  for i in range(1, params.s8bit_delay_repeats + 1):
643
  for original_note in notes_to_echo:
644
  # Create a copy of the note for the echo
645
  echo_note = copy.copy(original_note)
646
 
647
- # Calculate new timing and velocity
648
- time_offset = i * params.s8bit_delay_time_s
649
  echo_note.start += time_offset
650
  echo_note.end += time_offset
651
  echo_note.velocity = int(echo_note.velocity * (params.s8bit_delay_feedback ** i))
@@ -654,13 +677,21 @@ def create_delay_effect(midi_data: pretty_midi.PrettyMIDI, params: AppParameters
654
  if echo_note.velocity > 1:
655
  echo_notes.append(echo_note)
656
 
657
- # --- Step 3: Add the echo notes to a new, dedicated instrument track ---
658
  if echo_notes:
659
- # Use a softer program for the echo, like a synth pad, to differentiate it.
660
- echo_instrument = pretty_midi.Instrument(program=80, is_drum=False, name="Echo Layer")
 
 
 
 
 
 
 
 
661
  echo_instrument.notes.extend(echo_notes)
662
  processed_midi.instruments.append(echo_instrument)
663
- print(f" - Generated {len(echo_notes)} echo notes on a new track.")
664
 
665
  return processed_midi
666
 
@@ -3655,6 +3686,7 @@ if __name__ == "__main__":
3655
  "Continuous 16ths",
3656
  "Classic Upbeat (8th)",
3657
  "Pulsing 8ths",
 
3658
  "Pulsing 4ths",
3659
  "Galloping",
3660
  "Simple Quarter Notes"
@@ -3662,12 +3694,13 @@ if __name__ == "__main__":
3662
  value="Pulsing 8ths",
3663
  label="Arpeggio Rhythm Pattern",
3664
  info="""
3665
- - **Continuous 16ths:** A constant, driving wall of sound with no breaks. Creates a very dense, high-energy texture.
3666
- - **Classic Upbeat (8th):** The quintessential chiptune rhythm. Creates a bouncy, syncopated feel by playing on the off-beats. (Sounds like: _ _ ta-ta)
3667
  - **Pulsing 8ths:** A steady, on-beat rhythm playing two notes per beat. Good for a solid, rhythmic foundation. (Sounds like: ta-ta ta-ta)
 
3668
  - **Pulsing 4ths:** A strong, deliberate pulse on each downbeat, with a clear separation between notes. (Sounds like: ta_ ta_ ta_)
3669
  - **Galloping:** A driving, forward-moving rhythm with a distinctive long-short pattern. Excellent for action themes. (Sounds like: ta--ta ta--ta)
3670
- - **Simple Quarter Notes:** The most sparse pattern, playing one sustained note per beat. Creates a calm and open feel.
3671
  """
3672
  )
3673
  s8bit_arpeggio_pattern = gr.Dropdown(
@@ -3708,10 +3741,18 @@ if __name__ == "__main__":
3708
  label="Apply Delay to Melody Only",
3709
  info="Recommended. Applies the echo effect only to the lead melody notes, keeping the harmony clean."
3710
  )
3711
- s8bit_delay_time_s = gr.Slider(
3712
- 0.05, 0.5, value=0.15, step=0.01,
3713
- label="Delay Time (seconds)",
3714
- info="The time between each echo. Sync this to the beat for rhythmic delays (e.g., 0.25s for 8th notes at 120 BPM)."
 
 
 
 
 
 
 
 
3715
  )
3716
  s8bit_delay_feedback = gr.Slider(
3717
  0.1, 0.9, value=0.5, step=0.05,
 
199
  # --- MIDI Delay/Echo Effect Parameters ---
200
  s8bit_enable_delay: bool = False # Master switch for the delay effect
201
  s8bit_delay_on_melody_only: bool = True # Apply delay only to the lead melody
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
 
 
482
  "Pulsing 4ths": [(0.0, 0.5)],
483
  "Galloping": [(0.0, 0.75), (0.75, 0.25)],
484
  "Simple Quarter Notes": [(0.0, 1.0)],
485
+ "Triplet 8ths": [(0.0, 1/3), (1/3, 1/3), (2/3, 1/3)],
486
  }
487
  selected_rhythm = rhythm_patterns.get(params.s8bit_arpeggio_rhythm, rhythm_patterns["Classic Upbeat (8th)"])
488
 
 
605
  """
606
  Creates a delay/echo effect by duplicating notes with delayed start times
607
  and scaled velocities. Can be configured to apply only to the lead melody.
608
+ based on the MIDI's estimated BPM and the user's selected musical division.
609
  """
610
+ print("Applying tempo-synced MIDI delay/echo effect...")
611
  # Work on a deep copy to ensure the original MIDI object is not mutated.
612
  processed_midi = copy.deepcopy(midi_data)
613
+
614
+ # --- Step 1: Estimate Tempo and Calculate Delay Time in Seconds ---
615
+ try:
616
+ bpm = midi_data.estimate_tempo()
617
+ except:
618
+ bpm = 120.0
619
+ print(f" - Delay using tempo: {bpm:.2f} BPM")
620
+
621
+ # This map defines the duration of each note division as a multiplier of a quarter note (a beat).
622
+ division_map = {
623
+ "Quarter Note": 1.0,
624
+ "Dotted 8th Note": 0.75,
625
+ "8th Note": 0.5,
626
+ "Triplet 8th Note": 1.0 / 3.0,
627
+ "16th Note": 0.25
628
+ }
629
+ beat_duration_s = 60.0 / bpm
630
+ division_multiplier = division_map.get(params.s8bit_delay_division, 0.75)
631
+ delay_time_s = beat_duration_s * division_multiplier
632
+
633
+ print(f" - Delay set to {params.s8bit_delay_division}, calculated time: {delay_time_s:.3f}s")
634
+
635
+ # --- Step 2: Identify the notes that should receive the echo effect ---
636
  notes_to_echo = []
637
 
638
  if params.s8bit_delay_on_melody_only:
 
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
672
  echo_note.start += time_offset
673
  echo_note.end += time_offset
674
  echo_note.velocity = int(echo_note.velocity * (params.s8bit_delay_feedback ** i))
 
677
  if echo_note.velocity > 1:
678
  echo_notes.append(echo_note)
679
 
680
+ # --- Step 4: Add the echo notes to a new, dedicated instrument track ---
681
  if echo_notes:
682
+ # Inherit the program from the first non-drum instrument
683
+ # This ensures the echo has the same timbral character as the original sound,
684
+ # preventing perceived pitch shifts caused by different harmonic structures.
685
+ base_program = 0 # Default to piano if no instruments are found
686
+ for inst in midi_data.instruments:
687
+ if not inst.is_drum:
688
+ base_program = inst.program
689
+ break # Use the program of the first non-drum track we find
690
+
691
+ echo_instrument = pretty_midi.Instrument(program=base_program, is_drum=False, name="Echo Layer")
692
  echo_instrument.notes.extend(echo_notes)
693
  processed_midi.instruments.append(echo_instrument)
694
+ print(f" - Generated {len(echo_notes)} tempo-synced echo notes on a new track with program {base_program}.")
695
 
696
  return processed_midi
697
 
 
3686
  "Continuous 16ths",
3687
  "Classic Upbeat (8th)",
3688
  "Pulsing 8ths",
3689
+ "Triplet 8ths",
3690
  "Pulsing 4ths",
3691
  "Galloping",
3692
  "Simple Quarter Notes"
 
3694
  value="Pulsing 8ths",
3695
  label="Arpeggio Rhythm Pattern",
3696
  info="""
3697
+ - **Continuous 16ths:** A constant, driving wall of sound with no breaks. Creates a very dense, high-energy texture. (Sounds like: ta-ta-ta-ta ta-ta-ta-ta)
3698
+ - **Classic Upbeat (8th):** The quintessential chiptune rhythm. Creates a bouncy, syncopated feel by playing on the off-beats. (Sounds like: _ _ ta-ta _ _ ta-ta)
3699
  - **Pulsing 8ths:** A steady, on-beat rhythm playing two notes per beat. Good for a solid, rhythmic foundation. (Sounds like: ta-ta ta-ta)
3700
+ - **Triplet 8ths:** A rolling, "three-feel" rhythm that creates a swing or shuffle groove. Very common in Blues, Jazz, and Hip-Hop. (Sounds like: ta-ta-ta ta-ta-ta)
3701
  - **Pulsing 4ths:** A strong, deliberate pulse on each downbeat, with a clear separation between notes. (Sounds like: ta_ ta_ ta_)
3702
  - **Galloping:** A driving, forward-moving rhythm with a distinctive long-short pattern. Excellent for action themes. (Sounds like: ta--ta ta--ta)
3703
+ - **Simple Quarter Notes:** The most sparse pattern, playing one sustained note per beat. Creates a calm and open feel. (Sounds like: ta _ ta _ ta _ ta _)
3704
  """
3705
  )
3706
  s8bit_arpeggio_pattern = gr.Dropdown(
 
3741
  label="Apply Delay to Melody Only",
3742
  info="Recommended. Applies the echo effect only to the lead melody notes, keeping the harmony clean."
3743
  )
3744
+ s8bit_delay_division = gr.Dropdown(
3745
+ ["Quarter Note", "Dotted 8th Note", "8th Note", "Triplet 8th Note", "16th Note"],
3746
+ value="Dotted 8th Note",
3747
+ label="Delay Time (Tempo Synced)",
3748
+ info="""
3749
+ "The time between echoes, synced to the MIDI's tempo. 'Dotted 8th Note' is a classic rhythmic choice."
3750
+ - **Quarter Note:** A simple, stable echo on the next beat. (1, 2, 3, 4)
3751
+ - **Dotted 8th Note (Classic):** Creates a very popular, rolling syncopated rhythm. Highly recommended for adding energy and complexity.
3752
+ - **8th Note:** A steady, "call and response" echo on the off-beat. Creates a running or swing feel.
3753
+ - **Triplet 8th Note:** Creates a unique "shuffling" or "bouncing" 3-feel groove over the standard 4/4 beat.
3754
+ - **16th Note:** A very fast, dense echo. Can act more like a textural effect than a distinct rhythmic delay.
3755
+ """
3756
  )
3757
  s8bit_delay_feedback = gr.Slider(
3758
  0.1, 0.9, value=0.5, step=0.05,