Trackermodul-Engine (sehr einfach)
Re: Trackermodul-Engine (sehr einfach)
So...
ZVID steht ja mehr oder weniger, in einer funktionierenden, brauchbaren Form, es fehlt
vielleicht noch der Blocktyp "Eine Grundfarbe und ein paar andersfarbige Pixel".
Ich fasse meine zwischenzeitlichen Überlegungen zu meinem Trackerformat zusammen.
Es soll ja simpel sein, vor allem damit der Player möglichst schnell ist, die Patterndaten
trotz 8 Kanälen kompakt bleiben, und auch damit ich als Programmierer nicht von vorn-
herein derart gefordert bin, dass ich das nicht schaffe...
Folgende Eigenschaften / Limitierungen sollen es sein:
- vorerst keine Sample-Schleifen. Chiptunes fallen damit zwar weg, aber die klingen sowieso nur gut mit voller Effektpalette.
- als "Effekte" nur Lautstärke-Änderung und Wechsel auf andere Note ohne Sample neu zu starten. Kein Vibrato etc.
- Lautstärkewerte beschränken sich auf volle Lautstärke, halbe, viertel usw. damit das einfach mit Bitshifting erledigt ist
-> Somit ändern sich Tonhöhen und Lautstärken immer nur pro Zeile im Pattern, und nicht auch zwischendrin,
was die Sache vereinfacht.
Für mich und meinen Stil beim Musik machen sind das gar nicht so starke Einschränkungen.
Über die Jahre hat sich herausgestellt dass ich etwa zu 80% gar nicht mehr als diese Möglichkeiten brauche.
Natürlich wären Schleifen reizvoll, man könnte Musikstücke machen die vielleicht nur 1 KB groß sind.
Ich frage mich gerade aber ob ich das alles ersteinmal ohne Schleifen programmieren soll, oder
ob man direkt von vornherein alles so auslegen muss dass auch Schleifen möglich sind.
Gewissermaßen gönne ich mir beim dem Format aber einen Luxus, den die klassischen Formate nicht haben:
Eine frei einstellbare C-3-Samplerate. Dafür würde ich für jedes Sample beim Laden eine individuelle
Schrittweitentabelle mit z.B. 64 Einträgen berechnen. Verglichen mit dem Datenbedarf eines Samples
wäre eine solche Tabelle, angenommen es wären DWORD Werte, mit 256 Byte ja noch klein.
Die Bitbreite der Schrittweiten hängt letztlich aber davon ab für welche Auflösung ich mich bei
der Festkommarechnung entscheide. Wahrscheinlich müssten auch WORDs genügen und man
braucht vielleicht auch keine 5 Oktaven, stattdessen vielleicht vier, ergo 48 Tabellenwerte -> 96 Byte.
Was die Speicherung der Patterndaten angeht war ich bis zuletzt ziemlich effizient mit einer Redundanz-
kürzenden Speicherung, die zudem vielmehr ein Bitstream war und immer nur so viele Bit verwendet hat
wie notwendig, d.h. für die Lautstärkebefehle nur 3 Bit, da shiften um mehr als 7 keinen Sinn macht.
Weiterhin auch sehr effizient, wenn z.B. bei Schlagzeug ein Instrument nochmals genauso
gespielt wurde wie vorher, genügt 1 Bit um diese Information zu speichern.
Die Samples sollen übrigens unkomprimiert vorliegen. Das beschleunigt alles sehr,
ist vernünftig, man muss dann eben als "Komponist" besonnen auswählen, und es
ist umso netter wenn man dann mal so ein Modul in einen Wave-Editor lädt, dass
man die Samples am Stück hören kann. ;)
Wenn der Player fertig ist kann ich ja mal versuchen, in Kombination mit ZVID eine
kleine Demo bzw. ein Musikvideo zu machen.
ZVID steht ja mehr oder weniger, in einer funktionierenden, brauchbaren Form, es fehlt
vielleicht noch der Blocktyp "Eine Grundfarbe und ein paar andersfarbige Pixel".
Ich fasse meine zwischenzeitlichen Überlegungen zu meinem Trackerformat zusammen.
Es soll ja simpel sein, vor allem damit der Player möglichst schnell ist, die Patterndaten
trotz 8 Kanälen kompakt bleiben, und auch damit ich als Programmierer nicht von vorn-
herein derart gefordert bin, dass ich das nicht schaffe...
Folgende Eigenschaften / Limitierungen sollen es sein:
- vorerst keine Sample-Schleifen. Chiptunes fallen damit zwar weg, aber die klingen sowieso nur gut mit voller Effektpalette.
- als "Effekte" nur Lautstärke-Änderung und Wechsel auf andere Note ohne Sample neu zu starten. Kein Vibrato etc.
- Lautstärkewerte beschränken sich auf volle Lautstärke, halbe, viertel usw. damit das einfach mit Bitshifting erledigt ist
-> Somit ändern sich Tonhöhen und Lautstärken immer nur pro Zeile im Pattern, und nicht auch zwischendrin,
was die Sache vereinfacht.
Für mich und meinen Stil beim Musik machen sind das gar nicht so starke Einschränkungen.
Über die Jahre hat sich herausgestellt dass ich etwa zu 80% gar nicht mehr als diese Möglichkeiten brauche.
Natürlich wären Schleifen reizvoll, man könnte Musikstücke machen die vielleicht nur 1 KB groß sind.
Ich frage mich gerade aber ob ich das alles ersteinmal ohne Schleifen programmieren soll, oder
ob man direkt von vornherein alles so auslegen muss dass auch Schleifen möglich sind.
Gewissermaßen gönne ich mir beim dem Format aber einen Luxus, den die klassischen Formate nicht haben:
Eine frei einstellbare C-3-Samplerate. Dafür würde ich für jedes Sample beim Laden eine individuelle
Schrittweitentabelle mit z.B. 64 Einträgen berechnen. Verglichen mit dem Datenbedarf eines Samples
wäre eine solche Tabelle, angenommen es wären DWORD Werte, mit 256 Byte ja noch klein.
Die Bitbreite der Schrittweiten hängt letztlich aber davon ab für welche Auflösung ich mich bei
der Festkommarechnung entscheide. Wahrscheinlich müssten auch WORDs genügen und man
braucht vielleicht auch keine 5 Oktaven, stattdessen vielleicht vier, ergo 48 Tabellenwerte -> 96 Byte.
Was die Speicherung der Patterndaten angeht war ich bis zuletzt ziemlich effizient mit einer Redundanz-
kürzenden Speicherung, die zudem vielmehr ein Bitstream war und immer nur so viele Bit verwendet hat
wie notwendig, d.h. für die Lautstärkebefehle nur 3 Bit, da shiften um mehr als 7 keinen Sinn macht.
Weiterhin auch sehr effizient, wenn z.B. bei Schlagzeug ein Instrument nochmals genauso
gespielt wurde wie vorher, genügt 1 Bit um diese Information zu speichern.
Die Samples sollen übrigens unkomprimiert vorliegen. Das beschleunigt alles sehr,
ist vernünftig, man muss dann eben als "Komponist" besonnen auswählen, und es
ist umso netter wenn man dann mal so ein Modul in einen Wave-Editor lädt, dass
man die Samples am Stück hören kann. ;)
Wenn der Player fertig ist kann ich ja mal versuchen, in Kombination mit ZVID eine
kleine Demo bzw. ein Musikvideo zu machen.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Hallo Zatzen! Ich würde gern deine Motivation etwas besser verstehen... du willst du Patterndaten möglichst kompakt haben - aber die Samples sollen unkomprimiert sein. Ist es wirklich sinnvoll den verschwindend kleinen Teil, den die Patterndaten in einer MOD-Datei ausmachen, kleiner zu bekommen? Wenn du kleinere Dateien möchtest, dann würde eine Komprimierung der Samples um nur 10% vermutlich mehr bringen als die Patterndaten um 90% zu reduzieren.
Ist es vielleicht eine Alternative dein eigenes Format in z.B. MikMod einzubauen? Diese Library kann unzählige Formate lesen und ein kurzer Blick sagt mir, dass es relativ leicht ist, ein weiteres hinzuzufügen. Alles, was du nicht brauchst (Effekte usw...), schmeißt du im Anschluss raus. Warum das Rad neu erfinden, wenn das meiste schon da ist? Allerdings weiß ich nicht, in welcher Programmiersprache du unterwegs bist.
Ist es vielleicht eine Alternative dein eigenes Format in z.B. MikMod einzubauen? Diese Library kann unzählige Formate lesen und ein kurzer Blick sagt mir, dass es relativ leicht ist, ein weiteres hinzuzufügen. Alles, was du nicht brauchst (Effekte usw...), schmeißt du im Anschluss raus. Warum das Rad neu erfinden, wenn das meiste schon da ist? Allerdings weiß ich nicht, in welcher Programmiersprache du unterwegs bist.
-
- DOS-Gott
- Beiträge: 2347
- Registriert: Mo 8. Feb 2010, 16:59
Re: Trackermodul-Engine (sehr einfach)
Im Grunde gibt es den GLX2.12 welches der beste MOD-Player für Dos und XT/AT Rechner ist.
Eine noch verbesserte Form steckt in dem Scene-Demo 8088MPH.
Eine noch verbesserte Form steckt in dem Scene-Demo 8088MPH.
CPU: 486 DX2/66 MOBO: SNI-D882 RAM: 3x16MB - FDD: 3,5" 1,44MB HDD: 6,4GB Seagate ISA(1): Audican32Plus PCI(1): 3com TX 905 OS: MsDos622 - Win95a - WinNT 3.51
Re: Trackermodul-Engine (sehr einfach)
Ja, es gibt da ein paar Gründe, warum ich auch gerne mal das Rad neu erfinde in diesem Fall.
Ich kenne den GLX2.12 nicht, weiss nicht welche Rechenzeiten er verschlingt und wieviel von mir
ungenutze Funktionen er unterstützt.
Ich programmiere mit Borland Pascal 7 mit integriertem Assembler.
Da ich mich damit sowieso für heutige Verhältnisse in einem längst überholten Gebiet bewege denke ich,
dass es auch nicht schlimm wäre ebenso eigene Wege zu gehen, das Rad neu zu erfinden, statt auf eine
fertige LIB zuzugreifen oder diese um ein Format zu ergänzen. Ich glaube der Overhead dieser Library
wäre größer als wenn ich mein Format einfach selbst code.
Ich habe erst lange gehadert wegen Komprimierung der Samples und einiges ausprobiert, aber
in allen Fällen waren die Ergebnisse enttäuschend und so habe ich mich auch zugunsten der Rechenleistung
dafür entschieden die Samples nicht zu komprimieren.
Mit der Größe der Patterndaten vs. Samplegrößen muss man sehen.
In der heutigen Zeit wo man mit einem Tracker direkt auf hochfeine CD-Qualität abzielt, hat man
in so einem Modul gerne mal mehrere MB an Sampledaten, und dann sind die Patterndaten natürlich
vernachlässigbar.
Allerdings habe ich das Trackern in einer Zeit angefangen in der ich nur den konventionellen RAM
oder die Standard 256 KB der Gravis Ultrasound zur Verfügung hatte - und habe trotzdem gut klingende
Sachen hinbekommen.
Mit ein bisschen Erfahrung kann man die Größe der Sampledaten gut in Schach halten, beispielsweise
durch angemessene Sample-Rates, ich brauche für ein tiefes Bass-Sample nicht unbedingt 22 kHz.
Zudem würde mein Converter das ganze Stück einmal durchspielen und überprüfen, ob manche
Samples gar nicht ganz bis zum Ende gespielt würden, und dann am Ende entsprechend viel abschneiden,
ausserdem bei Samples mit "Nullen" am Ende auch diese entfernen, selbstverständlich auch bei
Samples mit Schleifen hinter dem Schleifenende abschneiden.
Legen wir den Gesamtspeicherplatz der Samples für ein Rechenexempel einfach mal auf 64 KB fest.
Das ist durchaus möglich und es gibt genug MODs mit so wenig Samplespeicherplatz, allerdings
bemerkt man das seltener weil die Dateien durch die Patterns so aufgeblasen werden.
Weiterhin belegt ein Pattern im normalen MOD Format 1 KB (vier Kanäle, 64 Zeilen).
Ich möchte allerdings 8 Kanäle haben. Das gibts auch im MOD Format, und dort auch ganz
einfach erweitert auf die doppelte Größe, also 2 KB.
Nun bevorzuge ich zudem noch 128 Zeilen statt 64 Zeilen, sprich, doppelte Geschwindigkeit,
um auch bequem und ohne Tricks 32tel Noten machen zu können. Das machen übrigens auch
viele mit dem MOD Format, haben dort dann aber eben auch nur wieder 64 Zeilen und müssen
deshalb einfach doppelt so viele Patterns verwenden.
Somit wären wir bei mir bei 4 KB pro Pattern, wobei aber tatsächlich ein Track-Event bei
mir nur 2 Byte belegt, somit dann doch wieder 2 KB pro Pattern.
Aber nehmen wir an, ein Pattern dauert 8 Sekunden, dann wären wir bei einem 3-Minuten
Song bei dem nicht nur im Sequencer immer nur ein paar gleiche Patterns gewechselt
werden bei 22 Patterns und dann 44 KB. Das gegenüber 64 KB Sampledaten tut schon etwas weh.
Vor allem weil ich für ein Spiel es eher so machen würde dass ich auch gerne mal längere
Stücke schreiben würde.
Nur einmal ein konkretes Beispiel anhand eines vielleicht bekannten Moduls, TURRICAN.MOD
bzw. "turrican-ii-remix". Das Stück hat über 50 Patterns voller Redundanzen und belegt
im Pattern-Teil etwa 59 KB. Die Samples belegen etwa 88 KB. Gesamtgröße 148 KB.
Und das sind jetzt nur vier Kanäle. Bei 8 Kanälen wären die Patterndaten, unkomprimiert,
größer als die Samples. Man könnte mit guter Patternkompression das Musikstück auf
unter 100 KB halten. Wobei ich zugeben muss, dass so ein MOD natürlich voll von Effekten
ist, die ich gar nicht (mehr) verwende, und daher pro Track Event eben 4 Byte verlangt,
während ich mein Format abgespeckt habe auf insgesamt 16 Bit (6 Bit Note, 6 Bit Samplenr,
3 Bit Volume, 1 Bit ob das Sample neu angeschlagen wird).
Wir reden hier also in einer anderen Dimension. Ich programmiere für den Real Mode, die unteren 640 KB,
von denen vielleicht eher nur 550 nutzbar sind. Und da möchte ich Grafik und Sound und natürlich auch
Code reinbekommen. Da kann ich es mir nicht immer leisten eine MOD Datei reinzuschmeissen die 400KB groß
ist und bei der das Verhältnis von Patterndaten zu Sampledaten tatsächlich 1:10 ist.
Eher möchte ich die Sampledaten unter 100 KB halten, und dann wäre es schön wenn die Patterndaten
unter 10 KB bleiben, auch bei ziemlich langen Stücken.
Noch mehr, für kurze oder langsame Stücke die Möglichkeit offenhalten, durch die Nutzung von Schleifen
d.h. sehr kurzen Samples auch mal Module machen, die nur ein paar KB groß sind, einfach aus Spass
an der Freude.
Meine Patterns würden Bitweise geschrieben und gelesen, deshalb weiss ich nicht wie MikiMod damit
zurecht käme. Ich möchte mich auch nicht vor GLX.12 verschliessen, aber die Frage wäre, ob mein
Player vielleicht sogar schneller wäre, weil er ein paar Dinge ziemlich simpel abhandelt ohne MUL
oder Tabellen, und nur Tonhöhen- und Lautstärkenänderung je Zeile(!), und nicht 50x in der Sekunde.
Letztlich... geht es mir weniger darum auf möglichst schnellem Weg irgendwie Trackermusik
in eine Anwendung zu bringen. Dann hätte ich mir längst irgendeine der zig Libs aus dem
Netz geholt. Vielmehr möchte ich mein eigenes Format machen. Eben weil es so abgespeckt ist
und nur das kann, was man in 90% der Fälle braucht, und aber genau in diesem Bereich auch
100% gefordert wird. Das coden (zumindest DOS wie anno dazumal) ist eben eine Hobbysache,
selber basteln, eigene Ideen umsetzen, auch wenn andere vorher schon bessere hatten und
umgesetzt haben.
Zu der Patternkompression kann ich konkret noch schreiben... nächstes mal.
Die könnte allerdings interessant sein, weil so in Echtzeit meines Wissens noch nirgends impementiert.
Übrigens scheint beim MOD Format bei den Patterns bei 64 oder 63 Stück Ende zu sein.
Ist mir früher bei nem MOD-Editor mal passiert, irgendwo bei einem 12-Minütigen Stück.
Wäre doch viel schöner wenn man einem Spiel mal einen richtig ausführlichen 60 Minütigen
Soundtrack verpassen könnte.
Ich kenne den GLX2.12 nicht, weiss nicht welche Rechenzeiten er verschlingt und wieviel von mir
ungenutze Funktionen er unterstützt.
Ich programmiere mit Borland Pascal 7 mit integriertem Assembler.
Da ich mich damit sowieso für heutige Verhältnisse in einem längst überholten Gebiet bewege denke ich,
dass es auch nicht schlimm wäre ebenso eigene Wege zu gehen, das Rad neu zu erfinden, statt auf eine
fertige LIB zuzugreifen oder diese um ein Format zu ergänzen. Ich glaube der Overhead dieser Library
wäre größer als wenn ich mein Format einfach selbst code.
Ich habe erst lange gehadert wegen Komprimierung der Samples und einiges ausprobiert, aber
in allen Fällen waren die Ergebnisse enttäuschend und so habe ich mich auch zugunsten der Rechenleistung
dafür entschieden die Samples nicht zu komprimieren.
Mit der Größe der Patterndaten vs. Samplegrößen muss man sehen.
In der heutigen Zeit wo man mit einem Tracker direkt auf hochfeine CD-Qualität abzielt, hat man
in so einem Modul gerne mal mehrere MB an Sampledaten, und dann sind die Patterndaten natürlich
vernachlässigbar.
Allerdings habe ich das Trackern in einer Zeit angefangen in der ich nur den konventionellen RAM
oder die Standard 256 KB der Gravis Ultrasound zur Verfügung hatte - und habe trotzdem gut klingende
Sachen hinbekommen.
Mit ein bisschen Erfahrung kann man die Größe der Sampledaten gut in Schach halten, beispielsweise
durch angemessene Sample-Rates, ich brauche für ein tiefes Bass-Sample nicht unbedingt 22 kHz.
Zudem würde mein Converter das ganze Stück einmal durchspielen und überprüfen, ob manche
Samples gar nicht ganz bis zum Ende gespielt würden, und dann am Ende entsprechend viel abschneiden,
ausserdem bei Samples mit "Nullen" am Ende auch diese entfernen, selbstverständlich auch bei
Samples mit Schleifen hinter dem Schleifenende abschneiden.
Legen wir den Gesamtspeicherplatz der Samples für ein Rechenexempel einfach mal auf 64 KB fest.
Das ist durchaus möglich und es gibt genug MODs mit so wenig Samplespeicherplatz, allerdings
bemerkt man das seltener weil die Dateien durch die Patterns so aufgeblasen werden.
Weiterhin belegt ein Pattern im normalen MOD Format 1 KB (vier Kanäle, 64 Zeilen).
Ich möchte allerdings 8 Kanäle haben. Das gibts auch im MOD Format, und dort auch ganz
einfach erweitert auf die doppelte Größe, also 2 KB.
Nun bevorzuge ich zudem noch 128 Zeilen statt 64 Zeilen, sprich, doppelte Geschwindigkeit,
um auch bequem und ohne Tricks 32tel Noten machen zu können. Das machen übrigens auch
viele mit dem MOD Format, haben dort dann aber eben auch nur wieder 64 Zeilen und müssen
deshalb einfach doppelt so viele Patterns verwenden.
Somit wären wir bei mir bei 4 KB pro Pattern, wobei aber tatsächlich ein Track-Event bei
mir nur 2 Byte belegt, somit dann doch wieder 2 KB pro Pattern.
Aber nehmen wir an, ein Pattern dauert 8 Sekunden, dann wären wir bei einem 3-Minuten
Song bei dem nicht nur im Sequencer immer nur ein paar gleiche Patterns gewechselt
werden bei 22 Patterns und dann 44 KB. Das gegenüber 64 KB Sampledaten tut schon etwas weh.
Vor allem weil ich für ein Spiel es eher so machen würde dass ich auch gerne mal längere
Stücke schreiben würde.
Nur einmal ein konkretes Beispiel anhand eines vielleicht bekannten Moduls, TURRICAN.MOD
bzw. "turrican-ii-remix". Das Stück hat über 50 Patterns voller Redundanzen und belegt
im Pattern-Teil etwa 59 KB. Die Samples belegen etwa 88 KB. Gesamtgröße 148 KB.
Und das sind jetzt nur vier Kanäle. Bei 8 Kanälen wären die Patterndaten, unkomprimiert,
größer als die Samples. Man könnte mit guter Patternkompression das Musikstück auf
unter 100 KB halten. Wobei ich zugeben muss, dass so ein MOD natürlich voll von Effekten
ist, die ich gar nicht (mehr) verwende, und daher pro Track Event eben 4 Byte verlangt,
während ich mein Format abgespeckt habe auf insgesamt 16 Bit (6 Bit Note, 6 Bit Samplenr,
3 Bit Volume, 1 Bit ob das Sample neu angeschlagen wird).
Wir reden hier also in einer anderen Dimension. Ich programmiere für den Real Mode, die unteren 640 KB,
von denen vielleicht eher nur 550 nutzbar sind. Und da möchte ich Grafik und Sound und natürlich auch
Code reinbekommen. Da kann ich es mir nicht immer leisten eine MOD Datei reinzuschmeissen die 400KB groß
ist und bei der das Verhältnis von Patterndaten zu Sampledaten tatsächlich 1:10 ist.
Eher möchte ich die Sampledaten unter 100 KB halten, und dann wäre es schön wenn die Patterndaten
unter 10 KB bleiben, auch bei ziemlich langen Stücken.
Noch mehr, für kurze oder langsame Stücke die Möglichkeit offenhalten, durch die Nutzung von Schleifen
d.h. sehr kurzen Samples auch mal Module machen, die nur ein paar KB groß sind, einfach aus Spass
an der Freude.
Meine Patterns würden Bitweise geschrieben und gelesen, deshalb weiss ich nicht wie MikiMod damit
zurecht käme. Ich möchte mich auch nicht vor GLX.12 verschliessen, aber die Frage wäre, ob mein
Player vielleicht sogar schneller wäre, weil er ein paar Dinge ziemlich simpel abhandelt ohne MUL
oder Tabellen, und nur Tonhöhen- und Lautstärkenänderung je Zeile(!), und nicht 50x in der Sekunde.
Letztlich... geht es mir weniger darum auf möglichst schnellem Weg irgendwie Trackermusik
in eine Anwendung zu bringen. Dann hätte ich mir längst irgendeine der zig Libs aus dem
Netz geholt. Vielmehr möchte ich mein eigenes Format machen. Eben weil es so abgespeckt ist
und nur das kann, was man in 90% der Fälle braucht, und aber genau in diesem Bereich auch
100% gefordert wird. Das coden (zumindest DOS wie anno dazumal) ist eben eine Hobbysache,
selber basteln, eigene Ideen umsetzen, auch wenn andere vorher schon bessere hatten und
umgesetzt haben.
Zu der Patternkompression kann ich konkret noch schreiben... nächstes mal.
Die könnte allerdings interessant sein, weil so in Echtzeit meines Wissens noch nirgends impementiert.
Übrigens scheint beim MOD Format bei den Patterns bei 64 oder 63 Stück Ende zu sein.
Ist mir früher bei nem MOD-Editor mal passiert, irgendwo bei einem 12-Minütigen Stück.
Wäre doch viel schöner wenn man einem Spiel mal einen richtig ausführlichen 60 Minütigen
Soundtrack verpassen könnte.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Ok, ich verstehe. Im Real-Mode ist der Speicher natürlich knapp. Lustigerweise habe ich das Turrican.mod, von dem du gesprochen hast, sogar hier. Ich habe mal die Samples mal rausgeschnitten und den Rest, der ja überwiegend Patterndaten enthält, mit einem simplen LZ-Algorithmus (Routine zur Dekomprimierung ca. 40 Codezeilen) komprimiert. Ergebis: von 62571 Bytes auf 6649. Wenn man jedes Pattern einzeln komprimiert, kommt man vielleicht auf 10 KB. Die Dekomprimierung hat memcopy-Geschwindigkeit. Wäre das keine Alternative? Der GLX scheint ziemlich schlank zu sein, allerdings habe ich den Sourcecode dafür nicht gefunden. Aber in einen schlanken Mod-Player einfach einen LZ-Dekomprimierer für die Pattern-Daten einbauen hört sich nach x-mal weniger Arbeit an als das, was du dir vorgenommen hast.
Ich weiß, dass es reizvoll ist etwas von Grund auf neu zu bauen um sich um jedes Bit Gedanken zu machen. Aber man sollte immer abwägen, ob man seine Zeit nicht an anderer Stelle sinnvoller einsetzen kann.
Ich weiß, dass es reizvoll ist etwas von Grund auf neu zu bauen um sich um jedes Bit Gedanken zu machen. Aber man sollte immer abwägen, ob man seine Zeit nicht an anderer Stelle sinnvoller einsetzen kann.
-
- DOS-Gott
- Beiträge: 2347
- Registriert: Mo 8. Feb 2010, 16:59
Re: Trackermodul-Engine (sehr einfach)
Ich habe mal gelesen dass der GLX selbstmodifizierenden Code enthält.
Ich glaube der GLX war auch die Basis für den Player in 8088MPH.
Villeicht einfach mal bei dem Soundprogger fragen:
http://www.reenigne.org/blog/8088-pc-sp ... -its-done/
Ich glaube der GLX war auch die Basis für den Player in 8088MPH.
Villeicht einfach mal bei dem Soundprogger fragen:
http://www.reenigne.org/blog/8088-pc-sp ... -its-done/
CPU: 486 DX2/66 MOBO: SNI-D882 RAM: 3x16MB - FDD: 3,5" 1,44MB HDD: 6,4GB Seagate ISA(1): Audican32Plus PCI(1): 3com TX 905 OS: MsDos622 - Win95a - WinNT 3.51
Re: Trackermodul-Engine (sehr einfach)
Programmieren an sich ist für mich nur ein Hobby so nebenbei. Richtig sinnvoll ist es so gesehen nicht,
wenn ich mich dem widme, ich habe noch andere Bereiche, die mir sehr viel mehr bedeuten.
Umso mehr macht es für mich Sinn, wenn ich dann doch eben von null auf meinen eigenen Kram realisiere,
das macht es ja alles gerade aus, dass ich alles, was ich selber programmieren kann, auch selber
programmiere und dabei keine "Abkürzungen" nehme durch vorgefertigte Lösungen...
Immerhin macht es mir ja gerade Spass, mein eigenes Format zu "erfinden", und dann will ich auch
den Player dafür selbst schreiben, der eben genauso abgespeckt ist wie das Format selbst.
Das gibt mir so ein bisschen das Gefühl doch noch etwas neues geschaffen zu haben.
Wenn ich programmiere ist das nicht nur zielorientiert, sondern ich möchte auch Erfahrungen
gemacht haben.
LZ-Dekompression... Gut, wenn es memcopy Geschwindigkeit hat, das hört sich ja ersteinmal gut an.
Ich hatte bisher das Auslesen der Patterns aber bewusst zeilenweise angedacht, um eventuelle
Verzögerungen beim Patternsprung zu vermeiden.
Siehe meine Idee zur Kompression in späteren Posts...
Wenn der GLX selbstmodifizierenden Code enthält frage ich mich, wie das dann mit meinem
Programm zusammen funktioniert. Zumindest würde ich den Player wohl gar nicht verstehen,
und darum geht es mir auch nicht zuletzt, dass ich verstanden habe was mein Programm macht.
wenn ich mich dem widme, ich habe noch andere Bereiche, die mir sehr viel mehr bedeuten.
Umso mehr macht es für mich Sinn, wenn ich dann doch eben von null auf meinen eigenen Kram realisiere,
das macht es ja alles gerade aus, dass ich alles, was ich selber programmieren kann, auch selber
programmiere und dabei keine "Abkürzungen" nehme durch vorgefertigte Lösungen...
Immerhin macht es mir ja gerade Spass, mein eigenes Format zu "erfinden", und dann will ich auch
den Player dafür selbst schreiben, der eben genauso abgespeckt ist wie das Format selbst.
Das gibt mir so ein bisschen das Gefühl doch noch etwas neues geschaffen zu haben.
Wenn ich programmiere ist das nicht nur zielorientiert, sondern ich möchte auch Erfahrungen
gemacht haben.
LZ-Dekompression... Gut, wenn es memcopy Geschwindigkeit hat, das hört sich ja ersteinmal gut an.
Ich hatte bisher das Auslesen der Patterns aber bewusst zeilenweise angedacht, um eventuelle
Verzögerungen beim Patternsprung zu vermeiden.
Siehe meine Idee zur Kompression in späteren Posts...
Wenn der GLX selbstmodifizierenden Code enthält frage ich mich, wie das dann mit meinem
Programm zusammen funktioniert. Zumindest würde ich den Player wohl gar nicht verstehen,
und darum geht es mir auch nicht zuletzt, dass ich verstanden habe was mein Programm macht.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Hier mal die Grundidee zur Kompression:
Und zwar Patternweise:
Während des Abspielens werden pro Track neue
Events in eine Liste eingespeist, wenn diese
noch nicht darin vorkommen (das entscheidet vorher
der Converter). Die Repräsentation dieser Events
muss dann jeweils nur so viel Bit haben wie die
Listenlänge zur Laufzeit es erfordert.
Gibt es nur ein Element in der Liste, so muss
nicht weiter entschieden werden, und der Vorgang
ist mit dem einleitenden Bit mit dem Wert 0,
für "kein neuer Eintrag sondern aus der Liste holen"
abgeschlossen. Ein einleitendes Bit mit dem Wert 1
bedeutet, dass ein neues Element der Liste hinzugefügt
wird und die Daten folgen. Bei selbigen kann vorher
noch geflagt werden, welche Informationen enthalten sind
und tatsächlich folgen (Note, Volume, Samplenummer ...)
Ist in der Liste mehr als ein Eintrag und es soll einer
rausgeholt werden, dann kommt ein 0 Bit und entsprechend
der Länge der Liste zur Laufzeit viele Bits zur Indizierung.
Heisst also konkret, wenn ich einen Track habe in dem z.B. nur eine Hihat läuft,
dann ist diese nach einmaligem Einspeisen für alle folgende Vorkommen mit einem einzigen 0 Bit definiert.
Habe ich einen Track mit einer simplen Melodie, die z.B. aus vier Tönen besteht, so werden diese
am Ende mit einem einleitenden 0 Bit und lediglich 2 weiteren Bit abgedeckt, die jeweiligen Töne
müssen nur jeweils ein einziges mal in ihrer vollen Definition eingelesen werden.
Solange diese Melodie im Pattern aber aus nicht mehr als 2 Tönen besteht
reicht auch 1 Bit nach dem einleitenden 0 Bit.
Ich Weiss nicht ob ihr das ganz verstanden habt, ich selber muss auch noch dran
feilen, und bisher beschränkt die Idee sich auf einen Kanal - wie ich effektiv
definiere dass benachbarte Kanäle gerade nichts beinhalten oder doch, und
wie ich am effektivsten ganze Leerzeilen definiere ist auch noch nicht klar.
Dennoch funktioniert das ganze Zeilenweise, der Player muss nur acht
Tabellen parallel halten, bei denen 32 Einträge für alle möglichen unterschiedlichen
Events auf einem Track eigentlich reichen müssten.
Je nachdem könnte man es noch auseinanderdröseln, bspw. ändert sich
oft die Samplenummer gar nicht, die könnte man getrennt behandeln -
mein ursprünglicher Ansatz war es ja, Samplenummer, Note, Lautstärke
und ob ein Sample neu gestartet wird getrennt zu behandeln und nur
zu vermerken wenn sich etwas änderte. O.g. Methode scheint mir aber
effektiver - angenommen wir haben eine Bassline die aus zwei sich
abwechselnden Tönen besteht - mein alter Algorhyhthmus hätte jede
Note mit 6 Bit gespeichert - Mein neuer geht hin, speichert die zwei
Noten und macht sie über 1 Bit (+ ein einleitendes 0 Bit) abrufbar.
Oh, da kommt mir eine Idee! Beim Einspeisen in die Tabelle wird nur
geflagt und entsprechende Daten vorgelegt die sich gegenüber des
letzten Events geändert haben. Damit wäre die Sache mit der Samplenummer
fein raus. Ich muss nur noch überlegen ob sich das auf das letzte in
die Tabelle hinzugefügte Event beziehen muss oder auf das was gerade
gespielt wird.
Hört sich vielleicht alles etwas kompliziert an, aber ist gar nicht so schwer umzusetzen,
während LZ bzw. Huffman für mich immer noch ein Buch mit sieben Siegeln ist...
Denkbar wäre allerdings noch, diese Art der Kompression nochmals mit LZ zu komprimieren,
da sich hier schon eine Rate von etwa 1:10 ergeben dürfte, und LZ da vielleicht noch
mehr rausholt weil es schon einigermaßen intelligent gestaffelt ist.
Denn wenn LZ so schnell ist wie memcopy, und meine selbst komprimierten Patterns
jeweils sagen wir nur 200 Byte groß sind, LZ dann 50 draus macht, dann denke ich
laufe ich nicht Gefahr, dass es beim LZ-Laden eines Patterns hakt.
Allerdings ist wohl eher anzunehmen, weil ich meine Patterns ja Bitweise schreibe,
dass LZ da auch nichts komprimierbares mehr sieht. Aber sieht man ja dann.
Und zwar Patternweise:
Während des Abspielens werden pro Track neue
Events in eine Liste eingespeist, wenn diese
noch nicht darin vorkommen (das entscheidet vorher
der Converter). Die Repräsentation dieser Events
muss dann jeweils nur so viel Bit haben wie die
Listenlänge zur Laufzeit es erfordert.
Gibt es nur ein Element in der Liste, so muss
nicht weiter entschieden werden, und der Vorgang
ist mit dem einleitenden Bit mit dem Wert 0,
für "kein neuer Eintrag sondern aus der Liste holen"
abgeschlossen. Ein einleitendes Bit mit dem Wert 1
bedeutet, dass ein neues Element der Liste hinzugefügt
wird und die Daten folgen. Bei selbigen kann vorher
noch geflagt werden, welche Informationen enthalten sind
und tatsächlich folgen (Note, Volume, Samplenummer ...)
Ist in der Liste mehr als ein Eintrag und es soll einer
rausgeholt werden, dann kommt ein 0 Bit und entsprechend
der Länge der Liste zur Laufzeit viele Bits zur Indizierung.
Heisst also konkret, wenn ich einen Track habe in dem z.B. nur eine Hihat läuft,
dann ist diese nach einmaligem Einspeisen für alle folgende Vorkommen mit einem einzigen 0 Bit definiert.
Habe ich einen Track mit einer simplen Melodie, die z.B. aus vier Tönen besteht, so werden diese
am Ende mit einem einleitenden 0 Bit und lediglich 2 weiteren Bit abgedeckt, die jeweiligen Töne
müssen nur jeweils ein einziges mal in ihrer vollen Definition eingelesen werden.
Solange diese Melodie im Pattern aber aus nicht mehr als 2 Tönen besteht
reicht auch 1 Bit nach dem einleitenden 0 Bit.
Ich Weiss nicht ob ihr das ganz verstanden habt, ich selber muss auch noch dran
feilen, und bisher beschränkt die Idee sich auf einen Kanal - wie ich effektiv
definiere dass benachbarte Kanäle gerade nichts beinhalten oder doch, und
wie ich am effektivsten ganze Leerzeilen definiere ist auch noch nicht klar.
Dennoch funktioniert das ganze Zeilenweise, der Player muss nur acht
Tabellen parallel halten, bei denen 32 Einträge für alle möglichen unterschiedlichen
Events auf einem Track eigentlich reichen müssten.
Je nachdem könnte man es noch auseinanderdröseln, bspw. ändert sich
oft die Samplenummer gar nicht, die könnte man getrennt behandeln -
mein ursprünglicher Ansatz war es ja, Samplenummer, Note, Lautstärke
und ob ein Sample neu gestartet wird getrennt zu behandeln und nur
zu vermerken wenn sich etwas änderte. O.g. Methode scheint mir aber
effektiver - angenommen wir haben eine Bassline die aus zwei sich
abwechselnden Tönen besteht - mein alter Algorhyhthmus hätte jede
Note mit 6 Bit gespeichert - Mein neuer geht hin, speichert die zwei
Noten und macht sie über 1 Bit (+ ein einleitendes 0 Bit) abrufbar.
Oh, da kommt mir eine Idee! Beim Einspeisen in die Tabelle wird nur
geflagt und entsprechende Daten vorgelegt die sich gegenüber des
letzten Events geändert haben. Damit wäre die Sache mit der Samplenummer
fein raus. Ich muss nur noch überlegen ob sich das auf das letzte in
die Tabelle hinzugefügte Event beziehen muss oder auf das was gerade
gespielt wird.
Hört sich vielleicht alles etwas kompliziert an, aber ist gar nicht so schwer umzusetzen,
während LZ bzw. Huffman für mich immer noch ein Buch mit sieben Siegeln ist...
Denkbar wäre allerdings noch, diese Art der Kompression nochmals mit LZ zu komprimieren,
da sich hier schon eine Rate von etwa 1:10 ergeben dürfte, und LZ da vielleicht noch
mehr rausholt weil es schon einigermaßen intelligent gestaffelt ist.
Denn wenn LZ so schnell ist wie memcopy, und meine selbst komprimierten Patterns
jeweils sagen wir nur 200 Byte groß sind, LZ dann 50 draus macht, dann denke ich
laufe ich nicht Gefahr, dass es beim LZ-Laden eines Patterns hakt.
Allerdings ist wohl eher anzunehmen, weil ich meine Patterns ja Bitweise schreibe,
dass LZ da auch nichts komprimierbares mehr sieht. Aber sieht man ja dann.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Bei deiner Datenrepräsentation bringen LZ oder Huffman nichts mehr. Im Prinzipzatzen hat geschrieben:Hier mal die Grundidee zur Kompression:
Hört sich vielleicht alles etwas kompliziert an, aber ist gar nicht so schwer umzusetzen,
während LZ bzw. Huffman für mich immer noch ein Buch mit sieben Siegeln ist...
Denkbar wäre allerdings noch, diese Art der Kompression nochmals mit LZ zu komprimieren,
da sich hier schon eine Rate von etwa 1:10 ergeben dürfte, und LZ da vielleicht noch
mehr rausholt weil es schon einigermaßen intelligent gestaffelt ist.
Denn wenn LZ so schnell ist wie memcopy, und meine selbst komprimierten Patterns
jeweils sagen wir nur 200 Byte groß sind, LZ dann 50 draus macht, dann denke ich
laufe ich nicht Gefahr, dass es beim LZ-Laden eines Patterns hakt.
Allerdings ist wohl eher anzunehmen, weil ich meine Patterns ja Bitweise schreibe,
dass LZ da auch nichts komprimierbares mehr sieht. Aber sieht man ja dann.
machst du ja so etwas wie Huffman: häufige Bytes durch wenige Bits darstellen und
umgekehrt seltene Bytes durch mehr Bits. Wenn du lernen möchtest, wie LZ funktioniert,
einfach mal nach "LZ Crush Source" googeln - supersimpler, aber sehr effektiver
Algorithmus und dazu noch public domain.
Ich rate dir nach wie vor zu einfacheren Lösungen. Deine Idee ist viel Bitgefrickel,
schwer zu debuggen, schwer zu testen und im Anschluss schwer erweiterbar. Aber wenn
diese Art von Programmieren dir Spaß macht - und das soll es ja geben - dann ist alles
gut.

