Implementation and review — writing sonar calculations in vanilla JavaScript
Drop the equations straight into JavaScript functions. At the end, a comprehensive-review case ties range, frequency, mode, and element count back together.
Part I: Reading the minimal implementation
This chapter has two parts. Part I (this section through the function-to-equation table) walks through the minimum vanilla JavaScript implementation that maps directly to the equations from Chapters 2 through 5. Part II then proceeds to case-based exercises that combine those functions.
How the minimal implementation is built
The implementation for this course is pure vanilla JavaScript. There are no external libraries, and inputs that violate the preconditions are stopped with a RangeError. For a teaching codebase, explicitly flagging violations is easier to follow than silently rounding or clamping — it keeps the correspondence between formula and code obvious.
A note on browser compatibility: the code below uses features introduced in ECMAScript 2015 (ES6) or later, including Math.log10, Number.isFinite, Number.isInteger, and the ** exponentiation operator. It runs in modern browsers (Chrome 44+, Firefox 25+, Safari 10+, Edge 12+) without any extra setup, but it will not run in legacy environments such as Internet Explorer. For legacy support, consider polyfilling — for example, Math.log10 = Math.log10 || function (x) { return Math.log(x) / Math.LN10; };.
The minimal JavaScript implementation
function assertFiniteNumber(name, value) {
if (!Number.isFinite(value)) {
throw new RangeError(`${name} must be a finite number`);
}
}
function assertRange(name, value, min, max) {
assertFiniteNumber(name, value);
if (value < min || value > max) {
throw new RangeError(`${name} must be in [${min}, ${max}]`);
}
}
function assertPositiveInteger(name, value) {
if (!Number.isInteger(value) || value <= 0) {
throw new RangeError(`${name} must be a positive integer`);
}
}
function mackenzieSoundSpeedMps(tempC, salinityPsu, depthM) {
assertRange('tempC', tempC, 2, 30);
assertRange('salinityPsu', salinityPsu, 25, 40);
assertRange('depthM', depthM, 0, 8000);
return 1448.96
+ 4.591 * tempC
- 5.304e-2 * tempC ** 2
+ 2.374e-4 * tempC ** 3
+ 1.340 * (salinityPsu - 35)
+ 1.630e-2 * depthM
+ 1.675e-7 * depthM ** 2
- 1.025e-2 * tempC * (salinityPsu - 35)
- 7.139e-13 * tempC * depthM ** 3;
}
// Educational simplified absorption formula (frequency only).
// Derived in form from François-Garrison (1982) / Ainslie-McColm (1998),
// but with temperature, salinity, pH and depth dependence removed.
// For field design, use the full models with environmental inputs.
function absorptionDbPerKm(frequencyKhz) {
assertRange('frequencyKhz', frequencyKhz, 0.4, 200);
const f2 = frequencyKhz ** 2;
return 0.11 * f2 / (1 + f2)
+ 44 * f2 / (4100 + f2)
+ 0.000275 * f2
+ 0.003;
}
function wavelengthM(soundSpeedMps, frequencyHz) {
assertRange('soundSpeedMps', soundSpeedMps, 1300, 1700);
assertRange('frequencyHz', frequencyHz, 1, 1_000_000);
return soundSpeedMps / frequencyHz;
}
function transmissionLossDb(rangeM, frequencyKhz, spreadingCoeff = 20) {
assertRange('rangeM', rangeM, 1, 1_000_000);
assertRange('spreadingCoeff', spreadingCoeff, 10, 20);
return spreadingCoeff * Math.log10(rangeM)
+ absorptionDbPerKm(frequencyKhz) * (rangeM / 1000);
}
// roundTripTimeSec: returns 2R / c.
// Lower bound on rangeM is 1 m because a zero range is physically meaningless
// for a sonar problem (target would coincide with the transducer).
function roundTripTimeSec(rangeM, soundSpeedMps) {
assertRange('rangeM', rangeM, 1, 1_000_000);
assertRange('soundSpeedMps', soundSpeedMps, 1300, 1700);
return (2 * rangeM) / soundSpeedMps;
}
function arrayGainDb(elementCount) {
assertPositiveInteger('elementCount', elementCount);
return 10 * Math.log10(elementCount);
}
function passiveSnrDb(sourceLevelDb, tlDb, noiseLevelDb, diDb) {
return sourceLevelDb - tlDb - (noiseLevelDb - diDb);
}
function activeSnrDb(sourceLevelDb, tlDb, targetStrengthDb, noiseLevelDb, diDb) {
return sourceLevelDb - 2 * tlDb + targetStrengthDb - (noiseLevelDb - diDb);
}This is the minimal set needed to compute range, wavelength, TL, and SNR consistently throughout the course. Real-world design adds boundary loss, reverberation, refraction, richer propagation models, bandwidth, and detection thresholds — but for an introduction, this is enough.
Function-to-equation map
| Function | Meaning |
|---|---|
mackenzieSoundSpeedMps | Approximates sound speed c from temperature, salinity, and depth |
wavelengthM | λ = c / f |
transmissionLossDb | One-way TL as spreading + absorption |
roundTripTimeSec | 2R / c |
arrayGainDb | The idealized 10 log10(N) |
passiveSnrDb | The simplified passive-SNR equation |
activeSnrDb | The simplified active-SNR equation |
The comprehensive review at the end ties things together in cases: that active SNR drops sharply with range, that higher frequencies shorten wavelengths but pay more absorption, and that adding elements helps through DI.
Part II: Reconstructing the picture through cases
From here onward we move into the comprehensive review. Mentally combine the functions you read in Part I and reconstruct the trade-offs of range, frequency, mode, and element count as a single story. Working out the answers while running the code makes the dependencies between functions much easier to internalize.
Comprehension check for this chapter
0 / 7 correct. Results are saved only in this browser's localStorage.
Q30. Read the return value of roundTripTimeSec
roundTripTimeSec(rangeM, soundSpeedMps), what does (1500, 1500) return, in seconds?Show hint
2 × range / c.Show reasoning
2 × 1500 / 1500 = 2, so the return value is 2.0 s.Q31. Read the return value of wavelengthM
wavelengthM(1500, 30000) return, in meters?Show hint
1500 / 30000.Show reasoning
Q32. How precondition violations are reported
Show hint
Show reasoning
RangeError.Q33. Array gain for 10 elements
arrayGainDb(10) return, approximately, in dB?Show hint
10 log10(10).Show reasoning
10 log10(10) = 10, so about 10 dB.Q34. Active echo SNR when the range is halved
transmissionLossDb function always includes absorption, so this is a simplified theoretical question). Halving the range improves 1-way TL by about 6 dB. By how much does active echo SNR improve, approximately?Show hint
2TL.Show reasoning
transmissionLossDb always includes both spreading and absorption.Q35. Which side takes the bigger range penalty
Show hint
Show reasoning
2TL, so it is hit harder than passive as range grows.Q36. Best fit for transmit-free marine-life monitoring
Show hint
Show reasoning
Takeaways from this chapter
- In the minimal implementation, equations turn directly into functions, and precondition violations are stopped with RangeError.
- Remember the four pillars: Mackenzie sound-speed approximation, TL approximation, SNR approximation, and the array-gain approximation.
- Finally, reconnect the range, frequency, mode, and element-count tradeoffs through a case study.