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) |