Skip to content

Commit a658915

Browse files
satraclaude
andauthored
Pedagogical tutorials + PPG phoneme duration analysis (#460)
* Pedagogical tutorials + PPG phoneme duration analysis New tutorials: - audio_recording_and_acoustic_analysis: record/load audio, waveform, spectrogram, pitch contour, formant tracks, emotion detection, speaker matching - transcription_and_phonemic_analysis: ASR, forced alignment with aligned multi-panel visualization, PPG extraction, phoneme duration analysis and timeline - shbt205_lab: course lab adapted from raw Whisper/SPARC/Promonet to use senselab APIs throughout Library changes: - ppg.py: fresh phoneme duration analysis (extract_mean_phoneme_durations, plot_ppg_phoneme_timeline) — supersedes PR #431 - Fix MPLBACKEND leak into subprocess venvs - 7 new PPG tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix ruff I001 for notebooks, add manifest entries - Add I001 to per-file-ignores for tutorials (import sorting in notebook cells is expected) - Add 3 new tutorials to manifest.json with CPU/GPU timeouts - Fix empty PPG segments edge case 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 e8451a5 commit a658915

15 files changed

Lines changed: 2646 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ convention = "google"
177177
[tool.ruff.lint.per-file-ignores]
178178
"src/tests/**/*.py" = []
179179
# Notebooks: cells share state but ruff lints per-cell, causing false positives
180-
"tutorials/**/*.ipynb" = ["F821", "E402"]
180+
"tutorials/**/*.ipynb" = ["F821", "E402", "I001"]
181181

182182
[tool.ruff.format]
183183
quote-style = "double"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Specification Quality Checklist: Pedagogical Audio Tutorials and PPG Phoneme Durations
2+
3+
**Purpose**: Validate specification completeness and quality before proceeding to planning
4+
**Created**: 2026-04-23
5+
**Feature**: [spec.md](../spec.md)
6+
7+
## Content Quality
8+
9+
- [x] No implementation details (languages, frameworks, APIs)
10+
- [x] Focused on user value and business needs
11+
- [x] Written for non-technical stakeholders
12+
- [x] All mandatory sections completed
13+
14+
## Requirement Completeness
15+
16+
- [x] No [NEEDS CLARIFICATION] markers remain
17+
- [x] Requirements are testable and unambiguous
18+
- [x] Success criteria are measurable
19+
- [x] Success criteria are technology-agnostic (no implementation details)
20+
- [x] All acceptance scenarios are defined
21+
- [x] Edge cases are identified
22+
- [x] Scope is clearly bounded
23+
- [x] Dependencies and assumptions identified
24+
25+
## Feature Readiness
26+
27+
- [x] All functional requirements have clear acceptance criteria
28+
- [x] User scenarios cover primary flows
29+
- [x] Feature meets measurable outcomes defined in Success Criteria
30+
- [x] No implementation details leak into specification
31+
32+
## Notes
33+
34+
- All items pass validation. Spec is ready for `/speckit.plan`.
35+
- FR-009 (PR #431 rebase) is a prerequisite dependency for FR-006 (PPG timeline in Tutorial 2).
36+
- The recording mechanism (FR-001) is browser-dependent; the fallback sample audio (FR-003) ensures CI testability.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Data Model: Pedagogical Audio Tutorials
2+
3+
**Date**: 2026-04-23
4+
5+
This feature is primarily tutorial/notebook content — no new persistent data models are introduced. The tutorials use existing senselab data structures.
6+
7+
## Existing Entities Used
8+
9+
### Audio
10+
- **Source**: `senselab.audio.data_structures.Audio`
11+
- **Created from**: file path, waveform tensor + sampling rate, or stream
12+
- **Key attributes**: `waveform` (torch.Tensor), `sampling_rate` (int), `filepath` (optional Path)
13+
- **Used in**: All tutorials (loaded from file or recording)
14+
15+
### AudioClassificationResult
16+
- **Source**: `senselab.audio.data_structures.AudioClassificationResult`
17+
- **Key attributes**: `labels` (List[str]), `scores` (List[float])
18+
- **Used in**: Tutorial 1 (emotion detection)
19+
20+
### ScriptLine
21+
- **Source**: `senselab.audio.data_structures.ScriptLine`
22+
- **Key attributes**: `text` (str), `start` (float), `end` (float), `chunks` (List[ScriptLine])
23+
- **Used in**: Tutorial 2 (ASR output, forced alignment)
24+
25+
### Model Types
26+
- `HFModel`: Hugging Face model reference (ASR, emotion)
27+
- `SpeechBrainModel`: SpeechBrain model (speaker verification, embeddings)
28+
- `DeviceType`: CPU/CUDA device selection
29+
30+
## New API Surface (fresh implementation, superseding PR #431)
31+
32+
### extract_mean_phoneme_durations
33+
- **Input**: `audio: Audio`, `posteriorgram: torch.Tensor`
34+
- **Output**: `Dict[str, Any]` with `phoneme_durations` list
35+
- **Used in**: Tutorial 2, SHBT205-Lab
36+
37+
### plot_ppg_phoneme_timeline
38+
- **Input**: `audio: Audio`, `posteriorgram: torch.Tensor`, `title: str`
39+
- **Output**: `matplotlib.figure.Figure`
40+
- **Used in**: Tutorial 2, SHBT205-Lab
41+
42+
## Tutorial Manifest Entries (new)
43+
44+
```json
45+
{
46+
"path": "tutorials/audio/audio_recording_and_acoustic_analysis.ipynb",
47+
"benefits_from_gpu": true,
48+
"requires_hf_token": false,
49+
"timeout_cpu": 600,
50+
"timeout_gpu": 300,
51+
"category": "audio"
52+
},
53+
{
54+
"path": "tutorials/audio/transcription_and_phonemic_analysis.ipynb",
55+
"benefits_from_gpu": true,
56+
"requires_hf_token": false,
57+
"timeout_cpu": 1800,
58+
"timeout_gpu": 600,
59+
"category": "audio"
60+
},
61+
{
62+
"path": "tutorials/audio/shbt205_lab.ipynb",
63+
"benefits_from_gpu": true,
64+
"requires_hf_token": true,
65+
"timeout_cpu": 1800,
66+
"timeout_gpu": 600,
67+
"category": "audio"
68+
}
69+
```
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Implementation Plan: Pedagogical Audio Tutorials and PPG Phoneme Durations
2+
3+
**Branch**: `20260423-213942-pedagogical-tutorials` | **Date**: 2026-04-23 | **Spec**: [spec.md](spec.md)
4+
**Input**: Feature specification from `/specs/20260423-213942-pedagogical-tutorials/spec.md`
5+
6+
## Summary
7+
8+
Create two new pedagogical audio tutorials for senselab, update two existing course notebooks to use senselab APIs, and implement fresh PPG phoneme duration analysis (superseding PR #431). Tutorial 1 covers audio recording/loading, waveform/spectrogram visualization, pitch extraction, formant extraction, emotion detection, and speaker matching. Tutorial 2 covers ASR, forced alignment, and PPG-based phoneme timeline analysis.
9+
10+
## Technical Context
11+
12+
**Language/Version**: Python 3.11-3.12 (Colab uses 3.12)
13+
**Primary Dependencies**: senselab (the library being tutorialized), papermill (CI execution), ipywebrtc or JS widgets (recording)
14+
**Storage**: N/A (notebooks are files in the repo)
15+
**Testing**: papermill for notebook execution in CI, pytest for PR #431 unit tests
16+
**Target Platform**: Google Colab (primary), local Jupyter (secondary)
17+
**Project Type**: Library tutorials (Jupyter notebooks)
18+
**Performance Goals**: Each tutorial completes in <30 minutes for students; CI execution within timeout limits
19+
**Constraints**: Must work on CPU (GPU optional); recording cells must be skippable for CI
20+
**Scale/Scope**: 4 notebooks (2 new, 2 updated), 1 PR merge, manifest updates
21+
22+
## Constitution Check
23+
24+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
25+
26+
| Principle | Status | Notes |
27+
|-----------|--------|-------|
28+
| I. UV-Managed Python | PASS | Tutorials use `!pip install uv` + `!uv pip install` in Colab; CI uses `uv run papermill` |
29+
| II. Encapsulated Testing | PASS | CI runs notebooks via papermill in uv-managed venv |
30+
| III. Commit Early and Often | PASS | Plan calls for incremental commits per task |
31+
| IV. CI Must Stay Green | PASS | All notebooks tested in CI via `test-tutorials` label |
32+
| V. Memory-Driven Anti-Pattern Avoidance | PASS | Checked memory for relevant warnings |
33+
| VI. No Unnecessary API Calls | PASS | Tutorials use local sample files; no redundant model downloads |
34+
| VII. Simplicity First | PASS | Tutorials use existing senselab APIs, no new abstractions |
35+
| VIII. No Hardcoded Parameters | PASS | Device auto-detected; sample files downloaded from URLs |
36+
37+
## Project Structure
38+
39+
### Documentation (this feature)
40+
41+
```text
42+
specs/20260423-213942-pedagogical-tutorials/
43+
├── plan.md # This file
44+
├── research.md # Phase 0 output
45+
├── data-model.md # Phase 1 output
46+
├── quickstart.md # Phase 1 output
47+
└── tasks.md # Phase 2 output (created by /speckit.tasks)
48+
```
49+
50+
### Source Code (repository root)
51+
52+
```text
53+
tutorials/
54+
├── audio/
55+
│ ├── audio_recording_and_acoustic_analysis.ipynb # NEW Tutorial 1
56+
│ ├── transcription_and_phonemic_analysis.ipynb # NEW Tutorial 2
57+
│ ├── shbt205_lab.ipynb # NEW (adapted from course)
58+
│ ├── 00_getting_started.ipynb # UPDATED
59+
│ └── ... (existing tutorials unchanged)
60+
├── manifest.json # UPDATED (add 3 entries)
61+
62+
src/senselab/audio/tasks/features_extraction/
63+
├── ppg.py # UPDATED (fresh phoneme duration implementation)
64+
└── __init__.py # UPDATED (phoneme duration exports)
65+
66+
src/tests/audio/tasks/
67+
└── features_extraction_test.py # UPDATED (PR #431 tests)
68+
```
69+
70+
**Structure Decision**: No new modules or directories. Tutorials go in existing `tutorials/audio/`. Phoneme duration functions added to existing ppg.py.
71+
72+
## Implementation Phases
73+
74+
### Phase 1: Implement PPG phoneme duration analysis (fresh)
75+
76+
**Goal**: Implement phoneme duration extraction and timeline plotting fresh on the current codebase, reviewing PR #431 for ideas but not merging it.
77+
78+
1. Read PR #431 code thoroughly (`extract_mean_phoneme_durations`, `plot_ppg_phoneme_timeline`)
79+
2. Implement equivalent functionality cleanly against current senselab APIs in `ppg.py`
80+
3. Add exports to `__init__.py`
81+
4. Write tests in `features_extraction_test.py`
82+
5. Run tests locally: `uv run pytest src/tests/audio/tasks/features_extraction_test.py -v -k "ppg or phoneme"`
83+
6. Run pre-commit: `uv run pre-commit run --all-files`
84+
7. Push, verify CI passes, merge to alpha
85+
8. Close PR #431 as superseded
86+
87+
### Phase 2: Create Tutorial 1 — Audio Recording & Acoustic Analysis
88+
89+
**Goal**: New notebook `tutorials/audio/audio_recording_and_acoustic_analysis.ipynb`
90+
91+
**Notebook structure** (sections with markdown + code cells):
92+
93+
1. **Title & Overview** (markdown): What students will learn — recording/loading audio, visualization, pitch, formants, emotion, speaker matching
94+
2. **Install senselab** (code): `!pip install -q uv` + `!uv pip install --pre --system "senselab"`
95+
3. **Restart runtime admonition** (markdown)
96+
4. **Imports & device setup** (code): senselab imports, auto-detect device
97+
5. **Section: Record or Load Audio** (markdown + code):
98+
- Markdown: Explain what audio is (waveform, sampling rate)
99+
- Code: JS-based recording widget (conditional on Colab) OR download sample audio
100+
- Code: Load as `Audio` object, display properties
101+
6. **Section: Waveform Visualization** (markdown + code):
102+
- Markdown: What a waveform represents (amplitude over time)
103+
- Code: `plot_waveform(audio)`, `play_audio(audio)`
104+
7. **Section: Spectrogram** (markdown + code):
105+
- Markdown: What a spectrogram shows (frequency content over time), mel scale
106+
- Code: `plot_specgram(audio)`, `plot_specgram(audio, mel_scale=True)`
107+
8. **Section: Pitch Extraction** (markdown + code):
108+
- Markdown: What pitch (F0) is, how it relates to vocal fold vibration
109+
- Code: `extract_pitch_from_audios([audio])`, plot pitch contour with matplotlib
110+
9. **Section: Formant Extraction** (markdown + code):
111+
- Markdown: What formants are (F1, F2), relationship to vowel identity
112+
- Code: `extract_praat_parselmouth_features_from_audios([audio])`, display F1/F2 values
113+
10. **Section: Speech Emotion Recognition** (markdown + code):
114+
- Markdown: How machines detect emotion from acoustic cues
115+
- Code: `classify_emotions_from_speech([audio], model)`, display labels + scores
116+
11. **Section: Speaker Matching** (markdown + code):
117+
- Markdown: How speaker verification works (embeddings + cosine similarity)
118+
- Code: Load/record second audio, `verify_speaker([(audio1, audio2)])`, interpret score
119+
120+
### Phase 3: Create Tutorial 2 — Transcription & Phonemic Analysis
121+
122+
**Goal**: New notebook `tutorials/audio/transcription_and_phonemic_analysis.ipynb`
123+
124+
**Notebook structure**:
125+
126+
1. **Title & Overview** (markdown): ASR, forced alignment, PPG phoneme timeline
127+
2. **Install senselab** (code): `!pip install -q uv` + `!uv pip install --pre --system "senselab[nlp]"`
128+
3. **Restart runtime admonition** (markdown)
129+
4. **Imports & device setup** (code)
130+
5. **Section: Load Audio** (code): Download sample audio or use recording from Tutorial 1
131+
6. **Section: Automatic Speech Recognition** (markdown + code):
132+
- Markdown: What ASR is, how Whisper works at a high level
133+
- Code: `transcribe_audios([audio], model=HFModel("openai/whisper-small"))`, display transcription
134+
7. **Section: Forced Alignment** (markdown + code):
135+
- Markdown: What forced alignment does (maps words/phones to time)
136+
- Code: `align_transcriptions([(audio, script, Language.EN)])`, display word and phone boundaries
137+
- Code: Visualize alignment on waveform (matplotlib overlay)
138+
8. **Section: Phonetic Posteriorgrams (PPGs)** (markdown + code):
139+
- Markdown: What PPGs are (probability distributions over phonemes per frame)
140+
- Code: `extract_ppgs_from_audios([audio])`, inspect shape and contents
141+
9. **Section: Phoneme Duration Analysis** (markdown + code):
142+
- Markdown: How to derive phoneme timing from PPGs
143+
- Code: `extract_mean_phoneme_durations(audio, ppg)`, display duration table
144+
- Code: `plot_ppg_phoneme_timeline(audio, ppg)`, interpret the timeline
145+
146+
### Phase 4: Update Existing Course Notebooks
147+
148+
**Goal**: Adapt `00_getting_started.ipynb` and `SHBT205-Lab.ipynb` for current senselab.
149+
150+
**4a: Update 00_getting_started.ipynb** (already in `tutorials/`):
151+
- Verify install cell uses current convention (already done in prior PR)
152+
- Verify all API calls use current senselab functions
153+
- Minor: This notebook is already up to date from the prior tutorial fix PR
154+
155+
**4b: Create shbt205_lab.ipynb** (new file adapted from course materials):
156+
- Start from `~/Downloads/drive-download-20260424T013242Z-3-001/SHBT205-Lab.ipynb`
157+
- Replace raw Whisper ASR → `transcribe_audios()`
158+
- Replace raw promonet PPG → `extract_ppgs_from_audios()` + PR #431 functions
159+
- Replace raw SPARC calls → `SparcFeatureExtractor.extract_sparc_features()`
160+
- Update install cell, add restart admonition, auto-detect device
161+
- Keep recording widget (conditional on Colab)
162+
- Add fallback sample audio download for CI
163+
- Clear all outputs
164+
165+
### Phase 5: Manifest & CI Integration
166+
167+
1. Add 3 new entries to `tutorials/manifest.json`:
168+
- `audio_recording_and_acoustic_analysis.ipynb` (timeout_cpu: 600, timeout_gpu: 300)
169+
- `transcription_and_phonemic_analysis.ipynb` (timeout_cpu: 1800, timeout_gpu: 600)
170+
- `shbt205_lab.ipynb` (timeout_cpu: 1800, timeout_gpu: 600)
171+
2. Test all notebooks locally via papermill
172+
3. Run pre-commit
173+
4. Push and verify CI with `test-tutorials` label
174+
175+
## Risk Mitigation
176+
177+
| Risk | Mitigation |
178+
|------|-----------|
179+
| PR #431 code outdated | Fresh implementation (Phase 1) avoids merge conflicts entirely |
180+
| Recording widget doesn't work in CI | All tutorials have downloaded sample audio fallback path |
181+
| PPG/SPARC subprocess venvs slow on CPU CI | Generous timeouts (1800s CPU) |
182+
| SHBT205-Lab SPARC voice conversion has no senselab wrapper | Document as known limitation; use SPARC feature extraction (which IS wrapped) and note voice conversion needs future API |
183+
| Colab JS recording approach varies across browsers | Provide clear instructions + sample audio fallback |
184+
185+
## Complexity Tracking
186+
187+
No constitution violations. All work uses existing senselab APIs and established tutorial patterns.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Quickstart: Pedagogical Audio Tutorials
2+
3+
## Prerequisites
4+
5+
```bash
6+
# Full development setup
7+
uv sync --extra articulatory --extra text --extra video --extra senselab-ai --group dev --group docs
8+
9+
# Pre-commit hooks
10+
uv run pre-commit install
11+
```
12+
13+
## Test a single tutorial locally
14+
15+
```bash
16+
export HF_TOKEN=<your-token>
17+
uv run papermill tutorials/audio/audio_recording_and_acoustic_analysis.ipynb /dev/null --cwd . -k python3 --execution-timeout 600
18+
```
19+
20+
## Test all tutorials
21+
22+
```bash
23+
export HF_TOKEN=<your-token>
24+
for nb in tutorials/audio/*.ipynb tutorials/video/*.ipynb tutorials/utils/*.ipynb; do
25+
echo -n "$(basename $nb): "
26+
uv run papermill "$nb" /dev/null --cwd . -k python3 --execution-timeout 600 2>&1 | tail -1
27+
done
28+
```
29+
30+
## Verify PR #431 PPG functions
31+
32+
```bash
33+
uv run pytest src/tests/audio/tasks/features_extraction_test.py -v -k "ppg or phoneme"
34+
```
35+
36+
## Key files to modify
37+
38+
| File | Purpose |
39+
|------|---------|
40+
| `tutorials/audio/audio_recording_and_acoustic_analysis.ipynb` | New Tutorial 1 |
41+
| `tutorials/audio/transcription_and_phonemic_analysis.ipynb` | New Tutorial 2 |
42+
| `tutorials/audio/00_getting_started.ipynb` | Update install/conventions |
43+
| `tutorials/audio/shbt205_lab.ipynb` | New — adapted from course materials |
44+
| `tutorials/manifest.json` | Add new tutorial entries |
45+
| `src/senselab/audio/tasks/features_extraction/ppg.py` | PR #431 merge |

0 commit comments

Comments
 (0)