code.py
  1  # SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import array
  6  from rainbowio import colorwheel
  7  import board
  8  import neopixel
  9  from analogio import AnalogIn
 10  
 11  led_pin = board.D0  # NeoPixel LED strand is connected to GPIO #0 / D0
 12  n_pixels = 12  # Number of pixels you are using
 13  dc_offset = 0  # DC offset in mic signal - if unusure, leave 0
 14  noise = 100  # Noise/hum/interference in mic signal
 15  samples = 60  # Length of buffer for dynamic level adjustment
 16  top = n_pixels + 1  # Allow dot to go slightly off scale
 17  
 18  peak = 0  # Used for falling dot
 19  dot_count = 0  # Frame counter for delaying dot-falling speed
 20  vol_count = 0  # Frame counter for storing past volume data
 21  
 22  lvl = 10  # Current "dampened" audio level
 23  min_level_avg = 0  # For dynamic adjustment of graph low & high
 24  max_level_avg = 512
 25  
 26  # Collection of prior volume samples
 27  vol = array.array('H', [0] * samples)
 28  
 29  mic_pin = AnalogIn(board.A1)
 30  
 31  strip = neopixel.NeoPixel(led_pin, n_pixels, brightness=.1, auto_write=True)
 32  
 33  
 34  def remap_range(value, leftMin, leftMax, rightMin, rightMax):
 35      # this remaps a value from original (left) range to new (right) range
 36      # Figure out how 'wide' each range is
 37      leftSpan = leftMax - leftMin
 38      rightSpan = rightMax - rightMin
 39  
 40      # Convert the left range into a 0-1 range (int)
 41      valueScaled = int(value - leftMin) / int(leftSpan)
 42  
 43      # Convert the 0-1 range into a value in the right range.
 44      return int(rightMin + (valueScaled * rightSpan))
 45  
 46  
 47  while True:
 48      n = int((mic_pin.value / 65536) * 1000)  # 10-bit ADC format
 49      n = abs(n - 512 - dc_offset)  # Center on zero
 50  
 51      if n >= noise:  # Remove noise/hum
 52          n = n - noise
 53  
 54      # "Dampened" reading (else looks twitchy) - divide by 8 (2^3)
 55      lvl = int(((lvl * 7) + n) / 8)
 56  
 57      # Calculate bar height based on dynamic min/max levels (fixed point):
 58      height = top * (lvl - min_level_avg) / (max_level_avg - min_level_avg)
 59  
 60      # Clip output
 61      if height < 0:
 62          height = 0
 63      elif height > top:
 64          height = top
 65  
 66      # Keep 'peak' dot at top
 67      if height > peak:
 68          peak = height
 69  
 70          # Color pixels based on rainbow gradient
 71      for i in range(0, len(strip)):
 72          if i >= height:
 73              strip[i] = [0, 0, 0]
 74          else:
 75              strip[i] = colorwheel(remap_range(i, 0, (n_pixels - 1), 30, 150))
 76  
 77      # Save sample for dynamic leveling
 78      vol[vol_count] = n
 79  
 80      # Advance/rollover sample counter
 81      vol_count += 1
 82  
 83      if vol_count >= samples:
 84          vol_count = 0
 85  
 86          # Get volume range of prior frames
 87      min_level = vol[0]
 88      max_level = vol[0]
 89  
 90      for i in range(1, len(vol)):
 91          if vol[i] < min_level:
 92              min_level = vol[i]
 93          elif vol[i] > max_level:
 94              max_level = vol[i]
 95  
 96      # minlvl and maxlvl indicate the volume range over prior frames, used
 97      # for vertically scaling the output graph (so it looks interesting
 98      # regardless of volume level).  If they're too close together though
 99      # (e.g. at very low volume levels) the graph becomes super coarse
100      # and 'jumpy'...so keep some minimum distance between them (this
101      # also lets the graph go to zero when no sound is playing):
102      if (max_level - min_level) < top:
103          max_level = min_level + top
104  
105      # Dampen min/max levels - divide by 64 (2^6)
106      min_level_avg = (min_level_avg * 63 + min_level) >> 6
107      # fake rolling average - divide by 64 (2^6)
108      max_level_avg = (max_level_avg * 63 + max_level) >> 6
109  
110      print(n)