Archive of ‘Allgemein’ category

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;

      // }

    }

//   }

*/

}

16 Step Sequencer for Akai MPX16 Sampler #DIY #Arduino

Hi, I have recycled the Code of the 16Step Sequencer to use it as a Sequencer for the Akai MPX16.

I belive, it could be used for the Akai MPX8 too. To trigger the sample-pads via MIDI, I used the MIDI-Channel 10 and the MIDI-Notes 36 to 51, for Pad 1 to Pad 16.

The functions are similar to the other 16-Step Sequencer.

Video:

https://youtu.be/Tr9Y971fDvM

Code MPX16 Sequencer




// E.Heinemann
// e.heinemann@hman-project.de
// 30.10.2016 - E.Heinemann  Bonn Germany
// 2021-02-07 - E.Heinemann Updated and modified the MIDI-Note-Numbers and their names to map it to the MPX16-Defaults
// 2021-02-07 - E.Heinemann, new attempt to combine Menu-Button as a DN-Button with the Step-Buttons

// 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

/*
 *  MPX16 receives on Channel 1 but sends on Channel 10 !
 *  Midi-Notes of the 16 Samples by default:
 *  40  41  42  43 | 48 49 50 51 
 *  36  37  38  39 | 44 45 46 47
 *  
 *  Simply Samples from Midi 36 to 51
 *  
 */ 


#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 = 1;    // Default Midi Channel
uint8_t midi_sync    = 0;     // 1 == Slave, 0 = Master - default

uint8_t LCD_Address = 0x27;   // LCD is integrated via PCF8574 on Port 0x27
LiquidCrystal_I2C lcd( LCD_Address, 16, 2 );  // simple LCD with 16x2

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;   // Menu/FN/Select-Button ..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 .. therefore 17 Instruments from 0 to 16
const String shortSounds[] ={ "ACC"   , "PAD 1", "PAD 2" , "PAD 3" , "PAD 4", "PAD 5", "PAD 6", "PAD 7", "PAD 8", "PAD 9", "PAD 10", "PAD 11", "PAD 12","PAD 13","PAD 14","PAD 15","PAD 16" };
const String Sounds[]      ={ "Accent", "PAD 1", "PAD 2" , "PAD 3" , "PAD 4", "PAD 5", "PAD 6", "PAD 7", "PAD 8", "PAD 9", "PAD 10", "PAD 11", "PAD 12","PAD 13","PAD 14","PAD 15","PAD 16" };
uint8_t iSound[] ={ -1,  36, 37, 38, 39, 40, 41, 42, 43,  44, 45, 46, 47, 48, 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, 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 = 1; // 1 = PAD  1, 0= Accent

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;
}


// Plug MIDI-Out of MPX16 into MIDI-In of the arduino 
// Press the PAD to select the PAD
void handleNoteOn( byte inChannel, byte inNote, byte inVelocity ){

  // has the user pressed a PAD on the MPX16??
  if( inChannel == 10 && inNote >=36 && inNote <= 51){
    Select_Instr( inNote - 35 );  
   }
    // const bool firstNote = midiNotes.empty();
    // midiNotes.add(MidiNote(inNote, inVelocity));
    // handleNotesChanged(firstNote);
}


// Callback from Timer1
void callback(){ 
  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] );
        
}

