Bils commited on
Commit
1a17445
·
verified ·
1 Parent(s): eda83fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -32
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import numpy as np
2
  import soundfile as sf
3
  import gradio as gr
 
4
 
5
  def binauralize(audio_file, simulate_rotation, rotation_speed):
6
  """
@@ -17,30 +18,25 @@ def binauralize(audio_file, simulate_rotation, rotation_speed):
17
  status (str): Status message.
18
  """
19
  try:
20
- # Load input audio file
21
  audio, sr = sf.read(audio_file)
22
  except Exception as e:
23
  return None, f"Error reading input audio file: {e}"
24
 
25
- # If the audio is stereo, convert to mono by averaging channels
26
  if audio.ndim > 1:
27
  audio = np.mean(audio, axis=1)
28
 
29
- # Create a time vector for the audio length
30
  t = np.arange(len(audio)) / sr
31
 
32
  if simulate_rotation:
33
  # Compute a time-varying angle for a full cycle (2π) at the desired rotation speed.
34
  angle = 2 * np.pi * rotation_speed * t
35
- # Constant power panning: left uses cosine, right uses sine.
36
  left = np.cos(angle) * audio
37
  right = np.sin(angle) * audio
38
  else:
39
- # If rotation is not enabled, duplicate the audio to both channels.
40
  left = audio
41
  right = audio
42
 
43
- # Combine the channels into a stereo signal.
44
  binaural_audio = np.stack((left, right), axis=-1)
45
 
46
  # Normalize to prevent clipping.
@@ -48,7 +44,6 @@ def binauralize(audio_file, simulate_rotation, rotation_speed):
48
  if max_val > 0:
49
  binaural_audio = binaural_audio / max_val
50
 
51
- # Save the output to a WAV file.
52
  output_file = "output_binaural.wav"
53
  try:
54
  sf.write(output_file, binaural_audio, sr)
@@ -57,16 +52,66 @@ def binauralize(audio_file, simulate_rotation, rotation_speed):
57
 
58
  return output_file, "Binaural conversion complete!"
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  # Create an enhanced UI using Gradio Blocks and Tabs.
61
  with gr.Blocks(title="SonicOrbit", css="""
62
- /* Custom CSS to enhance spacing and font styling */
63
  .title { font-size: 2.5em; font-weight: bold; text-align: center; margin-bottom: 0.5em; }
64
  .subtitle { font-size: 1.2em; text-align: center; margin-bottom: 1em; }
65
  .footer { text-align: center; font-size: 0.9em; margin-top: 2em; color: #555; }
 
66
  """) as demo:
67
 
68
  gr.Markdown("<div class='title'>SonicOrbit</div>")
69
- gr.Markdown("<div class='subtitle'>Binaural 360 Audio Converter with Dynamic Rotation</div>")
70
 
71
  with gr.Tabs():
72
  with gr.Tab("Converter"):
