DisclosureOS documentation · v2 Beta
Passive sonar profile
Validate received pressure, reproduce its frequency analysis, and trace each result to its input samples.
A passive sonar recording contains received sound, with no transmitted ping. This profile connects a pressure waveform to its frequency analysis and preserves which samples were available for each calculation.
You will reproduce eight seconds of fictional sound, validate the source files, inspect a spectral bin and its input window, then diagnose three failures. A passing check establishes consistency with the declared calculation. It does not locate or identify a sound source.
Profile status
Published in npm 2.0.0-beta.4. The profile evaluators and readers are available through @disclosureos/schema/experimental/v2. The retained source revision below is only needed to regenerate this example.
Equipment and captures explains the shared equipment model. This optional profile combines an observation, acquisition context and source selection with a declared pressure-to-spectrum calculation. It does not require a scalar measurement binding or claim history.
1. Open the published-package example
Requirements: Node.js 22, npm and a ZIP extractor. Create a local project and install the published API:
mkdir disclosureos-passive-sonar-beta4
cd disclosureos-passive-sonar-beta4
npm init -y
npm install --save-exact @disclosureos/schema@2.0.0-beta.4
curl --fail --location https://os.disclosure.org/examples/meridian.zip --output meridian.zip
unzip meridian.zip
curl --fail --location https://os.disclosure.org/examples/v2/passive-sonar/check-passive-sonar.mjs --output check-passive-sonar.mjs
node check-passive-sonar.mjs meridian/sonar-passive-v1The download contains the full fictional example case. This guide uses only meridian/sonar-passive-v1. Keep the terminal in this npm project for the checks below. The displayed profile and selected-value results are explained in the following sections.
What the profile describes
The profile describes a single hydrophone channel, its pressure samples and a derived power spectral density (PSD): how the pressure signal's mean-square content is distributed by frequency. It declares the sample clock, analysis window, window spacing, units and availability rules.
Four connected documents
| Document | What it provides |
|---|---|
observation.json | Sources, raw and derived products, the analysis method and its processing activity. |
acquisition.json | Equipment, pinned configuration, pressure channel, capture interval and raw artifacts. |
source-selection.json | Array axes, units, storage layout, byte lengths and digests. |
passive-sonar-profile.json | References to those documents, product roles, exact sample timing and the spectral calculation. |
The profile selects acquisition:passive-capture and channel pressure, whose declared quantity is sound_pressure with unit Pa. Its two acquisition-product references select the raw pressure and sample mask. The four entries in products select observation products and their source layouts.
processingRef selects process:windowed-psd. That activity must consume exactly the pressure and sample mask, produce the spectral density and frame mask, and use the declared method version. Both outputs must be marked as derived from that activity. Matching filenames alone cannot establish those connections.
Four source arrays
| File | Stored values | Role |
|---|---|---|
pressure.f32 | 32,768 float32 values in Pa | Received pressure, sampled at 4,096 Hz. |
sample-mask.u8 | 32,768 uint8 values | 1 for a retained pressure sample; 0 for an unavailable one. |
power-spectral-density.f32 | 253 × 257 float32 values in Pa²/Hz | Derived spectra in row-major [window, frequency bin] order. |
frame-mask.u8 | 253 uint8 values | 1 only when every input sample in that window is retained. |
Float32 values are little-endian. Missing pressure and density slots store zero placeholders. Consult their masks before treating those slots as values. methods.json and model.mjs retain the authored calculation; index.json is a convenience file index, not another standard contract.
2. Validate files and inspect a window
To use the public API separately from fixture reproduction, download the check script. Save it as check-passive-sonar.mjs in your npm project so it uses the installed Beta 4 package.
From your npm project, run:
node check-passive-sonar.mjs \
meridian/sonar-passive-v1The script imports the evaluator and readers from @disclosureos/schema/experimental/v2. It loads the four documents and all four local arrays, then calls:
const result = await evaluatePassiveSonarProfile(
observation,
context,
selection,
profile,
{ assets },
);assets maps each layout's productRef to complete file bytes, such as product:pressure or product:powerSpectralDensity. It is not keyed by filename or sample index. The runner uses the example index to locate files and does not fetch artifact URLs.
The first report is:
{
"success": true,
"checks": {
"structure": "passed",
"sources": "passed",
"semantics": "passed",
"reproduction": "passed"
},
"scientific": "not_checked",
"physicalCalibration": "not_checked",
"localization": "not_checked",
"issues": []
}| Check | Meaning |
|---|---|
structure | All four inputs satisfy their prerequisite contracts. |
sources | Source selections and retained bytes pass their checks, including lengths and digests. |
semantics | Document bindings, method ancestry, time bounds, axes, units and availability rules satisfy the profile. |
reproduction | The decoded products pass content checks and the retained spectra reproduce from the pressure samples within the profile's tolerance. |
The evaluator reports physicalCalibration: not_checked; the synthetic fixture reports calibration not_performed. Neither result establishes measured pressure accuracy, source location or scientific validity. A content failure can mark both semantics and reproduction failed, so read the issue message as well as the stage names.
Only after success, the runner reads a pressure sample and a spectral bin from the validated products:
const products = result.data.products;
const sample = readPassiveSonarSample(profile, products, 4096);
const spectrum = readPassiveSonarBin(profile, products, 32, 28);Indices are zero-based. The complete reader result is:
{
"sample": {
"sample": 4096,
"timeOffsetSeconds": 1,
"pressureByteOffset": 16384,
"sampleMaskByteOffset": 4096,
"available": true,
"pressure": 0.00024411342747043818
},
"spectrum": {
"frame": 32,
"bin": 28,
"frequencyHz": 224,
"timeOffsetSeconds": 1,
"centerOffsetSeconds": 1.0625,
"startSample": 4096,
"endSampleExclusive": 4608,
"lastSampleOffsetSeconds": 1.124755859375,
"endOffsetSeconds": 1.125,
"pressureByteOffset": 16384,
"pressureByteLength": 2048,
"sampleMaskByteOffset": 4096,
"sampleMaskByteLength": 512,
"densityByteOffset": 33008,
"frameMaskByteOffset": 32,
"available": true,
"density": 8.551504748766092e-8,
"densityDb": 49.32042541229472
}
}The 224 Hz bin uses all 512 input samples, from 4096 through 4607, not just sample 4096. Its half-open input interval is [1, 1.125) seconds after 2026-07-28T22:15:00Z. The final sample is at 1.124755859375 seconds; the window center is 1.0625 seconds. The spectrogram places the window at its start time.
The pressure byte range is [16384, 18432) in pressure.f32. The selected density occupies four bytes at offset (32 × 257 + 28) × 4 = 33008 in its own file. The two mask offsets point into separate files too.
The dependency-free readers do not check hashes or acquisition lineage. Use evaluatePassiveSonarProfile before presenting their output as checked source data. Keep exact sample/rate offsets even when a clock label rounds to milliseconds.
From pressure to frequency
Each window contains 512 samples, spanning 125 milliseconds. The next begins 128 samples later, a 31.25 millisecond step. Windows overlap by 75%. Their 257 bins run from 0 to 2,048 Hz in 8 Hz steps.
For each complete window, the declared calculation:
- Removes the arithmetic mean from the input samples.
- Applies a periodic Hann window:
w[n] = 0.5 − 0.5 cos(2πn / 512). - Computes the discrete Fourier transform without zero padding.
- Divides squared magnitude by sample rate × sum of squared window weights.
- Doubles the interior positive-frequency bins, leaving DC and Nyquist undoubled.
The stored result is float32 Pa²/Hz. The evaluator recomputes it from the retained pressure bytes and permits an absolute difference up to max(1e-25, abs(expected) × 1e-5) per bin. That is a numerical comparison tolerance, not physical measurement uncertainty. The independent model tests also check the calculation against a direct Fourier transform.
These conventions correspond to SciPy's density-scaled periodogram with a periodic Hann window, for which sym=False. This example calculates each window separately; it does not average them into a single spectrum.
The viewer converts density for display using:
10 × log10(density / 10⁻¹² Pa²/Hz)The label is dB re 1 μPa²/Hz. It is spectral density, not broadband sound pressure level. Display limits affect only the drawing; exact linear values remain available. A retained zero density converts to negative infinity. A missing density remains null. JSON cannot encode infinity, so a client exporting logarithmic values must preserve zero-versus-missing status explicitly rather than relying on JSON.stringify alone.
Neither 8 Hz frequency spacing nor 31.25 millisecond window spacing establishes physical resolution or the ability to separate nearby sources. The final pressure sample is at 7.999755859375 seconds; nominal capture duration is 8 seconds.
Missing samples and exact windows
Missing pressure is not silence. Mask 0 marks a storage placeholder. If any input sample is missing, the entire window's spectrum is unavailable, even if the missing sample would have zero Hann weight. The profile does not interpolate gaps or substitute a shorter window.
The runner's final report demonstrates the distinction:
{
"gap": {
"firstSample": 12416,
"firstSampleAvailable": true,
"missingSample": 12800,
"missingPressure": null,
"frame": 97,
"startSample": 12416,
"endSampleExclusive": 12928,
"windowAvailable": false,
"density": null,
"densityDb": null
}
}The first sample is retained, but later samples in that same window are missing. Its pressure samples remain individually inspectable; its spectrum does not. Show a gap in the spectrogram, not a quiet band.
This fixture omits pressure indices [12800, 13824) and [25600, 25856): 1,280 samples in total. Because windows overlap, those omissions make 16 spectra unavailable. A wider gap in the spectrogram does not imply a longer physical interruption.
3. Diagnose a failed check
Each flag changes only in-memory inputs. Use one at a time. Deliberate failures exit with status 1, leave all generated files untouched and stop before selected-sample output. An unsupported flag or extra argument exits with usage status 2.
Withhold the pressure file
node check-passive-sonar.mjs \
meridian/sonar-passive-v1 \
--missing-pressureThe profile reports sources: failed; semantics and reproduction remain not_checked:
{
"code": "PASSIVE_SONAR.SOURCE_BYTES",
"pointer": "/products",
"message": "Every retained source product is required for verification."
}The additional source report leaves external: not_checked and has no source issue. Missing bytes do not contradict a digest, but the saved spectra cannot be reproduced without their pressure input. An empty issue list alone is not a completed check.
Change spectral bytes without changing their digest
node check-passive-sonar.mjs \
meridian/sonar-passive-v1 \
--changed-spectrumSource checking fails before reproduction starts:
{
"code": "PASSIVE_SONAR.SOURCE.EXTERNAL.DIGEST",
"pointer": "product:powerSpectralDensity",
"message": "Artifact digest differs."
}The additional source report has external: failed and code EXTERNAL.DIGEST. Its pointer identifies the changed product.
Supply a matching digest for an incorrect spectrum
node check-passive-sonar.mjs \
meridian/sonar-passive-v1 \
--unreproducible-spectrumThis controlled exercise sets spectral cell [32, 28] to zero and updates its digest in both the in-memory observation and source layout. It deliberately creates a file that matches its declared hash but contradicts its inputs. This is a demonstration, not a repair for unexpected bytes.
structure and sources pass. Both semantics and reproduction fail:
{
"code": "PASSIVE_SONAR.CONTENT",
"pointer": "/products",
"message": "Spectral cell 32/28 does not reproduce from its retained pressure window."
}A matching hash establishes consistency with the declared bytes. It cannot prove that a derived result was calculated correctly. Rerun without a flag to restore the passing result; no file restoration is needed.
Scope and reproduction
This fixture is an authored single-hydrophone signal in pascals, not a calibrated field recording or partner dataset. It combines a tone, harmonic, changing frequency, transient and deterministic background. Those labels describe numerical components, not detected source identities.
The profile supplies no bearing, range, beamformed array product, hydrophone response or propagation model. Unlike Active sonar, it has no transmitted ping and no automatic delay-to-distance conversion. raw identifies the retained input product, not a claim about physical calibration.
The example case includes the same sonar-passive-v1 packet in its download.
For exact definitions at this revision:
- Profile schema: product roles, clock and calculation settings.
- Profile evaluator: bindings, lineage, integrity and result stages.
- Source reader: input ranges, masks and spectral reproduction.
The shared equipment field reference covers acquisition and measurement bindings, not this newer profile. Continue to Lidar point clouds for a different source geometry.