AI Generate HTML to show the relative distance to each speaker

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).

  1. 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$.
  2. 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 AudioContext and 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_SOUND constant 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.

  1. Sample Period ($T_s$):$$T_s = \frac{1}{\text{Sampling Rate}} = \frac{1}{48,000 \text{ Hz}} \approx 20.83 \text{ \mu s}$$
  2. 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 a gate~ 4 to cycle through your speakers.
  • Timing: Use cpuclock or timer. Trigger the “start” when the click is sent and the “stop” when the threshold~ of the microphone is breached.
  • Signal Detection: Use abs~ followed by threshold~ to detect the incoming click. You may need a small gate to 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 js object.
  • 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 gate in Max to ensure you are only timing the first arrival of the sound, not the reverberation.
  • Coordinate Mapping: In the js code above, Speaker 1 is the origin $(0,0)$. If your room layout differs, simply update the S1 through S4 constants 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?