Dragon 32 Homecomputer Kassetten in ASCII umwandeln...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

jens hat geschrieben:Macht es eigentlich Sinn zu versuchen das ganze komplett als Generator zu bauen?

z.Z. mache ich ja noch oft ein list() um dann doch wieder eine normale Liste zu haben...
Das kommt IMHO sehr darauf an, wieviel ein (vermutlich) deutlich komplexerer Code an Zeitgewinn bringt. Du kannst es natürlich ausprobieren und Messungen machen. Meine persönliche Erfahrung ist, dass Python in Sachen Listenerstellung schneller arbeitet als man vielleicht vermuten würde.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So... um es nochmal klar zu machen, was da überhaupt passiert:

1. WAV Sinus-Kurvenlänge -> Bitstream aus Nullen und Einsen
2. suche nach Muster "10101010" (0x55) Zeichenweise
3. in 8-Bit-Schritten weiter Springen, bis das Muster "10101010" nicht mehr vorkommt.
4. Suchen des Sync-Bits "00111100" (0x3C) Zeichenweise

Ab hier könnte ich IMHO jeweils 8-Bit in einem Byte umwandeln und nur noch damit weitermachen.

5. Ersten Bytes sind: Block-Typ und Block-Länge
6. Lese Blocklänge und werte die Daten aus (in Python Objekte packen)
7. Schleife wenn Block-Typ nicht EOF-Block ist, weiter bei Punkt 2.


In der aktuellen Implementierung arbeite ich viel zu lange mit dem Bit-Stream, statt ihn nach Punkt 4 umzuwandeln.

EDIT: Werte doch die Block-Länge aus ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Das mit den drei Nullbytes ist aber nicht wirklich robust. Die können schliesslich auch innerhalb der Daten vorkommen. Selbst bei reinem BASIC ist es nicht ausgeschlossen, bei (zusätzlichen) Binärdaten aber überhaupt nicht.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:@jens: Das mit den drei Nullbytes ist aber nicht wirklich robust. Die können schliesslich auch innerhalb der Daten vorkommen. Selbst bei reinem BASIC ist es nicht ausgeschlossen, bei (zusätzlichen) Binärdaten aber überhaupt nicht.
Hab ich schon editiert. In wirklich nutzte ich die Längeninformation des Blocks.

Die drei 0x0 kommen im BASIC-Code selbst vor: Ein 0x0 ist das Zeilenende Zeichen und dann nochmal zwei 0x0 markieren das Ende des BASIC Blocks.

Eigentlich ist das IMHO doppelt gemoppelt. Also das nochmal mit zwei 0x0 das Ende markiert wird, wenn vorher schon die Länge des Blocks angegeben ist.
Keine Ahnung warum das so ist...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Ich schrob doch gerade das drei Nullbytes auch *innerhalb* der Daten vorkommen können, nicht nur am Ende, also ist das nicht doppelt gemoppelt. Selbst wenn es bei (tokenisierten) BASIC-Programmen ausgeschlossen *wäre*: Das Bandformat soll ja nicht nur BASIC-Programme speichern können und man möchte nicht `x` verschiedene Formate für `x` Dateiarten haben, die alle eine eigene Laderoutine brauchen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So ist es allerdings laut http://dragon32.info/info/basicfmt.html spezifiziert.

Dennoch ist IMHO das unnötig. Man hat die Block länge und zusätzlich das Ende markiert. Naja... Sind ja nur zwei Byte unnötige Zeichen...

EDIT: Generell muß ich mir nochmal alle Dokumente von http://dragon32.info/info/ genauer ansehen...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Es können, auch wenn man sich an diese Spezifikation hält, *innerhalb* eines BASIC-Programms drei Nullbytes vorkommen. Davon abgesehen halten die Leute sich nicht immer an Spezifikationen. Man kann zum Beispiel Binärdaten hinter einem BASIC-Programm speichern. Beim C64 war das kein Problem auf diese Weise ein BASIC-Programm zusammen mit Maschinenprogrammteilen und/oder (Grafik)Daten in einer Datei zu speichern. Solange Zeilen kein Sprungziel sind, kann man die Zeilennummer auch beliebig manipulieren. Zum Beispiel all diese Zeilen auf die Zeilennummer 0 setzen. War beliebt um Programme vor Veränderungen zu schützen.

