Posts Tagged ‘Arduino’

Arduino 16 Step Drum Sequencer #DIY # Arduino

Based on a Arduino, I had build a 16 Step Drum Sequencer which triggers Drum-Sounds via MIDI.

To use 3 PCF 8475 ICs was inspired by this Video:

My Code is not really ready to all things in the menu, but perhaps – it could be a good starting-point for Your next project.

This code here is not 1:1 the code in the video because I lost that version. The here is very close to the Video.

// E.Heinemann

// 30.10.2016 – Bonn Germany

// Helping sources:

// https://learn.sparkfun.com/tutorials/midi-shield-hookup-guide/example-1-clock-generator–receiver

#include <Wire.h>

#include <MIDI.h>

#include <LiquidCrystal_I2C.h>

// http://www.86duino.com/?p=8254

#include “TimerOne.h”

// http://playground.arduino.cc/Main/MsTimer2

// #in clude <MsTimer2.h>

// other interesting Project:

// http://skagmo.com

// https://www.youtube.com/watch?v=q9LyRmzGL5g

// https://github.com/Catmacey/DrumMachine

// Projects to build the Drum-Synths

// http://dmitry.gr/index.php?r=05.Projects&proj=02.%20Single-chip%20audio%20Player

// http://www.enide.net/webcms/index.php?page=pcm2pwm

// Coron DS7 http://m.bareille.free.fr/ds7clone/ds7.htm

// http://electro-music.com/forum/phpbb-files/dr_55_rimshot_clone_196.pdf

// Monotribe schematic

// Boss DR110 Scheamtics

// http://www.sdiy.org/richardc64/new_drums/dr110/dr110a1.html

// http://www.freeinfosociety.com/electronics/schemview.php?id=129

// http://www.sdiy.org/richardc64/new_drums/dr110/clap_etc.html

// Good example of needed sounds: http://delptronics.com/ldb2e.php

// http://pdp7.org/boss_dr_sync/bossdr110.html

// http://www.theninhotline.net/dr110/

// Pi Zero as SamplePlayer

// https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=127585

// Mozzi-based Drum

// https://github.com/fakebitpolytechnic/cheapsynth/blob/master/Mozzi_drumsDG0_0_2BETA/Mozzi_drumsDG0_0_2BETA.ino

#include <EEPROM.h>

// Softserial is used to send MIDI via Pin TX 2, RX 3 

#include <SoftwareSerial.h>

SoftwareSerial softSerial(2,3);

MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiA);

// #define NBR_INST          16

// #define NBR_PATTERN       16

// #define NBR_MIDI_CH       10

//Midi message define

// #define MIDI_START 0xfa

// #define MIDI_STOP  0xfc

// #define MIDI_CLOCK 0xf8

// Not implemented yet

uint16_t bpm = 125;          // Default BPM

uint16_t old_bpm = bpm;      // Default BPM

uint8_t midi_channel = 10;    // Default Midi Channel

uint8_t midi_sync    = 0;     // 1 == Slave, 0 = Master – default

uint8_t LCD_Address = 0x3B;   // LCD is integrated via PCF8574 on Port 0x3B

LiquidCrystal_I2C lcd(LCD_Address,16,2);  // simple LCD with 16×2

uint8_t address1 = 0x3C;   // Address of the PCF8574 for first 8 Buttons and LEDs

uint8_t address2 = 0x38;   // Address of the second PCF for LEDS & Buttons 9 – 16

// Button-Pins

const int buttonPinS = 4;   // Select ..near to the POT

const int buttonPinL = 5;   // Left- or Start-Button

const int buttonPinR = 6;   // Right- or Stop-Button

// Value of the POT

int     aPin3 = 3;

int     aVal3;

int     old_aVal3;

uint8_t newNote; // Variable for the MidiNote in the Menu

uint8_t count_step =  0;   // Step counter

uint8_t count_bars = 16;  // count steps per Pattern, .. with 2 PCF8574, I am able to define 16 Steps max…. virtually perhaps 32….

uint8_t count_ppqn =- 1;  // 24 MIDI-Clock-Pulse per quart note counter

// Menu

String   Modes[] = { “Instr”, “Velo”, “Speed”, “Bars”, “Note”, “Scale”, “Sync” };

uint8_t  ModesNum[] = { 0, 1, 2, 3 , 4, 5, 6 };

// Is the Sequencer running or not

boolean  playBeats = true;

// Current Menu Settings

uint8_t curModeNum = 0;

String  curMode = Modes[0]; // Sound or Play

// Instruments, Accent is not an Instrument but internally handled as an instrument

const String shortSounds[] ={ “ACC”   , “CHH”  , “OHH”   , “SN”    , “CLP” , “BD”, “RID”, “MT”, “HT”, “RIM”, “LCO”, “HCO”, “TIM”, “i14”, “i15”, “i16” };

const String Sounds[]      ={ “Accent”, “Cl-HH”, “OP-HH” , “Snare” , “Clap”, “BD”, “Ride”, “Tom”, “Rim”, “LoConga”, “HiConga”, “i13”, “i14″,”i15” };

uint8_t iSound[] ={ -1,  42, 44, 38,  39, 36, 55,43,   45, 37, 76, 77,  61, 49, 50, 51  }; // MIDI-Sound, edited via Menu 50=TOM, 44=closed HH, 

uint8_t iVelo[]  ={ 127, 90, 90, 90,  90, 90, 90, 90,  90, 90, 90, 90 }; // Velocity, edited via Menu

uint8_t inotes1[]={ 255,255,255,255,255,255,255,255,  255,255,255,255,255,255,255,255, 255 };

uint8_t inotes2[]={ 255,255,255,255,255,255,255,255,  255,255,255,255,255,255,255,255, 255 };

