software_setup

Tinkering Tuesday – Playmobil Stage – Software

In this part I will explain my final software. It is a compromise between the desired functionality and what is possible with the technology I used.


Past parts of this series:

Tinkering Tuesday – Playmobil Stage – Introduction

Tinkering Tuesday – Playmobil Stage – Playmobil parts
Tinkering Tuesday – Playmobil Stage – LED Stage Lights
Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor
Tinkering Tuesday – Playmobil Stage – LED Rings
Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis from WAV input with simple logic, FHT and MSGEQ7
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis using Sound Sensor Module

Tinkering Tuesday – Playmobil Stage – Woodwork


Here you can download the full Arduino sketch LEDController2.ino as a zip file.
But let’s start part by part.

Definitions

First the libraries for the stepper motor and the LED rings are included:

#include <Stepper.h>
#include <FastLED.h>

Next, the input pins are defined. The initial sensitivity is not relevant as it is set later in the setup(). The InputSwitch is the toggle switch, low is for the Auduino, high uses the sound sensor board.

//Inputs
#define AuxPin A0
#define MicPin A1
#define SensitivityPin A2
int sensitivity = 400;
#define InputSwitch 2 //LOW=A0/Aux, HIGH=A1/Mic

The next part is defining everything for the disco ball. First of the 4 pins, my motor has 4096 steps. With these information the Stepper library is initialized.
The three variables are used later on for controlling the time the discoball is used.

//DiscoBall
#define motorpin1  7
#define motorpin2  8
#define motorpin3  12
#define motorpin4  13
#define STEPS 4096
Stepper stepper(STEPS, motorpin1, motorpin2, motorpin3, motorpin4);
bool isDiscoTime = false;
unsigned long discoStartTime = 0;
unsigned long discoEndTime = 0;

The definitions for the FastLED library are mostly copy/paste from the NoisePlusPalette sample code. As this uses a matrix I just set it to be 6*8=48 LEDs although I only have 47 connected…

//FastLED
#define LED_PIN     6
const uint8_t kMatrixWidth  = 6;
const uint8_t kMatrixHeight = 8;
const bool    kMatrixSerpentineLayout = true;
CRGB leds[kMatrixWidth * kMatrixHeight];
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
#define BRIGHTNESS  20
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
//The 16 bit version of LED coordinates
static uint16_t x;
static uint16_t y;
static uint16_t z;
// We're using the x/y dimensions to map to the x/y pixels on the matrix.  We'll
// use the z-axis for "time".  speed determines how fast time moves forward.  Try
// 1 for a very slow moving effect, or 60 for something that ends up looking like
// water.
uint16_t speed = 1; // speed is set dynamically once we've started up
// Scale determines how far apart the pixels in our noise matrix are.  Try
// changing these values around to see how it affects the motion of the display.  The
// higher the value of scale, the more "zoomed out" the noise iwll be.  A value
// of 1 will be so zoomed in, you'll mostly see solid colors.
uint16_t scale = 30; // scale is set dynamically once we've started up
// This is the array that we keep our computed noise values in
uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];
CRGBPalette16 currentPalette( PartyColors_p );
uint8_t       colorLoop = 1;

The defines for the LEDs are rather simple. I have one pin for controlling all the yellow LEDs, one pin for the red ones and two seperate ones for the white spots for the disco ball. If you wonder why I don’t have one for the blue LEDs: I simply can’t control them. I wanted to and everything was soldered for it but I messed up the wiring (I shorted the MOSFET) and so the blue LEDs are always on when there is power connected to the board. Better than always of… I wasn’t able to repair it because I first hot-glued the board in place and then realized my mistake (I was checking each LED but just checked whether it was lighting up…well, it did).
Here there are also start and end time variables as for the stepper motor. I will come to that later.

//LEDs
#define LEDRedPin 11
#define LEDWhiteRPin 9
#define LEDWhiteLPin 10
#define LEDYellowPin 3
bool isRedOn = false;
unsigned long redStartTime = 0;
unsigned long redEndTime = 0;
bool isYellowOn = false;
unsigned long yellowStartTime = 0;
unsigned long yellowEndTime = 0;

The next section is about the variables for the sound measurements. This is mostly copy/paste from the basic input sketch.

//Input Measuring
bool isAuxInput = false;
long signalInput = 0;
long signalAvgAdd = 0;
long signalAvg = 0;
long signalMax = 0;
long signalMin = 1024;
long signalSpan = 0;
long measureCount = 0;
#define MicSamples 1024
bool isInput = false;
long silenceStart = 0;

setup()

The setup method is also rather simple. It initializes the serial connection (for debug purposes), all the pins and the variables that are set from the switch or potentiometer. Also some other basic initializations are made for the stepper or the LED rings.

void setup() {

delay( 3000 ); // power-up safety delay
Serial.begin(115200);
pinMode(LEDRedPin, OUTPUT);
pinMode(LEDWhiteRPin, OUTPUT);
pinMode(LEDWhiteLPin, OUTPUT);
pinMode(LEDYellowPin, OUTPUT);

pinMode(InputSwitch, INPUT_PULLUP);
if(digitalRead(InputSwitch) == LOW) {
isAuxInput = true;
} else {
isAuxInput = false;
}
//Serial.print("InputMode (LOW=A0/Aux, HIGH=A1/Mic): "); Serial.println(digitalRead(InputSwitch));

pinMode(SensitivityPin, INPUT);
sensitivity = analogRead(SensitivityPin);
Serial.print("Sensitivity: "); Serial.println(sensitivity);

stepper.setSpeed(2);

LEDS.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds,NUM_LEDS);
LEDS.setBrightness(BRIGHTNESS);
FastLED.setMaxPowerInVoltsAndMilliamps(5,500);

// Initialize LED coordinates to some random values
x = random16();
y = random16();
z = random16();
}

loop()

I have to admit I was a bit lazy in structuring the code and so it evolved more or less in the loop method. First step in every loop is checking the input mode:

//Read Input Mode
if(digitalRead(InputSwitch) == LOW) {
isAuxInput = true;
} else {
isAuxInput = false;
}

Next step is the audio measurement. This only happens if the variable isDiscoTime is not set. The reason for this is the computing power that is required to drive the stepper motor. The Arduino is not really capable of driving the stepper motor and sampling the data. I tried parallelizing it but got measurements which went for several seconds. Even when I reduced my collected samples from 1024 to 256 or something like that it was not quite responsive. So I decided to make disco time a standalone feature.
The audio measurement itself is then also blocking but that is okay with the other functions (which are then a bit slowed down but that doesn’t lead to stuttering). This is also mostly a copy/paste from the basic input sketch.

//Audio Measurement
if(!isDiscoTime) {
for (int i = 0; i < MicSamples; i++) {
if(measureCount > MicSamples) {
signalAvg = signalAvgAdd/MicSamples;
signalSpan = signalMax - signalMin;
//Serial.println(" signalAvg: " + String(signalAvg));
measureCount = 0;
signalAvgAdd = 0;
signalMax = 0;
signalMin = 1024;
} else {

if(isAuxInput) {
signalInput = analogRead(AuxPin);
} else {
signalInput = analogRead(MicPin);
}
//Serial.println(" signalInput: " + String(signalInput));
signalAvgAdd += signalInput;
signalMin = min(signalMin, signalInput);
signalMax = max(signalMax, signalInput);
measureCount++;
}
}
}

