False Peek Read

Status
Not open for further replies.
Hi, I'm using the audio peek function to do beat detection and i am getting a false read.

Basically I want to blink an LED every other beat so I detect the beats and then use case: to either turn on the LED or ignore the beat.

The sounds I am using as my test has a beat of around 180ms then a gap of 240ms before the next beat (see image attached).

So, in theory I should be able to detect a peek then wait for 200ms before waiting for the next peek.

When I do have a 200ms delay and process this sound, the sketch detects 4 beats instead of 2.

With a delay of 400ms the sketches detects 3 beats.

If I up the delay to 500ms then it only detects the 2 beats.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

int led = 2;
int thresholdlevel = 15; // range 0 to 30
const int myInput = AUDIO_INPUT_LINEIN;
int delaytime = 500; //delay time in milliseconds
boolean lubdub = true;

AudioInputI2S        audioInput;         // audio shield: mic or line-in
AudioAnalyzePeak     peak_R;
AudioConnection c2(audioInput, 1, peak_R, 0);
AudioControlSGTL5000 audioShield;


void setup() {
  pinMode(led, OUTPUT);
  AudioMemory(6);
  audioShield.enable();
  audioShield.inputSelect(myInput);
  audioShield.volume(1);
  Serial.begin(115200);
  Serial.println("starting...");
}

elapsedMillis fps;

uint8_t cnt = 0;

void loop() {
  if (fps > 24) {
    if (peak_R.available()) {
      fps = 0;
      uint8_t rightPeak = peak_R.read() * 30.0;

      if ( rightPeak > thresholdlevel) {
        switch (lubdub) { 
          case 1:
            digitalWrite(led, HIGH);
            Serial.print("lub   ");

            cnt++;
            myDelay(delaytime);

            digitalWrite(led, LOW);
            lubdub = !lubdub;
            break;

          case 0:
            Serial.print("dub  ");
            Serial.println(cnt);
            myDelay(delaytime);
            lubdub = !lubdub;
            break;
        }

      }

      else {
        digitalWrite(led, LOW);
      }



    }
  }
}


void myDelay(unsigned long duration) { //my own delay to uses millis
  unsigned long starter = millis();
  while (millis() - starter <= duration)

  {
    // do nothing but wait
  }
}


falseRead.jpg
 
You don't show printed output of a run. As it is, I don't think it would help see the problem.

Add a print of rightPeak whenever you detect over thresholdlevel.

Your delay() may be getting in your way.

I don't know the operation of peak_R funcs() - but if you read it at 15 as a peak, I assume it would then reset at 16 and up, while you are in the delay. So you might see improvement if you do an: if (peak_R.available()) peak_R.read(); right after the delay()

A quick and dirty fix could be instead of an 'arbitrary delay' replace delay() with:
Code:
   fps = 0;  // use as a timed or forced exit to the loop, then reset
   while ( (100>fps) && !(peak_R.available()) ) {
      if ( (peak_R.read() * 30.0) > thresholdlevel )
        fps=1000;
   }
   fps = 0;

If that (or something sensible) fixes it - then rewrite your code so the outer test does something similar except don't enter your current code until the current peak drops.
 
Any chance you might put the source audio into a WAV file and edit this code to reproduce the problem by playing the file? That way, I and others could try reproducing the problem.
 
Hey Paul, I modified the code to read the .wav file from the SD card and still get the issue.

You will see if you run the code and have "heart.wav" on your SD card that it counts 9 beats when in reality there are only 6 in the file.

Currently the delay is set to 200ms which should be plenty of time between beats.

Here is the audio file
https://www.dropbox.com/s/z7nu6lg9tvytv2h/heart.wav?dl=0

Any thoughts?

Cheers.

