Add Nodus Drift, an offline ambient-sound mixer in Nodus Tools - #998
Drakonis96 wants to merge 6 commits into
Conversation
…us Tools Nodus Drift is a native tool in Nodus Tools: a page to build a mix of up to six sounds, and a Browser | Drift selector in the existing header media popover for quick play, pause and volume. - One AudioContext per window, owned by DriftProvider, so navigating, switching vault or opening the popover never stops or duplicates it. Nothing is created or played until an explicit action, and a restored mix always starts paused. - Sounds are recordings played with native looping (with a precomputed circular crossfade where a file needs one) and generators: white, pink and brown noise and five binaural presets, all computed locally. - Recordings reach the renderer through two IPC channels, drift:catalog and drift:read-audio, restricted to the main window's main frame. The main process takes an id only, confines the path under the bundled audio directory (symlinks included), checks size and SHA-256, and returns the bytes. - The catalogue is derived from the active Moodist catalogue at a pinned commit. Moodist declares Pixabay and CC0 for its audio only in general, never per file, so all 81 recordings are recorded as unresolved and pending review and none is bundled: the package ships the eight generators only. legal/drift holds the provenance record, the review protocol and the upstream MIT text. - prepare-drift-assets and verify-drift-assets generate and audit the bundled set; the audit reports the real number and size of packaged recordings. - UI text goes through the existing i18n in the eleven languages, with a neutral description of the audio and no claim about effects on the listener. Tests: unit and integration suites for the catalogue, state, DSP, engine, IPC, assets, UI, i18n and provider, and an end-to-end script that drives the real app and the packaged binary, measuring the signal that reaches the audio output.
…he README The smoke test pins the exact, alphabetical list of Toolkit cards, so the new tool has to be in it. The README gets one sentence about the tool.
… declares All 81 recordings are now approved and bundled, unmodified. Their licenses are the ones the Moodist repository declares for its audio, and the notice says so as Moodist states it: some sounds are under the Pixabay Content License and others under CC0, with links to both and to Moodist's README and LICENSE at the pinned commit. Moodist does not say which one covers which file and Nodus did not verify it, so the entries are recorded as `declared`, not `verified`. - shared/drift: a `declared` license status. The gate still needs a license, evidence, an approved review and the record that says so. The generators stay `verified`; an entry nobody approved stays `unresolved` and is held back. - The decision is written in legal/drift/REVIEW.md as the maintainer's, with what it rests on and what it does not establish. THIRD_PARTY_NOTICES.md, legal/drift/README.md and PROVENANCE.md carry Moodist's declaration verbatim. - The recordings are not committed. build/beforePack.cjs fetches them from the pinned commit (each checked against its size and SHA-256, retried on a lost connection, kept when already in place) and build/afterPack.cjs refuses an app that does not carry all of them, through verify-drift-assets --require-all. - The preparation and verification scripts flush their output before exiting: process.exit() right after printing to a pipe cut a listing short under load. - Tests cover the declared status, the recorded decision, the wording of the notices, retries, keep-if-intact, --require-all and the unavailable page, and none of them can reach the network.
A new profile now starts with Nodus Drift pinned, so its shortcut is in the sidebar and its pin is marked in the Toolkit hub from the start, the way Nodus Browser is in the sidebar. A profile that already saved its pins keeps them: this is only the value a profile without any starts from.
Discard obsolete queued reads and decode results while preserving paused mix caches and useful re-selections. Settle cancelled queue promises. Write circular crossfades directly into their destination AudioBuffer and check the actual PCM peak before allocating it. Close the parent popover anchor when its header trigger is detached. Add 13 engine/DSP regressions and 3 React header lifecycle regressions. Preserve the new-profile default pin introduced in d58ab00.
|
Patched the three review findings in 94ded55, on top of d58ab00. The new-profile default pin is unchanged.
Added 16 regression cases: 13 engine/DSP cases plus 3 rendered React header cases, including StrictMode and the Browser/Drift coexistence path. Existing tests were not weakened or replaced. Validation in this editing environment: all 13 engine/DSP regression cases pass in an isolated TypeScript-transpiled harness using the repository's Web Audio fakes; against the pre-fix sources, 10 fail and 3 pass. Syntax and whitespace checks also pass. This environment could not install the full dependency tree, so this is NOT a claim that the full npm suite, the new React/jsdom cases or packaged Electron testing ran locally. The normal CI has been triggered for this exact commit: https://github.com/Drakonis96/nodus/actions/runs/36646065049 . Its result remains the verification of record. Only three source files and the two new test files changed. No merge, release, audio/licensing changes or settings/default-pin changes. |
Summary
Nodus Drift is a new native tool in Nodus Tools: an offline mixer of ambient sounds for reading, studying and resting. It is the "built-in ambient sounds for studying" alternative listed under Media player in #779.
toolkit:drift), so its shortcut is in the sidebar and its pin is marked in the hub from the start, the way Nodus Browser is in the sidebar. It can be unpinned and pinned again.Browser | Driftselector. The Drift tab has play and pause, the master volume, clear, and a button that opens the tool. Choosing a tab never changes what is playing, and Drift never writes the system volume.Related issue
Closes #997.
Refs #779 (the roadmap thread; its "Media player" item could be updated once this merges).
Licensing of the recordings
The recordings are bundled unmodified and keep the licenses the Moodist repository declares for its audio. The third-party notice quotes that declaration as it is written (README, section "License", "Third-Party Assets"): some sounds are under the Pixabay Content License and others under CC0. It links both licenses, Moodist's README and Moodist's
LICENSEfile (MIT, its code) at the pinned commit11c0be22…, and says that the recordings are not covered by Nodus's AGPL.licenseStatus: 'declared'withLicenseRef-Moodist-declared-Pixabay-or-CC0, not asverified.legal/drift/REVIEW.mdwith what it rests on and what it does not establish, and the gate inshared/drift.tsonly checks that the record exists and is cited. An entry without one staysunresolvedand is held back.legal/drift/PROVENANCE.mdlists the pinned commit, how every technical field (bytes, SHA-256, duration, loop crossfade) was measured, and each of the 81 files.What changes
'drift'inTOOLKIT_TOOL_PAGES, aTOOLKIT_TOOLSentry (statewip), 36 new vector icons and a route inToolkitView. It is not aView, a sidebar item of its own or a vault-type entry. The exact-set tests, including the Toolkit list ine2e-smoke.mjs, are updated without loosening them.src/components/drift/audio/). OneAudioContextper window, owned byDriftProviderinmain.tsxand created and resumed inside the explicit play. Sources are single-use withAudioParamfades, stale async work is cancelled by per-voice and global tokens, loads run two at a time and are deduplicated, and decoded audio is capped at 192 MiB (inactive buffers are evicted first, an active voice never, and a voice that does not fit is refused with a notice). Recordings loop natively, with a precomputed circular crossfade for the 14 files that need one. Noise is generated once (pink by Voss-McCartney, brown by a leaky integrator with DC removal), and binaural presets are two sine oscillators on a channel merger, one preset at a time.drift:cataloganddrift:read-audio, for the main frame of the main window only. The main process takes an id and returns bytes: the path comes from the catalogue and is confined under the bundled audio directory (symlink escapes included), size and SHA-256 are checked, and a plain exactly-sizedUint8Arraycrosses the bridge. Nothing is exposed to Browser pages, iframes, auxiliary windows or the reduced bridges.localStoragekeynodus:drift:v1, a pure reducer and a debounced write. It holds configuration only, is normalized when corrupt, and is not part of backups, sync or the database.build/beforePack.cjsrunsscripts/prepare-drift-assets.mjs, which fetches the recordings from the pinned commit, checks each against its size and SHA-256, retries a lost connection and keeps a file that is already in place.build/afterPack.cjsrunsverify-drift-assets.mjs --asar … --require-all, so an app that lacks any of them does not become an installer.electron/assets/**/*is already packaged, so there is no configuration change.prepare-drift-assetsandverify-drift-assets(which report the real number and size of packaged recordings), andnpm run test:drift,test:e2e:drift,verify:drift-assetsanddrift:prepare-assets. The scripts flush their output before exiting:process.exit()right after printing to a pipe cut a listing short under load, which one test caught in the full run.Type of change
Validation
CI on this PR is the verification of record. This is what I ran locally, and when.
Before the last two commits, on the branch merged with
mainat 5.7.2:npm run lint,npm run typecheck,npm run buildandnpm run build:server-web.npm test: 4206 tests, of which 4204 pass, 1 is skipped as before (Compass needs the Electron ABI) and 1 failed. The failure wasthe list command names every pending recording precisely(78 of 81 lines: the script exited before its piped output drained), fixed in the last commit.node scripts/e2e-drift.mjs, 21 checks against the real app and against the packaged macOS arm64 binary. It measures the signal that reaches the audio output with anAnalyserNodeand no hook in the product. It also covers keyboard activation, pause and resume, navigation, a vault switch through the real switcher, independence from Browser media (it only reads the system volume), no request that leaves the machine, and a restore that stays silent.After them (the recordings and the licenses):
npm run typecheck, the Drift suites (187 tests) andnpm run licenses:verify, plus the license, packaging-config and backfill-parity tests.npm test,npm run build,npm run test:e2e(its Toolkit card list changed) and the Drift end-to-end script with the real recordings.beforePackdownload of about 99 MiB fromraw.githubusercontent.comand theafterPackcheck. The first packaging run will exercise them. A non-release run ofelectron-builder --dirwould be a cheap way to see them before the next release.Measured separately, with Electron's own decoder and not through the catalogue: Electron 43 decodes all 81 files, and their decoded durations match the measured ones to 0.5 ms. Each fits the 192 MiB decode budget on its own, but six of the longest together (about 488 MiB) do not, and the engine then refuses the extra voice with a notice.
Screenshots
Dark and light theme, the empty page, a mix with an error row, the seventh-voice notice and the header popover were reviewed in the real window. The images are not embedded in this description, because the command line cannot upload them.
Privacy and data review
localStoragekey that is not part of backups or sync.Contributor checklist
THIRD_PARTY_NOTICES.mdandlegal/drift. The Nodi documentation is not updated.CONTRIBUTING.mdandCODE_OF_CONDUCT.md.Things to know before merging
toolkitPinnedPagesnow starts as['drift']. A profile that already saved its pins, an empty list included, keeps them, so an existing user pins it once from the hub. Pinning it for existing profiles too would take a one-time migration that changes their sidebar, which this PR does not do.npm run drift:prepare-assetsfetches it, and the packaging hook does the same. Until then the recordings are listed as unavailable and the generators still work.