Chapter 7 · 7 practice questions · In-browser grading · Local storage

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.

Overall0 / 360%
This page0 / 7Correct answers
StoragelocalStorage onlyNo data sent to a server

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

FunctionMeaning
mackenzieSoundSpeedMpsApproximates sound speed c from temperature, salinity, and depth
wavelengthMλ = c / f
transmissionLossDbOne-way TL as spreading + absorption
roundTripTimeSec2R / c
arrayGainDbThe idealized 10 log10(N)
passiveSnrDbThe simplified passive-SNR equation
activeSnrDbThe 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.

Chapter 7 / Practice 1
Unanswered

Q30. Read the return value of roundTripTimeSec

Using the chapter's 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.
Chapter 7 / Practice 2
Unanswered

Q31. Read the return value of wavelengthM

What does wavelengthM(1500, 30000) return, in meters?

Show hint
1500 / 30000.
Show reasoning
The return value is 0.05 m.
Chapter 7 / Practice 3
Unanswered

Q32. How precondition violations are reported

When this course's minimal implementation receives a precondition violation — negative range, an out-of-range temperature, and so on — which exception does it use to stop?

Show hint
It is the exception for an out-of-range value.
Show reasoning
To keep the course material and implementation easy to follow, precondition violations are stopped with RangeError.
Chapter 7 / Practice 4
Unanswered

Q33. Array gain for 10 elements

What does arrayGainDb(10) return, approximately, in dB?

Show hint
10 log10(10).
Show reasoning
10 log10(10) = 10, so about 10 dB.
Chapter 7 / Practice 5
Unanswered

Q34. Active echo SNR when the range is halved

For conceptual clarity, consider an approximation that uses spherical spreading only and ignores absorption (note: the actual 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
Active pays 2TL.
Show reasoning
A 6 dB improvement in 1-way TL gives roughly 12 dB in active, because the round trip counts it twice. Note that to reproduce this in code, you would have to zero out the absorption term, since the implementation's transmissionLossDb always includes both spreading and absorption.
Chapter 7 / Practice 6
Unanswered

Q35. Which side takes the bigger range penalty

Compared at the same environment and same 1-way TL, under the simple baseline equations, which is hurt more as range grows?

Show hint
Active is out-and-back.
Show reasoning
Active pays 2TL, so it is hit harder than passive as range grows.
Chapter 7 / Practice 7
Unanswered

Q36. Best fit for transmit-free marine-life monitoring

Suppose we want to monitor whales and shipping noise with as little transmitted sound as possible. Which approach is the natural first pick?

Show hint
"Listen only" is the keyword.
Show reasoning
For monitoring marine life and ship noise, a passive hydrophone array is the natural entry-level choice.

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.