audio-mixer / app.py
smartdigitalnetworks's picture
Update app.py
f44fb22 verified
raw
history blame
4.39 kB
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()