Spaces:
Running
Running
add stereo imaging and neural pan + fix error
Browse files
app.py
CHANGED
@@ -7,10 +7,10 @@ import gradio as gr
|
|
7 |
|
8 |
# Default presets for common genres
|
9 |
PRESETS = {
|
10 |
-
"rock": {"gain": 6, "compress_threshold": -20, "compress_ratio": 4, "low_pass": 12000, "high_pass": 80},
|
11 |
-
"pop": {"gain": 4, "compress_threshold": -18, "compress_ratio": 3, "low_pass": 15000, "high_pass": 100},
|
12 |
-
"jazz": {"gain": 3, "compress_threshold": -22, "compress_ratio": 2, "low_pass": 14000, "high_pass": 60},
|
13 |
-
"electronic": {"gain": 8, "compress_threshold": -16, "compress_ratio": 5, "low_pass": 20000, "high_pass": 120},
|
14 |
}
|
15 |
|
16 |
# JSON export extension
|
@@ -27,33 +27,59 @@ def apply_mastering(audio: AudioSegment, params: dict) -> AudioSegment:
|
|
27 |
# Gain
|
28 |
processed = audio + params["gain"]
|
29 |
# Compression
|
30 |
-
processed = compress_dynamic_range(
|
|
|
31 |
threshold=params["compress_threshold"],
|
32 |
-
ratio=params["compress_ratio"]
|
|
|
33 |
# EQ filters
|
34 |
processed = high_pass_filter(processed, params["high_pass"])
|
35 |
processed = low_pass_filter(processed, params["low_pass"])
|
|
|
|
|
|
|
|
|
36 |
# Normalization
|
37 |
processed = normalize(processed)
|
38 |
return processed
|
39 |
|
40 |
|
41 |
-
def
|
42 |
-
#
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
if preset_name != "custom":
|
46 |
params = PRESETS[preset_name]
|
47 |
else:
|
48 |
params = {"gain": gain, "compress_threshold": threshold, "compress_ratio": ratio,
|
49 |
-
"low_pass": lp, "high_pass": hp}
|
50 |
-
# Process
|
51 |
out = apply_mastering(audio, params)
|
52 |
-
# Export audio
|
53 |
buf = io.BytesIO()
|
54 |
out.export(buf, format="wav")
|
55 |
buf.seek(0)
|
56 |
-
# Create preset JSON
|
57 |
preset = {"preset": preset_name, "params": params}
|
58 |
preset_buf = io.BytesIO(json.dumps(preset, indent=2).encode())
|
59 |
preset_buf.name = "preset" + EXPORT_EXT
|
@@ -65,40 +91,40 @@ def load_preset_json(file_obj):
|
|
65 |
name = data.get("preset", "custom")
|
66 |
params = data.get("params", {})
|
67 |
return name, params.get("gain", 0), params.get("compress_threshold", 0), \
|
68 |
-
params.get("compress_ratio", 1), params.get("low_pass", 20000),
|
|
|
69 |
|
70 |
with gr.Blocks(css=CUSTOM_CSS) as demo:
|
71 |
gr.Markdown("<div id='title'>Advanced Browser-based Audio Mastering Suite</div>")
|
72 |
with gr.Row():
|
73 |
with gr.Column():
|
74 |
-
|
|
|
75 |
preset_dropdown = gr.Dropdown(choices=list(PRESETS.keys()) + ["custom"], value="rock", label="Preset")
|
76 |
gain = gr.Slider(-10, 20, value=PRESETS["rock"]["gain"], label="Gain (dB)")
|
77 |
threshold = gr.Slider(-60, 0, value=PRESETS["rock"]["compress_threshold"], label="Compress Threshold (dB)")
|
78 |
ratio = gr.Slider(1, 10, value=PRESETS["rock"]["compress_ratio"], label="Compress Ratio")
|
79 |
lp = gr.Slider(1000, 20000, value=PRESETS["rock"]["low_pass"], label="Low-pass Frequency (Hz)")
|
80 |
hp = gr.Slider(20, 500, value=PRESETS["rock"]["high_pass"], label="High-pass Frequency (Hz)")
|
|
|
|
|
81 |
load_preset = gr.File(label="Load .master Preset", file_types=[".master"])
|
82 |
export_button = gr.Button("Process & Export")
|
83 |
with gr.Column():
|
84 |
-
|
|
|
85 |
export_preset_file = gr.File(label="Download Preset (.master)")
|
86 |
-
|
87 |
-
# Interactivity
|
88 |
def sync_sliders(preset):
|
89 |
if preset != "custom":
|
90 |
p = PRESETS[preset]
|
91 |
-
return gr.update(value=p["gain"]), gr.update(value=p["compress_threshold"]), \
|
92 |
-
|
93 |
-
|
|
|
94 |
return None
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
outputs=[preset_dropdown, gain, threshold, ratio, lp, hp])
|
99 |
-
export_button.click(process_and_export,
|
100 |
-
inputs=[audio_input, preset_dropdown, gain, threshold, ratio, lp, hp],
|
101 |
-
outputs=[output_audio, export_preset_file])
|
102 |
|
103 |
if __name__ == "__main__":
|
104 |
demo.launch()
|
|
|
7 |
|
8 |
# Default presets for common genres
|
9 |
PRESETS = {
|
10 |
+
"rock": {"gain": 6, "compress_threshold": -20, "compress_ratio": 4, "low_pass": 12000, "high_pass": 80, "stereo_width": 100, "pan": 0},
|
11 |
+
"pop": {"gain": 4, "compress_threshold": -18, "compress_ratio": 3, "low_pass": 15000, "high_pass": 100, "stereo_width": 100, "pan": 0},
|
12 |
+
"jazz": {"gain": 3, "compress_threshold": -22, "compress_ratio": 2, "low_pass": 14000, "high_pass": 60, "stereo_width": 90, "pan": 0},
|
13 |
+
"electronic": {"gain": 8, "compress_threshold": -16, "compress_ratio": 5, "low_pass": 20000, "high_pass": 120, "stereo_width": 120, "pan": 0},
|
14 |
}
|
15 |
|
16 |
# JSON export extension
|
|
|
27 |
# Gain
|
28 |
processed = audio + params["gain"]
|
29 |
# Compression
|
30 |
+
processed = compress_dynamic_range(
|
31 |
+
processed,
|
32 |
threshold=params["compress_threshold"],
|
33 |
+
ratio=params["compress_ratio"]
|
34 |
+
)
|
35 |
# EQ filters
|
36 |
processed = high_pass_filter(processed, params["high_pass"])
|
37 |
processed = low_pass_filter(processed, params["low_pass"])
|
38 |
+
# Stereo Imaging (Width)
|
39 |
+
processed = apply_stereo_width(processed, params.get("stereo_width", 100))
|
40 |
+
# Neural Pan
|
41 |
+
processed = apply_pan(processed, params.get("pan", 0))
|
42 |
# Normalization
|
43 |
processed = normalize(processed)
|
44 |
return processed
|
45 |
|
46 |
|
47 |
+
def apply_stereo_width(audio: AudioSegment, width: float) -> AudioSegment:
|
48 |
+
# Width in percent: 0 = mono, 100 = original, >100 = enhanced
|
49 |
+
left, right = audio.split_to_mono()
|
50 |
+
mid = (left + right) / 2
|
51 |
+
side = (left - right) / 2
|
52 |
+
# scale side for width control
|
53 |
+
side = side * (width / 100)
|
54 |
+
new_left = mid + side
|
55 |
+
new_right = mid - side
|
56 |
+
return AudioSegment.from_mono_audiosegments(new_left, new_right)
|
57 |
+
|
58 |
+
|
59 |
+
def apply_pan(audio: AudioSegment, pan: float) -> AudioSegment:
|
60 |
+
# Pan from -100 (left) to 100 (right)
|
61 |
+
left, right = audio.split_to_mono()
|
62 |
+
pan = max(-100, min(100, pan)) / 100
|
63 |
+
if pan > 0:
|
64 |
+
new_left = left * (1 - pan)
|
65 |
+
new_right = right
|
66 |
+
else:
|
67 |
+
new_left = left
|
68 |
+
new_right = right * (1 + pan)
|
69 |
+
return AudioSegment.from_mono_audiosegments(new_left, new_right)
|
70 |
+
|
71 |
+
|
72 |
+
def process_and_export(audio_file, preset_name, gain, threshold, ratio, lp, hp, width, pan):
|
73 |
+
audio = AudioSegment.from_file(audio_file)
|
74 |
if preset_name != "custom":
|
75 |
params = PRESETS[preset_name]
|
76 |
else:
|
77 |
params = {"gain": gain, "compress_threshold": threshold, "compress_ratio": ratio,
|
78 |
+
"low_pass": lp, "high_pass": hp, "stereo_width": width, "pan": pan}
|
|
|
79 |
out = apply_mastering(audio, params)
|
|
|
80 |
buf = io.BytesIO()
|
81 |
out.export(buf, format="wav")
|
82 |
buf.seek(0)
|
|
|
83 |
preset = {"preset": preset_name, "params": params}
|
84 |
preset_buf = io.BytesIO(json.dumps(preset, indent=2).encode())
|
85 |
preset_buf.name = "preset" + EXPORT_EXT
|
|
|
91 |
name = data.get("preset", "custom")
|
92 |
params = data.get("params", {})
|
93 |
return name, params.get("gain", 0), params.get("compress_threshold", 0), \
|
94 |
+
params.get("compress_ratio", 1), params.get("low_pass", 20000), \
|
95 |
+
params.get("high_pass", 20), params.get("stereo_width", 100), params.get("pan", 0)
|
96 |
|
97 |
with gr.Blocks(css=CUSTOM_CSS) as demo:
|
98 |
gr.Markdown("<div id='title'>Advanced Browser-based Audio Mastering Suite</div>")
|
99 |
with gr.Row():
|
100 |
with gr.Column():
|
101 |
+
# Use 'filepath' to get the file path for AudioSegment
|
102 |
+
audio_input = gr.Audio(label="Upload Audio", type="filepath")
|
103 |
preset_dropdown = gr.Dropdown(choices=list(PRESETS.keys()) + ["custom"], value="rock", label="Preset")
|
104 |
gain = gr.Slider(-10, 20, value=PRESETS["rock"]["gain"], label="Gain (dB)")
|
105 |
threshold = gr.Slider(-60, 0, value=PRESETS["rock"]["compress_threshold"], label="Compress Threshold (dB)")
|
106 |
ratio = gr.Slider(1, 10, value=PRESETS["rock"]["compress_ratio"], label="Compress Ratio")
|
107 |
lp = gr.Slider(1000, 20000, value=PRESETS["rock"]["low_pass"], label="Low-pass Frequency (Hz)")
|
108 |
hp = gr.Slider(20, 500, value=PRESETS["rock"]["high_pass"], label="High-pass Frequency (Hz)")
|
109 |
+
width = gr.Slider(0, 200, value=PRESETS["rock"]["stereo_width"], label="Stereo Width (%)")
|
110 |
+
pan = gr.Slider(-100, 100, value=PRESETS["rock"]["pan"], label="Pan (-100 Left to 100 Right)")
|
111 |
load_preset = gr.File(label="Load .master Preset", file_types=[".master"])
|
112 |
export_button = gr.Button("Process & Export")
|
113 |
with gr.Column():
|
114 |
+
# Set output to filepath to serve downloadable file
|
115 |
+
output_audio = gr.Audio(label="Processed Audio", type="filepath")
|
116 |
export_preset_file = gr.File(label="Download Preset (.master)")
|
|
|
|
|
117 |
def sync_sliders(preset):
|
118 |
if preset != "custom":
|
119 |
p = PRESETS[preset]
|
120 |
+
return (gr.update(value=p["gain"]), gr.update(value=p["compress_threshold"]), \
|
121 |
+
gr.update(value=p["compress_ratio"]), gr.update(value=p["low_pass"]), \
|
122 |
+
gr.update(value=p["high_pass"]), gr.update(value=p["stereo_width"]), \
|
123 |
+
gr.update(value=p["pan"]))
|
124 |
return None
|
125 |
+
preset_dropdown.change(sync_sliders, inputs=[preset_dropdown], outputs=[gain, threshold, ratio, lp, hp, width, pan])
|
126 |
+
load_preset.upload(load_preset_json, inputs=[load_preset], outputs=[preset_dropdown, gain, threshold, ratio, lp, hp, width, pan])
|
127 |
+
export_button.click(process_and_export, inputs=[audio_input, preset_dropdown, gain, threshold, ratio, lp, hp, width, pan], outputs=[output_audio, export_preset_file])
|
|
|
|
|
|
|
|
|
128 |
|
129 |
if __name__ == "__main__":
|
130 |
demo.launch()
|