@@ -86,32 +131,29 @@ with gr.Blocks(title="SonicOrbit", css="""
86
  outputs=[output_audio, status_text]
87
  )
88
 
89
- with gr.Tab("Instructions"):
90
- gr.Markdown("""
91
- ### How to Use SonicOrbit
92
- 1. **Upload Audio:**
93
- Upload a mono or stereo audio file. If you upload a stereo file, it will be converted to mono by averaging the channels.
94
- 2. **Simulate Rotation:**
95
- Enable this option to apply a dynamic panning effect that simulates a rotating sound source.
96
- 3. **Rotation Speed:**
97
- Adjust the slider to set the speed of the rotation effect (in Hertz). A higher value rotates the audio field faster.
98
- 4. **Convert Audio:**
99
- Click the **Convert Audio** button to process your audio file. The output is a binaural (stereo) audio file with the simulated 360° effect.
100
-
101
- Enjoy your immersive 3D audio experience!
102
- """)
103
-
 
 
 
104
  gr.Markdown("""
105
  <div class='footer'>
106
- © 2025 SonicOrbit. All rights reserved.
107
- <br>
108
  Created with ❤️ by <a href="https://bilsimaging.com" target="_blank" style="color: #88aaff;">bilsimaging.com</a>
109
- </div>
110
- """)
111
-
112
- gr.HTML("""
113
- <div style="text-align: center; margin-top: 1rem;">
114
- <a href="https://visitorbadge.io/status?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2FBils%2FSonicOrbit" target="_blank">
115
  <img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2FBils%2FSonicOrbit&countColor=%23263759" alt="visitor badge" /></a>
116
  </div>
117
  """)
 
1
  import numpy as np
2
  import soundfile as sf
3
  import gradio as gr
4
+ import math
5
 
6
  def binauralize(audio_file, simulate_rotation, rotation_speed):
7
  """
 
18
  status (str): Status message.
19
  """
20
  try:
 
21
  audio, sr = sf.read(audio_file)
22
  except Exception as e:
23
  return None, f"Error reading input audio file: {e}"
24
 
25
+ # Convert to mono if needed.
26
  if audio.ndim > 1:
27
  audio = np.mean(audio, axis=1)
28
 
 
29
  t = np.arange(len(audio)) / sr
30
 
31
  if simulate_rotation:
32
  # Compute a time-varying angle for a full cycle (2π) at the desired rotation speed.
33
  angle = 2 * np.pi * rotation_speed * t
 
34
  left = np.cos(angle) * audio
35
  right = np.sin(angle) * audio
36
  else:
 
37
  left = audio
38
  right = audio
39
 
 
40
  binaural_audio = np.stack((left, right), axis=-1)
41
 
42
  # Normalize to prevent clipping.
 
44
  if max_val > 0:
45
  binaural_audio = binaural_audio / max_val
46
 
 
47
  output_file = "output_binaural.wav"
48
  try:
49
  sf.write(output_file, binaural_audio, sr)
 
52
 
53
  return output_file, "Binaural conversion complete!"
54
 
55
+ def simulate_map(audio_file, listener_x, listener_y):
56
+ """
57
+ Process an input audio file and simulate binaural panning based on the listener's position.
58
+ The source is fixed at (0,0). Listener coordinates (listener_x, listener_y) determine the angle.
59
+ Also applies optional distance attenuation.
60
+ """
61
+ try:
62
+ audio, sr = sf.read(audio_file)
63
+ except Exception as e:
64
+ return None, f"Error reading input audio file: {e}"
65
+
66
+ if audio.ndim > 1:
67
+ audio = np.mean(audio, axis=1)
68
+
69
+ # Compute the angle (in radians) between the listener position and the source at (0,0)
70
+ # Here, we use atan2(listener_x, listener_y) so that a positive X (to the right) yields a positive angle.
71
+ theta_rad = math.atan2(listener_x, listener_y)
72
+ theta_deg = theta_rad * 180 / math.pi
73
+
74
+ # Clamp theta to [-90, 90] degrees (for panning purposes)
75
+ theta_deg = max(-90, min(90, theta_deg))
76
+
77
+ # Map theta from [-90, 90] to a panning parameter p in radians:
78
+ # When theta_deg = -90, p = 0 (full left); theta_deg = 0, p = 45° in radians; theta_deg = 90, p = 90° (full right)
79
+ p = (theta_deg + 90) * math.pi / 360
80
+
81
+ left_gain = math.cos(p)
82
+ right_gain = math.sin(p)
83
+
84
+ # Optional distance attenuation: the further away the listener, the lower the volume.
85
+ distance = math.sqrt(listener_x**2 + listener_y**2)
86
+ attenuation = 1 / (1 + distance)
87
+
88
+ left = audio * left_gain * attenuation
89
+ right = audio * right_gain * attenuation
90
+
91
+ binaural_audio = np.stack((left, right), axis=-1)
92
+
93
+ max_val = np.max(np.abs(binaural_audio))
94
+ if max_val > 0:
95
+ binaural_audio = binaural_audio / max_val
96
+
97
+ output_file = "output_map.wav"
98
+ try:
99
+ sf.write(output_file, binaural_audio, sr)
100
+ except Exception as e:
101
+ return None, f"Error writing output audio file: {e}"
102
+
103
+ return output_file, f"Listener: ({listener_x}, {listener_y}), Angle: {theta_deg:.1f}°"
104
+
105
  # Create an enhanced UI using Gradio Blocks and Tabs.
106
  with gr.Blocks(title="SonicOrbit", css="""
 
107
  .title { font-size: 2.5em; font-weight: bold; text-align: center; margin-bottom: 0.5em; }
108
  .subtitle { font-size: 1.2em; text-align: center; margin-bottom: 1em; }
109
  .footer { text-align: center; font-size: 0.9em; margin-top: 2em; color: #555; }
110
+ .tab-description { margin: 10px; font-size: 1em; }
111
  """) as demo:
112
 
113
  gr.Markdown("<div class='title'>SonicOrbit</div>")
114
+ gr.Markdown("<div class='subtitle'>Binaural 360 Audio Converter & Interactive Map</div>")
115
 
116
  with gr.Tabs():
117
  with gr.Tab("Converter"):
 
131
  outputs=[output_audio, status_text]
132
  )
133
 
134
+ with gr.Tab("Interactive Map"):
135
+ gr.Markdown("<div class='tab-description'>Move the listener around the source (fixed at (0,0)) using the sliders. The binaural panning will update based on the listener's position and distance.</div>")
136
+ with gr.Row():
137
+ map_audio = gr.Audio(type="filepath", label="Upload Audio (Mono or Stereo)")
138
+ with gr.Row():
139
+ listener_x = gr.Slider(-10, 10, value=0, step=0.1, label="Listener X Position")
140
+ listener_y = gr.Slider(-10, 10, value=1, step=0.1, label="Listener Y Position")
141
+ map_button = gr.Button("Update Listener Position")
142
+ with gr.Row():
143
+ map_output = gr.Audio(type="filepath", label="Binaural Map Output")
144
+ map_status = gr.Textbox(label="Status", interactive=False)
145
+
146
+ map_button.click(
147
+ fn=simulate_map,
148
+ inputs=[map_audio, listener_x, listener_y],
149
+ outputs=[map_output, map_status]
150
+ )
151
+
152
  gr.Markdown("""
153
  <div class='footer'>
154
+ © 2025 SonicOrbit. All rights reserved.<br>
 
155
  Created with ❤️ by <a href="https://bilsimaging.com" target="_blank" style="color: #88aaff;">bilsimaging.com</a>
156
+ and this <a href="https://visitorbadge.io/status?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2FBils%2FSonicOrbit" target="_blank">
 
 
 
 
 
157
  <img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2FBils%2FSonicOrbit&countColor=%23263759" alt="visitor badge" /></a>
158
  </div>
159
  """)