You are on page 1of 8

DATE ​October 13, 2017

TO ​LeAnne Dourte
FROM ​Jaimie Carlson, Jane Shmushkis
SUBJECT ​Lab A: Audio Filtering

ANALYSIS
We computationally and physically filtered a recording of a fetal heartbeat, overlaid with violet noise, to
extract the frequency of a heartbeat and make an LED light up in time with it.

Matlab Initial Signal Analysis:


We gathered time series data of the sound from the myDAQ board into Matlab (Appendix A). An fast
Fourier transform (FFT) algorithm was used to initially survey its frequencies, and we discerned a cutoff
between the heartbeat sounds (<2000 Hz) and the violet noise (increasing > 2000 Hz). To isolate the
bass frequencies (60-200Hz) of the noise we wanted, we ran a computational low-pass Butterworth filter
on the signal in Matlab, starting at 200 Hz. After this, we still noticed an offset of the time series data from
0, so a high pass filter was added to remove this. The time and frequency series were compared before
and after for these signals. As new low-pass and high-pass filter frequencies were tested experimentally
on the circuits, they were tested in Matlab (Figure 1).

Figure 1: ​Matlab Signal Processing: Time-series data and Fourier transform before and after filtering. (a)
Initial design: 160 Hz Low-pass filter, 70 Hz High-pass filter; both with Q=0.707; (b) Final design: 150 Hz
Low-pass filter; 1 Hz High-pass filter; both with Q=1

Analog Filter, Gain, and Envelope Detector:


A circuit was constructed (Figure 2) with the following values:
Circuit R1 (kΩ) R2 (kΩ) C1 (μF) C2 (μF) Targeted Values Actual Values

Low-Pass 33 15+1=16 0.1 0.022 f = 150 Hz f = 147.7 Hz


Filter (in series) Q=1 Q = 1.00

High-Pass 33 5.6+2.7=8.3 10 10 f = 1 Hz f = 0.962 Hz


Filter (in series) Q=1 Q = 0.997
Gain 1 22+2=24 Gain = 25 Gain = 15-20
(in series)

Envelope 470 0.1 T = RC = 0.470 s;


Detector f =21.27 Hz
Table 1:​ Circuit parameters

Figure 2​: Final audio filtering and LED blinking circuit

A function generator was used to test the circuit and tune parameters of the low-pass, high-pass, and
gain circuits. Figure 3 shows the final results.
Figure 3: ​Circuit-testing frequencies for a circuit with a high-pass filter of 1 Hz and a low-pass filter of 150
Hz. (a) 0.5 Hz frequency: below band, is filtered out (relative to gain placed on other frequencies) (b) 1
Hz: edge of accepted band is kept and amplified (c) 25 Hz: inside accepted band is kept and amplified (d)
60 Hz: inside accepted band is kept and amplified (e) 150 Hz: edge of accepted band is kept and
amplified (f) 2000 Hz: above band, is filtered out.

For the envelope detector, we first tested a circuit with a time constant of 0.470, since that fell between
the inverses of our carrier and modulation frequencies of 1 and 50 Hz respectively. It seemed like the
capacitor was discharging too quickly. To address this, we increased our capacitor values, but noticed
that the smooth discharge prevented us from setting a distinct, single cutoff voltage at which the Arduino
would turn on the LED. We went back to our initial envelope detector, predicting the cutoff voltage at
which the LED would turn on to be around 0.6 V to 1.2 V (Figure 4).

We used the findPeaks() function to identify the exact voltage threshold that picked up the most
appropriately-spaced peaks and corresponded to the heartbeat pace we were hearing (Appendix 2).
Looking at the plot of what peaks the function identified as above the threshold, we noticed there was
sometimes a voltage that was still above the threshold immediately after a peak of a higher voltage value.
To only have the Arduino light the LED at the higher peak and ensure the peaks were far enough apart,
we added a MinPeakDistance parameter to the findPeaks() function, tweaking it around a starting value of
0.33 since we knew the heartbeat was happening about three times per second from listening to it. The
MinPeakHeight (which corresponded to the voltage threshold for LED lighting) and the MinPeakDistance
were tuned until about ninety evenly-spaced peaks were identified (Figure 4).

