programming, just for fun,

A Personal, Portable Laugh Track

Adam Dunkels, PhD Adam Dunkels, PhD Follow Aug 19, 2024 · 4 mins read
A Personal, Portable Laugh Track
Share this

This was a ridiculous idea I had one day: wouldn’t it be fun to have a personal, portable laugh track – you know, like in those old sitcoms where there would be a canned laughter after every joke.

So I figured it would be a fun project to. This is the result:

How it works

The principle is simple: sample the microphone. If there is sound, wait until it gets quiet. Then play a laughter.

There is no further processing of the sound – and there is no AI involved. So it doesn’t matter how fun the jokes are, there will always be a laughter.

(In fact, sometimes there is booing instead of laughter.)

The code

The code written in JavaScript, runs directly in the browser, and is simple. It uses the Web audio API to get a buffer of sound from the microphone, then does a very simple analysis to see if there is sound or silence in the buffer.

First, we retrieve the audio buffer:

    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });

    const mediastreamaudiosourcenode = audioContext().createMediaStreamSource(stream);
    const analysernode = audioContext().createAnalyser();
    mediastreamaudiosourcenode.connect(analysernode);

Then we do a simple amplitude summation to see if there is enough sound in the buffer:

    const pcmdata = new Float32Array(analysernode.fftSize);
    const onFrame = () => {
      analyserNode.getFloatTimeDomainData(pcmdata);
      let sumsquares = 0.0;
      for (const amplitude of pcmdata) {
        sumsquares += amplitude * amplitude;
      }
      let value = Math.sqrt(sumsquares / pcmdata.length);
      recentvalues.push(value);
      if (recentvalues.length > 10) {
        recentvalues.shift();
      }

        if (!laughing) {
          if (value > 0.005) {
            if (sounddetected === true) {
              soundend = Date.now();
            } else {
              soundstart = Date.now();
              sounddetected = true;
            }
          } else {
            if (silencedetected === false) {
              silencestart = Date.now();
              silencedetected = true;
            }
          }

          if (sounddetected &&
            soundend - soundstart > 4 * 1000 &&
            silencedetected &&
            Date.now() - silencestart > 2 * 1000) {
            sounddetected = false;
          silencedetected  = false;
          hahatime = Date.now();
          console.log('haha');
          laugh();
        }
      }
      window.requestAnimationFrame(onFrame);
    };
    window.requestAnimationFrame(onFrame);

To produce the laughter, we use a set of mp3 files from freesound that we post to the audioContext that we get from the browser environment.

    const source = audioContext().createBufferSource();
    let type = weightedrandom([ 1, 1, 10 ]);
    let num = Math.floor(Math.random() * laughurls[type].length);

    source.buffer = laughbuffers[type][num];
    source.connect(audioContext().destination);
    source.start();

    laughing = true;
    setTimeout(function () {
      laughing = false;
    }, source.buffer.duration * 1000);

    currentlaughtype = type;
    currentlaughnum = num;

We also add a bit of randomess in there, since we don’t want to repeat the same laugh too often.

And to spice things up a little, we add a few emojis that show the state of the laughter (if it is laughing, or booing, or not doing anything at the moment). Also, a very neat oscilloscope by matthiasvr allow us to see what the microphone is currently hearing.

Then we package it up with a neat mobile frontend from framework7.

Rewriting the code with ChatGPT

I wrote the personal laughtrack code back in 2022, which was before ChatGPT was unleashed on the world. At the time, I was doing it the old way: by reading the documentation for the Web audio API and trying it out.

But in 2024, we don’t necessarily have to do it that way anymore, since we can get generative AI to help us. And ChatGPT does a pretty good job of providing a clear and simple example of how to use the Web audio API when asked:

// Request access to the microphone
navigator.mediaDevices.getUserMedia({ audio: true })
    .then(stream => {
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();
        const microphone = audioContext.createMediaStreamSource(stream);
        const analyser = audioContext.createAnalyser();

        analyser.fftSize = 256;
        const bufferLength = analyser.frequencyBinCount;
        const dataArray = new Uint8Array(bufferLength);

        microphone.connect(analyser);

        let silenceStart = null;
        let silenceTimeout = 2000; // 2 seconds of silence
        let soundDetected = false;

        function detectSound() {
            analyser.getByteFrequencyData(dataArray);

            let sum = 0;
            for (let i = 0; i < bufferLength; i++) {
                sum += dataArray[i];
            }
            const average = sum / bufferLength;

            if (average > 10) { // Threshold for sound detection
                soundDetected = true;
                silenceStart = null;
            } else if (soundDetected) {
                if (silenceStart === null) {
                    silenceStart = Date.now();
                } else if (Date.now() - silenceStart > silenceTimeout) {
                    playLaughSound();
                    soundDetected = false;
                    silenceStart = null;
                }
            }

            requestAnimationFrame(detectSound);
        }

        detectSound();

    })
    .catch(err => {
        console.error('Error accessing microphone: ', err);
    });

function playLaughSound() {
    const laughSound = document.getElementById('laughSound');
    laughSound.play();
}

In fact, the explanation provided by ChatGPT is better than the explanation that I have given here.

Conclusions

I recently rediscovered this little project I did back in 2022 and thought it would be fun to review it today, in light of how generative AI has changed how we prototype ideas. If I had done this little project today, I would most certainly have been helped by ChatGPT (or similar generative AI tools).

In any case, the portable laugh track is in itself a pretty fun thing to play around with for a while, even though it quickly can become somewhat annoying.

Adam Dunkels, PhD
Written by Adam Dunkels, PhD
Who am I?