// Current Instrument .. the first selected

int curIns = 5; // 5 = BD

uint16_t timer_time=5000; //Time in microsecond of the callback fuction

uint32_t tempo_delay;

uint8_t bar_value[]={ 1, 2, 3, 4, 6, 8, 12, 16};

// array of notes would be better, 2 bytes in notes for every instrument

uint8_t notes1; 

uint8_t notes2;

// Old MIDI-Tricks, HH-Sounds first, Cymbals first, Snare, BD at least,  … to keep a tight beat

uint8_t oldStatus1=B00000000;

uint8_t oldStatus2=B00000000;

uint8_t bits1 = 0;

uint8_t bits2 = 0;

// Scale, Menu to change Scale is yet not implemented

uint8_t scale=1 ;//Default scale  1/16

uint8_t      scale_value[]  = {     3,      6,    12,     24,       8,       16 };

const String scale_string[] = { “1/32”, “1/16”, “1/8”, “1/4”,  “1/8T”,   “1/4T” };

// BPM to MS Conversion

// http://www.sengpielaudio.com/Rechner-bpmtempotime.htm

// myRefresh is only a counter .. the higher the lower the BPM! To display a good BPM this value has to be translated

int myRefresh = 500;

int myStep  =  0;

int veloAccent  = 100;

int velocity    = 100;

int step_position = 0;

int b = 10;

uint8_t  count_instr = 00;

boolean oldStateS=1;

boolean buttonStateS=1;

boolean oldStateL=1;

boolean buttonStateL=1;

boolean oldStateR=1;

boolean buttonStateR=1;

String curPattern1=”xxxxxxxxx”;

String curPattern2=”xxxxxxxxx”;

// ### Base-Function for PCF8574 and WIRE ### 

// PCF8574 Explosion Demo (using same pin for Input AND Output)

// Hari Wiguna, 2016

void WriteIo(uint8_t bits,uint8_t thisAddress)

{

  Wire.beginTransmission(thisAddress);

  Wire.write(bits);

  Wire.endTransmission();

}

//  ### Base-Function for PCF8574 and WIRE ### 

// PCF8574 Explosion Demo (using same pin for Input AND Output)

// Hari Wiguna, 2016

uint8_t ReadIo( uint8_t address)

{  

  WriteIo(B11111111, address);        // PCF8574 require us to set all outputs to 1 before doing a read.

  Wire.beginTransmission(address);

  // Wire.write(B11111111);

  Wire.requestFrom((int)address, 1);  // Ask for 1 byte from slave

  uint8_t bits = Wire.read();         // read that one byte

  Wire.requestFrom((int)address1, 1); // Ask for 1 byte from slave

  Wire.endTransmission(); 

  return bits;

}

// ########################### Show Notes and current Step via LEDs #########################

void showStep ( int mystep, uint8_t address1, uint8_t address2, uint8_t notes1, uint8_t notes2  )

{

  uint8_t bitdp1 = notes1;

  uint8_t bitdp2 = notes2;

  // Current Step would be only shown if played

  if ( playBeats==true ) { 

   if ( mystep <  8 ) bitClear(bitdp1, step_position);

   if ( mystep >= 8 ) bitClear(bitdp2, (step_position-8));

  }

  WriteIo( bitdp1, address1);

  WriteIo( bitdp2, address2);

}

// ############################ Play the Midi-Notes ##################################### 

void Update_Midi() {

  // first 8 beats

   if (playBeats==true)

   {

    veloAccent = 100; // Normal Velocity by default

    // first Byte or first 8 Hits

    if ( step_position<8 ) // “song_position” is the current step 

    { // play notes1

       // Accent set?

       if (bitRead( inotes1[0],step_position ) == 0 ){

         // Accent is set

         veloAccent = iVelo[0];

       } 

       for (int i = 1; i < count_instr; i++) // loop through all instruments .. But ignore Accent with 0

       {

         if (bitRead( inotes1[i],step_position ) == 0) 

         {

           velocity  = round(iVelo[i] * veloAccent / 100);

           if ( velocity > 127 ) { velocity = 127; }

           midiA.sendNoteOff( iSound[i], 0, 1);       

           midiA.sendNoteOn(  iSound[i], velocity ,1);

         }  

       }

    }

    // second Byte or second 8 Steps

    if ( step_position >= 8 ) // bitClear(bitdp2, (a-8));

    { // play Notes2

       if (bitRead( inotes2[0],step_position ) == 0)

       { // Accent is set

         veloAccent = iVelo[0];

       } 

       for (int i = 1; i < count_instr; i++) // loop through all instruments .. but ignore the Accent with 0

       {

         if (bitRead( inotes2[i],(step_position-8) ) == 0) {

           velocity  = round(iVelo[i] * veloAccent / 100);

           if ( velocity > 127 ) { velocity = 127; }

           midiA.sendNoteOff( iSound[i], 0, 1       );       

           midiA.sendNoteOn( iSound[i], velocity ,1 );

         }  

       }   

    }

  }

}

// ############################## Select another Instrument and update all variables and LCD ###########

void Select_Instr( int newIns ) {

 //  if ( newIns != curIns ) {

    curIns = newIns;

    if ( newIns >= count_instr ) {

      curIns = count_instr-1; 

    }   

    lcd.setCursor(0,0);

    lcd.print( curMode  + ” ” + Sounds[ curIns ]  + ” ” + iVelo[ curIns ] +”    “);

    curPattern1=””;

    curPattern2=””;

    notes1 = inotes1[curIns];

    notes2 = inotes2[curIns]; 

    // Show new Pattern in Display

    for (int i = 1; i < 8; i++)

    {

      if (bitRead( notes1, i ) == 0) { 

        curPattern1 = curPattern1 + “X”;

        } else

       { 

        curPattern1 = curPattern1 + “-“;

        } 

      if (bitRead( notes2, i ) == 0) { 

        curPattern2 = curPattern2 + “X”;

        } else

       { 

        curPattern2 = curPattern2 + “-“;

        } 

      }

    lcd.setCursor(0,1);

    lcd.print( curPattern1 + curPattern2);

}

