Replace the generated cmd.exe + robocopy update helper — which silently
failed on some machines — with an in-app C# installer:
* Stage the new version to a per-user temp folder off the install and run
the new RemSound.exe from there, so nothing in the install is locked by
the updater itself.
* Wait for the old process to fully exit (real WaitForExit), then
back-up-and-swap files in C# with retry + rename-aside; roll the install
back to the previous version on any failure, so a failed update can never
leave a half-installed RemSound.
* Log every step to updater.log; clean up old stages and legacy batch
artefacts on launch. Removed the old BuildInstallScript batch generator.
Route the "RemSound is already running" dialog and its follow-up message
through ForegroundDialog so they surface in front from a background-relaunched
copy, matching the earlier post-update fix.
Docs: rewrite the readme update sections for the new mechanism, regenerate
MANUAL.md, refresh the About-box changelog and RELEASE_NOTES.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Recover an unplugged/replugged output sound card automatically (issue #5):
detect the dead WASAPI device and remember the receive-output selection so it
re-ticks and re-opens when the card returns.
- Adaptive per-card WASAPI buffer target sized to each card's pull chunk, held
stable so it never flits about under CPU/network load.
- Consolidate all per-user data (config, profiles, logs, sounds) into one
"user settings and logs" folder; migrate every older layout; exclude it from
the updater so custom cue sounds now survive updates.
- Mic-privacy detector: warn once when a Windows-blocked mic is switched on, or a
profile loads with one already on.
- All warning/notice dialogs now come to the foreground even when minimised.
- Apply volume + mute on profile load (were saved but not restored).
- Crash-safe (atomic) profile/config saves.
- Fix two resource leaks (push-mode capture MMDevice; UPnP DeviceFound handler).
- Remove dead code (baseline-diff machinery, dead ASIO probes, no-op stubs).
- Docs: readme.html, MANUAL.md, About-box changelog and release notes for v3.5.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tech Singer's Windows 7 log from 2026-05-28 had this pattern:
updater: GET https://api.github.com/repos/...
updater: check failed: HttpRequestException: The SSL connection could not be established
...
updater: startup check — up to date (v3.0.1)
i.e. the SSL handshake to GitHub failed (Win7's TLS stack missing
KB3140245 / KB4474419) but the updater logged 'up to date' and the
user-facing message in CheckForUpdatesManually said the same. So a
user with a broken update check has no way to distinguish that from
genuinely having the latest version.
Fix:
* CheckForUpdateAsync now returns a discriminated UpdateCheckResult
(UpdateAvailable / UpToDate / UpdateCheckFailed) instead of the
old UpdateInfo?. Each return-null site is replaced with the
appropriate concrete type.
* The catch-all 'try { ... } catch (Exception ex) { return null; }'
becomes 'return new UpdateCheckFailed(ClassifyFailure(ex), ...)'.
ClassifyFailure walks the exception chain and maps to a coarse
FailureKind enum: SecureConnection (TLS/auth), Timeout, HttpError,
NetworkUnreachable. SecureConnection is broken out separately so
the manual-check UI can point Win7 users at the specific Microsoft
KBs that fix the issue.
* MainForm.CheckForUpdatesManually pattern-matches on the result:
UpToDate -> existing 'you're running the latest' message;
UpdateAvailable -> existing install confirmation;
UpdateCheckFailed -> NEW dialog (ShowUpdateCheckFailedDialog)
whose wording is tailored to the FailureKind. The
SecureConnection branch explicitly names KB3140245 and KB4474419
and offers the manual zip-install URL as a fallback. All branches
keep the technical detail out of the dialog and route it to the
log instead.
* Background and startup polls stay silent on UpToDate and
UpdateCheckFailed (no point nagging the user about something
they can't act on from a timer tick), but the startup-poll log
now records the failure kind and detail instead of mislabelling
the outcome as 'up to date'.
No version bump - this rides along with the next feature release
(planned v3.2 with the Reaper ReaStream integration). The bug is
silent on the affected users today, and shipping a v3.1.2 just for
the error-message improvement would mean another update cycle for
everyone for marginal benefit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* New "Opus, live latency" codec mode: 2.5 ms frames (120 samples/ch at 48 kHz)
via the float-input encode path. End-to-end codec delay drops to ~5 ms (vs
~12.5 ms at standard 10 ms Opus). Test on LAN: 400 pps/lane, zero missed /
reordered / duplicate packets, ~15 ms one-way saved end-to-end.
* Wire-format change: AudioFormatInfo.FrameDurationMilliseconds renamed to
FrameSamplesPerChannel (int sample-count at announced sample rate). Removes
the lossy 48000*ms/1000 conversion that couldn't represent 2.5 ms. v3 <-> v3
exact; v3 <-> v2 still passes audio (Opus decoder is self-describing from
packet TOC) but v2 side over-sizes its buffer wildly. v2.x profiles auto-
migrate via <120 sentinel rule in RemSoundSettingsStore (anything below 120
is treated as legacy ms and multiplied by 48). Profile JSON key kept as
OpusFrameMilliseconds via [JsonPropertyName] so old profile files still load.
* Codec dropdown rebuilt with use-case names: "PCM 48K 24 bit - uncompressed",
"Opus, broadcast quality - loss tolerant", "Opus, live latency - for jamming
and monitoring". Middle 10 ms option retired; saved 480-sample profiles
collapse to broadcast quality (safer-side default).
* Profile auto-resume after self-update: RemSoundUpdater writes a one-shot
_resume-after-update.txt sentinel containing the active profile title before
exit; Program.Main reads + deletes it on next start and silently loads that
profile, skipping the picker. Helper batch's robocopy /XF excludes the
sentinel and the failure-branch cleans it up if the install aborts. Falls
through to normal startup behaviour (StartWithProfileTitle or picker) if the
sentinel is missing, empty, or names a profile that no longer exists.
* Read-only profile saves now go through on explicit Ctrl+S / File -> Save
with a one-time TaskDialog warning ("Save anyway" / "Cancel" + Do-not-show-
again). Lock continues to suppress the automatic unsaved-changes prompt on
close / profile switch (its main job). AppConfig.SaveOnReadOnlyMessageSuppressed
renamed to SaveOnReadOnlyWarningSuppressed; v2.x suppression flag is silently
discarded since the behaviour changed and the user needs to see the warning
once on each machine.
* Manual (readme.html) updated: codec table rewritten with the three new
choices and corrected bandwidth figures, send-rate description updated, new
sections "The same profile picks up automatically after an update" and
"Saving on purpose while a profile is locked".
* Subsumes the never-separately-released v2.2 work: native Opus encoder
(~97% less per-second memory churn on Opus send path via Concentus.Native),
efficiency tidy-ups (item 4 ASIO probe rate, item 6 WaitHandle, item 7
snapshot cache, items 14/16 heartbeat + discovery), legacy cleanup
(items 30/34/35/36: KeepAlive infrastructure, drift drop/repeat/accumulator
fields, fan-out cache stat). New diagnostic columns cpu/memMB/wsMB/
allocKBps/captureMs/sendMs/recvMs/renderMs gated on Enable-logs.
About dialog updated with v3.0 block at top; v2.2 block retained for the
subsumed work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The install helper's robocopy line was:
robocopy "{stagingRoot}" "{installDir}" ...
installDir is AppContext.BaseDirectory, which always ends in a directory
separator, so the destination argument was a quoted path ending in a
backslash: "D:\...\publish\". Windows command-line parsing reads the \"
as an escaped quote, so robocopy never received a valid destination,
rejected the command line, and exited 16 (usage error, nothing copied)
instantly. The auto-updater has never worked in any release because of
this — v1.0-v1.2 failed silently, v1.3+ detected the failure and wrote
update-failed.txt but never fixed the robocopy line.
Fix: BuildInstallScript now strips trailing separators —
stagingArg = stagingRoot.TrimEnd('\','/'), installArg likewise — and
the robocopy line uses the trimmed forms. Verified by running the
corrected robocopy against real staged files: exit 3 (success), files
copied.
The broken helper is baked into every shipped build including v1.8, and
the helper is generated by the running version — so v1.8 and earlier
cannot auto-install v1.9. v1.9 must be installed by hand once; from v1.9
onward the updater works.
No wire-format or audio-pipeline changes — v1.5 through v1.9 interoperate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Updater (RemSoundUpdater.cs), all from Andre's feedback:
* CheckForUpdateAsync now requests /releases?per_page=100 instead of the
default 30-item page, so a burst of server-vX.Y relay releases can't
push the newest client release off page 1.
* The install helper's robocopy now excludes remsound.config.json and the
logs / profiles / recordings folders — an update replaces app files
only and can never overwrite the user's own config or data.
* On a successful update the helper now also deletes _update-helper.log
and any stale update-failed.txt (the _update folder was already
removed), leaving a tidy install folder. The failure branch still keeps
them all for diagnosis.
* update-failed.txt rewritten as plain user-facing instructions: numbered
steps, no brand names, names the real _update folder, no robocopy
jargon. The exit code now goes to _update-helper.log only.
Manual (readme.html): rewritten in plain language — developer jargon
removed or explained in everyday terms — and a second pass removed the
keystroke-navigation choreography and screen-reader narration from the
prose. The Keyboard shortcuts section and all shortcut reference are
kept intact.
No wire-format or audio-pipeline changes — v1.5 through v1.8 interoperate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The client and the relay server are published from the same GitHub repo;
the server's releases use "server-" prefixed tags. RemSoundUpdater hit
/releases/latest, which is repo-wide — when a server release was newest,
the updater fed "server-v2.3" to ParseTag (-> a bogus 0.0.3) and concluded
"up to date", silently skipping real client updates.
CheckForUpdateAsync now lists /releases and picks the highest-versioned
release whose tag is a RemSound client tag (new IsClientReleaseTag: after
an optional leading "v", first char must be a digit). Drafts and
pre-releases are skipped. The server-side updater already filters to
"server-" tags, so client + server coexist in one repo cleanly.
Also rewrites build-release.ps1 with a data-safety check: it publishes to
a fresh staging folder and aborts the release if any logs/, profiles/,
recordings/ folder, .log file or remsound.config.json is present in the
staged output or the finished zip — preventing a repeat of the v1.5/v1.6
zips that shipped with developer logs and profiles.
No wire-format or audio-pipeline changes — v1.5/v1.6/v1.7 interoperate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v1.2's self-updater silently failed when the install folder lived
inside a Dropbox sync path. Dropbox held write locks on the existing
RemSound.exe / DLLs during the brief window between the parent exiting
and the helper script copying the new files in. The helper's robocopy
(/R:5 /W:1) gave up after 5 seconds, and the helper then
unconditionally relaunched the OLD binary — so the user saw the same
version they started with after pressing "Yes" on the install prompt,
with no visible error.
Helper script (BuildInstallScript) changes:
* Robocopy retries bumped to /R:60 /W:1 — up to 60 seconds per file.
Dropbox lock release happens reliably within that window in
practice.
* Robocopy exit code is captured and checked. Codes >= 8 are real
failures. On a failure the helper writes update-failed.txt to the
install folder with the cause + recovery steps, leaves the staging
folder intact, and does NOT relaunch the old binary. Earlier
versions silently relaunched the unmodified old binary, hiding the
failure.
* Helper appends a step-by-step trace to _update-helper.log (in the
install folder), with robocopy's own output included via /LOG+:.
* update-failed.txt, _update-helper.log, and _apply-update.cmd are
added to the /XF exclusion list so the helper's own state files
don't get copied to themselves on a repeat update run.
DownloadAndStageInstallAsync also clears any stale update-failed.txt
at the start of every new attempt, so a successful run leaves the
install folder clean.
readme.html "If install fails" section expanded with the new
update-failed.txt marker file behaviour and the _update-helper.log
location.
Wire format, audio pipeline, and recording feature unchanged from
v1.2 — this is updater-machinery-only.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>