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
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

OK, nochmal genauer angesehen.

Wenn ich das richtig sehe macht:

Code: Alles auswählen

for frames in iter(functools.partial(wavefile.readframes, WAVE_READ_SIZE), ''):
das selbe wie:

Code: Alles auswählen

    while True:
        frames = wavefile.readframes(WAVE_READ_SIZE)
        if not frames:
            break
Und das:

Code: Alles auswählen

for value in array.array(typecode, frames):
das selbe wie:

Code: Alles auswählen

        frame_count = len(frames) / samplewidth
        frames = struct.unpack(struct_unpack_str % frame_count, frames)
        for frame in frames:
Sehe ich das richtig?


btw. bei typecode = {1: 'b', 2: 'h', 4: 'i'} muß IMHO 4: 'l' lauten, oder? Wäre ein 32Bit wave file.

EDIT: Ich hab das mal eingepflegt: https://github.com/jedie/python-code-sn ... acd#L0R182

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

@jens: Die Code-Fragmente hast Du richtig interpretiert.

Beim `typecode`-Wörterbuch müsste man eigentlich dynamisch prüfen was die Buchstaben bedeuten, denn in der Dokumentation sind nur Minimalwerte für die Anzahl der Bytes pro Wert angegeben. 'i' ist ``int`` in C und 'l' ist ``long`` in C, was zumindest bei meinem System beides vier Bytes lang ist:

Code: Alles auswählen

In [2]: a = array.array('i')

In [3]: a.itemsize
Out[3]: 4

In [4]: a = array.array('l')

In [5]: a.itemsize
Out[5]: 4
Auf anderen Systemen mag das anders aussehen, und es sagt natürlich alles auch gar nichts darüber aus ob die Reihenfolge der Bytes in einem Wert richtig ist. Das ist also alles ein wenig zerbrechlich. Wenn man sicher sein will muss man mit dem `struct`-Modul arbeiten oder mit `ctypes`-Arrays. Dein Quelltext war an der Stelle also letztendlich sicherer was die Umwandlung der Zahlen angeht.

Edit: `array.array`\s haben ja eine `byteswap()`-Methode, damit könnte man also arbeiten wenn das System die „falsche” „Endianess” hat.

Edit2: Deine ``wav2bas``/``bas2wav`` könntest Du aufteilen in ``(de)tokenize`` und ``bin2wav``/``wav2bin``. Dann könnte man auch andere Daten als BASIC-Programme in WAVs und zurück wandeln.

Wie verhält sich das denn mit Daten? Wurden die auch auf Kassette gespeichert? Hat man auf dem Dragon auch wie auf dem C64 so etwas wie Adressverwaltungen in BASIC geschrieben, die ihre Daten auf Kassetten ablegen? Da der C64 ein sehr ähnliches Format auf der Kassette verwendet, frage ich mich nämlich gerade ob man mit dem jeweiligen Rechner Daten für das andere System lesen und schreiben kann. :-)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Wie verhält sich das denn mit Daten? Wurden die auch auf Kassette gespeichert? Hat man auf dem Dragon auch wie auf dem C64 so etwas wie Adressverwaltungen in BASIC geschrieben, die ihre Daten auf Kassetten ablegen? Da der C64 ein sehr ähnliches Format auf der Kassette verwendet, frage ich mich nämlich gerade ob man mit dem jeweiligen Rechner Daten für das andere System lesen und schreiben kann. :-)
Ja, daten scheint man auch auf Kassette speichern zu können.

Von http://archive.worldofdragon.org/index. ... le_Formats :
...
5.2 A file ID byte where:
00=BASIC program
01=Data file
02=Binary file
5.3 An ASCII flag where:
00=Binary file
FF=ASCII file
...
Wobei IMHO 5.3 eigentlich das heißen sollte:
00=tokenised BASIC
FF=ASCII BASIC
Noch eine andere Frage: Ich hab z.B. das: [0x1e, 0x12] und will daraus das haben= 0x1e12 Wie macht man sowas?
Also 2 8Bit Werte zu einem 16Bit Wert konvertieren?
EDIT: Ah, theoretisch geht das so: hex(0x1e*256+0x12)
EDIT2: Oder so: hex((0x1e<<8) | 0x12)

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

