Go Down

Topic: Signal processing analog click track to digital (peak period calculate) (Read 556 times) previous topic - next topic

frederickk

I'm looking to data from a click track and calculate the time (milliseconds) between peaks. Primarily I'm using analog (i.e. recorded) sources for the data. However, there are times when I'll be using a more digitally generated signal, like that from a Korg Volca or Teenage Engineering Pocket Operator.

Because of the variance of sources and signal strength, the floor to peak levels is dynamic. Here are screenshots showing the two primary signal types.

- Analog signal



- Digital signal




I've primarly been looking to solve this with software. Below is a class I've written (cobbled from bits sprinkled across this forum and the web) that takes signal values and looks for the peaks in order to calculate the timing. However, it's not all that accurate.

Code: [Select]

#ifndef _CLICK_TRACK_
#define _CLICK_TRACK_

#if (ARDUINO >= 100)
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

#define AVERAGE_COUNT 250
#define MIN_INTERVAL 240


class ClickTrack {
  private:
    unsigned int prev_;
    uint16_t max_;

    uint64_t startTime_;
    uint64_t endTime_;
    uint64_t timeDiff_;

    uint8_t triggerCount_;
    unsigned int average_;

  public:
    ClickTrack() {
      max_ = 0;
      startTime_ = 0;
      endTime_ = 0;
      timeDiff_ = 0;
      triggerCount_ = 0;
      average_ = 0;
    }

    update(unsigned int input) {
      unsigned int signal_ = ((signal_ * 7) + input) >> 3;

      if (signal_ > prev_) {
        // if positive slope...
        if (triggerCount_ == 0) {
          startTime_ = millis();
          triggerCount_++;
        } else if (triggerCount_ == 1 && (millis() - startTime_ >= MIN_INTERVAL)) {
          endTime_ = millis();
          timeDiff_ = endTime_ - startTime_;
          triggerCount_ = 0;
        }

      } else if (signal_ < prev_) {
        // if negative slope...
      }

      // Store previous value
      prev_ = signal_;

    }

    unsigned int get() {
      return timeDiff_;
    }

    unsigned int getAvg() {
      for (int i = 0; i < AVERAGE_COUNT; i++) {
        average_ += timeDiff_;
      }
      average_ /= AVERAGE_COUNT;
      return average_;
    }
};



#endif



I came across an approach from Sinneb that attaches an interrupt in order to listen for a RISING signal. This would be a fantastic solution, but this approach doesn't work with an analog signal. Hence, I've also looked into a hardware solution, but electronics are a out of my knowledge comfort zone.

Any code, help, guidance, nudges in the right direction would be greatly appreciated.

Thanks,
Ken

DVDdoug

I don't think you need to fine "the" peak.   

The click is the 1st signal after some silence so it looks like you just need to find any signal above some threshold.   Then, ignore the ringing for some period of time before looking for a signal again.

MarkT

Yes, I'd agree with that - you first want to look for a sudden change (above some programmable
threshold), and that defines your event.  You disable this detector for a programmable period of time
(basically just like button debouncing) upon detection, thus ignoring the rest of the transient.

Your data appears to be a power / time graph rather than a signal / time graph?  Typically with an
analog signal you need to be able to respond to changes or either sign equally to spot a transient.

You might want to trigger on the combination of a sufficiently large value and a sufficiently large
change between samples - less risk of false triggering on noise.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

ard_newbie


This would be a fantastic solution, but this approach doesn't work with an analog signal

This may work if the analog signal is sufficiently close to 5 V ( I guess you have a 5V compliant board), it deserves to be tested:

Use an digital input pin, attachInterrupt() on Rising edge and subtract timelapse Inside the interrupt function.

frederickk

Thanks for the advice to simplify, I suppose you all are right I don't actually need to detect the peaks. I've simplified as such:

Code: [Select]

if (signal_ > MIN_SIGNAL) {
  if (triggerCount_ == 0) {
    triggerCount_++;
    startTime_ = millis();
  } else if (triggerCount_ == 1 && (millis() - startTime_ >= MIN_INTERVAL)) {
    triggerCount_ = 0;
    endTime_ = millis();
    timeDiff_ = endTime_ - startTime_;
  }
}


It's simpler, and is a bit more accurate. However, there are some inputs I'm using that have a rather low signal, so I'm going to implement a simple pre-amp to boost the signal.

With that in mind, I'll also see if the pre-amp gets my signals to the 5V range so I can look into using an interrupt.

I'll post results here once I've made these changes.

Thanks for the help!
Ken

Go Up