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

@jens: Also als erstes aufgefallen ist mir das bei `in_negative` und `in_positive` doch wohl eins überflüssig ist, weil die doch immer entgegengesetzte Werte haben.

Ich hätte vom Ansatz her wahrscheinlich erst einmal die Sampledaten in eine Folge von Abständen von Nulldurchgängen umgewandelt. In Sekunden. Das dürfte die Datenmenge in der man dann die Sinusschwingungen sucht erst einmal ordentlich reduzieren. Die quantisiert man dann und schon sollten 0en und 1en recht leicht erkennbar sein. Theoretisch. Ob's praktisch so einfach ist, müsste man mal ausprobieren.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Einfach nur die Null-Durchgänge zählen dürfte IMHO nicht funktionieren, wenn Rauschen im WAVE drin ist.

Wobei ich sowas in der Art mache: Ich zähle wie viel Werte im Positiven/Negativen Bereich sind und werte das dann als Positive/Negative Teil-Sinuskurve.

Die Idee: Ist es Rauschen, dann würde es keine kontinuierlichen Positiv/Negativ Werte geben (Es sei denn es ist ein Rauschen von sehr niedriger Frequenz)

Eine ältere Idee, die ich am Anfang implementiert hatte: Es muß eine Bestimmte Anzahl von Sample-Werten kontinuierlich größer oder kleiner werden -> Anteigende-/Abfallende-Flange der Sinus-Kurve.

Später hatte ich mir gedacht, das Rauschen dabei zu verfälschten Werten kommen kann.

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

@jens: Aus dem Abständen der Nulldurchgänge könnte man solche die deutlich unter 0,4 Millisekunden liegen einfach verwerfen. Oder halt einen Filter anwenden. Das wird wahrscheinlich das sein was beim Dragon gemacht wird, allerdings in Hardware.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Man könnte sich auch ein wenig in Signalverarbeit einlesen und einfach einen der bewährten Ansätze nehmen. Angeblich gibt es dieses Problem schon seit mehreren Jahrzehnten, wurde ausgibig erforscht und zufriedenstellend gelöst ;-)
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

EyDu hat geschrieben:Man könnte sich auch ein wenig in Signalverarbeit einlesen und einfach einen der bewährten Ansätze nehmen. Angeblich gibt es dieses Problem schon seit mehreren Jahrzehnten, wurde ausgibig erforscht und zufriedenstellend gelöst ;-)
Hast du Suchwörter für mich? Kenne mich da so garnicht aus.

@BlackJack: Stimmt, das verwerfen von offensichtlich falschen Daten wäre eine Idee.


Btw. nun an einem anderen Rechner, dauert das eigentliche Einlesen der WAVE und struct.unpack, auch richtig lang. Fast genauso lang wie das anschließende Auswerten. :shock:
Konkretes Beispiel: Einlesen+unpack: 31Sec - umwandeln 39sec.

lesen und unpack sieht zusammen gekürzt so aus:

Code: Alles auswählen

WAVE_READ_SIZE = 16 * 1024 # How many frames should be read from WAVE file at once?
WAV_UNPACK_STR = {
    1: "<%db", #  8-bit wave file
    2: "<%dh", # 16-bit wave file
    4: "<%dl", # 32-bit wave file TODO: Test it
}

def iter_wave_values(wavefile):
    nchannels = wavefile.getnchannels() # typically 1 for mono, 2 for stereo
    assert nchannels == 1, "Only MONO files are supported, yet!"
    samplewidth = wavefile.getsampwidth() # 1 for 8-bit, 2 for 16-bit, 4 for 32-bit samples

    try:
        struct_unpack_str = WAV_UNPACK_STR[samplewidth]
    except KeyError:
        raise NotImplementedError(
            "Only %s wave files are supported, yet!" % ", ".join(["%sBit" % (i * 8) for i in WAV_UNPACK_STR.keys()])
        )

    frame_no = 0
    while True:
        frames = wavefile.readframes(WAVE_READ_SIZE)
        if not frames:
            break

        frame_count = len(frames) / samplewidth
        frames = struct.unpack(struct_unpack_str % frame_count, frames)
        for frame in frames:
            frame_no += 1
            yield frame_no, frame
Was ich generell noch nicht weiß: z.Z. lese ich als Generator aus, konvertiere später allerdings doch mit list() alles wieder in einem zusammen.

Die Ursprüngliche Idee war, alles als Generator-Strom zu verarbeiten. Dann könne man frühzeitig stoppen, falls Müll kommt. Oder z.Z. auch umschalten, falls Even/Odd verkehrt ist.

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

Es gibt doch sicherlich auch Bibliotheken, die Funktionalität zur effizienten Verarbeitung von Audiosignalen liefern. Vielleicht sogar NumPy bzw SciPy? Irgendwas in dieser Richtung sollte man IMHO definitiv zur Unterstützung bei solchen Aufgaben verwenden.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Andere Bibliotheken nutzten wäre sicherlich eine Option. Doch vorher will ich bei Batteries-Included bleiben und lieber mal PyPy testen.

