High-fidelity underwater acoustic simulation and sonar analysis toolkit for Node.js. Model sound propagation, predict sensor performance, and generate realistic sonar returns for physics-based maritime applications.
- Installation
- Quick Start
- Configuration
- API Reference
- Streaming API
- Error Handling
- TypeScript
- License
npm install @superinstance/sonar-vision-toolRequirements: Node.js ≥ 18.0
import { SonarVision } from '@superinstance/sonar-vision-tool';
const sonar = new SonarVision({
salinity: 35, // PSU
temperature: 10, // °C
depth: 100, // meters
frequency: 50000, // Hz
});
// Compute acoustic physics for the environment
const env = await sonar.physics();
console.log(`Sound speed: ${env.soundSpeed} m/s`);
// Ping a target at 500 m range, 30° bearing
const echo = await sonar.ping({ range: 500, bearing: 30 });
console.log(`Round-trip: ${echo.travelTime} ms, SNR: ${echo.snr} dB`);All constructor options are optional and fall back to standard oceanographic defaults.
| Option | Type | Default | Description |
|---|---|---|---|
salinity |
number |
35 |
Water salinity in practical salinity units (PSU). Range: 0 – 42 |
temperature |
number |
10 |
Water temperature in °C. Range: ‑2 – 35 |
depth |
number |
0 |
Sensor depth in meters. Range: 0 – 11000 |
frequency |
number |
50000 |
Transducer center frequency in Hz. Range: 100 – 1000000 |
pH |
number |
8.1 |
Seawater pH for absorption models. Range: 7.5 – 8.5 |
spreading |
'spherical' | 'cylindrical' | 'hybrid' |
'spherical' |
Geometric spreading loss model |
roughness |
number |
0.5 |
RMS seafloor roughness in meters. Range: 0 – 10 |
sediment |
'sand' | 'silt' | 'clay' | 'gravel' | 'rock' |
'sand' |
Dominant sediment type for backscatter |
noiseFloor |
number |
-90 |
Receiver noise floor in dB re 1 µPa. Range: ‑120 – ‑40 |
sourceLevel |
number |
220 |
Transmit source level in dB re 1 µPa @ 1 m. Range: 100 – 260 |
pulseLength |
number |
0.001 |
Transmit pulse length in seconds. Range: 1e‑6 – 1 |
bandwidth |
number |
1000 |
Receiver bandwidth in Hz. Range: 1 – 1e6 |
const sonar = new SonarVision({
salinity: 34.5,
temperature: 4,
depth: 250,
frequency: 120000,
sediment: 'clay',
spreading: 'hybrid',
sourceLevel: 210,
noiseFloor: -95,
});Computes the local acoustic environment: sound speed, absorption coefficient, scattering strength, and visibility/attenuation at the configured depth.
Signature:
async physics(): Promise<PhysicsResult>Returns: PhysicsResult
| Property | Type | Unit | Description |
|---|---|---|---|
soundSpeed |
number |
m/s | Local speed of sound via Mackenzie (1981) |
absorption |
number |
dB/km | Total absorption (Thorp–Fisher–Simmons) |
scattering |
number |
dB | Volume scattering strength at frequency |
visibility |
number |
m | Estimated acoustic visibility (1-way, 10 dB excess loss) |
Example:
const phys = await sonar.physics();
// {
// soundSpeed: 1490.23,
// absorption: 14.82,
// scattering: -72.4,
// visibility: 675.3
// }Calculates one-way or two-way travel time and signal-to-noise ratio for a target or beacon at a given range and bearing.
Signature:
async ping(opts: PingOptions): Promise<PingResult>PingOptions:
| Property | Type | Required | Description |
|---|---|---|---|
range |
number |
Yes | Slant range to target in meters (> 0) |
bearing |
number |
Yes | Horizontal bearing in degrees (0 – 360) |
elevation |
number |
No | Vertical elevation in degrees (-90 – 90, default: 0) |
targetStrength |
number |
No | Echo target strength in dB (default: -30) |
twoWay |
boolean |
No | Compute round-trip time (default: true) |
Returns: PingResult
| Property | Type | Unit | Description |
|---|---|---|---|
travelTime |
number |
ms | One-way or round-trip time of flight |
snr |
number |
dB | Signal-to-noise ratio at receiver |
spreadingLoss |
number |
dB | Geometric spreading loss |
absorptionLoss |
number |
dB | Cumulative absorption loss |
totalLoss |
number |
dB | Sum of spreading + absorption |
Example:
const echo = await sonar.ping({
range: 1200,
bearing: 45,
targetStrength: -20,
twoWay: true,
});
// {
// travelTime: 1609.1,
// snr: 18.3,
// spreadingLoss: 62.1,
// absorptionLoss: 17.8,
// totalLoss: 79.9
// }Simulates a multibeam or sidescan-like backscatter sweep across a swath of bearings, returning per-beam backscatter intensity and inferred sediment confidence.
Signature:
async scan(opts: ScanOptions): Promise<ScanResult>ScanOptions:
| Property | Type | Required | Description |
|---|---|---|---|
ranges |
number[] |
Yes | Array of slant ranges in meters (ascending) |
bearings |
number[] |
Yes | Array of bearings in degrees |
grazingAngle |
number |
No | Grazing angle in degrees (default: 30) |
resolution |
number |
No | Along-track resolution in meters (default: 1) |
Returns: ScanResult
| Property | Type | Description |
|---|---|---|
beams |
Beam[] |
One entry per (range, bearing) pair |
swathWidth |
number |
Approximate seafloor swath width in meters |
centerDepth |
number |
Nadir depth estimate in meters |
Each Beam:
| Property | Type | Unit | Description |
|---|---|---|---|
range |
number |
m | Slant range |
bearing |
number |
° | Beam bearing |
backscatter |
number |
dB | Normalized backscatter intensity |
grazing |
number |
° | Effective grazing angle |
confidence |
number |
0 – 1 |
Sediment-classification confidence |
Example:
const scan = await sonar.scan({
ranges: [50, 100, 200, 400, 800],
bearings: [-60, -30, 0, 30, 60],
grazingAngle: 45,
});
for (const beam of scan.beams) {
console.log(`${beam.range}m @ ${beam.bearing}° → ${beam.backscatter.toFixed(1)} dB`);
}Generates a 21-point vertical profile of temperature, salinity, sound speed, and absorption from the surface to the configured sensor depth.
Signature:
async profile(): Promise<ProfileResult>Returns: ProfileResult
| Property | Type | Description |
|---|---|---|
points |
ProfilePoint[] |
21 evenly-spaced depth samples |
depthStep |
number |
Vertical spacing in meters |
mixedLayerDepth |
number |
Estimated mixed-layer depth in meters |
soundChannel |
boolean |
Whether a deep sound channel is detected |
Each ProfilePoint (index 0 = surface):
| Property | Type | Unit | Description |
|---|---|---|---|
depth |
number |
m | Water depth of sample |
temperature |
number |
°C | Estimated temperature at depth |
salinity |
number |
PSU | Estimated salinity at depth |
soundSpeed |
number |
m/s | Computed sound speed |
absorption |
number |
dB/km | Absorption coefficient |
gradient |
number |
s⁻¹ | Local sound-speed gradient |
Example:
const profile = await sonar.profile();
console.log(`Mixed layer: ${profile.mixedLayerDepth} m`);
console.log(`Deep sound channel: ${profile.soundChannel ? 'yes' : 'no'}`);
for (const pt of profile.points) {
console.log(`${pt.depth}m: ${pt.soundSpeed.toFixed(1)} m/s`);
}Performs an active-sonar detection pass, returning contact list with estimated range, bearing, and probability of detection for each contact.
Signature:
async detect(opts: DetectOptions): Promise<DetectResult>DetectOptions:
| Property | Type | Required | Description |
|---|---|---|---|
maxRange |
number |
No | Maximum search range in meters (default: sonar.visibility * 2) |
beamwidth |
number |
No | Horizontal beamwidth in degrees (default: 10) |
sweep |
number[] |
No | Explicit bearing centers to search (default: [0, 30, 60, ..., 330]) |
falseAlarmRate |
number |
No | Desired Pfa (default: 1e-6) |
targetStrength |
number |
No | Assumed target strength in dB (default: -30) |
Returns: DetectResult
| Property | Type | Description |
|---|---|---|
contacts |
Contact[] |
Detected contacts (may be empty) |
sweptBearings |
number[] |
Bearings actually searched |
maxRange |
number |
Search radius used |
probabilityOfDetection |
number |
Aggregate Pd across swept volume |
Each Contact:
| Property | Type | Unit | Description |
|---|---|---|---|
range |
number |
m | Estimated slant range |
bearing |
number |
° | Estimated bearing |
elevation |
number |
° | Estimated elevation (0 if unknown) |
snr |
number |
dB | Measured SNR at detection |
probability |
number |
0 – 1 |
Single-look probability of detection |
confidence |
'high' | 'medium' | 'low' |
Contact confidence tier |
Example:
const detect = await sonar.detect({
maxRange: 2000,
beamwidth: 15,
targetStrength: -25,
falseAlarmRate: 1e-5,
});
console.log(`Contacts found: ${detect.contacts.length}`);
for (const c of detect.contacts) {
console.log(
` ${c.confidence.toUpperCase()} contact at ` +
`${c.range}m / ${c.bearing}° (SNR ${c.snr.toFixed(1)} dB)`
);
}For real-time or large survey simulations, every method exposes a streaming variant that yields incremental results via an AsyncIterable.
Streaming method names are suffixed with Stream:
| Classic | Streaming |
|---|---|
physics() |
physicsStream() |
ping() |
pingStream() |
scan() |
scanStream() |
profile() |
profileStream() |
detect() |
detectStream() |
Each stream yields { done: false, value: PartialResult } chunks and terminates with { done: true, result: FinalResult }.
const stream = sonar.scanStream({
ranges: Array.from({ length: 100 }, (_, i) => (i + 1) * 10),
bearings: Array.from({ length: 36 }, (_, i) => i * 10),
});
for await (const chunk of stream) {
if (!chunk.done) {
// chunk.value is a single Beam
processBeam(chunk.value);
} else {
// chunk.result is the full ScanResult
console.log(`Swath width: ${chunk.result.swathWidth} m`);
}
}const stream = sonar.profileStream();
let received = 0;
for await (const chunk of stream) {
if (!chunk.done) {
received++;
const pt = chunk.value;
console.log(`[${received}/21] ${pt.depth}m → ${pt.soundSpeed.toFixed(1)} m/s`);
} else {
console.log('Profile complete:', chunk.result.soundChannel);
}
}const controller = new AbortController();
// Abort after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
for await (const chunk of sonar.detectStream({ maxRange: 5000 }, controller.signal)) {
if (!chunk.done && chunk.value.confidence === 'high') {
console.log('High-confidence contact!', chunk.value);
}
}
} catch (err) {
if (err.name === 'AbortError') {
console.log('Search aborted by user');
} else {
throw err;
}
}All async methods throw typed SonarVisionError instances. Every error carries a code string for programmatic handling and a human-readable message.
| Code | Meaning | Typical Cause |
|---|---|---|
INVALID_CONFIG |
Constructor option out of range | Salinity > 42, negative depth, etc. |
INVALID_ARGUMENT |
Method argument out of range | Negative range, bearing > 360 |
COMPUTATION_ERROR |
Internal math or model failure | Extreme inputs causing divergence |
TIMEOUT |
Computation exceeded deadline | Very large scans without streaming |
ABORTED |
Request was cancelled | AbortSignal triggered |
Example:
import { SonarVision, SonarVisionError } from '@superinstance/sonar-vision-tool';
try {
const sonar = new SonarVision({ temperature: 50 }); // invalid
await sonar.physics();
} catch (err) {
if (err instanceof SonarVisionError) {
console.error(`[${err.code}] ${err.message}`);
// → [INVALID_CONFIG] temperature must be between -2 and 35 °C
} else {
console.error('Unexpected error:', err);
}
}Validation helper:
import { validateConfig } from '@superinstance/sonar-vision-tool';
const issues = validateConfig({ temperature: 50, depth: -5 });
// [
// { field: 'temperature', message: 'must be ≤ 35', value: 50 },
// { field: 'depth', message: 'must be ≥ 0', value: -5 }
// ]First-class TypeScript support with exported interfaces for all inputs and outputs.
import {
SonarVision,
SonarConfig,
PhysicsResult,
PingOptions,
PingResult,
ScanOptions,
ScanResult,
ProfileResult,
DetectOptions,
DetectResult,
SonarVisionError,
} from '@superinstance/sonar-vision-tool';
const config: SonarConfig = {
salinity: 35,
temperature: 10,
depth: 100,
frequency: 50000,
};
const sonar = new SonarVision(config);
const phys: PhysicsResult = await sonar.physics();MIT © SuperInstance Labs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.