kutipense's picture
Create app.py
7c1d039 verified
Raw
History Blame Contribute Delete
7.52 kB
import math
import os
import tempfile
import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
import trimesh
from scipy.interpolate import UnivariateSpline
LEN_SPLINE = 1000
def signal_to_spline(data, sr, signal_frame_length, spline_s, mult=1):
data = data * mult
data[data < 0] = 0
data = np.abs(data)
duration = len(data) / sr
f_len = int(sr * signal_frame_length)
frames = [data[i : i + f_len] for i in range(0, len(data), f_len)]
residue = f_len - len(data) % f_len
if residue:
frames[-1] = np.pad(frames[-1], (0, f_len - len(data) % f_len))
frame_max = np.max(frames, axis=1)
frame_times = (np.arange(len(frame_max)) * f_len + 0.5 * f_len) / sr
spline = UnivariateSpline(frame_times, frame_max, s=spline_s, ext=1)
time_spline = np.linspace(0, duration, LEN_SPLINE)
smooth_envelope = spline(time_spline)
smooth_envelope[smooth_envelope < 0] = 0
return smooth_envelope
def create_mesh_geometry(pos_env, neg_env, n_segs, z_scale, min_girth):
pos_env[pos_env < min_girth] = min_girth
neg_env[neg_env < min_girth] = min_girth
n_rings = len(pos_env)
theta = np.tile(np.linspace(0, np.pi * 2, n_segs, endpoint=False), (n_rings, 1))
space_0 = np.linspace(pos_env, neg_env, n_segs // 2, endpoint=False).T
space = np.concat([space_0, space_0[:, ::-1]], axis=1)
x = np.cos(theta) * space
y = np.sin(theta) * space
z = np.tile(np.linspace(0, z_scale, n_rings, endpoint=False)[:, None], (1, n_segs))
vertices = np.stack([x, y, z], axis=2).reshape(-1, 3)
j = np.arange(n_segs)
j_next = (j + 1) % n_segs
i = np.arange(n_rings - 1)
ring_offset = i[:, None] * n_segs
curr_j = ring_offset + j
curr_j_next = ring_offset + j_next
next_j = ring_offset + n_segs + j
next_j_next = ring_offset + n_segs + j_next
faces1 = np.stack([curr_j, curr_j_next, next_j], axis=2)
faces2 = np.stack([next_j, curr_j_next, next_j_next], axis=2)
faces = np.concatenate([faces1, faces2], axis=0).reshape(-1, 3)
return vertices, faces, n_segs, n_rings
def add_end_caps(vertices, faces, n_segs, n_rings):
bottom_center = vertices[:n_segs].mean(axis=0)
top_center = vertices[-n_segs:].mean(axis=0)
vertices = np.vstack([vertices, bottom_center, top_center])
bottom_center_idx = len(vertices) - 2
top_center_idx = len(vertices) - 1
bottom_ring_indices = np.arange(n_segs)
bottom_ring_next = (bottom_ring_indices + 1) % n_segs
bottom_faces = np.stack(
[np.full(n_segs, bottom_center_idx), bottom_ring_next, bottom_ring_indices],
axis=1,
)
top_ring_start = (n_rings - 1) * n_segs
top_ring_indices = top_ring_start + np.arange(n_segs)
top_ring_next = top_ring_start + (np.arange(n_segs) + 1) % n_segs
top_faces = np.stack(
[np.full(n_segs, top_center_idx), top_ring_indices, top_ring_next], axis=1
)
faces = np.vstack([faces, bottom_faces, top_faces])
return vertices, faces
def create_3d_model_from_audio(
audio_tuple, signal_frame_length, spline_s, n_segs, z_scale, y_scale, min_girth
):
if audio_tuple is None:
return None, None
sr, data = audio_tuple
if data.ndim > 1:
data = np.mean(data, axis=1)
data = data / np.max(data) * y_scale
duration = len(data) / sr
time = np.linspace(0, duration, len(data))
time_spline = np.linspace(0, duration, LEN_SPLINE)
pos_env = signal_to_spline(data, sr, signal_frame_length, spline_s)
neg_env = signal_to_spline(data, sr, signal_frame_length, spline_s, mult=-1)
fig_curve = plt.figure(figsize=(12, 3))
plt.plot(time, data, alpha=0.3, color="blue")
plt.plot(time_spline, pos_env, color="red")
plt.plot(time_spline, -neg_env, color="red")
plt.grid(True, linestyle="--", alpha=0.6)
plt.tight_layout()
vertices, faces, n_segs_actual, n_rings = create_mesh_geometry(
pos_env, neg_env, n_segs, z_scale, min_girth
)
vertices, faces = add_end_caps(vertices, faces, n_segs_actual, n_rings)
mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=True)
mesh.fix_normals()
mesh.apply_transform(
trimesh.transformations.rotation_matrix(math.pi / 2, [1, 0, 0])
)
torus = trimesh.creation.torus(
major_radius=min_girth * 1.5,
minor_radius=min_girth / 2,
major_sections=n_segs,
minor_sections=n_segs,
)
torus.apply_translation([0, min_girth, 0])
combined = trimesh.util.concatenate([mesh, torus])
temp_dir = tempfile.gettempdir()
output_path = os.path.join(temp_dir, "output_model.stl")
combined.export(output_path, file_type="stl")
return fig_curve, output_path
def create_interface():
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown(
"""
# 🔊 Audio to 3D Keychain Generator
Upload a WAV audio file and adjust the parameters to generate a 3D keychain model.
"""
)
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Input & Parameters")
audio_input = gr.Audio(type="numpy", label="Upload Audio File")
gr.Markdown("**Envelope Parameters**")
signal_frame_length = gr.Slider(
0.01,
0.5,
value=0.1,
step=0.01,
label="Signal Frame Length (lower -> more details)",
)
spline_s = gr.Slider(
1e-5, 1e-2, value=1e-4, step=1e-5, label="Spline Smoothing Factor"
)
gr.Markdown("**3D Model Parameters**")
n_segs = gr.Slider(
8, 128, value=32, step=1, label="Number of Segments (Resolution)"
)
z_scale = gr.Slider(
1.0, 20.0, value=5.0, step=0.5, label="Length (Z-Axis Scale)"
)
min_girth = gr.Slider(
0.0, 1.0, value=0.2, step=0.01, label="Minimum Girth (Radius)"
)
y_scale = gr.Slider(
0.1, 5.0, value=1.0, step=0.1, label="Radius (Y-Axis Scale)"
)
submit_btn = gr.Button("Generate Model", variant="primary")
with gr.Column(scale=2):
gr.Markdown("### Outputs")
plot_curve = gr.Plot(label="Signal Envelope")
model_3d = gr.Model3D(label="3D Model", camera_position=(0, -15, 5))
submit_btn.click(
fn=create_3d_model_from_audio,
inputs=[
audio_input,
signal_frame_length,
spline_s,
n_segs,
z_scale,
y_scale,
min_girth,
],
outputs=[plot_curve, model_3d],
)
gr.Examples(
[["voice.wav", 0.1, 1e-4, 64, 5.0, 0.2, 1.0]],
inputs=[
audio_input,
signal_frame_length,
spline_s,
n_segs,
z_scale,
min_girth,
y_scale,
],
outputs=[plot_curve, model_3d],
fn=create_3d_model_from_audio,
cache_examples=False, # Use False if you don't have a pre-existing voice.wav file
)
return demo
if __name__ == "__main__":
demo = create_interface()
demo.launch()