Commit | Line | Data |
---|---|---|
aa747a41 PH |
1 | import sounddevice |
2 | import numpy | |
4d6b7f96 | 3 | |
aa747a41 PH |
4 | |
5 | class Noise(): | |
4d6b7f96 PH |
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 | """ | |
aa747a41 PH |
15 | |
16 | self.duration = duration | |
17 | self.sample_rate = sample_rate | |
18 | ||
19 | def get_amplitudes_at_frequency_ranges(self, ranges): | |
4d6b7f96 PH |
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 | """ | |
aa747a41 PH |
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): | |
4d6b7f96 PH |
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 | """ | |
aa747a41 PH |
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 | ||
4d6b7f96 PH |
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 | """ | |
aa747a41 PH |
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 | ||
97ee1d88 | 76 | amp_low = numpy.mean(magnitude[noise_floor:mid_start]) |
aa747a41 PH |
77 | amp_mid = numpy.mean(magnitude[mid_start:high_start]) |
78 | amp_high = numpy.mean(magnitude[high_start:noise_ceiling]) | |
79 | amp_total = (low + mid + 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 | ) |