Next of I am checking whether there is input at all. For the input from the Auduino I am just using a threshold value of 50 (to be tolerant about background noise). For the sound sensor board I am using the input from the sensitivity potentiometer. The amplitude or signal span is compared to the sensivity (I mapped it to a lower value as it is the span…) and if it is below that threshold for more than 2 seconds I decided that there is no input recognized and all the effects are turned off (see method setAllOff() below). This is also suitable for song parts with lower volumes or breaks. As soon as the signal span is above the threshold there is input.

sensitivity = analogRead(SensitivityPin);
if((isAuxInput && signalAvg < 50) or (!isAuxInput && signalSpan < map(sensitivity, 0, 1023, 0, 450))) {
if(isInput) {
if(silenceStart == 0) {
silenceStart = millis();
Serial.println("SilenceStart");
}
if(millis()-silenceStart > 2000) {
Serial.println("AllOff");
setAllOff();
isInput = false;
}
}
} else {
if(!isInput) {
isInput = true;
Serial.println("Input recognized" + String(isInput));
} else {
silenceStart = 0;
}
}

The next part is only when there is an input signal and first checks whether the disco ball is off and there was a certain time since the last disco time. The threshold to the next disco time is a random number of milliseconds and allows a disco time only after at least 30 seconds but maximum of 3 minutes. If the threshold has passed then disco time is activated. It is active for a random time between 4 and 15 seconds and also randomly uses only one of the white LEDs or both.

If disco time is activated the stepper motor is turned and checked whether disco time has to be ended. If disco time is not active the LED rings are adjusted (remember, they are not active while the disco ball is turning). These are also the standard methods of the NoisePlusPalette sketch.

The last thing to do if there is input is controlling the yellow and the red LEDs. Those are also set to random values at random intervals. I know, now you are thinking of all the complicated input analysis stuff and how to be sound reactive and such from my last posts. Unfortunately it turned out when putting it all together, the input measuring, the LED rings, the stepper motor and the standard LEDs, together with two different input signals that are very different in behaviour, first the Arduino came to its limits in processing so I started to reduce everything. Then I rebuilt the functions but have to admit in the end the time was the biggest constraint. So I just went with random spotlights. Which due to the different thresholds works quite well!

if(isInput) {
//Disco Ball
if(!isDiscoTime && discoEndTime+random(30000,180000) <= millis()) {
setAllOff();
isDiscoTime = true;
discoStartTime = millis();
discoEndTime = millis() + random(4000,15000);
Serial.println(" Disco Time!!! Start: "+String(discoStartTime)+" End: "+String(discoEndTime));

switch(random(1,4)) {
case 1: //Both LEDs
analogWrite(LEDWhiteRPin ,250);
analogWrite(LEDWhiteLPin ,250);
Serial.println(" Both LEDs");
break;
case 2: //Left LED
analogWrite(LEDWhiteLPin ,250);
Serial.println(" Left LED");
break;
case 3: //Right LED
analogWrite(LEDWhiteRPin ,250);
Serial.println(" Right LED");
break;
}//switch
}//if

if(isDiscoTime) {
//turn stepper motor
stepper.step(1);
if(discoEndTime <= millis()){
isDiscoTime = false;
setAllOff();
Serial.println(" No Disco Time!!! ");
}
} else {

// Periodically choose a new palette, speed, and scale
ChangePaletteAndSettingsPeriodically();
fillnoise8();
mapNoiseToLEDsUsingPalette();
LEDS.show();
}
if(!isRedOn && redEndTime+random(300,7500) <= millis()) {
analogWrite(LEDRedPin, random(50,250));
redStartTime = millis();
isRedOn = true;
redEndTime = redStartTime + random(300, 2000);
} else if (isRedOn && redEndTime < millis()) {
isRedOn = false;
analogWrite(LEDRedPin, 0);
}

if(!isYellowOn && yellowEndTime+random(300,400) <= millis()) {
analogWrite(LEDYellowPin, random(100,250));
yellowStartTime = millis();
isYellowOn = true;
yellowEndTime = yellowStartTime + random(300, 5000);
} else if (isYellowOn && yellowEndTime < millis()) {
isYellowOn = false;
analogWrite(LEDYellowPin, 0);
}
}

setAllOff()

This method just deactivates all of the LEDs.

void setAllOff() {
analogWrite(LEDRedPin, 0);
analogWrite(LEDYellowPin, 0);
analogWrite(LEDWhiteRPin, 0);
analogWrite(LEDWhiteLPin, 0);
LEDS.clear();
LEDS.show();
}

FastLED Methods

The methods are also copy/paste from the NoisePlusPalette sketch. Except for the ChangePaletteAndSettingsPeriodically method where I added at least a bit of the input signal to control the LED rings. I adjust the speed of the rings light change with the signal span, see highlighted lines.

////////////////////////////////////////////////////////////////////////
//// FastLED NoisePlusPalette
////////////////////////////////////////////////////////////////////////

// Fill the x/y array of 8-bit noise values using the inoise8 function.
void fillnoise8() {
// If we're runing at a low "speed", some 8-bit artifacts become visible
// from frame-to-frame.  In order to reduce this, we can do some fast data-smoothing.
// The amount of data smoothing we're doing depends on "speed".
uint8_t dataSmoothing = 0;
if( speed < 50) {
dataSmoothing = 200 - (speed * 4);
}

for(int i = 0; i < MAX_DIMENSION; i++) {
int ioffset = scale * i;
for(int j = 0; j < MAX_DIMENSION; j++) {
int joffset = scale * j;

uint8_t data = inoise8(x + ioffset,y + joffset,z);

// The range of the inoise8 function is roughly 16-238.
// These two operations expand those values out to roughly 0..255
// You can comment them out if you want the raw noise data.
data = qsub8(data,16);
data = qadd8(data,scale8(data,39));

if( dataSmoothing ) {
uint8_t olddata = noise[i][j];
uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing);
data = newdata;
}

noise[i][j] = data;
}
}

z += speed;

// apply slow drift to X and Y, just for visual variation.
x += speed / 8;
y -= speed / 16;
}

void mapNoiseToLEDsUsingPalette()
{
static uint8_t ihue=0;

for(int i = 0; i < kMatrixWidth; i++) {
for(int j = 0; j < kMatrixHeight; j++) {
// We use the value at the (i,j) coordinate in the noise
// array for our brightness, and the flipped value from (j,i)
// for our pixel's index into the color palette.

uint8_t index = noise[j][i];
uint8_t bri =   noise[i][j];

// if this palette is a 'loop', add a slowly-changing base value
if( colorLoop) {
index += ihue;
}

// brighten up, as the color palette itself often contains the
// light/dark dynamic range desired
if( bri > 127 ) {
bri = 255;
} else {
bri = dim8_raw( bri * 2);
}

CRGB color = ColorFromPalette( currentPalette, index, bri);
leds[XY(i,j)] = color;
}
}

ihue+=1;
}

// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly.

// 1 = 5 sec per palette
// 2 = 10 sec per palette
// etc
#define HOLD_PALETTES_X_TIMES_AS_LONG 1

