forked from probablycorey/baudy
Split 783-line src/server/index.tsx into: - src/server/audio.ts: ggwave init, playback, mic listener - src/server/game.ts: pure game logic, returns GuessResult - src/server/terminal.ts: console output, startup, handshake routing - src/pages/phone.tsx: Forge components + serialized client JS Phone page is fully standalone after load — all communication via ggwave audio (HELLO/HEY BUDDY handshake, guess responses). Added sendAndWait() for clean half-duplex request/response flow with configurable timeout. Server waits 500ms before replying to give phone time to switch to listening. Added TLS support for getUserMedia on mobile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2.6 KiB
2.6 KiB
ggwave Gotchas
WASM
- WASM heap copies:
encode()returns Int8Array backed by WASM heap memory. Must copy data out (new Uint8Array(rawBytes.length); copy.set(...)) before using, or it gets corrupted. - Same instance required: Using separate ggwave instances for encode/decode causes WASM heap pointer corruption. Use one instance for both. Max 4 instances allowed.
- Encode output format:
encode()returns Int8Array of raw F32 bytes, not Float32Array. Reinterpret withnew Float32Array(bytesCopy.buffer)for AudioBuffer. - API naming: README says
TxProtocolIdbut actual API usesProtocolId. CheckObject.keys(ggwave)when in doubt.
iOS Safari Audio
- AudioContext must be created synchronously inside a user gesture handler (click/tap). Any
awaitbeforenew AudioContext()breaks the gesture chain and Safari blocks audio permanently for that context. - Silent mode bypass:
navigator.audioSession.type = 'playback'(iOS 17+) switches WebAudio from ringer channel to media channel, bypassing the hardware mute switch. Without this, the silent switch kills all WebAudio output. - Unlock pattern: Create AudioContext → play a silent buffer → then await async init. Never reverse this order.
macOS Microphone (sox + CoreAudio)
- Explicit device names produce all-zero data:
sox -t coreaudio "MacBook Air Microphone"gets zeros due to macOS TCC permission scoping. Onlysox -d(default device) gets actual mic access granted to the terminal app. - Workaround: Change the default input device in System Settings > Sound > Input, then use
sox -d. Or installswitchaudio-osx(brew install switchaudio-osx) to change it programmatically. - MacBook Air mic disabled when lid is closed: If using a monitor, the laptop mic won't work. Use the monitor's mic (e.g., Studio Display) instead.
- Sample rate must match: ggwave needs matching sample rates for encode/decode. Browser uses 48000Hz. Server must also use 48000Hz — sox will resample from the device's native rate automatically.
Half-Duplex Audio
- Both sides (server and browser) use the same audible protocol (GGWAVE_PROTOCOL_AUDIBLE_FAST). A
playingflag stops mic processing during playback to prevent self-hearing/feedback. 300ms gap after playback before resuming listening. - The browser uses
ScriptProcessorNodeto feed mic audio frames to ggwave for decoding. Frames are accumulated tosamplesPerFramesize before decoding. - Browser mic requires
getUserMediawithechoCancellation: false,noiseSuppression: false,autoGainControl: falseto preserve signal integrity.