Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ jobs:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras --dev
run: |
uv sync --all-extras --dev
uv pip install -e .

- name: Lint with Ruff
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ env/
.idea/

*.ipynb_checkpoints

*.egg-info/
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,51 @@ This project consists of four interconnected subsystems, each one enabling the n

---

## 🚀 Getting Started

This project uses **uv** for dependency management.

### 1. Clone & Enter

```bash
git clone https://github.com/JacksonFergusonDev/systems-audio-lab.git
cd systems-audio-lab
```

### 2. Install Environment
We use an editable install so changes to the `sysaudio` library are immediately reflected in the notebooks.

```bash
# Initialize virtual environment
uv venv

# Activate environment
source .venv/bin/activate # Mac/Linux
# .venv\Scripts\activate # Windows

# Install dependencies and the local package
uv pip install -e .
```

### 3. Usage Options
Option A: Interactive Analysis (Jupyter) Launch the lab to view the engineering reports and signal processing pipelines.

```bash
jupyter lab
```

Option B: Headless Tools (CLI Scripts) You can run the capture and visualization tools directly from the command line.

```bash
# Example: Launch the real-time oscilloscope visualization
python oscilloscope-rp2040/scripts/visualization/live_scope.py

# Example: Record a single burst of data
python oscilloscope-rp2040/scripts/capture/record.py
```

---

## 📂 Repository Structure