Du vermischt hier IMHO auch zwei Ebenen. Das eine ist das Speicherformat auf dem Band und das andere ist das Speicherformat für BASIC-Programme. Die Bandroutinen sollten wie schon gesagt so entworfen sein, dass für alle Dateitypen das Laden/Lesen grundsätzlich gleich funktioniert. Damit man eben nicht für jedem Typ eigene Laderoutinen braucht. Der Laderoutine ist der Blocktyp oder der Dateityp egal, die funktioniert immer gleich.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:*innerhalb* eines BASIC-Programms drei Nullbytes vorkommen
Aber wie soll das gehen? Eigentlich darf kein einziges 0x00 vorkommen, denn das markiert ja das Ende der BASIC Zeile.
Ansonsten könnte man die Zeilen nicht richtig trennen.

Hab auch angefangen, neben dem tokenized BASIC auch ASCII BASIC zu behandeln, hier: https://github.com/jedie/PyDragon32/blo ... DC.py#L717
Der wird aber bisher nur angezeigt, mehr nicht.

Interessant dabei: Beim tokenized BASIC wird das Ende des Codes mit zwei 0x00 extra Markiert. Beim ASCII BASIC sind die Zeilen mit einem \r getrennt. Sieht so aus:
\r1 PRINT "LINE NUMBER TEST"\r
\r10 PRINT 10\r
\r100 PRINT 100\r
\r1000 PRINT 1000\r
\r10000 PRINT 10000\r
\r32768 PRINT 32768\r
\r63999 PRINT "END";63999\r
Hätte jetzt zwei \r nochmal am Ende erwartet...

Auch hier darf dann im BASIC-Code kein \r vorkommen. Keine Ahnung ob man das überhaupt einfügen kann. Aber evtl. wird es dann escaped?
BlackJack hat geschrieben:Man kann zum Beispiel Binärdaten hinter einem BASIC-Programm speichern. Beim C64 war das kein Problem auf diese Weise ein BASIC-Programm zusammen mit Maschinenprogrammteilen und/oder (Grafik)Daten in einer Datei zu speichern. Solange Zeilen kein Sprungziel sind, kann man die Zeilennummer auch beliebig manipulieren. Zum Beispiel all diese Zeilen auf die Zeilennummer 0 setzen. War beliebt um Programme vor Veränderungen zu schützen.
Ich weiß noch nicht wie man sowas machen kann und ob der Dragon das kann.
Hab zwar schon damit gerechnet, das man BASIC+Daten+BIN speichern kann, aber ich bin davon ausgegenagen, das das dann immer in einem neuen Block passiert. Es also immer mehrere Dateien sind.

Hast du info's dazu? Evtl. vom C64 und man kann das "Übertragen" ?
BlackJack hat geschrieben:Du vermischt hier IMHO auch zwei Ebenen. Das eine ist das Speicherformat auf dem Band und das andere ist das Speicherformat für BASIC-Programme. Die Bandroutinen sollten wie schon gesagt so entworfen sein, dass für alle Dateitypen das Laden/Lesen grundsätzlich gleich funktioniert. Damit man eben nicht für jedem Typ eigene Laderoutinen braucht. Der Laderoutine ist der Blocktyp oder der Dateityp egal, die funktioniert immer gleich.
Nein, ich weiß um die beiden Ebenen. Nur weiß ich nicht ob wir vom selben reden ;)

Eigentlich sind das mehrere Ebenen:
* WAVE <-> bits (Also wie die Binäredaten im Ton moduliert werden, das macht IMHO der C64 anders)
* Blockebene: Die ich mir z.Z. mit get_block_info() durchgehe: https://github.com/jedie/PyDragon32/blo ... DC.py#L543
* Block Daten: Fileinfo-Block (Dateiname, Typ, Länge usw.) / Datei-Inhalt

