Jean Noël
Published © CC BY-SA

Pitch Timer / Applause meter

A pitch timer & clap-o-meter, fully based on the Seeed Studio's grove system for Arduino

IntermediateFull instructions provided2 hours304
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 Studio • 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 Studio • 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 Studio • 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

wiring it together
File missing, please reupload.

Finite State Machine Diagramm

Logic description of the Arduino sketch
File missing, please reupload.

historique chatGPT

File missing, please reupload.

Code

PitchTimerApplaudimeterV2.ino

C/C++
Arduino sketch
//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

Jean Noël
19 projects • 38 followers
pedagogical director at L'école LDLC https://www.linkedin.com/in/jnlootsidebox/

Comments