code.py
1 # SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 """Waterfall FFT demo adapted from 6 https://teaandtechtime.com/fft-circuitpython-library/ 7 to work with ulab on Adafruit CLUE""" 8 9 import array 10 11 import board 12 import audiobusio 13 import displayio 14 from ulab import numpy as np 15 from ulab.scipy.signal import spectrogram 16 17 display = board.DISPLAY 18 19 # Create a heatmap color palette 20 palette = displayio.Palette(52) 21 for i, pi in enumerate((0xff0000, 0xff0a00, 0xff1400, 0xff1e00, 22 0xff2800, 0xff3200, 0xff3c00, 0xff4600, 23 0xff5000, 0xff5a00, 0xff6400, 0xff6e00, 24 0xff7800, 0xff8200, 0xff8c00, 0xff9600, 25 0xffa000, 0xffaa00, 0xffb400, 0xffbe00, 26 0xffc800, 0xffd200, 0xffdc00, 0xffe600, 27 0xfff000, 0xfffa00, 0xfdff00, 0xd7ff00, 28 0xb0ff00, 0x8aff00, 0x65ff00, 0x3eff00, 29 0x17ff00, 0x00ff10, 0x00ff36, 0x00ff5c, 30 0x00ff83, 0x00ffa8, 0x00ffd0, 0x00fff4, 31 0x00a4ff, 0x0094ff, 0x0084ff, 0x0074ff, 32 0x0064ff, 0x0054ff, 0x0044ff, 0x0032ff, 33 0x0022ff, 0x0012ff, 0x0002ff, 0x0000ff)): 34 palette[51-i] = pi 35 36 class RollingGraph(displayio.TileGrid): 37 def __init__(self, scale=2): 38 # Create a bitmap with heatmap colors 39 self._bitmap = displayio.Bitmap(display.width//scale, 40 display.height//scale, len(palette)) 41 super().__init__(self._bitmap, pixel_shader=palette) 42 43 self.scroll_offset = 0 44 45 def show(self, data): 46 y = self.scroll_offset 47 bitmap = self._bitmap 48 49 board.DISPLAY.auto_refresh = False 50 offset = max(0, (bitmap.width-len(data))//2) 51 for x in range(min(bitmap.width, len(data))): 52 bitmap[x+offset, y] = int(data[x]) 53 54 board.DISPLAY.auto_refresh = True 55 56 self.scroll_offset = (y + 1) % self.bitmap.height 57 58 group = displayio.Group(scale=3) 59 graph = RollingGraph(3) 60 fft_size = 256 61 62 # Add the TileGrid to the Group 63 group.append(graph) 64 65 # Add the Group to the Display 66 display.show(group) 67 68 # instantiate board mic 69 mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA, 70 sample_rate=16000, bit_depth=16) 71 72 #use some extra sample to account for the mic startup 73 samples_bit = array.array('H', [0] * (fft_size+3)) 74 75 # Main Loop 76 def main(): 77 max_all = 10 78 79 while True: 80 mic.record(samples_bit, len(samples_bit)) 81 samples = np.array(samples_bit[3:]) 82 spectrogram1 = spectrogram(samples) 83 # spectrum() is always nonnegative, but add a tiny value 84 # to change any zeros to nonzero numbers 85 spectrogram1 = np.log(spectrogram1 + 1e-7) 86 spectrogram1 = spectrogram1[1:(fft_size//2)-1] 87 min_curr = np.min(spectrogram1) 88 max_curr = np.max(spectrogram1) 89 90 if max_curr > max_all: 91 max_all = max_curr 92 else: 93 max_curr = max_curr-1 94 95 print(min_curr, max_all) 96 min_curr = max(min_curr, 3) 97 # Plot FFT 98 data = (spectrogram1 - min_curr) * (51. / (max_all - min_curr)) 99 # This clamps any negative numbers to zero 100 data = data * np.array((data > 0)) 101 graph.show(data) 102 103 main()