monogram with initials UKR

Recreating the THX Deep Note in JavaScript

Updated: Under: projects Tags: #DSP

THX celebrates their 35th anniversary somewhere in this week. Short bit of history THX’s inception in 1983 was to make sure that the soundtrack of StarWars Return Of The Jedi be reproduced and heard the way it should be apart from being scared the shit out in theaters.

Dr. James A. Moorer former employee of Lucas films computer division was tasked with creating the sound for THX. Which now is one of the most iconic sounds that the masses associate THX with. Not just a sound, the sound it self has become a pop culture icon, IE- the famous Simpsons skit.

Recreation time

Enough yapping around, Let’s get to specs on creating the Deep Note. United States Patent and Trademark Office to the rescue as the trademark document contains basic details on the sound but not the note itself.

The THX logo theme consists of 30 voices over seven measures, starting in a narrow range, 200 to 400 Hz, and slowly diverting to preselected pitches encompassing three octaves. The 30 voices begin at pitches between 200 Hz and 400 Hz and arrive at pre-selected pitches spanning three octaves by the fourth measure. The highest pitch is slightly detuned while there are double the number of voices of the lowest two pitches.

“preselected pitches” eh? Today THX Offical Twitter put out a tweet with the score of the piece. I know one can easily do a spectrogram and on the note and determine the pitches, but this image inspired me to recreate the sound with the details give in the score.

THX Deep Note Score
Image 1: THX Deep Note Score

That’s much clear, I’m terrible at reading sheet music but this seemed fairly simple. With the aid of Tone.js I was easily able to recreate the Deep Note in JavaScript. though it sound like the chord is off but the general feel of it is there.

THX Demo

Please lower your volume, before clicking the button

Well that turned out okay, the code is a bit hacky and messy but gets the job done. Need to fix those pitches tho.

Source Code


var filter = new Tone.Filter(150, "lowpass");
var mixer = new Tone.Volume(-250).chain(filter,Tone.Master);

class THXOsc {
    constructor(pitch, n, vol) {
        this.osc = []
        this.freq = pitch
        this.drift = new Tone.CtrlRandom(200, 400)
        for (var i = 0; i <= n; i++) {
            var start_frq = this.drift.value;
            var o = new Tone.Oscillator(start_frq, "sawtooth32");
            o.detune.value = (Math.random() * 10) + 0;
            o.volume.value = vol;
            this.osc.push(o)
        }
    }
    makeRamp(osc) {
        osc.frequency.linearRampTo(this.drift.value, 3);
        osc.frequency.linearRampTo(this.drift.value, 2, 5);
        osc.frequency.linearRampTo(this.freq, 7, 5);
    
    }
    start() {
        for (osc of this.osc) {
            this.makeRamp(osc);
            var pan = new Tone.Panner(new Tone.CtrlRandom(0, 1).value)
            osc.chain(pan, mixer).start();
        }
    }
    stop() {
        for (osc of this.osc) {
            osc.stop();
        }
    }
}

OSC_POOL = []
TARGET_THREE = ["A3", "D3", "A4", "D4", "A5", "D5", "A5", "F#5"];
TARGET_TWO = ["A1", "D1", "D0"];    

function generatePitches(){
    OSC_POOL = [];

    OSC_POOL = OSC_POOL.concat(TARGET_THREE.map((str) => {
        return new THXOsc(Tone.Frequency(str).toFrequency(), 3, 0);
    }))
    
    OSC_POOL = OSC_POOL.concat(TARGET_TWO.map((str) => {
        return new THXOsc(Tone.Frequency(str).toFrequency(), 2, 5);
    }))
}

function startSound(){
    mixer.volume = -100;
    filter.frequency = 150;
    for (osc of OSC_POOL) {
        osc.start();
    }
    mixer.volume.linearRampTo(-30,5);
    filter.frequency.linearRampToValueAtTime(
        Tone.Frequency("A8").toFrequency(),5
    );
    mixer.volume.linearRampTo(-100,4,15);
}

function stopSound(){
    mixer.volume = -100;
    for (osc of OSC_POOL) {
        osc.stop();
    }
}

$(document).ready(function() {
    $('#start').click(function(){
        stopSound();
        generatePitches();
        startSound();
    });
});