RainBox: truly random "white noise" as a sleep aid
Goal : Generate random "white" noise to assist with sleeping in a standalone "rainbox"
Specifications/Research :
Living in an apartment, the usage of rain or ocean sounds has become a necessity to get a good night's sleep. Sometimes, I use it to focus while studying just to drown out any background noise. After taking ECE 251a: statistical signal processing at UCSD, I got the idea to design a white noise filter to simulate the sound of rain. After ECE 250: random processes, I realized I could generate white noise samples with uniformly distributed ones.
Link to code and other materials:
https://github.com/lwittemann/rainBox
Progress:
- 02-15-25: The first and most core part of this project is generating white noise in an efficient manner. Given that I was planning to implement this algorithm on an Arduino, I knew that Arduinos had the native ability to generate uniformly random samples. Subsequently, I used MATLAB to investigate generating zero mean, unit-variance Gaussian random variables using random variables that are uniformly distributed from 0 to 1. In theory, the standard method to do this is to use a scaled version of the inverse error function with the uniform random variable as an argument. For an actual implementation, I experimented with a couple of functions that approximate the inverse error function. These approximations provided very good results, but were still very computationally intensive, especially if used to generate normal samples one at a time. Given the nature of uniform random variables, I got the idea to use the uniform random variables as indices in a look up table (LUT) as opposed to inputs to an inverse error function approximation. For example, if I generate a 1000 value LUT of the inverse error function, I can convert a uniform sample ranging from 0 to 1 into a index ranging from 0 to 1000 using a simple multiplication and cast to integer. This means that I can bypass a complex calculation for each sample with a couple of multiplications and additions. This method was validated comparing a histogram of 60000 normal random variables with 60000 variables created using the uniform random variables and the LUT. The results are extremely promising, as the histograms match almost exactly. The only difference can be found in the tails of the distribution, which are events that are, by definition, quite unlikely anyway. For the final LUT, the values were scaled to range from -1.65 to 1.65 which corresponds to the 0 - 3.3V range of output voltages on the MCP 4725 DAC I will be using with the Arduino. I chose a LUT length of 512 samples to ensure the tails of the distribution were as well represented as possible. Another thing I experimented with was a quantized LUT which had only had integer values ranging from 0 - 4095, corresponding to possible outputs of the 12-bit DAC. In the end, I moved away from this because float-based math was still required for filtering.
Approximations of the standard and inverse error function
- 03-07-25: The second major part of this project is designing a filter which takes white noise as its input and outputs a colored noise which sounds as similar to rain as possible. I started by looking at the spectrum of rain. Compared to standard white noise, there is a spike around DC over a shelf that goes out to about 5 kHz. After that, there is very little frequency content. This is what led me to select a target sampling rate of 16 kHz. To create a filter with a similar magnitude response, I started by applying a moving average filter to the rain spectrum in order to smooth it. Then, this smoothed spectrum could be used to design a FIR filter using frequency sampling. To test the filter, I generated and filtered a couple second of white noise. I then tried a variety of filter lengths and found that a 20-tap filter was a good balance between computational efficiency and sounding very similar to rain.
- 03-20-25: To start with the actual implementation, I began interfacing with my MCP 4725 DAC over I2C using my Arduino nano every. During this stage, I realized that Arduino has a function which generates uniformly random integers in some range, meaning I wouldn't have to do any conversion from uniform random variables to indices. Next, I generated uniform and then white noise. This was verified using my oscilloscope, where you could clearly see the difference between the uniform and Gaussian noise. At this point, I also implemented a window-based filtering algorithm. To do this, I started by defining the length of my FIR filter, 20 taps in my case. I would then instantiate an array of this size and set all of its values to zero. This acts as a circular data window. I would also instantiate another array of the same size to hold the tap weights. During every sampling period, a new sample was calculated as follows. Starting at a position inside the data window, the window position, I would move in one direction, adding the product of the data window entry with the corresponding tap weight to the new sample. When I reached the end of the data window array, I would move back to the beginning, continuing until reaching the window position again. This new sample would then replace the oldest sample in the data window before being outputted to the DAC. Finally, the window position was incremented to prepare for the next sample. I implemented FIR filtering in this manner so that I could only had to perform one memory write per sampling period instead of 20 if directly implemented in the fashion of a shift register. This method was verified using a small data window and FIR filter before being moved to the full 20 tap filter.
- 03-23-25: Once I had the digital sample generation figured out, I started to focus on the analog circuitry. I first followed the DAC with a simple analog low pass which was buffered with a simple voltage follower. To remove aliasing, I added a 2nd order lowpass filter with a Sallen-Key topology designed using the TI filter design tool. After simulating and breadboarding this circuit, I noticed something odd. It seemed like the circuit was not removing all of the DC component from the DAC voltage transitions. I soon realized this was because the circuit was not operating anywhere near the sample rate I had hoped it would.
- 03-25-25: After doing numerous speed tests, I got some pretty disappointing results. To output samples at 16kHz, you need to have a sample ready every 62.5 microseconds. In my tests, I found that the calculation of each sample took around 450 microseconds and simply outputting a value to the DAC over I2C took almost 500 microseconds. At this point, I almost lost all hope and abandoned the project.
- 04-01-25: To see if the project could be salvaged, I started looking at a simpler IIR filter and a much lower but attainable sample rate, ~1800Hz. I designed a set of 2nd order IIR filters with the pole at ~200Hz, the lowest frequency that could be the outputted by the speaker I had chosen for the project. While it did not sound exactly like the rain I was initially going for, the results could still certainly be described as relaxing "white" noise. At this point, I would like to mention that technically, it should be referred to as colored noise but the popular terminology for any kind of random noise to relax or focus to is white noise. Next, I transitioned to a first order IIR filter because it was even simpler to implement and gave nearly identical results. After verifying that this setup could maintain a steady sample rate, I redesigned the analog circuitry for the new sample rate. To better simulate outputs from the DAC, I used a square wave input instead of a sine. This made me realize that I needed to step up the filter order from two to four in order to cleanly convert a Fs/2 square wave to the sine wave it was supposed to represent. The circuit starts with a high pass and potentiometer to remove the DC offset from the DAC and provide volume control. The signal is then buffered with a simple voltage follower before being run through a sixth order Bessel lowpass filter. The reason I did a sixth order filter when a fourth order would suffice is because the TL072 op amp's I had had two circuits in each package. I needed two circuits to implement the fourth order filter and one circuit for the buffer. So, I either would have had to leave a circuit unused, buy a single circuit op-amp, or add a couple extra passive components and increase the filter order. To make absolutely sure that aliasing would be removed, I decided to just step up the filter order to six. To remove the need for biasing, I designed the op amps to have +/-5V supply and elected to buy a DC-DC converter to make this happen. The filter circuitry is followed by a simple audio amplifier implemented using a LM386. The circuit for this amplifier is a slightly modified version of one from the TI datasheet for the LM386. After verifying the circuits operation with transient and AC simulations, I was ready to build it.
- 04-09-25: To finish the project up, I began assembling the circuit on a proto-board. The only issue I ran into was the DC-DC converter. I had believed the unit I had purchased created +/-5v referenced to the ground the converter was connected to. Unfortunately, it just produced 10v across the +/-5v terminals. This means I had to add a parallel resistor and capacitor to ground from each terminal such that the voltages were referenced to the circuit's ground. To make sure this DC value was stable and didn't drift, I tested the transient behavior over about 20 seconds for different values of this resistor/capacitor combo and settled on a 50k resistor in parallel with a 10uF capacitor. With this kink worked out, I assembled and tested the rest of the circuit. Now, I could see that both the DC component and the high frequency aliasing of the DAC output signal was being completely removed, resulting in a smooth signal before the amplifier. Finally, I added a power switch and barrel connector for the 12v power compatible with a basic 12v wall adapter. To hold the speaker and circuitry, I designed a two-part enclosure before 3d printing it. The top part holds the speaker and the bottom part holds the circuit board, switch, and power connection. I ended up adding a removable plate on the lower part such that the circuit could easily slide in. A final assembly and test showed that everything fit well and the box worked as expected! One thing I was worried about previously was the volume level, but I was delighted to find that the box could go more than loud enough to function as white noise.
Conclusion:
This was a really enjoyable project as it was a good intersection of things I have been learning at my graduate program at UCSD and a full mixed-signal chain I had been wanting to work with for a while. This was my first time using a microcontroller and DAC to create analog audio from digital audio, which was a difficult yet rewarding experience. If I were to re-do the project, I would have certainly used a more capable serial communication protocol such as SPI or I2S. I also would have used a more powerful microcontroller such as a Teensy 4.0 so that I would have had more headroom for more complex DSP. Another thing I would change is the usage of a DC-DC converter. I had never used one before so I was under the impression it would be simpler than biasing the circuit. Unfortunately, I had to do a form of biasing anyway with the +/-5v terminals anyway, so next time I would just properly bias the circuit to get away with a single supply voltage. All that being said, I have been using the completed box for the last couple of weeks and it has been extremely helpful for helping me go to and stay asleep!









Comments
Post a Comment