Ultimate Arduino Standalone Drum Machine

Hello, there! Whats up?
I've been developing an arduino (mega2560) drum kit which uses neither a midi controller or wave files. Instead, it uses some uint16_t arrays stored in flash memory. This data, however, is obtained from raw audio files that are manipulated with Audacity to reach a frequency of 16 kHz.
Sure it is a project with a bunch of limitations, but as it works on tests with Serial, i guess it is good. The problem is im unable to test it, as i dont have an oscilloscopes or an wave analyzer, and besides that, i can't get my hands on a low pass filter yet.
I would really appreciate some hints, and would be very glad with some one test this for me!
Thanks!!


// prescale to fast analogRead()
#define FASTADC 1
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

#define PIN_TIMER1 12       // OC2A for mega2560
#define ARR_MAX 4
#define INPUT_MAX 7         // number of drum pads
#define INPUT_LIMIT 300
#define INPUT_PERIOD 100
#define SIGNED_MAX 32767

// drum kit stored as arrays in flash memory
extern const uint16_t CYCdh_K2room_ClHat_05[1889] PROGMEM;
extern const uint16_t CYCdh_K2room_Kick_05[4025] PROGMEM;
extern const uint16_t CYCdh_K2room_OpHat_05[6414] PROGMEM;
extern const uint16_t CYCdh_K2room_Rim_05[5633] PROGMEM;
extern const uint16_t CYCdh_K2room_SdSt_05[3992] PROGMEM;
extern const uint16_t CYCdh_K2room_Snr_05[5620] PROGMEM;
extern const uint16_t CYCdh_K2room_SnrOff_05[6453] PROGMEM; 
 
uint16_t inputs[INPUT_MAX]; 
uint32_t lastMicroseconds = 0; 
volatile uint32_t counter {0}; 

// for info on hit sensors and sound they trigger
class Entry {
public:
  // counting control to set if an Entry is active or not
  uint32_t eEnd {}; 
  uint16_t eCounter {0}; 
  bool eOFF {true};
  // 16 bit pointer to array in PROGMEM
  const uint16_t* map {};
    
  // constructors 
  Entry() {} 
  Entry(const volatile uint32_t& _counter, const uint16_t& _size, const uint16_t _map[]): eEnd(_counter+_size), map(_map) {} 
  ~Entry() {}

  void turnOFF(const volatile uint32_t& _counter) { 
    
    if (_counter >= this->eEnd) {
      this->eOFF = true; 
    }   
  }
 
  // return indexed array value from PROGMEM
  uint16_t getData(const volatile uint32_t& _counter) { 
    
    uint16_t index {this->eCounter};
   this->eCounter++;  
    return pgm_read_word(this->map + index);
  } 

  // overload to help Buffer object handle entries
  Entry operator = (Entry const& _entry) {

    this->eEnd = _entry.eEnd;
    this->eCounter = _entry.eCounter;
    this->map = _entry.map;
    this->eOFF = false;
  }
}; // class Entry

// control and output calculation
class Buffer {
private:
  // array of entries 
  Entry entries[ARR_MAX];
  // 16 bit pwm signal
  uint16_t output {0};
 
public:  
  Buffer() {}
  
  // update entries array with new beat
  void setEntry(const volatile uint32_t& _temp, const uint16_t& _size, const uint16_t _arr[]) { 

    for (uint8_t i=0; i<ARR_MAX; i++) {  
      if (this->entries[i].eOFF == true) { 
        this->entries[i] = Entry(_temp, _size, _arr);
        break;
      }
      // raise ARR_MAX if entries size is too low (it kinda depends on the player's speed)
    }
  }  
  
  // set buffer's output 
  void setOutput(const volatile uint32_t& _counter) { 
 
    uint16_t preOutput {SIGNED_MAX}; 
 
    for (uint8_t i=0; i<ARR_MAX; i++) { 
      
      // shut down entries that reached their end
      this->entries[i].turnOFF(_counter); 

      uint16_t temp {this->entries[i].getData(_counter)};

      // sum and subtraction of unsigned int waves        
      if ((temp < SIGNED_MAX) && (this->entries[i].eOFF == false)) {
        preOutput -= (SIGNED_MAX - temp);
        continue;
      } 
      if (this->entries[i].eOFF == false) {
      preOutput += (temp - SIGNED_MAX);
      } 
    }   

    this->output = preOutput; 
  }

  // returns output value to update fast pwm's OCR1B pin
  uint16_t getOutput() const { return this->output; }
   
}; // class Buffer

