<html> <head>

<title>Calculate Your Vocal Tract Length</title>
<script>
  var audio_context;
  var recorder;

  window.onload = function init() {
    try {
      // webkit shim
      window.AudioContext = window.AudioContext || window.webkitAudioContext;
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
      window.URL = window.URL || window.webkitURL;

      audio_context = new AudioContext;
      navigator.getUserMedia({audio: true}, startUserMedia, function(e) {
        console.log('No live audio input: ' + e);
      });
    } catch (e) {
      alert('No web audio support in this browser!');
    }

  };

  function startUserMedia(stream) {
    var input = audio_context.createMediaStreamSource(stream);
    recorder = new Recorder(input);
    document.querySelector("[data-id=allowHint]").style.display = 'none';
    startRecordingButton = document.querySelector("[data-action=startRecording]");
    startRecordingButton.disabled = false;
    startRecordingButton.addEventListener("click", startRecording, false);
  }

  function startRecording() {
    this.innerHTML = "Recording... "
    this.disabled = true;
    recorder && recorder.record();

    setTimeout(stopRecording, 2000, this);
  }

  function stopRecording(button) {
    recorder && recorder.stop();

    document.querySelector("[data-action=startRecording]").innerHTML =
      "Calculating...";
    recorder.exportWAV(sendWAV);
  }

  function calculateVocalTractLength(formant1) {
    return 34400 / (4.0 * formant1);
  }

  function roundTo(number, places) {
    return Math.round(number * Math.pow(10, places))/Math.pow(10, places);
  }

  function setFormant1Result(formant1Text) {
    formant1 = parseFloat(formant1Text);
    document.querySelector("[data-action=startRecording]").disabled = false;
    document.querySelector("[data-action=startRecording]").innerHTML =
      "Record";
    document.querySelector("[data-id=results]").style.display = 'initial';
    document.querySelector("[data-id=formant1]").innerHTML = roundTo(formant1, 0);
    document.querySelector("[data-id=vocalTractLength]").innerHTML = roundTo(calculateVocalTractLength(formant1), 1);
  }

  function sendWAV(blob) {
    var formData = new FormData();
    formData.append('data', blob);
    xhr = new XMLHttpRequest();
    xhr.open("POST", "extract_formant1", true);
    xhr.onload = function() {
      setFormant1Result(xhr.responseText);
    }
    xhr.send(formData);

    recorder.clear();
  }

</script>
<script src="recorder.js"></script>

</head> <body>

<nav>
  <a href="/">Home</a>
  <a href="https://github.com/mxhold/vocal_tract_length">Source code</a>
</nav>
<section>
  <h1>Calculate Your Vocal Tract Length</h1>
  <p>
    Click <em>Record</em> and produce an unobstructed vowel (the last sound in
    the English word <em>comma</em>) continuously for 2 seconds.
  </p>
  <p data-id="allowHint">
    <em>First, click "Allow" in your browser to let this page to record your
      voice.</em>
  </p>
  <p>
    <button data-action="startRecording" disabled>Record</button>
  </p>
</section>
<section data-id="results" style="display: none;">
  <h1>Results</h1>
  <p>
    F<sub>1</sub> = <span data-id="formant1">error</span> Hz
  </p>
  <p>
    Estimated vocal tract length:
    <span data-id="vocalTractLength">error</span> cm
  </p>
  <p>
    Average vocal tract length (female): 14.1 cm<br>
    Average vocal tract length (male): 17 cm<br>
  </p>
</section>
<section>
  <h1>About</h1>
  <p>
    If we simplify things significantly, the human vocal tract is really just
    a tube that is closed on one end (where your vocal folds vibrate) and open
    on another end (your lips).
  </p>
  <p>
    Given this model, there is a formula that expresses the relationship
    between resonance frequencies (<em>f</em>, which we is what we are
    measuring here), the speed of sound (<em>v</em>, which we know), and the
    length of the tube (<em>L</em>, which is what we are trying to figure
    out):
  </p>
  <img src="frequency.png" alt="f = nv/2L" height="50">
  <p>
    The <em>n</em> here refers to which resonance frequency we're talking
    about. Vowels will usually have four or more distinguishable resonance
    frequencies, but we're just dealing with the first (F<sub>1</sub>) here,
    so we can replace this with 1. We can also replace <em>v</em> with 34400
    cm/s since we know that's the speed of sound at sea level at room
    temperature (20&deg;C). And since we know the frequency but want to
    determine the length, we can solve for L to get this equation:
  </p>
  <img src="length.png" alt="L = 34400/4f" height="50">
  <h2>How are you measuring F<sub>1</sub>?</h2>
  <p>
    When you hit record, we are using a Javascript library that wraps the
    browser's audio recording API to record a 2 second WAV audio clip.
    Thanks to
    <a href="http://matt-diamond.com/">Matt Diamond's</a>
    <a href="https://github.com/mattdiamond/Recorderjs">Recorderjs project</a>
    for making that library.
  </p>
  <p>
    This data is then sent to the server where it is processed using <a
       href="http://praat.org">Praat</a>, a seriously cool program that can do
     all sorts of phonetic analysis. This gives us the first formant which we
     then plug into the above equation.
  </p>
  <p>
    You can see the source code for this page and the server side Praat script
    on <a href="https://github.com/mxhold/vocal_tract_length">GitHub</a>.
  </p>
  <h2>Is this accurate? Why is the estimate so inconsistent?</h2>
  <p>
    It is pretty hard to get people to produce an unobstructed vowel
    consistently so that is probably the biggest limitation with this method.
    We're also using a simplified model of the vocal tract, likely using a
    less than perfect microphone, and only taking a 2 second sample.
  </p>
  <p>
    The point of this is not to actually give you an accurate measure, but
    rather to show it's possible to calculate the length of the vocal tract
    from a recording, which I think is pretty cool.
  </p>
</section>

</body> </html>