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.

Comments are closed.