void ChangePaletteAndSettingsPeriodically()
{
uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60;
static uint8_t lastSecond = 99;
if(isAuxInput) {
speed = signalSpan/random(10,50);
} else {
speed = signalSpan;
}

if( lastSecond != secondHand) {
lastSecond = secondHand;
if( secondHand ==  0)  { currentPalette = RainbowColors_p;         scale = 30; colorLoop = 1; }
if( secondHand ==  5)  { SetupPurpleAndGreenPalette();             scale = 50; colorLoop = 1; }
if( secondHand == 10)  { SetupBlackAndWhiteStripedPalette();       scale = 30; colorLoop = 1; }
if( secondHand == 15)  { currentPalette = ForestColors_p;          scale =120; colorLoop = 0; }
if( secondHand == 20)  { currentPalette = CloudColors_p;           scale = 30; colorLoop = 0; }
if( secondHand == 25)  { currentPalette = LavaColors_p;            scale = 50; colorLoop = 0; }
if( secondHand == 30)  { currentPalette = OceanColors_p;           scale = 90; colorLoop = 0; }
if( secondHand == 35)  { currentPalette = PartyColors_p;           scale = 30; colorLoop = 1; }
if( secondHand == 40)  { SetupRandomPalette();                     scale = 20; colorLoop = 1; }
if( secondHand == 45)  { SetupRandomPalette();                     scale = 50; colorLoop = 1; }
if( secondHand == 50)  { SetupRandomPalette();                     scale = 90; colorLoop = 1; }
if( secondHand == 55)  { currentPalette = RainbowStripeColors_p;   scale = 20; colorLoop = 1; }
}
}

// This function generates a random palette that's a gradient
// between four different colors.  The first is a dim hue, the second is
// a bright hue, the third is a bright pastel, and the last is
// another bright hue.  This gives some visual bright/dark variation
// which is more interesting than just a gradient of different hues.
void SetupRandomPalette()
{
currentPalette = CRGBPalette16(
CHSV( random8(), 255, 32),
CHSV( random8(), 255, 255),
CHSV( random8(), 128, 255),
CHSV( random8(), 255, 255));
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
// 'black out' all 16 palette entries...
fill_solid( currentPalette, 16, CRGB::Black);
// and set every fourth one to white.
currentPalette[0] = CRGB::White;
currentPalette[4] = CRGB::White;
currentPalette[8] = CRGB::White;
currentPalette[12] = CRGB::White;

}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
CRGB purple = CHSV( HUE_PURPLE, 255, 255);
CRGB green  = CHSV( HUE_GREEN, 255, 255);
CRGB black  = CRGB::Black;

currentPalette = CRGBPalette16(
green,  green,  black,  black,
purple, purple, black,  black,
green,  green,  black,  black,
purple, purple, black,  black );
}

//
// Mark's xy coordinate mapping code.  See the XYMatrix for more information on it.
//
uint16_t XY( uint8_t x, uint8_t y)
{
uint16_t i;
if( kMatrixSerpentineLayout == false) {
i = (y * kMatrixWidth) + x;
}
if( kMatrixSerpentineLayout == true) {
if( y & 0x01) {
// Odd rows run backwards
uint8_t reverseX = (kMatrixWidth - 1) - x;
i = (y * kMatrixWidth) + reverseX;
} else {
// Even rows run forwards
i = (y * kMatrixWidth) + x;
}
}
return i;
}
100917_1349_TinkeringTu13.png

Tinkering Tuesday – Playmobil Stage – Woodwork

Planning and assembling the wood and components.


Past parts of this series:

Tinkering Tuesday – Playmobil Stage – Introduction
Tinkering Tuesday – Playmobil Stage – Playmobil parts
Tinkering Tuesday – Playmobil Stage – LED Stage Lights
Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor
Tinkering Tuesday – Playmobil Stage – LED Rings
Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis from WAV input with simple logic, FHT and MSGEQ7
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis using Sound Sensor Module


When I planned the stage I did not really think about the dimensions it needs. So I just layed out some DIN A4 sized sheets of paper and had a look:

That is DIN A3 and looks quite good already. But I need some space on sides as well (for all of the electronics) so I planned to make a typical stage as can be seen on festivals, e.g. Rock am Ring:

So I initially planned with DIN A3 size for the stage and half a DIN A4 sheet for the sides. I thought about using precut wood like the following that is available in the DIN Ax sizes:

But then I also decided that it is not very cool to cut and merge different sheets to get to the DIN A3 plus DIN A4 size. So I decided I need to get that custom cut at the home improvement store and went with 5mm MDF. That’s when I came up with the following sketch, planning with the 4mm A4 precut sheets and the custom 5mm sheets:

I had a look in the different stores and some only offered 3mm and I also wanted to go with premade laths for the top and bottom of the stage (30 and 50mm in the sketch, at the top I need minimum 22mm to fit the stepper motor which will drive the disco mirror ball). Those are also supplied in different sizes in different stores. I planned to make the top sheet overlap the back and the side sheets, so it has to be wider by the width of the wood I buy.

I decided to make an excel sheet to dynamically calculate the required sizes which include the different material widths.

It calculates the required sizes based on the given dimensions of the stage and the width of the wood. I also added the height of the top and bottom as a variable to adjust to the different sizes of the laths, in this case it was Bauhaus with 24x48mm. I adjusted the inner size of the stage in the excel to 250mm widht because I plan to have a sheet of acrylic glass in the back and Bauhaus offered a precut sheet with a height of 250mm. Additionally the stage size was reduced to 600mm as the laths are 2m long and this way I can cut it into 3 parts and still have 200mm which I can use for the sides.

Unfortunately the wood I initially wanted (MDF, 3mm, black coated) was not available in the Bauhaus. It could have been ordered but 2-3 weeks were too much for me. So I went with 5mm raw MDF and decided to paint it myself.

After I got all the wood cut I layed it out and wondered why it didn’t fit. After several measurements I gave the guy at the wood cutting station the wrong numbers. I read columns D and E instead of E and F… So another trip to the store… But luckily it wasn’t that many sheets that were wrong. So: always check/measure twice before cutting and joining.

In the following picture you can see how I want to layout the acrylic glas and the sides:

In the following pictures you can see the final layout of the stage put together loosely.

Then I went to my little workbench in the cellar and build up the base using mostly my newly acquired Dremel 4000. First drilling and counter sinking all the holes

After that I put it the base together:

The next step was to paint everything:

Then adding all the components. First the servo from the top and the bottom:

Next the rods for the stage lights (I just drilled through everything and the outer panel is holding it in place):

I sanded the acrylic glass, which can be seen in the background here:

All of the other components are going into the left side of the stage, hot-glued in place:

Putting all the LED stage lights and LED rings in place (see the posts for those). I initially planned on using a USB connector for PC cases to power both of the Arduinos. This caused some weird sounds when the servo or especially the LED rings were turned on. I tried several things, also having some capacitors in several places (I wasn’t aware of the principle of decoupling capacitors at that time… now I am at least a little bit…) but nothing worked out. My first tests were done using two separate power sources (240V to USB adaptors from smart phones) and at that time there were no problems, so I had to go with two sepearate power cables. One for the Audio Arduino alone and the other one for all the light components and the other Arduino.

The sound sensor is facing to the front:

All of the outer sheets were fixed with black nails but to be able to reopen the stage to get to the electronics parts (which I already did once to check the speaker cabling because it was not loud enough) I used screws at the left side and glued on black plastic caps to have a better look (you can also see that it is not 100% perfect… I am not the most precise woodworker…):

At the back of the stage there is the push button, a switch, the potentiometer and the two cables (secured with hot glue and a rubber from a beer bottle):

