mono-Ausgabe nach eingegangenen MIDI NoteOn-/ NoteOff-Befehlen

Hallo,

bin am überlegen, wie ich es bewerkstelligen kann, nach eingehenden MIDI NoteOn-/ NoteOff-Befehlen eine mono-Ausgabe zu realisieren.

Ich möchte meinem Arduino also beibringen, dass er sich wie ein Mono-Synthesizer verhält.

Zwei Beispiele:

Taste 13 wird gedrückt -> Ausgabe 13
Taste 20 wird gedrückt, während Taste 13 weiterhin gedrückt gehalten wird -> Ausgabe 20
Taste 20 wird losgelassen, während Taste 13 weiterhin gedrückt gehalten wird -> Ausgabe 13
Taste 13 wird losgelassen -> Ausgabe stoppt

Taste 13 wird gedrückt -> Ausgabe 13
Taste 20 wird gedrückt, während Taste 13 weiterhin gedrückt gehalten wird -> Ausgabe 20
Taste 13 wird losgelassen, während Taste 20 weiterhin gedrückt gehalten wird -> Ausgabe 20
Taste 20 wird losgelassen -> Ausgabe stoppt

Da ich dieses Verhalten für bis zu zehn gleichzeitig gedrückt gehaltene Tasten realisieren möchte, frage ich mich nun, wie ich dies möglichst einfach umsetzen kann.

Eine erste Idee von mir lautet folgendermaßen:

  1. Die einzelnen gedrückt gehaltenen Tasten werden inkl. ihrer fest definierten Tonhöhen in ein zuvor leeres Array geschrieben.
  2. Wird die letzte gedrückt gehaltene Taste nun losgelassen, muss sich der Prozessor dann irgendwie die zuvor gedrückt gehaltene Taste aus dem Array picken,..

Wenn ich mir nun noch die ganzen "Sonderfälle" hinzudenke, habe ich bereits jetzt das Gefühl mich zu verstricken..

Sonderfälle:

  1. Zehn Tasten werden gleichzeitig gedrückt gehalten
  2. Einzelne NoteOn- oder NoteOff-Befehle wurden nicht erkannt
  3. Während die zuletzt gedrückt gehaltene Taste weiterhin gedrückt gehalten wird, werden ununterbrochen zuvor gedrückt gehaltene Tasten gedrückt.

Wie kann ich diese Aufgabenstelung sinnvoll angehen? Die von mir in diesem Projekt verwendete MIDI-Library kann mir beim Lösen dieses Problems ja leider nicht weiterhelfen. :~

Gruß Chris

Meinen momentanen Code habe ich angehängt, falls es hilft. Leider war er zu groß, um ihn auf gewöhnlichem Weg in dieses Posting einzubetten.

MIDI_Floppy.ino (9.47 KB)

Bist Du sicher, dass Du das so brauchst?
Rein musikalisch gesehen ist die "Mono-Funktionalität" mit High- oder Lowkey Priorität gesetzt.
Beispiel Highkey:
Du schlägst C3 an und hälst die Taste gedrückt (also Note on und kein Note off), jeder weitere Anschlag von Tasten unterhalb C3 werden ignoriert. Bis entweder C3 Note off kommt oder eine Taste oberhalb C3 gedrückt wird.

Was ich nicht verstehe, wie soll die Logik aussehen bei mehr als 2 Tasten? Woher weiß die Maschine, auf welche Taste sich der neue Anschlag relativ beziehen soll?

Oder mach ich's mir zu kompliziert?

Hallo Klaus,

die Logik der Monofunktionalität soll folgendermaßen aussehen:

  1. Generell soll die Tonhöhe der zuletzt gedrückt gehaltenen Taste erklingen
  2. Wird die zuletzt gedrückt gehaltene Taste losgelassen, soll die zugeordnete Tonhöhe der vorletzten gedrückten Taste erklingen

Hierzu zwei weitere Beispiele:

  1. Es wird die Taste C1 gedrückt gehalten. Ergebnis: C1 erklingt

  2. Nun wird zusätzlich die Taste C2 gedrückt gehalten. Ergebnis: C2 erklingt

  3. Wenn nun C2 losgelassen wird, soll wieder C1 erklingen, bis auch diese Taste losgelassen wird

  4. Es wird die Taste C2 gedrückt gehalten. Ergebnis: C2 erklingt

  5. Nun wird zusätzlich die Taste C1 gedrückt gehalten. Ergebnis: C1 erklingt

  6. Wenn nun C1 losgelassen wird, soll wieder C2 erklingen, bis auch diese Taste losgelassen wird

