Seeed
Published

Pitch Timer / Applause meter

A pitch timer & clap-o-meter, fully based on the Seeed Studio's grove system for Arduino. (Author: Jean Noël,pedagogical director at L'éco)

IntermediateProtip2 days466
Pitch Timer / Applause meter

Things used in this project

Hardware components

Grove - 12 Key Capacitive I2C Touch Sensor V2 (MPR121)
Seeed Studio Grove - 12 Key Capacitive I2C Touch Sensor V2 (MPR121)
×1
Grove - Speaker
Seeed Studio Grove - Speaker
×1
Seeed • Grove - Sound Sensor Based on LM358 amplifier
×1
Grove - WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m
Seeed Studio Grove - WS2813 RGB LED Strip Waterproof - 60 LED/m - 1m
×1
Seeed • Grove - 16X2 LCD RGB Backlight - Full Color Display
×1
Seeeduino Lotus V1.1 - ATMega328 Board with Grove Interface
Seeed Studio Seeeduino Lotus V1.1 - ATMega328 Board with Grove Interface
×1
Seeed • Wall Adapter Power Supply - 5VDC 3A Type-C
×1
Copper Foil Tape
×1
1000uF 25v capacitor
×1
acrylic tube 50/44 mm, lenght 1000 mm
×1
300x300x80 mm wood box
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Hot glue gun (generic)
Hot glue gun (generic)
Drill / Driver, Cordless
Drill / Driver, Cordless
Soldering iron (generic)
Soldering iron (generic)
Top-Handle Jigsaw

Story

Read more

Schematics

pitch timer applause meter schematic

Finite State Machine Diagramm

Code

PitchTimerApplaudimeterV2.ino

C/C++
//https://wiki.seeedstudio.com/Grove-16x2_LCD_Series/
//https://wiki.seeedstudio.com/Grove-12_Key_Capacitive_I2C_Touch_Sensor_V2-MPR121/
//https://github.com/thomasfredericks/Metro-Arduino-Wiring
//https://github.com/daPhoosa/MedianFilter


#include <Metro.h> 
#include <Adafruit_NeoPixel.h>  
#include <Adafruit_MPR121.h>
#include <rgb_lcd.h>
#include <Wire.h> 
#include <EEPROM.h>
#include <MedianFilter.h>  

#define BUZZER 5
#define PIN 6
#define MIC A0
#define MIC_TRESHOLD 300 
#define LEVEL_COEF 3
#define INTEGRATION_COEF 0.02
#define APPLAUSE_START_TRESHOLD 100
#define TIME_MIN 3
#define TIME_MAX 60

#define BP_START 0
#define BP_MODE 1
#define BP_MINUS 2
#define BP_PLUS 3

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 60

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
#define DELAYVAL 500
Adafruit_MPR121 cap = Adafruit_MPR121();

MedianFilter Filter(50, 0);
const int sampleWindow = 20; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;
unsigned int ApplausePeak = 0;   // peak-to-peak level
float ApplauseLevel = 0;
float ApplauseScore = 0;
String FirstLine="";

int Mode;

int Timing;
int HourGlassTotalSecondes;
int HourGlass;
int Minute;

bool PointSeconde;
bool Touch[12];
Metro Tick = Metro(100); 
Metro Tick1s = Metro(1000); 

enum Modes {HOURGLASS, APPLAUSEMETER,  AUTO};
enum States{INIT, SETUP, TIMER, APPMETER, APP_BEGIN, APP_END};
struct _FiniteStateMachine
{
  int State;
  int PreviousState;
  long Cycles;
  bool Change;
} FSM;

rgb_lcd lcd;
int colorR = 100;
int colorG = 100;
int colorB = 100;

