Spaces:
Sleeping
Sleeping
Commit
·
061389d
1
Parent(s):
3b53c15
new v2
Browse files
app.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
from plotly.subplots import make_subplots
|
2 |
from scipy.signal import find_peaks, butter, filtfilt
|
3 |
import plotly.graph_objects as go
|
|
|
4 |
import gradio as gr
|
5 |
import numpy as np
|
6 |
import itertools
|
@@ -8,55 +9,14 @@ import librosa
|
|
8 |
import random
|
9 |
import os
|
10 |
|
|
|
|
|
11 |
example_dir = "Examples"
|
12 |
example_files = [os.path.join(example_dir, f) for f in os.listdir(example_dir) if f.endswith(('.wav', '.mp3', '.ogg'))]
|
13 |
all_pairs = list(itertools.combinations(example_files, 2))
|
14 |
random.shuffle(all_pairs)
|
15 |
example_pairs = [list(pair) for pair in all_pairs[:25]]
|
16 |
|
17 |
-
# GENERAL HELPER FUNCTIONS
|
18 |
-
def getaudiodata(filepath)->tuple[int,np.ndarray]:
|
19 |
-
|
20 |
-
audiodata, sr = librosa.load(filepath, sr=None)
|
21 |
-
|
22 |
-
# Ensure audiodata is a numpy array
|
23 |
-
if not isinstance(audiodata, np.ndarray):
|
24 |
-
audiodata = np.array(audiodata)
|
25 |
-
|
26 |
-
# Check if audio is mono or stereo
|
27 |
-
if len(audiodata.shape) > 1:
|
28 |
-
# If stereo, convert to mono by averaging channels
|
29 |
-
audiodata = np.mean(audiodata, axis=1)
|
30 |
-
|
31 |
-
audiodata = np.astype(audiodata, np.float16)
|
32 |
-
|
33 |
-
return sr, audiodata
|
34 |
-
|
35 |
-
def getBeats(audiodata:np.ndarray, sr:int):
|
36 |
-
# Convert audio data to float32
|
37 |
-
audiodata = audiodata.astype(np.float32)
|
38 |
-
|
39 |
-
# Normalize the audio data
|
40 |
-
audiodata = audiodata / np.max(np.abs(audiodata))
|
41 |
-
|
42 |
-
# Set the threshold for peak detection (adjust this value as needed)
|
43 |
-
threshold = 0.5 # 50% of the maximum amplitude
|
44 |
-
|
45 |
-
# Find peaks above the threshold
|
46 |
-
peaks, _ = find_peaks(np.abs(audiodata), height=threshold, distance=int(sr * 0.3))
|
47 |
-
|
48 |
-
# Convert peak indices to times
|
49 |
-
peak_times = (peaks / sr)*2
|
50 |
-
|
51 |
-
# Calculate tempo (beats per minute)
|
52 |
-
if len(peak_times) > 1:
|
53 |
-
avg_interval = np.mean(np.diff(peak_times))
|
54 |
-
tempo = 60 / avg_interval
|
55 |
-
else:
|
56 |
-
tempo = 0
|
57 |
-
|
58 |
-
return [tempo], peak_times
|
59 |
-
|
60 |
def getHRV(beattimes: np.ndarray) -> np.ndarray:
|
61 |
# Calculate instantaneous heart rate
|
62 |
instantaneous_hr = 60 * np.diff(beattimes)
|
@@ -166,43 +126,6 @@ def plotCombined(audiodata, sr, filename):
|
|
166 |
|
167 |
return fig
|
168 |
|
169 |
-
def plotbeatscatter(tempo, beattimes):
|
170 |
-
# Calculate beat durations
|
171 |
-
beat_durations = np.diff(beattimes)
|
172 |
-
|
173 |
-
# Calculate cumulative times for x-axis
|
174 |
-
cumulative_times = np.cumsum(beat_durations)
|
175 |
-
|
176 |
-
# Create scatter plot
|
177 |
-
fig = go.Figure()
|
178 |
-
|
179 |
-
# Add scatter plot of beat durations
|
180 |
-
fig.add_trace(go.Scatter(
|
181 |
-
x=cumulative_times,
|
182 |
-
y=beat_durations,
|
183 |
-
mode='markers',
|
184 |
-
name='Beat Durations',
|
185 |
-
marker=dict(
|
186 |
-
size=8
|
187 |
-
)
|
188 |
-
))
|
189 |
-
|
190 |
-
# Add line for average beat duration
|
191 |
-
avg_duration = 60 / tempo[0] if isinstance(tempo, list) else 60 / tempo # Convert tempo (BPM) to seconds
|
192 |
-
fig.add_hline(y=avg_duration, line=dict(color='red', dash='dash'),
|
193 |
-
annotation_text=f"Average: {avg_duration:.2f}s",
|
194 |
-
annotation_position="top right")
|
195 |
-
|
196 |
-
# Update layout
|
197 |
-
fig.update_layout(
|
198 |
-
title_text='Beat Durations Over Time',
|
199 |
-
xaxis_title_text='Cumulative Time (seconds)',
|
200 |
-
yaxis_title_text='Beat Duration (seconds)',
|
201 |
-
showlegend=True
|
202 |
-
)
|
203 |
-
|
204 |
-
return fig
|
205 |
-
|
206 |
def analyze_single(audio:gr.Audio):
|
207 |
# Extract audio data and sample rate
|
208 |
filepath = audio
|
@@ -230,7 +153,7 @@ def analyze_single(audio:gr.Audio):
|
|
230 |
|
231 |
tempo, beattimes = getBeats(audiodata, sr)
|
232 |
spectogram_wave = plotCombined(audiodata, sr, filename)
|
233 |
-
beats_histogram = plotbeatscatter(tempo[0], beattimes)
|
234 |
|
235 |
# Add the new average heartbeat analysis
|
236 |
avg_beat_plot, avg_beat = create_average_heartbeat(audiodata, sr)
|
@@ -253,83 +176,28 @@ def analyze_single(audio:gr.Audio):
|
|
253 |
- Beat durations: {np.diff(beattimes)}
|
254 |
- Mean Beat Duration: {np.mean(np.diff(beattimes)):.4f}
|
255 |
"""
|
256 |
-
return results, spectogram_wave, avg_beat_plot
|
257 |
#-----------------------------------------------
|
258 |
#-----------------------------------------------
|
259 |
|
260 |
-
# HELPER FUNCTIONS FOR
|
261 |
-
def analyze_double(audio1:gr.Audio, audio2:gr.Audio):
|
262 |
-
|
263 |
-
sr1, audiodata1 = getaudiodata(audio1)
|
264 |
-
sr2, audiodata2 = getaudiodata(audio2)
|
265 |
|
|
|
266 |
|
267 |
-
|
268 |
-
|
269 |
|
270 |
-
|
271 |
-
|
272 |
-
fig = make_subplots(rows=2, cols=2, shared_xaxes=True, vertical_spacing=0.1, shared_yaxes=True,
|
273 |
-
subplot_titles=['Audio 1 Waveform','Audio 2 Audio Waveform', 'Audio 1 Spectrogram', 'Audio 2 Spectrogram'])
|
274 |
|
275 |
-
|
276 |
-
time = (np.arange(0, len(audiodata1)) / sr1)*2
|
277 |
-
fig.add_trace(
|
278 |
-
go.Scatter(x=time, y=audiodata1, mode='lines', line=dict(color='blue', width=1), showlegend=False),
|
279 |
-
row=1, col=1
|
280 |
-
)
|
281 |
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
times = librosa.times_like(S_db)
|
286 |
-
freqs = librosa.fft_frequencies(sr=sr1)
|
287 |
|
288 |
-
fig.add_trace(
|
289 |
-
go.Heatmap(z=S_db, x=times, y=freqs, colorscale='Viridis',
|
290 |
-
zmin=S_db.min(), zmax=S_db.max(), showlegend=False),#, colorbar=dict(title='Magnitude (dB)')),
|
291 |
-
row=2, col=1
|
292 |
-
)
|
293 |
|
294 |
-
# Waveform plot
|
295 |
-
time = (np.arange(0, len(audiodata2)) / sr2)*2
|
296 |
-
fig.add_trace(
|
297 |
-
go.Scatter(x=time, y=audiodata2, mode='lines', line=dict(color='blue', width=1), showlegend=False),
|
298 |
-
row=1, col=2
|
299 |
-
)
|
300 |
|
301 |
-
# Spectrogram plot
|
302 |
-
D = librosa.stft(audiodata2)
|
303 |
-
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
|
304 |
-
times = librosa.times_like(S_db)
|
305 |
-
freqs = librosa.fft_frequencies(sr=sr2)
|
306 |
-
|
307 |
-
fig.add_trace(
|
308 |
-
go.Heatmap(z=S_db, x=times, y=freqs, colorscale='Viridis',
|
309 |
-
zmin=S_db.min(), zmax=S_db.max(), showlegend=False),#, colorbar=dict(title='Magnitude (dB)')),
|
310 |
-
row=2, col=2
|
311 |
-
)
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
# Update layout
|
318 |
-
fig.update_layout(
|
319 |
-
height=800, width=1200,
|
320 |
-
title_text="Audio Analysis",
|
321 |
-
showlegend=False
|
322 |
-
)
|
323 |
-
|
324 |
-
fig.update_xaxes(title_text="Time (s)", row=2, col=1)
|
325 |
-
fig.update_yaxes(title_text="Amplitude", row=1, col=1)
|
326 |
-
fig.update_yaxes(title_text="Frequency (Hz)", type="log", row=2, col=1)
|
327 |
-
|
328 |
-
fig.update_xaxes(title_text="Time (s)", row=2, col=2)
|
329 |
-
fig.update_yaxes(title_text="Amplitude", row=1, col=2)
|
330 |
-
fig.update_yaxes(title_text="Frequency (Hz)", type="log", row=2, col=2)
|
331 |
-
|
332 |
-
return fig
|
333 |
|
334 |
|
335 |
|
@@ -337,7 +205,7 @@ def plotCombineddouble(audiodata1, sr1, audiodata2, sr2):
|
|
337 |
with gr.Blocks() as app:
|
338 |
|
339 |
gr.Markdown("# Heartbeat")
|
340 |
-
gr.Markdown("This App helps to analyze and extract Information from
|
341 |
gr.Markdown("""
|
342 |
- Beat (mean) (average heartbeat duration)
|
343 |
- S1, S2 (mean) (average S1,S2 duration)
|
@@ -346,21 +214,46 @@ with gr.Blocks() as app:
|
|
346 |
- Plot of Wave & Spectogram (Beats annotated)
|
347 |
""")
|
348 |
|
349 |
-
|
|
|
|
|
|
|
350 |
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
analyzebtn = gr.Button("analyze")
|
357 |
|
358 |
results = gr.Markdown()
|
359 |
spectogram_wave = gr.Plot()
|
360 |
avg_beat_plot = gr.Plot()
|
361 |
-
beats_histogram = gr.Plot()
|
362 |
|
363 |
-
analyzebtn.click(analyze_single, audiofile, [results, spectogram_wave, avg_beat_plot
|
364 |
|
365 |
gr.Examples(
|
366 |
examples=example_files,
|
@@ -371,34 +264,6 @@ with gr.Blocks() as app:
|
|
371 |
)
|
372 |
|
373 |
|
374 |
-
# with gr.Tab("Two Audios"):
|
375 |
-
|
376 |
-
# with gr.Row():
|
377 |
-
|
378 |
-
# audioone = gr.Audio(
|
379 |
-
# type="filepath",
|
380 |
-
# label="Audio of a Heartbeat",
|
381 |
-
# sources="upload")
|
382 |
-
# audiotwo = gr.Audio(
|
383 |
-
# type="filepath",
|
384 |
-
# label="Audio of a Heartbeat",
|
385 |
-
# sources="upload")
|
386 |
-
|
387 |
-
# analyzebtn2 = gr.Button("analyze & compare")
|
388 |
-
|
389 |
-
# with gr.Accordion("Results",open=False):
|
390 |
-
# results2 = gr.Markdown()
|
391 |
-
# spectogram_wave2 = gr.Plot()
|
392 |
-
|
393 |
-
# analyzebtn2.click(analyze_double, inputs=[audioone,audiotwo], outputs=spectogram_wave2)
|
394 |
-
|
395 |
-
# gr.Examples(
|
396 |
-
# examples=example_pairs,
|
397 |
-
# inputs=[audioone, audiotwo],
|
398 |
-
# outputs=spectogram_wave2,
|
399 |
-
# fn=analyze_double,
|
400 |
-
# cache_examples=False
|
401 |
-
# )
|
402 |
|
403 |
|
404 |
app.launch()
|
|
|
1 |
from plotly.subplots import make_subplots
|
2 |
from scipy.signal import find_peaks, butter, filtfilt
|
3 |
import plotly.graph_objects as go
|
4 |
+
import pandas as pd
|
5 |
import gradio as gr
|
6 |
import numpy as np
|
7 |
import itertools
|
|
|
9 |
import random
|
10 |
import os
|
11 |
|
12 |
+
from utils import getaudiodata, getBeats, plotBeattimes
|
13 |
+
|
14 |
example_dir = "Examples"
|
15 |
example_files = [os.path.join(example_dir, f) for f in os.listdir(example_dir) if f.endswith(('.wav', '.mp3', '.ogg'))]
|
16 |
all_pairs = list(itertools.combinations(example_files, 2))
|
17 |
random.shuffle(all_pairs)
|
18 |
example_pairs = [list(pair) for pair in all_pairs[:25]]
|
19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
def getHRV(beattimes: np.ndarray) -> np.ndarray:
|
21 |
# Calculate instantaneous heart rate
|
22 |
instantaneous_hr = 60 * np.diff(beattimes)
|
|
|
126 |
|
127 |
return fig
|
128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
def analyze_single(audio:gr.Audio):
|
130 |
# Extract audio data and sample rate
|
131 |
filepath = audio
|
|
|
153 |
|
154 |
tempo, beattimes = getBeats(audiodata, sr)
|
155 |
spectogram_wave = plotCombined(audiodata, sr, filename)
|
156 |
+
#beats_histogram = plotbeatscatter(tempo[0], beattimes)
|
157 |
|
158 |
# Add the new average heartbeat analysis
|
159 |
avg_beat_plot, avg_beat = create_average_heartbeat(audiodata, sr)
|
|
|
176 |
- Beat durations: {np.diff(beattimes)}
|
177 |
- Mean Beat Duration: {np.mean(np.diff(beattimes)):.4f}
|
178 |
"""
|
179 |
+
return results, spectogram_wave, avg_beat_plot
|
180 |
#-----------------------------------------------
|
181 |
#-----------------------------------------------
|
182 |
|
183 |
+
# HELPER FUNCTIONS FOR SINGLE AUDIO ANALYSIS V2
|
|
|
|
|
|
|
|
|
184 |
|
185 |
+
def getBeatsv2(audio:gr.Audio):
|
186 |
|
187 |
+
sr, audiodata = getaudiodata(audio)
|
188 |
+
_, beattimes = getBeats(audiodata, sr)
|
189 |
|
190 |
+
fig = plotBeattimes(beattimes, audiodata, sr)
|
191 |
+
beattimes_table = pd.DataFrame(data={"Beattimes":beattimes})
|
|
|
|
|
192 |
|
193 |
+
return fig, beattimes_table
|
|
|
|
|
|
|
|
|
|
|
194 |
|
195 |
+
def updateBeatsv2(beattimes_table:gr.Dataframe, audio:gr.Audio)-> go.Figure:
|
196 |
+
sr, audiodata = getaudiodata(audio)
|
197 |
+
return plotBeattimes(beattimes_table["Beattimes"], audiodata, sr)
|
|
|
|
|
198 |
|
|
|
|
|
|
|
|
|
|
|
199 |
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
|
202 |
|
203 |
|
|
|
205 |
with gr.Blocks() as app:
|
206 |
|
207 |
gr.Markdown("# Heartbeat")
|
208 |
+
gr.Markdown("This App helps to analyze and extract Information from Heartbeat Audios")
|
209 |
gr.Markdown("""
|
210 |
- Beat (mean) (average heartbeat duration)
|
211 |
- S1, S2 (mean) (average S1,S2 duration)
|
|
|
214 |
- Plot of Wave & Spectogram (Beats annotated)
|
215 |
""")
|
216 |
|
217 |
+
audiofile = gr.Audio(
|
218 |
+
type="filepath",
|
219 |
+
label="Upload the Audio of a Heartbeat",
|
220 |
+
sources="upload")
|
221 |
|
222 |
+
|
223 |
+
with gr.Tab("Single Audio V2"):
|
224 |
+
|
225 |
+
getBeatsbtn = gr.Button("get Beats")
|
226 |
+
|
227 |
+
beats_wave_plot = gr.Plot()
|
228 |
+
beattimes_table = gr.Dataframe(
|
229 |
+
col_count=1,
|
230 |
+
type='pandas',
|
231 |
+
interactive=True)
|
232 |
|
233 |
+
updateBeatsbtn = gr.Button("update Beats")
|
234 |
+
|
235 |
+
|
236 |
+
|
237 |
+
getBeatsbtn.click(getBeatsv2, inputs=audiofile, outputs=[beats_wave_plot, beattimes_table])
|
238 |
+
updateBeatsbtn.click(updateBeatsv2, inputs=[beattimes_table, audiofile], outputs=[beats_wave_plot])
|
239 |
+
|
240 |
+
gr.Examples(
|
241 |
+
examples=example_files,
|
242 |
+
inputs=audiofile,
|
243 |
+
fn=getBeatsv2,
|
244 |
+
cache_examples=False
|
245 |
+
)
|
246 |
+
|
247 |
+
|
248 |
+
with gr.Tab("Single Audio V1"):
|
249 |
+
|
250 |
analyzebtn = gr.Button("analyze")
|
251 |
|
252 |
results = gr.Markdown()
|
253 |
spectogram_wave = gr.Plot()
|
254 |
avg_beat_plot = gr.Plot()
|
|
|
255 |
|
256 |
+
analyzebtn.click(analyze_single, audiofile, [results, spectogram_wave, avg_beat_plot])
|
257 |
|
258 |
gr.Examples(
|
259 |
examples=example_files,
|
|
|
264 |
)
|
265 |
|
266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
267 |
|
268 |
|
269 |
app.launch()
|
utils.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import librosa
|
2 |
+
import numpy as np
|
3 |
+
import plotly.graph_objects as go
|
4 |
+
from scipy.signal import find_peaks
|
5 |
+
|
6 |
+
|
7 |
+
# GENERAL HELPER FUNCTIONS
|
8 |
+
def getaudiodata(filepath)->tuple[int,np.ndarray]:
|
9 |
+
|
10 |
+
audiodata, sr = librosa.load(filepath, sr=None)
|
11 |
+
|
12 |
+
# Ensure audiodata is a numpy array
|
13 |
+
if not isinstance(audiodata, np.ndarray):
|
14 |
+
audiodata = np.array(audiodata)
|
15 |
+
|
16 |
+
# Check if audio is mono or stereo
|
17 |
+
if len(audiodata.shape) > 1:
|
18 |
+
# If stereo, convert to mono by averaging channels
|
19 |
+
audiodata = np.mean(audiodata, axis=1)
|
20 |
+
|
21 |
+
audiodata = np.astype(audiodata, np.float16)
|
22 |
+
|
23 |
+
return sr, audiodata
|
24 |
+
|
25 |
+
def getBeats(audiodata:np.ndarray, sr:int):
|
26 |
+
# Convert audio data to float32
|
27 |
+
audiodata = audiodata.astype(np.float32)
|
28 |
+
|
29 |
+
# Normalize the audio data
|
30 |
+
audiodata = audiodata / np.max(np.abs(audiodata))
|
31 |
+
|
32 |
+
# Set the threshold for peak detection (adjust this value as needed)
|
33 |
+
threshold = 0.5 # 50% of the maximum amplitude
|
34 |
+
|
35 |
+
# Find peaks above the threshold
|
36 |
+
peaks, _ = find_peaks(np.abs(audiodata), height=threshold, distance=int(sr * 0.3))
|
37 |
+
|
38 |
+
# Convert peak indices to times
|
39 |
+
peak_times = (peaks / sr)*2
|
40 |
+
|
41 |
+
# Calculate tempo (beats per minute)
|
42 |
+
if len(peak_times) > 1:
|
43 |
+
avg_interval = np.mean(np.diff(peak_times))
|
44 |
+
tempo = 60 / avg_interval
|
45 |
+
else:
|
46 |
+
tempo = 0
|
47 |
+
|
48 |
+
return [tempo], peak_times
|
49 |
+
|
50 |
+
def plotBeattimes(beattimes:np.ndarray, audiodata:np.ndarray, sr:int)->go.Figure:
|
51 |
+
|
52 |
+
# Time array for the full audio
|
53 |
+
time = (np.arange(0, len(audiodata)) / sr) * 2
|
54 |
+
|
55 |
+
# CREATE BEATTIMES PLOT
|
56 |
+
# Waveform plot
|
57 |
+
fig = go.Figure(
|
58 |
+
go.Scatter(x=time, y=audiodata, mode='lines', name='Waveform', line=dict(color='blue', width=1))
|
59 |
+
)
|
60 |
+
# Add beat markers
|
61 |
+
beat_amplitudes = np.interp(beattimes, time, audiodata)
|
62 |
+
fig.add_trace(
|
63 |
+
go.Scatter(x=beattimes, y=beat_amplitudes, mode='markers', name='Beats',
|
64 |
+
marker=dict(color='red', size=8, symbol='circle'))
|
65 |
+
)
|
66 |
+
|
67 |
+
fig.update_layout(
|
68 |
+
showlegend=False
|
69 |
+
)
|
70 |
+
|
71 |
+
return fig
|
72 |
+
|
73 |
+
|
74 |
+
def iterate_beat_segments(beat_times, sr, audio):
|
75 |
+
"""
|
76 |
+
Iterate over audio segments between beats.
|
77 |
+
|
78 |
+
Parameters:
|
79 |
+
- beat_times: np.ndarray of beat times in seconds
|
80 |
+
- sr: Sample rate of the audio
|
81 |
+
- audio: np.ndarray of audio data
|
82 |
+
|
83 |
+
Yields:
|
84 |
+
- Tuple of (start_sample, end_sample, audio_segment)
|
85 |
+
"""
|
86 |
+
# Convert beat times to sample indices
|
87 |
+
beat_samples = librosa.time_to_samples(beat_times, sr=sr)
|
88 |
+
|
89 |
+
# Add start and end points
|
90 |
+
beat_samples = np.concatenate(([0], beat_samples, [len(audio)]))
|
91 |
+
|
92 |
+
# Iterate over pairs of beat samples
|
93 |
+
for start, end in zip(beat_samples[:-1], beat_samples[1:]):
|
94 |
+
# Extract the audio segment
|
95 |
+
segment = audio[start:end]
|
96 |
+
|
97 |
+
segment_metrics = segment_analysis(segment, sr)
|
98 |
+
|
99 |
+
|
100 |
+
|
101 |
+
def segment_analysis(segment, sr):
|
102 |
+
"""
|
103 |
+
Analyze an audio segment and compute various metrics.
|
104 |
+
|
105 |
+
Parameters:
|
106 |
+
- segment: np.ndarray of audio segment data
|
107 |
+
- sr: Sample rate of the audio
|
108 |
+
|
109 |
+
Returns:
|
110 |
+
- List of computed metrics
|
111 |
+
"""
|
112 |
+
# Duration
|
113 |
+
duration = len(segment) / sr
|
114 |
+
|
115 |
+
# RMS Energy
|
116 |
+
rms_energy = np.sqrt(np.mean(segment**2))
|
117 |
+
|
118 |
+
# Frequencies
|
119 |
+
# We'll use the mean of the magnitudes of the Fourier transform
|
120 |
+
fft_magnitudes = np.abs(np.fft.rfft(segment))
|
121 |
+
mean_frequency = np.mean(fft_magnitudes)
|
122 |
+
|
123 |
+
# Attempt to detect S1 and S2
|
124 |
+
# This is a simplified approach and may not be accurate for all cases
|
125 |
+
peaks, _ = find_peaks(np.abs(segment), distance=int(0.2*sr)) # Assume at least 0.2s between peaks
|
126 |
+
|
127 |
+
if len(peaks) >= 2:
|
128 |
+
s1_index, s2_index = peaks[:2]
|
129 |
+
s1_to_s2_duration = (s2_index - s1_index) / sr
|
130 |
+
s2_to_s1_duration = (len(segment) - s2_index + peaks[0]) / sr if len(peaks) > 2 else None
|
131 |
+
else:
|
132 |
+
s1_to_s2_duration = None
|
133 |
+
s2_to_s1_duration = None
|
134 |
+
|
135 |
+
return [
|
136 |
+
rms_energy,
|
137 |
+
mean_frequency,
|
138 |
+
duration,
|
139 |
+
s1_to_s2_duration,
|
140 |
+
s2_to_s1_duration
|
141 |
+
]
|