// ################################ Check the Potentiometer ##############################

void Check_POT() {

      // Check the POT — Menu-Functions

    aVal3 = round(analogRead(aPin3)/8);

    if ( aVal3 != old_aVal3  )

    { 

      // Correction

      myStep = myStep + 120; 

      if ( curModeNum==0 ) // Play and Sound-Select

      {

          Select_Instr( round(aVal3/8) );      

      }

      if ( curModeNum==1 ) // Velocity

      {

          if ( aVal3 > 127 ) { aVal3=127;}

          lcd.setCursor(0,0);

          lcd.print( curMode + ” ” + String(aVal3) + ” ” + Sounds[ curIns ] + ”     “);

          iVelo[ curIns ] = aVal3;  

      }

      if ( curModeNum==2 ) // Speed

      {

          old_bpm = bpm;

          bpm = round(analogRead(aPin3)/4 + 40);

          lcd.setCursor(0,0);

          lcd.print( curMode + ” ” + String(bpm) +” bpm   ”  );

          tempo_delay = 60000000 / bpm / 24;

      }

      if ( curModeNum==3 ) // Bars

      {

          if ( aVal3 > 127 ) { aVal3=127;}

          count_bars = round(aVal3 / 8)+1;

          if ( count_bars>16 ) 

            { count_bars=16; } // max of this setup

             lcd.setCursor(0,0);

          lcd.print( curMode + ”  ” + String( count_bars ) +”      ”  );

      }

      if ( curModeNum==4  && curIns>0 ) // Midi-Notes, perhaps we find a good Midi-Reference to replace Note and Name

      {   // 30 to 70 make sense

          newNote = round(aVal3/3) + 25;

          if ( newNote > 70 ) { newNote =70;}

          if ( newNote < 30 ) { newNote =30;}

          lcd.setCursor(0,0);

          lcd.print( curMode + ” ” + String(newNote) + ” ” + Sounds[ curIns ] + ”     “);

          iSound[ curIns ] = newNote; 

      }

      if ( curModeNum==5 ) // Scale

      {

          if ( aVal3 > 127 ) { aVal3=127;}

          scale = round(aVal3 / 16 );

          if ( scale >= sizeof(scale_string) ) { scale = sizeof(scale_string)-1 ; }

          lcd.setCursor(0,0);

          lcd.print( curMode + ”  ” + scale_string[ scale ] +”      ”  );

      }

      old_aVal3 = aVal3;    

    }

}

// ################################ Check Menu – Buttons –#######################

void Check_MENU() {

    // Check if Menu-Button or Start or Stop was pressed

    buttonStateS = digitalRead(buttonPinS);

    buttonStateL = digitalRead(buttonPinL);

    buttonStateR = digitalRead(buttonPinR);

    if ( oldStateS==1 && buttonStateS == 0)

    {

      curModeNum = curModeNum +1;

      if ( curModeNum >(5-playBeats) ) { curModeNum = 0 ;} 

      curMode= Modes[curModeNum];

      lcd.setCursor(0,0);

      if (curModeNum != 3 && curModeNum != 2 && curModeNum != 5)

      {

        lcd.print( curMode  + ” ” + Sounds[ curIns ] +” ” + buttonStateS  + ”      “);

      } else

      {

        if ( curModeNum == 3 ) {

          lcd.print( curMode + ”  ” + String( count_bars ) +”      ” );

        } 

        if ( curModeNum == 2 ) { // Speed

          lcd.print(  curMode + ” ” + String(bpm) +” bpm    ”   );

        }

        if ( curModeNum == 5 ) {

          lcd.setCursor(0,0);

          lcd.print( curMode + ”  ” + scale_string[ scale ] +”      ”  );

        }

      } 

    }

    if ( playBeats == false && buttonStateL != oldStateL && buttonStateL == LOW )

    {

      // Start-Button

        step_position=0;

        playBeats = true;

        lcd.setCursor(0,1);

        lcd.print( “Started Beating “);

        // MIDI-Clock

        midiA.sendRealTime(MIDI_NAMESPACE::Start);

    }

    if ( playBeats == true && buttonStateR != oldStateR && buttonStateR == LOW )

    {

      // Stop-Button

        step_position=0;

        playBeats = false;

        lcd.setCursor(0,1);

        lcd.print( “Stopped Beating “);

        midiA.sendRealTime(MIDI_NAMESPACE::Stop);

    }

    oldStateS = buttonStateS;

    oldStateL = buttonStateL;

    oldStateR = buttonStateR;

}

// ############################## Check the Buttons for Notes #################################