@jens: Zu 5.3: Die Unterscheidung Binär oder ASCII könnte doch auch für Datendateien als Information ganz interessant sein, und nicht nur für BASIC-Programme. Selbst bei Binärprogrammen könnte man auf die Idee kommen die als Hexdump oder ähnlichem zu speichern. :-)

Bezüglich der Wertumwandung gibt es viele Wege. ``xs[0] * 256 + xs[1]``, oder ``(xs[0] << 8) | xs[1]``, und noch die anderen beiden Varianten die man aus ``*``/``<<`` und ``+``/``|`` bilden kann.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab nun endlich die Geschichte mit den Zeilennummern richtig hin bekommen. Aber ich denke das ist viel zu Umständlich:

Code: Alles auswählen

data = (0x1e,0x1a,0x0,0x1,0x87,0x20,0x22,0x4c,0x49,0x4e,0x45,0x20,0x4e,0x55,0x4d,0x42,0x45,0x52,0x20,0x54,0x45,0x53,0x54,0x22,0x0,0x1e,0x23,0x0,0xa,0x87,0x20,0x31,0x30,0x0,0x1e,0x2d,0x0,0x64,0x87,0x20,0x31,0x30,0x30,0x0,0x1e,0x38,0x3,0xe8,0x87,0x20,0x31,0x30,0x30,0x30,0x0,0x1e,0x44,0x27,0x10,0x87,0x20,0x31,0x30,0x30,0x30,0x30,0x0,0x1e,0x50,0x80,0x0,0x87,0x20,0x33,0x32,0x37,0x36,0x38,0x0,0x1e,0x62,0xf9,0xff,0x87,0x20,0x22,0x45,0x4e,0x44,0x22,0x3b,0x36,0x33,0x39,0x39,0x39,0x0,0x0,0x0)

def to16bit(values):
    return (values[0]<<8) | values[1]

def list2generator(l):
    for item in l:
        yield item

def get_from_generator(g, count):
    result = []
    for i in xrange(count):
        try:
            result.append(g.next())
        except StopIteration:
            break
    return result

def get_until(g, until):
    result = []
    while True:
        try:
            item = g.next()
        except StopIteration:
            return result
        result.append(item)
        if item == until:
            return result

def debug_with_ascii(data):
    char_list = []
    txt = ""
    for no in data:
        if no in (32,34) or (no>=48 and no<=90):
            txt += chr(no)
        else:
            if txt:
                char_list.append(txt)
                txt = ""
            char_list.append(str(hex(no)))
    print ",".join(char_list)

g = list2generator(data)
while True:
    line_pointer = get_from_generator(g, 2)
    if line_pointer == [0x00, 0x00]:
        print "---EOF---"
        break

    line_pointer = to16bit(line_pointer)
    print "line Pointer:", line_pointer

    line_number = get_from_generator(g, 2)
    line_number = to16bit(line_number)
    print "line number:", line_number

    line_code = get_until(g, 0x00)
    debug_with_ascii(line_code)

    print "---------"
Ausgabe:

Code: Alles auswählen

line Pointer: 7706
line number: 1
0x87, "LINE NUMBER TEST",0x0
---------
line Pointer: 7715
line number: 10
0x87, 10,0x0
---------
line Pointer: 7725
line number: 100
0x87, 100,0x0
---------
line Pointer: 7736
line number: 1000
0x87, 1000,0x0
---------
line Pointer: 7748
line number: 10000
0x87, 10000,0x0
---------
line Pointer: 7760
line number: 32768
0x87, 32768,0x0
---------
line Pointer: 7778
line number: 63999
0x87, "END";63999,0x0
---------
---EOF---
Zur Info: Der "line pointer" ist irgendwie die Speicher Adresse der code Zeile im RAM, oder so... Brauche ich eigentlich nicht. Zumindest nicht bei WAV2BAS... Später bei BAS2WAV allerdings schon...

