code.py
  1  # SPDX-FileCopyrightText: 2018 Dave Astels for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  """
  6  Circuit Playground Express sounds activated ears.
  7  
  8  Adafruit invests time and resources providing this open source code.
  9  Please support Adafruit and open source hardware by purchasing
 10  products from Adafruit!
 11  
 12  Written by Dave Astels for Adafruit Industries
 13  Copyright (c) 2018 Adafruit Industries
 14  Licensed under the MIT license.
 15  
 16  All text above must be included in any redistribution.
 17  """
 18  
 19  import time
 20  import math
 21  import array
 22  import board
 23  import audiobusio
 24  import pwmio
 25  from adafruit_motor import servo
 26  from adafruit_circuitplayground.express import cpx
 27  
 28  # Exponential scaling factor.
 29  # Should probably be in range -10 .. 10 to be reasonable.
 30  CURVE = 2
 31  SCALE_EXPONENT = math.pow(10, CURVE * -0.1)
 32  
 33  # Number of samples to read at once.
 34  NUM_SAMPLES = 90
 35  
 36  # the trigger threshhold
 37  THRESHOLD = 6
 38  left_pwm = pwmio.PWMOut(board.A1, frequency=50)
 39  right_pwm = pwmio.PWMOut(board.A2, frequency=50)
 40  
 41  left_ear = servo.Servo(left_pwm)
 42  right_ear = servo.Servo(right_pwm)
 43  
 44  cpx.pixels.fill((0, 0, 0))
 45  left_ear.angle = 0
 46  right_ear.angle = 0
 47  
 48  # Restrict value to be between floor and ceiling.
 49  
 50  def constrain(value, floor, ceiling):
 51      return max(floor, min(value, ceiling))
 52  
 53  
 54  def log_scale(input_value, input_min, input_max, output_min, output_max):
 55      normalized_input_value = (input_value - input_min) / \
 56                               (input_max - input_min)
 57      return output_min + \
 58          math.pow(normalized_input_value, SCALE_EXPONENT) \
 59          * (output_max - output_min)
 60  
 61  
 62  # Remove DC bias before computing RMS.
 63  
 64  def normalized_rms(values):
 65      minbuf = int(mean(values))
 66      samples_sum = sum(
 67          float(sample - minbuf) * (sample - minbuf)
 68          for sample in values
 69      )
 70  
 71      return math.sqrt(samples_sum / len(values))
 72  
 73  
 74  def mean(values):
 75      return sum(values) / len(values)
 76  
 77  
 78  mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, board.MICROPHONE_DATA,
 79                         sample_rate=16000, bit_depth=16)
 80  
 81  
 82  # Record an initial sample to calibrate. Assume it's quiet when we start.
 83  samples = array.array('H', [0] * NUM_SAMPLES)
 84  mic.record(samples, len(samples))
 85  # Set lowest level to expect, plus a little.
 86  input_floor = normalized_rms(samples) + 10
 87  
 88  # Corresponds to sensitivity: lower means ears perk up at lower volumes
 89  # Adjust this as you see fit.
 90  input_ceiling = input_floor + 750
 91  
 92  ears_up = False
 93  
 94  while True:
 95      samples_read = mic.record(samples, len(samples))
 96      if samples_read < NUM_SAMPLES:
 97          print("MISSING SAMPLES, only: {0}".format(samples_read))
 98      magnitude = normalized_rms(samples)
 99      # You might want to print this to see the values.
100      # print(magnitude)
101  
102      # Compute scaled logarithmic reading in the range 0 to 10
103      c = log_scale(constrain(magnitude, input_floor, input_ceiling),
104                    input_floor, input_ceiling, 0, 10)
105  
106  
107      if c >= THRESHOLD and not ears_up:
108          ears_up = True
109          left_ear.angle = 90
110          right_ear.angle = 90
111          time.sleep(1.0)
112      elif c < THRESHOLD and ears_up:
113          ears_up = False
114          left_ear.angle = 0
115          right_ear.angle = 0
116          time.sleep(1.0)
117      else:
118          time.sleep(0.1)