Implement the Equations in JavaScript and Review
Turn the equations into small JavaScript functions, then review how range, frequency, sonar mode, and element count affect the result.
What this chapter does
In this chapter, we implement the equations from earlier chapters as small JavaScript functions. As you read, connect each mathematical symbol to the corresponding function name, argument, and return value.
The first half explains the code. The exercises in the second half review how range, frequency, passive versus active operation, and element count work together.
Map each equation to a function first
| Value | Equation or idea | JavaScript function |
|---|---|---|
| Sound speed c | The Mackenzie approximation using temperature, salinity, and depth | mackenzieSoundSpeedMps |
| Absorption coefficient α | The course's frequency-only teaching formula | absorptionDbPerKm |
| Wavelength λ | λ = c / f | wavelengthM |
| One-way transmission loss TL | k log10(R) + αR / 1000 | transmissionLossDb |
| Round-trip time | 2R / c | roundTripTimeSec |
| Directivity index DI | The idealized 10 log10(N) | arrayGainDb |
| Passive SNR | SL - TL - (NL - DI) | passiveSnrDb |
| Active SNR | SL - 2TL + TS - (NL - DI) | activeSnrDb |
For example, wavelengthM divides sound speed by frequency and returns wavelength. Pairing each function with one equation makes the longer code listing much easier to follow.
Validate inputs before calculating
The implementation uses standard JavaScript and no external libraries. Before calculating, it checks that a value is finite, that it falls within the chosen range, and that element count is a positive integer.
An invalid value is not silently rounded or clamped. Instead, the code throws a RangeError, making it clear which input violated the equation's assumptions. The code runs as written in current major browsers and Node.js.
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 minimum code needed to trace the relationships used in the course. A real deployment would also account for surface and seafloor loss, reverberation, refraction, bandwidth, and detection thresholds.
Read the code in four groups
- Validate the inputs:
assertFiniteNumber,assertRange, andassertPositiveIntegerstop invalid values before calculation. - Find the basic environmental quantities:
mackenzieSoundSpeedMpsreturns sound speed, andabsorptionDbPerKmreturns absorption per kilometer. - Calculate range- and array-related quantities:
wavelengthM,transmissionLossDb,roundTripTimeSec, andarrayGainDbeach implement the equation shown in the table above. - Calculate SNR last: passive sonar subtracts one-way
TLonce; active sonar subtracts the round-trip2TL.
Read one function at a time from its inputs to its return value. In particular, follow the value returned by transmissionLossDb into the two SNR functions to see how the full calculation connects.
Connect everything in the review
The exercises that follow mix direct function calculations with questions about how sonar is used. If useful, run the code in your browser's developer tools or in Node.js and check the result.
You have completed the review when you can explain, from both the equations and the functions, why increasing range raises TL, why increasing frequency shortens wavelength but raises absorption, and why adding elements increases DI.
Review the implementation and sonar fundamentals
0 / 7 correct. Results are saved only in this browser's localStorage.
Q30. Calculate the value returned by roundTripTimeSec
roundTripTimeSec(rangeM, soundSpeedMps) is called with (1500, 1500). How many seconds does it return?Show hint
2 × range / c.Show reasoning
2 × 1500 / 1500 = 2, so the function returns 2.0 s.Q31. Calculate the value returned by wavelengthM
wavelengthM(1500, 30000) is called. How many meters does it return?Show hint
1500 / 30000.Show reasoning
1500 / 30000 = 0.05, so the function returns 0.05 m.Q32. Identify the exception used for invalid input
Show hint
Show reasoning
RangeError to make it clear that an input is outside the range allowed by the equation.Q33. Calculate the array gain for 10 elements
arrayGainDb(10) is called. Approximately how many dB does it return?Show hint
10 log10(10).Show reasoning
10 log10(10) = 10, so about 10 dB.Q34. Find the active-SNR improvement when range is halved
Show hint
2TL.Show reasoning
transmissionLossDb includes absorption, its actual return value also depends on frequency.Q35. Decide which sonar mode pays the larger range penalty
Show hint
Show reasoning
2TL, so increasing range causes a larger loss than it does in passive sonar.Q36. Choose a method for monitoring without transmitting
Show hint
Show reasoning
Where this course leaves you
- Mapping mathematical inputs and outputs directly to JavaScript arguments and return values makes the implementation easier to follow.
- When an input violates an equation's assumptions, the code stops clearly with
RangeError. - Sound speed, transmission loss, array gain, and passive and active SNR can be built as separate functions and then combined.
- Real-world design also adds refraction, reverberation, surface and seafloor scattering, and detection thresholds.