Hieraus wird ersichtlich, dass weder eine High-, noch eine Lowkey Priorität gewünscht ist.

Bei mehr als zwei gleichzeitig gedrückt gehaltenen Tasten soll nach dem Loslassen der zuletzt gedrückten Taste die zugeordnete Tonhöhe der vorletzten gedrückten Taste erklingen. Wurde diese bereits wieder losgelassen, soll die zu drittletzt gedrückte Taste erklingen. Wurde diese bereits losgelassen, soll.. usw.

Nachdem ich mich gedanklich gestern weiter mit dem Thema beschäftigt habe, denke ich, dass es grundsätzlich nun zwei (ähnliche) Möglichkeiten gibt dieses Problem zu lösen:

  1. Mit einem Ringbuffer schauder :astonished:
  2. Mit einem Array, welches sich nach und nach mit den gedrückten Tonhöhen füllt und bei dem die Indexstellen beim Loslassen von Tasten "aufrücken"

Hab zudem das Netz wie ein Wilder durchwühlt und kann (selbst zu einer polyphonen Logik) im Bezug auf eine Programmierung mit einem Arduino nicts finden.

Gruß Chris

PS: Ich prüfe momentan noch auf Machbarkeit und habe noch immer keine Hardware.
Antwort auf eine Frage von Klaus aus einem anderen Thread.

Nachdem das geklärt ist: reden wir hier auch sendeseitig über Midi on/off Befehle oder was anderes?

Eingehende NoteOn- und NoteOff-Befehle sollen im Arduino verarbeitet werden und im Anschluss den Lesekopf eines Diskettenlaufwerks ansteuern. Diese Ansteuerung (welche auf mono beruht) funktioniert bereits.

Gruß Chris

Ok, da fällt mir auch nur das Befüllen und Löschen von Arrays ein. Tricky :0

Hallo,

du kannst ein Array mit 10Elementen und eine Variable nehmen, die auf das letzte Element zeigt. Jede neue Note wird als höchstes Element in das Array geschrieben. Wird die Note osgelassen, wird die darunterliegende gespielt. Wird ein Note-Off gesendet, wird entweder die Note im Stapel gesucht und durch aufrutschen der anderen gelöscht oder du schreibst eine Null hinein, als Zeichen dass die nächst gültige Note des Stapels gespielt werden soll. Die Aufrutschvariante dürfte fehlerfreier funktionieren.

So in der Art dachte ich mir das. Kannst Du mir evtl. die hierfür passenden C-Befehle nennen, damit ich irgendwo ansetzen kann. Mein Kenntnisstand reicht noch nicht aus um das ohne Hilfe stemmen zu können.

Ich bin aber durchaus lernfähig.

Gruß Chris

Hallo,

im Endeffekt geht das ganze mit for oder while-Schleifen und if-else Abfragen. Zeichne dir ein Flussdiagramm und übersetze das in C-Code.

Du brauchst das Array, die Zeigervariable, vielleicht ein, zwei Statusvariablen und Zählvariablen.

So,

ich habe mir das Ganze mal als (schnell hingekritzeltes) Flussdiagramm aufgemalt und konnte hierdurch drei Kernfragen herausarbeiten, die ich an dieser Stelle gerne stellen möchte:

  1. Wie kann man abfragen, ob bei einem Array sämtliche Indexe eine Null enthalten, sprich ob das Array "leer" ist?

  2. Wie kann man bei einem Array den Index eines gesuchten Wertes erfragen?
    Beispiel: array[] = {23, 36, 38} Gesucht wird nach "38". Als Ergebnis wird die "2" gesucht.

  3. Wie kann man bei einem Array (ähnlich wie bei der Bitmanipulation) bestimmte, aber nicht alle Indexe um jeweils einen Index nach links verschieben?

Gruß Chris

Warum wollst du abfragen, ob sämtliche Werte = 0 sind? Wenn du auf 0 setzen möchtest, schau dir memset an, ansonsten Wandel die Schleife ab.

const int MAX = 10;
const int SEARCH = 4;

for(int i = 0; i < MAX; i++)
{
if(array[i] == SEARCH) { Serial.print("Array Index : "); Serial.println(i);}
}
for(int i = 0; i < MAX-1; i++) 
{
array[i] = array[i+1];
}

Nachdem ich bei einer eingehenden NoteOff-Meldung den Wert im Array gesucht habe, wird dieser Index genullt. Im Anschluss würde ich dann prüfen, ob das Array nun leer ist. Ist es nicht leer, würde ich alle sich rechts von diesem Index sich befindenden Indexe nach links verschieben. Ist es leer, würde ich den Schreibindex, welcher bei einem eintreffenden NoteOn-Befehl den ersten leeren Index beschreiben soll auf null zurücksetzen.

