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
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
name: Test on ${{ matrix.os }} / Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
python-version: ["3.13"]

steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6

- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libportaudio2

- name: Install uv
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras --dev

- name: Lint with Ruff
run: |
uv run ruff check .
uv run ruff format --check .

- name: Run Tests
run: uv run pytest
7 changes: 4 additions & 3 deletions oscilloscope-rp2040/firmware/main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import machine
import array
import micropython
import sys
import gc
import sys

import machine
import micropython
import uselect

# -------------------------------------------------------------------------
Expand Down
14 changes: 7 additions & 7 deletions oscilloscope-rp2040/notebooks/02_instrument_analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@
],
"source": [
"# ruff: noqa: E402\n",
"import sys\n",
"import os\n",
"import sys\n",
"\n",
"import numpy as np\n",
"import seaborn as sns\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# 1. Define Paths\n",
"# Current: .../oscilloscope-rp2040/notebooks\n",
Expand All @@ -64,7 +64,7 @@
"sys.path.append(project_root)\n",
"\n",
"# 3. Import Libraries\n",
"from src import config, io, dsp, plots\n",
"from src import config, dsp, io, plots\n",
"\n",
"# 4. Set Styling\n",
"sns.set_style(\"darkgrid\")\n",
Expand Down Expand Up @@ -189,7 +189,7 @@
"clean_ac = dsp.remove_dc(volts_clean)\n",
"llama_ac = dsp.remove_dc(volts_llama)\n",
"\n",
"print(f\"--- LOAD COMPLETE ---\")\n",
"print(\"--- LOAD COMPLETE ---\")\n",
"print(f\"Clean Signal Amplitude: {(np.max(clean_ac) - np.min(clean_ac)):.3f} Vpp\")\n",
"print(f\"Llama Signal Amplitude: {(np.max(llama_ac) - np.min(llama_ac)):.3f} Vpp\")"
]
Expand Down Expand Up @@ -308,7 +308,7 @@
"noise_mean = np.mean(volts_noise)\n",
"noise_std = np.std(volts_noise)\n",
"\n",
"print(f\"--- DETECTOR CHARACTERIZATION ---\")\n",
"print(\"--- DETECTOR CHARACTERIZATION ---\")\n",
"print(f\"Bias Level (DC): {noise_mean:.6f} V\")\n",
"print(f\"Read Noise (RMS): {noise_std * 1000:.3f} mV\")\n",
"\n",
Expand Down Expand Up @@ -459,7 +459,7 @@
"thd_clean = dsp.calculate_selective_thd(clean_ac, fs, 82.4)\n",
"thd_llama = dsp.calculate_selective_thd(llama_ac, fs, 82.4)\n",
"\n",
"print(f\"--- SELECTIVE THD ---\")\n",
"print(\"--- SELECTIVE THD ---\")\n",
"print(f\"Clean DI Signal: {thd_clean:.2f}%\")\n",
"print(f\"Red Llama Signal: {thd_llama:.2f}%\")"
]
Expand Down Expand Up @@ -529,7 +529,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "hardware-builds (3.13.11)",
"display_name": "systems-audio-lab (3.13.11)",
"language": "python",
"name": "python3"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
],
"source": [
"# Cell 1: Imports & Setup\n",
"import sys\n",
"import os\n",
"import sys\n",
"\n",
"# Add project root to path\n",
"sys.path.append(\"..\")\n",
Expand Down
6 changes: 3 additions & 3 deletions oscilloscope-rp2040/notebooks/04_transfer_analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"outputs": [],
"source": [
"# ruff: noqa: E402\n",
"import sys\n",
"import os\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"# 1. Define Paths\n",
Expand All @@ -18,7 +18,7 @@
"sys.path.append(project_root)\n",
"\n",
"# 2. Import Libraries\n",
"from src import plots, io\n",
"from src import io, plots\n",
"\n",
"# 3. Define File Paths\n",
"DATA = Path(\"..\") / \"data\"\n",
Expand Down Expand Up @@ -124,7 +124,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "hardware-builds (3.13.11)",
"display_name": "systems-audio-lab (3.13.11)",
"language": "python",
"name": "python3"
},
Expand Down
40 changes: 6 additions & 34 deletions oscilloscope-rp2040/scripts/analysis/spectrum.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,18 @@
import sys
import os
import matplotlib.pyplot as plt
import numpy as np
import sys

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import io, dsp, config


def analyze_file(filepath):
print(f"Loading: {filepath}")

signal, fs = io.load_signal(filepath)

# Prep analysis
ac_signal = dsp.remove_dc(signal)
freqs, mags = dsp.compute_spectrum(ac_signal, fs)
fundamental = dsp.estimate_fundamental(freqs, mags)

# Plotting
t_axis = (np.arange(signal.size) / fs) * 1000

plt.figure(figsize=(12, 8))

plt.subplot(2, 1, 1)
plt.plot(t_axis, signal, color="lime")
plt.title(f"File: {os.path.basename(filepath)} | Pitch: {fundamental:.1f} Hz")
plt.grid(True, alpha=0.3)

plt.subplot(2, 1, 2)
plt.plot(freqs, mags, color="orange")
plt.xlim(0, 2000)
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
from src import config, io, viz


def main():
filepath = io.select_file_cli(config.DATA_DIR_BURST)
if filepath:
analyze_file(filepath)
print(f"Loading: {filepath}")
signal, fs = io.load_signal(filepath)

viz.analyze_signal_plot(signal, fs, title=f"File: {os.path.basename(filepath)}")


if __name__ == "__main__":
Expand Down
7 changes: 4 additions & 3 deletions oscilloscope-rp2040/scripts/capture/master_transfer.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import sys
import os
import sys
import time

import numpy as np
import sounddevice as sd

sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))