Ich hab nun mal audioop.ratecv ( http://docs.python.org/2/library/audioo ... oop.ratecv ) ausprobiert. Allerdings mit keinem Erfolg, siehe: http://www.python-forum.de/viewtopic.ph ... 77#p244377

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 schnell mal ein test mit PyPy gemacht. Was soll ich sagen, damit muß man nix optimieren :shock:

CPython: Lesen+unpack: 1,1Sec - decodieren: 4,3Sec
PyPy: Lesen+unpack: 1,4Sec - decodieren: 958ms

Allerdings braucht der PyPy Interpreter deutlich länger zum Starten...

Leider gibt es das audioop noch nicht in PyPy... Man findet dazu allerdings kaum Informationen. Einzig diesen Eintrah im Bug-Tracker: http://bugs.pypy.org/msg4430 (Mit ungültigem https Zertifikat :( )

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:

Gerade nochmal ein Test gemacht, ob ich nun eine normale Liste [1,1,1,0,0,0...] nehme oder array.array('B') ist von der Laufzeit her egal.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

jens hat geschrieben:...Hast du Suchwörter für mich? Kenne mich da so garnicht aus. ...
@jens:
Stichwörter zum Thema DSP und unerwünschte Frequenzen/Störungen wären Hoch-/Tief-/Bandpassfilter oder auch -stopfilter. scipy bringt da schon einiges mit. Da so ziemlich alles, was über eine simple Glättung hinausgeht, auf FFT setzt, will man das nicht wirklich mit reinen Pythonboardmitteln machen.
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich vermute mal, jens will die relativ "fette" Abhängigkeit von Numpy + Scipy vermeiden.

@jens: Falls dieses audioop die zeitkritischen Programmteile intern schafft, dann würde ich das einfach benutzen und sagen, dass dein Programm eben nur mit CPython funktioniert. Ich würde den Nutzer jedenfalls nicht zu PyPy zwingen wollen, wenn er eine akzeptable Laufzeit haben möchte.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@jens/snafu:
Naja - falls die Abhängigkeiten zu numpy & Co stören, wie wärs mit Abtrennen des Wave-Decoders vom "Interpreter"? Ich hatte den Eingangspost so verstanden, dass Du möglichst auch olle Kasetten digitalisieren können möchtest. Ohne FFT-Aufarbeitung würde Dein Decoder hier aber schon an einem "leierndem" Kasettenteil scheitern, da Du die Frequenzabweichung nicht "siehst" bzw. korrigieren könntest und die Konvertierung fehlschlägt.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Alte Kassetten hab ich eh nicht so viele ;) Vor dem einspielen eigener Kassetten kann man mal bei http://archive.worldofdragon.org/archiv ... es/Dragon/ nachsehen, ob die nicht schon dort abgelegt wurde. Oder schaut um Upload Forum nach: http://archive.worldofdragon.org/phpBB3 ... um.php?f=7

Denke die Grundaufbereitung kann man auch im richtigen Audio Editor machen. Rauschen sollte man recht effektiv raus filtern können, indem man einfach alles außer 1200Hz und 2400Hz leiser macht.

Das größte Problem, denke ich, ist nicht das Rauschen, sondern beim echten Kassetten-Rekorder die Laufschwankungen.

Die Erkennung hab ich mit https://github.com/jedie/python-code-sn ... fd8#diff-1 eh ein wenig umgestellt. Nun kann man diese Werte Angeben:

Code: Alles auswählen

BIT_ONE_HZ = 2400 # "1" is a single cycle at 2400 Hz
BIT_NUL_HZ = 1200 # "0" is a single cycle at 1200 Hz
MAX_HZ_VARIATION = 1000 # How much Hz can signal scatter to match 1 or 0 bit ?
Die ersten beiden sind eigentlich fest nach der Spezifikation.
Mit MAX_HZ_VARIATION gibt man an, wie weit sich die gemessene Frequenz von der Spezifizierten abweichen darf. Ist es zu weit weg, wird die Stelle ignoriert. Mit +/- 1000Hz hab ich ganz gute Erfahrungen bisher sammeln können.


Btw. mein eigentliches Ziel wäre auch ehr ein wav2bas und bas2wav... Also aus den Audio Daten einfache BASIC-Textdateien zu machen und davon wieder WAV Dateien zu erzeugen.
So könnte man den Basic Code auf dem PC bequem editieren, archivieren und hin und her austauschen.

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:

Ach, darüber hinaus gibt es noch das sog. CAS Format. Was in etwas ein Zwischending aus WAV<->BAS ist. Ich glaube fast der Bitstream aus dem WAV in binäre Form speichern. Informationen dazu gibt es hier: http://archive.worldofdragon.org/index. ... le_Formats

