-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Richard Hawthorn
committed
Aug 8, 2015
1 parent
6abaf4b
commit 38fd220
Showing
3 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/**************************************** | ||
Scrolling sound level meter | ||
Richard Hawthorn | ||
richardhawthorn.com | ||
Cambridge Hackspace | ||
Aug 2015 | ||
For use with SoundMeter board | ||
Based on code from Adafruit | ||
****************************************/ | ||
|
||
#include <Wire.h> | ||
#include "Adafruit_LEDBackpack.h" | ||
#include "Adafruit_GFX.h" | ||
|
||
// Include the Matrix code | ||
Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix(); | ||
|
||
const int maxScale = 8; | ||
const int redZone = 6; | ||
const int yellowZone = 3; | ||
|
||
const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz) | ||
unsigned int sample; | ||
|
||
void setup() | ||
{ | ||
analogReference(EXTERNAL); //use external voltage reference | ||
matrix.begin(0x70); // start the matrix display | ||
} | ||
|
||
void loop() | ||
{ | ||
unsigned long startMillis = millis(); // Start of sample window | ||
unsigned int peakToPeak = 0; // peak-to-peak level | ||
|
||
unsigned int signalMax = 0; | ||
unsigned int signalMin = 1024; | ||
|
||
//sample for a set period of time | ||
while (millis() - startMillis < sampleWindow) | ||
{ | ||
sample = analogRead(0); | ||
|
||
if (sample < 1024) //make sure we are in the right range | ||
{ | ||
if (sample > signalMax) | ||
{ | ||
signalMax = sample; // save max value | ||
} | ||
else if (sample < signalMin) | ||
{ | ||
signalMin = sample; //save sample value | ||
} | ||
} | ||
} | ||
peakToPeak = signalMax - signalMin; | ||
|
||
// map the peaks to the scale | ||
int displayPeak = map(peakToPeak, 0, 1023, 0, maxScale); | ||
|
||
// shift the previous values left | ||
for (int i = 0; i < 8; i++) | ||
{ | ||
matrix.displaybuffer[i] = (matrix.displaybuffer[i] >> 1); | ||
} | ||
|
||
// draw the new value | ||
for (int i = 0; i <= maxScale; i++) | ||
{ | ||
if (i >= displayPeak) | ||
{ | ||
matrix.drawPixel(7, 7-i, 0); | ||
} | ||
else if (i < yellowZone) // green | ||
{ | ||
matrix.drawPixel(7, 7-i, LED_GREEN); | ||
} | ||
else if (i < redZone) // yellow | ||
{ | ||
matrix.drawPixel(7, 7-i, LED_YELLOW); | ||
} | ||
else // red | ||
{ | ||
matrix.drawPixel(7, 7-i, LED_RED); | ||
} | ||
} | ||
matrix.writeDisplay(); // write the changes we just made to the display | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
/* | ||
PICCOLO is a tiny Arduino-based audio visualizer. | ||
Hardware requirements: | ||
- Most Arduino or Arduino-compatible boards (ATmega 328P or better). | ||
- Adafruit Bicolor LED Matrix with I2C Backpack (ID: 902) | ||
- Adafruit Electret Microphone Amplifier (ID: 1063) | ||
- Optional: battery for portable use (else power through USB) | ||
Software requirements: | ||
- elm-chan's ffft library for Arduino | ||
Connections: | ||
- 3.3V to mic amp+ and Arduino AREF pin <-- important! | ||
- GND to mic amp- | ||
- Analog pin 0 to mic amp output | ||
- +5V, GND, SDA (or analog 4) and SCL (analog 5) to I2C Matrix backpack | ||
Written by Adafruit Industries. Distributed under the BSD license -- | ||
see license.txt for more information. This paragraph must be included | ||
in any redistribution. | ||
ffft library is provided under its own terms -- see ffft.S for specifics. | ||
*/ | ||
|
||
// IMPORTANT: FFT_N should be #defined as 128 in ffft.h. | ||
|
||
#include <avr/pgmspace.h> | ||
#include <ffft.h> | ||
#include <math.h> | ||
#include <Wire.h> | ||
#include <Adafruit_GFX.h> | ||
#include <Adafruit_LEDBackpack.h> | ||
|
||
// Microphone connects to Analog Pin 0. Corresponding ADC channel number | ||
// varies among boards...it's ADC0 on Uno and Mega, ADC7 on Leonardo. | ||
// Other boards may require different settings; refer to datasheet. | ||
#ifdef __AVR_ATmega32U4__ | ||
#define ADC_CHANNEL 0 | ||
#else | ||
#define ADC_CHANNEL 0 | ||
#endif | ||
|
||
int16_t capture[FFT_N]; // Audio capture buffer | ||
complex_t bfly_buff[FFT_N]; // FFT "butterfly" buffer | ||
uint16_t spectrum[FFT_N/2]; // Spectrum output buffer | ||
volatile byte samplePos = 0; // Buffer position counter | ||
|
||
byte | ||
peak[8], // Peak level of each column; used for falling dots | ||
dotCount = 0, // Frame counter for delaying dot-falling speed | ||
colCount = 0; // Frame counter for storing past column data | ||
int | ||
col[8][10], // Column levels for the prior 10 frames | ||
minLvlAvg[8], // For dynamic adjustment of low & high ends of graph, | ||
maxLvlAvg[8], // pseudo rolling averages for the prior few frames. | ||
colDiv[8]; // Used when filtering FFT output to 8 columns | ||
|
||
/* | ||
These tables were arrived at through testing, modeling and trial and error, | ||
exposing the unit to assorted music and sounds. But there's no One Perfect | ||
EQ Setting to Rule Them All, and the graph may respond better to some | ||
inputs than others. The software works at making the graph interesting, | ||
but some columns will always be less lively than others, especially | ||
comparing live speech against ambient music of varying genres. | ||
*/ | ||
static const uint8_t PROGMEM | ||
// This is low-level noise that's subtracted from each FFT output column: | ||
noise[64]={ 8,6,6,5,3,4,4,4,3,4,4,3,2,3,3,4, | ||
2,1,2,1,3,2,3,2,1,2,3,1,2,3,4,4, | ||
3,2,2,2,2,2,2,1,3,2,2,2,2,2,2,2, | ||
2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,4 }, | ||
// These are scaling quotients for each FFT output column, sort of a | ||
// graphic EQ in reverse. Most music is pretty heavy at the bass end. | ||
eq[64]={ | ||
255, 175,218,225,220,198,147, 99, 68, 47, 33, 22, 14, 8, 4, 2, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, | ||
// When filtering down to 8 columns, these tables contain indexes | ||
// and weightings of the FFT spectrum output values to use. Not all | ||
// buckets are used -- the bottom-most and several at the top are | ||
// either noisy or out of range or generally not good for a graph. | ||
col0data[] = { 2, 1, // # of spectrum bins to merge, index of first | ||
111, 8 }, // Weights for each bin | ||
col1data[] = { 4, 1, // 4 bins, starting at index 1 | ||
19, 186, 38, 2 }, // Weights for 4 bins. Got it now? | ||
col2data[] = { 5, 2, | ||
11, 156, 118, 16, 1 }, | ||
col3data[] = { 8, 3, | ||
5, 55, 165, 164, 71, 18, 4, 1 }, | ||
col4data[] = { 11, 5, | ||
3, 24, 89, 169, 178, 118, 54, 20, 6, 2, 1 }, | ||
col5data[] = { 17, 7, | ||
2, 9, 29, 70, 125, 172, 185, 162, 118, 74, | ||
41, 21, 10, 5, 2, 1, 1 }, | ||
col6data[] = { 25, 11, | ||
1, 4, 11, 25, 49, 83, 121, 156, 180, 185, | ||
174, 149, 118, 87, 60, 40, 25, 16, 10, 6, | ||
4, 2, 1, 1, 1 }, | ||
col7data[] = { 37, 16, | ||
1, 2, 5, 10, 18, 30, 46, 67, 92, 118, | ||
143, 164, 179, 185, 184, 174, 158, 139, 118, 97, | ||
77, 60, 45, 34, 25, 18, 13, 9, 7, 5, | ||
3, 2, 2, 1, 1, 1, 1 }, | ||
// And then this points to the start of the data for each of the columns: | ||
* const colData[] = { | ||
col0data, col1data, col2data, col3data, | ||
col4data, col5data, col6data, col7data }; | ||
|
||
Adafruit_BicolorMatrix matrix = Adafruit_BicolorMatrix(); | ||
|
||
void setup() { | ||
uint8_t i, j, nBins, binNum, *data; | ||
|
||
memset(peak, 0, sizeof(peak)); | ||
memset(col , 0, sizeof(col)); | ||
|
||
for(i=0; i<8; i++) { | ||
minLvlAvg[i] = 0; | ||
maxLvlAvg[i] = 512; | ||
data = (uint8_t *)pgm_read_word(&colData[i]); | ||
nBins = pgm_read_byte(&data[0]) + 2; | ||
binNum = pgm_read_byte(&data[1]); | ||
for(colDiv[i]=0, j=2; j<nBins; j++) | ||
colDiv[i] += pgm_read_byte(&data[j]); | ||
} | ||
|
||
matrix.begin(0x70); | ||
|
||
// Init ADC free-run mode; f = ( 16MHz/prescaler ) / 13 cycles/conversion | ||
ADMUX = ADC_CHANNEL; // Channel sel, right-adj, use AREF pin | ||
ADCSRA = _BV(ADEN) | // ADC enable | ||
_BV(ADSC) | // ADC start | ||
_BV(ADATE) | // Auto trigger | ||
_BV(ADIE) | // Interrupt enable | ||
_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128:1 / 13 = 9615 Hz | ||
ADCSRB = 0; // Free run mode, no high MUX bit | ||
DIDR0 = 1 << ADC_CHANNEL; // Turn off digital input for ADC pin | ||
TIMSK0 = 0; // Timer0 off | ||
|
||
sei(); // Enable interrupts | ||
} | ||
|
||
void loop() { | ||
uint8_t i, x, L, *data, nBins, binNum, weighting, c; | ||
uint16_t minLvl, maxLvl; | ||
int level, y, sum; | ||
|
||
while(ADCSRA & _BV(ADIE)); // Wait for audio sampling to finish | ||
|
||
fft_input(capture, bfly_buff); // Samples -> complex #s | ||
samplePos = 0; // Reset sample counter | ||
ADCSRA |= _BV(ADIE); // Resume sampling interrupt | ||
fft_execute(bfly_buff); // Process complex data | ||
fft_output(bfly_buff, spectrum); // Complex -> spectrum | ||
|
||
// Remove noise and apply EQ levels | ||
for(x=0; x<FFT_N/2; x++) { | ||
L = pgm_read_byte(&noise[x]); | ||
spectrum[x] = (spectrum[x] <= L) ? 0 : | ||
(((spectrum[x] - L) * (256L - pgm_read_byte(&eq[x]))) >> 8); | ||
} | ||
|
||
// Fill background w/colors, then idle parts of columns will erase | ||
matrix.fillRect(0, 0, 8, 3, LED_RED); // Upper section | ||
matrix.fillRect(0, 3, 8, 2, LED_YELLOW); // Mid | ||
matrix.fillRect(0, 5, 8, 3, LED_GREEN); // Lower section | ||
|
||
// Downsample spectrum output to 8 columns: | ||
for(x=0; x<8; x++) { | ||
data = (uint8_t *)pgm_read_word(&colData[x]); | ||
nBins = pgm_read_byte(&data[0]) + 2; | ||
binNum = pgm_read_byte(&data[1]); | ||
for(sum=0, i=2; i<nBins; i++) | ||
sum += spectrum[binNum++] * pgm_read_byte(&data[i]); // Weighted | ||
col[x][colCount] = sum / colDiv[x]; // Average | ||
minLvl = maxLvl = col[x][0]; | ||
for(i=1; i<10; i++) { // Get range of prior 10 frames | ||
if(col[x][i] < minLvl) minLvl = col[x][i]; | ||
else if(col[x][i] > maxLvl) maxLvl = col[x][i]; | ||
} | ||
// minLvl and maxLvl indicate the extents of the FFT output, used | ||
// for vertically scaling the output graph (so it looks interesting | ||
// regardless of volume level). If they're too close together though | ||
// (e.g. at very low volume levels) the graph becomes super coarse | ||
// and 'jumpy'...so keep some minimum distance between them (this | ||
// also lets the graph go to zero when no sound is playing): | ||
if((maxLvl - minLvl) < 8) maxLvl = minLvl + 8; | ||
minLvlAvg[x] = (minLvlAvg[x] * 7 + minLvl) >> 3; // Dampen min/max levels | ||
maxLvlAvg[x] = (maxLvlAvg[x] * 7 + maxLvl) >> 3; // (fake rolling average) | ||
|
||
// Second fixed-point scale based on dynamic min/max levels: | ||
level = 10L * (col[x][colCount] - minLvlAvg[x]) / | ||
(long)(maxLvlAvg[x] - minLvlAvg[x]); | ||
|
||
// Clip output and convert to byte: | ||
if(level < 0L) c = 0; | ||
else if(level > 10) c = 10; // Allow dot to go a couple pixels off top | ||
else c = (uint8_t)level; | ||
|
||
if(c > peak[x]) peak[x] = c; // Keep dot on top | ||
|
||
if(peak[x] <= 0) { // Empty column? | ||
matrix.drawLine(x, 0, x, 7, LED_OFF); | ||
continue; | ||
} else if(c < 8) { // Partial column? | ||
matrix.drawLine(x, 0, x, 7 - c, LED_OFF); | ||
} | ||
|
||
// The 'peak' dot color varies, but doesn't necessarily match | ||
// the three screen regions...yellow has a little extra influence. | ||
y = 8 - peak[x]; | ||
if(y < 2) matrix.drawPixel(x, y, LED_RED); | ||
else if(y < 6) matrix.drawPixel(x, y, LED_YELLOW); | ||
else matrix.drawPixel(x, y, LED_GREEN); | ||
} | ||
|
||
matrix.writeDisplay(); | ||
|
||
// Every third frame, make the peak pixels drop by 1: | ||
if(++dotCount >= 3) { | ||
dotCount = 0; | ||
for(x=0; x<8; x++) { | ||
if(peak[x] > 0) peak[x]--; | ||
} | ||
} | ||
|
||
if(++colCount >= 10) colCount = 0; | ||
} | ||
|
||
ISR(ADC_vect) { // Audio-sampling interrupt | ||
static const int16_t noiseThreshold = 4; | ||
int16_t sample = ADC; // 0-1023 | ||
|
||
capture[samplePos] = | ||
((sample > (512-noiseThreshold)) && | ||
(sample < (512+noiseThreshold))) ? 0 : | ||
sample - 512; // Sign-convert for FFT; -512 to +511 | ||
|
||
if(++samplePos >= FFT_N) ADCSRA &= ~_BV(ADIE); // Buffer full, interrupt off | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Qty Value Device Package Parts Description | ||
1 DISP_SEGMENT_8X8_BICOLOR SEGMENT_BICOL LED1 8x8 Bi-Color LED Segment Displays: Row = Cathode | ||
3 0.1uf CAPPTH2 CAP-PTH-SMALL2 C1, C2, C4, C5 Capacitor | ||
5 10K R-US_0204/7 0204/7 R1, R2, R3, R7, R8 RESISTOR, American symbol | ||
1 10k R-US_0204/7 0204/7 R5 RESISTOR, American symbol | ||
1 10uf CAPPTH2 CAP-PTH-SMALL2 C3 Capacitor | ||
1 12pf CAPPTH2 CAP-PTH-SMALL2 C8 Capacitor | ||
1 1M R-US_0204/7 0204/7 R6 RESISTOR, American symbol | ||
1 1uf CAPPTH2 CAP-PTH-SMALL2 C7 Capacitor | ||
1 28-SOIC HT16K33_SOP28 SOP28_300MIL IC3 | ||
1 2k2 R-US_0204/7 0204/7 R4 RESISTOR, American symbol | ||
1 4.7uf CAPPTH2 CAP-PTH-SMALL2 C6 Capacitor | ||
1 8M10AHC49T 8M10AHC49T HC49U QF1 Monolithic Crystal Filters | ||
1 ATMEGA ATMEGA168PROUND DIL28-3-ROUND IC1 MICROCONTROLLER | ||
1 FTDI PINHD-1X6 1X06 CN4 PIN HEADER | ||
1 ICSP PINHD-2X3 2X03 ICSP1 PIN HEADER | ||
1 MIC9.7MM MIC9.7MM MIC-9.7MM U$6 Omni-directional electret microphone. 9.7mm standard diameter. | ||
1 OPA344 OPA344 DIL08 IC2 OPA344 | ||
1 REG1117 REG1117 SOT223 U5 800mA and 1A Low Dropout (LDO) Positive Regulator | ||
1 USB MINI-USB_SHIELD5P2-32005-601 32005-601 X1 MINI USB-B R/A DIP 5pol. |