Figure 4:​ Peaks identified in filtered data with the findPeaks() function. The output data that we ran the
function on was collected while connected to Arduino to eliminate the effect of voltage variation and
voltage threshold changes upon Arduino connection.

Arduino Processing:
We tuned the thresholds for voltage cutoff and timing cutoff by processing data in Matlab as described
above (Appendix 2) and using the MinPeakHeight and MinPeakDistance parameters of findPeaks()
function. These parameters went into Arduino code (Appendix 3) which read in the filtered signal and
blinked the LED when an above-threshold voltage was detected and another signal had not appeared in
the too-recent past. This was done by keeping track of the current loop time, comparing it to the previous
peak time that turned the LED on, and storing the newest peak time for the following comparison. We
added a time delay of 40 milliseconds to make the LED blind more noticeably with each peak.

DISCUSSION
We faced several challenges while completing this project. First, during signal analysis, we did not play
the recording at a loud enough volume to allow for clear analysis. Then, we did not correctly set the
sampling rate on the myDAQ. We used the default rate value of 500, which does not allow the capturing
of frequencies above 250 Hz based on the Nyquist frequency. So, we corrected that by allowing a higher
sampling rate and accounting for the sampling rate mathematically in our Butterworth filter.

When setting up our actual circuit, we spent some time tweaking R and C values in order to tune the
parameters of our low- and high-pass filters. Because of the non-ideality of filters, we did not get a perfect
cutoff of higher frequencies, which occurred at such great amplitudes that they could still get past the filter
at times. Notably, there were some very small peaks recurring at evenly-spaced intervals high
frequencies which appeared in the actual but not theoretical plot. We did not determine the cause of these
peaks but decided their overall effect was negligible compared to the low-frequency peaks. Initially, we
had our filter set with a high-pass threshold of around 60 Hz, the lower limits of “bass noise.” However, we
thought this might filter out too much when our minimal intention was to filter out < 1 Hz baseline drift, so
we lowered the high-pass threshold to 1 Hz. When our theoretical filter cutoff was too different from our
actual filter cutoff, we gradually increased Q from 0.5 to 0.707 to 1. Q = 1 had the sharpest cutoff.
Although it increased the amplitudes of frequencies right at the band’s edge, this did not matter especially
for our purposes, since we just wanted to cut out all frequencies outside the band. Thus, increasing Q
helped. In addition, we had to pick the correct time constant for our envelope detector, making sure it was
well within the time constants of our carrier frequency (60 Hz; T = 1/60 s) and our intended frequency (4
Hz; T = ¼ s), which it initially was not far enough between.

As can be seen in Figure 3, our theoretical gain of 25 differed from our actual gain of 15-20. We initially
thought this could possibly be due to the limits of the op amp, which could generate +- 12 V. This is
because when we tested with the function generator, a Bode plot for waves with 1 V peak amplitude had
22 gain within the pass band, while a Bode plot for waves with 2 V peak amplitude had a lower gain of 16
within the pass band; we initially thought that the amplification of a 2 V signal would run out of the
operational amplifier’s bounds more than a 1 V signal. However, the initial auditory signal had an
amplitude bounded by 0.2 V, so 25*0.2V = 5 V would still be well within the limit.

Picking a correct interval for the Arduino to pause between peaks was initially done by trial and error.

The Arduino pause time creates extra calculation for the Arduino and is slightly inelegant; future work
would seek to use only a voltage threshold instead of a time threshold requiring a certain delay since the
last peak, but it was necessary at the current stage due to paired peaks.

APPENDIX
Appendix A: Matlab Code for Acquiring Data
devices = daq.getDevices
s = daq.createSession('ni')
addAnalogInputChannel(s, 'myDAQ1', 0, 'Voltage')
s.DurationInSeconds = 15;
s.Rate = 100000;

