Brainstorm Ich möchte meinen eigenen MIDI-Sequencer programmieren

Tatsächlich grüble ich da schon einige Zeit, denn problematisch ist aus meiner Sicht die Datenübertagung z.B. beim Editieren und gleichzeitigen Abspielen des Editierten sowie dem visuellen Feedback

Kommt halt drauf an, was das alles am Ende können (und kosten) soll. Einen Arduino könnte man zum Beispiel per bidirektionalem UART direkt an einen Raspberry hängen, mit letzterem man dann alle Fancy-Schmanzy-Sachen (python, javascript, Bluetooth, RJ-45, WLAN, USB, GPIO) machen kann. Zusätzlich hängen die üblichen Arduino- und Raspberry-Baudraten dann auch noch die poplige Übertragungsgeschwindigkeit von MIDI gnadenlos ab.

Die ganze höhere Konfigurationslogik (und Speichermöglichkeiten von Patterns, Tracks, Rhytms etc.) kommt auf den Raspberry und die Lowlevel-Logik kommt auf den Arduino. Ferner könnte man eben auch noch direkt einen DIN-MIDI-IO-Port über die verbleibenden UART-Ports auf dem Arduino realisieren. Die erforderlichen Bauteile dafür sind ja auch überschaubar und vielerorts gut im Netz dokumentiert.
 
Zuletzt bearbeitet:
> Und zweitens etwas, auf dem das UI läuft und Sysex-Messages an den Sequencer abschießt.
Statt SysEx würde ich NRPN-Messages verwenden, die sind deutlich einfacher gehalten und können (fast) das Gleiche. Falls 128 NRPN-Messages mit je 128 Werten wider Erwarten nicht ausreichen sollten, kann man zusätzlich freie RPN nehmen.
 
Statt SysEx würde ich NRPN-Messages verwenden, die sind deutlich einfacher gehalten und können (fast) das Gleiche. Falls 128 NRPN-Messages mit je 128 Werten wider Erwarten nicht ausreichen sollten, kann man zusätzlich freie RPN nehmen.

Am Ende kommt es auf das Konzept dahinter an.

Wenn man den langsamen und aufgeblähten Datenstrom per MIDI ganz umgehen kann, ist das sicher nicht verkehrt. Dann könnte man nämlich per serieller UART-Verbindung mit nem eigenen Protokoll eben den zeitlichen Flaschenhals durch ne klassische Buffer-Lösung deutlich entschärfen und quasi direkt vom Raspberry in die Arduino-Arrays reinschreiben, die dann der MIDI-Taktrate entsprechend "geleert" werden.
 
Ich kenne mich mit Sequencing nicht aus, aber wenn die Zeit hier schon in Tics "gequantelt" ist, dann würde ich auch versuchen, alles Tic-genau zu realisieren.
Weil bei fortlaufender Addition von floating-point-Zahlen immer mehr Fehler auflaufen, würde ich stattdessen auf einen ausreichend breiten Integer-Typ gehen, weil bei Integer-Addition dieser Effekt nicht eintritt.
Sofern die Breite des Typs nicht ausreicht, würde ich die Gesamt-Sequenz in Segmente (Blöcke gleicher zeitlicher Länge bzw. mit derselben Zahl von Tics) unterteilen, innerhalb derer der schmale Integer-Typ für eine relative Adressierung der Zeit ausreicht.
D.h. es gäbe dann eine relative Zeit innerhalb des Segments und auf Segment-Ebene die nächst-gröbere Zeitunterteilung.
Z.B. 32 Bit für Events innerhalb eines Segments und 32 Bit für den Segment-Index (oder alternativ 64 Bit für den Segment-Offset), damit müsste man Speicherplatz-sparend schon eine ganze Menge Events organisieren können, weil man den Segment-Offset nur einmal pro Segment speichert oder weil er sich sowieso aus dem Segment-Index rechnerisch ergibt (Offset = Tics pro Segment * Segment-Index).
Die fortlaufende Darstellung in der Eventliste oder Piano-Roll oder anderen Sequenzansicht für die Benutzer ist dann eine View auf die Events in den Segmenten, bei der man die internen Blockgrenzen natürlich nicht mehr sieht und bei der der Segment-Offset und die relative Zeit eines Events innerhalb des Segments zu einer Gesamt-Zeitangabe zusammengefasst sind.
 
