Poti an analogen Pin als MIDI ausgeben

Ak-Nak47:
Habe mich jetzt an den vorgegebenen Examples aus der Arduino Software und den Beispielen hier etwas zusammengeschustert.

Zusammengeschustert ist das richtige Wort dafür.

Ak-Nak47:
Bin mir noch nicht im klaren darüber was ich gemacht habe,

Das sieht man, wenn Du Dir nicht die Mühe machst die Sprache zu lernen und die Funktion der einzelnen Befehle zu lernen, wirst Du nicht weit kommen. Du hast ein Beispiel mit einer LED benutzt, dessen Helligkeit über einen Sensor (Poti) geregelt wird, richtig?
Die Helligkeit einer LED hat aber nun so gar nichts mit dem MIDI-Protokoll zu tun.

Du kannst nicht irgend etwas senden und erwarten das Ableton Live das als MIDI versteht, wenn Du Dich nicht an das MIDI-Protokoll hältst. Das habe ich Dir weiter oben schon verlinkt.
Im Grunde ist es recht einfach:
Eine MIDI-Nachricht besteht aus ein bis drei Bytes.
Beispiele für ein Byte lange Kommandos sind z.B.
clock = 248
start = 250
stop = 252
Beispiele für drei Byte lange Kommandos hatten wir ja schon, z.B. 'note on' und 'note off'.
Nehmen wir als Beispiel 'note on':
Im ersten Byte wird der Befehl 'note on' und der Kanal, für den dies gilt, gesendet:
'note on' ist als 1001 definiert (diese vier Bit heissen im MIDI-Jargon 'first nibble' oder 'most-significant nibble'), jetzt hängen wir noch vier Bit für den Kanal dran ('least-significant nibble'). Nehmen wir Kanal 11, da wir bei Null mit dem Zählen beginnen, müssen wir tatsächlich 10 und nicht 11 verwenden. 10 in binärer Schreibweise ist 1010. Damit erhalten wir (binär) für das Kommando 'note on' auf Kanal 11: 10011010 (dezimal 154).

Das zweite Byte in diesem Kommando ist die Note. MIDI kennt 128 Noten (0 - 127); das der Kammerton A die MIDI Notennummer 69 hat wissen wir ja schon.

Das dritte Byte ist 'velocity', der Anschlag. Dieser kann Werte von 0-127 annehmen. Da wir richtig in die (eine) Taste hauen wollen, nehmen wir 127.

Somit haben wir jetzt die drei Bytes für das komplette Kommando "Note A3 auf Kanal 11 mit vollem Anschlag AN":
154 69 127
In Arduino wäre das dann:

Serial.write(154);
Serial.write(69);
Serial.write(127);

Und genauso wird für alle anderen Befehle, wie 'polyphonic aftertouch', 'control change', 'pitch wheel change', und wie sie alle heissen, verfahren.
Die Lautstärke z.B. versteckt sich hinter 'control change' ('first nibble' 1011). "Ändere etwas auf Kanal 11" wäre also 1011 + 1010 = 10111010 (dezimal 186) [Achtung, das ist natürlich keine Addition.]
Was ändere ich? Da schaue ich wieder bei midi.org und sehe: 'channel volume' ist definiert als 00000111 (dezimal 7). Worauf ändere ich die Lautstärke? Auf das, was der Arduino vom Poti einliest (mögliche Werte wieder 0-127).

Serial.write(186);              // control change auf Kanal 11
Serial.write(7);                // Ändere die Lautstärke...
Serial.write(meinWertVomPoti);  // ... auf meinWertVomPoti

Auf midi.org steht das alles in Binär, Hexadezimal und Dezimal; damit brauchst Du Dich also noch nicht einmal groß rumschlagen, nachlesen reicht. Ich wollte es, wegen dem grundsätzlichen Verständnis, aber einmal erklären.
Eigentlich ist das MIDI-Protokoll total simpel. Wenn Dir das, auf diese Art, zu kompliziert ist, würde ich Dir die Verwendung der MIDI-Bibliothek empfehlen, diese hat mkl0815 freundlicherweise bereits verlinkt.
Da vereinfacht sich die Sache zu 'sendControlChange (byte ControlNumber, byte ControlValue, byte Channel)'.
Für obiges Beispiel also sendControlChange(7, meinWertVomPoti, 11);

In Deinem Programm stecken, unabhängig von der MIDI-Kommunikation noch diverse Fehler. Ich bezweifle das Du das Programm überhaupt ohne Fehler compilieren kannst.
Hast Du das mal probiert? Am besten lernt man immer noch, wenn man versucht die Fehler selbst zu finden und zu beheben. Erst wenn man damit nicht weiter kommt, sollte man fragen.
Zu Deinem Programm:

  // auslesen des Wertes aus de Analogen Eingang
  sensorValue = analogRead(INPUT);

