ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A Duolingo-style web app for learning Seattle-area bird songs and calls. Flash-card style introduction, spaced repetition reviews, and discrimination exercises — all in a mobile-first PWA. Bird recordings are selected explicitly by Xeno-canto ID in a small TOML file, then resolved, trimmed, normalized, and attributed by a reproducible content sync.
Manual audio selections (content/audio-selections.toml) → content sync (manual_audio.py) → React app (beakspeak/)README 전문 보기 ↗A Duolingo-style web app for learning Seattle-area bird songs and calls. Flash-card style introduction, spaced repetition reviews, and discrimination exercises — all in a mobile-first PWA. Bird recordings are selected explicitly by Xeno-canto ID in a small TOML file, then resolved, trimmed, normalized, and attributed by a reproducible content sync.
Live at: beakspeak.app/beakspeak
Manual audio selections (content/audio-selections.toml) → content sync (manual_audio.py) → React app (beakspeak/)
content/manifest-base.json + manual audio → beakspeak/public/content/manifest.jsonthree_choice (identify the bird) and same_different (same species?)Prerequisites: Node.js 22+, the media files already downloaded (see below)
# 1. Install dependencies
cd beakspeak
npm install
# 2. Start dev server
npm run dev
# → http://localhost:5173
The app is a single-page app with no backend — all data is served as static files from beakspeak/public/content/.
The iOS app packages the same React application as the web build. It targets portrait iPhones on iOS 18.4 or newer; Android is intentionally not configured.
See docs/native-ios.md for the complete build, synchronization, signing, physical-device launch, and web-regression workflow. Internal beta configuration, tester instructions, and distribution evidence are tracked in docs/testflight-beta.md.
The normal development loop is:
cd beakspeak
npm install
npm run native:sync
npm run native:open
native:sync validates the manual-audio lock, builds with relative asset URLs, keeps exactly the production recordings referenced by the manifest, removes legacy/archive media, and synchronizes that output into Xcode. The generated ios/App/App/public/ directory is ignored; rerun the command after changing web code or content.
content/audio-selections.toml is the production source of truth. Each species must have at least one song and one call. The section assigns the app role even when Xeno-canto uses a different type label.
[[species.stja.songs]]
xc = 603262
start_s = 2.3
end_s = 6.3
[[species.stja.calls]]
xc = "XC109654"
# With no trim, sync chooses the strongest sustained window up to 10 seconds.
Trim values are seconds in the untouched Xeno-canto source. Provide both start_s and end_s, or neither. Manual trims longer than ten seconds are preserved with a warning. The same recording cannot be assigned more than once.
The initial file contains the previous 30 curated choices: one song and one call for all 15 species.
# Requires Python 3.12+, uv, ffmpeg, ffprobe, and XC_API_KEY for new metadata.
uv run python3 manual_audio.py
# Deliberately refresh all locked Xeno-canto metadata.
uv run python3 manual_audio.py --refresh-metadata
# Recalculate only automatically chosen windows.
uv run python3 manual_audio.py --refresh-windows
# Offline verification used by the production site build.
uv run python3 manual_audio.py --check
Sync fetches metadata for new IDs, caches untouched sources under .cache/manual-audio/, and writes generated OGG files under beakspeak/public/content/audio/manual/. These media directories are gitignored. Existing outputs and automatic windows are reused until their selection, source URL, algorithm version, or encoder version changes.
content/audio-metadata.lock.json and the generated manifest are checked in. The normal site build never contacts Xeno-canto; it fails with sync guidance when selections, metadata, the manifest, or local audio assets are stale or missing.
Bird photos ship with the app and the web demo, so learning makes no third-party requests. Each species' photo in content/manifest-base.json names its Wikimedia Commons file and lists local srcset candidates such as /content/bird-photos/amro-250.jpg 250w. bundle_photos.py downloads Wikimedia's own thumbnail of that file at each width, byte for byte, and records each file's path, thumbnail URL, Commons page, pixel size, byte count and SHA-256 in content/photo-metadata.lock.json. The photos are committed under beakspeak/public/content/bird-photos/.
# After changing a species' photo in manifest-base.json (contacts Wikimedia).
uv run python3 bundle_photos.py
uv run python3 manual_audio.py # re-syncs the generated manifest and audio lock digest
# Offline verification used by the production and native builds.
uv run python3 bundle_photos.py --check
Keep creator, license, license_url and source_url accurate when changing a photo, and do not edit image content. See docs/content-licenses.md.
The previous candidate-ranking, BirdNET, and local Audio Admin workflow remains available as a legacy research tool, but it no longer supplies production audio or the runtime manifest. Its photo downloads (download_media.py) go to the gitignored .cache/legacy-photos/, outside beakspeak/public/, so builds never include them.
BeakSpeak has five practical validation layers:
tsceslintvitestGitHub Actions runs all five checks. The test:ci command runs the four cross-platform checks and excludes the macOS XCTest smoke.
cd beakspeak
npm install
npx playwright install chromium
cd beakspeak
npm run typecheck
cd beakspeak
npm run lint
cd beakspeak
npm run test:unit
npm run test:unit is the same Vitest suite as npx vitest run.
Current unit coverage includes manifest loading, lesson gating/progression, FSRS scheduling, quiz session building, audio adapter behavior, and component-level interactions.
cd beakspeak
npm run test:e2e
Run this after the flow is implemented and you want browser-level confirmation that the mobile path still works.
The Playwright suite runs at a mobile viewport and currently covers:
Reusable E2E helpers live in beakspeak/e2e/fixtures.ts so future browser tests can compose app flows cleanly.
cd beakspeak
npm run test:ios
Run this after changes to the native build, Capacitor synchronization, Xcode project, app startup, lifecycle or audio integration, storage persistence, or smoke tests. The XCTest suite checks packaged launch and rendering, one accessible navigation step, recovery after backgrounding and foregrounding, and persistence across a force-quit and relaunch. It uses the shortest learner-visible setup needed. Playwright remains responsible for the complete learner journey and shared application behavior.
See docs/native-ios.md for prerequisites, simulator selection, artifacts, and physical-device checks. The simulator smoke does not validate audio audibility, silent-switch routing, exact stop-on-background behavior, VoiceOver quality, sustained performance, signing, or physical installation.
If you want to sanity-check the app yourself, the usual sequence is:
cd beakspeak
npm run dev
Then verify the main mobile flows in the browser:
cd beakspeak
npm run test:ci
This runs:
npm run typechecknpm run lintnpm run test:unitnpm run test:e2eUse this when you want all cross-platform checks in one command. Run npm run test:ios separately for the installed-app smoke.
GitHub Actions workflow: .github/workflows/beakspeak-ci.yml
CI currently performs:
BeakSpeak is deployed from this repo to beakspeak.app. This repo serves the landing page at /, a not-found page, and the app with its Support and Privacy pages under /beakspeak/. Whistlewood at /whistlewood/ is deployed from the whistlewood repo on more specific routes.
# Build and assemble the BeakSpeak deploy artifact
bash scripts/build-site.sh
# Deploy to Cloudflare
npx --prefix beakspeak wrangler deploy
The Worker uses the beakspeak.app and www.beakspeak.app custom domains (beakspeak.app is canonical). Legacy unformedideas.com/beakspeak* URLs are 301-redirected to beakspeak.app/beakspeak/ by the unformedideas repo, which owns the unformedideas.com landing page. Do not add unformedideas.com routes or other projects' assets to this repo.
beakspeak/
public/content/
manifest.json # Species data with local audio/photo paths
audio/manual/ # Generated manual-selection OGG clips (gitignored)
bird-photos/ # Bundled Wikimedia Commons thumbnails (committed)
src/
core/ # Pure TS — no React/DOM deps (portable to iOS later)
types.ts # All shared interfaces
manifest.ts # Manifest loading + species helpers
lesson.ts # Lesson gating, card building, intro quiz generation
fsrs.ts # FSRS-6 wrapper with auditory learning params
quiz.ts # Quiz session builder, clip/distractor selection
adapters/
audio.ts # WebAudioPlayer — AudioContext, buffer cache, state
storage.ts # Dexie (IndexedDB) — progress + confusion log
store/
appStore.ts # Zustand — manifest, progress Map, tab state, actions
components/
learn/ # BirdCard, LearnSession, IntroQuiz, LearnTab
quiz/ # ThreeChoiceQuiz, SameDifferent, QuizSession, QuizTab, QuizResult
progress/ # Dashboard
credits/ # CreditsPage
shared/ # Navigation, AudioButton, AttributionInfo
At the repository root, content/manifest-base.json owns non-audio app content, content/audio-selections.toml owns human recording choices, content/audio-metadata.lock.json records fetched metadata and generated-output state, and content/photo-metadata.lock.json records the provenance and hashes of the bundled photos.