import gradio as gr import numpy as np import librosa from pydub import AudioSegment import os import random import pyrubberband as rb import soundfile as sf from pedalboard import Pedalboard, Reverb # --- Create dummy files for examples. This runs on server startup. --- # This is the key fix: This code is now in the global scope. os.makedirs("generated_music", exist_ok=True) if not os.path.exists("example_vocal.wav"): sf.write("example_vocal.wav", np.random.randn(44100 * 2), 44100) if not os.path.exists("example_drums.wav"): sf.write("example_drums.wav", np.random.randn(44100 * 2), 44100) if not os.path.exists("example_synth.wav"): sf.write("example_synth.wav", np.random.randn(44100 * 2), 44100) # --- Musical Sequence Generation --- def generate_musical_sequence(mode="Scale", scale_name="Major", root_note="C4", num_notes=50): """ Generates a sequence of MIDI notes for scales or arpeggios. """ try: current_midi = librosa.note_to_midi(root_note) except Exception as e: raise ValueError(f"Invalid root note. Use scientific notation like 'C4'. Error: {e}") intervals = { "Chromatic": [1], "Major": [2, 2, 1, 2, 2, 2, 1], "Natural Minor": [2, 1, 2, 2, 1, 2, 2], "Harmonic Minor": [2, 1, 2, 2, 1, 3, 1], "Pentatonic Major": [2, 2, 3, 2, 3] } scale_intervals = intervals.get(scale_name, intervals["Chromatic"]) scale_notes = [current_midi] interval_index = 0 while len(scale_notes) < num_notes: current_midi += scale_intervals[interval_index % len(scale_intervals)] scale_notes.append(current_midi) interval_index += 1 if mode == "Scale": return scale_notes[:num_notes] if mode == "Arpeggio": arpeggio_tones = [scale_notes[i] for i in [0, 2, 4]] if not arpeggio_tones: return [] sequence = [] octave_offset = 0 tone_index = 0 while len(sequence) < num_notes: note = arpeggio_tones[tone_index % len(arpeggio_tones)] + (12 * octave_offset) sequence.append(note) tone_index += 1 if tone_index % len(arpeggio_tones) == 0: octave_offset += 1 return sequence # --- Core Audio Processing --- def get_random_segment(y, sr, min_len_ms=100, max_len_ms=1000): """Extracts a single random segment and its energy.""" seg_len_samples = int(random.uniform(min_len_ms, max_len_ms) / 1000 * sr) if seg_len_samples > len(y): seg_len_samples = len(y) start_sample = random.randint(0, len(y) - seg_len_samples) end_sample = start_sample + seg_len_samples segment_y = y[start_sample:end_sample] rms = librosa.feature.rms(y=segment_y).mean() return segment_y, rms def audio_orchestrator( audio_path, mode, scale_name, root_note, num_notes, note_duration_s, silence_s, reverb_mix, reverb_room_size, progress=gr.Progress() ): """Main function to orchestrate the entire audio transformation.""" if not audio_path: raise gr.Error("Please upload a source audio file first.") try: progress(0, desc="Loading Audio...") y, sr = librosa.load(audio_path, sr=None, mono=True) except Exception as e: raise gr.Error(f"Could not read the audio file. Error: {e}") if len(y) / sr < 0.1: raise gr.Error("Source audio is too short. Please use a file that is at least 0.1 seconds long.") progress(0.1, desc=f"Generating {mode}...") target_midi_notes = generate_musical_sequence(mode, scale_name, root_note, num_notes) final_y = np.array([], dtype=np.float32) silence_samples = np.zeros(int(silence_s * sr), dtype=np.float32) source_segments = [get_random_segment(y, sr) for _ in range(num_notes)] all_rms = [rms for _, rms in source_segments if rms > 0] max_rms = max(all_rms) if all_rms else 1.0 for i, (segment_y, rms) in enumerate(source_segments): target_note = target_midi_notes[i] note_name = librosa.midi_to_note(target_note) progress(0.2 + (i / num_notes) * 0.7, desc=f"Processing Note {i+1}/{num_notes}: {note_name}") source_duration = len(segment_y) / sr stretch_rate = source_duration / note_duration_s if note_duration_s > 0 else 1 stretched_y = rb.time_stretch(segment_y, sr, stretch_rate) source_pitch_hz = librosa.note_to_hz('C4') target_pitch_hz = librosa.midi_to_hz(target_note) pitch_shift_ratio = target_pitch_hz / source_pitch_hz shifted_y = rb.pitch_shift(stretched_y, sr, pitch_shift_ratio) volume_factor = (rms / max_rms) * 0.9 + 0.1 shifted_y *= volume_factor final_y = np.concatenate((final_y, shifted_y, silence_samples)) progress(0.95, desc="Applying final effects...") if reverb_mix > 0: board = Pedalboard([Reverb(room_size=reverb_room_size, wet_level=reverb_mix, dry_level=1.0 - reverb_mix)]) final_y = board(final_y, sr) output_dir = "generated_music" os.makedirs(output_dir, exist_ok=True) output_filename = f"{mode.lower()}_{scale_name.lower().replace(' ', '_')}_{root_note}.wav" output_path = os.path.join(output_dir, output_filename) sf.write(output_path, final_y, sr) progress(1, desc="Done!") return output_path # --- Gradio User Interface --- with gr.Blocks(theme=gr.themes.Base(primary_hue="teal", secondary_hue="orange")) as demo: gr.Markdown( """ # 🎹 Audio Orchestrator Pro 🎵 ### Turn any sound into a unique musical composition. Upload an audio file, configure the musical, rhythmic, and effects parameters, and generate a new piece of music. """ ) with gr.Row(): with gr.Column(scale=1): audio_input = gr.Audio(type="filepath", label="1. Upload Your Source Audio") with gr.Accordion("Musical Parameters", open=True): mode = gr.Radio(["Scale", "Arpeggio"], label="Generation Mode", value="Arpeggio") scale_name = gr.Dropdown(["Major", "Natural Minor", "Harmonic Minor", "Pentatonic Major", "Chromatic"], label="Scale", value="Major") root_note_choices = librosa.midi_to_note(list(range(36, 73))).tolist() root_note = gr.Dropdown(root_note_choices, label="Root Note", value="C4") num_notes = gr.Slider(10, 200, value=70, step=1, label="Number of Notes") with gr.Accordion("Rhythmic Parameters", open=False): note_duration_s = gr.Slider(0.05, 1.0, value=0.2, step=0.01, label="Note Duration (seconds)") silence_s = gr.Slider(0.0, 0.5, value=0.05, step=0.01, label="Silence Between Notes (seconds)") with gr.Accordion("Effects Rack", open=False): gr.Markdown("**Reverb**") reverb_mix = gr.Slider(0, 1, value=0.3, label="Wet/Dry Mix") reverb_room_size = gr.Slider(0, 1, value=0.6, label="Room Size") process_button = gr.Button("✨ Generate Composition", variant="primary") with gr.Column(scale=2): gr.Markdown("### Your Generated Music") audio_output = gr.Audio(type="filepath", label="Output") process_button.click( fn=audio_orchestrator, inputs=[ audio_input, mode, scale_name, root_note, num_notes, note_duration_s, silence_s, reverb_mix, reverb_room_size ], outputs=[audio_output] ) gr.Examples( # FIX IS HERE: Using simple filenames because the files are now in the root directory. examples=[ ["example_vocal.wav", "Arpeggio", "Natural Minor", "A3", 80, 0.15, 0.1, 0.4, 0.7], ["example_drums.wav", "Scale", "Pentatonic Major", "C3", 50, 0.25, 0.0, 0.2, 0.8], ["example_synth.wav", "Arpeggio", "Harmonic Minor", "E4", 120, 0.1, 0.02, 0.5, 0.9], ], inputs=[ audio_input, mode, scale_name, root_note, num_notes, note_duration_s, silence_s, reverb_mix, reverb_room_size ], outputs=[audio_output], fn=audio_orchestrator, cache_examples=True ) if __name__ == "__main__": demo.launch(debug=True)