ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
브라우저 음향 실험실, Web Audio API로 톤·스윕·시퀀스 생성·파형·스펙트럼·스펙트로그램 확인, 단일 파일 오프라인 실행
A browser acoustic laboratory: generate tones, sweeps and sequences with the Web Audio API and see them as waveform, spectrum and spectrogram. Runs offline from one file.
교육JavaScript · HTMLGitHub @korczis온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
OSCILLA — browser-based acoustic laboratory for sound generation, analysis, sequencing, synthesis and visualization. One static file, Web Audio, no backend.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
An interactive sound and frequency laboratory that runs in the browser from one static file.README 전문 보기 ↗A zero-install, scientifically honest acoustic laboratory in your browser: generate, measure and reproduce sound experiments, and know how far each result can be trusted.
What you can do with it today, from one static file and with no account:
Live demo: https://korczis.github.io/oscilla/
OSCILLA generates signals with the native Web Audio API and measures them while they play. It shows them as a waveform, a live spectrum and a spectrogram, and it compares them with what your microphone picks up. You can arrange signals into block sequences, shape them with filters, an ADSR envelope and additive harmonics, explore phase and stereo, and export the result. In the Measure workspace it records a sweep through your speaker and microphone. From that recording it computes the response of the whole playback and capture chain, rates the quality of the measurement and keeps it as a reproducible experiment. The app is a single HTML file. It needs no server, no account and no network once loaded, and it works when opened straight from disk.
The words OSCILLA uses for its objects (project, Studio, runtime, measurement, run, experiment, definition, calibration, trace) are defined in the glossary.
The header has these entries: Playground, Measure, Experiments, Analyze, Synthesis, Learn, Studio and About, in that order. Analyze and Synthesis are groups. Analyze opens Analyzer, Filter Lab and Compare; Synthesis opens Synthesis, Sequencer and Presets. A group is a button that shows its list of workspaces: Enter or Space opens it and Tab walks the list, or ArrowDown opens it on the current workspace and the arrow keys move. Escape closes it. Every V2 workspace is still there under the same name, and only the way to it has changed.
m=<workspace> into the page address
(#m=measure, #m=analyzer, …) and adds one browser history entry, also from file://.
Back and Forward move between workspaces, keep what each workspace holds and move focus to the
workspace heading; a reload reopens the workspace. A link is read by one dispatcher: the
Studio keys win over m, and m wins over a recipe link's mr, so #m=studio&mr=… opens
Studio and still loads the recipe into Measure
(ADR 0045).
Copy config URL keeps the recipe and Studio keys that are in the address. Each nav item
links to its workspace's address, so opening it in a new tab opens that workspace. A link
that is refused does not stay in the address or in history.#m=experiments&exp=<id>, no new history entry) on every path: when it
opens, when the detail closes, and on return to Experiments. A definition is def=<id> and a
finding finding=<id>; such an address stays until an experiment is opened. Following a
connected record is a new entry, so Back returns to the record you came from and moves focus
to its heading. A link names a record stored in this browser; for one that is not stored the
page says so and the address names what is shown again
(ADR 0048).V3 adds two workspaces after Playground: Measure takes a measurement, Experiments keeps and compares them. A measurement plays a known digital signal, records what comes back and analyses the recording offline. Its result is the observed response of the whole playback and capture chain, not of any one part of it. How to make a useful measurement is in the measurement guide. How the layers fit together is in the architecture. How each result is computed, and which tests pin it, is in the algorithm notes.
data: URL, so it also
works from file://. When AudioWorklet is unavailable, a ScriptProcessor fallback records
instead. The stimulus passes through the same master safety chain as every other sound, and
that chain is part of what is measured.?measure=loopback opens the page with it chosen, and the address follows the choice. The
page then says TEST CONTEXT on the result and in every saved experiment. The automated tests
use this mode, and it is never presented as a measurement of a physical system. Choosing an
input leaves it. Entering or leaving clears the result shown, valid or invalid (a saved
experiment of it stays, labelled), and the input check. A level calibration is kept on
either side: one stored in TEST CONTEXT does not apply to a real input, one stored with a
real input does not apply inside TEST CONTEXT, and each applies again on its own side once
its input is checked.The Frequency response tab shows the magnitude recovered from the captured sweep. The capture is divided by the spectrum of the rendered sweep, with band-limited regularisation. The tab offers:
Phase is shown only when requested and when the alignment is robust. Otherwise it is absent, not guessed. The magnitude is a ratio and never dB SPL.
The Impulse response tab derives the impulse response from the same sweep and the same spectral division. Time is shown in ms from the direct peak, and the absolute offset in the recording is kept. You can zoom to the direct sound, the early part or the whole response, show the analysis window, and switch to a dB view normalised to the peak. Windows and normalised views are derived objects, so the stored response never changes. The peak time includes unknown device and browser delays: it is not a time of flight and not a latency.
The analysis layer computes octave and one-third-octave band power on the standard base-10
band edges. Bands above 95 % of Nyquist are left out. An averager offers instant, fast and slow
time constants, peak hold and freeze. The RTA tab of the Measure workspace draws
one-third-octave band levels as bars over each band's edges, with peak hold, freeze and reset.
Levels are in dB relative (dBFS-like) unless a valid level calibration applies. No IEC 61260-1
filter class is claimed. An experiment saved from Measure does not store band levels yet: its
results.rta is empty, although the format, validation and CSV export already support them.
What is recorded. Every saved measurement becomes an experiment. It records the recipe
(stimulus, runs, analysis settings), the output level, the input device and the requested
and applied constraints, the calibration (profile name and identity, level calibration), the
sample rate and runs, the quality assessment and the algorithm ID of every result. It also
records the product version and build (including the build's source digest and, for the
deployed page, the artifact SHA-256), with a configuration hash and a result hash. The
result hash (version 4) covers the results, the verdict, the calibration, the input, the
output, the runs (each with a stable id, run-1, run-2, ...), the build, the recipe and
the definition the experiment was executed from. Files with a version 1, 2 or 3 hash still verify
in their own version.
Definitions. A definition says what to measure and how: the recipe as asked for, the conditions declared for every experiment and, optionally, the lowest quality verdict that meets it. Its hash covers only those fields, so a rename never changes it. Editing them creates a new version; earlier experiments keep the version they used. "Run this definition" runs its latest version, and Repeat loads a saved experiment's version again. An experiment records the version only when its measurement ran exactly that recipe. One without a definition, or whose setup was changed, records a definition derived from the recipe it played and marked as derived, never as authored. An experiment is shown under a stored definition's name only when that definition has its version with its hash (ADR 0043, proposed).
Recorded as measured. The calibration saved is the one the measurement applied, and the notes are those at its start. A profile loaded, a correction switched or a level calibration made after the measurement never becomes part of its record, and the Experiment panel says so; notes edited later are saved as an annotation. An experiment from an earlier version whose named calibration its own results contradict still opens and imports, and is marked as such: it is shown and compared as uncalibrated, never in dB SPL.
Only a log sweep is repeated. The measurement engine measures log sweeps only. A file whose stimulus is anything else (white or pink noise, a sine, a chirp) still imports and is kept as it is, with a warning that this version cannot measure it; Repeat and "Run this definition" refuse it rather than run a log sweep in its place.
Evidence. A saved experiment's detail answers two questions from what its record stores. "What produced this value?" traces the stored response value at a frequency you enter (1 kHz by default) through the analysis algorithms, the capture (input, sample rate, processing flags), the calibration as applied, the runs, the definition version, the build and, for an experiment run from Studio, the Studio graph the recipe was derived from with the measured path it names (the nodes, connections and measurement clips the measurement used). "Can I repeat this?" is a checklist: each item is recorded, partial or not recorded, with its reason, and there is no score. The result hash is recomputed and its reason says what it covers; the algorithm ids, environment notes and lineage are covered by no result hash. The raw capture is never retained: OSCILLA stores the derived result (see ). Anything the record does not store reads "not recorded" (, proposed).
Every measurement is rated GOOD, USABLE, POOR or INVALID by a versioned rule set
(oscilla.confidence.v4 for new measurements; v1-v3 are kept so stored ratings reproduce). The rules use named metrics: signal-to-noise, clipping, dropouts,
discontinuities, frequency coverage and resolution, repeatability, and calibration. Each
rating comes with its reasons, and each reason carries its value and unit. A check that was
not made reads NOT MEASURED. Severe clipping, an empty capture, or a dropout or discontinuity
inside the sweep makes a run INVALID; milder clipping lowers the rating. Statuses are shown with text, a glyph and a shape, never colour alone. The rationale
is in the proposed ADR 0025.
data: URL of the page's own analysis script so that no step blocks the page; without Worker
support the same code runs inline and yields between steps, with identical results (gap G21
in the algorithm notes, resolved).Studio is the last workspace before About. It is a patching and composition workspace over the same audio engine: no second engine, and the engine's master chain and limits apply. How to use it, with the keyboard shortcuts, is the Studio user guide. What it consists of (schemas, node and port types, tests, size, ADRs, records) and how its release and deployment are evidenced is the final report.
StudioModel:
a graph of typed nodes and ports, a timeline of tracks and clips, automation lanes, and
transport settings. Every edit is an action with undo and redo. The audio graph is compiled
from the model and patched in place with crossfades, so editing during playback does not
click. Details are in the model, the compiler,
the timeline and
patches and provenance./ finds a node by name, type or category and frames itStudio output and Playground output are exclusive: starting one stops the other, and a measurement stops the Studio.
Not yet built:
The reasoning is in the proposed ADR 0017
and the rule project.no-fake-science.
src/ (ES modules, plain CSS, the src/index.html shell)
-> scripts/build.mjs esbuild (pinned): one IIFE app bundle + one stylesheet
scripts/build-analysis-worker.mjs the analysis library (one IIFE), imported by the app
-> scripts/pack-single-file.mjs pure string assembly; drops developer comments and
markup indentation (pre, textarea, script, style verbatim)
-> dist/index.html the whole app: CSS, p5 block, analysis library, app
bundle, licence notices, one build-metadata region
npm run build:check rebuilds in memory and fails if the committed file
differs. npm run verify-dist checks the artifact statically: nothing fetched or imported
at runtime, no module scripts, licence notices present, size budget met.<script data-vendor="p5@…" data-sha256="…"> block. It is never bundled into the app code,
and verify-dist checks it against the pinned package file
(ADR 0013). Alpine.js and uPlot are
bundled; Lucide icons are inlined at build time
(ADR 0012).src/js/measurement/analysis-worker.js) is bundled once, into its own classic
<script data-analysis> before the app. The app imports it through that script's global and
carries no copy; the analysis Worker is started from a data: URL of that same script text,
so the Worker and the inline fallback run the same code and no file is loaded. The build
fails if an analysis module is bundled into the app as well
(ADR 0026, resolution note).commit: null. The Pages workflow
(.github/workflows/pages.yml) rewrites only that region with the deployed commit, the
production channel, the commit date, the SHA-256 of the committed file
(ADR 0028) and the release state:
whether the commit is its version's tag, and if it comes after the tag, how many commits
after. Pages deploys every push to , so between releases the page names itself
(N commits after ); only a release page reads
().You can double-click a single file and it works, even offline. GitHub Pages serves that same
file. The modular source exists so that the code can be tested and so that parallel work does
not collide; users never see it. See
ADR 0011 and the rule
project.single-file-deliverable.
Double-click dist/index.html. No build, server or network is needed. Some
browsers refuse microphone access from file://. In that case the microphone panel and the
Measure setup check say so, and everything else keeps working. Where IndexedDB is unavailable
from file://, Experiments keeps saved experiments in memory for that page view and says so;
export them to keep them. To use the microphone locally, serve the repository on localhost and
open dist/index.html there:
python3 -m http.server 8000 # then open http://127.0.0.1:8000/dist/index.html
Or use the public URL: https://korczis.github.io/oscilla/
Requires Node.js 22 or later. The fast check also runs majordomus doctor, so it needs the
majordomus CLI.
npm ci
npm run verify # fast: unit tests, version check, build check, artifact rules
npm run build # src/ -> dist/index.html; commit dist/ together with the source change
npm run release-gate # full gate, including the Playwright browser suites
npm run visual # compare a screenshot with the visual reference at 1536x1024
dist/index.html embeds a digest of every build input (src/, package.json, the lockfile,
the build scripts), so any change to them, including a merge from main into a long-lived
branch, makes it stale by design: run npm run build and commit the result, or build:check
and CI fail.
The browser suites need Playwright's browsers (npx playwright install chromium firefox webkit). Work lands through small pull requests. CI runs the gate on every pull request,
branch protection requires the gate check, and a merge to main redeploys Pages.
Two of the gate's jobs check the pull request rather than the product. fail-first: when the
title starts with feat or fix, a unit test the pull request adds or changes must fail on
the merge base and pass on the head, or the body carries fail-first: n/a <reason>
(node scripts/fail-first.mjs --title "<title>"). review-verdict: a change under src/js/audio,
src/js/analysis, src/js/experiments, src/js/studio, src/js/measurement,
src/js/calibration, src/js/core/storage*, src/js/core/safety*, the MEASURE, Experiments
and Findings adapters, scripts/release-*, the build, stamp and deploy-verification scripts,
the release rules' tests or .github/, or to the programs and tests of these rules, needs a
reviewer's verdict in .ai/repo/reviews/<pr>.yaml for the guarded content the pull request
changes (node scripts/review-verdict.mjs --pr <number>; --content prints the digest to
record, which a merge of main that leaves those files alone does not change). CI judges a
pull request with the base branch's copy of both programs, and ci.yml runs on pull requests
only. A pull request is bound from the first run of a head that contains these jobs. The rules are
project.fail-first, project.review-verdict and project.ci-bounded under
.ai/repo/rules/project/.
The release gate runs the built dist/index.html in Chromium, Firefox and WebKit (the engine
behind Safari) through Playwright. Each browser loads the file from file:// and from a
GitHub-Pages-like /oscilla/ sub-path, and the run must produce zero console errors.
Only these three engines are checked. No real device is: Safari on iOS, Chrome on Android and Edge have not been tried for sound starting only after a tap, resuming after the page returns from the background, or hold-to-play by touch. The owner declined real-device checks for now on 2026-10-29 (plan issue S071).
| Layer | Where | What it covers |
|---|---|---|
| Unit | tests/unit/ | Pure modules (DSP, analysis, sequencer, charts), the packer, the artifact rules and the release tooling, all under node --test |
| V1 freeze | tests/freeze/ | Golden vectors that pin V1 behaviour (ADR 0014) |
| Browser | npm run test:browser, test:engine, test:dsp, test:labs, test:sequencer | Playwright suites on the built page: the app gate (tests/browser/app.cjs), layout (tests/browser/layout.cjs), the V2 final-QA regressions (tests/browser/qa-regressions.cjs), navigation history and unsaved work (tests/browser/navigation.cjs), the MIDI support state (tests/browser/midi.cjs), the V1 engine port (tests/browser/engine-v1port.cjs), DSP (tests/browser/dsp.cjs), labs (tests/browser/labs.cjs) and the sequencer (tests/browser/sequencer.cjs) |
| Visual | scripts/visual-gate.mjs, tests/visual/ | The 1536x1024 reference and named regions, compared region by region with thresholds measured from run-to-run variance (ADR 0029) |
| V3 unit | tests/unit/v3-*.test.mjs | Measurement core, transfer and impulse response on synthetic systems with known answers, RTA and aggregation, calibration, quality, experiments (schema, round trip, corrupt imports, store), goldens per algorithm ID, the engine on a fake io and the view models, all in npm test |
| V3 browser | npm run test:measure | tests/browser/v3-measure.cjs: the measurement engine on real Web Audio, a recovered digital loopback response, limiter transparency, 0 nodes after finish and abort. tests/browser/analysis-worker.cjs: the offline analysis in its Worker against the inline run, identical results, no inline fallback, abort. tests/browser/v3-ui.cjs: Measure and Experiments of the built page, guided flow, abort at every stage, calibration, experiment import, compare and export, and no "SPL" without a level calibration. All run in Chromium, Firefox and WebKit, from and |
Every V3 browser check runs on a TEST CONTEXT digital loopback or a fake microphone. No automated test proves how a physical speaker, room or microphone behaves.
npm run release-gate is the authority on what the gate runs and in what order; the table
names every suite and visual script its test:release runs (tests/unit/docs-derived.test.mjs
holds it to package.json). tests/README.md describes each file and how to
run it alone.
version field in package.json is the only product version
(ADR 0027). The
status bar, the About workspace, the exported configuration's oscillaVersion and
window.OSCILLA.version all derive from it at build time. npm run version:check fails
when any of these disagree, or when the current version is hard-coded anywhere else. That is
why this README never names it. File-format schema versions are separate integers
(ADR 0023).type!: or a BREAKING CHANGE: footer). MINOR means a new compatible
capability (feat:). PATCH covers fixes, performance and any non-conventional commit.
Commits that are only docs, chore, ci, test, refactor, style or build need no release.
A new product generation (a new primary workspace family, such as V3's measurement
workbench) may also take a MAJOR version without an incompatible change: the owner sets the
untagged version in package.json, and release:prepare confirms it because it covers the
level the commits require.npm run release:analyze reads the conventional commits since the last v* tag and
proposes the level, with a reason for each commit.npm run release:prepare starts from a clean tree. It bumps package.json once (or
confirms an untagged version), rebuilds, and runs version:check, the release gate and
majordomus doctor. If anything fails, it restores the files. It never tags. Land the
result on main through a pull request. It refuses to start while the newest release has
no record (step 4), and it refuses the first stable release of a new major (X.0.0, and
equally X.0.1 or X.1.0 typed into package.json) that no published X.0.0-rc.N
preceded (npm run release:prepare -- --prerelease cuts the candidate;
ADR 0047).
The gate receipt it writes binds every tracked file except the release records, so any
other pull request merged before the publish means running it again
(rule ).The repository is supervised by Majordomus. .ai/ holds the
project rules (.ai/repo/rules/project/), the plan (milestones and
issues in .ai/repo/project/), the architecture decisions
(.ai/repo/adrs/), features, use cases, workflows and knowledge notes.
majordomus plan status shows milestone progress. AI workers start at
AGENTS.md (Claude Code: CLAUDE.md).
What is checked, and where:
.ai/repo/policy.yaml; majordomus doctor checks that the hook names each one): before a commit, majordomus worktree guard refuses a branch committed outside its canonical worktree
(docs/WORKTREES.md), git diff --cached --check refuses a staged
conflict marker, and majordomus doctor validates the layer; majordomus finish --check
refuses a push outside the active task's scope. npm run verify first checks that the
local majordomus is the version CI pins (scripts/majordomus-pin-check.mjs), then runs
majordomus doctor too..github/workflows/ci.yml, all required through gate):
the test suites; tests/unit/knowledge-integrity.test.mjs, which fails when a claim, rule,
bootstrap or this README names a file that does not exist, and when a claim's test or a
rule's x-majordomus test is not run by CI (other paths are checked only to exist), when a
rule's enforcement section names nothing CI runs, when a document names a project rule
that is not in force, and when a script header says a workflow runs a file it does not run;
tests/unit/repo-hygiene.test.mjs, which fails on a conflict marker in a tracked file;
tests/unit/storage-inventory.test.mjs, which holds the privacy table below to the code; tests/unit/no-fake-science.test.mjs; and majordomus doctor at a pinned version.project.rules-name-their-enforcement checks that it exists and runs); the advisory
session guidance in project.shared-machine-discipline; use-case coverage of the claims
is advisory; the worktree guard passes a detached HEAD and sees only commits. An ADR's status
proposed means recorded, not accepted (see .ai/repo/adrs/README.md).V3 Measure is specified in docs/specs/oscilla-v3-measure.md
(milestones M012-M020). Its features, use cases and claims are under
.ai/repo/features/, .ai/repo/use-cases/ and
docs/CLAIMS.yaml. Each claim names the file that implements it and the
test that proves it.
V3.1 Studio (docs/specs/oscilla-v3.1-studio.md,
milestones M021-M033) has shipped (the V3.1 release tag); its features, use cases and claims are in the same
places, and majordomus plan status shows which Studio issues remain open.
There are no analytics, no server and no account. The page fetches nothing at runtime. The microphone starts only when you press "Use microphone". Its signal goes to a local analyser and is never played back, recorded or uploaded. Exports are downloaded to your machine. A configuration URL contains only your settings, and it leaves your machine only if you share it.
In Measure, the microphone opens only for the setup check, a measurement, a level-calibration reference capture or the live RTA, and every track is stopped when they end or are aborted. The live RTA keeps only its latest analysis window, overwritten frame by frame. The setup check reads the input level from a 0.3 s window, and the reference capture (3 s) keeps only its level reading and the facts of the input; both recordings are dropped as soon as the level is read. A measurement's recording (the background-noise check and every run) is analysed in the page and lives only as long as that analysis: when the measurement completes, is found invalid, is aborted or fails, nothing in the page references it any more. It is never stored, exported or uploaded, and OSCILLA offers no option to keep it. A saved experiment keeps derived results: the frequency response (with the aggregate of repeated runs), the impulse response, the quality assessment and the facts of the run. The impulse response is computed from the recording of one run and is not anonymised: other sound the microphone picked up after the sweep had passed a frequency, in particular in the post-roll after the sweep (1.5 s by default), can be recovered from it. A saved experiment also contains the input device's label and a one-way hash of the browser's device ID, when the browser reports them, along with your notes. All of it goes into an exported file, so check it before you share it. Calibration profiles and level calibrations are kept in page memory only. The policy, its reasons and the tests that hold the code to it are ADR 0049 (proposed).
Browser storage holds only these keys and one database, oscilla-experiments (version 4):
| Storage | Key | Contents |
|---|---|---|
| localStorage | oscilla.presets | Your saved presets |
| localStorage | oscilla.v2.theme | Light or dark theme |
| localStorage | oscilla.v2.analysisTab | The last analysis tab |
| sessionStorage | oscilla.history | Recently played configurations (this tab only) |
| sessionStorage | oscilla.safetyNoticeCollapsed | Whether the safety notice was collapsed |
| IndexedDB | oscilla-experiments, object store experiments | Your saved experiments, in the exported file form |
| IndexedDB | oscilla-experiments, object store summaries | One small row per experiment for the list (name, date, schema and product version, quality status, result hash, size, its definition version, and the repeat, duplicate and Studio hashes it stores) |
| IndexedDB | oscilla-experiments, object store studio | Your saved Studio projects and patches (name, save time, studioHash and the document) |
| IndexedDB | oscilla-experiments, object store studioSummaries | One small row per Studio project or patch for the library list |
| IndexedDB | oscilla-experiments, object store definitions | Your experiment definitions, each with its versions (recipe, declared conditions, acceptance criterion, name and notes) |
| IndexedDB | oscilla-experiments, object store findings | Your findings (statement, status, notes, the evidence each cites and the id and result hash of each cited experiment) |
Experiments and findings are deleted only when you delete them. Permission to play continuously is never stored.
An HTML comment at the top of dist/index.html carries the name, version, licence and full
licence text of every bundled third-party package:
@vue/reactivity and @vue/shared) and uPlot: MIT.OSCILLA's own code is released under the MIT License (see LICENSE). The same
notice comment carries it first, so the single published file contains its own licence text.
Findings. A finding is your interpretation, linked to the evidence it rests on, and kept apart from what was measured: a measurement is what was observed or computed, an observation is what you recorded, and a finding interprets evidence. Its status is one of observation, hypothesis, supported, contradicted or inconclusive (a category, never a confidence number), and a supported or contradicted finding cites at least one piece of evidence: an experiment, a comparison of two experiments, or the stored value of an experiment at a frequency (the point the evidence lineage traces). Record one from an experiment's detail ("Record a finding about this experiment") or from a comparison; a comparison says what changed between the experiments, not why. Each experiment's detail lists the findings that cite it. A finding never changes an experiment. Deleting a cited experiment keeps the reference, which then reads "missing", and the delete dialog says how many findings cite the experiment. A finding records each cited experiment's id with its result hash. Every claim about a cited experiment is checked, at the moment it is made, against the record stored now (read fresh and verified):
A different record under the id is named as different, a hash missing on either side as an
identity that cannot be verified, and a stored record that cannot be read as unreadable. Only an
experiment with a result hash can be linked. A value reference must name exactly a frequency of
the experiment's stored response grid; only then does it read "(a stored grid point)", and every
stored point prints as its exact frequency. A status is shown as your judgement. Closing the
finding dialog without saving keeps a changed draft (offered again, guarded against a reload)
until you save or discard it. An edit is refused when another tab changed or deleted the finding
since you opened it; the dialog then offers to load the stored version (or, if it was deleted, to
save your text as a new finding) and keeps what you typed beside it to copy (the statement,
status, references and notes). Findings export as .oscilla-findings.json with
that identity; an import is validated whole before anything is stored, a newer schema is
refused, and a cited experiment that is not in this browser reads "not stored here"
(ADR 0046, proposed).
Connected records. An experiment's detail, each definition and each finding (and each saved project in Studio's Projects and patches dialog) answer two questions: what is this connected to, and what depends on it. Every entry comes from one field a record stores, and names that field: an experiment's definition version, the experiment it repeats or duplicates, the Studio project whose saved graph has the hash the experiment stores (or holds its measured path), the frequency profile it names, the build that made it; downstream, the findings citing it and the experiments repeating or duplicating it, the experiments a definition's versions were used by, the experiments that store a project's graph hash. Nothing is inferred from names, recipes or times. An entry reads "stored here" only for a record in the store whose identity verifies: it is stored, it was read just now (its result hash recomputed, at the same verification point the findings use) and it is the record the field names. Otherwise it says which: "missing" (not stored here, never hidden), "does not match" (another record under that id), "not verifiable" (the field records no identity to check, as a repeat names its original by id only) or "unreadable" (stored, but it fails validation). A build and a frequency profile are never stored, so they have their own words: "running here" or "not running here" for the build, "loaded here" or "not loaded" for the profile. The entries under a detail are those of the record the detail shows: when another record is stored under its id, the detail is brought to the stored record first. Each target is a link; an experiment shown as stored here is read again before its link is followed, also from Studio's dialog, the record opened is the one that was read, and one that changed since the list was read is not opened (ADR 0048, proposed).
A completed experiment cannot be changed. Only the name, the annotation notes and the baseline mark of a saved experiment can be edited, and no hash covers them. The store refuses any other change to a completed experiment, and rename edits only the name. A duplicate is the same experiment under a new ID, with the same hashes and creation time, and it records which experiment it was copied from (ADR 0040, proposed).
Repeats. With two or more runs, the stored response is the aggregate: a power mean with a standard-deviation envelope, or a median with a 10th-90th percentile band, plus a repeatability figure in dB. The stored transfer is the aggregate's centre, marked as derived, never a single run.
The Experiments workspace. It lists, opens, renames, duplicates, repeats (as a new
experiment from the same definition version), exports and deletes experiments. It also
shows an experiment in Measure. Its Definitions panel lists each definition with its version
count and last run, creates one from the Measure setup, renames and edits it, and runs it.
Its Findings panel lists each finding with its status and its references, and creates,
edits, deletes, exports and imports them.
Export gives an .oscilla.json file or CSV (transfer, impulse response, aggregate) with a
metadata header and explicit unit columns.
Import. An imported file is untrusted. Oversized files, wrong types, non-finite numbers, unknown algorithm IDs, a future schema version and a result-hash mismatch are all refused, each with its reason. The file format has its own schema version, independent of the product version (ADR 0023, proposed). Schema 1 files import through a migration that adds the run ids and keeps their hash. Schema 2 files import through a migration that gives the experiment the definition derived from its own recipe (marked as derived) and keeps their hash.
Compare. Comparing two or more experiments names every difference in calibration, sample rate, stimulus and algorithm. It also lists what changed between the experiments, grouped by domain: the definition version (an edit of the same definition between two experiments is said plainly), recipe (stimulus and analysis field by field, with units), algorithms (with the version step), calibration, input and output conditions, the Studio graph an experiment recorded (nodes and connections added or removed, parameters with their units, automation lanes), build provenance and the quality verdict and reasons. Execution changes come first. Layout, view and metadata changes are collapsed and never counted as execution changes. The list says what differs between the two records, not what caused a difference in the responses. A line names the evidence checklist items whose state differs between the experiments and the recorded identities (build, definition, calibration, input device) that differ. One experiment can be marked as the baseline: Compare then shows it first and compares a single selected experiment with it. The mark is metadata, kept in the file, and at most one experiment carries it (ADR 0041, proposed). A minus B is shown only for equivalent experiments, and only over their overlapping valid range. Equivalent experiments also get an impulse-response overlay, each response drawn from its own direct peak on its original scale (there is no A minus B of impulse responses).
Where they are kept. In this browser's IndexedDB only, also from file:// (tested in
Chromium, Firefox and WebKit, which keep an experiment across a reload). Where the database
cannot open, experiments are kept in memory for the page view and the page says so; a full
storage fails the save with its reason and keeps the result. The Playground never depends on
this storage.
file:// the browser's storage may be unavailable; the library then uses memory and
says so.#m=studio&st=<template id>&sv=<graph|timeline|inspector> opens
Studio, a shipped template and a view. It never starts playback, and it never replaces a
document that has unsaved changes. An invalid link is refused with the reason. Copy link
writes the current view, naming the template only while the document is that template
unmodified. An optional Fullscreen button uses the browser's Fullscreen API where it
exists; where it does not (Safari on iPhone), the button says so.mainvX.Y.Z+N.g<commit>vX.Y.ZvX.Y.Zsrc/js/measurement/, src/js/calibration/ and
src/js/experiments/. Almost every module there is pure: plain data in, plain data out, no
DOM and no Web Audio. Three modules are not: the engine, the browser capture adapter and the
experiment store. The UI renders view models and computes nothing itself.
docs/v3/architecture.md shows how stimulus, capture,
calibration, analysis, result, quality and experiment fit together.file:///oscilla/| V3 visual | scripts/visual-measure.mjs, tests/visual/measure/ | Measure at 1536x1024 and 390x844 showing a deterministic TEST CONTEXT experiment, against the accepted reference for each environment (part of npm run test:visual) |
| Studio browser | npm run test:studio | The Studio of the built page: the graph editor (tests/browser/v31-studio-graph.cjs), the timeline editor (tests/browser/v31-studio-timeline.cjs), the compiler and runtime on the real engine (tests/browser/v31-studio-audio.cjs), the transport on the audio clock (tests/browser/v31-studio-transport.cjs), offline rendering of the templates (tests/browser/v31-studio-offline.cjs), the transport Inspector, Find node, Render WAV and a measurement from Studio (tests/browser/v31-studio-workflows.cjs), deep links and fullscreen (tests/browser/v31-studio-links.cjs), and the Inspector's Runtime and Trace sections (tests/browser/v31-studio-runtime.cjs) |
| Studio visual | scripts/visual-studio.mjs, tests/visual/studio/ | Studio at 1536x1024 and its GRAPH and TIMELINE subviews at 390x844, against the accepted reference for each environment (part of npm run test:visual) |
project.release-receipt-binds-gatenpm run release:publish is a dry run by default. With -- --yes, on main, it creates
the annotated tag vX.Y.Z, pushes it, waits for the Pages deployment, verifies it, and
creates the GitHub Release with notes generated from the commits since the previous tag,
with the committed dist/index.html attached as oscilla-vX.Y.Z.html. A Pages run of
which no job has started for 15 minutes blocks it, reported with its run id, its commit
and whether that commit precedes HEAD, and so does a run list it cannot read; a run
GitHub still calls queued while its deploy job has run is slow, not stuck, and is
waited for. It never watches a run of which no job has started. It prints the majordomus finish line that verifies the published commit.npm run release:record -- --version X.Y.Z writes the release record
.ai/repo/releases/vX.Y.Z.yaml (Majordomus release/v1): the tag and its commit, the
channel, when the GitHub Release was published, its notes, and the attached file's
SHA-256 and size read off the downloaded bytes, which must equal the committed
dist/index.html at the tag (the artifact digest verify-deploy checks). A record is
evidence: -- --check refuses one that differs from what was published, naming the
field. Land it on main by a small pull request of its own
(chore/release-record-vX.Y.Z). The release is finished when the record is on main:
npm test fails for a tag in the checkout's history that is more than 6 hours old and
has no record in the checkout (the copy on origin/main counts only for a branch whose
history never contained the record, so a pull request that deletes one fails), and the
records job of .github/workflows/cadence.yml runs the same check on main
(node scripts/release-record.mjs --complete; rule project.release-flow-complete,
which states the whole flow in order).main
(rule project.deploy-often). node scripts/release-cadence.mjs prints the lag; the
Release cadence workflow (.github/workflows/cadence.yml) runs it on every push to main
and on an hourly schedule that GitHub runs best effort (3 to 6 runs a day were measured), fails
while a release is overdue and keeps one "release overdue" issue open until it ships.vX.Y.Z. V1, the original hand-written single file, is v1.0.0 (deployed) and
v1.0.1 (a maintenance tag that was never deployed).data-osc-release="X.Y", marked current) before
release:prepare. Rule project.about-names-current-release requires it, and
tests/unit/about.test.mjs refuses a bump to a line the page does not name, so verify and
the release gate fail until the station exists.+N.g<commit> for a build after
the release), whether the build is that release, the commit (linked to GitHub, or
"source build" for a local build), the channel and the source digest. Opening the page with
?debug=1 adds the full commit SHA, the source date, the artifact hash, the config schema
version and live engine state: sample rate, Nyquist, AudioContext state, voices, nodes,
microphone state and the last error.window.OSCILLA, the surface its own
browser tests and the post-deployment smoke use, so the bytes that are tested are the bytes
that are served. The stable reads are version, build, measure.state, measure.counts(),
studio.counts(), studio.model and studio.trace.steps(); everything else may change
without notice. Every member is one of three kinds: it observes; it drives an action you
already have in the page, through the same validation (measure.useLoopback() and
measure.useMicrophone() are the TEST CONTEXT choice of the Input device list,
measure.setValues() is a recipe link); or it injects test data.
measure.showResult() refuses a result that is not marked TEST CONTEXT, so no injected result
is shown or saved as a measurement, and a direct measure.setInputNow() is refused outside
TEST CONTEXT. What is injected stays in TEST CONTEXT: an input named there, or carried by a
TEST CONTEXT result, is not a checked input once the page is on the microphone, and a level
calibration made there is labelled TEST CONTEXT and does not apply outside it. It is
not a security boundary: any script running in the page can reach the same code without it.
?mock=1 only outlines the chart and view areas for layout comparison and never draws data.scripts/verify-deploy.mjs fetches the
public page, retrying a bounded number of times. It reverses the stamp and requires the
result to be byte-identical to the committed dist/index.html. It also requires the stamped
commit to be the deployed one, the version to match package.json, the release state to be
what git says for that commit, and the digest to match a fresh recomputation. Any mismatch
fails the workflow. Run the same check yourself, in a checkout of the commit Pages serves,
with npm run release:verify-deploy -- --commit origin/main. That proves the page served
now; a release is proved by its own Pages run and its release record, since a later push
replaces the page.