void Check_DrumButtons() {

  //– Don’t do anything unless they press a switch —

  if ( bits1 != B11111111 && oldStatus1 != bits1 ) // Unless they’re all high…

  {  

    //– Find lowest pressed switch —

    for ( byte bitIndex = 0; bitIndex < 8; bitIndex++ )

      if (bitRead(bits1, bitIndex) == 0) {

        if ( oldStateL == HIGH ) {

         if (bitRead(notes1, bitIndex) == 1) {

           bitClear( notes1, bitIndex ); 

          } else {

            bitSet( notes1, bitIndex );

          } 

          inotes1[curIns] = notes1;

        } else // Instrument_Select

        {  

          Select_Instr( bitIndex );

        }

        exit;

      }    

  }

  if ( bits2 != B11111111  && oldStatus2 != bits2 ) // Unless they’re all high…

  {

    //– Find lowest pressed switch —

    for (byte bitIndex = 0; bitIndex < 8; bitIndex++)

      if (bitRead(bits2, bitIndex) == 0) {

       if ( oldStateL == HIGH ) // && oldStateS == HIGH )  // Beide Buttons L und S sind nicht gedrückt!

        {

          if (bitRead(notes2, bitIndex ) == 1) {

             bitClear(notes2, bitIndex ); 

           } else {

             bitSet( notes2, bitIndex ); 

           }  

          inotes2[curIns] = notes2;

             // Serial.println (“Bit Cleared”);

          // } 

           // ExplosionAnimation(bitIndex, address2);

        } else

        {

          if (bitIndex == 0 ) // left 2 Bits to change the Scale

          { 

            if ( scale > 0 ){ scale = scale-1; }

            lcd.setCursor(0,0);

            lcd.print( Modes[5] + ”  ” + scale_string[ scale ] +”      ”  );

          }

          if (bitIndex == 1 ) // left 2 Bits to change the Scale

          { scale = scale+1;

            if ( scale >=4) scale=0; // only llop through straight scales 

            lcd.setCursor(0,0);

            lcd.print( Modes[5] + ”  ” + scale_string[ scale ] +”      ”  );

          }

          if (bitIndex==6 ) // left 4 Bits to change the Scale

          {

             old_bpm = bpm;

             bpm = bpm – 2;

             if (bpm < 40) {bpm=40;}

             lcd.setCursor(0,0);

             lcd.print( Modes[2] + ” ” + String(bpm) +” bpm   ”  );

             tempo_delay = 60000000 / bpm / 24;

          }

          if (bitIndex==7 ) // add some speed to BPM

          {

             old_bpm = bpm;

             bpm = bpm + 2;

             if (bpm > 300) {bpm=300;}

             lcd.setCursor(0,0);

             lcd.print( Modes[2] + ” ” + String(bpm) +” bpm   ”  );

             tempo_delay = 60000000 / bpm / 24;

          }

        } 

        exit;

      }    

  }

  oldStatus1 = bits1;

  oldStatus2 = bits2; 

}

// #### Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup Setup #####

void setup() {

  pinMode(13, OUTPUT);

  lcd.init(); // initialize the lcd 

  // Print a message to the LCD

  lcd.backlight(); // I have not enabled that pin… at my box, the backlight is always on, via hardwire.

  lcd.print(“hman-Projects.de”);

  count_instr = sizeof(iSound);

  Wire.begin(); // Arduino UNO uses (SDA=A4,SCL=A5)

  // Wire.begin(0,2); // ESP8266 needs (SDA=GPIO0,SCL=GPIO2)

  pinMode(buttonPinS, INPUT); // Selecct Button

  pinMode(buttonPinL, INPUT); // Left- or Start-Button

  pinMode(buttonPinR, INPUT); // Right- or Stop-Button

  // virtual Pull-Up-Resitor activated, Pins are by default “HIGH”

  digitalWrite(buttonPinS, HIGH); 

  digitalWrite(buttonPinL, HIGH);

  digitalWrite(buttonPinR, HIGH);

  notes1 = B11111111; // Bitwise per Instrument, No Note equals “1”, note to play equals “0” !

  notes2 = B11111111;

  midiA.begin(MIDI_CHANNEL_OMNI);    // Midi-Input for Sync

  tempo_delay = 60000000/bpm/24;     // delay in Microseconds ….

  Timer1.initialize( tempo_delay );  // initialize timer1, and set a 1/2 second period

  Timer1.attachInterrupt(callback);

}

void Read_Switches() {

    bits1 = ReadIo( address1 ); // Read all switches

    bits2 = ReadIo( address2 ); // Read all switches

}

// ############################################ void loop #################################################

void loop() {

  // b is a simple Counter to do nothing  

  b ++;

  if (b > 4) // read the IO-Pins every 5 cycles

  { 

    // Update the LEDs

    showStep (step_position, address1, address2, notes1, notes2 );

    Check_MENU();

    b = 0;

  }

  myStep ++; // This is second counter to find the right time to get the new values from the BUTTONS and the POT

  if ( myStep >= myRefresh  ) {

    myStep = 0;

    Read_Switches();

    Check_POT();

    Check_MENU();

    Check_DrumButtons(); 

    // lcd.setCursor(0,1);

   //  lcd.print (“Step:” + String(step_position) + ” ” + scale_value[scale] + ” ” + String(scale) + ”   ” );

  } 

}

void callback() // Callback from Timer1

  if ( old_bpm != bpm ) {  Timer1.initialize(tempo_delay); old_bpm = bpm; }

  count_ppqn++;    

  if (count_ppqn >= scale_value[scale] )

  {  

    step_position++;

    if ( step_position >= count_bars ) {

      step_position=0;

    }

    Update_Midi();

    count_ppqn =0;

    digitalWrite(13, digitalRead(13) ^ 1);

  }     

  // lcd.setCursor(0,1);

  // lcd.print (“Step:” + String(step_position) + ” ” + scale_value[scale] );

}

//////////////////////////////////////////////////////////////////

//This function is call by the timer depending Sync mode and BPM//

//////////////////////////////////////////////////////////////////