// initiate a buffer
Buffer buf;  

void setup() {
     
  setTimers();
}

void loop() { 
   
  uint32_t microseconds {micros()};

  if (microseconds - lastMicroseconds >= INPUT_PERIOD) {

    lastMicroseconds = microseconds;
    
    for (uint8_t i=0; i<INPUT_MAX; i++) {

      uint16_t reading = analogRead(i);

      if (reading > INPUT_LIMIT) getInput(i);
    }    
  }
}
 
void setTimers() {

    pinMode(PIN_TIMER1, OUTPUT); 

    // Set fast analog read
    #if FASTADC 
    // prescale to 8
    cbi(ADCSRA,ADPS2) ;
    sbi(ADCSRA,ADPS1) ;
    sbi(ADCSRA,ADPS0) ;
    #endif
      
      cli();

    // Timer 1: fast pwm - mode 15, 40.000 Hz OCR1A = 399, presc = 1
    TCCR1A = 0;
    TCCR1B = 0;
    TCCR1A |= (1<<COM1B1) | (1<<COM1B0) | (1<<WGM11) | (1<<WGM10);  // b10000011
    TCCR1B |= (1<<CS10) | (1<<WGM13)| (1<<WGM12);                   // b00011100
    OCR1A = 399;
    OCR1B = 0;


    // Timer 2: Ctc - mode 4,  16 kHz, presc. 8, OCR2A = 124
    TCCR2A = 0;
    TCCR2B = 0;
    TCCR2A |= (1<<WGM21);
    TCCR2B |= (1<<CS21);
    OCR2A = 124;
    TIMSK2 |= (1<<OCIE2A);

    sei();
}
 
// 40 khz Timer1-PWM and 16 kHz Timer2-SAMPLING
ISR(TIMER2_COMPA_vect) {

  OCR1B = buf.getOutput();
  buf.setOutput(counter);
  counter++; 
}

void getInput(const uint8_t& index) {

  switch (index) {

    case 0:
      buf.setEntry(counter, 1889, CYCdh_K2room_ClHat_05);
      break;
    case 1:
      buf.setEntry(counter, 4025, CYCdh_K2room_Kick_05);
      break;
    case 2:
      buf.setEntry(counter, 6414, CYCdh_K2room_OpHat_05);
      break;
    case 3:
      buf.setEntry(counter, 5633, CYCdh_K2room_Rim_05);
      break;
    case 4:
      buf.setEntry(counter, 3992, CYCdh_K2room_SdSt_05);
      break;
    case 5:
      buf.setEntry(counter, 5620, CYCdh_K2room_Snr_05);
      break;
    case 6:
      buf.setEntry(counter, 6453, CYCdh_K2room_SnrOff_05 );
      break;
    default:
      break;

  }
 
}

A link for the drum arrays on github:
https://github.com/BAL3IA/DrumMech/blob/6cc69bf17a34061a8d073bf2409003b9e6f09ef3/extern_arrays16kHz.ino

How would you test it without knowing the circuit schematic and providing the audio interface? So, how would someone go about testing it? Please elaborate.

Well, sorry for that. A low pass filter has a pretty known schematic. Besides that, the only other component is a 1Mohm parallel resistor for the piezzo input. For the interface, it is pretty much plug and play.

To test it, one needs the filter and the piezzo pull up resistor.

What cut off frequency should the low pass filter have? Which pin is it connected to? How many piezo are there, and what pins are they connected to? There is no information about any of this on the Github site you linked to.

If you want people to test this for you, you shouldn't expect them to dig for information (in the code, I guess?). Because, as you have presented this, it is not trivial to duplicate. Even if the basic principles are simple.

I would say non- trivial if one is looking to compare similar responses from multiple testers! Filters can be simple passive but still not be a single-pole LP design. Single Pole Recursive Filters (dspguide.com)

It would be preferred if Op detailed the exact input circuitry AND values!

Ray

So the Arduino input pin is being subjected to damaging negative voltages.

Does your description of this project as the "ultimate drum machine" mean that the mega will never work again after using this, because that is what is going to happen without a reverse biased diode on every piezo input.

Ok, now i got what you meant. Thanks for your attention!
For the cut off frequency, since it will output drum sounds which range from very low frequencies to +-15 kHz, i thought that cutting above 20 kHz would be enough.
This filter here http://sim.okawa-denshi.jp/en/OPstool.php with two 1ohm resistors and two 10 uF capacitors cut frequencies above 16 kHz, which is fine.
The inputs are seven by now, but i was working with only one piezzo on A0 port.
That link for github was only to share the sound arrays.
And i dont expect people to dig information on this, i just thought it was much simpler to test it.

