# VERSION.md — Audio Recorder Changelog --- ## v1.5.0 — MP3 Recording Support **Files changed:** `audio_recorder_server.py`, `BUILD.bat`, `AudioRecorder.spec` ### Added - **MP3 output format** using `lameenc` (pure-Python LAME wrapper, no external DLL) - Encodes at 192 kbps, quality=2 (high) - ~1.4 MB/min vs ~10 MB/min for WAV — approximately 7× smaller - Float32 PCM captured by sounddevice is converted to int16 before encoding - **Format size hints** in Settings tab — selecting a format shows approximate MB/min (WAV ~10, FLAC ~5, OGG ~1.5, MP3 ~1.4) - **MP3 availability flag** — if `lameenc` is not installed, the MP3 option is shown as disabled with a `pip install lameenc` hint rather than crashing - `lameenc` added to `BUILD.bat` install step and `AudioRecorder.spec` hiddenimports ### Format comparison | Format | Quality | ~Size/min | Notes | |--------|---------|-----------|-------| | WAV | Lossless | 10 MB | Best for editing | | FLAC | Lossless compressed | 5 MB | Good archive format | | OGG | Lossy | 1.5 MB | Open format | | MP3 | Lossy 192kbps | 1.4 MB | Most compatible | --- ## v1.4.0 — Remote Audio Visualiser + LCD Level Bar **Files changed:** `audio_recorder_server.py`, `AudioRecorderController.ino` ### Added - **Server-side RMS level meter** computed in `_audio_callback` on every audio block - Log scale conversion: −60 dB floor → 0.0, 0 dB → 1.0 - Peak hold: snaps up instantly, decays at ~0.012 per callback (~10 dB/s) - **`/api/level` endpoint** — lightweight JSON poll `{level, peak, state}` - Returns in <1ms, suitable for 80–120ms polling intervals - **Dual-mode web visualiser** - `localhost` → `getUserMedia` + Web Audio API (real frequency spectrum, 60fps) - Remote devices → polls `/api/level` every 80ms (works on all browsers/devices) - Automatic detection via `location.hostname === 'localhost'` - If `getUserMedia` is denied on localhost, falls back to server-side poll - Bar spread algorithm distributes single RMS value across 24 bars with centre-weighted falloff and per-bar smoothing (fast attack, slow decay) - **LCD signal bar on ESP32** (row 1 during recording) - 7 custom LCD characters loaded at boot: empty, 5 partial-fill levels, peak marker - 16 chars × 5 pixel columns = 80 pixel positions of resolution - Level polled from `/api/level` every 120ms with short 500ms HTTP timeout - Smoothing: attack ×0.6, decay ×0.9; peak hold with 0.03 drift-down per poll - Row 0 shows `* REC HH:MM:SS` simultaneously ### Why `getUserMedia` fails on remote devices Browsers enforce a security policy that blocks microphone access (`getUserMedia`) on non-secure origins. HTTP connections only qualify as secure if the hostname is `localhost` or `127.0.0.1`. Any other host — including a LAN IP like `192.168.1.x` — is treated as insecure, and mic access is denied regardless of user permissions. The server-side `/api/level` approach works everywhere. --- ## v1.3.0 — File Rename + Improved Filename Format **Files changed:** `audio_recorder_server.py` ### Added - **`POST /api/rename`** endpoint — renames a recording file - Validates: no path separators, no `..`, no overwriting existing files - Preserves the original file extension automatically - **Rename modal** in Files tab — ✏️ button per file opens an inline modal - Current stem pre-filled and selected for easy overtyping - Enter key submits, Escape cancels, clicking outside modal closes it - File list refreshes from server after successful rename ### Changed - **Default filename format** changed from `rec_YYYY-MM-DD_HH-MM-SS.ext` to `YYYYMMDD_DAY_HHMM.ext` (e.g. `20260304_TUE_0947.wav`) - Sorts correctly in file explorers - Human-readable day name - Shorter and cleaner --- ## v1.2.0 — Settings Page + Device Selection **Files changed:** `audio_recorder_server.py` ### Added - **Settings tab** in web UI with: - Input device dropdown (populated live from `/api/devices`) - Channels selector (Mono / Stereo) - Sample rate selector (22050 / 44100 / 48000 Hz) - File format selector (WAV / FLAC / OGG) - "Apply Settings" button — takes effect immediately without restart - Stereo Mix tip explaining how to capture system audio in Windows - **`POST /api/setdevice`** — sets device + channels + samplerate + format atomically - **`/api/devices`** — returns device list with `resolved_rate` and `resolved_ch` so the UI can pre-select the best supported settings - **`device_name`** field added to `/api/status` response - Active device name shown on Recorder tab below the control buttons ### Fixed - **PortAudio error -9996** (Invalid device) — added `resolve_device_settings()` which probes the device's actual capabilities before opening the stream - Queries `max_input_channels` and `default_samplerate` from sounddevice - Clamps channel count to device maximum - Tries sample rates in order: requested → device native → 48000 → 44100 → 22050 → 16000 - Uses `sd.check_input_settings()` to validate each candidate before committing - Actual rate and channels stored as `_actual_rate`, `_actual_ch` on the recorder instance and used for encoding (prevents corrupt WAV headers) ### Web UI - Converted to three-tab layout: **Recorder | Files | Settings** - Files tab includes download links for all recordings - 24-bar audio visualiser added to Recorder tab (Web Audio API / `getUserMedia`) --- ## v1.1.0 — Stable EXE Launcher + Internal Server Error Fix **Files changed:** `src/launcher.py`, `audio_recorder_server.py`, `AudioRecorder.spec` ### Fixed (critical) - **Infinite EXE spawn loop** crashing Windows - Root cause: `subprocess.Popen` in a frozen PyInstaller app re-executes the EXE entry point, spawning endless launcher copies - Fix 1: `multiprocessing.freeze_support()` as absolute first line of launcher - Fix 2: Server now runs as `threading.Thread` — no subprocess spawning at all - Fix 3: Flask runs with `use_reloader=False` — prevents Flask's file-watcher from spawning its own child process - **`_tkinter` ModuleNotFoundError** - tkinter does not bundle reliably with PyInstaller on Python 3.14 - Replaced entire launcher GUI with `pystray` system tray icon + `PIL` for the icon image. No window, no tkinter dependency. - **`audio_recorder_server.py` not found** (`Missing File` dialog) - `sys._MEIPASS` path was being set after the path variable was evaluated - Fix: path resolution moved to module top level before any imports - `BUNDLE_DIR = sys._MEIPASS` (bundled read-only files in temp dir) - `BASE_DIR = Path(sys.executable).parent` (writable dir next to EXE) - **Internal Server Error (500) on web UI** - Jinja2 template engine interpreted CSS/JS curly braces (e.g. `{display:flex}`) as template variables and raised `UndefinedError` - Fix: removed `render_template_string()` entirely. HTML returned as: `return html, 200, {"Content-Type": "text/html; charset=utf-8"}` - `build_ui_html()` builds HTML via Python string concatenation (no triple-quotes to avoid any risk of template engine involvement) - **`ValueError: signal only works in main thread`** - `signal.signal()` calls were at module top level in `audio_recorder_server.py` - When launcher imports the module from a background thread, these execute immediately and crash - Fix: moved both `signal.signal()` calls inside `if __name__ == "__main__":` ### Added - **Full startup logging** to `recorder_launcher.log` next to the EXE - Logs Python version, frozen state, BUNDLE_DIR, BASE_DIR, sys.path - Lists relevant files found in BUNDLE_DIR - Captures full traceback if server thread raises an exception - Accessible via tray menu → "Show Log" - **Tray icon** with right-click menu: Open Web UI, Start, Pause/Resume, Stop, Save, Show Log, Quit - **Tray tooltip** updates every 3 seconds showing state + elapsed time + LAN IP - **Error dialog** on startup failure shows full Python traceback (not just "server failed to start") using `ctypes.windll.user32.MessageBoxW` (no tkinter) --- ## v1.0.1 — PyInstaller Path Fix **Files changed:** `AudioRecorder.spec`, `BUILD.bat` ### Fixed - **`ERROR: script not found`** during PyInstaller build - `AudioRecorder.spec` used `os.path.dirname(SPECPATH)` which went one directory up from the spec file instead of using the spec file's own directory - Fix: `SRC_DIR = os.path.join(SPECPATH, 'src')` — `SPECPATH` is already the correct directory - **BUILD.bat** added `cd /d "%~dp0"` as first command to ensure it always runs from its own directory regardless of where it is launched from - **BUILD.bat** now passes full absolute path to PyInstaller: `pyinstaller "%~dp0AudioRecorder.spec"` instead of bare `AudioRecorder.spec` --- ## v1.0.0 — Initial Release **Files:** `audio_recorder_server.py`, `src/launcher.py`, `AudioRecorderController.ino`, `AudioRecorder.spec`, `BUILD.bat` ### Server (`audio_recorder_server.py`) - Flask HTTP server on port 5000 (configurable) - Real-time audio capture via `sounddevice` with `float32` PCM buffering - Encoding via `soundfile` — WAV (PCM_16), FLAC (PCM_16), OGG (Vorbis) - `RecorderState` machine: IDLE → RECORDING → PAUSED → SAVING - Thread-safe audio callback with `threading.Lock` - Elapsed time tracking with pause accumulation - Auto-timestamped filenames - File listing with size and modification date - Recordings served for download via `/recordings/` - Startup info printed to console with local IP address - `--list-devices` flag to enumerate audio inputs ### Launcher (`src/launcher.py`) - System tray icon with recording controls - Server started as background thread - `wait_for_server()` polls `/api/status` up to 12 seconds - Auto-opens browser on startup (configurable) - Graceful shutdown: saves active recording before exit - `recorder_config.json` persisted next to EXE ### Arduino (`AudioRecorderController.ino`) - ESP32-S3 with LCD Keypad Shield LCD1602 - Resistor-ladder button reading on ADC pin (SELECT/LEFT/DOWN/RIGHT/UP) - WiFi connection with NVS-persisted credentials - Config portal (AP mode) on first boot - HTTP GET commands forwarded to PC server - Status polling every 6 seconds - Elapsed timer displayed on LCD row 0 - Web UI hosted on ESP32 — forwards commands to PC server - Settings page (WiFi SSID, PC IP, PC port) with reboot-to-apply ### Build system - Single-file EXE via PyInstaller (`console=False`, `upx=False`) - `BUILD.bat` installs all dependencies and builds in one step - `debug_test.py` diagnostic tool for troubleshooting without building EXE --- ## Dependency Versions (as tested) | Package | Version | Purpose | |---------|---------|---------| | Python | 3.14.2 | Runtime | | Flask | 3.x | HTTP server | | sounddevice | 0.4.x | Audio capture | | soundfile | 0.12.x | WAV/FLAC/OGG encoding | | numpy | 1.x+ | Audio data processing | | Pillow | 10.x | Tray icon rendering | | pystray | 0.19.x | System tray | | lameenc | 1.x | MP3 encoding | | PyInstaller | 6.19.0 | EXE bundling | | ArduinoJson | 7.x | ESP32 JSON parsing |