void Count_PPQN(){

//—————–Sync SLAVE——————-//  

/*  if(midi_sync){

    timer_time=5000;

    if (midiA.read())                // Is there a MIDI message incoming ?

     {

      byte data = midiA.getType();

      if(data == midi::Start ){

        if(playBeats==true) //mode==MODE_PATTERN_PLAY || mode==MODE_PATTERN_WRITE || mode==MODE_INST_SELECT)

        {

          playBeats=true;

          // play_pattern = 1;

          count_ppqn=-1;

        }

        //if(mode==MODE_SONG_PLAY || mode==MODE_SONG_WRITE){

        //  play_song = 1;

        //  count_ppqn=-1;

        //  song_position=0;

        // }

      }

      else if(data == midi::Stop ) {

        playBeats=false;

        // play_pattern = 0;

        // play_song = 0;

        // count_step=0;

        step_position=0;

        count_ppqn=-1;

        // song_position=0;

      }

      else if(data == midi::Clock && playBeats==true) //(play_pattern == 1 || play_song == 1))    case midi::Clock

      {

        count_ppqn++;

        count_step=count_ppqn/scale_value[scale];

        if(count_ppqn>=(count_bars * scale_value[scale])-1) {

          count_ppqn=-1; 

          step_position++;     

          // song_position++;

          // if (song_position==16) song_position=0;

          if ( step_position >= count_bars ) { step_position=0; }

          // Play Notes!!

          Update_Midi();

          // Request news from Drum-Buttons

          Check_DrumButtons(); 

          // Update the LEDs 

          showStep (step_position, address1, address2, notes1, notes2 );  

        }

        // if (count_ppqn>1) led_flag=0;//Led clignote reste ON 1 count sur 6

        // if (count_ppqn<=1) led_flag=1; 

        // led_flag=!led_flag;  

      }

      // if (data==MIDI_CLOCK && (play_pattern == 0 || play_song==0)){

      //  count_led++;

      //  if(count_led==12){

      //    count_led=0;

      //    led_flag=!led_flag;

      //  }

      //}

    }

  }

  //—————–Sync MASTER——————-//

  if(!midi_sync){

  */

    // timer_time=2500000/bpm;

    // midiA(MIDI_CLOCK);

     digitalWrite(13, digitalRead(13) ^ 1);

    /* 

    lcd.setCursor(0,1);

    lcd.print( ” Timer” + String( count_ppqn ) ); 

    // midiA.sendRealTime(MIDI_NAMESPACE::Clock);

    if( playBeats==true ) //play_pattern||play_song)

    {   

      count_ppqn++;    

      count_step=count_ppqn/scale_value[scale];   

      if(count_ppqn>=(count_bars*scale_value[scale])-1){

        count_ppqn=-1;

        step_position++;

        // if (song_position==16) song_position=0;

        if ( step_position>= count_bars ) { step_position=0; }

        // Play Notes

        Update_Midi();

        Check_DrumButtons(); 

        // Update the LEDs 

        showStep (step_position, address1, address2, notes1, notes2 );

      }

      // if (count_ppqn>1) led_flag=0;//Led blink 1 count on 6

      // if (count_ppqn<=1) led_flag=1; 

      // led_flag=!led_flag;

    }

    else if(playBeats==false) // !play_pattern &&!play_song)

    {

      count_ppqn=-1;

      step_position=0;

      // count_led++;

      // song_position=0;

      // if(count_led==12){

      //  count_led=0;

      //  led_flag=!led_flag;

      // }

    }

//   }

*/

}

ESP8266 Stompbox ( Horsekick oder Finhol oder SPD::KICK ) Nachbau

Als Gitarrist und Elektroniker will man doch mal den Beat mittrampeln. Also hatte ich mir schon einige Projekte im Web angeschaut und dann Ende Oktober eine Audio-Library zum ESP8266 gefunden. Alternativen nur mit einem Piezo oder MIkrofon in einer Pappschachtel sollen auch funktionieren, – gefielen mir aber nicht.
Ein ESP8266-Development-Board gibt es für unter 10 €, der Adafruit DAC mit 3 Watt Amp hatte ich schon gekauft, – liegt auch bei 10 €.
Das OLED-Display, weitere Kleinteile wie Piezo, Taster und ein paar kleine Bauteile machen nochmal 15€. Das Holzgehäuse  bauen, bearbeiten und die Auswahl der Sounds dauert auch  länger. Als Powersupply dient eine ältere USB-Powerbank.
Wenn man alle Teile extra kaufen muss, dann rechnet sich das Projekt auf Grund von den Kosten und dem Aufwand aus meiner Sicht nicht. Ich hatte aber alle Teile herumliegen und wollte es einfach mal ausprobieren.

So sah die erste Version aus, die dann aber später in 2 Teile durchgetrennt wurde. Somit konnten dann die Bewegungen der Ferse und somit die Geräusche und Fehltrigger minimiert werden.
Die Basis bildet ein 3-Schicht Holz ca. 28 x 14cm. Unter der Unterlegscheibe befindet sich ein Hohlraum für den ESP8266 und den Piezo. Das OLED-Display ist leicht versetzt eingebaut. Rechts bzw. hier dann oben befindet sich in einer Einbuchtung ein Taster zur Soundwahl und links in der gegenüberliegenden Einbuchtung die Klinkenbuchse.

Hman-Stompbox

Halbfertige Alternativen zum ESP8266 könnten auch Raspi-Projekte wie Samplerbox oder fertige Platinen wie Wavetrigger von Robertronics sein. Beide bieten aber keinen Anschluss eines Piezos!
Raspi-Projekte könnten final noch teurer werden, denn der Zeitaufwand zur Einrichtung von Linux könnte größer ausfallen.

Als fertige Lösung gefiel mir das Ortega Horsekick Pro  ca. 170€ und vom Datenblatt und den Sounds her das Roland Boss SDP::Kick oder SDP::Wave für über 200€.

Zurück zum “selbermachen”.

Zur Ausgabe der Sounds verwende ich die fertige Audio-Library von Earle F. Philhower, III
https://github.com/earlephilhower/ESP8266Audio

