Table of Contents
Prompt
generate the html that would show the relative distance to each speaker that emits a simultaineous click received by smartphone using triangulation and time of flight. Derive the location of the phone both cartesian and polar coordinates.
Response
This is a classic “Acoustic GPS” problem. To solve this, we use Multilateration (MLAT) based on the Time Difference of Arrival (TDOA).
Below is a self-contained HTML/JavaScript application. It assumes three speakers at fixed coordinates and calculates the phone’s position based on the time it takes for a simultaneous “click” to reach the device.
The Mathematical Derivation
To find the location $(x, y)$, we use the distance formula between the phone and each speaker $S_n$ at $(x_n, y_n)$:
$$d_n = \sqrt{(x – x_n)^2 + (y – y_n)^2}$$
Since the clicks are simultaneous, the distance is $d_n = v \cdot t_n$, where $v$ is the speed of sound (~343 m/s).
- Cartesian Coordinates: We solve the system of quadratic equations. By subtracting the equations from one another, we linearize the problem to find $x$ and $y$.
- Polar Coordinates: Once we have $(x, y)$, we convert using:
- $r = \sqrt{x^2 + y^2}$
- $\theta = \operatorname{atan2}(y, x)$
Interactive Triangulation Calculator
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Acoustic Triangulation Tracker</title>
<style>
body { font-family: sans-serif; line-height: 1.6; max-width: 800px; margin: 20px auto; padding: 0 20px; background: #f4f4f9; }
.card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.input-group { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px; }
input { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
#results { margin-top: 20px; border-top: 2px solid #eee; padding-top: 20px; }
.coord-box { background: #e9ecef; padding: 15px; border-radius: 4px; font-family: monospace; }
</style>
</head>
<body>
<div class="card">
<h2>Acoustic Localization (ToF)</h2>
<p>Enter the time (ms) it took for the click to reach the phone from each speaker.</p>
<div class="input-group">
<div>
<label>Speaker A (0,0) - Time (ms):</label><br>
<input type="number" id="t1" value="10" step="0.1">
</div>
<div>
<label>Speaker B (10,0) - Time (ms):</label><br>
<input type="number" id="t2" value="15" step="0.1">
</div>
<div>
<label>Speaker C (5,10) - Time (ms):</label><br>
<input type="number" id="t3" value="12" step="0.1">
</div>
</div>
<button onclick="calculateLocation()">Calculate Position</button>
<div id="results">
<h3>Calculated Location:</h3>
<div class="coord-box" id="output">
Click calculate to see coordinates...
</div>
</div>
</div>
<script>
const V_SOUND = 343; // Speed of sound in m/s
// Speaker coordinates (Fixed anchors)
const S1 = { x: 0, y: 0 };
const S2 = { x: 10, y: 0 };
const S3 = { x: 5, y: 10 };
function calculateLocation() {
// Convert ms to s and calculate distances (d = v * t)
const d1 = (document.getElementById('t1').value / 1000) * V_SOUND;
const d2 = (document.getElementById('t2').value / 1000) * V_SOUND;
const d3 = (document.getElementById('t3').value / 1000) * V_SOUND;
/* Using Trilateration formula:
x = (d1^2 - d2^2 + x2^2) / (2 * x2)
y = (d1^2 - d3^2 + x3^2 + y3^2 - 2*x*x3) / (2 * y3)
*/
const x = (Math.pow(d1, 2) - Math.pow(d2, 2) + Math.pow(S2.x, 2)) / (2 * S2.x);
const y = (Math.pow(d1, 2) - Math.pow(d3, 2) + Math.pow(S3.x, 2) + Math.pow(S3.y, 2) - (2 * x * S3.x)) / (2 * S3.y);
// Convert to Polar
const r = Math.sqrt(x*x + y*y);
const thetaRad = Math.atan2(y, x);
const thetaDeg = thetaRad * (180 / Math.PI);
document.getElementById('output').innerHTML = `
<strong>Relative Distances:</strong><br>
Dist to S1: ${d1.toFixed(2)}m | S2: ${d2.toFixed(2)}m | S3: ${d3.toFixed(2)}m<br><br>
<strong>Cartesian Coordinates:</strong><br>
X: ${x.toFixed(3)} m<br>
Y: ${y.toFixed(3)} m<br><br>
<strong>Polar Coordinates:</strong><br>
Radius (r): ${r.toFixed(3)} m<br>
Azimuth (θ): ${thetaDeg.toFixed(2)}°
`;
}
</script>
</body>
</html>
Key Considerations
- HTTPS Requirement: As noted in your CIE Project Log, if you plan to use actual smartphone hardware (IMU or Mic), modern browsers will require an HTTPS connection to access the
AudioContextand motion sensors. - Clock Sync: This code assumes a “simultaneous” click. In practice, unless the speakers are wired to the same source, you would likely calculate TDOA (Time Difference of Arrival) rather than absolute Time of Flight, which requires a slightly different hyperbolic positioning algorithm.
- Environmental Factors: The speed of sound varies with temperature ($v \approx 331.3 + 0.606T$). For high precision, you should adjust the
V_SOUNDconstant based on the room temperature.
Expected Resolution
click here for response on prompt regarding resolution in meters at 48khz sample rate
At a sampling rate of 48 kHz, the raw expected resolution (or “granularity”) of an acoustic time-of-flight measurement is approximately 0.00715 meters (7.15 mm).
Mathematical Derivation
The spatial resolution is determined by how much distance sound travels in the duration of a single sample.
- Sample Period ($T_s$):$$T_s = \frac{1}{\text{Sampling Rate}} = \frac{1}{48,000 \text{ Hz}} \approx 20.83 \text{ \mu s}$$
- Distance per Sample ($d$):Using the standard speed of sound ($v \approx 343.2 \text{ m/s}$ at 20°C):$$d = v \times T_s = 343.2 \text{ m/s} \times 0.00002083 \text{ s} \approx 0.00715 \text{ m}$$
Factors Affecting Real-World Resolution
While 7.15 mm is the physical limit of a single discrete sample, your actual accuracy in the triangulation script you are building will be influenced by:
Geometric Dilution of Precision (GDOP): The resolution is highest when the phone is centered between speakers; as the phone moves toward the edge of the “tracking box,” the mathematical resolution degrades even if the sampling rate remains constant.
Sub-sample Interpolation: By using cross-correlation or phase-shifting techniques, it is possible to achieve “sub-sample” resolution, potentially reaching accuracies in the 1–2 mm range.
Temperature Sensitivity: The speed of sound changes by about 0.6 m/s for every 1°C change. If your room temperature drifts by 5°C, your distance calculation could be off by more than 1% (approx. 3 cm over a 3-meter span).
Clock Jitter & Latency: In smartphone-based tracking, OS-level audio buffer latencies can introduce significant jitter. For the best results, you should use a low-latency API like Web Audio API or Oboe (Android) to ensure the most consistent time-stamping possible.
MAX And Javascript (partial)
To implement the detection logic in Max MSP, you will need to precisely time the interval between the click emission and the arrival at the microphone.
Since you are working with ADC 1 for the microphone and DAC 1–4 for the speakers, the most robust method involves using a high-priority timer triggered by the audio signal itself.
Max MSP Patch Structure
- Emission Logic: Use a
click~object. Send it through agate~ 4to cycle through your speakers. - Timing: Use
cpuclockortimer. Trigger the “start” when the click is sent and the “stop” when thethreshold~of the microphone is breached. - Signal Detection: Use
abs~followed bythreshold~to detect the incoming click. You may need a smallgateto prevent false triggers from room noise or echoes.
Expanded JavaScript Logic (triangulation.js)
Based on your current setup and the dimensions of your room (20ft x 12ft), this script handles the conversion of arrival times into coordinates. It incorporates the speed of sound and the speaker coordinates you specified.
JavaScript
// triangulation.js
// Designed for a 20x12ft room
const V_SOUND = 1125; // Speed of sound in ft/s (approx 70°F)
// Corner Speaker Coordinates (ft)
const S1 = { x: 0, y: 0 }; // DAC 1
const S2 = { x: 20, y: 0 }; // DAC 2
const S3 = { x: 20, y: 12 }; // DAC 3
const S4 = { x: 0, y: 12 }; // DAC 4
function list() {
// Expects a list of 4 arrival times in milliseconds
var times = arrayfromargs(arguments);
if (times.length < 4) {
error("Requires 4 time values (one per speaker).");
return;
}
// 1. Convert ms to Distance (d = v * t)
// t is divided by 1000 to convert ms to seconds
var d = times.map(function(t) {
return (t / 1000) * V_SOUND;
});
// 2. Trilateration Calculation (Solving for X and Y)
// We use the first three speakers as primary anchors
var x = (Math.pow(d[0], 2) - Math.pow(d[1], 2) + Math.pow(S2.x, 2)) / (2 * S2.x);
// Using S1 and S4 for the Y calculation
var y = (Math.pow(d[0], 2) - Math.pow(d[3], 2) + Math.pow(S4.y, 2)) / (2 * S4.y);
// 3. Polar Coordinates Calculation
var r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var theta = Math.atan2(y, x) * (180 / Math.PI);
// 4. Output to Max
outlet(0, "cartesian", x, y);
outlet(1, "polar", r, theta);
// Optional: Validation check using the 4th speaker
var d4_calc = Math.sqrt(Math.pow(x - S3.x, 2) + Math.pow(y - S3.y, 2));
var error_margin = Math.abs(d4_calc - d[2]);
outlet(2, "error", error_margin);
}
Implementation Tips
- Calibration: For the most accurate results, perform a “zero-distance” test. Place the microphone directly against a speaker and measure the time. This value is your system latency. Subtract this constant from all future measurements before sending them to the
jsobject. - Room Environment: If you are working in a space like the Eclipse Gallery, remember that large flat surfaces can cause reflections. You may want to use a windowing function or a simple
gatein Max to ensure you are only timing the first arrival of the sound, not the reverberation. - Coordinate Mapping: In the
jscode above, Speaker 1 is the origin $(0,0)$. If your room layout differs, simply update theS1throughS4constants to match your actual physical measurements.
Do you need help setting up the threshold~ subpatch to ensure the click detection is reliable against background noise?
