Catch #61 with tests and fix
[EVA-2020-02-2.git] / library / enviroplus / noise.py
1 import sounddevice
2 import numpy
3
4
5 class Noise():
6 def __init__(self,
7 sample_rate=16000,
8 duration=0.5):
9 """Noise measurement.
10
11 :param sample_rate: Sample rate in Hz
12 :param duraton: Duration, in seconds, of noise sample capture
13
14 """
15
16 self.duration = duration
17 self.sample_rate = sample_rate
18
19 def get_amplitudes_at_frequency_ranges(self, ranges):
20 """Return the mean amplitude of frequencies in the given ranges.
21
22 :param ranges: List of ranges including a start and end range
23
24 """
25 recording = self._record()
26 magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate))
27 result = []
28 for r in ranges:
29 start, end = r
30 result.append(numpy.mean(magnitude[start:end]))
31 return result
32
33 def get_amplitude_at_frequency_range(self, start, end):
34 """Return the mean amplitude of frequencies in the specified range.
35
36 :param start: Start frequency (in Hz)
37 :param end: End frequency (in Hz)
38
39 """
40 n = self.sample_rate // 2
41 if start > n or end > n:
42 raise ValueError("Maxmimum frequency is {}".format(n))
43
44 recording = self._record()
45 magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate))
46 return numpy.mean(magnitude[start:end])
47
48 def get_noise_profile(self,
49 noise_floor=100,
50 low=0.12,
51 mid=0.36,
52 high=None):
53 """Returns a noise charateristic profile.
54
55 Bins all frequencies into 3 weighted groups expressed as a percentage of the total frequency range.
56
57 :param noise_floor: "High-pass" frequency, exclude frequencies below this value
58 :param low: Percentage of frequency ranges to count in the low bin (as a float, 0.5 = 50%)
59 :param mid: Percentage of frequency ranges to count in the mid bin (as a float, 0.5 = 50%)
60 :param high: Optional percentage for high bin, effectively creates a "Low-pass" if total percentage is less than 100%
61
62 """
63
64 if high is None:
65 high = 1.0 - low - mid
66
67 recording = self._record()
68 magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate))
69
70 sample_count = (self.sample_rate // 2) - noise_floor
71
72 mid_start = noise_floor + int(sample_count * low)
73 high_start = mid_start + int(sample_count * mid)
74 noise_ceiling = high_start + int(sample_count * high)
75
76 amp_low = numpy.mean(magnitude[noise_floor:mid_start])
77 amp_mid = numpy.mean(magnitude[mid_start:high_start])
78 amp_high = numpy.mean(magnitude[high_start:noise_ceiling])
79 amp_total = (amp_low + amp_mid + amp_high) / 3.0
80
81 return amp_low, amp_mid, amp_high, amp_total
82
83 def _record(self):
84 return sounddevice.rec(
85 int(self.duration * self.sample_rate),
86 samplerate=self.sample_rate,
87 blocking=True,
88 channels=1,
89 dtype='float64'
90 )