Das ZIP muss entpackt werden und dann nach /Arduino/libraries/ verschoben werden.

Die Anschaltung des DACs and den ESP8266 erfolgte wie in https://github.com/bbx10/SFX-web-trigger. Der DAC ist das “Adafruit I2S 3W Class D Amplifier Breakout – MAX98357A”.

Der Lautsprecher-Ausgang des DAC geht bei mir auf die beiden äußeren Enden eines 50kOhm-Trimmer, an dem dann der Mittelpin eines 6.3mm Klinkenbuchse über einen weiteren Widerstand hängt. Die Masse der Buchse ist auf einen “Aussenpin” des Trimmers gelegt. Nachträglich habe ich zwischen den Mittelabgriff des Trimmer und “+” der Klinkenbuchse noch einen Widerstand eingebaut … ca. 10kOhm und einen Kondensator zur Entkopplung von Gleichspannungen, – also in Reihe mit dem Widerstand.

Nach dem Zusammenbau von DAC, ESP8266, einigen Kondensatoren zur Spannungsstabilisierung und dem PIEZO fehlte nur noch die Abstimmung der Empfindlichkeit, denn der Beat soll sowohl mit sehr festen Fußwerk als auch Sportschuhen oder auch ohne Schuhe nur mit Socken funktionieren.

Nach zwei frustrierenden Abenden mit Tests verschiedener Einstellungen hatte ich dann ein einfaches Testscript gefunden und dieses funktionierte auf Anhieb ausreichend gut. Es  bildet nun die Grundlage für das eigene Stompbox-Script. Statt einen MIDI-Sound zu triggern, wird ein Sample aus dem “ProgMem” des ESP8266 ausgegeben.

Vorlage für das Testscript:

https://github.com/RyoKosaka/drums/blob/master/arduino/piezo_peak_test_MIDI/piezo_peak_test_MIDI.ino

Die Samples, die im “ProgMem” abgelegt werden, sollten unterschiedlich sein, so dass ich bei den nächsten Sessions die besten Sounds auswählen und die “schlechten” gegen andere tauschen kann.
Also habe ich in meiner Sample-Sammlung und Synths nach Percussion und Kickdrum-Samples gesucht. Diese gekürzt, auf Mono und 16 Bit gesetzt, – 44.1kHz.

Earle Philhower erklärt ausreichend gut, wie man die 16Bit-Wav-Files einfach in HEX-Files konvertrieren kann, um sie in seinen Libraries als “Audio-Source-ProgMem” zu verwenden. Ich hatte dazu aber eine eigene Javascript-Webpage gebaut, die mir die Sounds in h-Files convertiert. Die WAV-Files müssen als HEX-Files vorliegen.
Die Sound-hex-Files sehen dann etwas so aus:

const unsigned char cajonbass[] PROGMEM = {0x52,0x49,0x46,0x46,0x1a,0x6c,0x00,0x00,0x57,0x41,0x56,0x45,0x66,0x6d,0x74,0x20
,0x10,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x44,0xac,0x00,0x00,0x88,0x58,0x01,0x00....
};

Ein OLED128x64Pixel (grüne Fahne) (von Amazon) wurde an den ESP8266-Pins “D1” / “D2” angeschaltet., an “D3” liegt ein Taster, wenn er geschlossen ist, zieht er den Pin D3 dann gegen Masse um die Sounds zu wechseln.

Final passen 1 MByte in das System. Ich konnte damit ca. 20 sehr kurze Sounds laden.

Die Library für das OLED-Display:
https://github.com/squix78/esp8266-oled-ssd1306
Diese wird aber innerhalb der Arduino IDE zur Installation angeboten und nennt sich dort:
“ESP8266 and ESP32 OLED Driver for SSD1306 Display by Daniel Eichhorn, Fabrive Weinberg..:”

Urspünglich war statt des Tasters ein Rotary Encoder und ein kompelexeres Menü geplant. Auch die Darstellung von kleinen Icons zu den Instrumenten habe ich wieder verworfen, denn diese verbrauchten auch zusätzlichen RAM.

Eine Idee wäre die Auslagerung der Samples in externen Storage.
Die Umwandlung der Samples in andere komprimierte Formate würden zusätzliche Zeit zur Dekodierung und somit zu Latenzen beim Triggern führen.

Das Holzgehäuse besteht nun aus 2 Teilen, einem Teil mit der Elektronik und dem Piezo und einem Stück Holz. Beide Teile sind miteinander über Gummistücke verbunden und ergeben die Auflagefläche für einen Fuß. Der passive Teil liegt unter der Ferse und der aktive Teil mit dem Piezo kann sicher mit dem Fuß getrigggert werden.

Unten drunter gibt es jeweils Gummifüße gegen das Verrutschen. (Bilder folgen)
In einer ersten Version hatte ich nur eine große Holzplatte  eingeplant, musste diese Version aber verwerfen, denn schon die Bewegung des Fußes auf der Platte sorgte für Fehltrigger oder Doppeltrigger beim Runtertreten und beim Abheben des Fußes.

Technisch machen es sich die Hersteller einfach und liefern nur das aktive Teil, auf das man treten soll. Ergonomisch ist das aber nicht, denn man muss zum niedertreten den Fuß weit anheben.

Hier das vorläufige Arduino-Script:

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "AudioFileSourcePROGMEM.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2SDAC.h"

#include <Wire.h>  
#include "SSD1306.h"