Fs = s.Rate;
T = 1/Fs;
L = s.DurationInSeconds * Fs;
t = (0:L-1)*T;
[data, time] = s.startForeground;
figure;

numberOfPlots = 4;

%Plot data versus time


subplot(numberOfPlots, 1, 1);
plot(time, data);
title('Original Data of Heartbeat Ultrasound')
xlabel('Time (s)')
ylabel('Voltage (V)')

%Plot FFT of data


subplot(numberOfPlots, 1, 2);
Y = fft(data);
P2 = abs(Y/L);
P1 = P2(1:L/2+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(L/2))/L;
plot(f,P1)
title('Single-Sided Amplitude Spectrum of Original Data')
xlabel('f (Hz)')
ylabel('|P1(f)|')

%Cutoff frequency at 200 Hz to start


fc_low = 70;
fc_high = 160;
cutoff_low = fc_low/(Fs/2);
cutoff_high = fc_high/(Fs/2);

n = 2;

%Plot the new filtered data


subplot(numberOfPlots, 1, 3);
[b, a] = butter(n, [cutoff_low cutoff_high]);
filtered_data = filter(b, a, data);
plot(time, filtered_data);
title('Filtered Data of Heartbeat Ultrasound')
xlabel('Time (s)')
ylabel('Voltage (V)')

%Plot FFT of the new filtered data


subplot(numberOfPlots, 1, 4);
Y = fft(filtered_data);
P2 = abs(Y/L);
P1 = P2(1:L/2+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(L/2))/L;
plot(f,P1)
title('Single-Sided Amplitude Spectrum of Filtered Data)')
xlabel('f (Hz)')
ylabel('|P1(f)|')

%Plot Bode plot of the Butterworth filter


figure;
freqz(b,a)

Appendix B: Matlab Code for Testing on Circuit Output to Determine Timing and Voltage Thresholds
devices = daq.getDevices
s = daq.createSession('ni')
addAnalogInputChannel(s, 'myDAQ1', 1, 'Voltage')
s.DurationInSeconds = 35;
s.Rate = 100000;

Fs = s.Rate;
T = 1/Fs;
L = s.DurationInSeconds * Fs;
t = (0:L-1)*T;
[data, time] = s.startForeground;
figure;
plot(time, data);
title('Circuit-Filtered Data of Heartbeat Ultrasound')
xlabel('time (s)')
ylabel('Voltage (V)')

%Plot FFT of data


figure;
Y = fft(data);
P2 = abs(Y/L);
P1 = P2(1:L/2+1);
P1(2:end-1) = 2*P1(2:end-1);
f = Fs*(0:(L/2))/L;
plot(f,P1)
title('Single-Sided Amplitude Spectrum of Circuit-Filtered Data')
xlabel('f (Hz)')
ylabel('|P1(f)|')

%Find peaks for Arduino


[pks,loc]=findpeaks(data,Fs,'MinPeakHeight',.66,'MinPeakDistance',.27)
title('Times that met conditions for LED to light up')
xlabel('time (seconds)')
ylabel('Voltage (V)')

Appendix c: Arduino Code


int digitalOut = 12;
double thresh = .66;

double timeThreshMillis = 0.27*1000.0;


double timeSinceLastPeak = 0.0;
double lastPeakTime = 0.0;
double currTime;

void setup() {
pinMode(digitalOut, OUTPUT);
pinMode(A0, INPUT);

Serial.begin(9600);

}
void loop() {
currTime = millis();
timeSinceLastPeak = currTime - lastPeakTime;
double heartbeat = analogRead(A0);
double voltageHeartbeat = heartbeat*0.0049; //0.0049 mV / unit to map between
0-1023 input to 0-5V
if (voltageHeartbeat >= thresh && timeSinceLastPeak > timeThreshMillis) {
Serial.println(voltageHeartbeat);
digitalWrite(digitalOut, HIGH);
Serial.println(timeSinceLastPeak);
timeSinceLastPeak = 0.0;
lastPeakTime = currTime;
delay(40);
} else {
digitalWrite(digitalOut, LOW);
}
}

You might also like