Midi OUT --> Serial.write funktioniert nicht

Hallo zusammen,

ich bin noch neu auf dem Gebiet Arduino und möchte als erste Projekt ein MIDI-Modul bauen.
An das Midi-Modul angeschlossen sind Drum-Pads, die im Grunde jeweils aus einem Piezo-Tonabnehmer und einem Schlagzeugfell bestehen.

Wenn der Piezo "geschlagen" wird soll die Anschlagstärke ausgewertet und per MIDI-OUT an einen Windows-Rechner gesendet werden. An diesem läuft eine Drummachine-Software, die das Midi-Signal verarbeitet und einen entsprechenden Klang erzeugt (z.B. den einer Snare-Drum).

Vorlage für mein Projekt ist diese Seite hier:

Fürs erste hab ich meinen Versuch mal auf einen Piezo und einen etwas vereinfachten Code beschränkt:

Der Piezo hängt (wie auf der Seite beschrieben) Parallel mit einem 1MOhm Widerstand zum einen an Masse und zum anderen am Analog 0 des Arduino.

Der Midi Out hängt:

  • am Mittleren Pin an Masse
  • am zweite Pin an TX0 Port
  • am vierten Pin am 5V Anschluss, wobei noch ein 220kOhm Widerstand dazwischen hängt.

Der Midi-Out wurde genauso auch schon bei zahlreichen anderen Projekten verbaut und sollte grunddsätzlich funktionieren.

Der Code sieht folgendermaßen aus:

int piezoWert=0;

void setup() {
  Serial.Begin(31250); // MIDI-Baud Rate
}

void loop () {
  // Wert lesen
  piezoWert=analogRead(0);
  // Wert an MIDI-OUT senden
  Serial.Write(0x90); // = Midi-ON Befehl
  Serial.Write(38); // Midi-Note (hier mal Fix 38 für Snare-Drum)
  Serial.Write(piezoWert);
  delay(200);
  Serial.Write(0x80); // = Midi-OFF Befehl
  Serial.Write(38); // Midi-Note (hier mal Fix 38 für Snare-Drum)
  Serial.Write(0);
  delay(200); 
}

Folgendes funktioniert:
Wenn ich den Piezo antippe erscheint im Serial-Monitor ein entsprechender Wert.
Jedes Mal wenn ich den Piezo antippe, leuchtet die RX-LED am Arduino kurz.

Was nicht funktioniert:
Es scheint überhaupt nichts am Windows-Rechner anzukommen. Ich hab über das gleiche Kabel mal ein altes Drum-Midi-Modul angeschlossen und da funktionierts. Folglich kanns schon mal nicht am Kabel und am Windows MIDI-IN Port liegen.

Auch die am Steckboard verbaute MIDI-Buchse hab ich schon ausgetauscht und sämtliche Kabel überprüft.
Keine Chance.

Dann hab ich auch testweise mal den TX1 Port verwendet (Ich hab den Mega2560 gekauft, der hat ja noch weitere Serielle Anschlüsse). Das Skript hab ich entsprechend angepasst auf Serial1.Begin[...] und Serial1.Write[...].
Auch hier kommt nichts an.

Was mich auch wundert: Müsste beim schlagen des Piezo statt der RX LED nicht die TX LED leuchten?

Habt ihr noch irgendwelche Ideen?
Kann es sein, dass schlicht und einfach ein Defekt der Platine vorliegt?
kann ich irgendwie prüfen (z.B: mit dem Multimeter) ob am TX0 was rauskommt?

Viele Grüße

Daniel

Du schreibst Deinen MIDI-Output nach Serial, also zum Seriellen Monitor an USB, nicht zu einer MIDI-Buchse, würde ich nach dem ersten Eindruck vermuten. Oder hast Du es anders verschaltet?

Gruß Tommy

Midi ist nicht USB-kompatibel!
Midisignale einfach durch ein USB-Kabel zu schicken, funktioniert nicht.

Du brauchst entweder nen speziellen Chip dafür (Midi-Usb-Adapter) oder gleich ein entsprechendes Adapterkabel.
Jedes USB-fähige Keyboard, Schlagzeug oder sonstiges Gerät hat den eingebaut.

Tommy56:
Du schreibst Deinen MIDI-Output nach Serial, also zum Seriellen Monitor an USB, nicht zu einer MIDI-Buchse, würde ich nach dem ersten Eindruck vermuten. Oder hast Du es anders verschaltet?

Gruß Tommy

Hi,

ich dachte, mit Serial.Write() schreibe ich automatisch an den TX0 Port und kann das Signal dort abgreifen.
Denn an diesen ist ja meine MIDI-Buchse angeschlossen. So wird das in de von mir verlinkten Projekt ja auch gemacht und funktioniert wunderbar.

