Nach dem es in diesen Thread: Beitrag "USB CDC von Stefan Frings und WS" eine ganze Weile um USB-CDC ging, habe ich am Coronafreiwochenende mal versucht dem Code von WS so zu adaptieren, dass daraus ein Midi-Interface mit 1 bis 4 Ports (jeweils MidiIn und MidiOut) wird. Zu CDC findet man häufig was im Netz, Midi ist dagegen relativ selten oder in komplexen Umgebungen zu finden. Getestet habe ich das mit einem bluepill-Board mit stm32f103c8 (und auch mit einem Fake wo ein CKS32F103 drauf war). Im Anhang ein abgespecktes Projekt für Segger ES. Es ist nur der reine USB-Teil enthalten. Das Demoprogramm schickt das was es per USB erhält 1 zu 1 wieder zurück, nicht mehr und nicht weniger. Für ein richtiges Interface muss natürlich noch einiges mehr getan werden. Das ganze besteht nur aus den Dateien main.cpp, usbmidi.h und usbmidi.cpp. Alle anderen Dateien im Projekt hat Segger ES erzeugt und wurden nicht verändert. Aufpassen muss man in usbmidi.cpp auf den Namen des Interrupts, hier hat Segger seine eigene Logic. Soll das in andere Umgebungen übernommen werden, muss der Name angepasst werden. Wie hier ab Seite 16 beschrieben: https://www.usb.org/sites/default/files/midi10.pdf wird immer 4Byte weise gearbeitet. Deshalb habe ich die Ringbuffer auf uint32_t umgestellt. Der Devicedescriptor ist sehr cryptisch, aber da verschwende ich keine Lebenszeit damit das lesbarer zu machen. Für mich und das was ich damit vor habe ist es halt ausreichend. Vielleicht kann es ja jemand mal gebrauchen. Falls Stefan oder Niklas das interessiert oder sie es in ihren Artikeln verlinken oder verwenden oder veröffentlichen wollen, meinen Segen dafür gibt es.
temp schrieb: > ...habe ich am Coronafreiwochenende mal > versucht dem Code von WS so zu adaptieren, dass daraus ein > Midi-Interface mit 1 bis 4 Ports (jeweils MidiIn und MidiOut) wird. Ja, ganz nett. Ich hätte dazu aber noch ne Anmerkung: Das ganze Gefummel mit "DisableUsbIRQ();" mißfällt mir sehr - und es ist eigentlich auch überflüssig, solange man ein paar Grundsätze beachtet. Schauen wir mal das hier an:
1 | // liefert true, wenn eine MidiMsg abholbereit ist
|
2 | bool UsbRxAvail(void) |
3 | {
|
4 | DisableUsbIRQ(); |
5 | bool res = rxr != rxw; |
6 | EnableUsbIRQ(); |
7 | return res; |
8 | }
|
Diese Funktion kann ja nur liefern, ob etwas zum Lesen vorhanden ist oder nicht. Das macht sie, indem sie die Indizes rxr und rxw vergleicht. Lesen der beiden Indizes darf jeder, aber Schreiben jeweils nur einer, nämlich der, dem der Index gehört. Der Index, der zum Füllen des Ringpuffers dient, ist quasi Eigentum des Interrupthandlers. Der darf ihn schreiben. Der Index, der zum Entleeren des Ringpuffers dient, gehört dem UsbGetMidiMsg(void), diese Funktion darf ihn schreiben. Was also kann passieren, wenn mitten in UsbRxAvail(void) ein USB-Interrupt hineingrätscht? Dann kann sich nur rxw ändern - und zwar nur in Richtung "noch mehr abholbereite Daten sind enthalten". Das Umgekehrte (keine Daten enthalten) kann durch den Interrupt NICHT passieren. Nun gibt es prinzipiell für UsbRxAvail(void) nur 2 Möglichkeiten: a) es gibt nix b) es gibt was (egal wie viel) Dabei ist es doch egal, ob ein Interrupt dazwischen dazu führt, daß es NOCH MEHR Daten gibt, als zuvor schon vorhanden waren. Fall b) "es gibt was" ändert sich nicht, wenn es noch mehr gibt. Folglich ist das Verbieten des Interrupts in dieser Funktion schlichtweg überflüssig und sollte besser aus dieser Funktion beseitigt werden. Der eherne Grundsatz lautet eigentlich: niemand soll in dem Beritt eines Anderen herumreiten. Hier wäre das, daß weder der Interrupt-Handler in den Daten von main() und den Programmteilen, die von main() aus benutzt werden herumschreiben sollte, noch daß main() oder eine Funktion, die von main() aus benutzt wird, in den Daten des Interrupthandlers herumschreiben sollte. Das führt nämlich immer zu dem grundsätzlichen Problem, daß zwei "Leute" auf denselben Daten schreibend herumreiten - und als Notnagel müssen dann Interrupts verboten werden. Das ist wirklich nur ein Notnagel und niemals ein guter Code. Man sollte sowas also tatsächlich NUR DANN tun, wenn es sonst überhaupt nicht anders zu machen geht. Aus meiner Sicht ist das aber eher "programmieren mit dem Holzhammer". Ich hatte es bei meinem Ur-Treiber so gemacht, daß keine anderen Programmteile in den Daten des Interrupthandlers herumschreiben und daß man beim Benutzen sich auch nicht um inneren Befindlichkeiten wie z.B. das Senden von restlichen Zeichen zum Host zu kümmern hat. Andere haben da gemeint, dort hineingrätschen zu müssen, z.B. per UsbTxFlush() - aber das ist eigentlich eine Unannehmlichkeit, die bei einem virtuellen COM-Port nicht erforderlich ist - und auch nicht vorhanden sein sollte. Der VCP sollte das selbst erledigen, ohne daß sich jemand anderes drum kümmern müßte. Ob sowas bei MIDI nötig wäre, weiß ich nicht, hab mich um dieses spezielle Thema noch nicht gekümmert. Aber m.W. basiert MIDI ja wohl ursprünglich auf einem COM-Port mit galvanischer Trennung und 33K Bitrate (oder waren es 32K ? - zu lange her). Ob man da das Forcieren einer Übertragung nötig hat, oder mit dem USB-internen Tick von 1 ms auskommt, um die aufgelaufenen Daten zu transferieren? - ich tendiere da zu letzterem. Der interne Tick sollte ausreichen. W.S.
W.S. schrieb: > > Aber m.W. basiert MIDI ja wohl ursprünglich auf einem COM-Port > mit galvanischer Trennung und 33K Bitrate (oder waren es 32K ? > - zu lange her). > USB-MIDI hat keine Bitrate, sondern 4byte-Pakete mit Midi-Event-Daten. Das ist nicht auf Seriell aufgebaut. Könnte an ahnen, wenn man schon mal USB-Deskriptoren angeschaut hat.
:
Bearbeitet durch User
In 'Projekte & Code' hatte ich auch ein Beispiel für das Bluepill reingesetzt. Da benutze ich ein Stück mbed2 code, der enthält auch eine USBMidi Klasse. Da sind auch Midi Messages zu finden: https://github.com/JojoS62/Bluepill-TestUSBKbdMouse/tree/master/USBDevice/USBMIDI Beitrag "USB Keyboard und Maus mit Bluepill und mbed2"
W.S. schrieb: > Ich hätte dazu aber noch ne Anmerkung: Das ganze Gefummel mit > "DisableUsbIRQ();" mißfällt mir sehr - und es ist eigentlich auch > überflüssig, solange man ein paar Grundsätze beachtet. Ja, in diesem Punkt gebe ich dir Recht. Ich hatte halt nur den letzten Stand verwendet den Niklas in github gestellt hat. W.S. schrieb: > Aber m.W. basiert MIDI ja wohl > ursprünglich auf einem COM-Port mit galvanischer Trennung und 33K > Bitrate (oder waren es 32K ? - zu lange her). Das würde zum tragen kommen, wenn man wirklich ein reines Interface bauen will mit In/Out über UART. Für alle anderen Fälle, z.B. wenn man ein paar Potis oder Schalter in Midi-Daten umsetzt keine Baudrate relevant.
W.S. schrieb: > Ich hatte es bei meinem Ur-Treiber so gemacht, daß keine anderen > Programmteile in den Daten des Interrupthandlers herumschreiben und daß > man beim Benutzen sich auch nicht um inneren Befindlichkeiten wie z.B. > das Senden von restlichen Zeichen zum Host zu kümmern hat. Andere haben > da gemeint, dort hineingrätschen zu müssen, z.B. per UsbTxFlush() - aber > das ist eigentlich eine Unannehmlichkeit, die bei einem virtuellen > COM-Port nicht erforderlich ist - und auch nicht vorhanden sein sollte. > Der VCP sollte das selbst erledigen, ohne daß sich jemand anderes drum > kümmern müßte. Ja die anderen haben Bugs in deinem Code gefixt, was doch ganz freundlich ist. Mit einem Debugger übrigens ;)
Zunächst vielen Dank für den Treiber. Der Beitrag ist schon einige Monate alt, ich habe den Treiber jetzt ausprobiert und er funktioniert für Double- und Quad-, aber nicht für Single-Ports. Eventuell liest das ja noch der Thread-Ersteller und kann etwas dazu sagen?
Habe das Problem gelöst. Die erste Zeile von ConfigDescriptorSingle[] muss geändert werden von: 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0x80, 0x32, // Config nach: 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0x00, 0x32, // Config
Thorsten S. schrieb: > nach: > 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0x00, 0x32, // Config Wenn dem wirklich so ist, steckt da aber noch ein dicker Bug im Code. Beim vorletzten Byte muss bit7 immer 1 sein. Das schreibt die Spec seit usb1.1 vor.
Das ist ja sonderbar. Für die 2- und 4-Port Deskriptoren steht da nämlich auch 0x00.
Was passiert wenn du das bit mal beim 2-fach Port auf 1 setzt? Geht der dann noch?
Thomas Z. schrieb: >> nach: >> 0x09, 0x02, 0x65, 0x00, 0x02, 0x01, 0x00, 0x00, 0x32, // Config > > Wenn dem wirklich so ist, steckt da aber noch ein dicker Bug im Code. > Beim vorletzten Byte muss bit7 immer 1 sein. Das schreibt die Spec seit > usb1.1 vor Ja das ist korrekt. Leider habe ich das beim Kopieren vermasselt. Allerdings habe ich auch nur unter W10 64bit getestet. Alte Windows-Versionen habe ich nicht mehr. Und da ist dieses Bit völlig bedeutungslos. Ob ich bei allen 3 Varianten 0x80 oder 0x00 angebe ist egal, es geht immer. 0x80 ist aber sicher richtig. Bleibt die Frage nach den Unterschieden. Wie und mit was übersetzt du bzw. wie testest du?
temp schrieb: > Alte Windows-Versionen habe ich nicht mehr. Und da ist dieses Bit völlig > bedeutungslos Das Bit hatte soweit ich mich erinnere bei der usb1.0 Spec eine Bedeutung. Irgendwas mit Bus + Selfpowered zusätzlich zu bit6. Es ist also nicht verwunderlich dass es komplett ignoriert wird. Bleibt die Frage warum das eine Änderung bei Thorsten bewirkt.
temp schrieb: > Ob ich bei allen 3 Varianten 0x80 oder 0x00 angebe ist > egal, es geht immer. 0x80 ist aber sicher richtig. > Bleibt die Frage nach den Unterschieden. Wie und mit was übersetzt du > bzw. wie testest du? Bei mir läuft der Single Port nur, wenn ich 0x00 statt 0x80 verwende. Ich verwende die STM32 Cube IDE, also GCC. Die WS-CDC Version läuft bei mir anstandslos, also am Compiler wird es nicht liegen. Ich teste mit Ableton Live. Wenn ich 0x80 verwende, erscheint das MIDI Device zwar im Gerätemanager, aber Ableton Live zeigt keinen Midi Port mehr an. Bei 0x00 ist der Single Port da.
:
Bearbeitet durch User
Das wird eine lange Liste: ---------------------- Device Descriptor ---------------------- bLength : 0x12 (18 bytes) bDescriptorType : 0x01 (Device Descriptor) bcdUSB : 0x200 (USB Version 2.00) -> wrong, device is Full-Speed only bDeviceClass : 0x00 (defined by the interface descriptors) bDeviceSubClass : 0x00 bDeviceProtocol : 0x00 bMaxPacketSize0 : 0x40 (64 bytes) idVendor : 0x0416 (Nuvoton Technology Corp.) idProduct : 0x5012 bcdDevice : 0x0000 iManufacturer : 0x01 (String Descriptor 1) Language 0x0409 : "Nuvoton" iProduct : 0x02 (String Descriptor 2) Language 0x0409 : "USB MIDI STM32 " iSerialNumber : 0x03 (String Descriptor 3) Language 0x0409 : "NT2020101400" bNumConfigurations : 0x01 (1 Configuration) Data (HexDump) : 12 01 00 02 00 00 00 40 16 04 12 50 00 00 01 02 .......@...P.... 03 01 .. ------------------ Configuration Descriptor ------------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x02 (Configuration Descriptor) wTotalLength : 0x0065 (101 bytes) bNumInterfaces : 0x02 (2 Interfaces) bConfigurationValue : 0x01 (Configuration 1) iConfiguration : 0x00 (No String Descriptor) bmAttributes : 0x00 D7: Reserved, set 1 : 0x00 *!*ERROR Bit7 must be 1 D6: Self Powered : 0x00 (no) D5: Remote Wakeup : 0x00 (no) D4..0: Reserved, set 0 : 0x00 MaxPower : 0x32 (100 mA) Data (HexDump) : 09 02 65 00 02 01 00 00 32 09 04 00 00 00 01 01 ..e.....2....... 00 00 09 24 01 00 01 09 00 01 01 09 04 01 00 02 ...$............ 01 03 00 00 07 24 01 00 01 41 00 06 24 02 01 01 .....$...A..$... 00 06 24 02 02 02 00 09 24 03 01 03 01 02 01 00 ..$.....$....... 09 24 03 02 04 01 01 01 00 09 05 02 02 40 00 00 .$...........@.. 00 00 05 25 01 01 01 09 05 81 02 40 00 00 00 00 ...%.......@.... 05 25 01 01 03 .%... ---------------- Interface Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x04 (Interface Descriptor) bInterfaceNumber : 0x00 bAlternateSetting : 0x00 bNumEndpoints : 0x00 (Default Control Pipe only) bInterfaceClass : 0x01 (Audio) bInterfaceSubClass : 0x01 (Audio Control) bInterfaceProtocol : 0x00 iInterface : 0x00 (No String Descriptor) Data (HexDump) : 09 04 00 00 00 01 01 00 00 ......... ------ Audio Control Interface Header Descriptor ------ bLength : 0x09 (9 bytes) bDescriptorType : 0x24 (Audio Interface Descriptor) bDescriptorSubtype : 0x01 (Header) bcdADC : 0x0100 wTotalLength : 0x0009 (9 bytes) bInCollection : 0x01 baInterfaceNr[1] : 0x01 Data (HexDump) : 09 24 01 00 01 09 00 01 01 .$....... ---------------- Interface Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x04 (Interface Descriptor) bInterfaceNumber : 0x01 bAlternateSetting : 0x00 bNumEndpoints : 0x02 (2 Endpoints) bInterfaceClass : 0x01 (Audio) bInterfaceSubClass : 0x03 (MIDI Streaming) bInterfaceProtocol : 0x00 iInterface : 0x00 (No String Descriptor) Data (HexDump) : 09 04 01 00 02 01 03 00 00 ......... - MIDI Adapter Class specific MS Interface Descriptor - bLength : 0x07 (7 bytes) bDescriptorType : 0x24 (Class Specific Interface Descriptor) bDescriptorSubtype : 0x01 (MS Header) bcdADC : 0x0100 wTotalLength : 0x0041 (65 bytes) Data (HexDump) : 07 24 01 00 01 41 00 06 24 02 01 01 00 06 24 02 .$...A..$.....$. 02 02 00 09 24 03 01 03 01 02 01 00 09 24 03 02 ....$........$.. 04 01 01 01 00 09 05 02 02 40 00 00 00 00 05 25 .........@.....% 01 01 01 09 05 81 02 40 00 00 00 00 05 25 01 01 .......@.....%.. 03 . ---------------- MIDI IN Jack Descriptor -------------- bLength : 0x06 (6 bytes) bDescriptorType : 0x24 (Class Specific Interface Descriptor) bDescriptorSubtype : 0x02 (MIDI_IN_JACK) bJackType : 0x01 (Embedded) bJackID : 0x01 (ID=1) iJack : 0x00 (No String Descriptor) Data (HexDump) : 06 24 02 01 01 00 .$.... ---------------- MIDI IN Jack Descriptor -------------- bLength : 0x06 (6 bytes) bDescriptorType : 0x24 (Class Specific Interface Descriptor) bDescriptorSubtype : 0x02 (MIDI_IN_JACK) bJackType : 0x02 (External) bJackID : 0x02 (ID=2) iJack : 0x00 (No String Descriptor) Data (HexDump) : 06 24 02 02 02 00 .$.... --------------- MIDI OUT Jack Descriptor -------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x24 (Class Specific Interface Descriptor) bDescriptorSubtype : 0x03 (MIDI_OUT_JACK) bJackType : 0x01 (Embedded) bJackID : 0x03 (ID=3) bNrInputPins : 0x01 (1 pin) baSourceID(1) : 0x02 (input pin connected to entity ID=2) baSourcePin(1) : 0x01 (connected to input pin number 1 iJack : 0x00 (No String Descriptor) Data (HexDump) : 09 24 03 01 03 01 02 01 00 .$....... --------------- MIDI OUT Jack Descriptor -------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x24 (Class Specific Interface Descriptor) bDescriptorSubtype : 0x03 (MIDI_OUT_JACK) bJackType : 0x02 (External) bJackID : 0x04 (ID=4) bNrInputPins : 0x01 (1 pin) baSourceID(1) : 0x01 (input pin connected to entity ID=1) baSourcePin(1) : 0x01 (connected to input pin number 1 iJack : 0x00 (No String Descriptor) Data (HexDump) : 09 24 03 02 04 01 01 01 00 .$....... ----------------- Endpoint Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x02 (Direction=OUT EndpointID=2) bmAttributes : 0x02 (TransferType=Bulk) wMaxPacketSize : 0x0040 (64 bytes) bInterval : 0x00 (0 ms) bRefresh : 0x00 bSynchAddress : 0x00 Data (HexDump) : 09 05 02 02 40 00 00 00 00 ....@.... --- Class-Specific MS Bulk Data Endpoint Descriptor --- bLength : 0x05 (5 bytes) bDescriptorType : 0x25 (Audio Endpoint Descriptor) bDescriptorSubtype : 0x01 (MS General) bNumEmbMIDIJack : 0x01 (1 embedded MIDI jack) baAssocJackID(1) : 0x01 (Jack(1) ID=1) Data (HexDump) : 05 25 01 01 01 .%... ----------------- Endpoint Descriptor ----------------- bLength : 0x09 (9 bytes) bDescriptorType : 0x05 (Endpoint Descriptor) bEndpointAddress : 0x81 (Direction=IN EndpointID=1) bmAttributes : 0x02 (TransferType=Bulk) wMaxPacketSize : 0x0040 (64 bytes) bInterval : 0x00 (0 ms) bRefresh : 0x00 bSynchAddress : 0x00 Data (HexDump) : 09 05 81 02 40 00 00 00 00 ....@.... --- Class-Specific MS Bulk Data Endpoint Descriptor --- bLength : 0x05 (5 bytes) bDescriptorType : 0x25 (Audio Endpoint Descriptor) bDescriptorSubtype : 0x01 (MS General) bNumEmbMIDIJack : 0x01 (1 embedded MIDI jack) baAssocJackID(1) : 0x03 (Jack(1) ID=3) Data (HexDump) : 05 25 01 01 03 .%... ----------------- Device Qualifier Descriptor ----------------- Error : ERROR_GEN_FAILURE -------------------- String Descriptors ------------------- ------ String Descriptor 0 ------ bLength : 0x04 (4 bytes) bDescriptorType : 0x03 (String Descriptor) Language ID[0] : 0x0409 (English - United States) Data (HexDump) : 04 03 09 04 .... ------ String Descriptor 1 ------ bLength : 0x10 (16 bytes) bDescriptorType : 0x03 (String Descriptor) Language 0x0409 : "Nuvoton" Data (HexDump) : 10 03 4E 00 75 00 76 00 6F 00 74 00 6F 00 6E 00 ..N.u.v.o.t.o.n. ------ String Descriptor 2 ------ bLength : 0x20 (32 bytes) bDescriptorType : 0x03 (String Descriptor) Language 0x0409 : "USB MIDI STM32 " *!*CAUTION trailing space character Data (HexDump) : 20 03 55 00 53 00 42 00 20 00 4D 00 49 00 44 00 .U.S.B. .M.I.D. 49 00 20 00 53 00 54 00 4D 00 33 00 32 00 20 00 I. .S.T.M.3.2. . ------ String Descriptor 3 ------ bLength : 0x1A (26 bytes) bDescriptorType : 0x03 (String Descriptor) Language 0x0409 : "NT2020101400" Data (HexDump) : 1A 03 4E 00 54 00 32 00 30 00 32 00 30 00 31 00 ..N.T.2.0.2.0.1. 30 00 31 00 34 00 30 00 30 00 0.1.4.0.0.
Ich habe in UsbTreeView gesehen, dass das Bit nicht gesetzt ist, wird dort als Fehler ausgegeben. Habe dann das Bit wieder gesetzt, neu compiliert, und plötzlich sieht Ableton Live den Midi Port. Muss ich das verstehen? Erdstrahlung oder was? Ich hatte vorher zig-fach probiert, Ableton x-mal neu gestartet, etc. und der Midi Port wurde nicht erkannt. Übrigens auch in Studio One nicht (das hatte ich getestet, um GANZ sicher zu gehen). Ich verstehe es nicht, aber es ist ja immer schön, wenn es dann läuft.
Die Deskriptoren sind falsch. Die Reihenfolge ist falsch. wTotalLength im AC header muss die Länge der PinDesc. angeben. Die PinDesc müssen direkt nach dem AC Header folgen. Zusätzlich sollten die Bulk EPs 7Bytes lang sein. Mit den 9 Bytes wird's vermutlich aber auch gehen. Schau dir das Beispiel in der UsbMidi Spec an.
Thorsten S. schrieb: > Muss ich das verstehen? Das liegt vermutlich daran weil bei dir alle Versionen die gleichen IDs und SNs haben. Windows merkt sich solche Sachen. Sobald du die entsprechenden Einträge aus der Registry schmeißt, oder die pnf Dateien löschst wird das Problem wieder kommen. Ich werde morgen Mal einen Satz Deskriptoren posten die ich verwende.
man sollte späd abends keine Behauptungen mehr aufstellen die Deskriptoren sind vollkommen korrekt. Der Fehler liegt vermutlich an der Seriennummer. Setze die Id für die Seriennummer im Device Desc. mal auf 0. UsbAudio.sys braucht keine SN.
Im Augenblick läuft es, gerade noch mal getestet. Ich lasse das jetzt erst mal so.
Thomas Z. schrieb: > man sollte späd abends keine Behauptungen mehr aufstellen die > Deskriptoren sind vollkommen korrekt. Vielen Dank, das freut mich dass ich da erst mal keinen Fehler drin habe. Das mit der Seriennummer ist interessant. Vielleicht hilft das ja. Ich habe bei mir nur mit Sonar getestet und hatte keine Probleme.
Ich kann mir vorstellen, dass bei gleicher VID/PID und Sernr Windows ins Schleudern kam, dass aus vier Ports plötzlich ein Port wurde.
im Windows Gerätemanager kann man 'ausgeblendete Geräte' anzeigen lassen. Vielleicht hilft es den alten Geist zu löschen damit Windows keine gepufferten Daten verwendet.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.