Phil



Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlaySdWav           playSdWav1;     //xy=168,263
AudioAnalyzePeak         peak1;          //xy=491,482
AudioAnalyzePeak         peak2;          //xy=502,344
AudioOutputI2S           i2s1;           //xy=610,203
AudioConnection          patchCord1(playSdWav1, 0, peak2, 0);
AudioConnection          patchCord2(playSdWav1, 0, i2s1, 0);
AudioConnection          patchCord3(playSdWav1, 1, peak1, 0);
AudioConnection          patchCord4(playSdWav1, 1, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=189,662
// GUItool: end automatically generated code

int delaytime = 200; //delay time in milliseconds
boolean lubdub = true;
int led = 2;
int thresholdlevel = 15; // range 0 to 30

int cnt = 0;

void setup() {
  Serial.begin(115200);
  AudioMemory(10);
  pinMode(led, OUTPUT);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
  delay(1000);
}

// for best effect make your terminal/monitor a minimum of 62 chars wide and as high as you can.

elapsedMillis msecs;

void loop() {
  if (playSdWav1.isPlaying() == false) {
    Serial.println("Start playing");
    playSdWav1.play("heart.WAV");
    delay(10); // wait for library to parse WAV info
  }

  if (msecs > 40) {
    if (peak2.available()) {
      msecs = 0;

      float rightPeak = peak2.read() * 30;
      if ( rightPeak > thresholdlevel) {
        switch (lubdub) {
          case 1:
            digitalWrite(led, HIGH);
            Serial.print("lub   ");
             cnt++;
            Serial.println(cnt);
           
            myDelay(delaytime);

            digitalWrite(led, LOW);
            lubdub = !lubdub;
            break;

          case 0:
            Serial.print("dub  ");
            // Serial.println(rightPeak);
            cnt++;
            Serial.println(cnt);
            myDelay(delaytime);
            lubdub = !lubdub;
            break;
        }


      }
    }
  }
}








void myDelay(unsigned long duration) { //my own delay to uses millis
  unsigned long starter = millis();
  while (millis() - starter <= duration)

  {
    // do nothing but wait
  }
}
 
Last edited:
@philsplitler - did you try the notes I made in post #2? To my understanding you should expect what you are seeing if a peak higher than where you sampled was found - it would be held and found 24 millis after the delay - based on the prior sampling.
 
@defragster, I didn't try that as I am a little confused by the solution. I will see if I can figure you method out using the code snippet you provided.

Cheers.

Phil
 
@defragster, I tried your code and got 10 beats detected when I should only get 6.

Below is the code I used, I implemented it on the code that used the SD card.

Cheers

Phil

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioPlaySdWav           playSdWav1;     //xy=168,263
AudioAnalyzePeak         peak1;          //xy=491,482
AudioAnalyzePeak         peak2;          //xy=502,344
AudioOutputI2S           i2s1;           //xy=610,203
AudioConnection          patchCord1(playSdWav1, 0, peak2, 0);
AudioConnection          patchCord2(playSdWav1, 0, i2s1, 0);
AudioConnection          patchCord3(playSdWav1, 1, peak1, 0);
AudioConnection          patchCord4(playSdWav1, 1, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=189,662
// GUItool: end automatically generated code

int delaytime = 200; //delay time in milliseconds
boolean lubdub = true;
int led = 2;
int thresholdlevel = 15; // range 0 to 30

int cnt = 0;

void setup() {
  Serial.begin(115200);
  AudioMemory(10);
  pinMode(led, OUTPUT);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.5);
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
  delay(1000);
}

// for best effect make your terminal/monitor a minimum of 62 chars wide and as high as you can.

elapsedMillis msecs;
elapsedMillis fps;

void loop() {
  if (playSdWav1.isPlaying() == false) {
    Serial.println("Start playing");
    playSdWav1.play("heart.WAV");
    delay(10); // wait for library to parse WAV info
  }

  if (msecs > 40) {
    if (peak2.available()) {
      msecs = 0;

      float rightPeak = peak2.read() * 30;
      if ( rightPeak > thresholdlevel) {
        switch (lubdub) {
          case 1:
            digitalWrite(led, HIGH);
            Serial.print("lub   ");
             cnt++;
            Serial.println(cnt);
           
            myDelay();

            digitalWrite(led, LOW);
            lubdub = !lubdub;
            break;

          case 0:
            Serial.print("dub  ");
            // Serial.println(rightPeak);
            cnt++;
            Serial.println(cnt);
            myDelay();
            lubdub = !lubdub;
            break;
        }


      }
    }
  }
}








void myDelay() { 
  fps = 0;  // use as a timed or forced exit to the loop, then reset
   while ( (100>fps) && !(peak2.available()) ) {
      if ( (peak2.read() * 30.0) > thresholdlevel )
        fps=1000;
   }
   fps = 0;
}
 
@Phil > opps: nice placement - but seeing it there and reading it I see I didn't write it properly. Again - I've not used these functions or even tried to find their defined behavior . . .

The intent was to wait "SOME" time after 'peak' stopped seeing a high peak. I'm not sure if 100ms will be too long in the end or the next beat may be missed.

Code:
[B]      if ( (peak2.read() * 30.0) > thresholdlevel )
        fps=0;[/B]

Perhaps this is even better not knowing what peak2.read returns when !(peak2.available):

Code:
void myDelay() { 
  fps = 0;  // use as a timed exit to the loop, then reset
   while ( (100>fps)  ) {   // perhaps 10 to 50 is big enough to not miss the next beat
      if (peak2.available())
 [B]        if ( (peak2.read() * 30.0) > thresholdlevel )
           fps=0;[/B]
   }
   fps = 0;
}
 
You sound surprised :)