Wie kann ich denn dann das Signal an den TX0 senden?

Viele Grüße
Daniel

Rabenauge:
Midi ist nicht USB-kompatibel!
Midisignale einfach durch ein USB-Kabel zu schicken, funktioniert nicht.

Du brauchst entweder nen speziellen Chip dafür (Midi-Usb-Adapter) oder gleich ein entsprechendes Adapterkabel.
Jedes USB-fähige Keyboard, Schlagzeug oder sonstiges Gerät hat den eingebaut.

Hi,

siehe meine Antwort oben: Ich will nicht per USB übertragen sondern über den eigens verbauten MIDI-Ausgang, der wie beschrieben mit dem TX0 und 5V (+220kOhm) am Arduino verdrahtet ist.

Grüßle
Daniel

Hm-dass du nen Midi-Anschluss hast, hättest du evtl. erwähnen sollen....

Gut, schon mal.

Piezowert ist eine INT -aber du schreibst dann lediglich ein Byte in die Serielle-das wird so also nicht klappen.
Du wirst aus dem eingelesenen Wert ein Byte machen müssen (runterrechnen, z.B. mit map()), da die Anschlagsstärke (wahrscheinlich soll dein piezoWert nix anderes darstellen?) in Midi grundsätzlich nur ein Byte lang sein darf.

Was benutzt du um die Midi-Daten dann am Rechner auszulesen?
Hairless MidiSerial Bridge ist hier extrem hilfreich-damit siehst du am Rechner, was du wirklich schickst.
Grade mit den Variablen vertut man sich da schnell mal- die meisten Midi-Events (aber eben nicht alle!) sind lediglich Bytes.
Die Serialbrücke ist da tolerant: während ein Midi-Gerät, bei unverständlichen Daten eher einfach die Füsse stille hält (manche auch nicht, die produzieren dann "irgendwas kreatives") , zeigt sie dir, was du wirklich sendest- ungemein hilfreich bei der Fehlersuche.

Für echte Midi-Daten empfiehlt sich auch nach wie vor MidiOx als Monitor.
Bin aber nicht sicher, ob da auch fehlerhafte Midi-Daten angezeigt werden. Aber da ich keine solchen produzieren brauch ich das auch nicht :grin:

Och-mit dem Pitchbend-Controller (der nämlich grössere Werte hat als das besagte Byte) hatte ich meinen Spass seinerzeit. :slight_smile:

Die Hairless kann mehr: im Grunde kannst du mit der generell schauen, was da wirklich aus der Schnittstelle kommt (oder im Falle Midi: tröpfelt).
Das Midi-Protokoll versteht die zusätzlich auch. Zudem kommt die auch mit anderen Baudraten klar.

Hi zusammen,

ich habs nun hinbekommen.
Ich hab alles nochmal neu verdrahtet und dann gings auf einmal...zumindest ein "Hello World"-Effekt, der aber dann gleich mit dem nächsten Problem weiterging :wink:

Der aktuelle Stand ist, dass ich

  • den Midi Out wie beschrieben verdrahtet hab
  • mit einem Midi-Kabel direkt in den MIDI-IN meiner Soundkarte am Windows-Rechner reingeh.
  • Am Windows-Rechner das Programm "MidiOx" gestartet

Dann hab ich die ersten Tests gefahren:

Versuch 1

Im Skript den Wert "NUM_PIEZOS" auf 1 eingestellt und START_SLOT auf 0.
Piezo am AnalogInput0 angeschlossen.

--> Ergebnis: Keine Note wurde getriggert .

Versuch 2

NUM_PIEZOS auf 2 und START_SLOT bei 0 belassen.

Test 1: Piezo am AnalogInput0 angeschlossen:
--> Ergebnis: Die Note D und direkt danach die Note C wurden getriggert

Test 2:Piezo am AnalogInput1 angeschlossen:
--> Gleiches Ergebnis: Zuerst Note D und dann Note C

Versuch 3

NUM_PIEZOS auf 3 und START_SLOT bei 0 belassen.
Test 1: Piezo am AnalogInput0 angeschlossen:
--> Ergebnis: die Noten C, dann D und dann A wurden getriggert

Test 2: Piezo am AnalogInput1 angeschlossen
--> Ergebis: die Noten D, dann A und dann C wurden getriggert

Test 3: Piezo am AnalogInput2 angeschlossen
-> Ergebnis: die Noten A, dann C und dann D wurden getriggert.

Ich bin einfach zu unerfahren im Arduino-coden, um auf die Lösung zu kommen.
Aber anscheined sollte das Skript astrein sein, da es schon mehrmals im Netz verwendet wurde (und auch in dem Youtube Video des Skript Erstellers tiptop funktioniert.)

