10 KiB
HANDOFF — session state
Current operational state, read right after
TASKS.md. Trust this file as the truth of what is in flight — do not re-derive from git/fs unless it points at a problem. Conventions:schema.md. Rewrite this file at session end, compaction, or any interruption.
Session state (last updated: 2026-08-14)
- Branch:
main(audio milestone work is uncommitted, see "Uncommitted"). - AUDIO MILESTONE (TASK 9) — SHIPPED this session. Real stream audio + voice filters + auto-duck
- free TRAX background music. Build 0 warnings, full suite 196 passing (ONE integration
test:
AudioPipelineTests.Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe). Everything below in the milestone section is DONE. NOT yet committed — next step is review + commit (withai.md/TASKS.md/this file already updated in the same changeset).
- free TRAX background music. Build 0 warnings, full suite 196 passing (ONE integration
test:
- Two build-time deviations from the settled plan (both recorded in ai.md + TASKS.md):
MusicPlayerusesWaveOutEvent(new sibling packageNAudio.WinMM2.2.1) — notWasapiOutas planned;WaveOutEventisn't inNAudio.Wasapi/NAudio.Core.- No forced 48 kHz capture — NAudio 2.2.1's
WasapiCapturehas no overridableGetDefaultMixFormat, so both sources run the device mix format and the mixer'sTinyResamplernormalizes any rate to 48 kHz (resampler IS the design, not a fallback). FfmpegEncoder.csis untouched — the VM closes the audio pipe (_audioMixer.StopLive()) inStopStreamBEFORE the frame pump stops (audio EOF then video EOF, in order).
- SERVER RECOVERY (llamachile.tube / YunoHost / DO droplet) — done earlier session. Side task complete; rotate the DO API token (control-panel only) and any password no longer trusted.
- Shipped, all pushed before this session: TASK 21 (creator-hub About,
3d92bd0), TASK 8 (meter +10 dB), TASK 4 ship step 7 (one-click go-live + private-only). 174 tests passing, 0 warnings. - Uncommitted (whole TASK 9 milestone):
Services/Audio/(newVoiceFilterChain.cs,AudioRingBuffer.cs,AutoDucker.cs,TinyResampler.cs,MusicPlayer.cs,NamedPipeAudioWriter.cs, rewrittenAudioMixer.cs, simplifiedWasapiMicAudioSource.cs),Models/Music.cs(new),FfmpegArgs.cs,EncoderOptions.cs,LayoutStore.cs(v9),MainViewModel.cs,MainWindow.xaml,MainWindow.xaml.cs,ytLive.csproj(NAudio.WinMM),ytLive.Tests/(AudioPipelineTests.csnew,FfmpegEncoderTests.cs+LayoutStorePersistenceTests.csupdated), and docsai.md,TASKS.md,HANDOFF.md. - Next step: commit the milestone (docs already staged in the same changeset), push, then start
TASK 5 (reusable stream →
_rtmpUrlProvider— the last blocker before go-live actually encodes). Then itch.io product page setup (premium URL lands inMainViewModel.PremiumUrl, lights up Unlock Premium). Optional, not queued: rewriting the healed entry'sProfileUrltohttps://mastodon.llamachile.tube/@gramps(user must say the word).
TASK 9 audio milestone — SHIPPED 2026-08-14 (what changed)
The creator's feature review settled this as the single next branch ("all the audio issues done and
tested — a huge milestone"). Final spec and full shipped-state records live in TASKS.md (TASK 9)
and ai.md ("Live audio capture"). Highlights:
- Real audio into the encoder.
FfmpegArgsnow builds-f f32le -ar 48000 -ac 2 -i \\.\pipe\ytllive_audio- explicit
-map 0:v -map 1:a(replacesanullsrcsilence).MainViewModel.BeginGoLive→_audioMixer.StartLive(EncoderOptions.DefaultAudioPipeName);StopStream→_audioMixer.StopLive()before_framePump.StopAsync().
- explicit
- 2-input mix (mic + loopback), honest gains:
micGain = MicVolume(mute = 0),loopbackGain = GameMuted ? 0 : GameAudioVolume× duck. No third music channel. - Voice chain on the mic (TASK 22), before meter AND mix: bass 120 Hz +4 dB → treble 8 kHz +3 dB → gate (0.005 / hysteresis 0.5) → compressor (0.5, 4:1). Pure TDF2 DSP, per-sample, always on.
- Auto-duck: mic RMS > 0.02 → loopback ×0.25 (−12 dB), attack 0.05 / release 0.005.
- TRAX (free BGM):
MusicPlayer= MediaFoundationReader →VolumeWaveProvider16at fixed 0.20 →WaveOutEvent. Plays to the default device → rides the loopback into the stream (ducked with game). Footer TRAX button (dot red/yellow/green + "TRAX"), left-click toggles/picks, right-click opens the picker (OpenFileDialog), tooltip shows the track name. Track persists via schema v9 single-rowMusic. Sound-bar label "Game Audio Capture" → "Desktop Audio";IsGameAudioBarVisible = gameDetectorProducingSound || IsMusicPlaying. - Tests: new
AudioPipelineTests.cs(DSP/ring-buffer/ducker/resampler units + the ONE integration test reading real pipe bytes viaNamedPipeClientStream);FfmpegEncoderTests+ layout persistence updated for pipe args / Music roundtrip. Ring-buffer overwrite bug found by the unit test and fixed (head must NOT advance on eviction — the write itself advances it). Integration test pre-fills the ring buffers beforeStartLiveso the first pipe tick already carries audio (deterministic — a start-of-stream silence race was seen and eliminated).
Out of scope this branch: IP webcam, chat box, alt-key crop, credits, bg removal, music-off-VOD
track (YouTube mutes VODs with copyrighted music — future feature), itch.io PremiumUrl.
-
Landmines:
- Never add another test that constructs
new App()— useRealAppHost.Run(...)(shared STA host for the one WPF App per AppDomain; round-clip + source-naming tests). - Never set a local
Canvas.SetTopon the social bar — a local value permanently overrides{Binding SocialBarTop}(theClearValuelesson from 5.5). AudioMixermeterPushis unconditional by design now:OnMicSample/OnLoopbackSamplecompute the level first, then raise the event — a?.Invoke(meter.Push(...))short-circuit skipped the meter update when nothing was subscribed (found byRestartMic_ResetsLevel, fixed).MicConnectedcomes from the sourceStartedevent, raised right afterStartRecording()succeeds — tests mustMarkStarted()the fake source before asserting connection state.- The mic dot is red until a resource connects (see contract below) — green
only after
Started, yellow onFailed. - Zero mic devices at startup = red dot AND the mixer is never started, so loopback + the game bar can't run either (no capture at all) — acceptable.
- The game detector is polled on the UI thread via a 250ms
DispatcherTimer;OnGameAudioPollTickwrapsPoll()in try/catch +AppLog. - The pump reads the active scene on a background thread while the UI can still
edit it — a concurrent-mutation exception is contained (logged +
Failed+ the pump stops), not a crash. StopAsyncmust stop the encoder (closes stdin) before awaiting the pump loop — closing stdin unblocks a write stuck on pipe backpressure; the reverse order deadlocks. Same rule for audio:StopLive()(pipe EOF) before the pump stop, so ffmpeg's two inputs end in order.- Tests never instantiate
MainViewModeldirectly except via a realMainWindowon theRealAppHostSTA thread (round-clip + naming), which never go live. - Sandbox can't reach outbound HTTPS —
HttpSocialValidatorstub-handler tests only, never the real instance.
- Never add another test that constructs
-
Mic status contract (creator's rule, verified — do NOT "fix"): the status dot is red until a mic resource is actually connected.
MicStatusstartsNotConnected(red); it goes green ONLY when the mixer'sMicConnectedfires, which comes strictly from the mic source'sStartedevent raised afterStartRecording()succeeds. Zero devices at startup → stays red and the mixer is never started; capture failure → yellow. -
Secret/DB/port facts live: OAuth client id/secret in
Helpers/OAuthCredentials.cs; OAuth session token inHelpers/TokenStore.cs(DPAPI →%APPDATA%\ytLlive\ytLlive.auth); layout DB%APPDATA%\ytLlive\ytLlive.db(schema v9 — single-rowMusic; theSocialEntry.Softwarecolumn is a column-presence migration like the others); OAuth callbackhttp://localhost:8765/oauth2/callback; crash log%APPDATA%\ytLlive\startup.log.
Server recovery (llamachile.tube / YunoHost / DO) — 2026-08-14
Facts (hunted this session — do not re-hunt):
- Droplet llamachile.tube: DO droplet ID
473190301, IP143.244.176.131, sfo3, 4GB/2vCPU/50GB. SSH port = 2214 (matches git remotessh://llgit.llamachile.tube:2214/gramps/ytLlive.git); 22/2222/2200/etc all closed. SSH asgrampsworks with~/.ssh/id_ed25519(key auth, no password). - Root is YunoHost-locked:
PermitRootLogin noin/etc/ssh/sshd_config(there's a conflicting cloud-init override section below it, but DO's "Reset root password" email does NOT work on this box — cloud-initset-passwordsonly ran once at install). Root is reachable viasudo -ior the DO web Recovery Console (Settings → Recovery console — VNC-based; the droplet page's Console button is SSH-based and fails with "all configured authentication methods failed" when no account has a working password). - gramps is a YunoHost LDAP account (local
/etc/passwdentry has*, auth via pam_ldap) — its password is changed withsudo yunohost user update gramps -p '<pw>', NOTpasswd. - fail2ban bans the whole IP for 10-12 min after repeated failed SSH/root logins (all ports
refuse;
pingstill works; droplet API still showsactive). Wait it out — do NOT power-cycle. - Password reset this session:
sudo yunohost tools rootpw -n '<pw>'sets root (rootpw takes-n). Verification:getent shadow rootshows a$y$yescrypt hash +passwd -S root=P. - Mastodon services run as systemd units
mastodon-web/sidekiq/streaming(all wereactiveafter the reboot); gitea/nginx/mariadb/postgres/redis/yunohost-api also systemd units. Disk was 93% full (3.6G free) — keep an eye on it before any big upgrade. - The original outage was an interrupted Mastodon upgrade + a DO
password_resetreboot; everything came back on its own after boot. No code/schema damage observed.