Frequency To Light: Chromesthesia Abstraction

By Parvathi Krishnan

Background:

My interactive project is inspired by the ability of some people that see colors when listening to music. The set up aims to display different neopixel lights onto a reflective surface by matching the frequency it hears with the color of that note, creating an abstract mixture of colored lights.

Materials:

  • Paper
  • Marker
  • Reflective foil ( e.g. aluminum foil, clean snack wrapper)
  • Scissors
  • Tape
  • Adafruit Circuit Playground
  • Straw
  • Awl or sharp tool

Step 1:

Form the paper into a box shape of your choice. I made my box hexagonal to fit the shape of the circuit playground most accurately (origami hexagonal box tutorial). I later changed the shape of one of the boxes to be taller than the other. Cut a small section out of the base box to allow the circuit playground cord in.

Step 2:

Take the straw and divide it into ten smaller lengths. I made ten straw units since I have ten neopixel lights.

Step 3:

With scissors, cut small squares out of the reflective foil. Roll each square into a tube with the reflective side inward, and insert it into a straw piece. Repeat for all straw units.

Step 4:

Take a piece of paper and cut into a circular shape. Place over neopixels on the circuit playground and mark those spots with a marker. Use an awl or sharp tool to poke holes wide enough for the straws to fit. Place straw units through the holes.

Step 5:

Cut two panels out of the taller box. This will allow the viewer to see the colorful lights. Take the aluminum foil and tape to the inside of the taller hexagonal box, making sure to leave the open panels alone. I opened the box back up to make it easier to cut and tape. Fold back into its original shape.

Step 6:

Place the parts over one another in this order: base box, circuit playground, straw circle unit, top display box.

 

Step 7:

Upload the code into CircuitPython Mu Editor and watch as the lights react to your music!

 

Code:

import time
import array
import board
import audiobusio
import neopixel

pixels = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.02, auto_write=False)
#—| User Configuration |—————————
SAMPLERATE = 31500
#SAMPLERATE = 16000
SAMPLES = 1024
THRESHOLD = 112
MIN_DELTAS = 8
#DELAY = 0.2
DELAY = 0.1

# Lowercase letters represent flat notes
# octave = 1 2 3 4 5 6 7 8
NOTES = { “C” : (33, 65, 131, 262, 523, 1047, 2093, 4186),
“D” : (37, 73, 147, 294, 587, 1175, 2349, 4699),
“e” : (-1, -1, -1, 303, 620, 1246, -1, -1),
“E” : (41, 82, 165, 330, 659, 1319, 2637, 5274),
“F” : (44, 87, 175, 349, 698, 1397, 2794, 5588),
“G” : (49, 98, 196, 392, 785, 1568, 3136, 6272),
“A” : (55, 110, 220, 440, 880, 1760, 3520, 7040),
“b” : (-1, -1, -1, 464, 930, 1873, -1, -1),
“B” : (62, 123, 247, 494, 988, 1976, 3951, 7902),}
#—————————————————-
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
ORANGE = (255, 69, 0)
PINK = (156, 3, 39)
GREY = (220, 154, 154)
OFF = (0, 0, 0)

prevNote = “Z”

LIGHTS = {“C” : RED,
“D” : YELLOW,
“e” : PURPLE,
“E” : WHITE,
“F” : PINK,
“G” : ORANGE,
“A” : GREEN,
“b” : GREY,
“B” : BLUE,
“Z” : OFF
}

# Iterator to keep track of which iteration it’s on
c = 0

# Create a buffer to record into
samples = array.array(‘H’, [0] * SAMPLES)

# Setup the mic input
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK,
board.MICROPHONE_DATA,
sample_rate=SAMPLERATE,
bit_depth=16)

while True:
# Get raw mic data
mic.record(samples, SAMPLES)

# Compute DC offset (mean) and threshold level
mean = int(sum(samples) / len(samples) + 0.5)
threshold = mean + THRESHOLD

# Compute deltas between mean crossing points
# (this bit by Dan Halbert)
deltas = []
last_xing_point = None
crossed_threshold = False
for i in range(SAMPLES-1):
sample = samples[i]
if sample > threshold:
crossed_threshold = True
if crossed_threshold and sample < mean:
if last_xing_point:
deltas.append(i – last_xing_point)
last_xing_point = i
crossed_threshold = False

# Try again if not enough deltas
if len(deltas) < MIN_DELTAS:
continue

# Average the deltas
mean = sum(deltas) / len(deltas)

# Compute frequency
freq = SAMPLERATE / mean

print(“crossings: {} mean: {} freq: {} “.format(len(deltas), mean, freq))

# Find corresponding note
for note in NOTES:
for octave, note_freq in enumerate(NOTES[note]):
if note_freq * 0.97 <= freq <= note_freq * 1.03:
# input note data
print(“-“*10)
print(“NOTE = {}{}”.format(note, octave + 1))
print(“-“*10)
print(“Prev Note = {}”.format(prevNote))

# Retrieve associated color with note and set neopixel ‘c’ to that color
pixels[c] = LIGHTS[note]
# Set previous neopixel to color for chasing effect
pixels[c-1] = LIGHTS[note]
pixels.show()
time.sleep(0.1)
pixels.show()
prevNote = note

# Move to the next neopixel
c = c + 1
# Reset neopixel to 0 after reaching the 10th one
if c == 10:
c = 0