File size: 6,212 Bytes
ce6ed11
 
 
 
 
 
 
 
 
536aac5
 
 
 
ce6ed11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536aac5
 
ce6ed11
536aac5
 
ce6ed11
 
 
536aac5
 
 
 
ce6ed11
 
 
 
 
536aac5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ce6ed11
 
 
 
536aac5
ce6ed11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
536aac5
 
ce6ed11
 
 
 
 
536aac5
 
ce6ed11
 
 
 
 
 
536aac5
 
ce6ed11
 
 
536aac5
 
ce6ed11
 
 
 
536aac5
 
 
 
ce6ed11
536aac5
 
 
ce6ed11
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import io
import json
from pydub import AudioSegment
from pydub.effects import normalize, compress_dynamic_range, low_pass_filter, high_pass_filter
import numpy as np
import gradio as gr

# Default presets for common genres
PRESETS = {
    "rock": {"gain": 6, "compress_threshold": -20, "compress_ratio": 4, "low_pass": 12000, "high_pass": 80, "stereo_width": 100, "pan": 0},
    "pop": {"gain": 4, "compress_threshold": -18, "compress_ratio": 3, "low_pass": 15000, "high_pass": 100, "stereo_width": 100, "pan": 0},
    "jazz": {"gain": 3, "compress_threshold": -22, "compress_ratio": 2, "low_pass": 14000, "high_pass": 60, "stereo_width": 90, "pan": 0},
    "electronic": {"gain": 8, "compress_threshold": -16, "compress_ratio": 5, "low_pass": 20000, "high_pass": 120, "stereo_width": 120, "pan": 0},
}

# JSON export extension
EXPORT_EXT = ".master"

# Custom CSS for Gradio
CUSTOM_CSS = """
body { background-color: #1e1e1e; color: #f0f0f0; }
.gradio-container { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); }
#title { font-size: 2rem; margin-bottom: 1rem; }
"""

def apply_mastering(audio: AudioSegment, params: dict) -> AudioSegment:
    # Gain
    processed = audio + params["gain"]
    # Compression
    processed = compress_dynamic_range(
        processed,
        threshold=params["compress_threshold"],
        ratio=params["compress_ratio"]
    )
    # EQ filters
    processed = high_pass_filter(processed, params["high_pass"])
    processed = low_pass_filter(processed, params["low_pass"])
    # Stereo Imaging (Width)
    processed = apply_stereo_width(processed, params.get("stereo_width", 100))
    # Neural Pan
    processed = apply_pan(processed, params.get("pan", 0))
    # Normalization
    processed = normalize(processed)
    return processed


def apply_stereo_width(audio: AudioSegment, width: float) -> AudioSegment:
    # Width in percent: 0 = mono, 100 = original, >100 = enhanced
    left, right = audio.split_to_mono()
    mid = (left + right) / 2
    side = (left - right) / 2
    # scale side for width control
    side = side * (width / 100)
    new_left = mid + side
    new_right = mid - side
    return AudioSegment.from_mono_audiosegments(new_left, new_right)


def apply_pan(audio: AudioSegment, pan: float) -> AudioSegment:
    # Pan from -100 (left) to 100 (right)
    left, right = audio.split_to_mono()
    pan = max(-100, min(100, pan)) / 100
    if pan > 0:
        new_left = left * (1 - pan)
        new_right = right
    else:
        new_left = left
        new_right = right * (1 + pan)
    return AudioSegment.from_mono_audiosegments(new_left, new_right)


def process_and_export(audio_file, preset_name, gain, threshold, ratio, lp, hp, width, pan):
    audio = AudioSegment.from_file(audio_file)
    if preset_name != "custom":
        params = PRESETS[preset_name]
    else:
        params = {"gain": gain, "compress_threshold": threshold, "compress_ratio": ratio,
                  "low_pass": lp, "high_pass": hp, "stereo_width": width, "pan": pan}
    out = apply_mastering(audio, params)
    buf = io.BytesIO()
    out.export(buf, format="wav")
    buf.seek(0)
    preset = {"preset": preset_name, "params": params}
    preset_buf = io.BytesIO(json.dumps(preset, indent=2).encode())
    preset_buf.name = "preset" + EXPORT_EXT
    return (buf, "processed.wav"), (preset_buf, preset_buf.name)


def load_preset_json(file_obj):
    data = json.load(file_obj)
    name = data.get("preset", "custom")
    params = data.get("params", {})
    return name, params.get("gain", 0), params.get("compress_threshold", 0), \
           params.get("compress_ratio", 1), params.get("low_pass", 20000), \
           params.get("high_pass", 20), params.get("stereo_width", 100), params.get("pan", 0)

with gr.Blocks(css=CUSTOM_CSS) as demo:
    gr.Markdown("<div id='title'>Advanced Browser-based Audio Mastering Suite</div>")
    with gr.Row():
        with gr.Column():
            # Use 'filepath' to get the file path for AudioSegment
            audio_input = gr.Audio(label="Upload Audio", type="filepath")
            preset_dropdown = gr.Dropdown(choices=list(PRESETS.keys()) + ["custom"], value="rock", label="Preset")
            gain = gr.Slider(-10, 20, value=PRESETS["rock"]["gain"], label="Gain (dB)")
            threshold = gr.Slider(-60, 0, value=PRESETS["rock"]["compress_threshold"], label="Compress Threshold (dB)")
            ratio = gr.Slider(1, 10, value=PRESETS["rock"]["compress_ratio"], label="Compress Ratio")
            lp = gr.Slider(1000, 20000, value=PRESETS["rock"]["low_pass"], label="Low-pass Frequency (Hz)")
            hp = gr.Slider(20, 500, value=PRESETS["rock"]["high_pass"], label="High-pass Frequency (Hz)")
            width = gr.Slider(0, 200, value=PRESETS["rock"]["stereo_width"], label="Stereo Width (%)")
            pan = gr.Slider(-100, 100, value=PRESETS["rock"]["pan"], label="Pan (-100 Left to 100 Right)")
            load_preset = gr.File(label="Load .master Preset", file_types=[".master"])
            export_button = gr.Button("Process & Export")
        with gr.Column():
            # Set output to filepath to serve downloadable file
            output_audio = gr.Audio(label="Processed Audio", type="filepath")
            export_preset_file = gr.File(label="Download Preset (.master)")
    def sync_sliders(preset):
        if preset != "custom":
            p = PRESETS[preset]
            return (gr.update(value=p["gain"]), gr.update(value=p["compress_threshold"]), \
                    gr.update(value=p["compress_ratio"]), gr.update(value=p["low_pass"]), \
                    gr.update(value=p["high_pass"]), gr.update(value=p["stereo_width"]), \
                    gr.update(value=p["pan"]))
        return None
    preset_dropdown.change(sync_sliders, inputs=[preset_dropdown], outputs=[gain, threshold, ratio, lp, hp, width, pan])
    load_preset.upload(load_preset_json, inputs=[load_preset], outputs=[preset_dropdown, gain, threshold, ratio, lp, hp, width, pan])
    export_button.click(process_and_export, inputs=[audio_input, preset_dropdown, gain, threshold, ratio, lp, hp, width, pan], outputs=[output_audio, export_preset_file])

if __name__ == "__main__":
    demo.launch()