Ein mechanisches Problem kann es ja eigentlich nicht sein, da ja nur ein Piezo eingesteckt ist und somit der Fehler nicht in einem mittriggern durch Vibrationen liegen kann.

Hier mal das gesamte Skript, evtl. fällt einem von euch ja sofort der Fehler auf:

//Piezo defines
#define NUM_PIEZOS 3
#define SNARE_THRESHOLD 20     //anything < TRIGGER_THRESHOLD is treated as 0
#define KICK_THRESHOLD 20
#define HIHAT_THRESHOLD 20
#define START_SLOT 0     //first analog slot of piezos

//MIDI note defines for each trigger
#define SNARE_NOTE 38
#define HIHAT_NOTE 45
#define KICK_NOTE 36

//MIDI defines
#define NOTE_ON_CMD 0x90
#define NOTE_OFF_CMD 0x80
#define MAX_MIDI_VELOCITY 127

//MIDI baud rate
#define SERIAL_RATE 31250

//Program defines
//ALL TIME MEASURED IN MILLISECONDS
#define SIGNAL_BUFFER_SIZE 100
#define PEAK_BUFFER_SIZE 30
#define MAX_TIME_BETWEEN_PEAKS 20
#define MIN_TIME_BETWEEN_NOTES 50

//map that holds the mux slots of the piezos
unsigned short slotMap[NUM_PIEZOS];

//map that holds the respective note to each piezo
unsigned short noteMap[NUM_PIEZOS];

//map that holds the respective threshold to each piezo
unsigned short thresholdMap[NUM_PIEZOS];

//Ring buffers to store analog signal and peaks
short currentSignalIndex[NUM_PIEZOS];
short currentPeakIndex[NUM_PIEZOS];
unsigned short signalBuffer[NUM_PIEZOS][SIGNAL_BUFFER_SIZE];
unsigned short peakBuffer[NUM_PIEZOS][PEAK_BUFFER_SIZE];

boolean noteReady[NUM_PIEZOS];
unsigned short noteReadyVelocity[NUM_PIEZOS];
boolean isLastPeakZeroed[NUM_PIEZOS];

unsigned long lastPeakTime[NUM_PIEZOS];
unsigned long lastNoteTime[NUM_PIEZOS];

void setup()
{
  Serial.begin(SERIAL_RATE);
  
  //initialize globals
  for(short i=0; i<NUM_PIEZOS; ++i)
  {
    currentSignalIndex[i] = 0;
    currentPeakIndex[i] = 0;
    memset(signalBuffer[i],0,sizeof(signalBuffer[i]));
    memset(peakBuffer[i],0,sizeof(peakBuffer[i]));
    noteReady[i] = false;
    noteReadyVelocity[i] = 0;
    isLastPeakZeroed[i] = true;
    lastPeakTime[i] = 0;
    lastNoteTime[i] = 0;    
    slotMap[i] = START_SLOT + i;
  }
  
  thresholdMap[0] = KICK_THRESHOLD;
  thresholdMap[1] = SNARE_THRESHOLD;
  thresholdMap[2] = HIHAT_THRESHOLD;
  
  noteMap[0] = KICK_NOTE;
  noteMap[1] = SNARE_NOTE;
  noteMap[2] = HIHAT_NOTE;
    
}

void loop()
{
  unsigned long currentTime = millis();
  
  for(short i=0; i<NUM_PIEZOS; ++i)
  {
    //get a new signal from analog read
    unsigned short newSignal = analogRead(slotMap[i]);
    signalBuffer[i][currentSignalIndex[i]] = newSignal;
    
    //if new signal is 0
    if(newSignal < thresholdMap[i])
    {
      if(!isLastPeakZeroed[i] && (currentTime - lastPeakTime[i]) > MAX_TIME_BETWEEN_PEAKS)
      {
        recordNewPeak(i,0);
      }
      else
      {
        //get previous signal
        short prevSignalIndex = currentSignalIndex[i]-1;
        if(prevSignalIndex < 0) prevSignalIndex = SIGNAL_BUFFER_SIZE-1;        
        unsigned short prevSignal = signalBuffer[i][prevSignalIndex];
        
        unsigned short newPeak = 0;
        
        //find the wave peak if previous signal was not 0 by going
        //through previous signal values until another 0 is reached
        while(prevSignal >= thresholdMap[i])
        {
          if(signalBuffer[i][prevSignalIndex] > newPeak)
          {
            newPeak = signalBuffer[i][prevSignalIndex];        
          }
          
          //decrement previous signal index, and get previous signal
          prevSignalIndex--;
          if(prevSignalIndex < 0) prevSignalIndex = SIGNAL_BUFFER_SIZE-1;
          prevSignal = signalBuffer[i][prevSignalIndex];
        }
        
        if(newPeak > 0)
        {
          recordNewPeak(i, newPeak);
        }
      }
  
    }
        
    currentSignalIndex[i]++;
    if(currentSignalIndex[i] == SIGNAL_BUFFER_SIZE) currentSignalIndex[i] = 0;
  }
}