// WAV-Files
// WAV-Files, MONO 44.1 KBIT/s 16Bit
#include "fatbass.h"
#include "softkick.h"
#include "espkik03.h"
// #include "espkik04.h"
#include "cajonbass.h"
#include "espkik06.h"
#include "espkik07.h"
#include "espkik08.h"
#include "espkik09.h"
#include "espkik10.h"
#include "espkik11.h"
#include "espkik12.h"
#include "espkik13.h"
#include "espkik14.h"
#include "espkik15.h"
// #include "espkik16.h"
#include "espkik17.h"
#include "espkik18.h"
#include "espkik19.h"
#include "espkik20.h" 
#include "espkik22.h"
#include "ride1.h"
// #include "espkik28.h"

AudioGeneratorWAV *wav;
AudioFileSourcePROGMEM *file;
AudioOutputI2SDAC *out;

unsigned short buttonState = 0; // Selectbutton for the sounds
unsigned short oldButtonState = 1;
boolean piezo = false;
int maxPiezo = 400;
int gaindevider = 380;

unsigned short s = 0;
unsigned short s_old = 20; // Sample-ID


const char *samplenames1[] ={"0 Fat",
     " 1 Soft    " , " 2 Cajon  "
   , " 3 Rock   " , " 4 Ride  "
   , " 5 Cajon  " , " 6 Arabic"
   , " 7 Acoust " , " 8 Acoust"
   , " 9 R908   " , "10 R809  "
   
   , "11 Tambo- ", "12 Christ."
   , "13 Cabasa ", "14 Guiro  "
   , "15 Rattle ", "16 Rattle",
   , "16 Cow    ", "17 Claves "
   , "18 Clap   ", "19 Side   "
   , "20 Hihat  ", "21 Ride   "
   
  };

const char *samplenames2[] ={"Drum",
     "    Kick",  "    Bass"
   , "    Kick",  "    Cymbal"
   , "    Bass",  "    Bass"
   , "    Stomp", "    Stomp"
   , "    Synth", "    Synth"
   , "    Latin",  "    Latin"
   , "rine Man",  "    Bells"
   , "    Latin", "    Latin" 

   , "    Bell",  "         "
   , "    Clap",  "    Stick"
   , "   Closed","    Cymbal"

  };  


unsigned short samplecount = 21; // Anzahl der Samples für den Select-Button
SSD1306  display(0x3c, 5, 4); //Labeled D1, D2
const int buttonPin = 0; // Labeled D3, Pin is high by default, button between this pin and GND to switch the sounds


void setup() {
  WiFi.forceSleepBegin();
  display.init();
  // display.flipScreenVertically();
  display.setContrast(255);

  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_24);
  display.drawStringMaxWidth(0 , 0 , 128, " Heinemann");
  display.drawStringMaxWidth(0 , 24 , 128,"  Stomper");
  // display.drawXbm(20, 30, bassdrum_width, bassdrum_height, bassdrum_bits);
  display.display();

  
  // pinMode(BUILTIN_LED, OUTPUT);
  Serial.begin(115200);
  delay(3000);
  
  file = new AudioFileSourcePROGMEM( fatbass, sizeof(fatbass) ); // Short Demosound after Booting
  out = new AudioOutputI2SDAC();
  out->SetGain(0.02);
  wav = new AudioGeneratorWAV();
  wav->begin(file, out);
  
  pinMode( buttonPin, INPUT); // GPIO 0 == D03 on ESP8266 !!

}


void loop() {
  
  if ( s != s_old ) {
    if (s >= samplecount) {
      s=1;
    }
    s_old = s;
    display.clear();
    display.setTextAlignment(TEXT_ALIGN_LEFT);
    display.setFont(ArialMT_Plain_24);
    display.drawStringMaxWidth(0 , 0 , 128, samplenames1[s]);
    display.drawStringMaxWidth(0 , 24 , 128, samplenames2[s] ); // + (String) s);
    display.display();
  } 
  
  int sensorValue = analogRead(A0);
  int MAX;

  if (sensorValue > 20 &&  piezo == false) {
    int peak1 = analogRead(A0);
    MAX = peak1;
    delay(1);
    int peak2 = analogRead(A0);
    if (peak2 > MAX) {
      MAX = peak2;
    }
    // delay(1); // absichtlich deaktiviert!!
    int peak3 = analogRead(A0);
    if (peak3 > MAX) {
      MAX = peak3;
    }
    delay(1);
    int peak4 = analogRead(A0);
    if (peak4 > MAX) {
      MAX = peak4;
    }
   
    delay(1);
    int peak5 = analogRead(A0);
    if (peak5 > MAX) {
      MAX = peak5;
    }
    // delay(1); // absichtlich deaktiviert !!
    int peak6 = analogRead(A0);
    if (peak6 > MAX) {
      MAX = peak6;
    }
    if(MAX >= maxPiezo){
      MAX = maxPiezo;
    }

//     MAX = map(MAX, 10, maxPiezo, 1, 127); // entfaellt bei mir

    if(MAX <= 1){
      MAX = 1;
    }

    midiNoteOn( MAX );
    Serial.println( MAX);
    piezo = true;

    // teh next block could be replaced by delay(30); or anything similar...
    delay(1); //mask time
    Serial.println( 0);
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
    Serial.println( 0 );
    delay(1); //mask time
  }
  if (sensorValue <= 0 && piezo == true) {
    piezo = false;
  }

  
  buttonState = digitalRead(buttonPin);
  if ( buttonState == LOW &&  oldButtonState == HIGH ) {
    oldButtonState = buttonState;  
    s = s +1;    
    oldButtonState = buttonState;  
    delay(50); // debounce
  } else {
    oldButtonState = buttonState;  
  }

  if (wav->isRunning()) {
    if (!wav->loop()) { 
      wav->stop(); // wenn nicht mehr richtig looped dann stoppen
    }
  }  
}