from src import audio, daq, io, config, dsp
from src import audio, config, daq, dsp, io

# --- USER CONFIGURATION ---
# Uncomment ONE mode below to select it:
Expand Down Expand Up @@ -67,7 +68,7 @@ def run_sweep_capture():
def run_steady_capture():
print(f"🔹 Starting Oscillator ({STEADY_SHAPE} @ {STEADY_FREQ}Hz)...")

with audio.ContinuousOscillator(STEADY_SHAPE, STEADY_FREQ, STEADY_AMP) as osc:
with audio.ContinuousOscillator(STEADY_SHAPE, STEADY_FREQ, STEADY_AMP) as _:
# Allow signal to settle
time.sleep(0.5)

Expand Down
4 changes: 2 additions & 2 deletions oscilloscope-rp2040/scripts/capture/record.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sys
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 daq, dsp, io, config
from src import config, daq, dsp, io


def main():
Expand Down
5 changes: 3 additions & 2 deletions oscilloscope-rp2040/scripts/capture/stream.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import sys
import os
import sys
import time

import numpy as np

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

from src import daq, io, config
from src import config, daq, io


def record_stream():
Expand Down
3 changes: 2 additions & 1 deletion oscilloscope-rp2040/scripts/signal/play_sweep.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sys
import os
import sys

import sounddevice as sd

# Add project root to path
Expand Down
8 changes: 3 additions & 5 deletions oscilloscope-rp2040/scripts/signal/play_wave.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import sys
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 audio, daq, viz

# --- CONFIGURATION (EDIT ME) ---
# Configuration
SHAPE = "sine" # "sine", "triangle", "square", "saw"
FREQ_HZ = 440.0 # Frequency
AMPLITUDE = 0.5 # 0.0 to 1.0 (Watch your volume!)
# -------------------------------


def main():
Expand All @@ -19,12 +18,11 @@ def main():
# 1. Init Audio (auto_start=False means it waits)
with audio.ContinuousOscillator(SHAPE, FREQ_HZ, AMPLITUDE, auto_start=False) as osc:
# 2. Start Scope
# We pass osc.play as the callback. Viz will call this ONLY when the window is ready.
with daq.DAQInterface() as device:
viz.run_live_scope(
device.stream_generator(),
title=f"Generator: {SHAPE.title()} @ {FREQ_HZ}Hz",
on_launch=osc.play, # <--- The magic happens here
on_launch=osc.play,
)


Expand Down
Loading