void recordNewPeak(short slot, short newPeak)
{
  isLastPeakZeroed[slot] = (newPeak == 0);
  
  unsigned long currentTime = millis();
  lastPeakTime[slot] = currentTime;
  
  //new peak recorded (newPeak)
  peakBuffer[slot][currentPeakIndex[slot]] = newPeak;
  
  //1 of 3 cases can happen:
  // 1) note ready - if new peak >= previous peak
  // 2) note fire - if new peak < previous peak and previous peak was a note ready
  // 3) no note - if new peak < previous peak and previous peak was NOT note ready
  
  //get previous peak
  short prevPeakIndex = currentPeakIndex[slot]-1;
  if(prevPeakIndex < 0) prevPeakIndex = PEAK_BUFFER_SIZE-1;        
  unsigned short prevPeak = peakBuffer[slot][prevPeakIndex];
   
  if(newPeak > prevPeak && (currentTime - lastNoteTime[slot])>MIN_TIME_BETWEEN_NOTES)
  {
    noteReady[slot] = true;
    if(newPeak > noteReadyVelocity[slot])
      noteReadyVelocity[slot] = newPeak;
  }
  else if(newPeak < prevPeak && noteReady[slot])
  {
    noteFire(noteMap[slot], noteReadyVelocity[slot]);
    noteReady[slot] = false;
    noteReadyVelocity[slot] = 0;
    lastNoteTime[slot] = currentTime;
  }
  
  currentPeakIndex[slot]++;
  if(currentPeakIndex[slot] == PEAK_BUFFER_SIZE) currentPeakIndex[slot] = 0;  
}

void noteFire(unsigned short note, unsigned short velocity)
{
  if(velocity > MAX_MIDI_VELOCITY)
    velocity = MAX_MIDI_VELOCITY;
  
  midiNoteOn(note, velocity);
  midiNoteOff(note, velocity);
}

void midiNoteOn(byte note, byte midiVelocity)
{
  Serial.write(NOTE_ON_CMD);
  Serial.write(note);
  Serial.write(midiVelocity);
}

void midiNoteOff(byte note, byte midiVelocity)
{
  Serial.write(NOTE_OFF_CMD);
  Serial.write(note);
  Serial.write(midiVelocity);
}

Ja-zusammenkopieren ohne verstehen funktioniert weniger oft, als man glaubt- das "neue" Programm ist ein ganz anderes als das vorherige. Du hast da eine Matrix (und genau darauf ist alles ausgerichtet), benutzt aber gar keine...das klappt so nicht.
Das neue wirst du ganz sicher nicht verstehen (das zeigt deine Probiererei, das ist stochern im Nebel)-warum also machst du es dir also schwerer als nötig?

Das erste hatte grundsätzlich gute Ansätze und, was viel wichtiger ist: das kann ein Anfänger begreifen.
Nur damit wirst du weiter kommen, weil du nicht lernen kannst, was du nicht verstehst.

Da du youtube erwähnst: von einem gewissen Müller-Dürholt (oder so ähnlich) gibt es ein relativ gutes Tutorial auf Youtube, der bastelt sich da auch ein Midi-Gerät selber.
Der Bursche gehts hübsch langsam an- und am Ende funktioniert es auch.
Da er offenbar selbst Anfänger ist, erklärt er das ziemlich nachvollziehbar.

Zu MidiOx halt ich mich raus- bei mir gibts keine Windows-Programme.

Nochn Tip für später: das NoteOff-Event benutze ich _niemalsnich. Das funktioniert nämlich nicht bei jedem Gerät (sorgt dann aber für eine Menge "Spass")- daher sende ich einfach noch mal NoteOn mit Velocity=0. Damit hatte ich noch nie Probleme...

Ehrlich: für sowas braucht man kein fertiges Tutorial (mit fertigem Code, den man nich kapiert) sondern ein paar Grundlagen.
Matrix einlesen, auch noch zeitkritisch, serielle Kommandos basteln, die am Ende korrekt sind, all das ist etwas viel für den Anfang.
Guck dir mal die genannte Tutorialreihe an, da kannst du eine Menge lernen.

Und dann bau dir das Programm selber- das erste war eigentlich ein ganz guter Ansatz (bis auf das Problemchen, was ich weiter oben nannte mit dem Byte und der Int).

Ne Matrix kannst du später immernoch "nachrüsten".