//***********************************************************
void setup()
{
  //analogReference(INTERNAL);
  pixels.begin(); // INITIALIZE NeoPixel pixels object (REQUIRED)
  pixels.clear();
  pixels.show();
  
  Serial.begin(9600);
  
  EEPROM.get(0, Mode);
  if(Mode > AUTO) 
  {
    Mode=AUTO;
    EEPROM.put(0, Mode);
  }
  if(Mode < HOURGLASS) 
  {
    Mode=HOURGLASS;
    EEPROM.put(0, Mode);
  }
  EEPROM.get(2, Timing);
  if(Timing > TIME_MAX) 
  {
    Timing=TIME_MAX;
    EEPROM.put(2, Timing);
  }
  if(Timing < TIME_MIN) 
  {
    Timing=TIME_MIN;
    EEPROM.put(2, Timing);
  }
  FSM_init();
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setRGB(colorR, colorG, colorB);
  
  if (!cap.begin(0x5B)) 
  {
    Serial.println("MPR121 not found, check wiring?");
    while (1);
  }
}
//***********************************************************
void loop()
{
  TouchScan();
  FSM_compute();
}
//***********************************************************
void TouchScan()
{
  // Keeps track of the last pins touched
  // so we know when buttons are 'released'
  static uint16_t lasttouched = 0;
  static uint16_t currtouched = 0;

  // Get the currently touched pads
  currtouched = cap.touched();
  for (uint8_t i=0; i<12; i++) 
  {
    if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)) ) 
    {
      Serial.print(i); Serial.println(" touched");
      Touch[i]=true;
    }
    else Touch[i]=false;
  }
  lasttouched = currtouched;
}
//***********************************************************
void FSM_init()
{
  FSM.State = INIT;
  FSM.PreviousState = -1;
  FSM.Cycles = 0;
}
//***********************************************************
int FSM_compute()
{
  if (FSM.Cycles < 4000000000) FSM.Cycles++;
  if (FSM.State != FSM.PreviousState) FSM.Change = true;
  else FSM.Change = false;
  FSM.PreviousState = FSM.State;
  if (FSM.Change)
  {
    FSM.Cycles = 0;
  }

  switch (FSM.State)
  {
    case INIT: //---------------------------------------------------
      lcd.clear();
      lcd.setCursor(0, 0); 
      lcd.print("Applause meter");
      lcd.setCursor(0, 1); 
      lcd.print("by l'Ecole LDLC");
      pixels.clear();
      pixels.show();
      for (int i = 0; i < NUMPIXELS; i++)  // actaulise the strip with the colors
      {
        pixels.setPixelColor(i, pixels.Color(0, 0, 100));
        pixels.show();
        tone(BUZZER, 60, 10);
        delay(20);
      }
      pixels.show();
      pixels.clear(); // Set all pixel colors to 'off'
      pixels.show();
      FSM.State=SETUP;
    break;
    case SETUP: //---------------------------------------------------
      MicRecord(false);
      if (FSM.Change) 
      {
        AfficheMode();
        AfficheTiming();
        tone(BUZZER, 880);
        delay(200);
        tone(BUZZER, 440, 800);
        delay(1000);
        ApplauseLevel = 0;
        ApplauseScore = 0; 
      }
      if(Touch[BP_MODE]) 
      {
        Mode++;
        if(Mode > AUTO) Mode=HOURGLASS;
        AfficheMode();
        AfficheTiming();
        EEPROM.put(0, Mode);
        tone(BUZZER, 440, 50);        
       }
      if(Mode != APPLAUSEMETER)
      {
        if(Touch[BP_PLUS]) 
        {
          Timing++;
          if(Timing > TIME_MAX) Timing=TIME_MAX;
          AfficheTiming();
          EEPROM.put(2, Timing);
          tone(BUZZER, 440, 50);        
        }
        if(Touch[BP_MINUS]) 
        {
          Timing--;
          if(Timing < TIME_MIN) Timing=TIME_MIN;
          AfficheTiming();
          EEPROM.put(2, Timing);
          tone(BUZZER, 440, 50);        
       }
      }
      if(Touch[BP_START]) 
      {
        switch(Mode) //{HOURGLASS, APPLAUSEMETER,  AUTO};
        {
          case HOURGLASS:
          case AUTO: 
            FSM.State= TIMER; 
          break;
          case APPLAUSEMETER:
            FSM.State= APPMETER;
          break;
        }
      }
    break;
    case TIMER: //---------------------------------------------------
      MicRecord(false); 
      if (FSM.Change) 
      {
        tone(BUZZER, 440);
        delay(200);
        tone(BUZZER, 880, 500);
        delay(500); 
        HourGlass=Timing;
        Minute=0;
        AfficheTime();  
        while(Tick1s.check() == 1) {};  
      }
      if ((Tick1s.check() == 1) )
      {
        if(PointSeconde) PointSeconde=false;
        else PointSeconde=true;
        Minute--;
        if(Minute < 0) 
        {
          Minute=59;
          if(HourGlass) HourGlass--;
        }
        tone(BUZZER, 60, 20);
        AfficheTime();
        ledpixels();
      }
      if((HourGlass== 0)&&(Minute==0)) 
      {
        switch(Mode) 
        {
          case HOURGLASS:
            FSM.State=INIT;
          break;
          case AUTO: 
            FSM.State= APPMETER;
          break;
        }
      }
      if(Touch[BP_START]) 
      {
        FSM.State=INIT;
      }
    break;
    
    case APPMETER: //---------------------------------------------------
      MicRecord(true); 
      if (FSM.Change) 
      {
          tone(BUZZER, 440);
          delay(200);
          tone(BUZZER, 880, 500);
          delay(500);
      }
      ledpixels();
      AfficheScore(false);
      if(ApplauseLevel > APPLAUSE_START_TRESHOLD) FSM.State=APP_BEGIN;
      if(Touch[BP_START]) 
      {
        FSM.State=INIT;
      }
    break;
    
    case APP_BEGIN: //---------------------------------------------------
      MicRecord(true); 
      ledpixels();
      AfficheScore(false);
      if(ApplauseLevel < APPLAUSE_START_TRESHOLD) 
      {
        if (FSM.Cycles >200) FSM.State=APP_END;
      }
      else FSM.Cycles =0;
      if(Touch[BP_START]) 
      {
        FSM.State=APP_END;
      }
    break;
    
    case APP_END: //---------------------------------------------------
      MicRecord(false); 
      if (FSM.Change) 
      {
          tone(BUZZER, 880);
          delay(200);
          tone(BUZZER, 440, 800);
          delay(500);
          ledpixels();
          AfficheScore(true);
      }
      if(Touch[BP_START]) 
      {
        FSM.State=INIT;
      }
    break; 
  }
  return (FSM.State);
}
//***********************************************************
void AfficheMode()
{ 
      lcd.setCursor(0, 0); 
      lcd.print("                ");
      lcd.setCursor(0, 0); 
      lcd.print("Mode: ");
      switch(Mode)
      {
        case HOURGLASS:
          lcd.print("Hourglass");
        break;
        case APPLAUSEMETER:
          lcd.print("App.meter");
        break;
        case AUTO:
          lcd.print("Auto");
        break;
      }
}
void AfficheTiming()
{
      lcd.setCursor(0, 1);
      lcd.print("                ");
      if(Mode != APPLAUSEMETER)
      {
        lcd.setCursor(0, 1);
        lcd.print("Time: ");
        lcd.print(Timing);
        lcd.print(" minutes");
      }
}
//***********************************************************
void AfficheScore(bool Ended)
{ 
      lcd.setCursor(0, 1);
      lcd.print("                ");
      lcd.setCursor(0, 1);
      if(!Ended)lcd.print("App.Score= ");
      else lcd.print("Final Score=");
      lcd.print((int)ApplauseScore);
}
//***********************************************************
void AfficheTime()
{ 
      lcd.setCursor(0, 1);
      lcd.print("                ");
      lcd.setCursor(0, 1);
      lcd.print("HourGlass= ");
      //lcd.print(HourGlassTotalSecondes);
      if(HourGlass<10) lcd.print(' ');
      lcd.print(HourGlass);
      lcd.print(":");
      if(Minute<10) lcd.print(0);
      lcd.print(Minute);
}
//**********************************************************************************
void MicRecord(bool On)
{
  unsigned long startMillis = millis(); // Start of sample window
  unsigned int signalMax = 0;
  unsigned int signalMin = 1024;

  while (millis() - startMillis < sampleWindow)
  {
    sample = analogRead(MIC);
    
    if (sample < 1024)  // toss out spurious readings
    {
      if (sample > signalMax)
      {
        signalMax = sample;  // save just the max levels
      }
      else if (sample < signalMin)
      {
        signalMin = sample;  // save just the min levels
      }
    }
  }
  if (On) ApplausePeak = signalMax - signalMin;  // max - min = peak-peak amplitude
    else ApplausePeak = 0;
    
  if(ApplausePeak > MIC_TRESHOLD) Filter.in(ApplausePeak);
  else Filter.in(0);
  
  ApplauseLevel = Filter.getMean() * LEVEL_COEF;
  if(ApplauseLevel > 1000) ApplauseLevel=1000;
  
  if ((Tick.check() == 1) )
    {
      if(ApplausePeak > MIC_TRESHOLD) ApplauseScore+= ApplauseLevel * INTEGRATION_COEF;
    }
  if(ApplauseScore > 1000) ApplauseScore=1000;
  
  Serial.print(ApplausePeak);
  Serial.print(" ");
  Serial.print(MIC_TRESHOLD);
  Serial.print(" ");
  Serial.print(ApplauseLevel);
  Serial.print(" ");
  Serial.print(ApplauseScore); 
  Serial.print(" ");
  Serial.print(1024);
  Serial.print(" ");
  Serial.print(0);
  Serial.println();
}
//************************************************************************
void ledpixels()
{
  int LedLevel, LedScore, LedPeak;
  int R[NUMPIXELS];  // red
  int G[NUMPIXELS];  // green
  int B[NUMPIXELS];  // blue
  for(int i=0; i<NUMPIXELS; i++) // reintialise R G and B all cells at 0
  {
    R[i]=0;
    G[i]=0;
    B[i]=0;
  }
  pixels.clear(); // Set all pixel colors to 'off'

  switch (FSM.State)
  {
    case APPMETER:
    case APP_BEGIN:
    case APP_END:
      LedLevel = map(ApplauseLevel, 0, 1000, 5, NUMPIXELS-1);  // map the value to the number of pixel on the pixels
      LedScore = map(ApplauseScore, 0, 1000, 5, NUMPIXELS-1);  // map the max value to the number of pixel on the pixels
      LedPeak = map(ApplausePeak, 0, 1023, 5, LedLevel );  // map the peak to peak to the number of led -2

      for (int i = 0; i < LedLevel; i++)  //  print in red the actual value
      {
        B[i]=10;
      }
      for (int i = 0; i < LedPeak; i++)  // print in blue the clap value
      {
        R[i]=20;
      }
      for (int i = 0; i < NUMPIXELS; i++)  // actaulise the pixels with the colors
      {
        pixels.setPixelColor(i, pixels.Color(R[i], G[i], B[i]));
      }
      if (FSM.State != APP_END)
      {
        pixels.setPixelColor(LedScore, pixels.Color(255, 0, 0));  // actualise the max value
        pixels.setPixelColor(LedScore-1, pixels.Color(255, 0, 0));  
        pixels.setPixelColor(LedScore-2, pixels.Color(255, 0, 0)); 
      }
      else
      {
        pixels.setPixelColor(LedScore, pixels.Color(0,255,  0));  // actualise the max value
        pixels.setPixelColor(LedScore-1, pixels.Color(0,255, 0));  
        pixels.setPixelColor(LedScore-2, pixels.Color(0,255,  0)); 
      }
      
      break;

     case TIMER:
      HourGlassTotalSecondes= HourGlass*60 + Minute;
      LedLevel = map(HourGlassTotalSecondes, 0, (Timing*60), 5, NUMPIXELS);  // map the value to the number of pixel on the pixels
      
      for (int i = 0; i < LedLevel+1; i++)  //  print in red the actual value
      {
        if(HourGlassTotalSecondes >= (Timing*60)/2) B[i]=50;
        else if (HourGlassTotalSecondes >= (Timing*60)/3) G[i]=50;
        else if (HourGlassTotalSecondes >= 60) G[i]=30, R[i]=50;
        else R[i]=50;
      }
      for (int i = 0; i < NUMPIXELS; i++)  // actaulise the pixels with the colors
      {
        pixels.setPixelColor(i, pixels.Color(R[i], G[i], B[i]));
      }
      if(PointSeconde) 
      {
        if (HourGlassTotalSecondes >= 60)
        {
          pixels.setPixelColor(LedLevel, pixels.Color(200, 200, 200));
          pixels.setPixelColor(NUMPIXELS-1, pixels.Color(200, 200, 200));
        }
        else 
        {
          pixels.setPixelColor(LedLevel, pixels.Color(255, 0, 0));
          pixels.setPixelColor(NUMPIXELS-1, pixels.Color(255, 0,0));
        }
      }
     break;
  }
  pixels.show();  // refresh all the led pixels
}

Credits

Seeed

Seeed

102 projects • 168 followers
Seeed R&D Team

Comments