Sound Masking demo

Author: Kees van den Doel


Generate two sine waves and white noise to experiment with masking. Keep the first tone at decibel level 0 and at some fixed frequency. Then see how soft the softest tone 2 needs to be to be just barely audible as a function of frequency.

The result is called the masking threshold, and is a complicated function of the second frequency, and also of the absolute level and frequency of tone 1. So if you crank up your speakers you should get different results! The result has provided audiologists deep insights in the functioning of the ear. It is thought to originate in the non-linear excitations of the basilar membrane, a drum-like membrane in the inner ear which resonates with sounds coming into your ear. Different sounds generate different patterns in this membrane. Sensors are attached at various places to the basilar membrane and generate currents when it moves. Those currents then excite braincells, which excite other braincells, and eventually you'll experience the sound of Bachs Cantata 106, fingernails on a blackboard, crickets in the night, etc.

You can also use this applet to measure noise-tone masking. Set the white noise to some intermeditate level and see how loud a tone must be to be just audible. For frequencies around 440 Hz or so this should be at a difference of about 17 dB for most people. For higher tones the threshold is higher. Of course your computer speakers and soundcard are most likely not calibrated, so you'll get inaccurate results, but you can still get a feeling for the effect.

The masking threshold data are widely used in audio compression such as MPEG (mp3; NAPSTER!) to remove tones we supposedly can't hear anyways. No point downloading stuff you can't hear anyways!

Of course there are always people who claim to have better taste than others and they complain that the sounds that are eliminated are based on the average population, whereas they are much more disciminating and can hear stuff that the average Joe never gave a second thought. Some people believe these are "elitists", other people believe they are "right".

So try it for yourself...

Source code:

import jass.render.*;
import jass.engine.*;
import jass.generators.*;
import java.util.*;
import java.net.*;

/*
  Tones of the form:
  y = a*sin(2*pi*f*t)
  I(y) = a*a/2
  db = 10*log(a*a/(2*I0)) = 10*log(a*a/2) - 10*log(I0)

  noise of the form:
  y = a * rnd(t), rnd white noise of bandwidth Fb
  I(y) = a*a/3
  D(y) = I(y)/Fb
  Level l(y) = 10*log(a*a/(3*Fb*I0)) = 10*log(a*a/(3*Fb)) - 10*log(I0)

  A tone can be heard when its db level is 17 above the density level
  of white noise in these units.
  We've set I0 = 1, arbitrary.
 */
public class MaskingApplet extends AppletController {

    float dbMax=0,dbMin=-100,lMax = -50,lMin = -100;
    float f1=1200,f2=400,a1,a2,db1=dbMax,db2=dbMin,a_n,l_n=lMax;
    String sine50Hz44100 = "../data/sin20ms.wav";
    float baseFreqWavFile = 50;
    float srate = 44100.f;
    
    SourcePlayer player;
    LoopBuffer tone1,tone2;
    RandOut randOut;
    Mixer mixer;
    int nSources = 3; // 2 tones + noise

        
    public void setNSliders() {
        nsliders = 5;
    }

    public void setNButtons() {
        nbuttons = 2;
    }

    // Convert db to amp for tone
    public double db2a(double db) {
        return Math.sqrt(2)*Math.exp(db*Math.log(10)/20);
    }

    // Convert density level to amp for white noise
    public double level2a(double lev) {
        return Math.sqrt(3*srate/2)*Math.exp(lev*Math.log(10)/20);
    }
    
    public void init() {
        super.init();
        sine50Hz44100 = getParameter("wavfile");
    }

    public void start() {
        int bufferSize = 1024;
        int bufferSizeJavaSound = 8*1024;
        URL codebase = getCodeBase();
        URL wavurl = null;
        try {
            wavurl = new URL(codebase,sine50Hz44100);
        } catch(MalformedURLException e) {
            System.out.println(e+" Malformed URL: " +codebase+" "+ sine50Hz44100);
        }
        tone1 = new LoopBuffer(srate,bufferSize,wavurl);
        tone2 = new LoopBuffer(srate,bufferSize,wavurl);
        randOut = new RandOut(bufferSize);
        mixer = new Mixer(bufferSize,nSources);
        player = new SourcePlayer(bufferSize,bufferSizeJavaSound,srate);
        try {
            player.addSource(mixer);
            mixer.addSource(tone1);
            mixer.addSource(tone2);
            mixer.addSource(randOut);
        } catch(Exception e) {
        }
        // Add control panel
        String[] names = {"Freq 1 ",
                          "Level (db) 1 ",
                          "Freq. 2 ",
                          "Level (db) 2 ",
                          "Noise density level (db/Hz) "
        };
        double[] val =   {f1,
                          db1,
                          f2,
                          db2,
                          l_n
        };
        double[] min =   {50,
                          dbMin,
                          50,
                          dbMin,
                          lMin
        };
        double[] max =   {srate/2,
                          dbMax,
                          srate/2,
                          dbMax,
                          lMax
        };
        tone1.setSpeed(f1/baseFreqWavFile);
        tone2.setSpeed(f2/baseFreqWavFile);
        a1 = (float)db2a(db1);
        a2 = (float)db2a(db2);
        a_n = (float)level2a(l_n);
        mixer.setGain(0,a1);
        mixer.setGain(1,a2);
        mixer.setGain(2,a_n);
        setValues(val,min,max,names);
        jButton[0].setText ("Reset");
        jButton[1].setText ("Mute");
        //show();
        player.start();
    }

    protected void onSlider(int k) {
        switch(k) {
        case 0:
            f1 = (float)this.val[0];
            tone1.setSpeed(f1/baseFreqWavFile);
            break;
        case 1:
            db1 = (float)this.val[1];
            a1 = (float)db2a(db1);
            mixer.setGain(0,a1);
            break;
        case 2:
            f2 = (float)this.val[2];
            tone2.setSpeed(f2/baseFreqWavFile);
            break;
        case 3:
            db2 = (float)this.val[3];
            a2 = (float)db2a(db2);
            mixer.setGain(1,a2);
        case 4:
            l_n = (float)this.val[4];
            a_n = (float)level2a(l_n);
            mixer.setGain(2,a_n);
        }
    }
    
    protected void jButtonMousePressed (int k,java.awt.event.MouseEvent evt) {
        switch(k) {
        case 0:
            player.resetAGC();
            break;
        case 1:
            boolean muted = player.getMute();
            player.setMute(!muted);
            if(muted) {
                jButton[1].setText ("Mute");
            } else {
                jButton[1].setText ("Unmute");
            }
            break;
        } 
    }   
    
}