I'm only disappointed I didn't see it through the right way around on my first post as it was what I intended - but just putting code into the web browser ... is my excuse.

delay() can cause, hide or create lots of troubles. Was good to see you found elapsedMillis, that is a great way to keep the loop() flowing. The faster - less delay - there is in the loop the more responsive everything is. Hopefully you can see your way to take out the myDelay() and get back out and perhaps use another elapsedMillis variable to control re-entering the beat detect until the last beat is gone.

You can use that while loop to track how long you stay waiting for the beat to suggest what the wait should be:
Code:
void myDelay() { 
  unsigned long lastBeat=0;
  unsigned long totalWait=0;
  int countWait = 0
  fps = 0;  // use as a timed exit to the loop, then reset
   while ( (100>fps)  ) {   // perhaps 10 to 50 is big enough to not miss the next beat
      if (peak2.available())
         if ( (peak2.read() * 30.0) > thresholdlevel ) {
           lastBeat = fps;
           totalWait += fps;
           countWait++;
           fps=0;
      }
   }
   Serial.print( "Last Beat seen at ms=" );
   Serial.println( lastBeat );
   Serial.print( "wait ms=" );
   Serial.println( totalWait );
   Serial.print( "beat re-Peaks seen=" );
   Serial.println( countWait );
   fps = 0;
}

That will show how long the total wait was and how much extra you waited, as long as you wait at least (totalWait/countWait) for each new re-peak it won't leave early.

Would be fun to see the working code.
 
Great, I'll give this a go tomorrow. For now I'm just glad it's working....

Still unsure why my method of waiting 200ms before looking again didn't work but I can live with that :)

Cheers.

Phil
 
Phil - now that you have the code typed correctly - re-read this from my first post - and read through the code:

Your delay() may be getting in your way.

... if you read it at 15 as a peak, I assume it would then reset at 16 and up, while you are in the delay. So you might see improvement if you do an: if (peak_R.available()) peak_R.read(); right after the delay()

> before finding the threshold "peak" - you checked over and over - discarding all the lesser peak values.

> during the delay the returned peak value continued to it real maximum.

> However during and after the delay that highest peak value for the sound at hand was registered and stored awaiting an: if (peak_R.available()) peak_R.read();

> Doing that as noted in post #2 might have corrected the behavior for the proper delay value.

> But the proper value would not be measureable "where is the maximum peak" - as it is in my updated post 12 code - because delay halts all processing.
 
slightly OT
being a fan of detection algorithms I played with the pulses (using Matlab) and come up with the following detection algorithm
Code:
fname= '\users\walter\Downloads\6 Beats.wav';
info=audioinfo(fname);
[xx,fs]=audioread(fname);
tt=(0:size(xx,1)-1)'/fs;

% modified Taeger Kaiser (adapted to filter multiples of 60 Hz)
f0 = 60; %Hz
nn=size(xx,1);
ii=1:size(xx,1);
di = round((fs/4)/f0);
ia = max(1,ii-di);
ib = min(nn,ii+di);
yy= xx(:,1).^2 - xx(ia,1).*xx(ib,1);

% threshold detector
th = 0.1;
zz= yy>th;

% bridge short holes in pulse detection 
% find detections
dz=diff(zz);
iu=find(dz>0); %start detection
id=find(dz<0); %end detection

