import os import time import numpy as np import librosa import soundfile as sf import pyloudnorm as pyln import scipy.signal import gradio as gr # ---- CONFIG ---- TARGET_LOUDNESS = -24.0 FINAL_LOUDNESS = -14.0 # ---- AUDIO PROCESSING FUNCTIONS ---- def normalize_loudness(audio, sr, target_lufs): meter = pyln.Meter(sr) return pyln.normalize.loudness(audio, meter.integrated_loudness(audio), target_lufs) def highpass(audio, sr, cutoff=40): sos = scipy.signal.butter(2, cutoff, btype='highpass', fs=sr, output='sos') return scipy.signal.sosfilt(sos, audio) def pan_stereo(mono_audio, pan): left = mono_audio * (1 - max(0, pan)) right = mono_audio * (1 - max(0, -pan)) return np.vstack((left, right)) def auto_mix(track1, track2, track3, track4, track5, track6, track7, track8, vol1, vol2, vol3, vol4, vol5, vol6, vol7, vol8, pan1, pan2, pan3, pan4, pan5, pan6, pan7, pan8, bass_boost=0.0, brightness=0.0, vocal_boost=0.0): tracks = [track1, track2, track3, track4, track5, track6, track7, track8] volumes = [vol1, vol2, vol3, vol4, vol5, vol6, vol7, vol8] pans = [pan1, pan2, pan3, pan4, pan5, pan6, pan7, pan8] tracks_info = [(t, v, p) for t, v, p in zip(tracks, volumes, pans) if t is not None] if not tracks_info: return None stems = [] sr = None for t, vol, pan in tracks_info: fpath = t.name audio, sr = librosa.load(fpath, sr=None, mono=True) audio = normalize_loudness(audio, sr, TARGET_LOUDNESS) audio = highpass(audio, sr) if bass_boost > 0: sos = scipy.signal.butter(2, 150, btype='low', fs=sr, output='sos') audio += bass_boost * scipy.signal.sosfilt(sos, audio) if brightness > 0: sos = scipy.signal.butter(2, 4000, btype='high', fs=sr, output='sos') audio += brightness * scipy.signal.sosfilt(sos, audio) if vocal_boost > 0 and "voc" in os.path.basename(fpath).lower(): audio *= (1.0 + vocal_boost) audio *= vol stems.append(pan_stereo(audio, pan)) max_len = max(stem.shape[1] for stem in stems) mix = np.zeros((2, max_len)) for stem in stems: padded = np.zeros((2, max_len)) padded[:, :stem.shape[1]] = stem mix += padded meter = pyln.Meter(sr) mix = pyln.normalize.loudness(mix.T, meter.integrated_loudness(mix.T), FINAL_LOUDNESS).T timestamp = time.strftime("%Y%m%d_%H%M%S") out_file = f"mix_{timestamp}.wav" sf.write(out_file, mix.T, sr) return out_file # ---- GRADIO UI ---- with gr.Blocks() as demo: gr.Markdown("## 🎚️ Mini Automatic Mixer (Online Version)") tracks = [] vols = [] pans = [] # ---- First row: Track 1-4 ---- with gr.Row(): for i in range(4): with gr.Column(): track = gr.File(label=f"Track {i+1}", file_types=[".wav", ".aiff", ".mp3"]) vol = gr.Slider(0, 2.0, step=0.05, label="Volume", value=1.0) pan = gr.Slider(-1.0, 1.0, step=0.05, label="Pan", value=0.0) tracks.append(track) vols.append(vol) pans.append(pan) # ---- Second row: Track 5-8 ---- with gr.Row(): for i in range(4, 8): with gr.Column(): track = gr.File(label=f"Track {i+1}", file_types=[".wav", ".aiff", ".mp3"]) vol = gr.Slider(0, 2.0, step=0.05, label="Volume", value=1.0) pan = gr.Slider(-1.0, 1.0, step=0.05, label="Pan", value=0.0) tracks.append(track) vols.append(vol) pans.append(pan) # ---- General effects row ---- with gr.Row(): bass = gr.Slider(0, 1.0, step=0.1, label="Bass Boost") bright = gr.Slider(0, 1.0, step=0.1, label="Brightness") vocal = gr.Slider(0, 1.0, step=0.1, label="Vocal Boost (files with 'voc' in name)") # ---- Mix / Preview / Download ---- mix_btn = gr.Button("🎛️ Auto Mix & Master") audio_out = gr.Audio(label="Preview Mix", type="filepath") download_btn = gr.File(label="Download Mix") mix_btn.click( auto_mix, inputs=tracks + vols + pans + [bass, bright, vocal], outputs=[audio_out] ) audio_out.change(lambda f: f, inputs=audio_out, outputs=download_btn) demo.launch(share=True)