From: Philip Howard Date: Wed, 5 Feb 2020 14:01:24 +0000 (+0000) Subject: Merge pull request #33 from pimoroni/noise X-Git-Url: https://zdv2.bktei.com/gitweb/EVA-2020-02-2.git/commitdiff_plain/02c9f287f5963f5221b23fb05c2925eb261ca2de?hp=a503cc8c555b9de26e738f049e4865ba5f6891ef Merge pull request #33 from pimoroni/noise Noise library and examples for basic FFT and frequency binning --- diff --git a/examples/noise-amps-at-freqs.py b/examples/noise-amps-at-freqs.py new file mode 100755 index 0000000..8b1ddd5 --- /dev/null +++ b/examples/noise-amps-at-freqs.py @@ -0,0 +1,44 @@ +import ST7735 +from PIL import Image, ImageDraw +from enviroplus.noise import Noise + +print("""noise-amps-at-freqs.py - Measure amplitude from specific frequency bins + +This example retrieves the median amplitude from 3 user-specified frequency ranges and plots them in Blue, Green and Red on the Enviro+ display. + +As you play a continuous rising tone on your phone, you should notice peaks that correspond to the frequency entering each range. + +Press Ctrl+C to exit! + +""") + +noise = Noise() + +disp = ST7735.ST7735( + port=0, + cs=ST7735.BG_SPI_CS_FRONT, + dc=9, + backlight=12, + rotation=90) + +disp.begin() + +img = Image.new('RGB', (disp.width, disp.height), color=(0, 0, 0)) +draw = ImageDraw.Draw(img) + + +while True: + amps = noise.get_amplitudes_at_frequency_ranges([ + (100, 200), + (500, 600), + (1000, 1200) + ]) + amps = [n * 32 for n in amps] + img2 = img.copy() + draw.rectangle((0, 0, disp.width, disp.height), (0, 0, 0)) + img.paste(img2, (1, 0)) + draw.line((0, 0, 0, amps[0]), fill=(0, 0, 255)) + draw.line((0, 0, 0, amps[1]), fill=(0, 255, 0)) + draw.line((0, 0, 0, amps[2]), fill=(255, 0, 0)) + + disp.display(img) diff --git a/examples/noise-profile.py b/examples/noise-profile.py new file mode 100755 index 0000000..1afdff5 --- /dev/null +++ b/examples/noise-profile.py @@ -0,0 +1,40 @@ +import ST7735 +from PIL import Image, ImageDraw +from enviroplus.noise import Noise + +print("""noise-profile.py - Get a simple noise profile. + +This example grabs a basic 3-bin noise profile of low, medium and high frequency noise, plotting the noise characteristics as coloured bars. + +Press Ctrl+C to exit! + +""") + +noise = Noise() + +disp = ST7735.ST7735( + port=0, + cs=ST7735.BG_SPI_CS_FRONT, + dc=9, + backlight=12, + rotation=90) + +disp.begin() + +img = Image.new('RGB', (disp.width, disp.height), color=(0, 0, 0)) +draw = ImageDraw.Draw(img) + + +while True: + low, mid, high, amp = noise.get_noise_profile() + low *= 128 + mid *= 128 + high *= 128 + amp *= 64 + + img2 = img.copy() + draw.rectangle((0, 0, disp.width, disp.height), (0, 0, 0)) + img.paste(img2, (1, 0)) + draw.line((0, 0, 0, amp), fill=(int(low), int(mid), int(high))) + + disp.display(img) diff --git a/library/enviroplus/noise.py b/library/enviroplus/noise.py index e69de29..2e7472d 100644 --- a/library/enviroplus/noise.py +++ b/library/enviroplus/noise.py @@ -0,0 +1,90 @@ +import sounddevice +import numpy + + +class Noise(): + def __init__(self, + sample_rate=16000, + duration=0.5): + """Noise measurement. + + :param sample_rate: Sample rate in Hz + :param duraton: Duration, in seconds, of noise sample capture + + """ + + self.duration = duration + self.sample_rate = sample_rate + + def get_amplitudes_at_frequency_ranges(self, ranges): + """Return the mean amplitude of frequencies in the given ranges. + + :param ranges: List of ranges including a start and end range + + """ + recording = self._record() + magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate)) + result = [] + for r in ranges: + start, end = r + result.append(numpy.mean(magnitude[start:end])) + return result + + def get_amplitude_at_frequency_range(self, start, end): + """Return the mean amplitude of frequencies in the specified range. + + :param start: Start frequency (in Hz) + :param end: End frequency (in Hz) + + """ + n = self.sample_rate // 2 + if start > n or end > n: + raise ValueError("Maxmimum frequency is {}".format(n)) + + recording = self._record() + magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate)) + return numpy.mean(magnitude[start:end]) + + def get_noise_profile(self, + noise_floor=100, + low=0.12, + mid=0.36, + high=None): + """Returns a noise charateristic profile. + + Bins all frequencies into 3 weighted groups expressed as a percentage of the total frequency range. + + :param noise_floor: "High-pass" frequency, exclude frequencies below this value + :param low: Percentage of frequency ranges to count in the low bin (as a float, 0.5 = 50%) + :param mid: Percentage of frequency ranges to count in the mid bin (as a float, 0.5 = 50%) + :param high: Optional percentage for high bin, effectively creates a "Low-pass" if total percentage is less than 100% + + """ + + if high is None: + high = 1.0 - low - mid + + recording = self._record() + magnitude = numpy.abs(numpy.fft.rfft(recording[:, 0], n=self.sample_rate)) + + sample_count = (self.sample_rate // 2) - noise_floor + + mid_start = noise_floor + int(sample_count * low) + high_start = mid_start + int(sample_count * mid) + noise_ceiling = high_start + int(sample_count * high) + + amp_low = numpy.mean(magnitude[self.noise_floor:mid_start]) + amp_mid = numpy.mean(magnitude[mid_start:high_start]) + amp_high = numpy.mean(magnitude[high_start:noise_ceiling]) + amp_total = (low + mid + high) / 3.0 + + return amp_low, amp_mid, amp_high, amp_total + + def _record(self): + return sounddevice.rec( + int(self.duration * self.sample_rate), + samplerate=self.sample_rate, + blocking=True, + channels=1, + dtype='float64' + ) diff --git a/library/setup.cfg b/library/setup.cfg index ed7cef7..c48c7ba 100644 --- a/library/setup.cfg +++ b/library/setup.cfg @@ -33,6 +33,7 @@ install_requires = ltr559 st7735 ads1015 + sounddevice [flake8] exclude = @@ -51,13 +52,16 @@ py2deps = python-numpy python-smbus python-pil + libportaudio2 py3deps = python3-pip python3-numpy python3-smbus python3-pil + libportaudio2 configtxt = dtoverlay=pi3-miniuart-bt + dtoverlay=adau7002-simple commands = printf "Setting up i2c and SPI..\n" raspi-config nonint do_spi 0