Was ist INPUT, das ist nirgendwo definiert. Hier sollte sicher "analogInPin" stehen.

analogWrite(analogInPin, outputValue);

Warum willst Du nochmal einen Wert auf dem Eingangspin ausgeben? Das macht keinen Sinn. Abgesehen davon ist "analogWrite()" eine Funktion die nur für PWM-Pins Sinn macht, da Du damit das Tast-Verhältnis des PWM-Signals setzen kannst.

Der Rest ist erstmal syntaktisch korrekt, an der Semantik musst Du aber definitiv noch arbeiten.
Wenn Du es geschafft hast, die korrekten MIDI-Daten an Deinen Rechner zu senden, solltest Du das sicher nicht alle 10ms machen, sondern nur dann, wenn sich der Wert auch geändert hat. Ziel sollte es immer sein nur so viele Daten zu übertragen, wie nötig, nicht so viele wie möglich.

Wie gesagt, du musst dir überlegen, wie die MIDI-Kommunikation eigentlich funktioniert, zumindest den groben Rahmen. Wenn die Erläuterungen von mime (zumindest halbwegs) verstanden hast, du aber eine leichtere, eingängige Lösung suchst, dann kannst du die bereits verlinkte MIDI-Bibliothek verwenden. Soweit ich das beurteilen kann, lässt diese vom Funktionsumfang keine Wünsche übrig.

mkl0815:
In Deinem Programm stecken, unabhängig von der MIDI-Kommunikation noch diverse Fehler. Ich bezweifle das Du das Programm überhaupt ohne Fehler compilieren kannst.

Doch es läßt sich compilieren und es sendet auch Daten an den Computer. Da er sich aber offenbar überhaupt nicht mit dem MIDI-Protokoll beschäftigt hat, sendet sein Programm quer durch allerlei MIDI-Kommandos und Kanäle irgendetwas, manchmal auch für MIDI-Geräte/Programme unverständlichen Kram. Das hat mich auch erstaunt.

14:31:31.105	To MIDI Monitor (Untitled)	Active Sense		
14:31:31.115	To MIDI Monitor (Untitled)	Active Sense		
14:31:31.125	To MIDI Monitor (Untitled)	Stop		
14:31:31.136	To MIDI Monitor (Untitled)	Continue		
14:31:31.145	To MIDI Monitor (Untitled)	Start		
14:31:31.156	To MIDI Monitor (Untitled)	Invalid		1 bytes
14:31:31.167	To MIDI Monitor (Untitled)	Invalid		1 bytes
14:31:31.177	To MIDI Monitor (Untitled)	Clock		
14:31:31.187	To MIDI Monitor (Untitled)	Clock		
14:31:31.197	To MIDI Monitor (Untitled)	Invalid		1 bytes
14:31:31.208	To MIDI Monitor (Untitled)	Clock		
14:31:31.217	To MIDI Monitor (Untitled)	Invalid		1 bytes
14:31:31.228	To MIDI Monitor (Untitled)	Tune Request		
14:31:31.248	To MIDI Monitor (Untitled)	Invalid		1 bytes
14:31:31.270	To MIDI Monitor (Untitled)	Invalid		2 bytes
14:31:31.290	To MIDI Monitor (Untitled)	Invalid		1 bytes
14:31:31.352	To MIDI Monitor (Untitled)	SysEx		1 bytes
14:31:31.659	To MIDI Monitor (Untitled)	Note Off	13	122	0

sth77:
Soweit ich das beurteilen kann, lässt diese vom Funktionsumfang keine Wünsche übrig.

Man kann Omni wohl nur empfangen, was ihm nicht viel nützen wird, da er ja senden will.
(Omni heisst nichts anderes als "auf allen 16 Kanälen", statt nur auf einem Kanal)
Aber diese kleine Restriktion ließe sich leicht mit einer For-Schleife, die einmal durch alle 16 Kanäle rattert, umgehen.
Mir persönlich ist die "mach es selbst"-Methode lieber, da weiss man was das Programm tatsächlich macht (und was man selbst macht). Aber das ist vielleicht Geschmackssache. So oder so muß man sich mit dem MIDI-Protokoll beschäftigen, wenn ich nicht weiss, was ich tue, hilft mir auch keine library weiter.