Der eigentliche BASIC Code ist der:

Code: Alles auswählen

1 PRINT "LINE NUMBER TEST"
10 PRINT 10
100 PRINT 100
1000 PRINT 10000
32768 PRINT 32768
63999 PRINT "END";63999
Aber halt "tokenised BASIC"... Wobei z.B. 0x87 == "PRINT" ist.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

list2generator = iter
get_from_generator = itertools.islice
get_until = itertools.takewhile oder iter(g, until)
Das Leben ist wie ein Tennisball.
BlackJack

@jens: Der Line Pointer ist die Adresse des Line Pointers der nächsten Zeile. Das braucht der BASIC-Interpreter um sich von Zeile zu Zeile zu bewegen.

Dieses Grundformat sieht beim C64-BASIC genauso aus. Und es ist furchtbar ineffizient, weil ein ``GOTO 4711`` bedeuten, dass um das auszuführen um Ernstfall tatsächlich von der ersten Zeile an linear gesucht wird wo denn Zeile 4711 im Programm ist. Ernstfall bedeutet das GOTO steht in einer Zeile nach Zeile 4711. Wenn es davor steht, kann linear ab der aktuellen Zeile gesucht werden. Wegen diesem Format ist ein Tipp zur Laufzeitsteigerung auch Unterroutinen absteigend nach Häufigkeit des Aufrufs an den Programmanfang zu packen, weil dann die Aufrufe schneller sind, da die Zeilen schneller gefunden werden.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

EyDu hat geschrieben:list2generator = iter
get_from_generator = itertools.islice
get_until = itertools.takewhile oder iter(g, until)
Danke!

'get_until' ersetzten habe ich aber nicht geschafft. Dafür hab ich es nun anders gemacht:

Code: Alles auswählen

import itertools


def to16bit(values):
    return (values[0]<<8) | values[1]


def get_until(g, until):
    while True:
        item = g.next()
        if item == until:
            raise StopIteration()
        yield item


def get_16bit_char(g):
    values = itertools.islice(g, 2)
    values = list(values)
    return (values[0]<<8) | values[1]


class CodeLine(object):
    def __init__(self, line_pointer, line_no, code):
        assert isinstance(line_no, int), "Line number not integer, it's: %s" % repr(line_no)
        self.line_pointer = line_pointer
        self.line_no = line_no
        self.code = code

    def __repr__(self):
        return "<CodeLine pointer: %s line no: %s code: %s>" % (
            repr(self.line_pointer), repr(self.line_no), repr(self.code)
        )


class CodeLines(object):
    def __init__(self,data):
        self.code_lines = []

        while True:
            line_pointer = list(itertools.islice(data, 2))
            if line_pointer == [0x00, 0x00]:
                # end of block
                break
            line_pointer = to16bit(line_pointer)

            line_number = get_16bit_char(data)

            code = list(get_until(data, 0x00))

            self.code_lines.append(
                CodeLine(line_pointer, line_number, code)
            )

data = iter(
    (0x1e,0x1a,0x0,0x1,0x87,0x20,0x22,0x4c,0x49,0x4e,0x45,0x20,0x4e,0x55,0x4d,0x42,0x45,0x52,0x20,0x54,0x45,0x53,0x54,0x22,0x0,
    0x1e,0x23,0x0,0xa,0x87,0x20,0x31,0x30,0x0,
    0x1e,0x2d,0x0,0x64,0x87,0x20,0x31,0x30,0x30,0x0,
    0x1e,0x38,0x3,0xe8,0x87,0x20,0x31,0x30,0x30,0x30,0x0,
    0x1e,0x44,0x27,0x10,0x87,0x20,0x31,0x30,0x30,0x30,0x30,0x0,
    0x1e,0x50,0x80,0x0,0x87,0x20,0x33,0x32,0x37,0x36,0x38,0x0,
    0x1e,0x62,0xf9,0xff,0x87,0x20,0x22,0x45,0x4e,0x44,0x22,0x3b,0x36,0x33,0x39,0x39,0x39,0x0,
    0x0,0x0)
)