Re: Trackermodul-Engine (sehr einfach)
Ich sehe es absolut ein dass LZ aufgrund der Einfachheit vernünftiger wäre, bin mir allerdings nicht sicher ob
es wirklich effektiver ist als was ich vor habe. Ich kombiniere ja, nur relative Änderungen speichern,
und Streichung der Redundanzen. Und dabei nur so viele Bits verwenden wie gerade im jeweiligen
Track benötigt, aufgrund der Anzahl der Tabelleneinträge. Das ist ein anderer Ansatz, als häufig
vorkommende Elemente mit wenigen, und selten vorkommende mit vielen Bits zu speichern.
Ich finde diese Idee zu interessant, um sie unversucht zu lassen.
Ein Bitgefrickel wäre es allerdings nicht, ich würde mir einfach zwei Routinen bauen:
function readbits(anzahl_bits)
procedure writebits(wert, anzahl_bits)
Die Verwaltung an welcher entsprechenden Byte-Stelle wo und wie geschrieben und gelesen wird
passiert dann automatisch, das habe ich bereits in Vorgängerversionen umgesetzt.
Zwischendrin setzt man den Pointer auf den entsprechenden Anfang des Patterns.
Benötigt LZ rekursive Routinen? Damit habe ich ein großes Problem.
Es ist wahrscheinlich kein guter Vergleich, aber vielleicht kennt ihr die Art und Weise wie die
alten Sierra Spiele die Hintergrundgrafiken gespeichert haben. Das wurde durch eine Art Programmiersprache
umgesetzt die das Bild durch Linien, Flächen etc. zusammensetzte, und der Künstler musste das Bild
auf diese Weise intelligent durchgeplant Zeichnen. Es wurde also nicht als Bitmap genommen und
dann Huffman oder RLE codiert.
es wirklich effektiver ist als was ich vor habe. Ich kombiniere ja, nur relative Änderungen speichern,
und Streichung der Redundanzen. Und dabei nur so viele Bits verwenden wie gerade im jeweiligen
Track benötigt, aufgrund der Anzahl der Tabelleneinträge. Das ist ein anderer Ansatz, als häufig
vorkommende Elemente mit wenigen, und selten vorkommende mit vielen Bits zu speichern.
Ich finde diese Idee zu interessant, um sie unversucht zu lassen.
Ein Bitgefrickel wäre es allerdings nicht, ich würde mir einfach zwei Routinen bauen:
function readbits(anzahl_bits)
procedure writebits(wert, anzahl_bits)
Die Verwaltung an welcher entsprechenden Byte-Stelle wo und wie geschrieben und gelesen wird
passiert dann automatisch, das habe ich bereits in Vorgängerversionen umgesetzt.
Zwischendrin setzt man den Pointer auf den entsprechenden Anfang des Patterns.
Benötigt LZ rekursive Routinen? Damit habe ich ein großes Problem.
Es ist wahrscheinlich kein guter Vergleich, aber vielleicht kennt ihr die Art und Weise wie die
alten Sierra Spiele die Hintergrundgrafiken gespeichert haben. Das wurde durch eine Art Programmiersprache
umgesetzt die das Bild durch Linien, Flächen etc. zusammensetzte, und der Künstler musste das Bild
auf diese Weise intelligent durchgeplant Zeichnen. Es wurde also nicht als Bitmap genommen und
dann Huffman oder RLE codiert.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Nein. Wenn Crush noch zu kompliziert ist, lohnt vielleicht ein Blick auf LZRW - ist noch einfacher.zatzen hat geschrieben: Benötigt LZ rekursive Routinen? Damit habe ich ein großes Problem.
Der Vergleich ist erstaunlich gut. Diese Art der Darstellung funktioniert für simple Grafiken, die aus wenigen Formen zusammengesetzt sind. Sobald aber schätzungsweise mehr als 50 Formen im Spiel sind, wäre LZ in jeder Hinsicht überlegen. Und dieses Verhalten deckt sich mit deinem Ansatz. Für die einfachen Tracks, die du im Kopf hast, funktioniert es wahrscheinlich sehr gut, aber sobald die Tracks komplexer werden, ist es irgendwann kontraproduktiv.zatzen hat geschrieben: Es ist wahrscheinlich kein guter Vergleich, aber vielleicht kennt ihr die Art und Weise wie die
alten Sierra Spiele die Hintergrundgrafiken gespeichert haben. Das wurde durch eine Art Programmiersprache
umgesetzt die das Bild durch Linien, Flächen etc. zusammensetzte, und der Künstler musste das Bild
auf diese Weise intelligent durchgeplant Zeichnen. Es wurde also nicht als Bitmap genommen und
dann Huffman oder RLE codiert.
Ich bin eher ein Freund des einfachen und effizienten Codes. Für mich wäre der Ansatz, der anscheinend bei den Sierra-Spielen verwendet wurde, unsinnig. Man tauscht die Ersparnis von ein paar Byte gegen höhere Codekomplexität und langsamere Ausführunggeschwindigkeit. Der Ansatz skaliert sehr schlecht und schränkt Grafiker massiv ein. Bessere Renderqualität bei unterschiedlichen Auflösungen wäre einer der wenigen Punkte auf der "PRO"-Seite gewesen, aber ich kann mich nicht daran erinnern, dass Sierra-Spiele SVGA-Auflösungen angeboten haben.
Re: Trackermodul-Engine (sehr einfach)
Mein Problem ist auch die mangelnde Motivation, mich in eine Art von
Kompression einzulesen und mir eine selbstgeschriebene Implementierung
zu erarbeiten, also nicht nur eine "fremde" Pascal Unit dafür zu benutzen,
wenn ich eigentlich selber eine Idee zur Kompression habe, deren Ausarbeitung
mir Spass macht als kleine Knobelei, so wie vielen Leuten Kreuzworträtsel.
Auch wenn es pragmatisch kein Problem wäre ist es mir zuwider, beim Dekodieren
den Weg über ein komplettes entpacktes Pattern zu nehmen, ich möchte das
lieber Zeilenweise erledigen, auch wenn es im Verhätnis langsamer ist, dennoch
höchstwahrscheinlich während der Laufzeit schnell genug, weil ein Zeilensprung
im Pattern ja mit einer eher niedrigen Frequenz erfolgt.
Auf jeden Fall habe ich mich aufgrund eurer Anregung nochmals mit LZ77 und auch
Huffmann beschäftigt. Diese verstehe ich vom Prinzip her, sehe das Standard LZ77
aber als zu uneffektiv (Pointer wären etwa so groß wie die Patternevents selbst),
und Huffman hatte ich auch vor einiger Zeit durchgespielt und befunden dass
die Kompression zwar funktioniert, aber doch enttäuschend ist.
Sicherlich, weiterentwickelte Varianten dieser theoretischen Grundprinzipien
wären effektiv, aber das würde dann wieder den Rahmen meiner Motivation sprengen
Zeit da hinein zu investieren. Stattdessen denke ich mir selber etwas maßgeschneidertes
für meine Patterns aus, deren Wesen ich sehr gut kenne nach 22 Jahren Trackermusik
machen.
Übrigens noch zum Beispiel Grafik. Im entsprechenden Thread stelle ich mein
Format "ZVID" vor. Weil ich der LZ Kompression nicht mächtig war/bin und auch
dachte, das wäre nicht schnell genug, habe ich dieses Format ausgeklügelt.
Es ist verlustfrei, Details im Thread wie gesagt. Was ich nur sagen möchte:
Es kommt nicht ganz an die Effizienz von GIF, PNG oder ZIP heran. Interessant ist
aber, dass ZIP eine ZVID-Datei nochmals deutlich kleiner macht und dabei
sogar deutlich kleiner als wenn man diese Bitmaps (wohlgemerkt auch 8 Bit, hier
wie da) direkt mit ZIP komprimiert, ohne den Umweg über ZVID. Das wird daran liegen
dass ZVID die Grafiken etwas spezieller oder intelligenter komprimiert als
der für allgemeine Daten ausgelegte Algorithmus bei ZIP.
Bei Animationen ist ZVID sowohl bei der Farbzuteilung als auch was Redundanzen
angeht Bilder bzw. Sprite-übergreifend und somit ziemlich effektiv.
Wen es interessiert, hier meine aktuellen Ideen für die Patternkompression und
warum diese auch bei eher dicht beschriebenen Patterns nicht kontraproduktiv wäre.
Musik hat immer eine gewisse redundante Struktur.
Speicherung der Track-Events:
- Ein Informations-Bit:
0: Wert aus Tabelle holen, es folgen so viel Bits
wie die Bitbreite der Tabellenlänge zur Laufzeit. Bei nur einem Eintrag reicht dieses Bit allein.
1:
weiteres Bit 0:
Event kommt nur 1x vor, wird in die aktuelle Zeile geladen, aber nicht in die Liste
oder weiteres Bit 1:
Event kommt mehrfach im Track vor, wird in die aktuelle Zeile geladen und in die Liste
Die Events werden also in dem Moment, in dem sie in die Liste aufgenommen werden
auch gespielt bzw. in die Abspielzeile der Routine geschrieben. Es entsteht kein Overhead,
es ist nicht so, dass erst eine Liste erstellt wird und danach erst darauf zugegriffen wird.
Um die Liste optimal und kurz zu halten, kommen dort wirklich nur mehrfach vorkommende
Events rein. Ein wichtiger Punkt ist, dass die Idizierung der Liste, sprich die Anzahl der
erforderlichen Bits, quasi dynamisch ist und sich zur Laufzeit danach ausrichtet wie lang
die Liste ist.
Die Events selbst werden deklariert bzw. geflagt durch vier Bits:
je 1 Bit für: Notenwert vorhanden, Laustärke vorhanden,
Samplenummer vorhanden, "Trigger" vorhanden.
Das kommt von daher, dass ich nur tatsächliche Änderungen eines Events speichere.
Angenommen ich habe ein Event am Anfang des Tracks:
C-3 Vol7 S#13
und das nächste wäre
D-3 Vol7 S#13
Dann brauche ich nur den Notenwert neu zu speichern und spare 9 Bit oder 10,
je nachdem ob ich doch noch 127 Samples erlaube.
Natürlich sind da jetzt eben diese 4 Bit extra. Aber irgendwie muss das
ja geflagt werden. Vielleicht fällt mir noch etwas besseres ein.
Für die Information, welcher Track gerade ein Event enthält und in welchem
Track in der Zeile gerade nichts ist würde ich ein Byte gebrauchen, weil es
ja genau 8 Bit enthält.
Das würde aber bedeuten dass ein Pattern mit der Länge 128 Zeilen immer
fest 128 Byte von vornherein schon belegen würde (wenn man von Leerzeilen
absehen würde). Wenn man sich nun aber einmal die Struktur ansieht wie
die Events auftreten auf den Zeilen, dann fällt auf, dass es da gar nicht so
viele verschiedene Kombinationen gibt. Also kann ich das auch wieder per
Tabelle, ähnlich wie bei den Tracks, sehr kompakt halten.
Letztlich, was die Leerzeilen angeht, da habe ich mir zuletzt gedacht, ich hänge
an jede abgeschlossene Zeile zwei Bit ran, plus ein weiteres Bit das anzeigt
ob nochmals weitere 2 Bit folgen. Leerzeilen von 0 bis 3 können von den ersten
zwei Bit abgedeckt werden, bei mehr Leerzeilen werden die weiteren Bits gelesen,
2x nach links geshiftet und die ersten beiden dazuaddiert, so dass 15 Leerzeilen
auf diese Weise realisierbar sind.
Soweit mal die grobe Abhandlung. Wie immer klingt es in Prosa erklärt komplizierter
als es ist, tatsächlich brauche ich im späteren Programm nur ein paar Tabellen,
eine Zeile die zu befüllen ist, und ein paar Routinen die die Bits entsprechend
reinziehen, und das durch die function readbits sehr elegant, man schreibt einfach z.b.
if readbits(1) then ...
wenn ich mich recht entsinne dass boolean bei Pascal auch mit Zahlenwerten funktioniert.
Sonst eben noch ein "= 1" dranhängen.
Oder sonst soetwas wie track[number].note = readbits(6)
Der Converter ist sicherlich etwas schwieriger zu schreiben. Aber beim decodieren
sehe ich weniger das Problem.
Ich frage mich nur, warum so einige Tracker es ähnlich machen, für die Patterns
eine eher simple Kompression verwenden wie z.B. RLE Trackweise, und für die
Samples dann soetwas wie ZIP. Klar man könnte sagen, die Patterns sind sowieso
nicht so groß und brauchen daher keine so effektive Kompression, aber wenn
ein Algorithmus wie LZ so simpel und schnell ist, warum wird er nicht auch
bei den Patterns einfach angewendet, bei Trackerformaten die ähnliches wie gesagt
bei den Samples verwenden... Zumal die meisten Trackerformate die ich kenne
die gesamte Kompression eigentlich nur dafür drin haben, damit die Dateigröße
nicht Überhand nimmt, und man nicht darauf abzielt, die Pattens auch im Speicher
komprimiert zu halten.
Das mit der Patternkompression ist für mich jetzt eigentlich geklärt.
Fragen habe ich dann später wahrscheinlich nochmal, wenn es an die Programmierung
der Abspielroutinen geht.
Kompression einzulesen und mir eine selbstgeschriebene Implementierung
zu erarbeiten, also nicht nur eine "fremde" Pascal Unit dafür zu benutzen,
wenn ich eigentlich selber eine Idee zur Kompression habe, deren Ausarbeitung
mir Spass macht als kleine Knobelei, so wie vielen Leuten Kreuzworträtsel.
Auch wenn es pragmatisch kein Problem wäre ist es mir zuwider, beim Dekodieren
den Weg über ein komplettes entpacktes Pattern zu nehmen, ich möchte das
lieber Zeilenweise erledigen, auch wenn es im Verhätnis langsamer ist, dennoch
höchstwahrscheinlich während der Laufzeit schnell genug, weil ein Zeilensprung
im Pattern ja mit einer eher niedrigen Frequenz erfolgt.
Auf jeden Fall habe ich mich aufgrund eurer Anregung nochmals mit LZ77 und auch
Huffmann beschäftigt. Diese verstehe ich vom Prinzip her, sehe das Standard LZ77
aber als zu uneffektiv (Pointer wären etwa so groß wie die Patternevents selbst),
und Huffman hatte ich auch vor einiger Zeit durchgespielt und befunden dass
die Kompression zwar funktioniert, aber doch enttäuschend ist.
Sicherlich, weiterentwickelte Varianten dieser theoretischen Grundprinzipien
wären effektiv, aber das würde dann wieder den Rahmen meiner Motivation sprengen
Zeit da hinein zu investieren. Stattdessen denke ich mir selber etwas maßgeschneidertes
für meine Patterns aus, deren Wesen ich sehr gut kenne nach 22 Jahren Trackermusik
machen.
Übrigens noch zum Beispiel Grafik. Im entsprechenden Thread stelle ich mein
Format "ZVID" vor. Weil ich der LZ Kompression nicht mächtig war/bin und auch
dachte, das wäre nicht schnell genug, habe ich dieses Format ausgeklügelt.
Es ist verlustfrei, Details im Thread wie gesagt. Was ich nur sagen möchte:
Es kommt nicht ganz an die Effizienz von GIF, PNG oder ZIP heran. Interessant ist
aber, dass ZIP eine ZVID-Datei nochmals deutlich kleiner macht und dabei
sogar deutlich kleiner als wenn man diese Bitmaps (wohlgemerkt auch 8 Bit, hier
wie da) direkt mit ZIP komprimiert, ohne den Umweg über ZVID. Das wird daran liegen
dass ZVID die Grafiken etwas spezieller oder intelligenter komprimiert als
der für allgemeine Daten ausgelegte Algorithmus bei ZIP.
Bei Animationen ist ZVID sowohl bei der Farbzuteilung als auch was Redundanzen
angeht Bilder bzw. Sprite-übergreifend und somit ziemlich effektiv.
Wen es interessiert, hier meine aktuellen Ideen für die Patternkompression und
warum diese auch bei eher dicht beschriebenen Patterns nicht kontraproduktiv wäre.
Musik hat immer eine gewisse redundante Struktur.
Speicherung der Track-Events:
- Ein Informations-Bit:
0: Wert aus Tabelle holen, es folgen so viel Bits
wie die Bitbreite der Tabellenlänge zur Laufzeit. Bei nur einem Eintrag reicht dieses Bit allein.
1:
weiteres Bit 0:
Event kommt nur 1x vor, wird in die aktuelle Zeile geladen, aber nicht in die Liste
oder weiteres Bit 1:
Event kommt mehrfach im Track vor, wird in die aktuelle Zeile geladen und in die Liste
Die Events werden also in dem Moment, in dem sie in die Liste aufgenommen werden
auch gespielt bzw. in die Abspielzeile der Routine geschrieben. Es entsteht kein Overhead,
es ist nicht so, dass erst eine Liste erstellt wird und danach erst darauf zugegriffen wird.
Um die Liste optimal und kurz zu halten, kommen dort wirklich nur mehrfach vorkommende
Events rein. Ein wichtiger Punkt ist, dass die Idizierung der Liste, sprich die Anzahl der
erforderlichen Bits, quasi dynamisch ist und sich zur Laufzeit danach ausrichtet wie lang
die Liste ist.
Die Events selbst werden deklariert bzw. geflagt durch vier Bits:
je 1 Bit für: Notenwert vorhanden, Laustärke vorhanden,
Samplenummer vorhanden, "Trigger" vorhanden.
Das kommt von daher, dass ich nur tatsächliche Änderungen eines Events speichere.
Angenommen ich habe ein Event am Anfang des Tracks:
C-3 Vol7 S#13
und das nächste wäre
D-3 Vol7 S#13
Dann brauche ich nur den Notenwert neu zu speichern und spare 9 Bit oder 10,
je nachdem ob ich doch noch 127 Samples erlaube.
Natürlich sind da jetzt eben diese 4 Bit extra. Aber irgendwie muss das
ja geflagt werden. Vielleicht fällt mir noch etwas besseres ein.
Für die Information, welcher Track gerade ein Event enthält und in welchem
Track in der Zeile gerade nichts ist würde ich ein Byte gebrauchen, weil es
ja genau 8 Bit enthält.
Das würde aber bedeuten dass ein Pattern mit der Länge 128 Zeilen immer
fest 128 Byte von vornherein schon belegen würde (wenn man von Leerzeilen
absehen würde). Wenn man sich nun aber einmal die Struktur ansieht wie
die Events auftreten auf den Zeilen, dann fällt auf, dass es da gar nicht so
viele verschiedene Kombinationen gibt. Also kann ich das auch wieder per
Tabelle, ähnlich wie bei den Tracks, sehr kompakt halten.
Letztlich, was die Leerzeilen angeht, da habe ich mir zuletzt gedacht, ich hänge
an jede abgeschlossene Zeile zwei Bit ran, plus ein weiteres Bit das anzeigt
ob nochmals weitere 2 Bit folgen. Leerzeilen von 0 bis 3 können von den ersten
zwei Bit abgedeckt werden, bei mehr Leerzeilen werden die weiteren Bits gelesen,
2x nach links geshiftet und die ersten beiden dazuaddiert, so dass 15 Leerzeilen
auf diese Weise realisierbar sind.
Soweit mal die grobe Abhandlung. Wie immer klingt es in Prosa erklärt komplizierter
als es ist, tatsächlich brauche ich im späteren Programm nur ein paar Tabellen,
eine Zeile die zu befüllen ist, und ein paar Routinen die die Bits entsprechend
reinziehen, und das durch die function readbits sehr elegant, man schreibt einfach z.b.
if readbits(1) then ...
wenn ich mich recht entsinne dass boolean bei Pascal auch mit Zahlenwerten funktioniert.
Sonst eben noch ein "= 1" dranhängen.
Oder sonst soetwas wie track[number].note = readbits(6)
Der Converter ist sicherlich etwas schwieriger zu schreiben. Aber beim decodieren
sehe ich weniger das Problem.
Ich frage mich nur, warum so einige Tracker es ähnlich machen, für die Patterns
eine eher simple Kompression verwenden wie z.B. RLE Trackweise, und für die
Samples dann soetwas wie ZIP. Klar man könnte sagen, die Patterns sind sowieso
nicht so groß und brauchen daher keine so effektive Kompression, aber wenn
ein Algorithmus wie LZ so simpel und schnell ist, warum wird er nicht auch
bei den Patterns einfach angewendet, bei Trackerformaten die ähnliches wie gesagt
bei den Samples verwenden... Zumal die meisten Trackerformate die ich kenne
die gesamte Kompression eigentlich nur dafür drin haben, damit die Dateigröße
nicht Überhand nimmt, und man nicht darauf abzielt, die Pattens auch im Speicher
komprimiert zu halten.
Das mit der Patternkompression ist für mich jetzt eigentlich geklärt.
Fragen habe ich dann später wahrscheinlich nochmal, wenn es an die Programmierung
der Abspielroutinen geht.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Diese Art der Speicherung erinnert mich zumindest teilweise an die Speicherung von GIF-Bildern.
Re: Trackermodul-Engine (sehr einfach)
Es gäbe noch einen weiteren Ansatz der auf den ersten Blick vielleicht sinnvoller und weniger kompliziert
erscheint. Man scannt alle Tracks der Patterns 8-Zeilen-Blockweise durch und legt eine Datenbank an wobei
man die Redundanzen rausnimmt. Das kann je nach Musikstück sehr effizient sein, allerdings müssten
die Patterns dann alle 8 Zeilen Verweise auf die Datenbank bekommen.
Die Datenbank selbst könnte nochmal komprimiert werden durch vorangendende Bytes mit deren Bits
geflagt wird wo in so einem 8er Block Leerzeilen auftreten.
Auch bei den Patterns muss nicht jede 8te Zeile direkt 8 Verweise haben,
also nicht immer auf jedem Track, es könnten ja auch Leerzeilen gleich
oder länger als 8 auftreten oder gar ein ganzer Track nicht belegt sein.
Gut würde dieses Verfahren also für Musik mit vielen Wiederholungen und Redundanz funktionieren.
Aber man hätte immer die Datenbank plus die rund 150 durchschnittlichen Byte eines Patterns (hatte
ich mal so überschlägig geschätzt).
Man kann es so rechnen, angenommen ich brauche 12 Bit alle 8 Zeilen in einem Track, um einen
Track aus entsprechend indizierten Mustern aus der Datenbank zusammenzubauen.
Dann sind das bei 128 Zeilen 24 Byte.
24 Byte ist aber gleichzeitig schon eine Größe, die eher oberhalb von dem liegt, was einem die
vorher besprochene Kompressionsvariante mit der "Bitfrickelei" durchschnittlich einbringt.
Und die braucht dann zudem auch keine Datenbank.
Da setze ich lieber doch die Bit-spar-Variante der Kompression ein, um mit gutem Gewissen auch Musik
machen zu können, bei der kein Pattern dem anderen gleicht und somit mehr Abwechslung drin ist.
erscheint. Man scannt alle Tracks der Patterns 8-Zeilen-Blockweise durch und legt eine Datenbank an wobei
man die Redundanzen rausnimmt. Das kann je nach Musikstück sehr effizient sein, allerdings müssten
die Patterns dann alle 8 Zeilen Verweise auf die Datenbank bekommen.
Die Datenbank selbst könnte nochmal komprimiert werden durch vorangendende Bytes mit deren Bits
geflagt wird wo in so einem 8er Block Leerzeilen auftreten.
Auch bei den Patterns muss nicht jede 8te Zeile direkt 8 Verweise haben,
also nicht immer auf jedem Track, es könnten ja auch Leerzeilen gleich
oder länger als 8 auftreten oder gar ein ganzer Track nicht belegt sein.
Gut würde dieses Verfahren also für Musik mit vielen Wiederholungen und Redundanz funktionieren.
Aber man hätte immer die Datenbank plus die rund 150 durchschnittlichen Byte eines Patterns (hatte
ich mal so überschlägig geschätzt).
Man kann es so rechnen, angenommen ich brauche 12 Bit alle 8 Zeilen in einem Track, um einen
Track aus entsprechend indizierten Mustern aus der Datenbank zusammenzubauen.
Dann sind das bei 128 Zeilen 24 Byte.
24 Byte ist aber gleichzeitig schon eine Größe, die eher oberhalb von dem liegt, was einem die
vorher besprochene Kompressionsvariante mit der "Bitfrickelei" durchschnittlich einbringt.
Und die braucht dann zudem auch keine Datenbank.
Da setze ich lieber doch die Bit-spar-Variante der Kompression ein, um mit gutem Gewissen auch Musik
machen zu können, bei der kein Pattern dem anderen gleicht und somit mehr Abwechslung drin ist.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;
Re: Trackermodul-Engine (sehr einfach)
Was auf jeden Fall gemacht wird ist, die Patterns durchzuscannen auf welchen Notenwerten die
jeweiligen Samples überhaupt insgesamt gespielt werden. Angenommen ich habe ein Sample
das eine nicht allzu komplexe Melodie spielt, so benutzt dieses vielleicht nicht mehr als acht
verschiedene Töne. Somit kann ich eine Tabelle anlegen die man mit 3 Bit indizieren kann.
Mehr noch, wenn ich ein Schlagzeugsample habe, das immer nur auf der gleichen Tonhöhe spielt,
dann hat die Tabelle dazu nur einen Eintrag und braucht gar keine Indizierung.
Ein Nebeneffekt davon ist noch, dass ich mich nicht mehr auf eine Spannweite von 64 Notenwerten
beschränken muss. Ausserdem wollte ich, damit man den Komfort hat, jedem Sample eine eigene
Samplerate zu geben (was fürs Tuning sehr praktisch ist), in diesen Tabellen direkt eine Schritt-
weite von WORD-Breite zur Fixkomma-Addition im Player auszurechnen und zu speichern.
wenn euch das Thema nicht interessiert kann ich auch aufhören drüber zu schreiben
und melde ich dann wieder wenn alles fertig ist oder ich zwischendrin Fragen habe die ich
selber nicht lösen kann.
jeweiligen Samples überhaupt insgesamt gespielt werden. Angenommen ich habe ein Sample
das eine nicht allzu komplexe Melodie spielt, so benutzt dieses vielleicht nicht mehr als acht
verschiedene Töne. Somit kann ich eine Tabelle anlegen die man mit 3 Bit indizieren kann.
Mehr noch, wenn ich ein Schlagzeugsample habe, das immer nur auf der gleichen Tonhöhe spielt,
dann hat die Tabelle dazu nur einen Eintrag und braucht gar keine Indizierung.
Ein Nebeneffekt davon ist noch, dass ich mich nicht mehr auf eine Spannweite von 64 Notenwerten
beschränken muss. Ausserdem wollte ich, damit man den Komfort hat, jedem Sample eine eigene
Samplerate zu geben (was fürs Tuning sehr praktisch ist), in diesen Tabellen direkt eine Schritt-
weite von WORD-Breite zur Fixkomma-Addition im Player auszurechnen und zu speichern.

und melde ich dann wieder wenn alles fertig ist oder ich zwischendrin Fragen habe die ich
selber nicht lösen kann.
mov ax, 13h
int 10h
while vorne_frei do vor;
int 10h
while vorne_frei do vor;