// ########################### 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
    // "song_position" is the current step 
    if( step_position<8 ){ // play notes1
       // Accent set?
       if( bitRead( inotes1[0],step_position ) == 0 ){
         // Accent is set
         veloAccent = iVelo[0];
       } 
       // loop through all instruments .. But ignore Accent with 0       
       for( int i = 1; i < count_instr; i++ ){
         if( bitRead( inotes1[i],step_position ) == 0 ){
           velocity  = round( iVelo[i] * veloAccent / 100);
           if( velocity > 127 ) {
             velocity = 127; 
           }
           midiA.sendNoteOff( iSound[i],         0, midi_channel );       
           midiA.sendNoteOn(  iSound[i], velocity ,midi_channel );
         }  
       }
    }
    
    // second Byte or second 8 Steps
    // bitClear(bitdp2, (a-8));
    if( step_position >= 8 ){ 
       // play Notes2
      if (bitRead( inotes2[0],step_position ) == 0){ // Accent is set
        veloAccent = iVelo[0];
      } 
       
      // loop through all instruments .. but ignore the Accent with 0
      for( int i = 1; i < count_instr; i++ ){
        if( bitRead( inotes2[i], ( step_position-8 ) ) == 0 ){
          velocity  = round(iVelo[i] * veloAccent / 100);
          if( velocity > 127 ){
            velocity = 127; 
          }
          midiA.sendNoteOff( iSound[i],         0, midi_channel );       
          midiA.sendNoteOn(  iSound[i], velocity , midi_channel );
        }  
      }   
    }
  }
}

