< Tech-Blog >
Zungentrommel (Tongue Drum) mit Midi
Der "Bedarf" / Das Problem:
Integration einer Zungentrommel (Tongue Drum) in meinen "Maschinenpark" (Ableton, analoge Sythesizer, Modular Synthesizer, selbstgebaute Sequenzer und Controller).
Die Zungentrommel soll Mididaten aus unterschiedlichen Sequenzern empfangen und mechanisch wiedergeben. Die Zungentrommel soll polyphone Daten (Zweiklänge, komplexe Akkorde) wiedergeben können.
Die Idee:
Ein Teensy LC (wegen der nativen Midiunterstützung) empfängt Mididaten aus Ableton (oder jeder anderen Midiquelle) und steuert damit servobetriebene "Schlaghämmerchen". Das Ganze möglichst latenzfrei und leicht für andere Zungentrommeln adaptierbar.
Die Hämmerchen sollen mit unterschiedlichen "Belägen" bestückt werden um unterschiedliche "Klangfarben" beim Auftreffen auf der Zugentrommel erzeugen zu können.
Die Lösung:
1x Tensy LC, 9x SG90 Servos, 9x Servohalterung und 9x Schlaghämmerchen (aus meinem 3D-Drucker), 1x externes Netzteil (5 Volt, 5 Ampere) für ausreichende Versorgung der Servos, 1x Siebdruckplatte als Montageplattform, 4x Piezo-Pickup, 2x Tasterschalter, 1x Poti, Kleinteile wie Kabel, Buchsen, Stecker
Der Teensy empfängt die Mididaten aus einem Sequenzer und steuert dann die Servomotoren mit Hammer. Die beiden Tastschalter dienen zum "Öffnen" der Hämmerchen um die Zugentrommel austauschen zu können und zum "Schliessen" der Hammermechanik nach dem Einbau der Tongue Drum. Das Poti steuert die "Ruheposition" der Hämmer und ist dadurch eine "Art mechanische Decay-Steuerung": Je näher die Ruheposition an einer Zunge der Trommel ist, desto länger "verweilt" der Hammer nach dem Anschlag auf der Zunge und dämpft dadurch den "Nachklang" des jeweiligen Tones. Zusätzlich dazu kann mit einem "Dämpfer" der Gesamtnachklang der ganzen Zugentrommel beeinflusst werden (schliesslich will ich ja nicht meditieren, sondern grooven).
Die 3D-Druckteile (mit Freecad gezeichnet, mit Cura gesliced, mit customized Ender 3 gedruckt)
Der Code für den Teensy
/* Receive Incoming USB MIDI by reading data. This approach
gives you access to incoming MIDI message data, but requires
more work to use that data. For the simpler function-based
approach, see InputFunctionsBasic and InputFunctionsComplete.
Use the Arduino Serial Monitor to view the messages
as Teensy receives them by USB MIDI
You must select MIDI from the "Tools > USB Type" menu
This example code is in the public domain.
*/
#include <Servo.h>
Servo my_servo1;
Servo my_servo2;
Servo my_servo3;
Servo my_servo4;
Servo my_servo5;
Servo my_servo6;
Servo my_servo7;
Servo my_servo8;
Servo my_servo9; //daempfer
int count;
// über Potistellung Hammer-Startpunkt ändern (wird beim nächsten Midisignal für den jeweiligen Hammer übernommen
// 1x Tonleiter für Anwendung auf ALLE Hämmer
int potPin = A5;
int pos = 10;
// Stellung: Hämmer geöffnet für Trommeltausch
int posS = 10;
void setup() {
Serial.begin(115200);
delay(1000);
my_servo9.attach(23, 1000, 2000);
delay(1000);
my_servo8.attach(20, 1000, 2000);
delay(1000);
my_servo7.attach(17, 1000, 2000);
delay(1000);
my_servo6.attach(16, 1000, 2000);
delay(1000);
my_servo5.attach(10, 1000, 2000);
delay(1000);
my_servo4.attach(9, 1000, 2000);
delay(1000);
my_servo3.attach(6, 1000, 2000);
delay(1000);
my_servo2.attach(4, 1000, 2000);
delay(1000);
my_servo1.attach(3, 1000, 2000);
delay(1000);
pinMode(21, INPUT_PULLUP); // Schalter1
pinMode(22, INPUT_PULLUP); // Schalter2
pinMode(13, OUTPUT); //LED
}
void loop() {
// usbMIDI.read() needs to be called rapidly from loop(). When
// each MIDI messages arrives, it return true. The message must
// be fully processed before usbMIDI.read() is called again.
if (usbMIDI.read()) {
processMIDI();
}
//Serial.println("Button is not pressed...");
if (digitalRead(21) == HIGH) {
Serial.println("Button is not pressed...");
digitalWrite(13, LOW);
} else {
Serial.println("Button pressed!!!");
digitalWrite(13, HIGH);
processOpen();
}
if (digitalRead(22) == HIGH) {
Serial.println("Button is not pressed...");
digitalWrite(13, LOW);
} else {
Serial.println("Button2 pressed!!!");
digitalWrite(13, HIGH);
processClose();
}
}
void processOpen(void) {
my_servo9.write(posS);
delay(500);
my_servo8.write(posS);
delay(500);
my_servo7.write(posS);
delay(500);
my_servo6.write(posS);
delay(500);
my_servo5.write(posS);
delay(500);
my_servo4.write(posS);
delay(500);
my_servo3.write(posS);
delay(500);
my_servo2.write(posS);
delay(500);
my_servo1.write(posS);
delay(500);
}
void processClose(void) {
my_servo9.write(pos);
delay(500);
my_servo8.write(pos);
delay(500);
my_servo7.write(pos);
delay(500);
my_servo6.write(pos);
delay(500);
my_servo5.write(pos);
delay(500);
my_servo4.write(pos);
delay(500);
my_servo3.write(pos);
delay(500);
my_servo2.write(pos);
delay(500);
my_servo1.write(pos);
delay(500);
}
void processMIDI(void) {
byte type, channel, data1, data2, cable;
// fetch the MIDI message, defined by these 5 numbers (except SysEX)
//
type = usbMIDI.getType(); // which MIDI message, 128-255
channel = usbMIDI.getChannel(); // which MIDI channel, 1-16
data1 = usbMIDI.getData1(); // first data byte of message, 0-127
data2 = usbMIDI.getData2(); // second data byte of message, 0-127
cable = usbMIDI.getCable(); // which virtual cable with MIDIx8, 0-7
// uncomment if using multiple virtual cables
//Serial.print("cable ");
//Serial.print(cable, DEC);
//Serial.print(": ");
// print info about the message
//
switch (type) {
case usbMIDI.NoteOff: // 0x80
Serial.print("Note Off, ch=");
Serial.print(channel, DEC);
Serial.print(", note=");
Serial.print(data1, DEC);
Serial.print(", velocity=");
Serial.println(data2, DEC);
if ((channel == 16) && (data1 == 74)) {
my_servo9.write(pos);
}
if ((channel == 16) && (data1 == 60)) {
my_servo8.write(pos);
}
if ((channel == 16) && (data1 == 62)) {
my_servo7.write(pos);
}
if ((channel == 16) && (data1 == 64)) {
my_servo6.write(pos);
}
if ((channel == 16) && (data1 == 65)) {
my_servo5.write(pos);
}
if ((channel == 16) && (data1 == 67)) {
my_servo4.write(pos);
}
if ((channel == 16) && (data1 == 69)) {
my_servo3.write(pos);
}
if ((channel == 16) && (data1 == 71)) {
my_servo2.write(pos);
}
if ((channel == 16) && (data1 == 72)) {
my_servo1.write(pos);
}
break;
case usbMIDI.NoteOn: // 0x90
pos = analogRead(potPin);
pos = map(pos,0,1023,10,160);
Serial.print("Note On, ch=");
Serial.print(channel, DEC);
Serial.print(", note=");
Serial.print(data1, DEC);
Serial.print(", velocity=");
Serial.println(data2, DEC);
if ((channel == 16) && (data1 == 74)) {
my_servo9.write(180);
}
if ((channel == 16) && (data1 == 60)) {
my_servo8.write(180);
}
if ((channel == 16) && (data1 == 62)) {
my_servo7.write(180);
}
if ((channel == 16) && (data1 == 64)) {
my_servo6.write(180);
}
if ((channel == 16) && (data1 == 65)) {
my_servo5.write(180);
}
if ((channel == 16) && (data1 == 67)) {
my_servo4.write(180);
}
if ((channel == 16) && (data1 == 69)) {
my_servo3.write(180);
}
if ((channel == 16) && (data1 == 71)) {
my_servo2.write(180);
}
if ((channel == 16) && (data1 == 72)) {
my_servo1.write(180);
}
break;
case usbMIDI.AfterTouchPoly: // 0xA0
Serial.print("AfterTouch Change, ch=");
Serial.print(channel, DEC);
Serial.print(", note=");
Serial.print(data1, DEC);
Serial.print(", velocity=");
Serial.println(data2, DEC);
break;
case usbMIDI.ControlChange: // 0xB0
Serial.print("Control Change, ch=");
Serial.print(channel, DEC);
Serial.print(", control=");
Serial.print(data1, DEC);
Serial.print(", value=");
Serial.println(data2, DEC);
break;
case usbMIDI.ProgramChange: // 0xC0
Serial.print("Program Change, ch=");
Serial.print(channel, DEC);
Serial.print(", program=");
Serial.println(data1, DEC);
break;
case usbMIDI.AfterTouchChannel: // 0xD0
Serial.print("After Touch, ch=");
Serial.print(channel, DEC);
Serial.print(", pressure=");
Serial.println(data1, DEC);
break;
case usbMIDI.PitchBend: // 0xE0
Serial.print("Pitch Change, ch=");
Serial.print(channel, DEC);
Serial.print(", pitch=");
Serial.println(data1 + data2 * 128, DEC);
break;
case usbMIDI.SystemExclusive: // 0xF0
// Messages larger than usbMIDI's internal buffer are truncated.
// To receive large messages, you *must* use the 3-input function
// handler. See InputFunctionsComplete for details.
Serial.print("SysEx Message: ");
printBytes(usbMIDI.getSysExArray(), data1 + data2 * 256);
Serial.println();
break;
case usbMIDI.TimeCodeQuarterFrame: // 0xF1
Serial.print("TimeCode, index=");
Serial.print(data1 >> 4, DEC);
Serial.print(", digit=");
Serial.println(data1 & 15, DEC);
break;
case usbMIDI.SongPosition: // 0xF2
Serial.print("Song Position, beat=");
Serial.println(data1 + data2 * 128);
break;
case usbMIDI.SongSelect: // 0xF3
Serial.print("Sond Select, song=");
Serial.println(data1, DEC);
break;
case usbMIDI.TuneRequest: // 0xF6
Serial.println("Tune Request");
break;
case usbMIDI.Clock: // 0xF8
Serial.println("Clock");
break;
case usbMIDI.Start: // 0xFA
Serial.println("Start");
break;
case usbMIDI.Continue: // 0xFB
Serial.println("Continue");
break;
case usbMIDI.Stop: // 0xFC
Serial.println("Stop");
break;
case usbMIDI.ActiveSensing: // 0xFE
Serial.println("Actvice Sensing");
break;
case usbMIDI.SystemReset: // 0xFF
Serial.println("System Reset");
break;
default:
Serial.println("Opps, an unknown MIDI message type!");
}
}
void printBytes(const byte *data, unsigned int size) {
while (size > 0) {
byte b = *data++;
if (b < 16) Serial.print('0');
Serial.print(b, HEX);
if (size > 1) Serial.print(' ');
size = size - 1;
}
}
Die fertige Maschine
Video mit "nachgestimmter" Tonleiter und Dämpfereinsatz
Meine Zungentrommel in f***k C-Dur
Eine Tonleiter in C-Dur ist ja gut und recht....aber nicht abendfüllend und für meine Einsatzzwecke nicht sonderlich brauchbar. 8 Töne inkl. Oktave in C-Dur sind wie ein Klavier ohne schwarze Tasten. Ein "Dis" und ein "B"... des wär' sche'!
Beim Experimentieren mit verschiedenen Dämpfern und Hammerformen fiel mir auf, dass eine "lange, starke Berührung" die Tonhöhe der Zunge beeinflusst. Nach zahlreichen Versuchen mit der "Stimmung" der Trommel habe ich folgende (für mich optimale) non-destruktive Stimm-Methode gefunden: Kleine Neodym-Magnete (auf den Photos die kleinen silbrigen Kreise auf den Zungen). Damit lassen sich alle Tonhöhen feinnuanciert beeinflussen OHNE den Klang (die Schwingung) zu ruinieren. Die Zungen schwingen frei und durch die erhöhte Masse wird der Ton um einen Halbton nach unten "geschoben".
Naturklang und / oder FX
Der Klang einer Zungentrommel ist ja doch eher "meditativ". Die "Sticks" mit den "Gummibällen" am Ende ...ohhhhhjeeeeeeeee.... Da riechts irgendwie gleich nach Räucherstäbchen und lauwarmem Ingwerwasser.
Deshalb: Harte Hammerfläche (PLA), ein Dämpfer für "Hard Attack und controllable Decay" UND 4 Piezo-Pickups.
Und los geht der Spass: Jeweils 2 Piezos pro Kanal (L/R) in den Mixer einspeisen -> ab damit in die Hardware-FX-Chain -> Raus auf die Ohren!.
Durch die Abnahme der Schwingungen über Piezos direkt an der Trommel kommen im Mischer fast keine Servogeräusche an (...denn die Dinger sind schon penetrant laut, wenn es zur Sache geht). Das Servogeräusch klingt einer "Rassel" nicht unähnlich lässt sich auch gut in einem Track verwenden, aber eben nicht in jedem... deshalb Piezos und kein Micro.
Im Speedtest konnte ich bis 250bpm keine Ausfälle in der Machanik feststellen. In Ableton habe ich für die Midi-Out-Spur zur Zungentrommel die Ausgabe um -40ms korrigiert um alles "tight" zu bekommen.