void midiNoteOn( int midiVelocity){
  if (midiVelocity > 0) {
  switch (s) {
      case 0:
        file = new AudioFileSourcePROGMEM( fatbass, sizeof(fatbass) ); 
        break;
      case 1:
        file = new AudioFileSourcePROGMEM( softkick, sizeof(softkick) );
        break;
      case 2:
        file = new AudioFileSourcePROGMEM( softkick, sizeof(softkick) );
        break;
      case 3:
        file = new AudioFileSourcePROGMEM( espkik03, sizeof(espkik03) );
        break;

       case 4:
         file = new AudioFileSourcePROGMEM( ride1, sizeof(ride1) );
         break;
      case 5:
        file = new AudioFileSourcePROGMEM( cajonbass, sizeof(cajonbass) );
        break;
      case 6:
        file = new AudioFileSourcePROGMEM( espkik06, sizeof(espkik06) );
        break;

      case 7:
        file = new AudioFileSourcePROGMEM( espkik07, sizeof(espkik07) );
        break;
      case 8:
        file = new AudioFileSourcePROGMEM( espkik08, sizeof(espkik08) );
        break;
      case 9:
        file = new AudioFileSourcePROGMEM( espkik09, sizeof(espkik09) );
        break;
      case 10:
        file = new AudioFileSourcePROGMEM( espkik10, sizeof(espkik10) );
        break;
      case 11:
        file = new AudioFileSourcePROGMEM( espkik11, sizeof(espkik11) );
        break;
      case 12:
        file = new AudioFileSourcePROGMEM( espkik12, sizeof(espkik12) );
        break;
      case 13:
        file = new AudioFileSourcePROGMEM( espkik13, sizeof(espkik13) );
        break;
      case 14:
        file = new AudioFileSourcePROGMEM( espkik14, sizeof(espkik14) );
        break;
      case 15:
        file = new AudioFileSourcePROGMEM( espkik15, sizeof(espkik15) );
        break;
     case 16: // the same as 15
        file = new AudioFileSourcePROGMEM( espkik15, sizeof(espkik15) );
        break;

       case 17:
        file = new AudioFileSourcePROGMEM( espkik17, sizeof(espkik17) );
        break;
      case 18:
        file = new AudioFileSourcePROGMEM( espkik18, sizeof(espkik18) );
        break;
      case 19:
        file = new AudioFileSourcePROGMEM( espkik19, sizeof(espkik19) );
        break;
      case 20:
        file = new AudioFileSourcePROGMEM( espkik20, sizeof(espkik20) );
        break;
      case 21:
        file = new AudioFileSourcePROGMEM( espkik22, sizeof(espkik22) );
        break;

      default:
        file = new AudioFileSourcePROGMEM( fatbass, sizeof(fatbass) ); 
        s = 1;  
      }
        
    // file = new AudioFileSourcePROGMEM(  );
    // out = new AudioOutputI2SDAC();

    // eigentlich p statt oldPiezoVal
    out->SetGain( 0.05 +  (float) midiVelocity/gaindevider ); // 0.8(float) p/130
    
    if (wav->isRunning()) {
      wav->stop(); // wenn nicht mehr richtig looped dann stoppen
    }
    wav->begin(file, out);
  }
}

Die lange Kette von Seriel.println ist ganz praktisch um die Ausgabe per Serial-Plotter zu ermöglichen. Die Sounds hatte ich Anfangs nur durchnumeriert, werde das aber ändern und sprechende Namen verwenden.

Die hex-files liegen im Ardino-Projektordner und nicht in Unterverzeichnissen. Man muss hier auch keinen Tricks zum Upload der Files anwenden.

Mit der Fuktion “out->SetGain und der Variablen “gaindevider” kann man experimentieren und somit die “Velocity” oder Anschlagempfindlichkeit verändern.

Pollin WDC2704 Display am Arduino im 4 Bit-Mode

Hallo zusammen, da ich nun selbst das Problem lösen konnte, hier die Anleitung für die Nachwelt.
Bei Pollin gab es mal das WDC2740 Display preiswert zu kaufen.
Eine einfache Ansteuerung des Displays über die LCDLib und 4 Bit scheiterte im ersten Anlauf.

Lösung: Das Display ist intern 2 Displays , die Anschlussleitungen müssen sehr kurz gehalten werden, Datenleitungen und RS über 100Ohm entkoppeln, Initialisierung als 40×2-Display, obwohl nur 27×2 sichtbar sind….. viele Stolperfallen eben.

Hier die Beschaltung:

LCD / Arduino

R/W auf Masse direkt am Display
LCD RS über 100R auf Arduino D13
LCD ENA2 auf Arduino D12
LCD ENA1 auf Arduino D11
LCD D07 über 100R auf Arduino D10
LCD D06 über 100R auf Ardiono D09
LCD D05 über 100R auf Arduino D08
LCD D04 über 100R auf Ardiono D07

Ansteuerung dann:

#include <LiquidCrystal.h>
// we need 2 LCD-Instances
LiquidCrystal lcd1(13, 11, 7,8,9,10);
LiquidCrystal lcd2(13, 12, 7,8,9,10);

void setup() {
// set up the LCD’s number of columns and rows:
// Internally they are 40×2 controlers, but the physical display is 27×2
lcd1.begin(40, 2);
lcd2.begin(40, 2);
// Print a message to the LCD.
delay(1000);
lcd1.print(“LCD1 Hello World”);
delay(2);
lcd2.print(“LCD2 Hello World, too”);
delay(2);
}

void loop() {
// set the cursor to column 0, line 1
// (note: line 1 is the second row, since counting begins with 0):
lcd1.setCursor(0, 1);
// print the number of seconds since reset:
lcd1.print(“LCD1 ” + String (millis()/1000));

lcd2.setCursor(0, 1);
lcd2.print(“LCD2 ” + String (millis()/1000) );
delay(20);
}