// ############################## 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 on the 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; 

      // Select Instrument Menu
      if( curModeNum==0 ){
        Select_Instr( round(aVal3/8) );      
      }
      
      // Velocity
      if( curModeNum==1 ){
          if ( aVal3 > 127 ) { aVal3=127;}
          lcd.setCursor(0,0);
          lcd.print( curMode + " " + String(aVal3) + " " + Sounds[ curIns ] + "     ");
          iVelo[ curIns ] = aVal3;  
      }

      // Speed-Menu
      if( curModeNum==2 ){
          old_bpm = bpm;
          bpm = round(analogRead(aPin3)/4 + 40);
          lcd.setCursor(0,0);
          lcd.print( curMode + " " + String(bpm) +" bpm   ");
          tempo_delay = 60000000 / bpm / 24;
      }
      
      // Bars
      if( curModeNum==3 ){
          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 ) +"      ");

      }

      // Midi-Notes, perhaps we find a good Midi-Reference to replace Note and Name
      if( curModeNum==4  && curIns>0 ){   // 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; 
      }

      // Scale
      if( curModeNum==5 ){
          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 ); // OLD: Menu changes on buttonpress .. not on release. NEW: It has to change the Menu on "Release" of the button
                                              // Acting by release offers to do something while the button is pressed. ... faster Change 
    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 --
  
   // Unless they're all high...  
  if ( bits1 != B11111111 && oldStatus1 != bits1 ){  
    //-- 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;
      }    
    }  
  }

  // Unless they're all high...
  if ( bits2 != B11111111  && oldStatus2 != bits2 ){
    //-- Find lowest pressed switch --
    for( byte bitIndex = 0; bitIndex < 8; bitIndex++){
      if( bitRead(bits2, bitIndex) == 0 ){
        //  Both Buttons areL and S are not pressed!
        
        if( oldStateL == HIGH ){ 
          // && oldStateS == HIGH )
          if( bitRead(notes2, bitIndex ) == 1){
             bitClear(notes2, bitIndex ); 
           }else{
             bitSet( notes2, bitIndex ); 
           }  
           inotes2[curIns] = notes2;
           // Serial.println ("Bit Cleared");
           // } 
           // ExplosionAnimation(bitIndex, address2);
          
        }else{
          // left 2 Bits to change the Scale
          if( bitIndex == 0 ){ 
            if( scale > 0 ){ scale = scale-1; }
            lcd.setCursor(0,0);
            lcd.print( Modes[5] + "  " + scale_string[ scale ] +"      "  );
          }
          // left 2 Bits to change the Scale
          if( bitIndex == 1 ){ 
            scale = scale+1;
            if ( scale >=4){ scale=0; }// only llop through straight scales 
            lcd.setCursor(0,0);
            lcd.print( Modes[5] + "  " + scale_string[ scale ] +"      "  );
          }

          // left 4 Bits to change the Scale
          if( bitIndex==6 ){
             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;
          }
          
          // add some speed to BPM
          if( bitIndex==7 ){
            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(){

  // for Debugging
  // Serial.begin( 115200 );

  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" ); // << put in Your prefered Name for this Sequencer into this variable:)

  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); // Selecct Button
  digitalWrite( buttonPinL, HIGH); // Left- or Start-Button
  digitalWrite( buttonPinR, HIGH); // Right- or Stop-Button

  notes1 = B11111111; // Bitwise per Instrument, No Note equals "1", note to play equals "0" !
  notes2 = B11111111;
  midiA.setHandleNoteOn( handleNoteOn );
  midiA.begin( MIDI_CHANNEL_OMNI );   // Midi-Input for Sync
  midiA.turnThruOff(); // this prevents the MPX16 to collaps
  
  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 ++;
  // read the IO-Pins every 5 cycles
  if( b > 4 ){ 
    // 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) + "   " );
  } 


  // Check MIDI IN
  // Is there a MIDI message incoming ?
  midiA.read();
  /*
    switch( midiA.getType()){      // Get the type of the message we caught
      // case midi::ProgramChange:       // If it is a Program Change,
      
        // BlinkLed(MIDI.getData1());  // blink the LED a number of times
                                            // correponding to the program number
                                            // (0 to 127, it can last a while..)
      // case midi::                                            
      break;
        // See the online reference for other message types
        default:
        break;
     }
  */
}

   


//////////////////////////////////////////////////////////////////
//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;
      // }
      
    }
//   }
*/
}

Samplerbox mit Novation Launchkey Mini MK2

das kleine MIDI-USB-Keyboard läuft gerade im Ausverkauf, – ich hatte es schon vorher und nicht allzu oft verwendet.
Beim MK2 fehlen einfach sehr viele Controls, die man bei anderen Keyboards erwartet. Pitchbend, Volume und Program-Change sind nicht als Buttons verfügbar.

Um es mit der Samplerbox-Hardware verwenden zu können kann man mit der Version von HansEV ein Mapping der MIDI-CCs zu den Controls durchführen.
Die Dateien liegen auf der SD im Ordner samplerbox oder wenn man sich per SSH auf die Box verbindet dann in /boot/samplerbox/ .
Volume habe ich auf den zweiten Poti gelegt und 2 Buttons sind nun Patch +/- – Buttons.
Button1 und Button2 wollte ich für die Reverb an/aus – das hat aber noch nicht funktioniert.

controllerCCs.csv in
Potentiometer
Controller,CC,Val,
#ModWheel,1,-1,# Standard controller is 1
#Volume,7,-1,# Standard controller is 1
ModWheel,21,-1,# Standard controller is 1
Volume,22,-1,# Standard controller is 1
Sustain,64,127,# Standard controller is 64
ButtonUp,104,127,# Launchkey Mini 2
ButtonDown,105,127,# Launchkey MIni 2
PotCC23,23,-1, # Launchkey Mini 2 Pot3
PotCC24,24,-1, # Launchkey Mini 2 Pot4
PotCC25,25,-1, # Launchkey Mini 2 Pot5
PotCC26,26,-1, # Launchkey Mini 2 Pot6
PotCC27,27,-1, # Launchkey Mini 2 Pot7
PotCC28,28,-1, # Launchkey Mini 2 Pot8
Button1,108,1,# Launchpad Mini
Button2,109,1,# Launchpad Mini

Samplerbox Hardware with Raspberry PI

The Project Samplerbox is still activ. (2020-01-26)
I used the image and instruction from http://homspace.xs4all.nl/homspace/samplerbox/index.html

I have tried to use earlier versions in last years but it does not work as expected with my older Raspi 1.
I bought a Raspberry 3+  to test samplerbox with it. For now it works fine with the last image from Hans!

Samplerbox-rearside,
3 Buttons, 1 LCD, 4 USB-Ports, the ethernet-Port is hidden in the case.
Side-View, with Left Audio Out, Right Audio-Out, MIDI

Bill of material to build a Samplerbox:

  • Raspberry Pi 3+ ( perhaps newer works too)
  • Powersupply, 5V, 2.5 Amps or more
  • SD-Card
  • generic USB-Sound-Card – or DAC-Board PCM5102
  • USB-Stick to store Samples
  • Case for the Raspberry Pi
  • Optional:
    • MIDI-Board (DIN-Socket, 1x10k, 1x1k, 1x 220Ohm Resistor, 6N138 Optocoupler, 1 Diode)
    • USB-MIDI-Keyboard or MIDI-Keyboard with DIN-MIDI if the MIDI-Board is build
    • LCD Display 16×2 HD44780 (16 Pins)
    • 3 Buttons
    • Status-LEDs: 2 LEDs & 220Ohm Resistor

I didn´t buy a lot because I had already most things in stock. I used “Guttagliss” – Hobbycolor plates to build a case and painted it with spray paint. Hobbycolor looks like plastic but is some more flexible and very easy to cut with a knife, could be glued, bend with the heatgun and painted. In the top of the case, I have cutted the holes for the LCD and the 3 Buttons. Finally I used a transparent acrylic-plate to cover to top. Finally, inside the case I used some layers of Hobbycolor-pieces to create some support structures to be able to fix the different circuit with woodscrews.

I created 3 circuit, first circuit to connect to all GPIO-Pins of the Raspberry PI and provides space for the PCM5102-Board.

Second a Circuit to hold the 3 Buttons and which has the width of the LCD. Both, button-board and LCD is mounted with four 3mm Screws and some layers of nuts to get the right distance to the top of the holes in the case.

Third circuit, and the last, is the MIDI-Converter, which transforms classical MIDI into 3.3Volts serial data. This board is the smallest and I have used a small circuit-board which is too small in my case because it has to be mounted very well and fixed with the case. The big DIN-Plug causes some force and this has to be managed with screws between DIN-Plug and the case.

If you would only test the software, the minimum would be Raspberry PI, a USB-Sound-Device which is compliant as Sounddevice for Linux and a USB-Keyboard. There is already one sound on the SD-Image.

My Result, – the view inside with the Circuits ( the MIDI-Circuit was added later)

, the background is the inside the case and is the backside of the top plate. The green Circuit is the LCD-Board. The board at the bottom is the board for the buttons.
The upper board connects to the Raspberry Pi and on its backside, there is the PCM5102-DAC and all the wired. The upper Breadboard is fixed by 4 screws. The 2 jacks on the right-side are the Audio-Output 1/4 inch, 6.3mm – jacks.

Backpanel of the case with some features for aircondition. The white and black stripes at the corner are “inside” and are used to fix the screws from the side.

Wiring of the PCM-, LCD- and MIDI-Board with the Raspberry-Pi-GPIOs

The wiring through the Audio-Jacks is made with plastic jacks which connect the tip- and ring-contact to an outpoing pin if no plug is inserted in the jacks. That way, the upper jack is stereo if nothing is connected to the right-side-jack. The right-side-jack is a mono-jack, the left-side-jack is a stereo jack.

The MIDI-Circuit was tested with an LED, because I had to order the 6N138 and wanted to avoid any mistake.

Test MIDI-Circuit with LED first

Finally after testing, I was able to connect all cables.
Powersupply, Audio, MIDI-Keyboard, USB-Stick with samples, USB-Keyboard,

How does it sound? – exactly as the samples are. On the USB-Stick, for every instrument or “patch”, you need a folder which starts with a number.
Inside each folder, you have to store all needed samples for the patch and a definition.txt which declares the structure, placing of the samples etc.

The wave-files could contain Loop-Points to be able to play an area of the sample in a loop as long as you presses the key on the keyboard.
For short samples – or most string- or drum-instruments, it sounds more realistic to use a long sounding sample which simple fades out. After releasing the key, samplerbox will release these samples in a few milliseconds which mostly sounds natural.

The content of a definition.txt looks a bit like “code” but is very “simple” to declare for “simple” instruments. Lets view the “code”.
To avaid load distorsion, I lower the gain for this instrument to 80% – 0.8.
%%velmode=accurate is more or less a standard which has to be defined.
The last line of my definition is something like a Regular Expression but much more easier. * covers all content in flexible length. %notename has to be something like C4, c4 or c#4 or C#4 but NOT Gb3 or G.
By default, the notation could be used as the notenames but only normal notes or sharp notes. As an alternative, “%notenumber” could be used which is simply the MIDI-Notenumber, C4=60. %velocity could be used to provide a %velocity-Number to define WAV-Files when keys are hitted faster/stronger for loader notes and other WAV-Files for quite notes.
There are a lot more options available for the definition.txt which are described well in the internet.

%%gain=0.8
%%velmode=accurate
* %notename %velocity.wav

The definitions are made mostly for key-ranges without a specified border of the range. If you would like to define something for a specific key, that could be done with specific variables in the code:
kick.wav, %midinote=36

The Options per Sound are:
%notename
%midinote
%velocity

More Details are documented here, but this documents don´t cover the version from HANS by 100%!!
samplerbox.readthedocs.io/en/latest/Sample_Sets/

The Software-Image from Hans covers special files on the SD-Card! – /boot/config.txt and /boot/samplerbox/configuration.txt and some more…
These files provide the ability to define the used GPIO-Ports and are used to enabled the Serial-MIDI-Port. ( BT would be moved to miniuart to make the real UART available for the MIDI-Port via midi-uart0)
(The configuration-files are readable on the SD-Card on MAC and probably windows.)

Raspi3+ in config.txt I changed this:

enable_uart=1
dtoverlay=pi3-miniuart-bt
dtoverlay=midi-uart0

and removed from config.txt
init_uart_clock=2441406,
init_uart_baud=38400 in config.txt and

and remove this in cmdline.txt
bcm2708.uart_clock=3000000

MIDI-Controllers could be used to control all the effects or the select the patches. I am using an older Novation Launchkey MINI MK2 which does not provide Program-Change command. Therefore, I used the Controller-Configuration from HANS to remap 2 buttons on the launchkey to do so and a POT on the Launchkey to control the volume. All Configuration-Files are described here: http://homspace.xs4all.nl/homspace/samplerbox/SBconfig.html

What could You do better than I did?
– Try to push all connections to only 2 sides of the case. I have created connections on the left side for audio and MIDI and the Power-Plug is on the right side and USB-Ports on the top . That way, the Samplerbox takes too much place on my desk.
– create a stiffer case and a bigger circuit-board for the MIDI-Port. A MIDI-Port, shaking loosely inside the case wouldn´t be “pro”,
– Try to use the OLED-SPI-Display-Option, – my 16×2 LCD does not work well and shows agyptian characters when the Raspberry Pi is cold. My LCD is specified for 5 Volts but the data-pins of the Raspi provide 3.3Volts, this could be the problem. – but sometimes it works!?
– Don´t use litz-wires for the connection between the circuits. I had to rethink and rebuild the circuits and the case and had many issues with wires which created shorts or were simply disconnected.
– Test Your setup on a breadboard first
– Connect the LCD only with Data-Pins Data4-Data7, – the 8Bit-Mode is not supported by the version from HANS.

What I would do with the samplerbox?
I already own a Akai MPC2500, – which plays drumsounds well but not keyboard-sounds mapped to a keyboard. I own a Novation-Circuit too which has limited polyphony and I am thinking about to create 2 small samplerboxes to extend the MPC and the Circuit.
I already sampled a few instruments by using the MPC 2.0 Software (version 2.6 with the autosampler) and created samples of my mandolins and mandola by using garageband as the “recorder”.
I would like to use the Samplerbox as a small expander with Samples which I recorded or to provide drum-loops.
A further step would be a Arduino as a MIDI-Merger and Sequencer between the MIDI-Board and the Samplerbox. A Arduino-Nano or ESP32 could do that perhaps.

Things, which are not that good:
The documentation is not covering all different versions of Samplerbox. The Effectsettings are global and are not saved in the definition.txt for a single patch. But to be fair, all these effects are a great step forward for samplerbox.
The forum(s) is not splittet into different subjects. Most forum-posts are asking for help or saying that the sound is crappy – they only need the hint to lower the gain in definition.txt. That way, samplerbox-users are not in a real discussion in the forum.

PHP mit Curl Export File

Es gibt manchmal das Problem, dass keine direkte Verarbeitung, sondern ein Loggen des Ergebnis von CURL gewünscht ist. Dazu muss man nicht erst eine Verarbeitung des Returnvalues durch PHP starten.

Ausschnitt aus PHP:

$ch = curl_init();
curl_setopt($ch, CURLOPT_FILE, $out);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_URL, $file);

Samplerbox basierend auf Raspberry PI – 2020 lohnt sich das noch?

Samplerbox hatte ich schon mit dem Raspberry-PI, gefühlt 2010 getestet. Es war sicher 2 oder 3 Jahre später aber der Sampleplayer basierend auf Raspberry 2 lief eher mit Fehlern und die Klangqualität ware eher sehr bescheiden.

Samplerbox ist eine Software – oder besser schon “Betriebssystem”, das man als Boot-Image auf eine SD-Karte kopiert und dann diese in den Raspberry-Pi steckt.
In die USB-Ports des Raspberry-PI steckt man noch ein USB-MIDI-Keyboard, eine USB-Soundcard und einen USB-Stick mit Sounds. Nach dem Booten kann man dann die Sounds, die auf dem USB-Stick gespeichert sind auf dem MIDI-KEyboard abspielen.

Technisch war Samplerbox als einfacher mobiler Keyboard-Expander angesetzt. Dank gut gesampelter Piano-Sounds scheint das Projekt aber nun eher bei Usern zum Upgrade von Homepianos eingesetzt zu werden.

Im Vergleich zu einem vollwertigen Sampler, – so wie es sie früher in den 90ern mal gab, fehlen der Samplerbox viele Funktionen:

  • Samplen kann man damit nicht, nur Sounds wiedergeben
  • Es gibt keine ADSR-Hüllkurve, sondern nur eine Releasetime
  • 16Bit, und somit kann es bei vollem “Gain” zu Verzerrungen kommen
  • Kein integrierter Sample-Editor

Auf der Plusseite haben wir aber 2020 mit der Version von Hans ( http://homspace.xs4all.nl/homspace/samplerbox/SBbuild.html ) nun eine stabile Version, die auch schon eine größere Liste von Effekten kennt: Lowpass, Freeverb, Delay, WahWah.
Die Version von Hans läuft auch nach Stunden und wildem Wechsel des USB-Sticks noch stabil!

Die Version von Hans unterstützt auch ein einfaches optionales GUI, mit 2 Buttons, einem Display (16x2LCD oder SPI-OLED), DIN-Midi und 2 LEDs.

Ich habe mir eine einfache PCM5180-Platine bei Amazon bestellt und diese als Hifiberry-Soundcard installiert, ein LCD-Display und 2 Buttons verbaut. Da ich noch keinen 3D-Drucker habe, habe ich ein Gehäuse aus Guttagliss PHoobycolor – Platten gebaut. .. das ging auch in wenigen Minuten.

Anders als angegeben müssen nur die Datenleitungen D4 bis D7 des LCDs mit GPIO-Pins verbunden werden. Die final ausgewählte Belegung wird in einer Configdatei (configuration.txt) auf der SD-Card definiert. Somit können auch vorhandene LCDs und Hifiberry weiter verwendet werden.

Sounds habe ich mit der Akai MPC erstellt und dann auf den USB-Stick rüber kopiert und somit stehen diese nun mit einer Polyphonie von 80 Sounds zur Verfügung.

Spaß machen die A-Piano und E-Piano-Sounds, bei denen man keine besonderen ADSR-Hüllkurven erwartet. Synth-Sounds klingen sehr statisch, denn hier erwartet man eben Sounds, die genau nochmal eine Veränderung zeigen, wenn man länger drückt, fester drückt (Aftertouch) oder los lässt.

Für alle, die einen einfachen MIDI-Expander, – Sampleplayer für Keyboard-Sounds, Drum-Sounds oder Loops suchen und schon einen Raspberry-Pi ab Modell 3 haben, sollten Samplerbox nochmal testen.

Ich werde meinen Raspberry PI noch mit einem DIN-MIDI-Port ausstatten und einen Arduino als Sequencer hinzufügen und zu meinem kleinen Volca-Beat synchronisieren.

Links:

http://homspace.xs4all.nl/homspace/samplerbox/SBbuild.html

Script to sample down the sounds of a SF2-File
https://github.com/adrianhoehne/Sf2_Batch_Recording/blob/master/Sf2_Batch_Recording

Trick zur Sample-Convertierung:
Unter MacOSX gibt e snatürlich viele Tools um vorhandene Sounds in einem Verzeichnis auf die richtige Sample-Frequenz und die richtige Bitgröße zu bringen. Mit dem Tool “SOX” geht das auch in der CLI/Bash:
for file in *.wav; do ./sox “$file” -r 44100 -b 16 44100/”$file” -V; done

Erweiterung zum Samplen gibt es lieder noch nicht fertig. Aber es gibt ein Script, welches die Basics zum Samplen abdeckt: https://www.samplerbox.org/forum/444

import RPi.GPIO as gpio 
from recorder import Recorder 
gpio.setmode(gpio.BCM)  

class ButtonRecorder(object): 
    def __init__(self, filename): 
        self.filename = filename 
        gpio.setup(23, gpio.IN, pull_up_down=gpio.PUD_UP) 
        self.rec = Recorder(channels=2) 

    def start(self): 
        gpio.add_event_detect(23, gpio.FALLING, callback=self.falling, bouncetime=10) 

    def rising(self, channel): 
        gpio.remove_event_detect(23) 
        print 'Button up' 
        gpio.add_event_detect(23, gpio.FALLING, callback=self.falling, bouncetime=10) 
        self.recfile.stop_recording() 
        self.recfile.close() 

    def falling(self, channel): 
        gpio.remove_event_detect(23) 
        print 'Button down' 
        gpio.add_event_detect(23, gpio.RISING, callback=self.rising, bouncetime=10) 
        self.recfile = self.rec.open(self.filename, 'wb')    
        self.recfile.start_recording() 

rec = ButtonRecorder('36.wav')
rec.start() 

try: 
    raw_input() 

except KeyboardInterrupt: 
    pass 

gpio.cleanup()

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.

Phonegap – Cordova 2017 Teil 6: Scrolling im IPhone-Emulator und Font-Awesome

Scrolling

Im IPhoen Emulator lassen sich die pages nicht scrollen, – im Gegentest im richtigen IPhone zeigt, dass sie sich auf dem realen IPhone doch scrollen lassen.  Merkwürdig aber erstmal kein Hindernis.
Nach einigen Test mit dem Simulator habe ich festgestellt, dass Scrollen auch im Emulator funktioniert. Man muss nur (more…)

Phonegap – Cordova 2017 Teil 5: Javascript Alert funktioniert nicht

Der normale Alert funktioniert leider nicht. Als Programmierer verwendet man einen Debugger oder gibt Logs irgendwo aus und wenn es garnicht anders geht, dann verwendet man eben „alert“.
In Cordova gibt es ein Plugin für Dialoge, welches installiert werden muss.
cordova plugin add cordova-plugin-dialogs
Der zugehörige Code sieht wie folgt aus:

(more…)

Phonegap – Cordova 2017 Teil 4: Usersettings innerhalb der App sichern

Settings, – irgendwo muss der User ja seine Account-Daten für den Zugriff auf diese Tools sichern.

Wie sichert man Daten im Browser und funktioniert das auch noch in Cordova?
Die Maske war dazu schnell erstellt und dann um diese Accordionpane aus JQuery-Mobile erweitert.
Page mit Usersettings

Hier der Code:

(more…)

1 2