This is the ChucK source code. There's also a recording of the program in action, with the input (room recording) panned to the left channel and the output (generated noise) to the right.
I often run into the problem of trying to study in an environment with many auditory distractions. The interesting thing is that the environment is not continuously noisy, but instead the noise rises and falls as people and conversations come and go.
As far as I know, traditional noise-canceling headphones have trouble dealing with such non-repetitive noise patterns. The alternative of just blasting some sort of music or noise into the headphones is far from ideal as well, since
a) the music itself can be distracting, and
b) constant loud noise quickly causes listener fatigue.
The system uses input from a microphone and analyzes the noise in the room. That information is used to control various parameters of a noise synthesized by the program and sent to the user's headphones. The synthesized noise is smooth and care is taken that it never changes it abruptly . That is key, since it is what makes the masking noise non-distracting and therefore preferable to the room noise.
The system is adaptive both in frequency and in time: the noise it generates gets louder or softer depending on the room's noise level. Also, the noise's spectral composition matches the room's. That allows the program to minimize the disturbance to the user: noise is generated only for the frequencies and times where it is required.
The project is implemented as a ChucK program. The diagram above shows the signal flow. Here is a detailed description:
An input signal is acquired using a microphone
The microphone signal is fed into a set of eight band pass filters (BPF) in parallel. The filters are tuned to the frequencies 80, 150, 300, 600, 1200, 2400, 4800 and 9600 Hz.
Each BPF is connected to a separate envelope follower.
Every 20ms the energy of each envelope follower energy is examined, and if its level exceeds a threshold (i.e. a peak has been detected), that peak's energy is stored.
Every 1 second, the average energy of the peaks is calculated. This energy is fed into another leaky integrator which tracks the energy of peaks over time and stores it in the variable “grossEnvelope”. There is a separate grossEnvelope for each band.
Every 1 second, another shred examines all “grossEnvelopes” and uses them to control the gain of the corresponding BPF on the synthesis side. In order to make the gain change smooth, there is a third leaky integrator responsible for smoothly changes to the new gain level. See interpTargetLevel() in the code.
On the synthesis side we start from a white noise generator.
The white noise is fed into eight BPF's tuned to the same frequencies as on the analysis side. However, the Q's of these filter are set to a higher value. That was done so that the generated noise would have a somewhat tonal quality, which makes it more pleasant to listen to.
The gain of the BPFs is controlled by the analysis of the room noise, as described above
The output of the BPFs is sent to the user's headphones.
The reason three levels of leaky integration were needed was to ensure that all types of changes in the generated signal were as smooth as possible, so that they would not form a distraction to the user.
Another challenge was to keep the program sensitive enough to pick up noises in the entire room while not over-responding to noises the user makes (e.g. typing, tapping the table). It was usually possible to handle this by placing the microphone as far away from the user as possible, making it point away, and tuning the input sensitivity of the audio interface.