code.py
  1  # SPDX-FileCopyrightText: 2013 Phil Burgess for Adafruit Industries
  2  # SPDX-FileCopyrightText: 2017 Mikey Sklar for Adafruit Industries
  3  #
  4  # SPDX-License-Identifier: BSD-3-Clause
  5  
  6  # LED VU meter for Arduino and Adafruit NeoPixel LEDs.
  7  
  8  # Hardware requirements:
  9  # - M0 boards
 10  # - Adafruit Electret Microphone Amplifier (ID: 1063)
 11  # - Adafruit Flora RGB Smart Pixels (ID: 1260)
 12  # OR
 13  # - Adafruit NeoPixel Digital LED strip (ID: 1138)
 14  # - Optional: battery for portable use (else power through USB or adapter)
 15  # Software requirements:
 16  # - Adafruit NeoPixel library
 17  
 18  # Connections:
 19  # - 3.3V to mic amp +
 20  # - GND to mic amp -
 21  # - Analog pin to microphone output (configurable below)
 22  # - Digital pin to LED data input (configurable below)
 23  # See notes in setup() regarding 5V vs. 3.3V boards - there may be an
 24  # extra connection to make and one line of code to enable or disable.
 25  
 26  # Written by Adafruit Industries.  Distributed under the BSD license.
 27  # This paragraph must be included in any redistribution.
 28  
 29  # fscale function:
 30  # Floating Point Autoscale Function V0.1
 31  # Written by Paul Badger 2007
 32  # Modified fromhere code by Greg Shakar
 33  # Ported to Circuit Python by Mikey Sklar
 34  
 35  import time
 36  
 37  import board
 38  import neopixel
 39  from rainbowio import colorwheel
 40  from analogio import AnalogIn
 41  
 42  n_pixels = 16  # Number of pixels you are using
 43  mic_pin = AnalogIn(board.A1)  # Microphone is attached to this analog pin
 44  led_pin = board.D1  # NeoPixel LED strand is connected to this pin
 45  sample_window = .1  # Sample window for average level
 46  peak_hang = 24  # Time of pause before peak dot falls
 47  peak_fall = 4  # Rate of falling peak dot
 48  input_floor = 10  # Lower range of analogRead input
 49  # Max range of analogRead input, the lower the value the more sensitive
 50  # (1023 = max)
 51  input_ceiling = 300
 52  
 53  peak = 16  # Peak level of column; used for falling dots
 54  sample = 0
 55  
 56  dotcount = 0  # Frame counter for peak dot
 57  dothangcount = 0  # Frame counter for holding peak dot
 58  
 59  strip = neopixel.NeoPixel(led_pin, n_pixels, brightness=1, auto_write=False)
 60  
 61  
 62  def remapRange(value, leftMin, leftMax, rightMin, rightMax):
 63      # this remaps a value fromhere original (left) range to new (right) range
 64      # Figure out how 'wide' each range is
 65      leftSpan = leftMax - leftMin
 66      rightSpan = rightMax - rightMin
 67  
 68      # Convert the left range into a 0-1 range (int)
 69      valueScaled = int(value - leftMin) / int(leftSpan)
 70  
 71      # Convert the 0-1 range into a value in the right range.
 72      return int(rightMin + (valueScaled * rightSpan))
 73  
 74  
 75  def fscale(originalmin, originalmax, newbegin, newend, inputvalue, curve):
 76      invflag = 0
 77  
 78      # condition curve parameter
 79      # limit range
 80      if curve > 10:
 81          curve = 10
 82      if curve < -10:
 83          curve = -10
 84  
 85      # - invert and scale -
 86      # this seems more intuitive
 87      # postive numbers give more weight to high end on output
 88      curve = (curve * -.1)
 89      # convert linear scale into lograthimic exponent for other pow function
 90      curve = pow(10, curve)
 91  
 92      # Check for out of range inputValues
 93      if inputvalue < originalmin:
 94          inputvalue = originalmin
 95  
 96      if inputvalue > originalmax:
 97          inputvalue = originalmax
 98  
 99      # Zero Refference the values
100      originalrange = originalmax - originalmin
101  
102      if newend > newbegin:
103          newrange = newend - newbegin
104      else:
105          newrange = newbegin - newend
106          invflag = 1
107  
108      zerorefcurval = inputvalue - originalmin
109      # normalize to 0 - 1 float
110      normalizedcurval = zerorefcurval / originalrange
111  
112      # Check for originalMin > originalMax
113      # -the math for all other cases
114      # i.e. negative numbers seems to work out fine
115      if originalmin > originalmax:
116          return 0
117  
118      if invflag == 0:
119          rangedvalue = (pow(normalizedcurval, curve) * newrange) + newbegin
120      else:  # invert the ranges
121          rangedvalue = newbegin - (pow(normalizedcurval, curve) * newrange)
122  
123      return rangedvalue
124  
125  
126  def drawLine(fromhere, to):
127      if fromhere > to:
128          to, fromhere = fromhere, to
129  
130      for index in range(fromhere, to):
131          strip[index] = (0, 0, 0)
132  
133  
134  while True:
135  
136      time_start = time.monotonic()  # current time used for sample window
137      peaktopeak = 0  # peak-to-peak level
138      signalmax = 0
139      signalmin = 1023
140      c = 0
141      y = 0
142  
143      # collect data for length of sample window (in seconds)
144      while (time.monotonic() - time_start) < sample_window:
145  
146          # convert to arduino 10-bit [1024] fromhere 16-bit [65536]
147          sample = mic_pin.value / 64
148  
149          if sample < 1024:  # toss out spurious readings
150  
151              if sample > signalmax:
152                  signalmax = sample  # save just the max levels
153              elif sample < signalmin:
154                  signalmin = sample  # save just the min levels
155  
156      peaktopeak = signalmax - signalmin  # max - min = peak-peak amplitude
157  
158      # Fill the strip with rainbow gradient
159      for i in range(0, len(strip)):
160          strip[i] = colorwheel(remapRange(i, 0, (n_pixels - 1), 30, 150))
161  
162      # Scale the input logarithmically instead of linearly
163      c = fscale(input_floor, input_ceiling, (n_pixels - 1), 0, peaktopeak, 2)
164  
165      if c < peak:
166          peak = c  # keep dot on top
167          dothangcount = 0  # make the dot hang before falling
168  
169      if c <= n_pixels:  # fill partial column with off pixels
170          drawLine(n_pixels, n_pixels - int(c))
171  
172      # Set the peak dot to match the rainbow gradient
173      y = n_pixels - peak
174      strip.fill = (y - 1, colorwheel(remapRange(y, 0, (n_pixels - 1), 30, 150)))
175      strip.write()
176  
177      # Frame based peak dot animation
178      if dothangcount > peak_hang:  # Peak pause length
179          dotcount += 1
180          if dotcount >= peak_fall:  # Fall rate
181              peak += 1
182              dotcount = 0
183      else:
184          dothangcount += 1