code_lines = CodeLines(data)
for line in code_lines.code_lines:
    print repr(line)

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:

Nun auch im git: https://github.com/jedie/python-code-sn ... af69545baa
Dazu habe ich noch eine WAVE Datei gepackt, mit dem test code:

Code: Alles auswählen

1 PRINT "LINE NUMBER TEST"
10 PRINT 10
100 PRINT 100
1000 PRINT 1000
10000 PRINT 10000
32768 PRINT 32768
63999 PRINT "END";63999
Btw. 63999 ist wohl die höchste Zeilennummer beim Dragon 32. Weiß nicht ob der Dragon 64 mehr kann...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

jens hat geschrieben:'get_until' ersetzten habe ich aber nicht geschafft.

Code: Alles auswählen

>>> for i in itertools.takewhile(lambda x: x!=4, xrange(1000)):
...     print i                                                                                                                       
...                                                                                                                                   
0
1
2
3
get_until mit iter zu lösen ist in diesem Fall wohl etwas umständlicher, da, im Fall eines vorhandenen sentinels, iter ein Callable erwartet.

Könntest du vielleicht noch die ewig langen Zeilen an Daten umbrechen? Ich kann es zwar lesen wenn ich meinen Browser auf beide Monitore ausdehne, aber das ist doch etwas unpraktisch :-)


Edit: gut, schön ist anders:

Code: Alles auswählen

>>> for i in iter(iter(xrange(1000)).next, 4):
...     print i
... 
0
1
2
3
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

btw. hab meine Sourcen all in ein separates git repo gepackt -> https://github.com/jedie/PyDragon32

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

@jens: `get_16bit_char()` ist ein bisschen eigenartig als Name weil `char` ja eher mit Byte assoziiert wird. Insbesondere wenn es um die guten alten 8-Bit-Rechner geht. Besser wäre IMHO `get_uint16()` oder im Kontext von den 8-Bittern `get_word()`.

Es ist auch ein wenig umständlich da erst mit `islice()` einen Iterator zu erstellen, dessen zwei Elemente in eine Liste zu stecken und dann damit zu rechnen:

Code: Alles auswählen

def get_word(byte_iterator):
    return (next(byte_iterator) << 8) | next(byte_iterator)
Die `to16bit()` kann man sich sparen wenn man auch beim lesen des Linepointers gleich den 16-Bit-Wert liest und nicht erst zwei einzelne Bytewerte. Auf „nicht Null” kann man auch schöner prüfen als auf eine Liste mit zwei Werten. Es ist an der Stelle sowieso unschön, dass der gleiche Name in der Funktion mal an eine Liste und mal an eine Zahl gebunden wird.

Code: Alles auswählen

class CodeLines(object):
    def __init__(self, data):
        self.code_lines = list()
        while True:
            line_pointer = get_word(data)
            if not line_pointer:
                break
            line_number = get_word(data)
            code = list(iter(data.next, 0))
            self.code_lines.append(
                CodeLine(line_pointer, line_number, code)
            )
63999 ist auch die grösste Zeilennummer beim C64. Das BASIC ist halt ebenfalls von Microsoft. Ich denke nicht das der Dragon 64 mehr kann. Theoretisch ginge natürlich 65535, aber die dachten wohl knapp unter 64000 ist so eine schöne runde Zahl als Grenze. Und wirklich ausschöpfen kann man die ja nicht.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Danek für all die Verbesserungsvorschläge, gerade auch was Namen von Funktionen betrifft! Bin da nicht im Thema...

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...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Und bitte (, bitte, bitte, bitte) setze dich endlich mit Zahlensystem auseinander! Du wandelst ständig zwischen Einsen und Nullen als String, einzelnen Bytes, Strings aus Bytes und Listen von diesen, hin und her. Das kann man alles viel einfacher, sauberer und schneller lösen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack Vorschläge: https://github.com/jedie/PyDragon32/com ... 82ff51ddc1

@EyDu: Ja, wenn ich das besser wüste, könnte ich das besser machen ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
snafu
User
Beiträge: 6740
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.
Antworten