Spaces:
Runtime error
Runtime error
| 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) |