Once you've finished setting up your Feather nRF52840 Sense with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.

To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.

# SPDX-FileCopyrightText: 2021 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

'''Adapted from the FFT Example: Waterfall Spectrum Analyzer
by Jeff Epler
https://learn.adafruit.com/ulab-crunch-numbers-fast-with-circuitpython/overview '''

import array
import board
import audiobusio
import busio
from ulab import numpy as np
from ulab.scipy.signal import spectrogram
import adafruit_is31fl3741
from adafruit_is31fl3741.adafruit_rgbmatrixqt import Adafruit_RGBMatrixQT

#  Manually declare I2c (not board.I2C()) to access 1 MHz speed for
i2c = busio.I2C(board.SCL, board.SDA, frequency=1000000)
#  Declare is31 w/buffering preferred (low RAM will fall back on unbuffered)
is31 = Adafruit_RGBMatrixQT(i2c, allocate=adafruit_is31fl3741.PREFER_BUFFER)
#  In buffered mode, MUST use show() to refresh matrix (see line 94)

#  brightness for the RGBMatrixQT
#  set to about 20%
is31.set_led_scaling(0x19)
is31.global_current = 0x03
is31.enable = True

#  array of colors for the LEDs
#  goes from purple to red
#  gradient generated using https://colordesigner.io/gradient-generator
heatmap = [0xb000ff,0xa600ff,0x9b00ff,0x8f00ff,0x8200ff,
           0x7400ff,0x6500ff,0x5200ff,0x3900ff,0x0003ff,
           0x0003ff,0x0047ff,0x0066ff,0x007eff,0x0093ff,
           0x00a6ff,0x00b7ff,0x00c8ff,0x00d7ff,0x00e5ff,
           0x00e0ff,0x00e6fd,0x00ecf6,0x00f2ea,0x00f6d7,
           0x00fac0,0x00fca3,0x00fe81,0x00ff59,0x00ff16,
           0x00ff16,0x45ff08,0x62ff00,0x78ff00,0x8bff00,
           0x9bff00,0xaaff00,0xb8ff00,0xc5ff00,0xd1ff00,
           0xedff00,0xf5eb00,0xfcd600,0xffc100,0xffab00,
           0xff9500,0xff7c00,0xff6100,0xff4100,0xff0000,
           0xff0000,0xff0000]

#  size of the FFT data sample
fft_size = 64

#  setup for onboard mic
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       sample_rate=16000, bit_depth=16)

#  use some extra sample to account for the mic startup
samples_bit = array.array('H', [0] * (fft_size+3))