This was the woodworking part. Next up is the final Software. After that I will share some problems, learnings and additional possibilities (that I planned but lacked time).

091117_2114_TinkeringTu1.png

Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis using Sound Sensor Module

In this post I will describe my results using a sound sensor board to analyze music to make a sound reactive light show. Based on my last post I will exchange the audio input from the Auduino with the analog readings of a cheap sound sensor board.


Past parts of this series:

Tinkering Tuesday – Playmobil Stage – Introduction (englische Einführung, restliche Parts sind nur auf Englisch verfügbar)

Tinkering Tuesday – Playmobil Stage – Playmobil parts

Tinkering Tuesday – Playmobil Stage – LED Stage Lights
Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor
Tinkering Tuesday – Playmobil Stage – LED Rings

Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino

Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis from WAV input with simple logic, FHT and MSGEQ7


Sound Sensor Board Keyes KY-037

I ordered a sound sensor board from ebay, which seems to be the Keyes KY-037 (2,09 EUR for 3 pcs). More details can be found at https://tkkrlab.nl/wiki/Arduino_KY-037_Sensitive_microphone_sensor_module (there is also another version, the KY-038 which seems to be only differing in sensitivity, see https://tkkrlab.nl/wiki/Arduino_KY-038_Microphone_sound_sensor_module) and the power point presentation found at http://brd4.braude.ac.il/~ksamuel/ElIn.31361/Lectures/022-Basic%20Definitions%20and%20Concepts%20Sensors%20and%20Actiators%202013-10-10.pdf (slide 40 ff.).

The wiring is very simple. Just connect ground and 5V and depending whether you want to use digital or analog input the pin D0 and/or A0. In the following schematic you can see both:

Analog input

Using the example sketch from tkkrlab for the analog reading I got some reading in silence from the analog pin. That base reading can be adjusted by using the screw on the board. I adjusted it to around 100 for the first tests:

First I modified the sketch to not delay the reading based on the value so I had all the data.

int sensorPin = A1; // select the input pin for the potentiometer
int sensorValue = 0; // variable to store the value coming from the sensor
void setup ()
{
Serial.begin (9600);
}
void loop ()
{
sensorValue = analogRead (sensorPin);
Serial.println (sensorValue, DEC);
}