% some checks
if iu(1)>id(1) % we started with detection
    iu=[1,iu];  % add start pulse at beginning
end
if iu(end)>id(end) % we ended with detection
    id=[id,nn];  % add end pulse at end
end
% find holes  (two detections are separated by less than di samples)
ih = find(iu(2:end)-iu(1:end-1)<di);

% fill holes
for ii=1:length(ih)
    jj=ih(ii);
    kk=id(jj):iu(jj+1);
    zz(kk)=1;
    % adjust iu,id
    id(jj)=id(jj+1);
    id(jj+1)=-1;
end
ir = id<0; 
iu(ir)=[];
id(ir)=[];

% remove shorties (all remaining detections shorter than 2*di samples)
is = find((id-iu)<2*di);
for ii=1:length(is)
    jj=is(ii);
    kk=iu(jj):id(jj);
    zz(kk)=0;
end 
iu(is)=[];
id(is)=[];

figure(1), 
subplot(211)
hp=plot(tt,xx(:,1),tt,yy,'r',tt,0.9*zz,'b');
set(hp(1),'color',0.7*[1,1,1])
set(hp(2),'linewidth',2)
xlim(tt([1,end]))

subplot(212)
hp=plot(tt,xx(:,1),tt,yy,'r',tt,0.9*zz,'b');
set(hp(1),'color',0.7*[1,1,1])
set(hp(2),'linewidth',2)
xlim([0 0.25])

generating the following output
Beat_detection.jpg

one could then use the detector output to switch the led on and off

OK, this code may or may not be useful for the OP, but it may inspire someone else who is interested in pulse detections.
Obviously, implementation in C (to be used with Teensy) may need some work. I may even do it, as I need a click detector anyhow soon.

Edit:
Here is a version that does not use Matlab vector commands, but may be easier implemented in C
Code:
fname= '\users\walter\Downloads\6 Beats.wav';
info=audioinfo(fname);
[xx,fs]=audioread(fname);
nn=size(xx,1);
tt=(0:nn-1)'/fs;

% modified Taeger Kaiser (adapted to filter multiples of 60 Hz)
f0 = 60; %Hz
di = round((fs/4)/f0);
ia = max(1,ii-di);
ib = min(nn,ii+di);

yy=0*xx(:,1);
for ii=1:nn
    ia=max(1,ii-di);
    ib=min(nn,ii+di);
    yy(ii) = xx(ii,1)*xx(ii,1) - xx(ia,1)*xx(ib,1);
end

% threshold detector
th = 0.1;
zz=0*yy;
for ii=1:nn
    zz(ii)= yy(ii)>th;
end

% bridge short holes in pulse detection 
% find detections
id=0;
if zz(1)>0, iu =1; else iu=0; end

for ii=2:nn
    dz = zz(ii)-zz(ii-1);
    if dz==0, continue, end
    if dz < 0, id=ii; end
    if dz > 0, iu=ii; end
    if (id>0) && (iu==ii) && (iu-id < di)
        for jj = id:iu-1
            zz(jj)=1;
        end
        id=0;
    end
end

% remove shorties (all remaining detections shorter than 2*di samples)
id=0;
if zz(1)>0, iu =1; else iu=0; end

for ii=2:nn
    dz = zz(ii)-zz(ii-1);
    if dz==0, continue, end
    if dz < 0, id=ii; end
    if dz > 0, iu=ii; end
    if (iu>0) && (id==ii) && (id-iu < 2*di)
        for jj = iu:id-1
            zz(jj)=0;
        end
        iu=0;
    end
end

figure(1), 
subplot(211)
hp=plot(tt,xx(:,1),tt,yy,'r',tt,0.9*zz,'b');
set(hp(1),'color',0.7*[1,1,1])
set(hp(2),'linewidth',2)
xlim(tt([1,end]))

subplot(212)
hp=plot(tt,xx(:,1),tt,yy,'r',tt,0.9*zz,'b');
set(hp(1),'color',0.7*[1,1,1])
set(hp(2),'linewidth',2)
xlim([0 0.25])

NOTE:
Matlab starts index with 1, while C with 0
Also Theshold should be made in relation the background noise level (the 0.1 value seems good for this dataset)
 
Last edited:
Status
Not open for further replies.
Back
Top