#  sends visualized data to the RGB matrix with colors
def waves(data, y):
    offset = max(0, (13-len(data))//2)

    for x in range(min(13, len(data))):
        is31.pixel(x+offset, y, heatmap[int(data[x])])

# main loop
def main():
    #  value for audio samples
    max_all = 10
    #  variable to move data along the matrix
    scroll_offset = 0
    #  setting the y axis value to equal the scroll_offset
    y = scroll_offset

    while True:
        #  record the audio sample
        mic.record(samples_bit, len(samples_bit))
        #  send the sample to the ulab array
        samples = np.array(samples_bit[3:])
        #  creates a spectogram of the data
        spectrogram1 = spectrogram(samples)
        # spectrum() is always nonnegative, but add a tiny value
        # to change any zeros to nonzero numbers
        spectrogram1 = np.log(spectrogram1 + 1e-7)
        spectrogram1 = spectrogram1[1:(fft_size//2)-1]
        #  sets range of the spectrogram
        min_curr = np.min(spectrogram1)
        max_curr = np.max(spectrogram1)
        #  resets values
        if max_curr > max_all:
            max_all = max_curr
        else:
            max_curr = max_curr-1
        min_curr = max(min_curr, 3)
        # stores spectrogram in data
        data = (spectrogram1 - min_curr) * (51. / (max_all - min_curr))
        # sets negative numbers to zero
        data = data * np.array((data > 0))
        #  resets y
        y = scroll_offset
        #  runs waves to write data to the LED's
        waves(data, y)
        #  updates scroll_offset to move data along matrix
        scroll_offset = (y + 1) % 9
        #  writes data to the RGB matrix
        is31.show()

main()

Upload the Code and Libraries to the Feather nRF52840 Sense

After downloading the Project Bundle, plug your Feather nRF52840 Sense into the computer's USB port. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the Feather nRF52840 Sense's CIRCUITPY drive. 

  • lib folder
  • code.py

Your Feather nRF52840 Sense CIRCUITPY drive should look like this after copying the lib folder and the code.py file.

How the CircuitPython Code Works

The code is a port of the Waterfall Spectrum Analyzer CircuitPython code written by Jeff Epler for the ulab: Crunch Numbers Fast in CircuitPython Learn Guide. Be sure to check out that guide for more information on the ulab CircuitPython library.

The main portion of the code remains the same. Audio is sampled via the onboard PDM microphone. The sampled data is then visualized with color. The difference is that instead of displaying that visualization with a screen, as seen on the CLUE board, it is displayed with the LED's on the matrix.

#  setup for onboard mic
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
                       sample_rate=16000, bit_depth=16)

#  use some extra sample to account for the mic startup
samples_bit = array.array('H', [0] * (fft_size+3))

#  sends visualized data to the RGB matrix with colors
def waves(data, y):
    offset = max(0, (13-len(data))//2)

    for x in range(min(13, len(data))):
        is31.pixel(x+offset, y, heatmap[int(data[x])])

In the loop, you'll find the ulab CircuitPython library in action, using numpy to crunch the sampled data into a pretty, colorful light show. 

while True:
        #  record the audio sample
        mic.record(samples_bit, len(samples_bit))
        #  send the sample to the ulab array
        samples = np.array(samples_bit[3:])
        #  creates a spectogram of the data
        spectrogram1 = spectrogram(samples)
        # spectrum() is always nonnegative, but add a tiny value
        # to change any zeros to nonzero numbers
        spectrogram1 = np.log(spectrogram1 + 1e-7)
        spectrogram1 = spectrogram1[1:(fft_size//2)-1]
        #  sets range of the spectrogram
        min_curr = np.min(spectrogram1)
        max_curr = np.max(spectrogram1)
        #  resets values
        if max_curr > max_all:
            max_all = max_curr
        else:
            max_curr = max_curr-1
        min_curr = max(min_curr, 3)
        # stores spectrogram in data
        data = (spectrogram1 - min_curr) * (51. / (max_all - min_curr))
        # sets negative numbers to zero
        data = data * np.array((data > 0))
        #  resets y
        y = scroll_offset
        #  runs waves to write data to the LED's
        waves(data, y)
        #  updates scroll_offset to move data along matrix
        scroll_offset = (y + 1) % 9
        #  writes data to the RGB matrix
        is31.show()

Need for Speed

It's important for data visualizations to be as speedy and reactive as possible for the best effect. To do this for the IS31FL3741 matrix, I2C is overclocked to communicate using 1 MHz speed. Additionally, the FFT data sample size is set to 64.

#  Manually declare I2c (not board.I2C()) to access 1 MHz speed for
i2c = busio.I2C(board.SCL, board.SDA, frequency=1000000)
#  Declare is31 w/buffering preferred (low RAM will fall back on unbuffered)
is31 = Adafruit_RGBMatrixQT(i2c, allocate=adafruit_is31fl3741.PREFER_BUFFER)
#  In buffered mode, MUST use show() to refresh matrix (see line 94)

#  size of the FFT data sample
fft_size = 64

All the Colors

After speed, the colors used to visualize the data come into play. The selected gradient in the code is a rainbow effect ranging from purple to red through the color spectrum. You can experiment with creating your own gradients too. There are quite a few tools available online for this. For this project, colordesigner.io was used.

The color gradient is stored in the heatmap array and consists of 52 hexadecimal color codes.

#  array of colors for the LEDs
#  goes from purple to red
#  gradient generated using https://colordesigner.io/gradient-generator
heatmap = [0xb000ff,0xa600ff,0x9b00ff,0x8f00ff,0x8200ff,
           0x7400ff,0x6500ff,0x5200ff,0x3900ff,0x0003ff,
           0x0003ff,0x0047ff,0x0066ff,0x007eff,0x0093ff,
           0x00a6ff,0x00b7ff,0x00c8ff,0x00d7ff,0x00e5ff,
           0x00e0ff,0x00e6fd,0x00ecf6,0x00f2ea,0x00f6d7,
           0x00fac0,0x00fca3,0x00fe81,0x00ff59,0x00ff16,
           0x00ff16,0x45ff08,0x62ff00,0x78ff00,0x8bff00,
           0x9bff00,0xaaff00,0xb8ff00,0xc5ff00,0xd1ff00,
           0xedff00,0xf5eb00,0xfcd600,0xffc100,0xffab00,
           0xff9500,0xff7c00,0xff6100,0xff4100,0xff0000,
           0xff0000,0xff0000]

This guide was first published on Oct 06, 2021. It was last updated on Sep 28, 2021.

This page (Code the Waterfall Visualizer) was last updated on May 31, 2023.

Text editor powered by tinymce.