EDIT: Gerade das nochmal ins Auge gesprugen:
5.4 A gap flag to indicate whether the
data stream is continuous (00) as
in binary or BASIC files, or in blocks
where the tape keeps stopping (FF) as
in data files.
Ist in den Meta-Daten des Fileinfo-Blocks...

Keine Ahnung was das wirklich bedeutet. Habe festgestellt, das es pausen zwischen den Blöcken gibt. Ob das damit gemeint ist?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Natürlich dürfen noch andere Nullbytes vorkommen. Sonst könnte man keine Zeilennummer verwenden die als 16-Bit-Wert ein Nullbyte enthält, insbesondere nicht die Zeilennummer 0, die sogar zu *zwei* Nullbytes wird. Und es dürfte keine BASIC-Zeile an einer Speicheradresse beginnen deren High- oder Lowbyte Null ist. Womit dann auch klar ist wie man völlig legal drei Nullbytes hinbekommt: Eine Zeile mit der Zeilennummer 0 die so im Speicher platziert ist, dass das Lowbyte der Adresse der nächsten Zeile 0 ist.

Aber wie schon gesagt, die Leute stellen manchmal die abenteuerlichsten Sachen an. Die Beschreibung der Spezifikation sagt ja zum Beispiel nichts darüber aus, dass die Zeilennummern in jeder Zeile verschieden sein müssen. Man kann auch einfach allen Zeilen die kein Sprungziel sind, die Zeilennnummer 0 verpassen. Damit steigt die Wahrscheinlichkeit drei Nullbytes in den Daten zu bekommen. Das könnte zu Problemen mit Sprunganweisung die im Programm *zurück* springen geben, darum wird das so gelöst, dass die Zeilennummern immer bei einer Zeile die Sprungziel sind, um 1 hochgezählt wird. Sinn davon ist übrigens nicht nur das Bearbeiten zu erschweren, sondern auch Programme kürzer zu machen, denn die Sprungziele werden im Quelltext als Ziffernfolge gespeichert. Kurze Zeilennummern bedeuten also weniger Bytes.

Das '\r' ist ja die Return-Taste. Wie würdest Du das denn eingeben wollen? Wenn Du die betätigst wird ja die Zeile übernommen und in den Speicher geschrieben.

Beim C64 hat man einfach die verschiedenen Dateien in den Speicher geladen. Die Zeiger für das Ende des BASIC-Programms hinter die letzten Daten gesetzt und dann den BASIC-Speicherbefehl aufgerufen.

Wave->Bits macht der C64 sehr ähnlich und diverse TurboTape-Routinen auf dem C64 noch ähnlicher. Die beiden Bitwerte haben jeweils eine Pulslänge zugeordnet, wie beim Dragon.

Kleine Pausen werden auch bei „continuous”-Dateien sein. Technisch bedingt. Bei Datendateien werden die grösser sein, weil das ja wahrscheinlich so läuft, dass es einen Blockpuffer im Speicher gibt, der immer wenn er voll ist geschrieben wird, oder beim lesen immer wenn er leer ist, der nächste Block vom Band geladen wird. Dafür muss vor jedem Block der Bandmotor gestartet und nach dem Block der Motor wieder gestoppt werden. So entstehen an sich schon Lücken, die dann wahrscheinlich noch etwas verlängert werden um Laufwerksunterschiede auszugleichen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Also du meinst 0x00 kann vorkommen z.B. bei GOTO ?

Wenn das wirklich so ist, wie soll ich dann die Zeilen parsen können?
BlackJack hat geschrieben:Wave->Bits macht der C64 sehr ähnlich und diverse TurboTape-Routinen auf dem C64 noch ähnlicher. Die beiden Bitwerte haben jeweils eine Pulslänge zugeordnet, wie beim Dragon.
Stimmt: http://en.wikipedia.org/wiki/Datasette#Physical_coding
Ich hab nur flüchtig auf das Bild dort geblickt und dachte, das würde was mit der Amplitude gemacht werden. Aber offensichtlich geht es da auch nur um die Länge...