Zuletzt bearbeitet:
Nicht was MIDI-Sequencer angeht, wie mir scheint.
Leider richtig, denn vieles von dem, was es mal gab, wird nicht mehr in aktuelle Geräte eingebaut: Echter Songmodus, Fußschalterbedienung, Metronomausgang, Drummaps, Tempotrack, Signaturtrack - nur um mal ein paar Beispiele zu nennen.

Tatsächlich grüble ich da schon einige Zeit, denn problematisch ist aus meiner Sicht die Datenübertagung z.B. beim Editieren und gleichzeitigen Abspielen des Editierten sowie dem visuellen Feedback
Wirklch Konsequent geht das eigentlich nur mit getrennten Prozessoren für Panel und Engine. Die alten MPCs bis zur 4000 haben das so gehandhabt (NEC 78xx fürs Panel, NEC V-50 als Hauptprozessor und noch ein Custom-DSP für die Soundengine), die daziwschen (500/1000/2500) waren Einprozessormaschinen (SuperH mit DSP, sobald man die eh miesen internen Effekte benutzt ist das Timing im Eimer), die Aktuellen trennen wieder, da haben Panel, Engine und Audiointerface getrennte MCUs.
 
Hab vor Jahren keinen Sequenzer gebaut, sondern nur ein proof of concept. Letztlich nur Midi-IO und Abspielen textbasierter Notenlisten, die Zeitpunkt und Note enthielten, etwa so:

Code:
1 1 1 C2
1 3 1 D#

Zu den Fallstricken, wenn's komplexer wird, kann ich also nichts sagen. Einige Anmerkungen habe ich schon.

Der einfachste Fall - ist zu einfach​


Wenn du nur einen nackten Midi-Event-Recorder und -Player haben willst, wäre es ausreichend, ein Integer-Array mit soviel Elementen wie man Ticks haben will zu definieren. Mit 96 PPQ und einer Stunde maximaler Länge wären das 44 MB. Ein ziemliche Verschwendung von Speicherplatz, dafür spart man sich einiges Nachdenken beim Programmieren. ;-) Welche Midi-Message beim Tick gesendet werden soll, bringst du im Integer unter. Der läßt sich ja als Folge von Bytes interpretieren.

Das Konzept scheitert erstens an deiner Forderung nach 480 ppq. Zweitens daran, daß die Events ohne jeden logischen Zusammenhang gespeichert werden. Note-On und Note-Off hast du ja schon angesprochen. Was auch immer dein Editor erlaubt, Noten in einer Piano-Roll malen oder Schnippsel von Notenfolgen im Arrange hinundherzuschieben, braucht eine Entsprechung als Klasse. Deine Datenstrukturen müssen sich daran orientieren, was dein Editor können soll, nicht an einer schnöden Midi-Eventlist.

Wie ich es machen würde - immer noch zu einfach.​


1) Klasse TimelineEvents. Eine Arrayliste mit Referenzen auf einen Timestamp und eine weitere Liste. Alternativ ein Dictionary, dessen Key der Timestamp ist und im Wert die Referenz auf eine weitere Liste enthält.

2) Klasse TickEvents, die eine Referenz auf alle Midiereignisse enthält, die zu einem bestimmten Tick passieren. (Midi gibt nur ein Event pro Tick her. In Editoren kann man Midievents übereinanderlegen.)

3) Klasse MidiEvent. Enthält die Mididaten. Notennummer, Velocity, Kanal. Ob das Eventobjekt einen Timestamp haben sollte oder nicht, überblicke ich gerade nicht. Würde aber definitiv eine eigene Klasse Timestamp haben wollen mit einer Methoden wie Verschieben.

Verdrahtung:

Du hast einen High-Resolution-Timer, der in Abständen der Tick-Zeit ein OnTimerEvent auslöst. Damit weißt du, "ich bin jetzt bei Tick #14326". Du schaust in die TimelineEvents, ob zu diesem Tick Ereignisse angesagt sind. Falls ja, gehst du die Liste der Ereignisse durch, holst dir jedes MidiEvent aus der TickEvent-Liste und feuerst es ab. Natürlich nicht alle auf einmal, das kann Midi ja nicht. Wo hier die Grenzen liegen, weiß ich nicht.