Screenshot from 2022-05-17 00-20-52
Do you think this is fine?
R1 and R2 = 1 Ohm
C1 and c2 = 10 uF
I know very little about these filters, but as i understand, im trying to achive a filter with a very fast step response.
Screenshot from 2022-05-17 00-23-36
u think for a frequency like 16 kHz cut off i can achive a smaller step response than this?

Man... wow, and how is that? I was not aware of this. Just read a bunch of stuff here on the forum, but never anything about diodes on drum triggers. Could you explain that? I understand the piezzos generate AC current, right? But how is that the analog inputs are not prepared to work with it?

For the "ultimate drum machine" part hahaha, joke is on me by now. I called that cause i never saw any edrum project like it here, even it being kinda obvious... for the program part, i think i did good, but on schematics and the electronics i know im deep in fault...

AC voltage actually not current. Very few electronic devices can cope with signals outside the voltage rails that power it. That is a basic rule in electronics. So applying negative values is going to damage things.
You maybe haven't seen this on other drum projects posted because they are mainly posted by people with an inflated sense of their own skills. Also many beginners think if it works it is a good circuit, where as in fact without suppressing the negative side of the signal every hit chips away at the input port and eventually it will fail.

The 1M resistor does suppress the maximum signal from the sensor which can be over 100V without it.
Finally it is relatively simple to get a hit / no hit reading but getting a signal proportional to the strength of the hit is tricky because you have to catch the sensor during the hit and keep making repeated readings until you reach a peak. Then there are what look like repeated hits due to acoustic reflections from the sensor mount.
The more sensors you have the more chance of not being able to track the hit signal as it develops. That is why the number of reum pads is normally limited in projects like this.

This is the sort of voltages you can get without the 1M resistor but with a diode, for a single hit.

And this is the sort of signal you can get with clamping it to 5V. Note the multiple triggers.

Low = hit

By clamping you mean with the resistor and diode?

No, I mean limiting it to 5V by driving a 2N7000 FET to pull down the input pin to ground, so you only get a logic signal on the input. This offers complete protection of the Arduino inputs.
Hence the signal inversion of low being a hit.

This is a video of a full project I did where those waveforms came from. It was for the Raspberry Pi but the hardware is applicable to an 5V Arduino project as well. The software was written in Python but can be converted to C. Back copies are available as a free PDF download from the magazine's web site.

No, not fine. Those values would overload the op amp output.

You don't understand, you don't want a fast step response for an anti-aliasing (well, actually analogue reconstruction) filter. What you want, is a steep high frequency cutoff. That is defined in the frequency domain, not in the time domain where you are observing a step response.

A fast step response is associated with a high cutoff frequency, for you this will be fixed, not variable, it is dictated by your sample rate.

1 Like

Man, I'm very much obliged. Thanks a lot! Will look into this material for sure

For the cut off frequency, I think I don't understand it as well. Is it related to my sampling rate, as I'm using it to "smooth" the pwm's signal, or to it's most basic function, cutting off undesired frequencies? Something tells me it is both

Did not find your pdf anywhere. Which magazine were you referring to?

We live in a time where guessing is not necessary:
Circuit Simulator Applet (falstad.com)

$ 1 0.000005 10.20027730826997 65 5 50 5e-11
r 160 176 240 176 0 10000
r 240 176 320 176 0 10000
r 480 192 480 256 0 5860
r 480 256 480 320 0 10000
g 480 320 480 336 0 0
w 240 128 240 176 0
c 320 176 320 256 0 1.59e-7 -0.7550757949741432 0.001
g 320 256 320 272 0 0
w 320 176 368 176 0
a 368 192 480 192 9 15 -15 1000000 -0.7550638196619633 -0.7550757949741432 100000
w 368 208 368 256 0
w 368 256 480 256 0
w 480 192 496 192 0
w 496 192 496 128 0
c 496 128 240 128 0 1.59e-7 -2.5051340848980237 0.001
O 496 192 560 192 0 0
p 160 176 160 256 0 0 0
g 160 256 160 272 0 0
170 160 176 128 176 3 20 800 5 0.2
o 16 64 0 4098 10 0.00009765625 0 1
o 15 64 0 4098 10 0.000048828125 1 1

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.