I then played a song on my PC („Wecker“ from my band Monkey Fly: https://www.youtube.com/watch?v=3l2iKzLA9VY) and exported the readings to excel to draw a graph resulting in a wonderful waveform:

Here are the numbers:

You can see that the readings at 500 have a little wider span than those at 100, so I will go with this being in the middle of the value range for analog readings (0-1023). The adjustment around 750 is not bringing any more accuracy.

I also did a test with different volume levels (using the same song and the same speaker level I adjusted the volume via Windows to 50%, 75% and 100%:

The peaks are much lower but I think this will work for some sound reacting lights. I just have to dynamically adopt the thresholds to the minimum and maximum over some readings or implement a potentiometer to adjust the sensivity dynamically.

Digital input

Just for completeness I also tested the digital readings. Still having the screw to an analog level of around 750, I installed the following sketch reading the analogValue when a HIGH is recognized at the digital Input (yes, there is some delay but it has to be precise enough here). Playing my song back I got no feedback, even on the bass notes. Clapping gave analog readings around 500.

Bringing the analog readings back down to around 100 resulted in the digital input always being HIGH. I turned the screw until the reading got LOW at around 560 again.

Using the Basic input sketch from the audio input I also get similar readings so I could easily switch between both inputs:

-- base reading without sound
Time: 230 Min: 502 Max: 505 Avg: 503 Span: 3, 2, 1
Time: 229 Min: 502 Max: 505 Avg: 503 Span: 3, 2, 1
Time: 229 Min: 501 Max: 506 Avg: 503 Span: 5, 3, 2
Time: 230 Min: 501 Max: 506 Avg: 503 Span: 5, 3, 2

-- Intro of the song (silent part)
Time: 229 Min: 499 Max: 508 Avg: 503 Span: 9, 5, 4
Time: 229 Min: 501 Max: 506 Avg: 503 Span: 5, 3, 2
Time: 230 Min: 500 Max: 506 Avg: 503 Span: 6, 3, 3
Time: 229 Min: 499 Max: 506 Avg: 503 Span: 7, 3, 4
Time: 230 Min: 500 Max: 508 Avg: 503 Span: 8, 5, 3

-- Intro of the song (loud part)
Time: 229 Min: 461 Max: 531 Avg: 503 Span: 70, 28, 42
Time: 229 Min: 477 Max: 525 Avg: 503 Span: 48, 22, 26
Time: 230 Min: 476 Max: 524 Avg: 503 Span: 48, 21, 27
Time: 229 Min: 478 Max: 527 Avg: 503 Span: 49, 24, 25
Time: 230 Min: 486 Max: 521 Avg: 503 Span: 35, 18, 17

Summary

I think the analog input will work together with the results from my previous post on the audio analysis. The plan is to combine the sound sensor and the wav playback. So if there is no sound playing on the other Arduino I will read the sound sensor but for this I have to notify on playback. Let’s see how I will do this.

More to read

Sound Sensor Modul: http://linksprite.com/wiki/index.php5?title=Advanced_Sensors_Kit_for_Arduino (auch Laser)

Sound Sensor Modul: https://tkkrlab.nl/wiki/Arduino_KY-038_Microphone_sound_sensor_module (auch Laser https://tkkrlab.nl/wiki/Arduino_KY-008_Laser_sensor_module)

Sound Sensor Modul: http://brd4.braude.ac.il/~ksamuel/ElIn.31361/Lectures/022-Basic%20Definitions%20and%20Concepts%20Sensors%20and%20Actiators%202013-10-10.pdf

Spectrum analysis using electret microphone and LM386 amp: http://shin-ajaran.blogspot.de/2014/11/arduino-spectrum-analyzer-for-music-to.html

091117_2114_TinkeringTu6.png

Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis from WAV input with simple logic, FHT and MSGEQ7

Past posts of this series:

Tinkering Tuesday – Playmobil Stage – Einführung

Tinkering Tuesday – Playmobil Stage – Introduction

Tinkering Tuesday – Playmobil Stage – Playmobil parts

Tinkering Tuesday – Playmobil Stage – LED Stage Lights

Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor

Tinkering Tuesday – Playmobil Stage – LED Rings
Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino


This post will be about different possibilities to do sound input analysis (or spectrum analysis) with the Arduino. The source of sound will be my Arduino audio output from the last post for now. But I plan on using a sound sensor board additionally for the final build. The audio output from my last post is a little bit special because it uses PWM. „Normal“ audio is more of a sine wave. To not have any side effects I will do my testing with a second Arduino (nano in my case)

But lets start using the PWM output directly:

Sound input analysis on PWM signals

I made a direct connection from the PWM output of my audio output Arduino to A0 of the input Arduino:

New is only the Arduino nano, connecting A0 and GND.

With this we can use a simple input analysis, based on the code at http://blog.yavilevich.com/2016/08/arduino-sound-level-meter-and-spectrum-analyzer/ (which is by the way a great post for the theory also).

//based on: http://blog.yavilevich.com/2016/08/arduino-sound-level-meter-and-spectrum-analyzer/
#define MicSamples (1024*2)
#define MicPin A0

// measure basic properties of the input signal
// determine if analog or digital, determine range and average.
void MeasureAnalog()
{
long signalAvg = 0, signalMax = 0, signalMin = 1024, t0 = millis();
for (int i = 0; i < MicSamples; i++)
{
int k = analogRead(MicPin);
signalMin = min(signalMin, k);
signalMax = max(signalMax, k);
signalAvg += k;
}
signalAvg /= MicSamples;

// print
Serial.print("Time: " + String(millis() - t0));
Serial.print(" Min: " + String(signalMin));
Serial.print(" Max: " + String(signalMax));
Serial.print(" Avg: " + String(signalAvg));
Serial.print(" Span: " + String(signalMax - signalMin));
Serial.print(", " + String(signalMax - signalAvg));
Serial.print(", " + String(signalAvg - signalMin));
Serial.println("");

}
void setup() {
Serial.begin(115200);
}

void loop() {
MeasureAnalog();
}

As a result I got some readings. First you see the basic noise (without the output Arduino connected), then the plugin noise of the output Arduino and finally the sound playing.

--Basic noise
Time: 229 Min: 0 Max: 8 Avg: 2 Span: 8, 6, 2
Time: 229 Min: 0 Max: 7 Avg: 2 Span: 7, 5, 2
Time: 230 Min: 0 Max: 8 Avg: 2 Span: 8, 6, 2
Time: 229 Min: 0 Max: 8 Avg: 2 Span: 8, 6, 2
--PlugIn
Time: 229 Min: 0 Max: 221 Avg: 89 Span: 221, 132, 89
Time: 229 Min: 0 Max: 180 Avg: 108 Span: 180, 72, 108
Time: 229 Min: 28 Max: 88 Avg: 52 Span: 60, 36, 24
--Playing Sound
Time: 230 Min: 0 Max: 1023 Avg: 24 Span: 1023, 999, 24
Time: 229 Min: 0 Max: 1023 Avg: 327 Span: 1023, 696, 327
Time: 230 Min: 0 Max: 1023 Avg: 332 Span: 1023, 691, 332
Time: 229 Min: 0 Max: 1023 Avg: 327 Span: 1023, 696, 327
Time: 229 Min: 0 Max: 1023 Avg: 331 Span: 1023, 692, 331
Time: 230 Min: 0 Max: 1023 Avg: 323 Span: 1023, 700, 323
Time: 229 Min: 0 Max: 1023 Avg: 327 Span: 1023, 696, 327
Time: 230 Min: 0 Max: 1023 Avg: 326 Span: 1023, 697, 326
Time: 229 Min: 0 Max: 1023 Avg: 325 Span: 1023, 698, 325
Time: 229 Min: 0 Max: 1023 Avg: 325 Span: 1023, 698, 325

Having a closer look at the readings shows what could already be expected. Values ranging from 0 to 1023, the averages varying a bit. It is PWM, so fully on (1023) or fully off (0). That can be verified by a pure analog read:

#define MicPin A0

void setup() {
Serial.begin(115200);

}

void loop() {
Serial.println(analogRead(MicPin));
}
1023
0
0
1023
0
0
195
1
0
524
0
7
1023
0
988
0
7
1023
0
62
0
574
0
1023
0
0
0
902
0
1023

I don’t know quite the theory why the „in-between-values“ are there but they are. But mostly you can see 0 and 1023.

There is an advanced function to analyse audio on the arduino and it is called the Arduino FHT library. For more information about that please have a look at http://wiki.openmusiclabs.com/wiki/ArduinoFHT. There is also the full theory.

The following sketch is a modified example code and is producing serial output:

/*
fht_adc.pde
guest openmusiclabs.com 9.5.12
example sketch for testing the fht library.
it takes in data on ADC0 (Analog0) and processes them
with the fht. the data is sent out over the serial
port at 115.2kb.  there is a pure data patch for
visualizing the data.
*/

#define LOG_OUT 1 // use the log output function
#define FHT_N 256 // set to 256 point fht

#include <FHT.h> // include the library

void setup() {
Serial.begin(115200); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
}

void loop() {
while(1) { // reduces jitter
cli();  // UDRE interrupt slows this way down on arduino1.0
long t0 = micros();
for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples
while(!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= 0x0200; // form into a signed int
k <<= 6; // form into a 16b signed int
fht_input[i] = k; // put real data into bins
}
long dt = micros() - t0;
fht_window(); // window the data for better frequency response
fht_reorder(); // reorder the data before doing the fht
fht_run(); // process the data in the fht
fht_mag_log(); // take the output of the fht
//    sei();
//    Serial.write(255); // send a start byte
//    Serial.write(fht_log_out, FHT_N/2); // send out the data
// print as text
for (int i = 0; i < FHT_N / 2; i++)
{
Serial.print(fht_input[i]);
Serial.print(',');
}
long sample_rate = FHT_N * 1000000l / dt;
Serial.print(dt);
Serial.print(',');
Serial.println(sample_rate);
}
}

The result can be seen in the following screenshot:

Puh, that’s a lot of numbers. As a plot in excel this is the result:

Using digital to analog conversion (DAC) to convert PWM back to analog signal

Searching for some solutions I stumbled upon the DAC from http://www.instructables.com/id/Analog-Output-Convert-PWM-to-Voltage/

This is using a capacitor and a resistor to smoothen the PWM signal back into a voltage level. It is also known as a low pass filter (if you exchange the resistor and the capacitor you get a high pass filter as I read in the comments…).

The following schematic shows the enhanced connection:

R1=3,9k, C=100nF (0,1uF)

The output from the basic input readings look much better now:

Time: 230 Min: 155 Max: 547 Avg: 347 Span: 392, 200, 192
Time: 229 Min: 174 Max: 562 Avg: 347 Span: 388, 215, 173
Time: 230 Min: 150 Max: 545 Avg: 347 Span: 395, 198, 197
Time: 229 Min: 175 Max: 528 Avg: 347 Span: 353, 181, 172
Time: 229 Min: 163 Max: 513 Avg: 347 Span: 350, 166, 184
Time: 229 Min: 149 Max: 511 Avg: 347 Span: 362, 164, 198
Time: 230 Min: 180 Max: 539 Avg: 347 Span: 359, 192, 167
Time: 230 Min: 202 Max: 504 Avg: 347 Span: 302, 157, 145
Time: 229 Min: 161 Max: 540 Avg: 347 Span: 379, 193, 186
Time: 230 Min: 147 Max: 582 Avg: 347 Span: 435, 235, 200
Time: 229 Min: 118 Max: 546 Avg: 349 Span: 428, 197, 231
Time: 229 Min: 59 Max: 610 Avg: 347 Span: 551, 263, 288
Time: 230 Min: 187 Max: 553 Avg: 347 Span: 366, 206, 160
Time: 229 Min: 179 Max: 603 Avg: 348 Span: 424, 255, 169
Time: 230 Min: 186 Max: 569 Avg: 347 Span: 383, 222, 161
Time: 229 Min: 175 Max: 502 Avg: 347 Span: 327, 155, 172
Time: 229 Min: 191 Max: 497 Avg: 347 Span: 306, 150, 156
Time: 231 Min: 170 Max: 494 Avg: 347 Span: 324, 147, 177
Time: 229 Min: 203 Max: 517 Avg: 347 Span: 314, 170, 144
Time: 230 Min: 218 Max: 532 Avg: 347 Span: 314, 185, 129
Time: 229 Min: 189 Max: 514 Avg: 347 Span: 325, 167, 158
Time: 230 Min: 201 Max: 484 Avg: 347 Span: 283, 137, 146
Time: 229 Min: 117 Max: 532 Avg: 347 Span: 415, 185, 230

This can also be observed in the pure readings:

245
253
310
368
424
398
382
370
390
405
402
386
400
399
387
345
308
292
320
338
337
313
317
335
353
371
348
290
280
279
263
232
221
247
319

I have to admit in the FHT output I don’t see much difference:

Connecting some LEDs I modified the sketch to light the LEDs up based on some thresholds.

//based on: http://blog.yavilevich.com/2016/08/arduino-sound-level-meter-and-spectrum-analyzer/
#define MicSamples (1024*2)
#define MicPin A0
#define LEDRedPin 11
#define LEDGreenPin 9
#define LEDBluePin 10

// measure basic properties of the input signal
// determine if analog or digital, determine range and average.
void MeasureAnalog()
{
long signalAvg = 0, signalMax = 0, signalMin = 1024, t0 = millis();
for (int i = 0; i < MicSamples; i++) { int k = analogRead(MicPin); signalMin = min(signalMin, k); signalMax = max(signalMax, k); signalAvg += k; } signalAvg /= MicSamples; // print Serial.print("Time: " + String(millis() - t0)); Serial.print(" Min: " + String(signalMin)); Serial.print(" Max: " + String(signalMax)); Serial.print(" Avg: " + String(signalAvg)); Serial.print(" Span: " + String(signalMax - signalMin)); Serial.print(", " + String(signalMax - signalAvg)); Serial.print(", " + String(signalAvg - signalMin)); Serial.println(""); if(signalMax - signalMin > 450){
analogWrite(LEDRedPin, 255);
} else {
analogWrite(LEDRedPin, 0);
}
if(signalMax - signalMin > 400){
analogWrite(LEDGreenPin, 255);
} else {
analogWrite(LEDGreenPin, 0);
}
if(signalMax - signalMin > 500){
analogWrite(LEDBluePin, 255);
} else {
analogWrite(LEDBluePin, 0);
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(LEDRedPin, OUTPUT);
pinMode(LEDBluePin, OUTPUT);
pinMode(LEDGreenPin, OUTPUT);
}

void loop() {
MeasureAnalog();
}

It is currently based on the intro, so it is not really an optimal solution for the whole song and for other songs. I will have to see how to configure the threshholds best. Maybe they have to adopt to the levels of the prior readings.

MSGEQ7

Then I connected the MSGEQ7 according to the schematic shown at http://nuewire.com/info-archive/msgeq7-by-j-skoba/. Using the very same Arduino sketch from there I got a lot of zeros for my input with some readings in different frequencies in between. What I also recognized was that the direct PWM output from the other Arduino was giving at least some results (not for the intro of the test song but from the chorus onwards. I have no clue why this is…). When I used the DAC / low pass filter shown above, I got no readings at all. I had no 200ohm resistor and used a 220ohm but should this result in such a difference? I don’t think so. I also tried connecting an MP3 player directly to the MSGEQ7 but also got no results.

Abb: Wiring with DAC – R2=200k, C2=33pF, C3=100nF, C4=10nF, C5=100nF

Abb: Wiring without DAC – R2=200k, C2=33pF, C3=100nF, C4=10nF, C5=100nF

I found a MSGEQ7 Demo app at https://forum.arduino.cc/index.php?topic=425799.0. Adopted to my setup:

/*
MSGEQ7 Demo app
Look for the breakout board on www.whizoo.com

This code runs on an Arduino Duemilanove, but will run on other Arduino models.

Connections:
- GND to GND on MSGEQ7 breakout board, and LED's
- 5V to VDD on MSGEQ7 breakout board
- A0 to OUT on MSGEQ7 breakout board
- D2 to STROBE on MSGEQ7 breakout board
- D3 to RESET on MSGEQ7 breakout board
- D9 to LED 0 (indicator for frequency band 0)
- D10 to LED 1 (indicator for frequency band 1)
- D11 to LED 2 (indicator for frequency band 2)
- no pin to LED 3 (indicator for frequency band 3)
- no pin to LED 4 (indicator for frequency band 4)
- no pin to LED 5 (indicator for frequency band 5)

*/

// Hardware-specific defines
#define MSGEQ7_STROBE_PIN      2
#define MSGEQ7_RESET_PIN       3
#define MSGEQ7_ANALOG_PIN      A0

#define NUM_FREQUENCY_BANDS    3

// Duemilanove only has 6 PWM outputs, so the last LED won't respond properly.  Your
// board may have more PWM outputs.  Typically you only want to monitor the lowest
// frequency bands because that is where the beat is.
int led[NUM_FREQUENCY_BANDS] = {9, 10, 11};

// There is a concept of "persistence of vision" with LED's.  The LED has to be on long enough
// for the eye to recognise that it is on.  When a high volume is received on a frequency band,
// The LED is turned on (at a high PWM value) and then gradually faded until the next beat in
// that frequency.
int ledPWMValue[NUM_FREQUENCY_BANDS] = {0, 0, 0};

void setup() {
// Set the LED pins as outputs
for (int i=0; i<NUM_FREQUENCY_BANDS; i++)
pinMode(led[i], OUTPUT);

// Set up the MSGEQ7 IC
pinMode(MSGEQ7_ANALOG_PIN, INPUT);
pinMode(MSGEQ7_STROBE_PIN, OUTPUT);
pinMode(MSGEQ7_RESET_PIN, OUTPUT);
digitalWrite(MSGEQ7_RESET_PIN, LOW);
digitalWrite(MSGEQ7_STROBE_PIN, HIGH);
Serial.begin(9600);
}

// This loop executes around 100 times per second
void loop() {
int frequencyBandVolume;

// Toggle the RESET pin of the MSGEQ7 to start reading from the lowest frequency band
digitalWrite(MSGEQ7_RESET_PIN, HIGH);
digitalWrite(MSGEQ7_RESET_PIN, LOW);

// Read the volume in every frequency band from the MSGEQ7
for (int i=0; i<NUM_FREQUENCY_BANDS; i++) { digitalWrite(MSGEQ7_STROBE_PIN, LOW); // delayMicroseconds(20); // Allow the output to settle frequencyBandVolume = analogRead(MSGEQ7_ANALOG_PIN); digitalWrite(MSGEQ7_STROBE_PIN, HIGH); Serial.print(frequencyBandVolume); Serial.print(" "); if (i == NUM_FREQUENCY_BANDS-1) { Serial.println(""); } // The read value is 10-bit (0 to 1024). PWM needs a value from 0 to 255, so divide by 4 frequencyBandVolume = frequencyBandVolume >> 2;

// Fade the current LED value for this band
ledPWMValue[i] = ledPWMValue[i] > 7? ledPWMValue[i] - 7 : 0;

// Don't show the lower values
if ((frequencyBandVolume > 70) && (frequencyBandVolume < 100)){ // If the new volume is greater than that currently being showed then show the higher volume if (frequencyBandVolume > ledPWMValue[i])
ledPWMValue[i] = frequencyBandVolume;
}

if (frequencyBandVolume > 220) {
// If the new volume is greater than that currently being showed then show the higher volume
if (frequencyBandVolume > ledPWMValue[i])
ledPWMValue[i] = frequencyBandVolume;
}

// Set the LED PWM value to the frequency band's volume
analogWrite(led[i],  ledPWMValue[i]);
}

// Wait before executing this loop again
//delay(2);
}

With some LEDs connected (there will be another post regarding the LEDs) you can see the result for the chorus in the video below:

I don’t have so much time to tinker more with the MSGEQ7 right now, so I will just go with a simple input solution I guess. At least I will take the fading of the LEDs from the MSGEQ7 Demo app. But one more note: http://www.eetimes.com/author.asp?doc_id=1323030 recommends to also use 100nF instead of the 10nF capacitor. Perhaps that might be something worth to try. The site has also the wiring for stereo input.

Doing it all on one Arduino

For my tests I used two separate Arduinos. But of course I would really like to reduce this down to one Arduino. So I connected the output of the DAC directly to the A0 pin of the Arduino UNO and combined the two sketches:

// ---------------------------------------------------------------------------------
// DO NOT USE CLASS-10 CARDS on this project - they're too fast to operate using SPI
// ---------------------------------------------------------------------------------

#include <SD.h>
#include <TMRpcm.h>
TMRpcm tmrpcm;

File root;
File entry;
#define MicSamples (1024^2)
#define MicPin A0
#define LEDRedPin 11
#define LEDGreenPin 9
#define LEDBluePin 10

// ---------------------------------------------------------------------------------
// set chipSelect to '10' if using the $2 SD card module or '4' if using the
// Ethernet shield's microSD card instead.
const int chipSelect = 10;
// ---------------------------------------------------------------------------------

const int oldCard = SPI_HALF_SPEED;
const int newCard = SPI_QUARTER_SPEED;

// ---------------------------------------------------------------------------------
// set cardType to 'oldCard' if using an old SD card (more than a few years old) or
// to 'newCard' if using a newly-purchase Class-4 card.
int cardType = newCard;
// ---------------------------------------------------------------------------------

int wasPlaying = 0;
int inSwitch = 7;
int finished = 0;
int start = 0;
int pauseOn = 0;
unsigned long timeDiff = 0;
unsigned long timePress = 0;
int analogMeasureCounter = 0;
int analogMeasureValue = 0;
long signalAvg = 0, signalMax = 0, signalMin = 1024, t0 = millis();

void setup() {
Serial.begin(9600);
Serial.print("\nInitializing SD card...");
pinMode(chipSelect, OUTPUT);
if (!SD.begin(chipSelect,cardType)) {
Serial.println("failed!");
return;
}
Serial.println("done.");

tmrpcm.speakerPin = 9;

pinMode(inSwitch,INPUT_PULLUP);
digitalWrite(inSwitch,HIGH);

root = SD.open("/");
}

void loop(void) {
if(!tmrpcm.isPlaying() && wasPlaying == 1) {
tmrpcm.stopPlayback();
playNext();
}
MeasureAnalog();
if (millis() - timeDiff > 50) { // check switch every 100ms
timeDiff = millis(); // get current millisecond count

if(digitalRead(inSwitch) == LOW) {

if(start==0) {
start=1;
playNext();
delay(200);

} else {

timePress = millis();
while(digitalRead(inSwitch)==LOW) {
delay(50);
}
if (millis() - timePress < 1000 && start == 1) { tmrpcm.pause(); if (pauseOn == 0) { pauseOn = 1; } else { pauseOn = 0; } } if (millis() - timePress > 1000 && start == 1) {
if (pauseOn == 1) {pauseOn = 0; tmrpcm.pause();}
tmrpcm.stopPlayback();
timePress = millis();
if (finished == 0) {
playNext();
} else {
finished = 0;
Serial.println("Restarting.");
root.rewindDirectory();
playNext();
}
}
}
}
}
}

void playNext() {
entry = root.openNextFile();
if (entry) {
entry.close();
tmrpcm.play(entry.name());
Serial.print("Playing ");
Serial.print(entry.name());
wasPlaying = 1;
} else {
if (wasPlaying == 1) {
Serial.println("Completed playback.");
wasPlaying = 0;
finished = 1;
start = 0;
root.rewindDirectory();
}
}
}
void MeasureAnalog()
{
if (analogMeasureCounter == MicSamples) {
signalAvg /= MicSamples;

// print
Serial.print("Time: " + String(millis() - t0));
Serial.print(" Min: " + String(signalMin));
Serial.print(" Max: " + String(signalMax));
Serial.print(" Avg: " + String(signalAvg));
Serial.print(" Span: " + String(signalMax - signalMin));
Serial.print(", " + String(signalMax - signalAvg));
Serial.print(", " + String(signalAvg - signalMin));
Serial.println("");

//    if(signalMax - signalMin > 450){
//      analogWrite(LEDRedPin, 255);
//    } else {
//      analogWrite(LEDRedPin, 0);
//    }
//    if(signalMax - signalMin > 400){
//      analogWrite(LEDGreenPin, 255);
//    } else {
//      analogWrite(LEDGreenPin, 0);
//    }
//    if(signalMax - signalMin > 500){
//      analogWrite(LEDBluePin, 255);
//    } else {
//      analogWrite(LEDBluePin, 0);
//    }
t0 = millis();
analogMeasureCounter = 0;
} else {
//Serial.println( analogRead(MicPin));
analogMeasureValue = analogRead(MicPin);
//signalMin = min(signalMin, analogMeasureValue);
//signalMax = max(signalMax, analogMeasureValue);
signalAvg += analogMeasureValue;
analogMeasureCounter++;
}

}

That resulted in very slow readings. Instead of every 230 milliseconds I got a reading every 2-4 seconds. Also reducing the number of MicSamples down to 512 resulted in the following output:

Time: 1197 Min: 1024 Max: 0 Avg: 234 Span: -1024, -234, -790
Time: 1274 Min: 1024 Max: 0 Avg: 236 Span: -1024, -236, -788
Time: 1235 Min: 1024 Max: 0 Avg: 233 Span: -1024, -233, -791
Time: 1275 Min: 1024 Max: 0 Avg: 236 Span: -1024, -236, -788
Time: 1196 Min: 1024 Max: 0 Avg: 231 Span: -1024, -231, -793
Time: 1258 Min: 1024 Max: 0 Avg: 231 Span: -1024, -231, -793
Time: 1274 Min: 1024 Max: 0 Avg: 238 Span: -1024, -238, -786

So still over a second. That is not usable for sound reactive lights. It would lag very much behind.

I also got to remove the calculation of min and max values because the dynamic memory was giving a warning and the playback did not start anymore.

TMRpcm versions

I used the version of TMRpcm that came with the Auduino. It doesn’t include volume adjustment yet and so I wanted to upgrade to the most recent version of the library from https://github.com/TMRh20/TMRpcm/wiki. Unfortunately I was not able to get that one running. I tried the basic examples and also my above used codes but it was refusing to playback anything. I have no clue why.

In the end I will just continue with the current setup of two Arduinos (perhaps I might exchange the one that is doing the audio playback with an Arduino nano and try the recent version again. But for now I don’t want to reconnect all the cables. I will go for some PCB design soon and solder everything together. That will make rewiring everything easier.

More to read

Fading RGB FHT: http://shin-ajaran.blogspot.de/2014/11/arduino-spectrum-analyzer-for-music-to.html

PWM to Voltage: http://www.instructables.com/id/Arduino-RC-Circuit-PWM-to-analog-DC/

Wiring of the MSGEQ7 http://nuewire.com/info-archive/msgeq7-by-j-skoba/

Matching Instructable: http://www.instructables.com/id/Extreme-Nightlight-Color-Organ/

MSGEQ7 forum post with TestCode https://forum.arduino.cc/index.php?topic=425799.0

MSGEQ7 Datasheet: https://www.sparkfun.com/datasheets/Components/General/MSGEQ7.pdf

MSGEQ7 Tutorial http://tronixstuff.com/2013/01/31/tutorial-arduino-and-the-msgeq7-spectrum-analyzer/

MSGEQ7 Tutorial https://www.baldengineer.com/msgeq7-simple-spectrum-analyzer.html

Mic Amp: http://www.redcircuits.com/Page38.htm

AnalogIn Basic analysis, theory: http://blog.yavilevich.com/2016/08/arduino-sound-level-meter-and-spectrum-analyzer/

FFT for Arduino: http://wiki.openmusiclabs.com/wiki/ArduinoFFT

FHT for Arduino: http://wiki.openmusiclabs.com/wiki/ArduinoFHT

Arduino, FHT, RGB Led, sound to lighting: https://gist.github.com/teos0009/6e54aff7e3d236d17ac4

091117_2112_TinkeringTu6.png

Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino

Past Posts of this series:

Tinkering Tuesday – Playmobil Stage – Einführung
Tinkering Tuesday – Playmobil Stage – Introduction
Tinkering Tuesday – Playmobil Stage – Playmobil parts
Tinkering Tuesday – Playmobil Stage – LED Stage Lights
Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor
Tinkering Tuesday – Playmobil Stage – LED Rings


 

The most important thing a stage needs is sound! As I want to keep costs down I want to use one of the several cheap Arduino copies I have lying around. That will be not the best quality sound but it will work. In the following post I will describe my way to Arduino sound.

microSD card to Arduino

I bought some cheap microSD card modules from eBay (1 EUR for 2 pcs) to save the audio files on.

MicroSD cards work with 3.3V serial communication, the Arduino with 5V. As I had a spare LLC (Logic Level Converter) from my HUEify project lying around I decided to use that one. I couldn’t find much about using one with a microSD adapter on the web (here is a german site: http://physudo.blogspot.de/2013/09/sd-karten-mit-dem-arduino-beschreiben.html) but in the end it is straight forward. The only I issue I had (which made me nearly giving up) was that connecting everything through the LLC always resulted in the Arduino not being able to initialize the card. Directly connecting the MISO (Master In Slave Out) pin from the SD to the Arduino finally solved it. There is no issue in voltage level as this pin only sends data from the SD card to the Arduino (Master=Arduino in, Slave=SD card Out) so it communicates with 3.3V which is enough for the Arduino.

This is the schematic I came up with (the fancy colors are a result of my jumper wires on the breadboard, see picture below):

If you even want to save the microSD adapter, you can build your own adapter for normal SD cards. In this picture shown by Nathan Chantrell on flickr, you can also see the connections that have to be made.

Using the sample code from https://www.arduino.cc/en/Tutorial/listfiles you can test whether your connections are working. My first attempts always showed „initialization failed“ until I connected MISO directly to the Arduino and not via the LLC. You also need to format the card in FAT16 or FAT32. I had a 2GB card lying around and my bigger cards did not work. So just try different ones if it is not working.

Making the audio connection

I had no Amplifier or something (but I ordered it) at first, so I searched for a way to hook up the Arduino directly to a speaker. I had a small battery powered Bluetooth speaker which also had a 3.5 input jack. So I used that one. It is pretty much crap but for testing it is sufficient. Mainly I followed the instructions at apcmag, building the Auduino. Only with some adjustments to match what I have on hand.

The connection to audio is also shown very simply in the following screenshot:

Source: Arduino Music and Audio Projects – Mike Cook (on Google books)

The setup can be seen in the following image:

For coding I used the Auduino code from apcmag. Try setting the cardType variable to newCard if working with newer cards (they provide a modified SD card library). You also need to convert your audio to compatible WAV files. Just follow the instructions on apcmag.

What should I say: There is sound!

But as you can hear in the video the output has much noise. The speaker has no volume control so this might be the problem.

Using LM386 amplifier module with passive speaker

To attach a normal speaker, I ordered some cheap LM386 modules from ebay (5pcs 3.79 EUR). Specifications can be found on www.petervis.com. It is rated for 5V-12V power so can be powered directly from the Arduino.

I had an old Logitech speaker set lying around which I don’t need anymore so I cut the passive speaker off the other part to serve as a speaker.

The connection is also very easy:

LM386 Arduino
VCC/VDD 5V
IN Pin 9
GND GND

The second ground pin is for use with external power. You need then to connect the IN and GND from the sound source and the VCC and second GND pin from your power source.

The result can be seen in the following picture and the sound quality is already much better than the crappy bluetooth speaker:

The only thing that is not working is the volume control. On the left of the small potentiometer there is a small space where it gets a little more quiet and finally silent but everywhere else it is nearly the same volume.

Using PAM8403 amplifier module with passive speaker

Just out of curiosity I ordered another cheap amplifying module based on the PAM8403. This one is stereo and has a bigger potentiometer. It is 1 EUR on ebay for one piece. It is also rated for 5V power (2.5V-5V) and has a maximum output of 2x3W (5V, 4 Ohm)

The pinout is as easy as from the LM386 but for stereo. I soldered the pin headers. You need 3 for input, 2 for power and 4 for the output to the speakers. I used only mono output for the test:

From left to right:

  • rout-
  • rout+
  • lout+
  • lout-
  • power-
  • power+
  • input L
  • input GND
  • input R

Input GND and power- are connected so you only need one if powering directly from the Arduino.

The setup of the module looked like this:

The sound is slightly better and also louder. By using the volume control the sound goes to mute smoothly but is also clipping at about half the way.

Summary

The PAM I felt was slightly better at sound quality but only at the right volume. The advantage would be an adjustable volume but as half of the potentiometer causes distortion I can’t expose it to the user. So in the end I think I will stick with the LM386 for the build because the sound quality was good and the volume was also acceptable so it doesn’t have to be adjusted.

Further reading

Here are some more links on the topics:

Auduino build: http://apcmag.com/arduino-project-5-digital-audio-player.htm/

Audio on an Arduino nano: http://apcmag.com/play-wav-filesn-arduino-nano.htm/

Extensive post using SimpleSDaudio library, building SD card adapter, connecting speakers http://www.hackerspace-ffm.de/wiki/index.php?title=SimpleSDAudio

Builiding an adapter for microSD cards (and some other useful breadboarding tips): https://protostack.com.au/2011/09/8-breadboard-hacks/

SD card connection to arduino: https://www.flickr.com/photos/nathanchantrell/6323290363/in/photostream/

Connecting SD card to Arduino with LLC: http://physudo.blogspot.de/2013/09/sd-karten-mit-dem-arduino-beschreiben.html

SD/MMC From the ground up: http://forum.arduino.cc/index.php?topic=8314.0

Another great instruction from sparkfun – MicroSD Shield and SD Breakout Hookup Guide: https://learn.sparkfun.com/tutorials/microsd-shield-and-sd-breakout-hookup-guide