Ich bau mir einen CoreMidi Wrapper - und brauche Hilfe

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Ich bau mir einen CoreMidi Wrapper - und brauche Hilfe

    Hallo zusammen,

    ich möchte gerne etwas mit MIDI I/O in C++ programmieren. Unter OS X bietet sich da ja CoreMidi an. Leider wünsche ich mir da eigentlich etwas viel einfacheres, intuitiveres als das was CoreMidi bietet. Ich möchte primär ein MIDI-Objekt haben, welches ich einmal initialisiere, dann einen Kanal setzte und welches dann einzelne Methoden besitzt um die Standard MIDI-Nachrichten zu versenden. Empfangen soll es auch können, das soll aber erst in einem zweiten Schritt implementiert werden.

    Das Projekt habe ich hier ins GitHub gepackt, da findet sich aktuell der C++ Code für den Wrapper an sich und eine kleine main um das ganze zu testen.

    Ich hab mir von hier ein wenig Code geklaut um mein MIDI Gerät grundsätzlich zu finden. Dazu muss ich noch einmal betonen, dass ich keinen großen Plan von Objective C habe - das ist zwar schon ein Zukunftsziel - aber am Ende soll das ganze bestenfalls in reinem C++ münden. Vorerst ist es für die Kompatibilität mit solchen gefundenen Codeschnipseln jedoch erst einmal als Objective C++ Projekt angelegt und ich habe die in Objective C geschriebenen Codezeilen aus dem Link fürs erste grob übernommen.

    Zum eigentlichen Senden habe ich wiederum keine Beispiele gefunden und mir deswegen anhand der der Apple MIDI Services API Reference das etwas selbst zusammengedichtet. Mit halbem Erfolg. Wenn ihr in die main im verlinkten git schaut, seht ihr dass ich die bisher implementierten drei Methoden jeweils einmal mit irgendwelchen Dummy-Werten aufrufe:

    C-Quellcode

    1. midiInterface.sendNote(0x23, 0x34, noteOff);
    2. midiInterface.sendControlChange(0x12, 0x36);
    3. midiInterface.sendProgramChange(8);

    Jedoch wird nur der erste Aufruf wirklich versendet. Kommentiere ich den ersten aus, geht der zweite raus, der dritte aber nicht, usw.

    Grundsätzlich gehe ich in allen drei Methoden gleich vor, hier z.B. die ControlChange:

    C-Quellcode

    1. //Paketliste anlegen und Paket hinzufügen
    2. MIDIPacketList dataToSend;
    3. dataToSend.numPackets=1;
    4. dataToSend.packet[0].timeStamp=0;
    5. dataToSend.packet[0].length=3;
    6. dataToSend.packet[0].data[0]=0xB0 | sendChannel;
    7. //Werte anhängen
    8. dataToSend.packet[0].data[1]=control;
    9. dataToSend.packet[0].data[2]=value;
    10. //Daten senden
    11. MIDISend(outputPort, destination, &dataToSend);
    Alles anzeigen

    Ist das grundsätzlich schon mal grob richtig oder mache ich etwas grundlegend falsch? Irgendwas scheine ich ja auf jeden Fall falsch zu machen. Ich freu mich über ein paar Tips, gerne auch Links die ich nicht gefunden habe in denen das vernünftige Vorgehen erklärt wird.

    Besten Dank schon mal!
  • uC_J schrieb:

    Vorerst ist es für die Kompatibilität mit solchen gefundenen Codeschnipseln jedoch erst einmal als Objective C++ Projekt angelegt und ich habe die in Objective C geschriebenen Codezeilen aus dem Link fürs erste grob übernommen.
    Wenn mich das nicht täuscht, dann ist CoreMidi in C geschrieben (Stichwort: Core Foundation). Da solltest Du direkt in C++ nutzen können bzw. deine Adapter schreiben.

    Jetzt müsste ich wirklich nachschauen, aber MIDIPacketList dürfte eine stink-normale C-Struktur sein. Schau dir mal die Header an, wie Du dafür Speicher anlegen musst!
  • manoh schrieb:

    Wenn mich das nicht täuscht, dann ist CoreMidi in C geschrieben (Stichwort: Core Foundation). Da solltest Du direkt in C++ nutzen können bzw. deine Adapter schreiben
    Ja, das ist sie auch. Nur sind die meisten Beispiele im Netz die diese nutzen von OS X Entwicklern die gerne in Objective C programmieren geschrieben und somit in Objective C. Ziel ist es also diese nutzen zu können und zu gegebener Zeit in gleich funktionalen C++ Code umzuschreiben.

    Ich habe inzwischen die Funktionen MIDIPacketListInit und MIDIPacketListAdd gefunden, die die PacketList vernünftig füllen anstatt so wie ich es tat das darunter liegende Struct (auch da hat manoh recht) direkt manuell zu befüllen. Wahrscheinlich war das ein Fehler. Ich werde es später zu Hause testen, wenn ich zugriff auf meine MIDI Interfaces habe.
  • Danke für den Link und den Buchtipp!

    Leider konnte ich was das Thema MIDI senden angeht keine Hilfe finden. Im Gegenteil, wenn ich mir den Code im oben verlinkten GitHub Repository anschaue finde ich hier eine Funktion die meinem Ansatz aus dem ersten Post stark ähnelt. Mein aktueller Ansatz sieht so aus:

    Quellcode

    1. int midiIO::sendMidiBytes(uint8_t *bytesToSend, int length){
    2. //Initialize Packetlist
    3. pkt = MIDIPacketListInit(&pktList);
    4. //Add bytes to send to list
    5. pkt = MIDIPacketListAdd(&pktList, sizeof(pktList), pkt, 0, length, bytesToSend);
    6. //Send list
    7. MIDISend(outputPort, destination, &pktList);
    8. return 0;
    9. }
    Dabei sind *pkt und pktList private Variablen des Objektes midiIO. Der Funktion wird ein Array von Bytes der größte 2 oder 3 übergeben, welche die eigentliche MIDI-Nachricht darstellt. Dafür habe ich noch extra Funktionen sendNote, sendControlChange, sendProgram geschrieben, die dieses Array zusammenbasteln.

    Dies funktioniert wieder nicht, wenn ich mehrere solcher Aufrufe direkt hintereinander packe, es wird nur die erste Nachricht gesendet. Was aber tut ist, eine Sekunde sleep dazwischen zu packen:


    Quellcode

    1. midiInterface.sendNote(0x23, 0x34, noteOnOff);
    2. sleep(1);
    3. midiInterface.sendControlChange(0x12, 0x36);
    4. sleep(1);
    5. midiInterface.sendProgramChange(8);

    Was könnte da das Problem sein? Warum schafft er es sonst nicht, alles direkt hintereinander fertig zu senden und warum aber mit dem sleep dazwischen (was übrigens durch pures ausprobieren dahin kam)?
  • Ich habe nach langem verzweifeln das Problem endlich gefunden - mein Code war offenbar die gesamte Zeit lang grundsätzlich korrekt, nur mein billiges MIDI Interface hat es nicht richtig hinbekommen. Hab es nun mit zwei verschiedenen neuen Interfaces ausprobiert und war direkt erfolgreich.
    Senden funktioniert also zufriedenstellend.

    Nun hänge ich noch beim Thema empfangen. Dafür brauche ich ja eine MIDIReadProc Funktion, die beim Aufruf von MIDIInputPortCreate angebe. Vorgesehen ist, dass MIDIReadProc eine gewöhnliche C-Funktion sein soll, die laut Doku von einem separaten Thread aufgerufen wird.

    Ich würde es mir eigentlich so wünschen, dass mein C++ Wrapper einige private Function Pointer enthält, die auf verschiedene Callback-Funktionen für die verschiedenen Sorten von MIDI Nachrichten die so ankommen können zeigen, die ich mit öffentlichen Member Funktionen jeweils einmalig externen Funktionen zuordnen kann. Die ReadProc Funktion soll dann schauen welcher Nachrichtentyp eingetroffen ist und die jeweils passende Funktion auf die der Pointer zeigt aufrufen und ihr die für den Nachrichtentyp relevanten Parameter übergeben.

    Dazu habe ich ein Problem:
    Wenn ReadProc private member Funktion meiner Klasse sein soll, kann sie nur static sein, alles andere wird nicht zugelassen. Damit ist sie meines Verständnisses nach eigentlich faktisch nicht viel mehr als eine gewöhnliche C-Funktion. Auch wenn ich das nicht sofort vorhabe frage ich mich, wie das funktionieren kann, wenn ich einmal zwei Objekte habe, welche zwei verschiedene Inputs verwalten. Spätestens da sollte es ja schief gehen, weil ja nicht differenziert wird welche Callback-Funktionen nun aufgerufen werden sollen - die static Funktion gehört ja keinem speziellen Objekt sondern existiert nur für die Klasse allgemein.

    Schaut gerne in mein git rein, wie ich die ganze Sache bisher implementiert habe. Würde mich über ein Feedback und Anregungen freuen!

    Ach ja, da diese Frage nun ja eher speziell C++ relevant sind und weniger spezifisch auf Apples Core Midi Framework bezogen - gehört das dann überhaupt noch in dieses Board?
  • uC_J schrieb:

    Dazu habe ich ein Problem:
    Wenn ReadProc private member Funktion meiner Klasse sein soll, kann sie nur static sein, alles andere wird nicht zugelassen. Damit ist sie meines Verständnisses nach eigentlich faktisch nicht viel mehr als eine gewöhnliche C-Funktion. Auch wenn ich das nicht sofort vorhabe frage ich mich, wie das funktionieren kann, wenn ich einmal zwei Objekte habe, welche zwei verschiedene Inputs verwalten. Spätestens da sollte es ja schief gehen, weil ja nicht differenziert wird welche Callback-Funktionen nun aufgerufen werden sollen - die static Funktion gehört ja keinem speziellen Objekt sondern existiert nur für die Klasse allgemein.
    Klassenfunktion brauchst Du eigentlich nicht. ReadProc kann einfach nur eine statische Funktion sein. Wenn ich das in der Kürze überblicke, gibt es da ja zwei Contex-Pointer (void *). Kannst Du nicht deine Instanz ablegen => die statische Funktion ruft dann eine Methode deiner Instanz auf.


    uC_J schrieb:

    Schaut gerne in mein git rein, wie ich die ganze Sache bisher implementiert habe. Würde mich über ein Feedback und Anregungen freuen!
    Hab mir nur einen Teil angesehen und etwas Anregung aufgeschrieben. (Muss man nicht so ernst sehen)

    coreMidiWrapper.h
    Zeile 8, 9: Defines schreibt man eigentlich groß
    Zeile 18: lässt man gerne weg, dann muss man sich nicht fragen, was eigentlich vector oder function bedeutet. Jedenfalls bin ich so es gewohnt bzw. so liest man das auch oft.
    21 - 23: Konstanten schreibt man in C++ eigentlich anders, aber bitte in Großbuchstaben
    31: Braucht man die Struktur wirklich ode geht das nicht einfacher mit einer Methode, die einem den Namen vom Device zurück gibt. Gibt's das nicht schon???
    37: Kommentar überflüssig, die Funktion ist ausreichend gut benamt. Was intern geschieht interessiert in diesem Fall nicht.
    38: Könnte man da nicht eine Referenz übergeben
    57: BytesToSend beginnt mit einem Großbuchstaben, obwohl alle anderen Variablen mit einem Kleinbuchstaben beginnen
    62 - 64: schreib dir Typedefs
    80: recieve oder receive?

    coreMidiWrapper.mm
    12: würde ich weg lassen
    14: Das brauchst Du nicht, nutzte Context Variablen
    16: Die Implementierung von printDeviceName gefällt mir einfach nicht. Du gibst je nur auf stdout aus. Geht das in CoreFoundation nicht einfacher - Also die Ausgabe eines CFStrings auf stdout? (- Ich weiß es nicht mehr, zu lange her, dass ich CF benutzt habe)
    Allgemein: Vor, nach Operatoren darf man ruhig ein Leerzeichen machen, das liest sich leicher. Auch nach Kontrollfunktionen wie z.B. if schadet ein Leerzeichen nicht. Auch wenn meine Zeilen eher Richtung 80 Zeichen tendieren, mit Leerzeichen spare ich trotzdem nicht.
    ...
  • Ich bin vorhin nicht fertig geworden. Jedenfalls noch zu printDeviceName(). Für was braucht man eigentlich diese Funktion. Möchte man nicht lieber sowas wie cout << myMidiDevice.name() schreiben. Also bezogen darauf, wenn Du einen Adapter schreiben möchtest.

    coreMidiWrapper.mm
    Zeile 45...59: Wär da nicht ein Switch schöner?

    Zeile 70: den Code nur überflogen, aber wenn Du schon nicht auf NULL prüfst, dann vermerke es, dass der Vektor gültig sein muss. Apropos Vector: Wenn das ein std::vector sein soll, für was braucht man dann deviceCount? Der Return-Wert der Funktion gefällt mir auch nicht, vor allem weil ich diesen nicht verstehe. Was soll mir die 1 sagen? Was bringt eigentlich das noDevice?

    Klasse midiIO: Was bedeutet eigentlich diese Klasse? - Managed das mehrere Midi Interface oder ist das ein midi Port oder? Ich finde halt, dass searchDevices und selectDevice nicht zusammen passt.

    Allgemein: Vermutlich schon erwähnt, aber Zeilen wie if((selectedDevice>(connectedDevicesCount-1))||(selectedDevice<0)){ ist echt nicht einfach zu lesen. Und ich bin lesefaul und denkfaul! Für mich ist if ((selectedDevice >= connectedDevicesCount)) || (selectedDevice < 0)){ leichter verständlich, wobei man hier auch ein do {} while schreiben könnte oder gleich eine extra Funktion, die die Eingabe vom Nutzer entgegen nimmt und auf Gültigkeit prüft.

    Hoffe das ist jetzt nicht zu wirr geworden...
  • So, jetzt meld ich mich auch mal wieder. Ich schaff es oft dann doch einige Zeit lang nicht an solchen netten Sachen weiterzuarbeiten, hab mich aber sehr über das ausführliche Feedback gefreut. Man muss dazu sagen, dass ich mir C++ aus meinen basis C-Kenntnissen komplett selbst beigebracht, daher danke auch für das Feedback bzgl. Konventionen und Stil. Anbei einige Anmerkungen zu den Anmerkungen...


    manoh schrieb:

    Klassenfunktion brauchst Du eigentlich nicht. ReadProc kann einfach nur eine statische Funktion sein. Wenn ich das in der Kürze überblicke, gibt es da ja zwei Contex-Pointer (void *). Kannst Du nicht deine Instanz ablegen => die statische Funktion ruft dann eine Methode deiner Instanz auf.
    Ja das fände ich auch am besten, aber irgendwie habe ich nicht ganz verstanden für was diese beiden Pointer genau stehen. Und wie ich dann anhand dessen identifiziere zu welcher Instanz die eintreffenden Daten gehören...

    manoh schrieb:

    Zeile 8, 9: Defines schreibt man eigentlich groß
    Weiß ich eigentlich. Ich glaube das hat Xcode selbst so angelegt... ;)

    manoh schrieb:

    Zeile 18: lässt man gerne weg, dann muss man sich nicht fragen, was eigentlich vector oder function bedeutet. Jedenfalls bin ich so es gewohnt bzw. so liest man das auch oft.
    Okay, ich fand bisher immer schön übersichtlich weil ich halt keinen anderen namespace brauchte. Aber die Begründung leuchtet ein. Werde ich beim nächsten größeren Projekt so halten.


    manoh schrieb:

    21 - 23: Konstanten schreibt man in C++ eigentlich anders, aber bitte in Großbuchstaben
    Wie hättest du es gemacht, eine variable mit Schlüsselwort constant oder was meinst du?



    manoh schrieb:

    31: Braucht man die Struktur wirklich ode geht das nicht einfacher mit einer Methode, die einem den Namen vom Device zurück gibt. Gibt's das nicht schon???
    Ich fand es so hübscher, weil dann immer der sprechende Name mit der wenigsagenden deviceRef gruppiert sind.


    manoh schrieb:

    80: recieve oder receive?
    Ups! :D


    manoh schrieb:

    16: Die Implementierung von printDeviceName gefällt mir einfach nicht. Du gibst je nur auf stdout aus. Geht das in CoreFoundation nicht einfacher - Also die Ausgabe eines CFStrings auf stdout? (- Ich weiß es nicht mehr, zu lange her, dass ich CF benutzt habe)
    Also mir gefällt es auch nicht so wirklich, ich konnte aber partout auch nichts dazu finden wie man einen CFString auf stdout rausgibt. Hab mich dann entschieden es einfach so zu halten, weil es eh nur für ein Prototypen Command-Line Tool gebraucht würde und man in anderen Fällen eh nicht auf stdout ausgeben würde und dort evtl. direkt mit den CFString weiterarbeiten kann - oder was ich eigentlich Plane: Das ganze eh nur zu nutzen um mir einen Wrapper mit dem selben Interface für diverse Mikrocontroller Plattformen zu bauen um das darauf aufbauende irgendwann wieder auch einen Mikroprozessor zu sortieren. Und in dieser Anwendung würde dieser Part dann eh rausfallen. Trotzdem hätte ich es gerne schöner gemacht.

    manoh schrieb:

    Ich bin vorhin nicht fertig geworden. Jedenfalls noch zu printDeviceName(). Für was braucht man eigentlich diese Funktion. Möchte man nicht lieber sowas wie cout << myMidiDevice.name() schreiben. Also bezogen darauf, wenn Du einen Adapter schreiben möchtest.
    Ich finde nicht dass es zur Klasse gehören sollte, da ich die Verfügbaren Interfaces ausgeben können will bevor ich eine Instanz anlege der ich ein bestimmtes Interface zuweise. Dennoch hast du grundsätzlich Recht mit deiner Anregung, daher hab ich es jetzt in eine Funktion umgewandelt, die statt der direkten Ausgabe auf stdout das ganze als std::string zurückgibt.



    manoh schrieb:

    Zeile 70: den Code nur überflogen, aber wenn Du schon nicht auf NULL prüfst, dann vermerke es, dass der Vektor gültig sein muss. Apropos Vector: Wenn das ein std::vector sein soll, für was braucht man dann deviceCount? Der Return-Wert der Funktion gefällt mir auch nicht, vor allem weil ich diesen nicht verstehe. Was soll mir die 1 sagen? Was bringt eigentlich das noDevice?
    Stimmt, den Abschnitt überarbeite ich demnächst noch.





    manoh schrieb:

    Klasse midiIO: Was bedeutet eigentlich diese Klasse? - Managed das mehrere Midi Interface oder ist das ein midi Port oder? Ich finde halt, dass searchDevices und selectDevice nicht zusammen passt.
    Jede Instanz der Klasse soll paar von MIDI I/O Ports repräsentieren. Daher ist jetzt auch searchDevices wie oben schon erwähnt aus der Klasse rausgeflogen, weil es nichts direkt mit der Instanz zu tun hat.

    Das wars erst mal, ich werde jetzt parallel mal meine eigentliche Zielanwendung entwickeln welche den Wrapper nutzt und dabei parallel diesen immer mal wieder weiter entwickeln.
  • Auf die Kürze nur zwei Rückmeldung:

    uC_J schrieb:

    Ja das fände ich auch am besten, aber irgendwie habe ich nicht ganz verstanden für was diese beiden Pointer genau stehen. Und wie ich dann anhand dessen identifiziere zu welcher Instanz die eintreffenden Daten gehören...
    Guck dir an, wie hier player bei MIDIInputPortCreate übergeben wird und bei readProc wieder rausgeholt wird:

    github.com/abbood/Learning-Cor…CH11_MIDIToAUGraph/main.c



    uC_J schrieb:

    Jede Instanz der Klasse soll paar von MIDI I/O Ports repräsentieren. Daher ist jetzt auch searchDevices wie oben schon erwähnt aus der Klasse rausgeflogen, weil es nichts direkt mit der Instanz zu tun hat.
    CoreMidi ist ja objektorientiert. Zwar in C und da muss man halt sein Objekt immer jeder Methode übergeben. Ich würde mich eher an die vorhandene API halten und einen Cpp-Adapter schreiben. Das dann einfach erweitern und vorallem Beipiel-Code und etwas Dokumentation schreiben.

    Bzw. wenn ich nach Obj-C und CoreMidi suche, dann habe ich gleich zwei Wrapper gefunden. Da kannst Du dir auch Inspiration holen: z.B. github.com/mixedinkey-opensource/MIKMIDI
  • uC_J schrieb:

    Also mir gefällt es auch nicht so wirklich, ich konnte aber partout auch nichts dazu finden wie man einen CFString auf stdout rausgibt. Hab mich dann entschieden es einfach so zu halten, weil es eh nur für ein Prototypen Command-Line Tool gebraucht würde und man in anderen Fällen eh nicht auf stdout ausgeben würde und dort evtl. direkt mit den CFString weiterarbeiten kann
    Mal schnell rumgecodet, aber man kann ja auch einfach direkt den vorhanden C-String ausgeben ohne davor erst eine Kopie zu erstellen.

    C-Quellcode

    1. //
    2. // Cpp Konsolenprojekt erstellen
    3. //
    4. // Linked Framework and Libraries:
    5. // - CoreMidi.framework
    6. // - CoreFoundation.framework
    7. //
    8. #include <iostream>
    9. #include <CoreMIDI/CoreMIDI.h>
    10. int main(int argc, const char * argv[]) {
    11. ItemCount defCount = MIDIGetNumberOfDevices();
    12. for (ItemCount i = 0; i < defCount; ++i) {
    13. MIDIObjectRef device = MIDIGetDevice(i);
    14. assert(device != 0);
    15. CFStringRef name = nil;
    16. OSStatus status = MIDIObjectGetStringProperty(device, kMIDIPropertyName, &name);
    17. assert(status == noErr);
    18. const char *cName = CFStringGetCStringPtr(name, kCFStringEncodingUTF8);
    19. assert(cName != NULL);
    20. std::cout << i << " device name: " << cName << std::endl;
    21. }
    22. return 0;
    23. }
    Alles anzeigen
    Das geht sicherlich noch eleganter und hat vermutlich schon wer gemacht. Nach kurz rumgooglen bin ich auf CFPP gestoßen. Sieht interessant aus auf den ersten Blick.

    uC_J schrieb:

    oder was ich eigentlich Plane: Das ganze eh nur zu nutzen um mir einen Wrapper mit dem selben Interface für diverse Mikrocontroller Plattformen zu bauen um das darauf aufbauende irgendwann wieder auch einen Mikroprozessor zu sortieren. Und in dieser Anwendung würde dieser Part dann eh rausfallen. Trotzdem hätte ich es gerne schöner gemacht.
    Ich denke nicht, dass das Sinn macht. Beim Mikrocontroller nutzt Du halt UART und unter einem PC USB-MIDI-Schnittstellen. Die MIDI-Schnittstellen sind einmal statisch und das andere mal dynamisch, können sich ändern. In deinem µC-Programm weißt Du aber genau, wieviele Ein-/Ausgänge es besitzt und Namen interessieren auch keinen. Das eine ist halt Port-A und das andere Port-B (oder wie man es benennt).