```text
Expand All @@ -83,7 +128,7 @@ This project consists of four interconnected subsystems, each one enabling the n
│ │ ├── 02_instrument_analysis.ipynb # Harmonic Analysis 🟢
│ │ ├── 03_transfer_acquisition.ipynb # Sine Sweep Generation 🟢
│ │ └── 04_transfer_analysis.ipynb # Deconvolution (In Progress) 🟡
│ ├── src/ # Analysis Library (FFT, Plotting, Signal Processing)
│ ├── sysaudio/ # Analysis Library (FFT, Plotting, Signal Processing)
│ └── schematics/ # Signal Conditioning Circuit Design
├── red-llama-build/ # Guitar Overdrive Test Circuit
│ └── procurement/ # Bills of Materials
Expand Down
56 changes: 40 additions & 16 deletions oscilloscope-rp2040/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,50 @@ The MicroPython logic running on the MCU.
* **Engine:** Uses `@micropython.native` emitters for a jitter-free polling loop.
* **Protocol:** Implements a binary "Handshake" protocol (`s` for Science Burst, `v` for Video) to manage USB serial backpressure.

### [`src/`](./src)
### [`sysaudio/`](./sysaudio)
The Python library backing the analysis pipeline.
* **`src.daq`**: Hardware abstraction for USB UART stream processing.
* **`src.audio`**: Host-side signal generation (Sine, Square, Log Sweeps) via `sounddevice`.
* **`src.dsp`**: Signal processing (FFT, THD, Triggering).
* **`src.analysis`**: Publication-ready plotting and reporting engines.
* **Core & Hardware**
* `sysaudio.daq`: Hardware abstraction for USB UART stream processing.
* `sysaudio.calibration`: Routines for robust sample rate ($F_s$) verification.
* `sysaudio.audio`: Host-side signal generation (Sine, Square, Log Sweeps) via `sounddevice`.
* **Experimentation**
* `sysaudio.experiments`: High-level automation for Notebooks (Linearity, Bandwidth, THD captures).
* `sysaudio.io`: Standardized file handling for burst (`.npz`) and continuous signal data.
* **Analysis & Viz**
* `sysaudio.dsp`: Signal processing primitives (FFT, THD calculation, Triggering).
* `sysaudio.plots`: Publication-ready plotting wrappers (Bode, Transfer Curves, THD Fingerprints).
* `sysaudio.viz`: Real-time rendering engines for the live oscilloscope.

### [`scripts/`](./scripts)
User-facing tools for interaction.
* **`capture/`**: Headless recording tools.
* `record.py`: High-fidelity bursts.
* `measure_transfer.py`: Automated stimulus-response testing (Sweep/Steady).
* `record.py`: High-fidelity burst capture.
* `master_transfer.py`: Automated stimulus-response testing (Sweep/Steady).
* `stream.py`: Continuous data streaming utility.
* **`signal/`**: Interactive function generators.
* `play_wave.py`: Infinite waveform generator with live scope.
* `play_sweep.py`: Logarithmic sine sweep generator.
* **`visualization/`**: Real-time monitoring (`live_scope.py`) and rendering (`render_scope_video.py`).
* **`visualization/`**: Monitoring and rendering.
* `live_scope.py`: Real-time oscilloscope with FFT.
* `playback_scope.py`: Offline replay of captured signal files.
* `joyplot.py`: Ridge-line plot generation for spectral waterfalls.
* `render_scope_video.py`: Exports scope data to video frames.
* **`fun/`**: Creative coding experiments.
* `neon_torus.py`: XY oscilloscope visualization.
* `render_landscape.py`: 3D terrain generation from audio data. Used to generate the title graphic for [systems_audio_tech_report.pdf](../docs/systems_audio_tech_report.pdf)

### [`notebooks/`](./notebooks)
The scientific workbench.
* **`01_acquisition.ipynb`**: Hardware calibration and raw data inspection.
* **`02_analysis.ipynb`**: Empirical characterization of the Red Llama Overdrive (Harmonics & Topology).
* **`03_transfer_function.ipynb`**: System identification (Linearity & Frequency Response).
* **`01_instrument_acquisition.ipynb`**: Hardware calibration and raw data inspection.
* **`02_instrument_analysis.ipynb`**: Empirical characterization of the Red Llama Overdrive (Harmonics & Topology).
* **`03_transfer_acquisition.ipynb`**: Automated capture of transfer function datasets.
* *Set A:* Linearity (Triangle Wave).
* *Set B:* Bandwidth (Log Sine Sweep).
* *Set C:* Standard THD (1kHz Sine).
* **`04_transfer_analysis.ipynb`**: System Identification (In Progress).
* *Bode Response:* Magnitude and Phase analysis.
* *Transfer Curves:* Input vs. Output linearity mapping.
* *THD Fingerprinting:* Harmonic distortion profiling.

## Hardware Requirements
**Total Cost:** ~$4.14 USD
Expand All @@ -78,26 +100,28 @@ The scientific workbench.
Flash the RP2040 with MicroPython (v1.27+) and copy `firmware/main.py` to the root of the device.

### 2. Host Environment
Install dependencies (requires Python 3.10+):
Dependencies are managed at the **project root**. Run the following from the top-level `systems-audio-lab` directory:

```bash
uv sync
# Install dependencies and link the sysaudio package
uv pip install -e .
```

### 3. Execution
Run scripts from the project root to ensure proper package resolution.

Real-Time Monitoring:
```bash
python scripts/visualization/live_scope.py
python oscilloscope-rp2040/scripts/visualization/live_scope.py
```

Function Generator (Signal + Scope):
```bash
python scripts/signal/play_wave.py
python oscilloscope-rp2040/scripts/signal/play_wave.py
```

Automated Transfer Function Capture:
```bash
# Capture a 5-second log sweep for Bode plotting
python scripts/capture/measure_transfer.py sweep --duration 5 --amp 0.5
python oscilloscope-rp2040/scripts/capture/master_transfer.py sweep --duration 5 --amp 0.5
```
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@
],
"source": [
"# Cell 1: Setup\n",
"import sys\n",
"\n",
"sys.path.append(\"..\")\n",
"\n",
"from src import calibration, experiments\n",
"from sysaudio import calibration, experiments\n",
"\n",
"# Verify Sampling Rate\n",
"FS = calibration.load_calibration()\n",
Expand Down
15 changes: 4 additions & 11 deletions oscilloscope-rp2040/notebooks/02_instrument_analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"id": "4ea87ab1",
"metadata": {},
"outputs": [
Expand All @@ -41,14 +41,13 @@
}
],
"source": [
"# ruff: noqa: E402\n",
"import os\n",
"import sys\n",
"\n",
"import numpy as np\n",
"import seaborn as sns\n",
"from sysaudio import config, dsp, io, plots\n",
"\n",
"# 1. Define Paths\n",
"# Define Paths\n",
"# Current: .../oscilloscope-rp2040/notebooks\n",
"current_dir = os.getcwd()\n",
"project_root = os.path.abspath(os.path.join(current_dir, \"..\"))\n",
Expand All @@ -60,13 +59,7 @@
"# Ensure it exists\n",
"os.makedirs(FIGURES_DIR, exist_ok=True)\n",
"\n",
"# 2. Setup System Path\n",
"sys.path.append(project_root)\n",
"\n",
"# 3. Import Libraries\n",
"from src import config, dsp, io, plots\n",
"\n",
"# 4. Set Styling\n",
"# Set Styling\n",
"sns.set_style(\"darkgrid\")\n",
"print(f\"📂 Figures will be saved to: {os.path.relpath(FIGURES_DIR, repo_root)}\")"
]
Expand Down
6 changes: 1 addition & 5 deletions oscilloscope-rp2040/notebooks/03_transfer_acquisition.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@
"source": [
"# Cell 1: Imports & Setup\n",
"import os\n",
"import sys\n",
"\n",
"# Add project root to path\n",
"sys.path.append(\"..\")\n",
"\n",
"from src import calibration, experiments\n",
"from sysaudio import calibration, experiments\n",
"\n",
"# Verify Sampling Rate\n",
"FS = calibration.load_calibration()\n",
Expand Down
14 changes: 4 additions & 10 deletions oscilloscope-rp2040/notebooks/04_transfer_analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,19 @@
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"id": "62d1e8ef",
"metadata": {},
"outputs": [],
"source": [
"# ruff: noqa: E402\n",
"import os\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"# 1. Define Paths\n",
"from sysaudio import io, plots\n",
"\n",
"# Define Paths\n",
"current_dir = os.getcwd()\n",
"project_root = os.path.abspath(os.path.join(current_dir, \"..\"))\n",
"sys.path.append(project_root)\n",
"\n",
"# 2. Import Libraries\n",
"from src import io, plots\n",
"\n",
"# 3. Define File Paths\n",
"DATA = Path(\"..\") / \"data\"\n",
"\n",
"FILE_SWEEP_SRC = DATA / \"continuous\" / \"src_sweep_20260125_124443.npz\"\n",
Expand Down
6 changes: 1 addition & 5 deletions oscilloscope-rp2040/scripts/analysis/spectrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
"""

import os
import sys

# Ensure src is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import config, io, viz
from sysaudio import config, io, viz


def main() -> None:
Expand Down
8 changes: 1 addition & 7 deletions oscilloscope-rp2040/scripts/capture/master_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
It delegates the actual acquisition logic to the `experiments` module.
"""

import os
import sys

# Ensure src is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import experiments # noqa: E402
from sysaudio import experiments

# Configuration
# Options: 'sweep' (Log Sine Sweep) or 'steady' (Single Frequency Burst)
Expand Down
8 changes: 1 addition & 7 deletions oscilloscope-rp2040/scripts/capture/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
the result to the burst data directory.
"""

import os
import sys

# Add project root to path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import config, daq, dsp, io # noqa: E402
from sysaudio import config, daq, dsp, io


def main() -> None:
Expand Down
8 changes: 1 addition & 7 deletions oscilloscope-rp2040/scripts/capture/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
logging or monitoring sessions.
"""

import os
import sys

# Ensure src is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import experiments # noqa: E402
from sysaudio import experiments


def main() -> None:
Expand Down
8 changes: 1 addition & 7 deletions oscilloscope-rp2040/scripts/fun/neon_torus.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,11 @@
using time-delay embedding.
"""

import os
import sys
import time

import numpy as np
import sounddevice as sd

# Ensure src is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import audio, daq, dsp, plots # noqa: E402
from sysaudio import audio, daq, dsp, plots

# Configuration
FREQ_1: float = 55.0
Expand Down
8 changes: 1 addition & 7 deletions oscilloscope-rp2040/scripts/fun/record_drone.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@
visualizations.
"""

import os
import sys
import time
from typing import List

import numpy as np
import sounddevice as sd

# Ensure src is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import audio, config, daq, dsp, io # noqa: E402
from sysaudio import audio, config, daq, dsp, io

# Configuration
FREQ: float = 55.0
Expand Down
8 changes: 1 addition & 7 deletions oscilloscope-rp2040/scripts/fun/render_landscape.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@
`plots.plot_spectral_landscape` function.
"""

import os
import sys

# Ensure src is in path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import config, dsp, io, plots # noqa: E402
from sysaudio import config, dsp, io, plots


def main() -> None:
Expand Down
Loading