Wäre ja interessant, das ganze dann modular auch für den C64 zu bauen.

Ich glaube ich habe auch einen Denkfehler beim umwandeln der WAVE in den Bit-Strom: Denn bei https://github.com/jedie/PyDragon32/blo ... DC.py#L266 suche ich immer nach einer kompletten Sinus-Kurve. Dann schaue ich wie lange die war und unterscheide zwischen 0 und 1

Ich denke das Problem liegt darin, das ich bei falscher even_odd Vorgabe, eine Hälfte der Sinuskurve von z.B. 1 zur anderen Hälfte von 0 zu zähle. In dem Fall kommt natürlich Müll raus.

Somit sollte ich lieber die Zeiten der Halben Sinus-Kurve messen. Also doch einfach die Nulldurchgänge... Doch echte Nulldurchgänge vom Rauschen Unterscheiden, wäre dabei das Problem.

Allerdings kann ich dabei vielleicht bei dem Aktuellen Algorithmus bleiben (Kommt ja nicht auf die Absolut genaue Zeit des Nulldurchgangs an): https://github.com/jedie/PyDragon32/blo ... DC.py#L203

Im Groben: Ein Nulldurchgang ist immer dann geschehen, wenn mehrere MIN_TOGGLE_COUNT Sample-Werte im Positiven oder Negativen Bereich sind.

Vielleicht das ganze in drei hintereinander geschalteten Generatoren:

1. Generator liefert die WAVE-Samples zurück iter_wave_values(): https://github.com/jedie/PyDragon32/blo ... DC.py#L134

2. Generator wartet auf die mehrere MIN_TOGGLE_COUNT Sample-Werte im Positiven oder Negativen Bereich und spuckt dann nur die rohen Zeiten aus.

3. Generator nimmt die Zeiten entgehen und bildet damit den Bit-Stream

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Nein, nicht bei GOTO sondern bei den Zeilennummern und den Pointern auf die nächste Zeile. Da kann im Grunde jeder beliebige Bytewert, also auch die 0 vorkommen. Danach bis zum Ende der Zeile sollte kein Nullbyte mehr vorkommen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:@jens: Nein, nicht bei GOTO sondern bei den Zeilennummern und den Pointern auf die nächste Zeile. Da kann im Grunde jeder beliebige Bytewert, also auch die 0 vorkommen. Danach bis zum Ende der Zeile sollte kein Nullbyte mehr vorkommen.
Achso... Deswegen hab ich dich nicht verstanden... Ja klar, da kommen Null-Bytes vor. Aber das ist nicht das Problem.

Es kommen immer 2-Byte "Line-Pointer" dann 2-Byte "Zeilen-Nummer" dann X-Bytes Code bis 0x00

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Ja, aber wenn Du das *so* verarbeitest, an welcher Stelle suchst Du denn dann nach drei Nullbytes? Darum ging es doch. Das Ende erkennt man in dem Fall doch an einem Nullpointer, also *zwei* Nullbytes, und auch nicht ohne Kontext, sondern in dem Wissen, dass man sich gerade am Anfang von den Daten einer neuen Zeile befindet. Und das hat ja schon alles nichts mehr mit dem Lesen vom Band zu tun.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wüßte nicht wie man es anders machen kann.

An dieser Stelle ist das mit den zwei 0x00 Bytes: https://github.com/jedie/PyDragon32/blo ... DC.py#L699

Siehe auch das Beispiel von hier: http://www.python-forum.de/viewtopic.ph ... 35#p244435

EDIT: An dieser Stelle geht es um das Parsen der Roh "tokenized BASIC source code"

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Ähm, der Start dieser (Teil)Diskussion war, dass Du gesagt hast man könnte das Ende eines BASIC-Programms an den drei Nullbytes erkennen und darum wäre die Längenangabe überflüssig. Das Stand mal in diesem Beitrag: http://www.python-forum.de/viewtopic.ph ... 70#p244470
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

jens hat geschrieben:So... um es nochmal klar zu machen, was da überhaupt passiert:

1. WAV Sinus-Kurvenlänge -> Bitstream aus Nullen und Einsen
2. suche nach Muster "10101010" (0x55) Zeichenweise
3. in 8-Bit-Schritten weiter Springen, bis das Muster "10101010" nicht mehr vorkommt.
4. Suchen des Sync-Bits "00111100" (0x3C) Zeichenweise
Hier mal eine Hilfsklasse zum Umwandeln von Bits in Integern, falls du es gebrauchen kannst:

Code: Alles auswählen

import collections

class BitContainer(object):
    def __init__(self, bits=[], max_size=8):
        self._bits = collections.deque(maxlen=max_size)
        self.feed_iterable(bits)

    def __repr__(self):
        return '{}({})'.format(type(self).__name__, self.to_string())

    def __str__(self):
        return self.to_string()

    def __len__(self):
        return len(self._bits)

    def __int__(self):
        return self.to_integer()

    def __iter__(self):
        return iter(self._bits)

    @property
    def bits(self):
        return list(self._bits)

    def feed(self, bit):
        bit = int(bit)
        if not bit in (0, 1):
            raise ValueError('bit must be 0 or 1')
        self._bits.append(bit)

    def feed_iterable(self, bits):
        for bit in bits:
            self.feed(bit)

    def to_integer(self):
        return sum(
            bit << i for i, bit in enumerate(reversed(self._bits))
        )

    def to_string(self):
        return ''.join(map(str, self._bits))
Gedachte Anwendung:

Code: Alles auswählen

bc = BitContainer()
for bit in bitstream:
    bc.feed(bit)
    if int(bc) == 0x55:
        break
Das Ding ist allerdings nicht auf Geschwindigkeit optimiert und ich habe mir erlaubt, nützliche Zusatzfunktionalität mit einzubauen. :)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:@jens: Ähm, der Start dieser (Teil)Diskussion war, dass Du gesagt hast man könnte das Ende eines BASIC-Programms an den drei Nullbytes erkennen und darum wäre die Längenangabe überflüssig. Das Stand mal in diesem Beitrag: http://www.python-forum.de/viewtopic.ph ... 70#p244470
ja, anders herum: Die beiden 0x00 Bytes am Ende sind eigentlich Überflüssig. Denn wenn Block zu Ende, dann BASIC code zuende...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So, hab den ASCII BASIC Parser fertig mit: https://github.com/jedie/PyDragon32/com ... 8130d21cfb

Dann noch die Block-Längenangabe geprüft: https://github.com/jedie/PyDragon32/com ... a821917447

Wobei das ein wenig Witzlos ist, denn ich schneide ja anhand der Block Länge auch wirklich den Code raus. Von daher kann die Länge ja beim parsen nicht anders sein...

@snafu: Deinen Code muß ich mir später ansehen.

Generell weiß ich nicht, wie früh man die Bits in Bytes umformen kann/sollte.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

jens hat geschrieben:Generell weiß ich nicht, wie früh man die Bits in Bytes umformen kann/sollte.
In welchem Zusammenhang meinst du das? Wegen möglichen Fehlinterpretationen, wenn man zu früh umwandelt, oder warum? Falls die Spec nichts anderes vorgibt, dann würde ich doch mal davon ausgehen, dass 1 Byte = 8 Bit.

Falls man sich eh immer solche Päckchen holt, dann braucht man natürlich keinen BitContainer mehr, sondern wirft die Päckchen einfach in eine Funktion ähnlich zu der `.to_integer()`-Methode in meinem Code.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

@snafu: Warum der Umweg über deque?

Code: Alles auswählen

class BitContainer(object):
    def __init__(self, bits=[], max_size=8):
        self.bits = 0
        self._mask = (1 << max_size) - 1
        
        self.feed_iterable(bits)
    
    def feed(self, bit):
        bit = int(bit)
        
        if not bit in (0, 1):
            raise ValueError('bit must be 0 or 1')
        
        self.bits = ((self.bits << 1) | bit) & self._mask
    
    def feed_iterable(self, bits):
        for bit in bits:
            self.feed(bit)
Das Leben ist wie ein Tennisball.
Antworten