Werde das Flussdiagramm die Tage mal sauber aufmalen und auch dann selbstverständlich den fertigen. Code posten, damit alle was davon haben.

Gruß Chris

Chris72622:
2. Wie kann man bei einem Array den Index eines gesuchten Wertes erfragen?
Beispiel: array[] = {23, 36, 38} Gesucht wird nach "38". Als Ergebnis wird die "2" gesucht.

Du kannst eine for-Schleife vorzeitig mit "break" verlassen. Wenn du also den Index außerhalb der Schleife deklariert, kannst du jedes Element mit einer Konstante vergleichen und dann mit "break" abbrechen wenn sie gleich sind.

Genauso kannst du vergleichen ob alle 0 sind. Du setzt einen boolean vorher auf true. Dann brichst du ab sobald ein Element ungleich 0 ist und setzt den boolean auf false. Wenn er nach dem Ende Schleife immer noch true ist, sind alle Werte 0

Umkopieren sollte eigentlich nicht nötig sein. Merke dir wie gesagt einen Index in einer zusätzlichen Variablen.

Serenifly,

ich verstehe Deinen letzten Satz nicht.

sschultewolter, dank Dir. Damit kann ich was anfangen.

Anbei wie versprochen die Flussdiagramme.

Kompilieren tut es nun schonmal..

/**************************************************  Auswertung MIDI_NoteOn  */

void rx_NoteOn(byte channel, byte pitch, byte velocity)  // NoteOn_Befehl erhalten
{
  debug_print("NoteOn, Taste ");
  debug_println(pitch);
  if(array_full() == 0)  // Sollte das Array noch nicht komplett voll sein
  {
    pitchArray[writeIndex] = pitch;
    readIndex++;
    writeIndex++;
  }
}

boolean array_full()  // Prüft, ob das Array komplett voll ist
{
  boolean fill = 1;
  for(int i = 1; i < sizeof(pitchArray) - 1; i++)
  {
    if(pitchArray[i] == 0)
    {
      debug_println("Array nicht voll");
      fill = 0;
      break;
    }
  }
  if(fill) debug_println("Array voll");
  return fill;
}

/**************************************************  Auswertung MIDI_NoteOff  */

void rx_NoteOff(byte channel, byte pitch, byte velocity)  // NoteOff_Befehl erhalten
{
  debug_print("NoteOff, Taste ");
  debug_println(pitch);
  index_search(pitch);     // Suche nach der passenden Indexposition
  if(eraseIndex)           // Nur wenn sich die zu suchende Taste im Array befindet..
  {
    pitchArray[eraseIndex] = 0;  // Index benullen
    if(array_empty()) index_shift();  // Verschiebt alle Indexe rechts von "eraseIndex", wenn sich noch etwas im Array befindet
    else debug_println("Keine Tasten werden mehr gedrueckt gehalten");
    readIndex--;
    writeIndex--;
  }
}

void index_search(byte pitch)  // Im Array nach dem Index einer bestimmten Taste suchen
{
  for(byte i = 0; i < sizeof(pitchArray) - 1; i++)
  {
    if(pitchArray[i] == pitch)
    {
      eraseIndex = i;  // Gibt den Index an, an dem die Taste abgelegt wurde
      break;
    }
  }
}

boolean array_empty()  // Prüft, ob das Array komplett leer ist
{
  boolean fill = 0;
  for(int i = 0; i < sizeof(pitchArray) - 1; i++)
  {
    if(pitchArray[i] != 0)
    {
      debug_println("Array nicht leer");
      fill = 1;
      break;
    }
  }
  if(!fill) debug_println("Array leer");
  return fill;
}

void index_shift()  // Verschiebt alle Indexe rechts von "eraseIndex" um jeweils einen Index nach links
{
  byte i = eraseIndex;
  for(i; i < sizeof(pitchArray) - 1; i++)
  {
    pitchArray[i] = pitchArray[i+1];
  }
}

Gruß Chris

Edit_20032014: Flussdiagramm wurde weiter vereinfacht, in dem Index 0 weder für Schreib-, noch für Lesevorgänge verwendet wird. Daher hat das Array nun elf Indexe. eraseIndex gibt an, an welcher Indexposition eine zuvor gedrückt gehaltene Taste "gelöscht" werden soll.

Edit_27032014: Flussdiagramm wurde noch weiter vereinfacht und getestet. Bei Interesse am Code einfach fragen. :stuck_out_tongue: