Skip to content

Commit a92fcb5

Browse files
satraclaude
andauthored
Split recording widget from audio loading cell (#464)
* Split recording widget into separate cell from audio loading Recording cell shows the widget and saves to file. Load cell (next) checks if recording exists, falls back to downloading sample audio. This lets students record first, then load their recording in the subsequent cell. Applied to Tutorial 1 and Speech Representations Lab. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add recording widget to speech representations lab Split into recording cell (Colab JS widget with two buttons for sentence 1 and sentence 2) + load cell (checks for recordings, falls back to sample files). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add recording widget to Tutorial 2 Same pattern: recording cell (Colab JS, 5s) + load cell (checks for recording, falls back to sample). All three tutorials now have consistent recording support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7eddd5c commit a92fcb5

3 files changed

Lines changed: 261 additions & 76 deletions

File tree

tutorials/audio/audio_recording_and_acoustic_analysis.ipynb

Lines changed: 110 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,38 @@
3434
"metadata": {},
3535
"outputs": [],
3636
"source": [
37-
"import os\n\nimport matplotlib.pyplot as plt\nimport torch\nimport torchaudio.transforms as T\nfrom senselab.audio.data_structures import Audio\nfrom senselab.audio.tasks.classification.speech_emotion_recognition.api import (\n classify_emotions_from_speech,\n)\nfrom senselab.audio.tasks.features_extraction.praat_parselmouth import (\n extract_praat_parselmouth_features_from_audios,\n)\nfrom senselab.audio.tasks.features_extraction.torchaudio import (\n extract_pitch_from_audios,\n)\nfrom senselab.audio.tasks.plotting.plotting import (\n play_audio,\n plot_specgram,\n plot_waveform,\n)\nfrom senselab.audio.tasks.preprocessing.preprocessing import (\n downmix_audios_to_mono,\n resample_audios,\n)\nfrom senselab.audio.tasks.speaker_verification.speaker_verification import (\n verify_speaker,\n)\nfrom senselab.utils.data_structures import DeviceType, HFModel, SpeechBrainModel\n\n# Auto-detect device\ndevice = DeviceType.CUDA if torch.cuda.is_available() else DeviceType.CPU\nprint(f\"Using device: {device.value}\")"
37+
"import os\n",
38+
"\n",
39+
"import matplotlib.pyplot as plt\n",
40+
"import torch\n",
41+
"import torchaudio.transforms as T\n",
42+
"from senselab.audio.data_structures import Audio\n",
43+
"from senselab.audio.tasks.classification.speech_emotion_recognition.api import (\n",
44+
" classify_emotions_from_speech,\n",
45+
")\n",
46+
"from senselab.audio.tasks.features_extraction.praat_parselmouth import (\n",
47+
" extract_praat_parselmouth_features_from_audios,\n",
48+
")\n",
49+
"from senselab.audio.tasks.features_extraction.torchaudio import (\n",
50+
" extract_pitch_from_audios,\n",
51+
")\n",
52+
"from senselab.audio.tasks.plotting.plotting import (\n",
53+
" play_audio,\n",
54+
" plot_specgram,\n",
55+
" plot_waveform,\n",
56+
")\n",
57+
"from senselab.audio.tasks.preprocessing.preprocessing import (\n",
58+
" downmix_audios_to_mono,\n",
59+
" resample_audios,\n",
60+
")\n",
61+
"from senselab.audio.tasks.speaker_verification.speaker_verification import (\n",
62+
" verify_speaker,\n",
63+
")\n",
64+
"from senselab.utils.data_structures import DeviceType, HFModel, SpeechBrainModel\n",
65+
"\n",
66+
"# Auto-detect device\n",
67+
"device = DeviceType.CUDA if torch.cuda.is_available() else DeviceType.CPU\n",
68+
"print(f\"Using device: {device.value}\")"
3869
]
3970
},
4071
{
@@ -50,21 +81,16 @@
5081
"metadata": {},
5182
"outputs": [],
5283
"source": [
53-
"# Option 1: Record audio in Google Colab (uses browser microphone)\n",
54-
"# Option 2: Automatically falls back to downloading a sample file\n",
55-
"import urllib.request\n",
84+
"# Record audio in Google Colab (uses browser microphone)\n",
85+
"# If not in Colab, skip this cell and run the next one to load a sample file.\n",
5686
"from pathlib import Path\n",
5787
"\n",
5888
"Path(\"tutorial_audio_files\").mkdir(exist_ok=True)\n",
5989
"\n",
60-
"base_url = \"https://github.com/sensein/senselab/raw/main/src/tests/data_for_testing\"\n",
61-
"audio_path = None\n",
62-
"\n",
6390
"try:\n",
6491
" from google.colab import output\n",
6592
" from IPython.display import HTML, display\n",
6693
"\n",
67-
" # JavaScript-based recording widget for Colab\n",
6894
" RECORD_JS = \"\"\"\n",
6995
" <button id=\"record-btn\" style=\"font-size:16px;padding:8px 16px\">\ud83c\udf99 Click to Record (3s)</button>\n",
7096
" <script>\n",
@@ -85,7 +111,7 @@
85111
" google.colab.kernel.invokeFunction('save_recording', [b64], {});\n",
86112
" };\n",
87113
" reader.readAsDataURL(blob);\n",
88-
" btn.textContent = '\u2705 Recorded! Saved.';\n",
114+
" btn.textContent = '\u2705 Recorded! Run the next cell to load it.';\n",
89115
" };\n",
90116
" recorder.start();\n",
91117
" setTimeout(() => recorder.stop(), 3000);\n",
@@ -104,22 +130,34 @@
104130
" output.register_callback(\"save_recording\", save_recording)\n",
105131
" display(HTML(RECORD_JS))\n",
106132
" print(\"Click the button above to record 3 seconds of audio.\")\n",
107-
" print(\"After recording, re-run this cell to load it.\")\n",
108-
"\n",
109-
" rec_path = Path(\"tutorial_audio_files/my_recording.wav\")\n",
110-
" if rec_path.exists() and rec_path.stat().st_size > 1000:\n",
111-
" audio_path = str(rec_path.resolve())\n",
112-
" print(f\"Using your recording: {audio_path}\")\n",
133+
" print(\"After recording, run the NEXT cell to load it.\")\n",
113134
"except Exception:\n",
114-
" pass\n",
135+
" print(\"Recording not available (not in Colab). Run the next cell to load a sample file.\")"
136+
]
137+
},
138+
{
139+
"cell_type": "code",
140+
"execution_count": null,
141+
"metadata": {},
142+
"outputs": [],
143+
"source": [
144+
"# Load recorded audio if available, otherwise download a sample file\n",
145+
"import urllib.request\n",
115146
"\n",
116-
"# Fallback: download sample audio files\n",
147+
"base_url = \"https://github.com/sensein/senselab/raw/main/src/tests/data_for_testing\"\n",
148+
"\n",
149+
"# Always download sample files (needed for speaker comparison later)\n",
117150
"for fname in [\"audio_48khz_mono_16bits.wav\", \"audio_48khz_stereo_16bits.wav\"]:\n",
118151
" dest = Path(\"tutorial_audio_files\") / fname\n",
119152
" if not dest.exists():\n",
120153
" urllib.request.urlretrieve(f\"{base_url}/{fname}\", str(dest))\n",
121154
"\n",
122-
"if audio_path is None:\n",
155+
"# Check if a recording was made\n",
156+
"rec_path = Path(\"tutorial_audio_files/my_recording.wav\")\n",
157+
"if rec_path.exists() and rec_path.stat().st_size > 1000:\n",
158+
" audio_path = str(rec_path.resolve())\n",
159+
" print(f\"Using your recording: {audio_path}\")\n",
160+
"else:\n",
123161
" audio_path = os.path.abspath(\"tutorial_audio_files/audio_48khz_mono_16bits.wav\")\n",
124162
" print(f\"Using sample audio: {audio_path}\")\n",
125163
"\n",
@@ -398,7 +436,60 @@
398436
"metadata": {},
399437
"outputs": [],
400438
"source": [
401-
"# Enhance the audio using SpeechBrain's speech enhancement model\nfrom senselab.audio.tasks.speech_enhancement.api import enhance_audios\n\nenhanced_audio = enhance_audios(\n [audio_16k], model=SpeechBrainModel(path_or_uri=\"speechbrain/sepformer-wham16k-enhancement\"), device=device\n)[0]\nprint(f\"Enhanced audio: {enhanced_audio.waveform.shape[1]} samples at {enhanced_audio.sampling_rate} Hz\")\n\n# Compare spectrograms: before vs after enhancement\nfig, axes = plt.subplots(2, 2, figsize=(16, 10))\n\n# --- Before enhancement ---\nwf_orig = audio_16k.waveform.squeeze().numpy()\ndur_orig = len(wf_orig) / audio_16k.sampling_rate\n\n# Spectrogram (before)\nspec_t = T.MelSpectrogram(sample_rate=16000, n_fft=1024, hop_length=256, n_mels=80, f_max=5500)\nspec_orig = T.AmplitudeToDB()(spec_t(audio_16k.waveform.squeeze()))\naxes[0, 0].imshow(spec_orig.numpy(), aspect=\"auto\", origin=\"lower\", extent=[0, dur_orig, 0, 5500], cmap=\"magma\")\naxes[0, 0].set_title(\"Before Enhancement\")\naxes[0, 0].set_ylabel(\"Frequency (Hz)\")\n\n# Spectrogram (after)\nspec_enh = T.AmplitudeToDB()(spec_t(enhanced_audio.waveform.squeeze()))\ndur_enh = enhanced_audio.waveform.shape[1] / enhanced_audio.sampling_rate\naxes[0, 1].imshow(spec_enh.numpy(), aspect=\"auto\", origin=\"lower\", extent=[0, dur_enh, 0, 5500], cmap=\"magma\")\naxes[0, 1].set_title(\"After Enhancement\")\n\n# Pitch comparison\npitch_orig = extract_pitch_from_audios([audio_16k])[0][\"pitch\"].squeeze()\npitch_enh = extract_pitch_from_audios([enhanced_audio])[0][\"pitch\"].squeeze()\nt_orig = torch.arange(len(pitch_orig)) * 160 / 16000\nt_enh = torch.arange(len(pitch_enh)) * 160 / 16000\n\nv_orig = pitch_orig > 0\naxes[1, 0].scatter(t_orig[v_orig], pitch_orig[v_orig], s=2, c=\"blue\", label=\"Before\")\naxes[1, 0].set_title(\"Pitch: Before\")\naxes[1, 0].set_ylabel(\"F0 (Hz)\")\naxes[1, 0].set_xlabel(\"Time (s)\")\naxes[1, 0].grid(True, alpha=0.3)\n\nv_enh = pitch_enh > 0\naxes[1, 1].scatter(t_enh[v_enh], pitch_enh[v_enh], s=2, c=\"green\", label=\"After\")\naxes[1, 1].set_title(\"Pitch: After\")\naxes[1, 1].set_xlabel(\"Time (s)\")\naxes[1, 1].grid(True, alpha=0.3)\n\nplt.suptitle(\"Speech Enhancement: Before vs After\", fontsize=14)\nplt.tight_layout()\nplt.show()\n\n# Play both for comparison\nprint(\"Original audio:\")\nplay_audio(audio_16k)"
439+
"# Enhance the audio using SpeechBrain's speech enhancement model\n",
440+
"from senselab.audio.tasks.speech_enhancement.api import enhance_audios\n",
441+
"\n",
442+
"enhanced_audio = enhance_audios(\n",
443+
" [audio_16k], model=SpeechBrainModel(path_or_uri=\"speechbrain/sepformer-wham16k-enhancement\"), device=device\n",
444+
")[0]\n",
445+
"print(f\"Enhanced audio: {enhanced_audio.waveform.shape[1]} samples at {enhanced_audio.sampling_rate} Hz\")\n",
446+
"\n",
447+
"# Compare spectrograms: before vs after enhancement\n",
448+
"fig, axes = plt.subplots(2, 2, figsize=(16, 10))\n",
449+
"\n",
450+
"# --- Before enhancement ---\n",
451+
"wf_orig = audio_16k.waveform.squeeze().numpy()\n",
452+
"dur_orig = len(wf_orig) / audio_16k.sampling_rate\n",
453+
"\n",
454+
"# Spectrogram (before)\n",
455+
"spec_t = T.MelSpectrogram(sample_rate=16000, n_fft=1024, hop_length=256, n_mels=80, f_max=5500)\n",
456+
"spec_orig = T.AmplitudeToDB()(spec_t(audio_16k.waveform.squeeze()))\n",
457+
"axes[0, 0].imshow(spec_orig.numpy(), aspect=\"auto\", origin=\"lower\", extent=[0, dur_orig, 0, 5500], cmap=\"magma\")\n",
458+
"axes[0, 0].set_title(\"Before Enhancement\")\n",
459+
"axes[0, 0].set_ylabel(\"Frequency (Hz)\")\n",
460+
"\n",
461+
"# Spectrogram (after)\n",
462+
"spec_enh = T.AmplitudeToDB()(spec_t(enhanced_audio.waveform.squeeze()))\n",
463+
"dur_enh = enhanced_audio.waveform.shape[1] / enhanced_audio.sampling_rate\n",
464+
"axes[0, 1].imshow(spec_enh.numpy(), aspect=\"auto\", origin=\"lower\", extent=[0, dur_enh, 0, 5500], cmap=\"magma\")\n",
465+
"axes[0, 1].set_title(\"After Enhancement\")\n",
466+
"\n",
467+
"# Pitch comparison\n",
468+
"pitch_orig = extract_pitch_from_audios([audio_16k])[0][\"pitch\"].squeeze()\n",
469+
"pitch_enh = extract_pitch_from_audios([enhanced_audio])[0][\"pitch\"].squeeze()\n",
470+
"t_orig = torch.arange(len(pitch_orig)) * 160 / 16000\n",
471+
"t_enh = torch.arange(len(pitch_enh)) * 160 / 16000\n",
472+
"\n",
473+
"v_orig = pitch_orig > 0\n",
474+
"axes[1, 0].scatter(t_orig[v_orig], pitch_orig[v_orig], s=2, c=\"blue\", label=\"Before\")\n",
475+
"axes[1, 0].set_title(\"Pitch: Before\")\n",
476+
"axes[1, 0].set_ylabel(\"F0 (Hz)\")\n",
477+
"axes[1, 0].set_xlabel(\"Time (s)\")\n",
478+
"axes[1, 0].grid(True, alpha=0.3)\n",
479+
"\n",
480+
"v_enh = pitch_enh > 0\n",
481+
"axes[1, 1].scatter(t_enh[v_enh], pitch_enh[v_enh], s=2, c=\"green\", label=\"After\")\n",
482+
"axes[1, 1].set_title(\"Pitch: After\")\n",
483+
"axes[1, 1].set_xlabel(\"Time (s)\")\n",
484+
"axes[1, 1].grid(True, alpha=0.3)\n",
485+
"\n",
486+
"plt.suptitle(\"Speech Enhancement: Before vs After\", fontsize=14)\n",
487+
"plt.tight_layout()\n",
488+
"plt.show()\n",
489+
"\n",
490+
"# Play both for comparison\n",
491+
"print(\"Original audio:\")\n",
492+
"play_audio(audio_16k)"
402493
]
403494
},
404495
{

0 commit comments

Comments
 (0)