Oder jede Spur hat eine eigene Datenstruktur, die alle beim Abspielen abgesucht werden müssen. Machbar bei 16 Spuren, aber wie ist es mit den Verzögerungen z.B. bei 64 Spuren, die jede noch Echtzeitmodifikation haben wie Delay, Transposition, Dynamik, Quantisierung?
Alles erstmal nebenläufig in einen Abspielbuffer legen, und Abspielen beginnt, sobald er gut genug gefüllt ist?
Das wären auch meine Bedenken.

Ich würde jedem logischen Element eine Methode mitgeben, die mir den tatsächlichen Timestamp und den tatsächlichen Notenwert nach Transponierung etc. gibt. Sollte es zu langsam sein, das in Echtzeit zu berechnen, müßte man das puffern, so daß es zur "logischen Darstellung" = C auf das dritte Viertel in Takt 25 mit Schnippselparameter transponiert um +3, Velocity +10 und Delay -1/8 eine "resultierende Darstellung" gibt.

Irgend sowas muß es geben, nutzt ja nichts, wenn man das Schippsel, das auf dem zweiten Takt liegt fragt: "Und? Hast du was zu spielen?" Und es antwortet: "Ja, hättest du schon vor einem Achtel ausspielen sollen."


Sonstiges​


- Vergiß Linked-Lists. Die lernt man vielleicht im Studium, in der Praxis nimmt man die nur für extreme Spezialfälle. Nimm Arraylisten. Die sind unter der Haube Arrays, haben aber Methoden wie Einfügen und Heraustrennen, so daß man sie bequem bedienen kann und das nicht selbst bauen muß. Eine vernünftige Implementation von Arraylisten hat eine Grow-Methode. Wenn du ein neues Element anforderst, wird das Array nicht mühsam um ein Element vergrößert, es ist bereits vorher auf bestimmte Größe gewachsen und muß erst dann wieder wachsen, wenn diese Größe überschritten wird. Oder nimm Dictionaries. Damit hast O(1)-Zugriff auf die Elemente, allerdings ohne Sortierung der Elemente nach Key, falls man das braucht.

Speicherlücken durch Ein- und Austragen von Element-Objekt sind - im Normalfall - nicht relevant. Jeder gute Speichermanager alloziert Speicherblöcke auf Vorrat und weist neuen Speicheranforderungen alten, bereits freigegebenem Speicher zu ohne über teure Speicheranforderungen ans Betriebssystem gehen zu müssen.


- Vergiß Floats für die Timeline. Deine Programmiersprache wird eine Funktion namens GetTickCount haben. Die liefert dir einen Integer, wieviele Ticks seit Start des Computers oder des Counters vergangen sind. Wenn der TickCount keine Floats braucht, brauchst du sie auch nicht. Außer natürlich zur Anzeige, daß TickCount X soundsovielen Sekundenbruchteilen entspricht.
 
Was mir gerade noch einfällt, ich habe den Kilpatrik Carbon Sequenzer (falls jemand den mir abkaufen möchte, einfach per PM melden ;-)), und für den ist die Firmware Open Source und hier zu finden: https://github.com/kilpatrickaudio/CARBON. Und auch halbwegs lesbar, immerhin habe ich dafür ein PR mit neuer Funktionalität hinbekommen ;-) Vom Sequenzerkonzept ist der jetzt sehr weit von dem entfernt, was Dir vorschwebt, aber vielleicht gibt es ja trotzdem Teile, wie z.B. für die MIDI-Clock, die für Dich interessant sein können, vorrausgesetzt, dass Du auch planst Deinen Code unter der GPL zu veröffentlichen. Carbon hat übrigens ein Feature, welches ich sehr mochte, nämlich dass jeder MIDI-Port die ppq für die MIDI-Clock eingestellt werden kann, das habe ich dann auch gleich mal in meinem Sequenzer übernommen.
 
Nicht für Anwender, nur für Programmierer
Da habe ich mich leider missverständlich ausgedrückt. Die Frage sollte ehr lauten: Wie sehr willst Du den Umfang der Funktionalität erweitern wenn die Hardware bereits im Umlauf ist?

Meine Erfahrung hat gezeigt: Ein zugängliches Bedienkonzept braucht dedizierte Buttons und flache Menüstrukturen. Diese werden jedoch schnell zum Bottleneck wenn die Software wächst. Die Kunst ist also den Sweetspot zu finden zwischen Zugänglichkeit und Flexibilität.

Sehr gut gelöst im Synthesizer Markt haben das IMHO der Hydrasynth und Prophet 12 Desktop. Bei Sequenzern ist es ein schwieriges Thema und ich bin da nicht ganz unbiased. Aber alles was für melodischen Content in 2024 keine Pad Matrix hat geht bei mir direkt nach /dev/null :D
 
Wie sehr willst Du den Umfang der Funktionalität erweitern wenn die Hardware bereits im Umlauf ist?
Das wird nicht ausbleiben, aber so wenig wie möglich.

dedizierte Buttons und flache Menüstrukturen
Ja, ich denke, das ist die Kunst. Ich möchte mich nicht durch Menüs oder sogar Untermenüs suchen und an massenweise Features vorbeistolpern, deren Funktion hochspeziell ist.

Pad Matrix verbinde ich mit Live-Anwendungen. Im Studio käme ich mit 8 oder 16 Spuren nicht aus und eine Matrix mit 24 oder 32 Knöpfen dürfte sehr unübersichtlich werden. Ich sehe tatsächlich die mehrkanalige senkrechte Piano-Roll als Zentrum, bin mir aber nicht sicher
 
wenn die Zeit hier schon in Tics "gequantelt" ist, dann würde ich auch versuchen, alles Tic-genau zu realisieren.
Weil bei fortlaufender Addition von floating-point-Zahlen immer mehr Fehler auflaufen, würde ich stattdessen auf einen ausreichend breiten Integer-Typ gehen, weil bei Integer-Addition dieser Effekt nicht eintritt.
- Vergiß Floats für die Timeline. Deine Programmiersprache wird eine Funktion namens GetTickCount haben. Die liefert dir einen Integer, wieviele Ticks seit Start des Computers oder des Counters vergangen sind. Wenn der TickCount keine Floats braucht, brauchst du sie auch nicht. Außer natürlich zur Anzeige, daß TickCount X soundsovielen Sekundenbruchteilen entspricht.
Man hat nicht nur die Ticks, sondern auch das Tempo (BPM), das man unabhängig von den Ticks verändern kann. Es ist also per se immer eine Fließkomma-Rechnung mit Vergleich, die man natürlich auch als Integer-Rechnung durchführen kann, aber nativ ist Zeit eher kontinuierlich.

Aber natürlich verwendet das "Standard MIDI file format" nur Ganzzahlen. Eine Umrechnung ist also in jedem Fall notwendig.


Sofern die Breite des Typs nicht ausreicht
Double oder 32bit Integer reichen garantiert. Float eigentlich auch, wenn ok ist, dass ab 28 Min. Songlänge die Auflösung von 5000ppq auf 2500ppq zurückfällt und ab 56 Min. auf 1250ppq (und bedenkt, dass aktuelle Sequencer standardmäßig mit 480ppq arbeiten). Mir geht es um das Vermeiden von Rundungsfehlern: Eine SMF-Datei, die geladen, editiert, zurückeditiert und wieder gespeichert wird, soll im Timing verlässlich gar nicht verändert werden.
 
Zuletzt bearbeitet:
Ich möchte vorausschicken, daß ich keine Ahnung vom Sequenzerbau habe - mein proof of concept gilt da nicht. Alles folgende sind ganz allgemeine Überlegungen. Natürlich kann man etwas auf ganz unterschiedliche Arten implementieren.

Man hat nicht nur die Ticks, sondern auch das Tempo (BPM), das man unabhängig von den Ticks verändern kann.
Dann verstehe ich dein Konzept noch nicht ganz. Woher weiß dein Sequenzer "jetzt muß ich was tun"?

Ein Tick kommt vom Timer in so und soviel Millisekunden. Bei höherem Tempo kommen die Ticks halt schneller.

Denk an externen Sync, falls du das planst. Dann mußt du warten, bis ein Tick kommt, egal was die Uhr sagt. Deshalb ist der Tick das Wesentliche, egal ob er von extern oder von intern kommt.


Es ist also per se immer eine Fließkomma-Rechnung mit Vergleich, die man natürlich auch als Integer-Rechnung durchführen kann, aber nativ ist Zeit eher kontinuierlich.
Nativ im Universum, ja, aber nicht im Rechner. ;-)


Dazu noch ein paar Worte:
Ich kenne mich mit Sequencing nicht aus, aber wenn die Zeit hier schon in Tics "gequantelt" ist, dann würde ich auch versuchen, alles Tic-genau zu realisieren.
Weil bei fortlaufender Addition von floating-point-Zahlen immer mehr Fehler auflaufen, würde ich stattdessen auf einen ausreichend breiten Integer-Typ gehen, weil bei Integer-Addition dieser Effekt nicht eintritt.
Genau. Ich versuche mal zu sagen, was das praktisch bedeutet:


Du hast ein Event auf 12,123456789 s liegen.
Deine Uhr sagt, du bist bei 12,12345676 s.
Spielst du das Event aus?

Das grundlegendere Problem ist, daß du Floats nicht so einfach vergleichen kannst. Das hier geht nicht:

Code:
if SomeFloatTime = SomeOtherFloatTime then
weil manche Dezimalzahlen nicht verlustfrei ins Binärsystem und zurück übertragbar sind.

Es muß so sein:

Code:
if abs(SomeFloatTime - SomeOtherFloatTime) < delta then //oder besser delta/2 ?

Das ist letztlich ein Float mit einer fixen Zahl an Nachkommastellen. Da kann ich gleich auf das Float verzichten und einen Integer nehmen, der in deltas = ppq-ticks zählt. Ich interpretiere den Integer quasi als die Anzahl der Millisekunden, die sich aus den ppqs ergeben.

Das ist das Integer-Array, das Turing empfohlen hat.


Mir geht es um das Vermeiden von Rundungsfehlern: Eine SMF-Datei, die geladen, editiert, zurückeditiert und wieder gespeichert wird, soll im Timing verlässlich gar nicht verändert werden.
Ja, eben. Wenn Ticks Ticks bleiben, stellt sich das Problem gar nicht. Das entsteht erst durch's Konvertieren von Ticks in Float-Zeit und zurück. Wobei ich mir ziemlich sicher bin, daß das zuverlässig geht, mit einem passend gerundetem 64-bit Float.
 
Kleiner Hinweis zu den ticks: Immer prüfen, dass der RDTSC ein invariant timer ist, der unabhängig vom Prozessortakt läuft, denn letzterer wird u.a. durch SpeedStep und andere Energiemanagement-Funktionen variiert.

Kann so aus dem Register ausgelesen werden:
1711652364326.png

Am besten den TSC direkt via Assembler auslesen:

Code:
/**
 * get_ticks
 *
 * runs assembly code to get number of cpu ticks
 *
 * @return    number of cpu ticks since cpu started
*/



unsigned long long getRDTSC()
{
    unsigned long long    rdtsc = 0;
    unsigned long        min = 0;
    unsigned long        may = 0;

    asm volatile(    "cpuid \n"
                    "rdtsc"
                :    "=a"(min),
                    "=d"(may)
                :    "a" (0)
                :    "%ebx", "%ecx"
    );

    return ((((unsigned long long) may) << 32) | ((unsigned long long) min));
}

Alternativ einen HPET nehmen, wenn verfügbar.
 
Zuletzt bearbeitet von einem Moderator:
Wobei ich mir ziemlich sicher bin, daß das zuverlässig geht, mit einem passend gerundetem 64-bit Float.
Ja, ich mir auch:
Double oder 32bit Integer reichen garantiert.
Die Frage war, ob 32-Bit-float reicht und das müsste es theoretisch. Ich habe das noch nicht zuende gedacht.
Du hast ein Event auf 12,123456789 s liegen.
Deine Uhr sagt, du bist bei 12,12345676 s.
Spielst du das Event aus?
Nein, denn meine Uhr ist noch nicht beim Event angekommen. Bei den Zeitstempeln gibt es kein Vergleichsproblem, wenn man sich z.B. auf größergleich einigt: Ein Event wird dann gespielt, wenn die Uhr gerade im Begriff ist, an ihm vorüberzuwandern (d.h. es wird auch gespielt, wenn die Uhr schon 1ms drüber ist, es aber noch nicht gespielt wurde. Aus dem Mittelwert all der Überschreitungen ließe sich die Härte des timings berechnen).

Dann mußt du warten, bis ein Tick kommt, egal was die Uhr sagt. Deshalb ist der Tick das Wesentliche, egal ob er von extern oder von intern kommt.
Tja, die Zeit ist so ein merkwürdiges Ding ;-) Stell Dir vor, Du musst die Geschwindigkeit Deines Fahrrads aufgrund eines Impulsgebers am Vorderrad messen. Der Umfang des Reifens sei 2m. Innerhalb der 2m kannst Du aber die Geschwindigkeit deutlich verändern, wenn Du gerade beschleunigst (accelerando) oder abbremst (ritardando). Nun könnte man bei jeder Umdrehung die Geschwindigkeitsanzeige einmal updaten, fährt man langsam, passieren diese Updates selten, fährt man schnell, passieren sie sehr häufig...

U.s.w. da kann man sich ganz schön verfransen, zumal MIDI-Clock nicht 100% jitterfrei sein kann, da sie sich mit anderen Events den seriellen Bus teilen muss.

Aber das ist alles gar nicht der Punkt. Eher:
Die Zeit ist knapp vor 28 Minuten, nämlich 1677,7210s. Wie oft kann ich 0,0001s hinzuaddieren bis es zu Problemen kommt? Und zu welchen Problemen?

Danke für den Hinweis! Wikipedia:
"The Time Stamp Counter (TSC) is a 64-bit register present on all x86 processors since the Pentium."
"On Windows platforms, Microsoft strongly discourages using the TSC for high-resolution timing"
"On Linux systems, a program can get similar function by reading the value of CLOCK_MONOTONIC_RAW clock using the clock_gettime function."

Unter 64bit-linux ist bereits ein int 32 bit und ein long 64 bit.

Wir verfransen uns gerade etwas zu sehr in den Eingeweiden der Bits und Bytes, die oft CPU-abhängig sind.
 
Zuletzt bearbeitet:
Wir verfransen uns gerade etwas zu sehr in den Eingeweiden der Bits und Bytes, die oft CPU-abhängig sind.

Ohne stabile Zeitbasis kannst du alles andere noch so perfekt lösen und trotzdem failen.
Ich würde das ganze Projekt von der Zeitbasis her denken - was nimmst du als Quelle, wie bildest du sie ab, welche Datenstruktur ist für die Zeitbasis am besten zu prozessieren, .... die Zeitbasis wird immer hardware nahe sein müssen, jede Abstraktionsschicht, die du zwischen dem Timer und deiner Anwendung hast, wird dir eine Ungenauigkeit hinzufügen.
 
Ohne stabile Zeitbasis kannst du alles andere noch so perfekt lösen und trotzdem failen.
Aufnahme und Wiedergabe läuft ja schon seit einiger Zeit mit clock_gettime und CLOCK_MONOTONIC ohne RAW. Ich hatte den Jitter der Ticks mit unter ±0,03ms ermittelt, womit ich gut leben kann, wenn man bedenkt, dass
1) ein NoteOn im Running Status mindestens 0,64ms zum Versenden braucht und
2) alle aktuellen Sequencer mit 480ppq arbeiten, was 1,04ms bei 120BPM entspricht.
 
Zuletzt bearbeitet:
Die Zeit ist knapp vor 28 Minuten, nämlich 1677,7210s. Wie oft kann ich 0,0001s hinzuaddieren bis es zu Problemen kommt? Und zu welchen Problemen?
Wer es wissen will:
Es ist doch anders als ich erwartet habe:
Bis ein 32-Bit-float im Vorkommateil 2^9Bit=512 (=8Min32Sec) erreicht, sind für den Nachkommateil 2^15Bit verfügbar. Dezimal wird er also mit 1/(2^15)=0.0000305 aufgelöst. Entsprechend wird bei einer Addition von 0,0001 tatsächlich 0.0000916 addiert.

Das wäre als Genauigkeit ok, aber ab 512 bis 1023 im Vorkommateil sind 10Bit nötig und es bleiben nur 14Bit für den Nachkommateil.

Die Auflösung geht also auf 1/(2^14)=0.000061 zurück und damit wird statt 0,0001 immer 0,000122 addiert, wodurch hin und wieder eine 10tel ms im Zähler übersprungen wird, was bei Song-Dauern über 17 Minuten und erst recht 34 Minuten dann richtig problematisch wird.

Also: Wenn Songs länger als 8:32 sein dürfen sollen, dann kein float! Entweder double oder 32Bit-Int.
 
Zuletzt bearbeitet:


Neueste Beiträge

News

Zurück
Oben