Im Übrigen könnte es sein, das man das selbe, mit leichten Anpassungen auch für andere Kassetten-Formate wie z.B. vom Commodore C64 gebrauchen könnte. Ich hatte nur nie einen, von daher hab ich wenig Interesse daran.

EDIT: Ich sehe gerade, das was beim Umwandeln von den WAVE-Samples in Bits passiert, nennt man Schmitt-Trigger, siehe: http://de.wikipedia.org/wiki/Schmitt-Trigger
Im Grunde ist es ähnlich was Anfänglich Akustikkoppler und Modems machten...

EDIT2: Als Suchwörter wäre wohl Frequency-shift keying (FSK) btw. binary FSK (BFSK) das richtige, siehe: http://en.wikipedia.org/wiki/Audio_freq ... ift_keying

EDIT3: Wenn eine externe Lib, dann wäre evtl. GNU-Radio das richtige?
-> http://gnuradio.org/redmine/projects/gn ... plications

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

@jerch: Ich denke man kann viel primitiver arbeiten. Die Signale von diesen Geräten sind ziemlich grob aufgelöst, die müssen also schon ziemlich stark abweichen oder leiern bis man sie nicht mehr eindeutig zuordnen kann. Die antike Hardware musste ja schon mit sehr einfachen Mitteln mit relativ starken Abweichungen klar kommen, weil jedes Bandlaufwerk mit einer geringfügig anderen Geschwindigkeit läuft, es viele Selbstbauprojekte und Dritthersteller gab, und ab und zu auch Datenkassetten mit „Ghettoblastern” kopiert wurden. Letztendlich muss eine 8-Bit/1 Mhz CPU die Daten dekodieren können.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Stimmt. Deswegen will ich eigentlich auf externe Libs verzichten. Zumal man ja auch eine Audio-Editor zum aufbereiten nehmen könnte...

Das was ich bisher hab, funktioniert ja auch schon recht brauchbar.

Der einzige Knackpunkt ist noch, den richtigen Einstieg zu finden. Halt die Variable even_odd bei mir, siehe: https://github.com/jedie/python-code-sn ... de.py#L234

Evtl. hat sich das schon erledigt, wenn man die Stille am Anfang überspringt und als Startpunkt die ersten Ansteigenden-Flange der Sinuskurve hat.

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

@jens: Für den C64 gibt es das TAP-Format. Das ist scheinbar näher an den WAV-Daten als das CAS-Format, denn es speichert keine Bits sondern die Länge der „pulses”, also Zeitabstände zwischen den Nulldurchgängen. Welche Zeitabstände oder Kombinationen davon ein Bit beschreiben ist nicht wirklich fest definiert, weil es beim C64 neben I/O-Routinen im ROM viele verschiedene Software-Loader gibt, die alle ihr eigenes Süppchen kochen und Anforderungen zwischen schneller als das ROM und Kopierschutz erfüllen wollen.
BlackJack

Ich habe mal den C-Code zum WAV-Tape-lesen aus ``xroar`` in Python umgesetzt: http://pastebin.com/Ykt0AWKY

Interessanterweise funktioniert es *nicht* mit dem Beispiel das mit ``xroar`` gespeichert wurde, aber mit Jens' Aufnahme vom Originalgerät scheint das Ergebnis zu passen. :shock:

Edit: Die `main()` sollte in sauber und ordentlich natürlich so aussehen:

Code: Alles auswählen

def main():
    #filename = 'HelloWorld1 xroar.wav'
    filename = 'HelloWorld1 origin.wav'
    with Tape(filename) as tape:
        print tape.block_in()
        print tape.block_in()
        print tape.block_in()
Edit2: Fehler gefunden: Das `xroar` ist in 8-Bit *unsigned* gespeichert, da muss man also erst noch einen Korrekturwert draufschlagen:

Code: Alles auswählen

def iter_samples(wave):
    samplewidth = wave.getsampwidth()
    try:
        typecode = {1: 'b', 2: 'h', 4: 'i'}[samplewidth]
    except KeyError:
        raise ValueError('Unsupported samplewidth: {0}'.format(samplewidth))
    for frames in iter(partial(wave.readframes, READ_FRAMES_PER_BLOCK), ''):
        if samplewidth == 1:
            frames = bias(frames, 1, 127)
        for value in array(typecode, frames):
            yield value
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So einfach kann es gehn? :shock:

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Die Bits gleich von links reinschieben ist natürlich sinnvoller :)

Ich hab mir mal die Frequenzspektren von ein paar der Dateien von worldofdragon angeschaut, die sind eigentlich gut trennbar. Mit einem Gangunterschied von f zu 2*f ist ja einiges an Toleranz auch für leiernde Kassettendecks vorhanden.
Antworten