Es Funktioniert durch Zufall, weil INPUT in der Arduino.h der IDE definiert wird. Ansonsten würde es Fehler geben beim Compilieren.
Allerdings mit 0x0, also dem Wert 0.
Es wird also ein analogRead(0) aufgerufen. Das bringt aber nix, denn "A0" ist mit 14 definiert.
Damit wird also nicht das Poti ausgelesen.

Frag mich nicht warum es überhaupt geht, wundert mich auch sehr. Aber ich habe es ja ausprobiert, siehe oben, es sendet halt irgendetwas und das Ergebnis ändert sich auch, wenn ich am Poti drehe. Nur halt völlig wahl- und sinnlos. Schon erstaunlich.

rofl :astonished:

int analogRead(uint8_t pin)
{
        uint8_t low, high;

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
        if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#else
        if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

Das ist aus /hardware/arduino/cores/arduino/wiring_analog.c (IDE Libs)
Es funktioniert, weil für den Arduino der A0 Eingang auf Pin 14 gemappt wird. Da wir einen ATmega328 (bzw.168) haben, gibt es nur digitale 13 Pins und die IDE verwendet Pin14 als ersten Analogen. Daher funktioniert das Programm auch, denn da das INPUT als 0x0 definiert ist und das als Parameter übergeben wird, wird tatsächlich der erste analoge Eingang ausgewertet.
Würde man korrekterweise die 14 (oder A0) übergeben, würde aus dann genau der folgende Code ausgeführt:

if (pin >= 14) pin -= 14; // allow for channel or pin numbers

Und es würde auch wieder "0" heraus kommen.
Wir haben es also mit einem "falschen Fehler" zu tun. :slight_smile:

Ich seh schon ich will mit dem Kopf durch die Wand. :slight_smile: Doch der Beton ist härter als ich dachte. Das ist halt absolutes Neuland für mich. Hauptberuflich bin ich Konstrukteur i Sondermaschinenbau und das hier soll der ausgleich zu täglichen Mechanik sein.

Ich will euch auf keinen Fall verrückt machen, ich probiere jetzt einfach ma weiter und lass euch meine Ergebnisse hier auf dem Board, wäre nett wenn ihr sie weiter ausdiskutiert. :fearful:

...scheiße ich muss passen, ich krieg das irgendwie nicht hin. weiss gar net wo ich anfangen soll...! arghhh

Also wieder ein wenig geschuster und es tut auf jedenfall das was es tun sollte im abelton.

void setup()
{                
Serial.begin(38400); 
}

int THRESHOLD=2;
int maxPin;
int knobFunction(const int pin, int divisor)
{
  static int prevRead[6] = {0, 0, 0, 0, 0, 0};   
  int output = analogRead(pin)/4;	   
  if (abs(output - prevRead[0])  >= THRESHOLD)
    {prevRead[pin] = output;}
  else
    {output = prevRead[pin];}
  return output/2;
}

void loop () {
  static int lastmaxVal = 0;
  int maxVal = knobFunction(0, 4);
  if(maxVal != lastmaxVal)
  {
    Serial.println(maxVal);
    lastmaxVal = maxVal;
    
Serial.write(186);              // control change auf Kanal 11
Serial.write(7);                // Ändere die Lautstärke...
Serial.write(maxVal);       // ... auf meinWertVomPoti
  }
}

Jetzt habe ich aber noch 3 Dinge die ich nicht ganz kapiere oder nicht nachvollziehen kann:

  1. Senden tut er das Signal nur wenn ich am Poti drehe, das ist so in Ordnung. Im Abelton zeigt er aber ständig ein Empfangs-Signal an. Ich gehe davon aus das liegt daran das das Tool Serial_MIDI_Converter dafür verantwortlich ist. Oder?

  2. Im Serialmonitor zeigte vor meiner "schusterei" einen Wert von 0-127 an, so hat er es aber nicht im Abelton erkannt, dann habe ich die Serial.write Befehle eingefügt. Dann hat er im Serialmonitor beim drehen des potis einen "Mischwert" angzeigt z.B.: 87 und eine Zeile darunter ºaW (so was ähnliches). Ich gehe davon aus das im Code die Zeile Serial.println(maxVal); dafür verantwortlich ist das er beide Werte anzeigt?

  3. Um das Signal ans Abelton zu übergeben benutze ich das Tool Serial_MIDI_Converter, gibt es eine Möglichkeit das ganze auch ohne dieses Tool benutzen zu müssen?

Viele Grüße.

Das ständige Senden liegt einfach daran, das Du innerhalb von loop() ein

  static int lastmaxVal = 0;

stehen hast. Damit wird bei jedem Durchlauf von loop die Variable neu erzeugt und auf 0 gesetzt.
Damit ist das " if(maxVal != lastmaxVal)" aber immer erfüllt und es wird jedesmal gesendet.
Wenn Du das noch aus der Loop rausnimmst, sollte es passen.
Mario.

Er sendet nicht ständig, sondern nur, wenn sich das Signal um THRESHOLD verändernd hat :wink:

Ak-Nak47:
2. Im Serialmonitor zeigte vor meiner "schusterei" einen Wert von 0-127 an, so hat er es aber nicht im Abelton erkannt, dann habe ich die Serial.write Befehle eingefügt. Dann hat er im Serialmonitor beim drehen des potis einen "Mischwert" angzeigt z.B.: 87 und eine Zeile darunter ºaW (so was ähnliches). Ich gehe davon aus das im Code die Zeile Serial.println(maxVal); dafür verantwortlich ist das er beide Werte anzeigt?

mit Serial.println(maxVal); schickst Du ja auch nur irgendwas über die Leitung, was nicht dem MIDI-Protokoll entspricht.
Bei mir sieht das dann so aus:
+10.037 - Error: got nonsense MIDI data: 35300d0a
+10.047 - Error: got nonsense MIDI data: 35310d0a
+10.057 - Error: got nonsense MIDI data: 35320d0a
+10.067 - Error: got nonsense MIDI data: 35330d
+10.069 - Error: got nonsense MIDI data: 0a
usw. Also das musst Du rauskommentieren, das kannst Du zum debuggen in der IDE nehmen, aber für Live musst Du es löschen/auskommentieren.
Die °A °B und sonstige seltsame Symbole kommen von den drei Zeilen in denen Du die MIDI-Nachricht schickst.

Ak-Nak47:
3. Um das Signal ans Abelton zu übergeben benutze ich das Tool Serial_MIDI_Converter, gibt es eine Möglichkeit das ganze auch ohne dieses Tool benutzen zu müssen?

Nur wenn Du Hardware hast, die dir MIDI zu USB umsetzt und Du z.B. ein MIDI-Shield für den Arduino hast.

Die Variable 'divisor', welche Du mit der Funktion 'knobFunction' übergibst, wird überhaupt nicht verwendet, dann kannst Du sie auch komplett rauswerfen. (kostet nur unnötig Speicher)

int output = analogRead(pin)/4; hier wird durch 4 und
return output/2; hier noch mal durch 2 dividiert - eine Division reicht :wink:

int output = analogRead(pin)/8;
...
return output;

Multiplikationen und Divisionen sind für Mikrokontroller sehr aufwendig, deshalb solltest Du eine entfernen, dann wird der Code flotter (und 10 Bytes kürzer).

mime:
Er sendet nicht ständig, sondern nur, wenn sich das Signal um THRESHOLD verändernd hat :wink:

Stimmt, das liegt aber daran, das die Funktion knobFunction den gleichen Fehler macht und das Array "prefRead" immer wieder neu definiert und damit wenn der Wert sich nicht ändert eine "0" zurückliefert. Damit ist aber im loop die Bedingung wieder erfüllt. Wieder ein falscher Fehler :slight_smile:
Der Code ist alles andere als optimal, das macht es unheimlich schwierig Bugs von echten Fehlern zu unterscheiden ...

mkl0815:
Stimmt, das liegt aber daran, das die Funktion knobFunction den gleichen Fehler macht und das Array "prefRead" immer wieder neu definiert und damit wenn der Wert sich nicht ändert eine "0" zurückliefert. Damit ist aber im loop die Bedingung wieder erfüllt. Wieder ein falscher Fehler :slight_smile:
Der Code ist alles andere als optimal, das macht es unheimlich schwierig Bugs von echten Fehlern zu unterscheiden ...

Nein, die Variablen sind ja als "static" definiert:
Variables declared as static will only be created and initialized the first time a function is called.

edit: Ich finde den Code aber auch verwirrend.

Okay danke für die Verbesserungen und Tips. Habe das jetzt auch verändert mit der division und dem Serial.print.

Wenn ich jetzt mehrere Potis anschießen will also im Eingang 0, 1, 2, 3 usw. wie muss ich das im code einbinden bzw. ansteuern?

Grüße
Ak-Nak47

Als erstes möchte ich Dir vorschlagen vernünftige Namen zu vergeben, also nicht so etwas wie poti1, poti2 .... in ein paar Wochen weisst Du sonst u.U. selbst nicht mehr was was ist. Also vielleicht so etwas wie lautPoti...oder was immer Dir einleuchtend erscheint.

Du kannst den Pin jedesmal in die Befehle schreiben:
meinWert = analogRead(A0);

Oder, was bei größeren Programmen besser ist, auf eine Variable legen. Das hat den großen Vorteil, daß Du, wenn Du die Hardware änderst, nicht den ganzen Code abklappern musst um A0 in Irgendetwas zu ändern, sondern nur diese eine Variable ändern musst.

Beispiel:

/* Variablen */
int volume, int balance;

/* Pinbelegung */
const int volumePot = A0;
const int balancePot = A1;
// u.s.w.
void setup() {
  ....
}
void loop(){
....
}

Oder meintest Du, wie die Funktion 'knobFunction' mit mehreren Potis funktioniert?

ja ich meinte wie ich diese funktion die ich jetzt auf den einen poti habe auch für mehrere angeschlossene potis benutze kann, wo genau binde ich die potis im code ein?

Das ist in der Funktion schon eingebaut. Du übergibst ihr den Pin an dem das Poti hängt.

static int prevRead[6] = {0, 0, 0, 0, 0, 0};
In dieser Zeile wird ein Array 'prevRead' erzeugt, darin speichert die Funktion sechs Werte für die sechs analogen Eingänge. Also 'prevRead[0]' ist dann der Wert von A0 u.s.w..
int output = analogRead(pin)/8;
Hier wird der Eingang 'pin' ausgelesen, 'pin' ist die Variable, die Du der Funktion übergeben musst. Das sieht man auch an der Zeile:
int knobFunction(const int pin)
In der Klammer steht der Parameter der übergeben wird.

/* Variablen */
int volume, lastVolume, balance, lastBalance;
int threshold = 2;

/* Pinbelegung */
int volumePot = A0;
int balancePot = A1;

void setup() {
  Serial.begin(38400); 
}

void loop(){
/* Read and send volume */
  volume = knobFunction(volumePot);
  if(volume != lastVolume) {
    lastVolume = volume;
    //Serial.print("volume: ");
    //Serial.println(volume);
    Serial.write(186);
    Serial.write(7);
    Serial.write(volume);
  }
/* Read and send balance */
  balance = knobFunction(balancePot);
  if(balance != lastBalance) {
    lastBalance = balance;
    //Serial.print("balance: ");
    //Serial.println(balance);
    // irgend etwas tun
  }
}

int knobFunction(const int pin)
{
  static int prevRead[6] = {0, 0, 0, 0, 0, 0};   
  int output = analogRead(pin)/8;	   
  if (abs(output - prevRead[0])  >= threshold)
    {prevRead[pin] = output;}
  else
    {output = prevRead[pin];}
  return output;
}

Balance fällt auch wieder unter 'control change'.
Kanal ist nun auch eine Variable (die habe ich wieder auf Byte gesetzt um RAM zu sparen), somit kannst Du den Kanal einfach ändern. Wenn Du einen Taster hast, dann z.B. mit dem.

/* Variablen */
byte volume, lastVolume, balance, lastBalance;
byte channel = 1;     // Hier Kanal festlegen (1 bis 16)
int threshold = 2;

/* Pinbelegung */
const int volumePot = A0;   // Poti für die Lautstärke
const int balancePot = A1;  // Poti für die Balance

void setup() {
  Serial.begin(38400); 
}

void loop(){
/* Lies und sende Lautstärke */
  volume = knobFunction(volumePot);
  if(volume != lastVolume) {
    lastVolume = volume;
    //Serial.print("volume: ");
    //Serial.println(volume);
    Serial.write(175 + channel);  // Sende 'control change' auf Kanal 'channel'
    Serial.write(7);              // ändere Lautstärke auf...
    Serial.write(volume);         // den Wert der Variablen 'volume'
  }
/* Lies und sende Balance */
  balance = knobFunction(balancePot);
  if(balance != lastBalance) {
    lastBalance = balance;
    //Serial.print("balance: ");
    //Serial.println(balance);     
    Serial.write(175 + channel);  // Sende 'control change' auf Kanal 'channel'
    Serial.write(8);              // ändere Balance auf...
    Serial.write(balance);        // den Wert der Variablen 'balance'
   }
}

/* Funktionen */
int knobFunction(const int pin)
{
  static int prevRead[6] = {0, 0, 0, 0, 0, 0};   
  int output = analogRead(pin)/8;	   
  if (abs(output - prevRead[0])  >= threshold)
    {prevRead[pin] = output;}
  else
    {output = prevRead[pin];}
  return output;
}

Das ist ein bißchen schizophren, Variablen & Co englisch und Kommentare deutsch :roll_eyes: