Vor einigen Jahren habe ich einen Wecker auf dem ATmega8-Mikrocontroller erstellt, auf dem ich einen einfachen Melodie-Synthesizer mit einem einzigen Ton (mit einer Stimme) implementiert habe. Es gibt viele Artikel im Internet für Anfänger zu diesem Thema. In der Regel wird ein 16-Bit-Timer verwendet, um die Frequenz (Noten) zu erzeugen, die auf bestimmte Weise konfiguriert ist, und auf Hardwareebene gezwungen, ein Signal in Form eines Mäanders an einem bestimmten Pin des MC auszugeben. Der zweite (8-Bit-) Timer wird verwendet, um die Dauer einer Note oder Pause zu implementieren. Noten nach bekannten Formeln werden mit Frequenzen verglichen und sie werden wiederum mit bestimmten 16-Bit-Zahlen verglichen, die umgekehrt proportional zu den Frequenzen sind, die die Timer-Zählperioden angeben.
In meinem Entwurf habe ich drei Melodien bereitgestellt, die in derselben Tonart und Tonleiter geschrieben wurden. Daher musste ich eine begrenzte und bestimmte Anzahl von Noten verwenden, was das Modellieren erleichterte. Außerdem wurden alle drei Stücke im gleichen Tempo gespielt. Der Notencode und sein Dauercode passen leicht in ein Byte. Der einzige Nachteil dieses Modells war die mangelnde Vielseitigkeit, die Fähigkeit, die Melodie schnell zu bearbeiten, zu ersetzen oder zu ergänzen. Um eine Melodie aufzunehmen, skizzierte ich sie zuerst in einem Musikeditor auf einem Computer, kopierte dann die Noten und ihre Dauer, deren Nummerierung ich im Voraus festgelegt hatte, und bildete dann die resultierenden Bytes. Ich habe die letzten Operationen mit dem Excel-Programm durchgeführt.
In Zukunft wollte ich den oben genannten Nachteil beseitigen, dem Design eine gewisse Universalität verraten und die Zeit für die Implementierung der Melodie verkürzen. Es gab eine solche Idee, dass das MK-Programm die Bytes eines der berühmten Musikformate las. Am beliebtesten und gebräuchlichsten ist das MIDI-Format. Im wahrsten Sinne des Wortes ist dies weniger ein Format als eine ganze „Wissenschaft“, über die im Internet gelesen werden kann. Die MIDI-Spezifikation definiert das Protokoll für die Übertragung von Echtzeitnachrichten über die entsprechende physikalische Schnittstelle und beschreibt, wie die MIDI-Dateien angeordnet sind, in denen diese Nachrichten gespeichert werden können. Das Midi-Format ist musikorientiert und findet daher Anwendung auf dem entsprechenden Gebiet. Dies ist eine synchrone Steuerung von Tonausrüstung, Farbmusik, Musiksynthesizern und Robotern usw. Im häuslichen Bereich wurde das Midi-Format in der Ära des Beginns der Entwicklung von Mobiltelefonen angetroffen. In diesem Fall werden Nachrichten über die Aufnahme oder Deaktivierung einer bestimmten Note, Informationen über ein Musikinstrument, die Lautstärke der klingenden Noten usw. in der MIDI-Datei aufgezeichnet. Das Mobiltelefon, das eine solche Datei abspielt, enthält einen Synthesizer, der die MIDI-Nachrichten in dieser Datei in Echtzeit interpretiert und die Melodie abspielt. In den frühesten Stadien konnten Telefone nur Einzeltonmelodien abspielen. Im Laufe der Zeit trat die sogenannte Polyphonie auf.
Im Internet traf ich Artikel über die Implementierung eines polyphonen Synthesizers auf MK, der MIDI-Dateien liest. In diesem Fall wird mindestens eine vorgeformte "Wellentabelle" (eine Liste von Schallwellenformen) für jedes Musikinstrument verwendet, das im Speicher von MK gespeichert ist. In meinem speziellen Fall konzentrieren wir uns auf die Implementierung eines einfacheren Modells: eines Single-Tone-Synthesizers (Single-Voice).
Zunächst habe ich die Struktur der MIDI-Datei sorgfältig untersucht und festgestellt, dass sie zusätzlich zu den erforderlichen Informationen zu Noten zusätzliche redundante Informationen enthält. Daher wurde beschlossen, ein einfaches Programm zu schreiben, um eine MIDI-Datei in ein eigenes Format zu konvertieren. Das Programm, das mit vielen MIDI-Dateien arbeitet, konvertiert Formate nicht nur, sondern organisiert sie auch auf eine bestimmte Weise. Im Voraus habe ich beschlossen, die Speicherung vieler Musikstücke im ROM-Speicher (EEPROM 24XX512) zu organisieren. Zur Vereinfachung der Visualisierung im HEX-Editor habe ich sichergestellt, dass jede Melodie am Anfang des Sektors beginnt. Im Gegensatz zu einer SD-Karte (zum Beispiel) ist das Konzept eines Sektors nicht auf das verwendete ROM anwendbar, daher drücke ich mich bedingt aus. Die Sektorgröße beträgt 512 Bytes. Und der erste Sektor des ROM ist für die Adressen der Sektoren der Anfänge jeder Melodie reserviert. Es wird angenommen, dass die Melodie mehrere Sektoren umfassen kann.
Eine vollständige Beschreibung des Midi-Dateiformats lohnt sich hier natürlich nicht. Ich werde nur auf die notwendigsten und notwendigsten Punkte eingehen. Eine Midi-Datei enthält 16 Kanäle, die in der Regel dem einen oder anderen Musikinstrument entsprechen. In unserem Fall spielt es keine Rolle, um welche Art von Instrument es sich handelt, und es wird nur ein Kanal benötigt. Der Inhalt jedes Kanals wird zusammen mit dem Header in einer Midi-Datei nach einem Prinzip erstellt, das der Organisation der Speicherung von Video- und Audiostreams in einem AVI-Container sehr ähnlich ist. Ich habe darüber früher in einem meiner Artikel geschrieben. Der Midi-Datei-Header besteht aus einigen Parametern. Ein solcher Parameter ist die Zeitauflösung. Sie wird in der Anzahl der „Ticks“ (eine Art Pixel) pro Quartal (PPQN) ausgedrückt. Ein Viertel ist eine Zeitspanne, in der eine Viertelnote gespielt wird. Je nach Tempo der Melodie kann die Dauer des Viertels unterschiedlich sein. Daher hängt die Dauer eines „Pixels“ (Abtastperiode) vom Tempo und PPQN ab. Alle Informationen zum Zeitpunkt eines Ereignisses werden mit Genauigkeit auf diese Dauer ermittelt.
Darüber hinaus enthält der Header den Typ der MIDI-Datei (Typ 0 oder Typ 1) und die Anzahl der Kanäle. Ohne auf Details einzugehen, werden wir mit Typ 1, der Anzahl der Kanäle 2, arbeiten. Eine MIDI-Datei mit einer Einzeltonmelodie enthält logischerweise einen Kanal. In der Midi-Datei „Typ 1“ gibt es jedoch neben dem Hauptkanal einen weiteren „nicht-musikalischen“ Kanal, in dem zusätzliche Informationen aufgezeichnet werden, die keine Noten enthalten. Dies sind die sogenannten Metadaten. Es besteht auch keine Notwendigkeit, auf Details einzugehen. Die einzigen Informationen, die wir dort benötigen, sind Informationen über das Tempo und in einem ungewöhnlichen Format: Mikrosekunden pro Quartal. In Zukunft wird gezeigt, wie diese Informationen zusammen mit PPQN verwendet werden, um den MK-Timer zu konfigurieren, der für das Tempo verantwortlich ist.
Im Hauptkanalblock mit Notizen interessieren uns nur Informationen über die Ereignisse beim Ein- und Ausschalten von Notizen. Ein Notenaktivierungsereignis hat zwei Parameter: Notennummer und Lautstärke. Insgesamt stehen 128 Noten und 128 Lautstärken zur Verfügung. Wir interessieren uns nur für den ersten Parameter, da es keine Rolle spielt, wie laut die Note ist: Alle Noten beim Spielen der MK-Melodie klingen mit der gleichen Lautstärke. Und natürlich sollte die Melodie keine „überspielten“ Noten enthalten, dh es sollte zu keinem Zeitpunkt mehr als eine Note gleichzeitig erklingen. Der Code des Ereignisses, bei dem die Notizen aufgenommen (eingeschaltet) werden, lautet 0x90. Der Hinweis für den Ereigniscode lautet 0x80. Zumindest der Cakewalk Pro Audio 9-Editor verwendet das Ereignis mit dem 0x80-Code jedoch nicht, wenn die Komposition in das Midi-Format exportiert wird. Stattdessen findet das 0x90-Ereignis während des gesamten musikalischen Teils statt, und die Note, bei der die Note ausgeschaltet ist, hat die Lautstärke Null. Das heißt, das Ereignis "Note ausschalten" entspricht dem Ereignis "Note mit Lautstärke Null einschalten". Vielleicht geschieht dies aus wirtschaftlichen Gründen. Gemäß der Spezifikation kann der Ereigniscode nicht neu geschrieben werden, wenn dieses Ereignis wiederholt wird. Zwischen Ereignissen werden Informationen über das Zeitintervall in einem Format variabler Länge aufgezeichnet. Dies sind die ganzzahligen Werte der oben genannten Anzahl von "Ticks". Meistens reicht ein Byte aus, um das Zeitintervall aufzuzeichnen. Wenn zwei Ereignisse nacheinander folgen, ist das Zeitintervall zwischen ihnen offensichtlich gleich Null. Dies deaktiviert beispielsweise die erste und die Aufnahme der darauf folgenden zweiten Note, wenn zwischen ihnen keine Pause (Leerzeichen) steht.
Versuchen wir, mit dem Programm „Cakewalk Pro Audio 9“ eine Notenfolge zu schreiben. Es gibt viele Redakteure, aber ich habe mich für den ersten entschieden, der mir begegnet ist.

Zuerst müssen Sie die Projekteinstellungen konfigurieren. In diesem Editor können Sie die zeitliche Auflösung (PPQN) einstellen. Ich wähle den Mindestwert gleich 48. Ein zu großer Wert ist bedeutungslos, da Sie mit großen Zahlen arbeiten müssen, die größer als 1 Byte sind. Der Mindestwert von 48 ist jedoch durchaus zufriedenstellend. In fast jeder Melodie werden keine Noten gefunden, die kürzer als 1/32 sind. Und wenn die Anzahl der „Ticks“ pro Quartal 48 beträgt, hat die Note oder Pause 1/32 eine Dauer von 48 / (32/4) = 6 „Ticks“. Das heißt, es gibt eine theoretische Möglichkeit, 1/32 Note vollständig durch 2 und sogar durch 3 zu teilen. Die verbleibenden Parameter bleiben standardmäßig im Projekteigenschaftenfenster.

Öffnen Sie als Nächstes die Eigenschaft des ersten Titels und weisen Sie ihm eine Kanalnummer gleich 1 zu. Wählen Sie nach Ihrem Geschmack einen Patch aus, der einem Musikinstrument entspricht, wenn Sie eine Melodie im Editor abspielen. Die Patch-Nummer hat natürlich keinen Einfluss auf das Endergebnis.

Das Melodietempo wird in der Anzahl der Viertel pro Minute in der Editor-Symbolleiste festgelegt. Der Standardtempowert ist 100 Schläge pro Minute.
Der Mikrocontroller verfügt über einen 8-Bit-Timer, mit dem, wie bereits erwähnt, die Dauer der klingenden Noten und Pausen gesteuert wird. Es wurde entschieden, dass das Zeitintervall zwischen benachbarten Operationen (Unterbrechungen) eines solchen Zeitgebers dem Intervall eines "Ticks" entsprechen würde. Abhängig vom Tempo der Melodie ist der Wert dieses Zeitintervalls unterschiedlich. Ich entschied mich für Interflow-Timer-Interrupts. Abhängig vom anfänglichen Timer-Initialisierungsparameter ist es möglich, dasselbe Zeitintervall einzustellen, das vom Tempo der Melodie abhängt. Fahren wir nun mit den Berechnungen fort.
In der Praxis liegt das Tempo der Songs in der Regel im Durchschnitt in der Größenordnung von 50 bis 200. Es wurde bereits gesagt, dass das Tempo in der Midi-Datei in Mikrosekunden um ein Viertel eingestellt ist. Für Tempo 50 beträgt dieser Wert 60.000.000 / 50 = 1.200.000 und für Tempo 250 240.000. Da laut Projekt ein Viertel 48 Ticks enthält, beträgt die Ticklänge für das Mindesttempo 1.200.000 / 48 = 25.000 μs. Und für das maximale Tempo, wenn Sie auf die gleiche Weise berechnen, - 5000 μs. Für MK mit einer Quarzfrequenz von 8 MHz und einem maximalen vorläufigen Zeitgeberteiler von 1024 erhalten wir Folgendes. Für das Mindesttempo muss der Timer 25000 / (1024/8) = 195 mal berechnet werden. Das Ergebnis wird auf den nächsten ganzzahligen Wert gerundet, der Rundungsfehler hat praktisch keinen Einfluss auf das Ergebnis. Für das maximale Tempo - 5000 / (1024/8) = 39. Hier wirkt sich der Rundungsfehler nicht mehr aus, da auch für benachbarte Tempowerte von 248 bis 253 ein gerundeter Wert von 39 erhalten wird. Dementsprechend muss der Timer mit einem inversen Wert initialisiert werden: für das minimale Tempo - (256-195) = 61 und für das maximale - (256) -39) = 217. Das Mindesttempo, mit dem der Timer in der aktuellen MK-Konfiguration bereitgestellt wird, beträgt 39 Schläge pro Minute. Mit diesem Wert muss der Timer 250 Mal gezählt werden. Und mit einem Wert von 38 - bereits 257, was über die Grenzen des Timers hinausgeht. Ich entschied mich für den Wert von 40 Schlägen pro Minute für das minimale Tempo und 240 für das Maximum.
Um die Anzahl der Ticks zu berechnen, wird ein virtueller Timer verwendet, der auf dem Vorstehenden basiert. Es ist die Anzahl der Ticks, die die Dauer einer Note oder Pause festlegt, wie oben bereits erwähnt.
Um die Wiedergabe von Noten zu implementieren, wird ein zweiter 16-Bit-Timer verwendet. Gemäß der MIDI-Spezifikation werden insgesamt 128 Noten bereitgestellt. In der Praxis werden sie jedoch viel weniger verwendet. Darüber hinaus werden die Noten der niedrigsten (mit Frequenzen von ungefähr 50 Hz) und der höchsten (mit Frequenzen von ungefähr 8 kHz) Oktaven vom Mikrocontroller nicht harmonisch wiedergegeben. Trotzdem deckt ein 16-Bit-Timer mit festem Teiler fast den gesamten Notenbereich von Midi ab, nämlich ohne die ersten 35. Aber ich habe als Anfang die Notiz mit der Nummer 37 gewählt (ihr Code ist 36, da die Codierung von Null kommt). Dies geschieht der Einfachheit halber, da diese Zahl der Note „C“ entspricht, wie die erste Note in einer traditionellen Skala. Es entspricht einer Frequenz von 65,4 Hz und der Halbzyklus beträgt - 1 / 65,4 / 2 = 0,00764 s. Diese Zeitspanne bei einer MK-Frequenz von 8 MHz und einem Teiler 1 (dh ohne Teiler) zählt den Timer ungefähr insgesamt für 0,00764 / (1/8000000) = 61156-mal. Wenn Sie für die 35. Note zählen, beträgt dieser Wert 68645, was außerhalb des Bereichs des 16-Bit-Timers liegt. Aber selbst wenn es notwendig war, Noten unter dem 36. zu spielen, können Sie den ersten verfügbaren Timer-Teiler eingeben, der gleich 8 ist. Dies ist jedoch praktisch nicht erforderlich, ebenso wie es keinen gibt, der die obersten Noten spielt. Für die oberste 128. Note, "G" -Note mit einer Frequenz von 12.543,85 Hz, beträgt der Timer-Wert bei ähnlicher Zählung 319. Die Besonderheiten aller obigen Berechnungen werden durch die spezifische Konfiguration des Timer-Modus bestimmt, die später gezeigt wird.
Jetzt habe ich eine nicht weniger wichtige Frage: Wie erhalte ich die Beziehung zwischen der Notennummer und dem Code für den Timer? Es gibt eine bekannte Formel zur Berechnung der Frequenz einer Note anhand ihrer Nummer. Und der Zeitgebercode für eine bekannte Frequenz kann leicht berechnet werden, wie oben in den Beispielen gezeigt. Die Wurzel des 12. Grades erscheint jedoch in der Formel für die Abhängigkeit der Frequenz von der Note, und im Allgemeinen möchte ich den Controller nicht mit solchen Berechnungsverfahren laden. Andererseits ist es auch nicht rational, ein Array von Timer-Codes für alle Noten zu erstellen. Und ich entschied mich für Folgendes und wählte einen Mittelweg. Es reicht aus, ein Array von Timer-Codes für die ersten 12 Noten zu erstellen, die eine Oktave umfassen. Und die Noten der folgenden Oktaven sollten erhalten werden, indem die Frequenzen der Noten der ersten Oktave nacheinander mit 2 multipliziert werden. Oder das gleiche, indem die Werte der Timer-Codes nacheinander durch 2 geteilt werden. Eine weitere Annehmlichkeit besteht darin, dass die Oktavzahl zufällig ein Argument bei der bitweisen Verschiebung nach rechts ist ( »), Die als Operation der Division durch Zweierpotenzen verwendet wird. Ich habe diesen Operator nicht zufällig gewählt, da sein Argument den Exponenten der Potenz des Divisors widerspiegelt (die Anzahl der Divisionen durch 2). Und das ist die Oktavzahl. Für meine Noten sind insgesamt 8 Oktaven beteiligt (die letzte Oktave ist unvollständig). Eine Note in einer MIDI-Datei wird mit einem Byte, genauer gesagt 7 Bit, codiert. Um Noten in MK zu spielen, müssen Sie gemäß der obigen Idee zuerst die Oktavnummer und die Notennummer in der Oktave mit dem Notencode berechnen. Dieser Vorgang wird in der Phase der Konvertierung der MIDI-Datei in ein vereinfachtes Format ausgeführt. Acht Oktaven können in drei Bits codiert werden, und 12 Noten in einer Oktave können in vier Bits codiert werden. Insgesamt stellt sich heraus, dass die Note in den gleichen sieben Bits wie in der Midi-Datei codiert ist, jedoch nur in einer anderen für MK geeigneten Darstellung. Aufgrund der Tatsache, dass 16 Bits mit 4 Bits und Noten in einer Oktave von 12 codiert werden können, gibt es nicht verwendete Bytes.
Das letzte achte Bit kann als Marker zum Aktivieren oder Deaktivieren von Noten verwendet werden. Im Fall von MK sind aufgrund der Einstimmigkeit der Melodie Informationen über die gedämpfte Note überflüssig. Bei einem direkten Notenwechsel in der Melodie gibt es kein "Ausschalten-Einschalten", sondern einen "Schalter" der Note. Und im Falle einer Pause wird "Stille ist aktiviert", für die Sie ein spezielles Byte aus dem Satz nicht verwendeter Bytes auswählen können und die Informationen zum Ausschalten der Note überhaupt nicht verwenden. Eine solche Idee ist insofern gut, als sie die Größe der resultierenden Melodie nach der Konvertierung speichert, das Modell jedoch im Allgemeinen kompliziert. Ich bin dieser Idee nicht gefolgt, da es bereits viel Gedächtnis gibt.
Informationen zu den Melodienoten in der Midi-Datei werden im Block des entsprechenden Kanals in der Ansicht „Intervall-Ereignis-Intervall-Ereignis ...“ gespeichert. Im konvertierten Format gilt genau das gleiche Prinzip. Um ein Ereignis aufzuzeichnen (eine Notiz ein- oder auszuschalten), wie oben erwähnt, wird ein Byte verwendet. Das erste Bit (das höchstwertige Bit 7) codiert den Ereignistyp. Der Wert "1" ist die Note ein und der Wert "0" ist die Note aus. Die nächsten drei Bits codieren die Oktavnummer und die niedrigsten vier Bits codieren die Notennummer in der Oktave. Ein Byte wird auch zum Aufzeichnen des Zeitintervalls verwendet. Im ursprünglichen Midi-Format wird hierfür ein Format variabler Länge verwendet. Sein kleiner Nachteil ist, dass nur 7 Bits das Zeitintervall (die Anzahl der "Ticks") codieren und das achte Bit ein Zeichen der Fortsetzung ist. Das heißt, mit einem Byte können Sie tatsächlich ein Intervall von bis zu 128 Ticks codieren. Da die Zeitintervalle zwischen Ereignissen in realen und einfachen Melodien manchmal 128, aber fast nie 256 überschreiten, habe ich das Format variabler Länge aufgegeben und mit einem Byte verwaltet. Es codiert ein Zeitintervall von bis zu 256 Ticks. Da das Projekt 48 Ticks pro Quartal oder 48 * 4 = 192 Ticks pro Zyklus verwendet, kann ein Byte verwendet werden, um ein Intervall von 256/192 = 1 Dauer zu codieren. (3) (ein ganzes und ein Drittel) Zyklen, die ganz genug.
In dem nativen Format, in das die Midi-Datei konvertiert wird, habe ich auch einen kleinen Header mit einer Größe von 16 Bytes angewendet. Die ersten 14 Bytes enthalten den Namen der Melodie. Natürlich sollte der Name 14 Zeichen nicht überschreiten. Dann kommt ein Nullraum. Das vorletzte Byte spiegelt das Tempo der Melodie in einer für MK geeigneten Ansicht wider. Dieser Wert wird in der Konvertierungsphase berechnet und dient zur Initialisierung des MK-Timers, der für das Tempo verantwortlich ist. Wie es berechnet wird, wird in einigen Absätzen oben erläutert.
Ab dem 17. Byte folgt der Inhalt der Melodie. Jedes ungerade Byte entspricht einem Zeitintervall und jedes gerade Byte entspricht einem Ereignis (Anmerkung).
Das erste Byte ist Null, wenn die Melodie mit einer Note vom Anfang der Midi-Datei ohne vorläufige Pause beginnt. Ein Zeichen für das Ende der Melodie ist eine Bezeichnung von zwei Bytes 0xFF. Die Aufgabe beinhaltet die zyklische Wiedergabe einer Melodie durch einen Mikrocontroller. Damit die Melodie in der Schleife aus rhythmischer Sicht harmonisch klingt, muss sie korrekt geloopt werden. Um dies zu tun, müssen Sie nach einer letzten Note bei Bedarf eine bestimmte Länge pausieren, normalerweise bis der letzte Takt gefüllt ist. Und dafür müssen Sie das entsprechende Ereignis umleiten. Ich habe das Byte 0x0F verwendet, das beim Codieren von Notizen nicht verwendet wird. Dies entspricht dem Deaktivieren der 16. Note in der ersten Oktave, was absurd ist, da die Oktave nur 12 Noten enthält. Wir haben oben über nicht verwendete Bytes gesprochen. Somit codiert dieses Byte eine "stille Note",Das hohe Bit kann trotz der Redundanz der Informationen auch in diesem Fall auch als Zeichen für das Ein- und Ausschalten dienen. Um diese Note im Midi-Editor einzustellen, habe ich die erste oder zweite Note (eine davon) genommen. Ich möchte Sie daran erinnern, dass die ersten 36 Noten im Modell nicht verwendet werden. Daher wird die erste (oder zweite) Note nach Bedarf für die korrekte Vervollständigung der Melodie verwendet, damit der Rhythmus beim Spielen in einer Schleife nicht unterbrochen wird.Wir werden weiterhin im Editor von „Cakewalk Pro Audio 9“ arbeiten und eine beliebige Melodie komponieren. Die folgenden Abbildungen zeigen die Melodienotizen, die ich von einem der Bilder im Internet umgeschrieben habe. Notenbilder werden in zwei Stilen präsentiert: im Stil der „Pianorolle“ und im klassischen Stil. Die erste ist sehr praktisch zum Schreiben und Bearbeiten von Melodien mit einer Computermaus. Das ist was ich benutze.
Wie Sie der Abbildung entnehmen können, wird am Ende die niedrigste (erste) Note für das Zeichen der Stille im richtigen Zeitintervall angewendet, um das zyklische Muster korrekt zu organisieren. Und ganz am Anfang der Melodie befindet sich angesichts des Vorhandenseins einer Berührung ein Einzug von einem Viertel vor der ersten Note.Der Editor bietet einen Modus zum Anzeigen von Ereignissen in Tabellenform.
Wie Sie der Abbildung entnehmen können, enthält die Liste der Ereignisse nichts Überflüssiges, außer Notizen zu machen, wie dies manchmal bei unnötigen Manipulationen an einem Musikprojekt der Fall ist. Wenn jedoch unnötige Ereignisse, die sich nicht auf Notizen beziehen, aus irgendeinem Grund in der Liste enthalten sind, können sie durch Drücken der Entf-Taste gelöscht werden. Obwohl in der Phase der Konvertierung alle unnötigen Ereignisse ignoriert werden und sich die Deltazeit „ansammelt“. Übrigens habe ich diese Funktion dem Programm in der Debugging-Phase hinzugefügt. Wie Sie vielleicht erraten haben, spiegelt die Tabelle die Pünktlichkeit und Dauer jeder Note zusammen mit anderen Eigenschaften wider, die wir nicht benötigen. Das heißt, mit einer Zeile in der Tabelle werden zwei Midi-Ereignisse gleichzeitig ausgedrückt: Ein- und Ausschalten von Noten.Speichern Sie die Melodie im „Midi 1“ -Format, wie in der Abbildung gezeigt.
Öffnen Sie die gespeicherte Datei im HEX-Editor. Es sollte sofort beachtet werden, dass im Gegensatz zu denselben AVI-Dateien (wie ich zuvor geschrieben habe) Bytes mit numerischen Werten in einer Midi-Datei nicht in umgekehrter Reihenfolge, sondern nach Dienstalter (Big Endian) dargestellt werden.
In der Abbildung habe ich nur die gewünschten Bytes mit Markierungen markiert. Zunächst umreißt ein fetter roter Rahmen drei Gruppen mit jeweils zwei Bytes. Dies ist jeweils die Art des MIDI-Formats (1), die Anzahl der Kanäle (2) und die Anzahl der Ticks pro Quartal (48). Diese Werte müssen diese drei Konstanten für die weitere Arbeit des Transformationsprogramms haben. Lila Bögen markieren den Anfang jedes der beiden Kanäle. Im ersten Kanal sind 6 Bytes mit einem grauen Rahmen markiert, innerhalb dessen drei Bytes mit einem blauen Rahmen hervorgehoben sind. Diese 6 Bytes beziehen sich auf ein Metaereignis (Markierungsmarker 0xFF) mit einem Code von 0x51 und einer Inhaltslänge von 0x03 Bytes. Drei Bytes weiter - der Inhalt des Ereignisses. Dieses Ereignis legt das Tempo der Melodie mit nur diesen drei Bytes in einem blauen Rahmen fest. Das letzte niedrige Byte kann sicher verworfen werden, da die Supergenauigkeit nicht wichtig ist. Ich werde nicht alle Bytes in der Datei detailliert und gründlich beschreiben.In der zweiten Spur - in der Spur mit Noten - sind die Werte der Zeitintervalle in einem blauen Rahmen eingekreist. Übrigens haben sie in diesem Beispiel ein Byte nicht überschritten, mit Ausnahme des einzigen Falls mit der vorletzten Note. Es ist die vorletzte Note der Melodie (einschließlich der zusätzlichen Pseudonote des Endes), die drei Viertel eines Takts dauert, was 48 * 3 = 144 Ticks entspricht und 128 überschreitet. Dafür müssen Sie je nach Format mit variabler Länge zwei Bytes verwenden. Und um das Zeitintervall im konvertierten Format darzustellen, kann der Wert 144 leicht mit einem Byte codiert werden. Ich umkreiste diesen Sonderfall in einem doppelten blauen Rahmen. Notizen sind in einem grünen Rahmen oder besser gesagt in ihren Codes eingekreist. Die Lautstärke jeder Note ist in einem grauen Rahmen eingekreist. Wie bereits erwähnt, ist ein Volumen von Null ein Zeichen für die Stummschaltung (Freigabe) der Note, und in der gesamten Komposition gibt es ein Ereignis:Notizen einschalten. Der Code für dieses Ereignis, 0x90, ist gelb markiert. Ich habe nicht alle Noten bis zum Ende der Melodie umrissen. Die einzige Ausnahme ist der doppelte blaue Rahmen für ein einzelnes Zeitintervall, der den Schwellenwert von 128 Ticks überschreitet.Wie oben erwähnt, arbeitet das Programm zum Konvertieren einer MIDI-Datei in ein eigenes Format für MK tatsächlich mit einer Gruppe von mehreren MIDI-Dateien und erstellt am Ausgang eine Bilddatei für das EEPROM. Betrachten Sie ein Fragment aus dieser Datei, das sich auf den Inhalt der konvertierten Melodie aus dem obigen Beispiel bezieht. Ich habe es in einem anderen HEX-Editor geöffnet, um das Bild nach Sektoren anzuzeigen und darauf zu achten. Jede neue Melodie beginnt mit einem neuen Sektor.
Das letzte Byte aus der ersten Zeile (die ersten 16 Bytes), das in einem roten Rahmen eingekreist ist, legt das Tempo der Melodie fest. Berechnungen zufolge fällt der Wert 0xC1 (193) auf das Tempo 154, 155 und 156. Gerade im Projekt habe ich das Melodietempo auf 155 Schläge pro Minute eingestellt, was in einem der Screenshots zuvor zu sehen war. Die ersten Bytes (bis zum 14.), die in einem blauen Rahmen eingekreist sind, bestimmen den Namen der Komposition. In diesem Beispiel "Klassisch". Für MK sind diese Informationen nicht erforderlich, sie werden nur zur Orientierung im HEX-Editor benötigt. Wenn Sie mit dem Display ein komplexeres Projekt auf dem MK erstellen, können Sie diese Informationen verwenden, indem Sie den Namen der gespielten Melodie anzeigen.Die zweite Zeile (ab dem 17. Byte) beginnt den Inhalt der Melodie. Wie bei der ursprünglichen Midi-Datei habe ich nicht alle Noten gemalt, sondern nur einen Teil. Die ungeraden blau hervorgehobenen Bytes sind Zeitintervalle. Sogar Bytes, die mit einem grünen Rahmen markiert sind, sind Notizen zusammen mit Zeichen ihres Ein / Aus. Beispielsweise beziehen sich die ersten beiden grünen Bytes, 0xB4 und 0x34, auf dieselbe Notiz mit dem Code 0x34, und die Bytes unterscheiden sich nur in einem höherwertigen Bit. In Byte 0xB4 (0b10110100) ist das High-Bit eins, was ein Zeichen für das Einschalten einer Note ist, und in Byte 0x34 (0b00110100) ist das High-Bit Null, was ein Zeichen für das Ausschalten einer Note ist. Byte 0x34 codierte eine Note mit den folgenden Parametern: Oktavcode 0b011 und Notencode in einer Oktave - 0b0100. Oder in Dezimalform 3 bzw. 4. Wenn Sie nicht von Null zählen,es stellt sich heraus, dass die erste Note in der Melodie zur vierten Oktave gehört und die fünfte darin ist. Die Oktavnummerierung wird hier willkürlich gewählt, ohne die Standardnummerierung zu berücksichtigen. Die vereinbarte Note ist nach meiner Berechnungstabelle Excel die Note mit dem Code 76 (0x4C) für das Midi-Format, dh die Note E6 (Note „e“ der 6. Mitteloktave). So ist es: Die Komposition beginnt mit dieser Note.Es sollte ein Sonderfall in der Musiksequenz beachtet werden, wenn dieselbe Note ohne Pause wiederholt wird. In unserem Beispiel sind alle benachbarten Noten, die pausenfrei sind, unterschiedlich. Es gibt jedoch Melodien, bei denen sich die Note ohne Pause wiederholt. Das heißt, das Zeitintervall zwischen dem Ausschalten einer und dem Einschalten der nächsten exakt gleichen Note ist Null. Angesichts der Besonderheit einer komplexen Musiksynthese wird eine solche Sequenz jedem Synthesizer bekannt vorkommen. Im Fall von MK klingt es jedoch so zusammenhängend, dass es schwierig ist, den Unterschied zwischen zwei identischen Noten zu hören. In der Praxis wird es natürlich keine eindeutige Verschmelzung aufgrund von Zwischenberechnungen im MC geben, aber dennoch ist dieses Zeitintervall höchstwahrscheinlich viel kürzer als die Dauer von nur einem Tick. Für solche Sonderfälle befindet sich das Programm in der Konvertierungsphase,Wenn Sie auf eine solche Kombination stoßen, wird eine Pause zwischen Noten mit einer Tick-Länge eingeleitet und die Dauer einer Note links von der Note um dasselbe Zeitintervall verringert. Eine minimale „Lücke“ von 1 Tick ist völlig ausreichend, wie die Praxis gezeigt hat.In einem doppelten blauen Rahmen umkreiste ich den Wert des Zeitintervalls (0x90), der 128 überschreitet und für den ich je nach Format mit variabler Länge zwei Bytes in der Midi-Datei ausgeben musste. Grüne Kreise sind Bytes auf und neben derselben Pseudonote, um die Komposition auszurichten. Wenn das MK-Programm diese Bytes sieht, interpretiert es sie als eingeschaltete Stille. Schließlich markieren zwei 0xFF-Bytes, die in einem fetten blauen Rahmen eingekreist sind, das Ende der Melodie. Die Werte aller folgenden Bytes innerhalb des aktuellen Speichersektors können beliebig sein, sie werden ignoriert.Betrachten Sie den allerersten Sektor der Ausgabe-EEPROM-Bilddatei. Wie ich bereits geschrieben habe, dient es als Liste von Adressen von Sektoren des Beginns von Melodien. Das Programm hat erfolgreich 8 Melodien ohne Fehler gescannt (zum Zeitpunkt des Schreibens hatte ich 8 Melodien aufgenommen). Der Wert der Anzahl der Melodien wird im letzten 512. Byte des Sektors aufgezeichnet. Und von Anfang an werden Adressen geschrieben. Für die erste Melodie lautet die Adresse 0x01, was dem zweiten Sektor entspricht (der erste, wenn Sie von Grund auf neu zählen). Die dritte und vierte Melodie (zwei von acht) erwiesen sich als lang und passten nicht in einen Sektor. Daher werden Lücken in der Adresssequenz beobachtet. Wenn Sie 64 KB Speicher zählen, können Sie nicht mehr als 127 Musikstücke aufnehmen, sodass ein Sektor für die Adressierung völlig ausreicht.
Alle vorläufigen Schätzungen und Berechnungen, die im Artikel enthalten sind, habe ich in Excel durchgeführt. Die folgenden Screenshots zeigen Screenshots der resultierenden Tabellen (im Dual-Window-Modus).
Wen kümmert es, unten unter dem Spoiler befindet sich der Text eines C-Programms, das MIDI-Dateien in eine Datei für den Mikrocontroller konvertiert. Aus dem Text habe ich die zusätzlichen Zeilen entfernt, die zum Debuggen verwendet wurden. Das Programm funktioniert bisher und gibt nicht vor, lesbar zu sein und Code zu schreiben.Hauptdatei 1.cpp#include <stdio.h> #include <windows.h> #include <string.h> #define SPACE 1 HANDLE openInputFile(const char * filename) { return CreateFile ( filename, // Open Two.txt. GENERIC_READ, // Open for writing 0, // Do not share NULL, // No security OPEN_ALWAYS, // Open or create FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template file } HANDLE openOutputFile(const char * filename) { return CreateFile ( filename, // Open Two.txt. GENERIC_WRITE, // Open for writing 0, // Do not share NULL, // No security OPEN_ALWAYS, // Open or create FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template file } void filepos(HANDLE f, unsigned int p){ LONG LPos; LPos = p; SetFilePointer (f, LPos, NULL, FILE_BEGIN); //FILE_CURRENT //https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointer } DWORD wr; DWORD ww; unsigned long int read32(HANDLE f){ unsigned char b3,b2,b1,b0; ReadFile(f, &b3, 1, &wr, NULL); ReadFile(f, &b2, 1, &wr, NULL); ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b3<<24|b2<<16|b1<<8|b0; } unsigned long int read24(HANDLE f){ unsigned char b2,b1,b0; ReadFile(f, &b2, 1, &wr, NULL); ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b2<<16|b1<<8|b0; } unsigned int read16(HANDLE f){ unsigned char b1,b0; ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b1<<8|b0; } unsigned char read8(HANDLE f){ unsigned char b0; ReadFile(f, &b0, 1, &wr, NULL); return b0; } void message(unsigned char e){ printf("Error %d: ",e); switch(e){ case 1: // - -; printf("In track0 event is not FF\n"); break; case 2: // - 127 printf("Len of FF >127\n"); break; case 3: // ; printf("Midi is incorrect\n"); break; case 4: // ; printf("Delta>255\n"); break; case 5: // RPN NRPN; printf("RPN or NRPN is detected\n"); break; case 6: // ; printf("Note in 1...35 range\n"); break; case 7: // ; printf("Long of name of midi file >18\n"); break; } system("PAUSE"); } int main(){ HANDLE in; HANDLE out; unsigned int i,j; unsigned int inpos; unsigned int outpos=0; unsigned char byte; // ; unsigned char byte1; // 1 ; unsigned char byte2; // 2 ; unsigned char status; //- ( ); unsigned char sz0; // -; unsigned long int bsz0; // -; unsigned short int format, ntrks, ppqn; // ; unsigned long int bsz1; // ; unsigned long int bpm; // ( . ); unsigned long int time=0; // ( ); unsigned char scale; // , ; unsigned char oct; // ; unsigned char nt; // ; unsigned char outnote; // ; unsigned char prnote=0; // ; unsigned char tdt; // () ; unsigned int dt; // ( ); unsigned int outdelta=0; // ( ); unsigned char prdelta=0; // ; char fullname[30]; // ; char name[16]; // ; WIN32_FIND_DATA fld; // mid; HANDLE hf; unsigned short int csz; // ; unsigned char nfile=0; // ; unsigned char adr[128]; // ; out=openOutputFile("IMAGE.out"); outpos=512; // ; filepos(out,outpos); hf=FindFirstFile(".\\midi\\*.mid",&fld); do{ printf("\n***** %s *****\n",fld.cFileName); if(strlen(fld.cFileName)>18){ // ; message(7); } sprintf(name,"%s",fld.cFileName); name[strlen(fld.cFileName)-4]=0; // ; sprintf(fullname,".\\midi\\%s",fld.cFileName); // ; WriteFile(out, name, strlen(name), &ww, NULL); // ; in=openInputFile(fullname); // ; #include "process.cpp" // ; outpos+=((csz/512)+1)*512; // ; adr[nfile]=(outpos/512)-((csz/512)+1); // () ; filepos(out,outpos); CloseHandle(in); nfile+=1; }while(FindNextFile(hf,&fld)); // , ; FindClose(hf); WriteFile(out, &outnote, 1, &ww, NULL); outpos=0; // ; filepos(out,outpos); WriteFile(out, adr, nfile, &ww, NULL); outpos=511; // ; filepos(out,outpos); WriteFile(out, &nfile, 1, &ww, NULL); CloseHandle(out); system("PAUSE"); return 0; }
Der grundlegende Teil des Programms für MK ist in der Tat sehr einfach. Betrachten Sie eine der Optionen für die Implementierung, genauer gesagt den Hauptteil.
Timer 1, der zum Erzeugen des Tons von Noten verwendet wird, ist wie folgt konfiguriert. Zum Aktivieren und Deaktivieren von Notizen werden jeweils die folgenden Ersetzungen verwendet.
#define ENT1 TCCR1B=0x09;TCCR1A=0x40 #define DIST1 TCCR1B=0x00;TCCR1A=0x00;PORTB.1=0
Bevor Sie den Timer starten, müssen Sie dem OCR1A-Register einen 16-Bit-Wert zuweisen, der der gespielten Frequenz entspricht. Dies wird später gezeigt. Wenn der Timer eingeschaltet ist, wird dem TCCR1B-Register der Wellenformgenerierungsmodus mit einem Timer-Teiler von 1 zugewiesen, und das TCCR1A-Register wird beim Vergleichen der Übereinstimmung auf OC1A umschalten gesetzt. In diesem Fall wird das Signal vom speziell dafür vorgesehenen Ausgang von MK „OC1A“ entfernt. Beim ATmega8 im SMD-Paket ist dies Pin 13, der mit PORTB.1 identisch ist. Wenn der Timer ausgeschaltet wird, werden beide Register zurückgesetzt und der Ausgang von PORTB.1 wird auf Null gesetzt. Dies ist notwendig, um während der Stille das Ausgeben einer konstanten Spannung zu verhindern, was für den Eingang des VLF unerwünscht wäre. Sie können zwar einen Kondensator in die Schaltung einbauen, aber Sie können den Ausgang auch programmgesteuert deaktivieren. An diesem Ausgang kann eine konstante Spannung auftreten, wenn die Note zum Zeitpunkt der entsprechenden Phase des Signals ausgeschaltet wird, und dies ist in 50% der Fälle der Fall.
Erstellen Sie ein Array von Timer-Werten für 12 Noten der ersten Oktave. Diese Werte wurden im Voraus berechnet.
freq[]={61156,57724,54484,51426,48540,45815,43244,40817,38526,36364,34323,32396};
Die Noten anderer Oktaven werden, wie gesagt, durch Teilen durch zwei Grad erhalten.
Die Konfiguration von Timer 0 ist noch einfacher. Es arbeitet ständig mit einem Überlauf-Interrupt und wird jedes Mal neu mit dem Wert initialisiert, der dem Tempo der Melodie entspricht. Der Timer-Teiler ist 5: TCCR0 = 0x05. Basierend auf diesem Timer wird ein virtueller Timer erstellt, der die Tics (Zeiten) in der Melodie zählt. Die Verarbeitung der Antwort dieses Timers wird in den Hauptprogrammzyklus gestellt.
Die Timer 0-Interrupt-Funktion ist wie folgt.
interrupt [TIM0_OVF] void timer0_ovf_isr(void){ if(ent01){ vt01+=1; } TCNT0=top0; }
Hier ist die Variable ent01 für die Aktivierung des virtuellen Timers verantwortlich. Mit dieser Variablen kann sie bei Bedarf ein- oder ausgeschaltet werden. Die Variable vt01 ist die zählbare primäre Variable des virtuellen Timers. Die Zeile TCNT0 = top0 zeigt die Initialisierung des Timers 0 auf den gewünschten Wert top0 an, der vor dem Abspielen aus dem Titel der Melodie gelesen wird.
Die Nummer der zu spielenden Melodie entspricht der Variablen alm. Es dient auch als Flagge für den Beginn der Reproduktion. Sie muss je nach Aufgabe auf eine der verschiedenen Arten eine Melodienummer zuweisen. Danach wird der nächste Block des Hauptzyklus aktiv.
if(alm){
Das weitere Umschalten von Note zu Note erfolgt in der Verarbeitungseinheit des virtuellen Timers, die sich ebenfalls in der Hauptschleife befindet.
if(vt01>=top01){
Aus den Kommentaren im Programmtext sollte alles klar und verständlich sein.
Verwenden Sie zum Stoppen der Melodie die folgende Einfügung der Hauptschleife.
if(stop){
Es gibt eine kleine Bemerkung zur Implementierung der Melodienwiedergabe. Bevor jede neue Note zu ertönen beginnt, verbringt der Mikrocontroller einige Zeit damit, das gelesene Byte der Note in einen Timerwert umzuwandeln. Diese Zeit ist, wie sich in der Praxis herausstellte, relativ gering und beeinträchtigt die Wiedergabequalität nicht. Aber ich hatte Zweifel, dass diese Operation unsichtbar bleiben würde. In diesem Fall würden vor jeder Note zusätzliche Pausen erscheinen und der Rhythmus der Melodie würde unterbrochen. Dieses Problem ist aber auch lösbar. Es reicht aus, die Timer-Werte der nächsten Note im Voraus zu berechnen, während die aktuelle Note ertönt. Diese Prozedur muss getrennt von der Verarbeitung des virtuellen Timers in der Hauptprogrammschleife unter Verwendung eines speziell bezeichneten Flags durchgeführt werden. Aufgrund der Tatsache, dass die Berechnungszeit die Spielzeit selbst der kürzesten Note wahrscheinlich nicht überschreitet, ist eine solche Lösung angemessen.
Fahren wir nun mit dem Testen des Programms fort.
Zusätzlich zu den obigen Codefragmenten habe ich dem MK-Programm Schaltflächenverarbeitungsfunktionen hinzugefügt, mit denen ich die Aufnahme oder Deaktivierung einer bestimmten Melodie steuern kann. Das EEPROM ist über einen I2C-Bus mit MK verbunden, dessen Arbeit auf Softwareebene implementiert wird. Das Projekt wurde mit Hilfe von „CodeVisionAVR“ zusammen mit „CodeWizardAVR“ durchgeführt. Ich gebe MK von Pin 13 über den Teiler auf die PC-Soundkarte aus und nehme den Klang der Melodie im Soundeditor auf. Ich habe den EEPROM-Speicher mit Hilfe der Firmware geflasht, über die ich in einem der vorherigen Artikel geschrieben habe. Aufgrund der Tatsache, dass nicht alle Bytes der Bilddatei nützlich sind, kann die Speicherfirmware nur durch nützliche Bytes (bis zu den Endmarkierungen von Melodien) implementiert werden, um Aufnahmezeit und Chipressourcen zu sparen. Zu diesem Zweck können Sie ein separates Programm erstellen oder während der Konvertierung direkt Bytes auf den Chip schreiben und dem Hauptprogramm hinzufügen.
Unter den acht Melodien gibt es drei Testmelodien, mit deren Hilfe ich den Frequenzbereich nach Gehör, den Klang der Zusammenführung identischer Noten, den Klang der kürzesten Noten, schnelle Übergänge usw. bewerten werde. Ich möchte Sie daran erinnern, dass das Zusammenführen derselben Noten tatsächlich mit einer Pause von einem Tick klingt und die erste Note bei der Fusion einen Tick weniger dauert.
Eine der Testmelodien ist eine Folge von Noten vom ersten bis zum letzten mit einer Dauer von einer Note in einem Viertel und einem Melodietempo von 40 Schlägen pro Minute.

In diesem Szenario klingt eine Note etwas länger als eine Sekunde. Daher können Sie detailliert hören, wie der gesamte Notenbereich klingt. Im Frequenzspektrum des Audio-Editors "Adobe Audition" werden die Hauptfrequenzkomponenten und ihre oberen Harmonischen aufgrund der entsprechenden Sägezahnwellenform beobachtet. Auffällig ist die logarithmische Beziehung zwischen Notennummer und Frequenz.

Bei der Analyse der Zeitintervalle ist deutlich zu erkennen, dass die tatsächliche Pause zwischen aufeinanderfolgenden Noten durchschnittlich etwa 145 Abtastwerte (bei einer Abtastfrequenz der Audioaufzeichnung von 44100 Hz) beträgt, was etwa 3 ms entspricht. Dies ist die Zeit, in der der MK die erforderlichen Berechnungen durchführt. Diese Beilagen sind regelmäßig vor jeder Note vorhanden. Ich habe speziell die Bedeutung in den Beispielen geschrieben, da diese Informationen origineller und genauer sind, obwohl dies nicht sehr wichtig ist.

Und die Länge eines Ticks bei einem durchschnittlichen Melodietempo von 120 Schlägen pro Minute beträgt etwa 10 ms. Daraus folgt, dass es im Prinzip möglich wäre, nicht dieselbe Korrektur in einem Tick einzuführen, wenn zwei identische Noten ohne Pause nacheinander gehen. Ich denke, dass das regelmäßige Einfügen von 3 ms zwischen Noten völlig ausreichend wäre. Beim Anhören einer Melodie fallen diese regulären Einfügungen überhaupt nicht auf und die Melodien klingen gleichmäßig. Daher muss der Timer-Wert für die nächste Note nicht besonders berechnet werden, während die aktuelle Note abgespielt wird.
Eine andere Testmelodie mit einem Tempo von 200 Schlägen pro Minute enthält nacheinander die gleichen 1/32 Noten aus dem mittleren Bereich ohne Pause. In diesem Fall gibt es nach der Verarbeitung beim Spielen zwischen ihnen eine Pause von 1 Tick, die bei diesem schnellen Tempo von 310 Samples (ca. 6 ms) des aufgezeichneten Signals.

Die Länge dieser Pause ist übrigens vergleichbar mit der Periode des Signals, die ein hohes Tempo der Melodie anzeigt. Und sein Klang erinnert an einen Triller.
Grundsätzlich kann dies beendet werden. Ich war mit dem Ergebnis des Gerätes zufrieden, es hat alle Erwartungen übertroffen. Die meiste Zeit widmete ich mich dem Studium des Midi-Formats und dem Debuggen des Konvertierungsprogramms. In einem der folgenden Artikel werde ich mich auch einem Thema im Zusammenhang mit MIDI widmen, in dem es um die Anwendung dieses Formats in anderen interessanten Anwendungen geht.