VERSION.md 11 KB

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
    • localhostgetUserMedia + 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/<filename>
  • 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