@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.
Dragon 32 Homecomputer Kassetten in ASCII umwandeln...
- 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.
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.
@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.
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.
- jens
- Python-Forum Veteran
- Beiträge: 8502
- Registriert: Dienstag 10. August 2004, 09:40
- Wohnort: duisburg
- Kontaktdaten:
Hast du Suchwörter für mich? Kenne mich da so garnicht aus.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
@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.

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

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

@jens:jens hat geschrieben:...Hast du Suchwörter für mich? Kenne mich da so garnicht aus. ...
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.
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.
@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.
@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.
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.
- 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:
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.

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 ?
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.
- 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
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
@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.
- 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.
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.
@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.
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.
Edit: Die `main()` sollte in sauber und ordentlich natürlich so aussehen:
Edit2: Fehler gefunden: Das `xroar` ist in 8-Bit *unsigned* gespeichert, da muss man also erst noch einen Korrekturwert draufschlagen:
Interessanterweise funktioniert es *nicht* mit dem Beispiel das mit ``xroar`` gespeichert wurde, aber mit Jens' Aufnahme vom Originalgerät scheint das Ergebnis zu passen.

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

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.